Help us shape the future of Go! Take the 2026 Go Developer Survey

usecase

package
v0.45.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 27, 2026 License: AGPL-3.0 Imports: 43 Imported by: 0

Documentation

Overview

Package usecase holds the use cases of ermite. It depends on the domain and on the ports only.

Index

Constants

View Source
const (
	// DefaultHistoryCacheEntries bounds the cached answers.
	DefaultHistoryCacheEntries = 256
	// DefaultHistoryCacheBytes bounds their estimated size.
	DefaultHistoryCacheBytes = 8 << 20
)

Default bounds of the history cache of Browse.

View Source
const (
	// DefaultCIPollTimeout is how long FetchJob holds a call: the contract's 50 s.
	DefaultCIPollTimeout = 50 * time.Second
	// DefaultCITick is how often the watchdog looks at the jobs in flight.
	DefaultCITick = time.Second
	// DefaultCIKeepalive is how often WatchJob sends a keepalive.
	DefaultCIKeepalive = 30 * time.Second
	// DefaultCITriggerQueue is the size of the queue of pushes to plan.
	DefaultCITriggerQueue = 256
	// DefaultCIRemoteConcurrency is the git ls-remote processes ResolveActions
	// runs at once, whatever the number of jobs that resolve.
	DefaultCIRemoteConcurrency = 4
	// DefaultCIResolveTimeout bounds one ResolveActions call.
	DefaultCIResolveTimeout = 60 * time.Second
	// DefaultCIConcurrencyPruneBatch is the stale marks of a concurrency group a
	// transaction removes at most.
	DefaultCIConcurrencyPruneBatch = 256
	// MaxCIChecks is the most checks ListChecks returns.
	MaxCIChecks = 1000
	// MaxCILogStreams and MaxCILogStreamsPerClient bound the concurrent log
	// streams (GetJobLog).
	MaxCILogStreams          = 64
	MaxCILogStreamsPerClient = 8
)

Defaults of the CI engine.

View Source
const (
	// DefaultCIPullRefreshes bounds the merge requests (and the pushes) waiting
	// for the refresh worker. Past it the repository is marked dirty instead and
	// the worker rescans its open merge requests when it has capacity, so that
	// nothing stays stale for good.
	DefaultCIPullRefreshes = 4096
	// DefaultCIPullSnapshot bounds the merge requests one push takes the snapshot
	// of (the open requests whose source branch it pushed). Past it the rest is
	// skipped, an error is logged, and the repository is rescanned for the refs.
	DefaultCIPullSnapshot = 256
)
View Source
const (
	// DefaultPullFilesPerPage and MaxPullFilesPerPage are GitHub's page sizes.
	DefaultPullFilesPerPage = 30
	MaxPullFilesPerPage     = 100
)
View Source
const (
	// DefaultCIScheduleTick is how often the scheduler looks at its clock.
	DefaultCIScheduleTick = time.Second
	// DefaultCIScheduleBudget is the repositories the scheduler handles at most
	// per tick.
	DefaultCIScheduleBudget = 64
)
View Source
const (
	// MaxWatchTopics is the most topics one watch may follow.
	MaxWatchTopics = 64
	// MaxWatchers is the most watches in progress at once.
	MaxWatchers = 256
	// MaxWatchersPerClient is the most watches in progress for one client
	// address (see ports.WatchParams.Client).
	MaxWatchersPerClient = 16
	// DefaultRevalidateEvery is how long a watcher with a stored token goes
	// without asking the token store whether the token still exists.
	DefaultRevalidateEvery = 5 * time.Second
)

Limits of the watchers.

View Source
const (
	// TopicHome follows every repository the caller may see.
	TopicHome = "home"
	// TopicOwner follows the merge requests and issues of every repository,
	// for the owner only (the inbox). Anyone else is told it is an unknown
	// topic.
	TopicOwner = "owner"
)

Topics a watcher may follow.

View Source
const (
	// DefaultPageSize is the page size when the caller gives none.
	DefaultPageSize = 50
	// MaxPageSize is the largest page; a larger request is lowered to it.
	MaxPageSize = 100
	// MaxRepoPageSize is the largest page of ListRepos. A repository list is
	// one scan of the metadata whatever the page, so a caller that wants the
	// whole list (the web pages) asks for it in one page rather than in
	// several scans.
	MaxRepoPageSize = 1000
	// MaxRefPageSize is the largest page of ListRefs, for the same reason:
	// each page of refs runs the whole for-each-ref again.
	MaxRefPageSize = 1000
)

Page sizes of the listings.

View Source
const (
	// MaxPendingPasskeyRegistrations bounds the registrations that wait to be
	// finished. Only the owner starts one.
	MaxPendingPasskeyRegistrations = 16
	// MaxPendingPasskeyLogins bounds the logins that wait to be finished.
	// Anyone may start one, so a login is never refused: on overflow one is
	// dropped instead, and anonymous callers cannot lock the owner out.
	MaxPendingPasskeyLogins = 256
	// MaxPendingPasskeyLoginsPerClient bounds the logins one client (one client
	// key) may have waiting. A client over it loses its own oldest.
	MaxPendingPasskeyLoginsPerClient = 8
)

Owner passkeys (ADR 0026). A ceremony is a registration or a login that the browser carries out between two requests; its data lives in memory only.

View Source
const (
	// LoginLinkTTL is how long a login link works.
	LoginLinkTTL = 60 * time.Second
	// MaxPendingLoginLinks bounds the links that wait to be used, so that a
	// loop of CreateLoginLink cannot grow the memory.
	MaxPendingLoginLinks = 16
	// SessionTTL is the lifetime of a session.
	SessionTTL = 12 * time.Hour

	// LoginPath is the path prefix of login links on the public URL.
	LoginPath = "/-/login/"
)

Web login (ADR 0018). The owner asks for a one-time link with the API, opens it in a browser and confirms; the confirmation trades the link for a signed session token that the web keeps in an HttpOnly cookie.

View Source
const DefaultCIPullAttempts = 4096

DefaultCIPullAttempts bounds the merge requests whose last conflicting merge is remembered.

View Source
const DefaultEventBuffer = 2048

DefaultEventBuffer is the number of events the Events use case remembers. The owner-only signals share the ring with the public events: a push to a default branch buffers a PushEvent and a JournalUpdated, so the ring holds about 1024 such pushes for the home and repository streams.

View Source
const DefaultMirrorSyncWait = 8 * time.Second

DefaultMirrorSyncWait is how long a manual sync holds its caller before answering "queued": under the 10 s the edge proxy allows a response, and well under the client timeout.

View Source
const MaintenanceDelay = 5 * time.Second

MaintenanceDelay is how long maintenance waits after the first push of a burst, so that pushes in quick succession share one run.

View Source
const MaxCommentsPerIssue = 1000

MaxCommentsPerIssue is the most comments an issue holds. Past it AddComment fails with domain.ErrResourceExhausted. Comments are never deleted, so the limit counts the ones ever added.

View Source
const MaxCommentsPerMergeRequest = 1000

MaxCommentsPerMergeRequest is the most comments a merge request holds. Past it AddComment fails with domain.ErrResourceExhausted (resource_exhausted in the API). Comments are never deleted, so the limit counts the ones ever added.

View Source
const MaxCommitSkip = 100_000

MaxCommitSkip bounds how deep into a history a page token may point: each page walks the history from its start with `git log --skip`.

View Source
const MaxDescriptionLen = 500

MaxDescriptionLen is the longest repository description, in bytes.

View Source
const MaxIssueLinkCommits = ports.MaxMergeRequestCommits
View Source
const MaxIssueLinksPerIssue = 100
View Source
const MaxMergeRequestCommits = ports.MaxMergeRequestCommits

MaxMergeRequestCommits is the most commits GetMergeRequest returns.

View Source
const MaxSignatureCacheEntries = 10000

MaxSignatureCacheEntries bounds the verification results kept in memory.

View Source
const MaxTokenLabelLen = 100

MaxTokenLabelLen is the longest token label, in bytes.

View Source
const OIDCKeyBits = 3072

OIDCKeyBits is the size of the RSA signing keys.

View Source
const OIDCPath = "/-/oidc"

OIDCPath is where the provider is served under the public URL; the issuer is the public URL followed by it.

View Source
const RunnerOnlineWindow = 2 * time.Minute

RunnerOnlineWindow is how long after its last sign of life (a poll or a heartbeat) a runner is online, by the server's clock.

Variables

This section is empty.

Functions

This section is empty.

Types

type Auth

type Auth struct {
	// contains filtered or unexported fields
}

Auth authenticates secrets presented by callers.

func NewAuth

func NewAuth(rootHash domain.TokenHash, tokens ports.TokenStore, clock ports.Clock) *Auth

NewAuth builds the authentication use case. rootHash is the SHA-256 of the admin token.

func (*Auth) Authenticate

func (a *Auth) Authenticate(ctx context.Context, secret string) (domain.Principal, error)

Authenticate resolves a token secret to its principal: root when it is the admin token (constant-time comparison of the hashes), else the stored token with that hash if it has not expired. Any other secret, including the empty one, gives domain.ErrUnauthenticated. last_used_at of a stored token is written at most once a minute; a failure to write it is logged, not returned.

func (*Auth) Revalidate

func (a *Auth) Revalidate(ctx context.Context, p domain.Principal) error

Revalidate checks that the holder of a principal obtained earlier is still authorized: root always is; a stored token must still exist and not have expired. It gives domain.ErrUnauthenticated otherwise, and any error of the store as it is, so that callers fail closed. It is for long operations that outlive the request that authenticated them, such as a push.

func (*Auth) WithRepoTokens added in v0.9.0

func (a *Auth) WithRepoTokens(s ports.CIRepoTokenStore) *Auth

WithRepoTokens makes Authenticate accept the job repository tokens of CI (the "github.token" of a job, ADR 0025). They resolve to a principal shaped like a stored token (repo:read on one repository, with an expiry), so no rule of the forge knows them; they are stored apart from the stored tokens, so ListTokens never shows them and token:admin never manages them. Call it once while wiring.

type AutoMerge added in v0.31.0

type AutoMerge struct {
	// contains filtered or unexported fields
}

AutoMerge serializes evaluation within each repository. Triggers only enqueue work, including when invoked while CI or merge-request locks are held.

func NewAutoMerge added in v0.31.0

func NewAutoMerge(ctx context.Context, cfg AutoMergeConfig) *AutoMerge

NewAutoMerge builds an evaluator; call Start once, and Close before MRs.Wait.

func (*AutoMerge) Close added in v0.31.0

func (s *AutoMerge) Close(_ context.Context)

Close cancels workers and pending retry waits, then waits for them.

func (*AutoMerge) OnAutoMergeEnabled added in v0.31.0

func (s *AutoMerge) OnAutoMergeEnabled(_ context.Context, repo domain.RepoName)

OnAutoMergeEnabled remembers an intent before queuing evaluation.

func (*AutoMerge) OnRepoChanged added in v0.31.0

func (s *AutoMerge) OnRepoChanged(_ context.Context, repo domain.RepoName)

OnRepoChanged marks and wakes only; it never merges on the caller's stack.

func (*AutoMerge) Start added in v0.31.0

func (s *AutoMerge) Start()

Start starts workers and discovers repositories, retrying failed discovery.

type AutoMergeConfig added in v0.31.0

type AutoMergeConfig struct {
	MRs      *MergeRequests
	Store    ports.MergeRequestStore
	Meta     ports.RepoMetaStore
	Git      ports.MergeGit
	Checks   ports.CIChecksReader
	Activity ports.CIActivity
	Tokens   ports.TokenStore
	Clock    ports.Clock
	After    func(time.Duration) <-chan time.Time
}

AutoMergeConfig holds dependencies of the asynchronous merge evaluator.

type Bernard added in v0.40.0

type Bernard struct {
	// contains filtered or unexported fields
}

Bernard checks the dependencies of the repositories every day and keeps the pending updates. It implements ports.DepsService.

func NewBernard added in v0.40.0

func NewBernard(cfg BernardConfig) (*Bernard, error)

NewBernard builds Bernard. It returns domain.ErrInvalid for a missing port or a bad cron expression.

func (*Bernard) Check added in v0.40.0

Check runs the check of one repository now and returns its stored record.

func (*Bernard) CheckDepsReport added in v0.43.0

func (b *Bernard) CheckDepsReport(ctx context.Context, run domain.CIRun, job domain.CIJob, report ports.DepsReport) error

CheckDepsReport checks the same report that asynchronous publication consumes. It reads the record without the repository lock, which publication holds during its git writes: only the state, the run and its updates are needed, and DepsJobEnded checks the record again under the lock (the asynchronous path re-checks on purpose and stays authoritative). An error that wraps domain.ErrInvalid or domain.ErrPreconditionFailed means the report is refused for good; any other error is transient.

func (*Bernard) Close added in v0.40.0

func (b *Bernard) Close(ctx context.Context)

Close stops the schedule and waits for it until ctx ends.

func (*Bernard) DepsJobEnded added in v0.42.0

func (b *Bernard) DepsJobEnded(ctx context.Context, run domain.CIRun, job domain.CIJob, report *ports.DepsReport)

DepsJobEnded applies the outcome of a dependency job that ended: the runner's report is checked and published, or the record fails with a bounded reason (the log is on the run). A run the record left (the owner closed the merge request meanwhile) is dropped. It implements ports.DepsRunObserver.

func (*Bernard) Ignore added in v0.40.0

func (b *Bernard) Ignore(ctx context.Context, p domain.Principal, repo domain.RepoName, expected []domain.DepRef) (domain.RepoDeps, error)

Ignore hides exactly the expected versions from now on.

func (*Bernard) List added in v0.40.0

List returns the records with pending updates, or only repo's.

func (*Bernard) Open added in v0.40.0

func (b *Bernard) Open(ctx context.Context, p domain.Principal, repoName domain.RepoName, expected []domain.DepRef) (domain.RepoDeps, error)

Open opens the Bernard merge request for the expected updates, or rebuilds its branch from the target when it is open already. The updates of an ecosystem that cannot be applied in the server (Go modules) go to a runner as a job: the record is Applying until the runner reports (applyRunnerReport). The others are applied in the server, at once or when the report arrives.

func (*Bernard) ScheduleNow added in v0.40.0

func (b *Bernard) ScheduleNow(ctx context.Context)

ScheduleNow looks at the clock once: when a new minute is due by the cron expression it checks every repository, one after the other, with one registry. A panic is logged, not propagated. It is for the schedule and tests.

func (*Bernard) Snooze added in v0.40.0

Snooze hides the repository's updates for the configured time.

func (*Bernard) Start added in v0.40.0

func (b *Bernard) Start()

Start starts the daily schedule, once.

type BernardConfig added in v0.40.0

type BernardConfig struct {
	Store      ports.DepsStore
	Meta       ports.RepoMetaStore
	Reader     ports.RepoReader
	Ecosystems []ports.DepsEcosystem
	// Registry returns a fresh registry, with its own cache, for each run.
	Registry func() ports.DepsRegistry
	Bus      ports.EventBus
	Clock    ports.Clock
	// CheckAt is the cron expression (UTC) of the daily check.
	CheckAt string
	// Cooldown is how old a version must be to be proposed.
	Cooldown time.Duration
	// Snooze is how long a snooze hides a repository.
	Snooze time.Duration
	// JobTimeout is the time budget of a dependency job once a runner has it
	// (rounded up to the minute); ten minutes when zero. The wait for a runner
	// is CI's queue timeout.
	JobTimeout time.Duration
	// Runs starts and cancels the CI runs of the dependency jobs; may be nil
	// in a Bernard that does not open merge requests.
	Runs ports.DepsRunner
	// RunnerLabel is the runner label that serves the dependency jobs.
	RunnerLabel string
	// Tick is how often the schedule looks at the clock; one minute when zero.
	Tick time.Duration

	// Repos gives the default branch.
	Repos ports.GitRepos
	// Git reads the branches and writes the tree, the commit and the branch.
	Git ports.MergeGit
	// Signer signs Bernard's commits with the instance key.
	Signer ports.CommitSigner
	// Committer is the instance identity.
	Committer domain.Identity
	// Tips records who pushes bernard/deps; may be nil.
	Tips ports.CIRefTipStore
	// CI starts the runs of a push; may be nil.
	CI ports.WorkflowTrigger
	// MRs opens the merge request as Bernard.
	MRs ports.MergeRequestService
	// MRStore tells whether the stored merge request is still open.
	MRStore ports.MergeRequestStore
}

BernardConfig configures Bernard.

type Browse

type Browse struct {
	// contains filtered or unexported fields
}

Browse are the read use cases over the Git data of a repository: refs, trees, blobs, history and commits. Every method applies the access rule first: a repository the principal may not read, or that does not exist, answers domain.ErrNotFound before the Git reader is touched.

func NewBrowse

func NewBrowse(meta ports.RepoMetaStore, reader ports.RepoReader) *Browse

NewBrowse builds the read use cases over their ports, with a history cache of the default bounds.

func (*Browse) GetCommit

GetCommit returns a commit with its statistics and its diff against the first parent, within the default diff limits or, with opts.Files, the limits of that file window. An invalid window gives domain.ErrInvalid.

func (*Browse) GetTree

func (b *Browse) GetTree(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision, path domain.TreePath, includeLastCommit bool) (domain.Tree, error)

GetTree lists a directory at a revision. With includeLastCommit, entries get their last commit, for at most domain.MaxLastCommitEntries of them.

func (*Browse) ListCommits

ListCommits returns a page of history. The page token holds the commit the listing started from, how many commits were already returned and a digest of the path; a token used with another path is invalid.

func (*Browse) ListPaths

func (b *Browse) ListPaths(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision) (domain.PathList, error)

ListPaths lists the paths of every file and symbolic link of a revision, at most domain.MaxPathEntries paths and domain.MaxPathBytes bytes of paths.

func (*Browse) ListRefs

func (b *Browse) ListRefs(ctx context.Context, p domain.Principal, repo domain.RepoName, params ports.ListRefsParams) (ports.RefPage, error)

ListRefs lists HEAD, the branches and the tags, in that order, then by name. The page token names the last ref of the previous page, so refs that appear or disappear between two pages never shift the others.

func (*Browse) OpenBlob

OpenBlob returns the description of a file and a reader of its content. The caller closes the reader.

func (*Browse) RepoStats added in v0.23.0

func (b *Browse) RepoStats(ctx context.Context, p domain.Principal, repo domain.RepoName, head domain.ObjectID, now time.Time) (ports.RepoStats, error)

RepoStats counts the commits reachable from head, all of them, those of the last ports.RecentDays days and those of each recent day, and names the latest tag. The answer is kept for statsTTL, or until the repository is pushed to: the home page and its live updates read it on every row.

func (*Browse) ResolveRefPath

func (b *Browse) ResolveRefPath(ctx context.Context, p domain.Principal, repo domain.RepoName, path string) (domain.RefPath, error)

ResolveRefPath splits a path such as "feature/x/dir/file" into the longest leading ref and the path inside it.

func (*Browse) ResolveRevision

func (b *Browse) ResolveRevision(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision) (domain.ResolvedRevision, error)

ResolveRevision turns a revision into the commit it designates.

func (*Browse) WithHistoryCache

func (b *Browse) WithHistoryCache(maxEntries int, maxBytes int64) *Browse

WithHistoryCache replaces the bounds of the history cache: at most maxEntries answers and about maxBytes of memory, least recently used first out. A bound that is not positive disables the cache. Call it before the first request.

func (*Browse) WithVerifier added in v0.26.0

func (b *Browse) WithVerifier(v ports.CommitVerifier) *Browse

WithVerifier sets the checker of commit signatures (ADR 0030): commits returned by ListCommits and GetCommit then carry their Verification. Call it before the first request.

type CI

type CI struct {
	// contains filtered or unexported fields
}

CI is the CI use cases: the workflow trigger, the planner, the assignment of jobs to runners, the runner protocol and the owner side (ADR 0025).

Every change of a run goes through its lock (withRun): the records of a run are read, changed and written again by one goroutine at a time, so that two calls (a runner's update and an owner's cancel) never lose one another's write. Assignment takes the assignment lock before the lock of the run.

func NewCI

func NewCI(ctx context.Context, cfg CIConfig) *CI

NewCI builds the CI use cases. ctx carries the logger; its cancellation is not followed: call Start to run the workers and Close to stop them.

func (*CI) AppendLog

func (c *CI) AppendLog(ctx context.Context, secret, jobID string, step int32, offset int64, lines []domain.CILogLine) (int64, error)

AppendLog appends lines of a step to the log of the job; see runner.proto for the rules of offsets. The metadata store is written only when a step gets its first line.

func (*CI) CachedSchedules added in v0.15.0

func (c *CI) CachedSchedules(repo domain.RepoName) int

CachedSchedules is the number of scheduled workflows the scheduler holds for a repository, -1 when it holds nothing for it. For tests and diagnostics.

func (*CI) CancelDepsRun added in v0.42.0

func (c *CI) CancelDepsRun(ctx context.Context, repo domain.RepoName, runID int64) error

CancelDepsRun cancels a dependency run like the owner's cancel does, on a server-side decision. A completed run is left alone; a run that is not a dependency run of repo is domain.ErrNotFound.

func (*CI) CancelRun

func (c *CI) CancelRun(ctx context.Context, p domain.Principal, name domain.RepoName, runID int64) (domain.CIRun, error)

CancelRun cancels the latest attempt of a run. The principal needs ci:write on the repository. Jobs not given to a runner end at once; the others get a cancel request, and the run completes when they have ended.

func (*CI) Close

func (c *CI) Close(_ context.Context)

Close stops the workers and waits for them.

func (*CI) CommitChecks

func (c *CI) CommitChecks(ctx context.Context, repo domain.RepoName, sha string) ([]domain.CICheck, error)

CommitChecks returns the checks of a commit, without access control. A commit with more than MaxCIChecks checks is an error (domain.ErrResourceExhausted): the merge rule cannot answer from a part of them.

func (*CI) CommitJobArtifact added in v0.16.0

func (c *CI) CommitJobArtifact(ctx context.Context, secret, jobID string, artifactID int64, blockIDs [][]byte) (int64, string, error)

CommitJobArtifact assembles the content of a pending artifact from its blocks, then records it and drops the blocks. The size counts against the limits with the repeats of the list.

func (*CI) CompleteJob

func (c *CI) CompleteJob(ctx context.Context, secret string, params ports.CompleteJobParams) (ports.CompleteJobResult, error)

CompleteJob ends a job, once. A later call returns the stored result.

func (*CI) CreateJobArtifact added in v0.16.0

func (c *CI) CreateJobArtifact(ctx context.Context, secret string, params ports.CreateJobArtifactParams) (ports.CreatedArtifact, error)

CreateJobArtifact begins an upload: a pending artifact with a new id. A pending artifact of the same job and name is discarded.

func (*CI) DeleteArtifact added in v0.16.0

func (c *CI) DeleteArtifact(ctx context.Context, p domain.Principal, name domain.RepoName, artifactID int64) error

DeleteArtifact deletes an artifact. It needs ci:write on the repository and, like the other writes on a run, the branch of the run within the prefixes of a limited token.

func (*CI) DeleteJobArtifact added in v0.16.0

func (c *CI) DeleteJobArtifact(ctx context.Context, secret, jobID, name string) (int64, error)

DeleteJobArtifact deletes the visible artifact of that name of the job's run.

func (*CI) DisableWorkflow added in v0.27.0

func (c *CI) DisableWorkflow(ctx context.Context, p domain.Principal, name domain.RepoName, file string) (ports.WorkflowInfo, error)

DisableWorkflow stops a workflow file from starting runs. The file is named by its path or its file name and does not have to exist. It needs repo:admin on the repository (adminAccess), and is idempotent.

func (*CI) DisabledWorkflows added in v0.27.0

func (c *CI) DisabledWorkflows(ctx context.Context, p domain.Principal, name domain.RepoName) ([]string, error)

DisabledWorkflows returns the disabled paths of a repository, sorted, without reading any file.

func (*CI) DispatchWorkflow added in v0.15.0

func (c *CI) DispatchWorkflow(ctx context.Context, p domain.Principal, name domain.RepoName, params ports.DispatchParams) (domain.CIRun, error)

DispatchWorkflow starts a run of a workflow file by hand (workflow_dispatch, CI-3 D5), like `gh workflow run`.

Who may: a principal with ci:write on the repository, or the holder of a job token whose permissions include "actions: write" (GitHub's one exception to "events made with GITHUB_TOKEN start no run"; a token of a run that is not trusted never has it, TokenPermissions lowers it, so a job token that gets here belongs to a trusted run). A token limited to branch prefixes may only dispatch on a branch under them, and its run is not trusted (D1). Whoever calls, the run is trusted only when the ref it runs on was moved to its commit by a trusted principal (ci_tips.go).

The workflow is read at ref (the default branch when empty) and must declare workflow_dispatch; the inputs are checked against its declaration.

func (*CI) DownloadArtifact added in v0.16.0

func (c *CI) DownloadArtifact(ctx context.Context, p domain.Principal, name domain.RepoName, artifactID, offset int64, send func(ports.ArtifactChunk) error) error

DownloadArtifact streams a visible artifact of the repository.

func (*CI) DownloadJobArtifact added in v0.16.0

func (c *CI) DownloadJobArtifact(ctx context.Context, secret, jobID string, artifactID, offset int64, send func(ports.ArtifactChunk) error) error

DownloadJobArtifact streams a visible artifact of the job's run.

func (*CI) EnableWorkflow added in v0.27.0

func (c *CI) EnableWorkflow(ctx context.Context, p domain.Principal, name domain.RepoName, file string) (ports.WorkflowInfo, error)

EnableWorkflow undoes DisableWorkflow. Same access; idempotent.

func (*CI) FetchJob

func (c *CI) FetchJob(ctx context.Context, secret string, params ports.FetchJobParams) (ports.FetchJobResult, error)

FetchJob holds the call until a queued job matches the runner, or the poll time passes. A job is given to exactly one runner: assignments are made one at a time under a lock and recorded before the answer is sent.

func (*CI) FinalizeJobArtifact added in v0.16.0

func (c *CI) FinalizeJobArtifact(ctx context.Context, secret, jobID, name string, size int64, digest string) (domain.CIArtifact, error)

FinalizeJobArtifact makes the committed upload of the job with that name visible. An artifact of that name from an earlier attempt is deleted in the same write; one finalized in this attempt is already_exists.

func (*CI) ForgetRepo added in v0.15.0

func (c *CI) ForgetRepo(repo domain.RepoName)

ForgetRepo drops what CI holds in memory for a repository that was deleted: its cached schedules, so that it is never scheduled again (D12).

func (*CI) GetDepsRun added in v0.42.0

func (c *CI) GetDepsRun(ctx context.Context, repo domain.RepoName, id int64) (domain.CIRun, error)

GetDepsRun reads a run for Bernard.

func (*CI) GetJob

func (c *CI) GetJob(ctx context.Context, p domain.Principal, name domain.RepoName, jobID string, attempt int32) (ports.JobView, error)

GetJob returns one job with its steps.

func (*CI) GetJobLog

func (c *CI) GetJobLog(ctx context.Context, p domain.Principal, name domain.RepoName, params ports.GetJobLogParams, send func(ports.LogBatch) error) error

GetJobLog streams the lines of a job log from an offset, in batches of at most 1,000 lines and 64 KiB. With Follow it goes on while the job runs, and ends once the job completed and every line was sent.

func (*CI) GetRepo added in v0.9.0

func (c *CI) GetRepo(ctx context.Context, p domain.Principal, owner, name string) ([]byte, error)

GetRepo returns GET /repos/{owner}/{repo}.

The object is the repository of the push event (repoObj: real values where ermite has them, GitHub's for a repository without the feature elsewhere) with the REST differences: dates are ISO 8601 strings, and the permissions of the caller are given, as GitHub does for an authenticated request.

func (*CI) GetRun

func (c *CI) GetRun(ctx context.Context, p domain.Principal, name domain.RepoName, runID int64, attempt int32) (ports.RunDetail, error)

GetRun returns a run with the jobs of an attempt (0 for the latest).

func (*CI) GetStepSummaries added in v0.9.0

func (c *CI) GetStepSummaries(ctx context.Context, p domain.Principal, name domain.RepoName, jobID string, attempt int32, maxBytes int64) (ports.SummaryList, error)

GetStepSummaries returns the summaries of a job at an attempt (0 for the latest): the first 20 steps that have one, by step number. With maxBytes above 0, no more than that many bytes of Markdown are read.

func (*CI) HashAlgorithm added in v0.9.0

func (c *CI) HashAlgorithm(ctx context.Context, p domain.Principal, owner, name string) (string, error)

HashAlgorithm answers GET /repos/{owner}/{repo}/hash-algorithm: checkout asks it before it runs git init. ermite repositories are SHA-1.

func (*CI) Heartbeat

func (c *CI) Heartbeat(ctx context.Context, secret, jobID string) error

Heartbeat tells the server that the runner is alive. The first one acknowledges the assignment; the others only move the deadline.

func (*CI) IDToken added in v0.16.0

func (c *CI) IDToken(ctx context.Context, secret, audience string) (string, error)

IDToken signs an ID token for the job whose request token is secret. The token must exist (it is deleted when the job ends), not have expired, and its job must be assigned or running, still allowed to have ID tokens. audience "" is the default, "<public URL>/<owner>" as GitHub's "https://github.com/<owner>".

func (*CI) ListAnnotations added in v0.9.0

func (c *CI) ListAnnotations(ctx context.Context, p domain.Principal, name domain.RepoName, runID int64, attempt int32, jobID string) (ports.AnnotationList, error)

ListAnnotations returns the annotations of a run attempt (0 for the latest), of one job when jobID is set, ordered by job, step and production order. Each job gives those of the attempt in which it executed.

func (*CI) ListArtifacts added in v0.16.0

func (c *CI) ListArtifacts(ctx context.Context, p domain.Principal, name domain.RepoName, runID int64) (ports.ArtifactList, error)

ListArtifacts lists the visible artifacts of a run with the space of the repository.

func (*CI) ListChecks

func (c *CI) ListChecks(ctx context.Context, p domain.Principal, name domain.RepoName, sha string) (ports.ChecksResult, error)

ListChecks returns the checks of a commit: the one of each job of the latest attempt of each run on it. A commit without a run has none.

func (*CI) ListJobArtifacts added in v0.16.0

func (c *CI) ListJobArtifacts(ctx context.Context, secret, jobID, name string, artifactID int64) ([]domain.CIArtifact, error)

ListJobArtifacts lists the visible artifacts of the job's run.

func (*CI) ListPullFiles added in v0.14.0

func (c *CI) ListPullFiles(ctx context.Context, p domain.Principal, owner, name string, number int64, page, perPage int) ([]byte, string, error)

ListPullFiles implements ports.GitHubAPIService.

func (*CI) ListRunners

func (c *CI) ListRunners(ctx context.Context, p domain.Principal, size int, token string) (ports.RunnerPage, error)

ListRunners returns a page of the runners ordered by name. The principal needs runner:register on a token that is not restricted to repositories.

func (*CI) ListRuns

func (c *CI) ListRuns(ctx context.Context, p domain.Principal, name domain.RepoName, params ports.ListRunsParams) (ports.RunPage, error)

ListRuns returns a page of the runs of a repository, newest first. The principal needs the access of reading the repository.

func (*CI) ListWorkflows added in v0.27.0

func (c *CI) ListWorkflows(ctx context.Context, p domain.Principal, name domain.RepoName) ([]ports.WorkflowInfo, error)

ListWorkflows returns the workflow files at the tip of the default branch and the disabled ones whatever the branch holds, ordered by path. Reading the repository is enough.

func (*CI) OIDCDiscovery added in v0.16.0

func (c *CI) OIDCDiscovery() ([]byte, error)

OIDCDiscovery returns the OpenID Provider configuration.

func (*CI) OIDCEnabled added in v0.16.0

func (c *CI) OIDCEnabled() bool

OIDCEnabled reports whether the server issues ID tokens.

func (*CI) OIDCKeys added in v0.16.0

func (c *CI) OIDCKeys() ([]byte, error)

OIDCKeys returns the JWKS.

func (*CI) OnMerge added in v0.9.0

func (c *CI) OnMerge(ctx context.Context, repo domain.RepoName, updates []domain.RefUpdate, merger domain.Principal)

OnMerge queues the move of a target branch by a merge. The actor of the run is the owner, as GitHub shows; the pusher recorded with it, whose rights the actions of other repositories are resolved with, is the principal that merged: a token limited to some repositories does not gain the owner's reach by merging. For the same reason the run is trusted (gets secrets) only when the merger is: the displayed actor is not the one that decides.

func (*CI) OnPullRequest added in v0.14.0

func (c *CI) OnPullRequest(ctx context.Context, repo domain.RepoName, ev ports.PullRequestEvent, p domain.Principal)

OnPullRequest queues an activity of a merge request.

func (*CI) OnPush

func (c *CI) OnPush(ctx context.Context, repo domain.RepoName, updates []domain.RefUpdate, pusher domain.Principal)

OnPush queues the refs of a push or a merge for planning and returns; a worker plans them. A full queue drops the trigger, logged at error: a push is never slowed down or failed by CI. The push workflows are queued first; the merge requests the push touches are then handed to their own worker, with a short lock and no I/O, so nothing here waits for Git or the store.

A push made with a job token starts nothing, as on GitHub (events created with GITHUB_TOKEN start no run): that is what stops a workflow that pushes from running itself again.

func (*CI) ReconcileConcurrency added in v0.13.0

func (c *CI) ReconcileConcurrency(ctx context.Context)

ReconcileConcurrency repairs the groups. It runs at startup and every minute. For each stored group it prunes the entries that are gone or over, asks again the running entry a cancel-in-progress entry replaced, cancels every entry marked replaced or rejected (their cancellation is owed), and plans the entries the record names whose own record does not know it yet.

Cost, per pass: a scan of the stored groups and their marks, and for each group a version-checked transaction (a write only if something is repaired; at most CIConfig.ConcurrencyPruneBatch stale marks go in one, the group is taken again until none is left) plus a read of the run or job of each entry the record or the marks name (at most CIConfig.ConcurrencyQueueMax waiting ones per group). It grows with the groups in use, not with the open runs.

func (*CI) Register

Register adds a runner and returns its token, shown once. The principal needs runner:register on a token that is not restricted to repositories, or to be root. A name in use is domain.ErrAlreadyExists unless Replace is set, which removes the old runner (its token stops working at once).

func (*CI) RemoveLogs

func (c *CI) RemoveLogs(ctx context.Context, runIDs []int64) error

RemoveLogs deletes the logs, the summaries and the artifact files of runs of a repository that was deleted (its artifact records went with it).

func (*CI) RemoveRunner

func (c *CI) RemoveRunner(ctx context.Context, p domain.Principal, id string) error

RemoveRunner removes a runner: its token stops working at once and a poll of it in progress ends with unauthenticated. A job it runs is not affected.

func (*CI) RerunRun

func (c *CI) RerunRun(ctx context.Context, p domain.Principal, name domain.RepoName, runID int64, failedOnly bool) (domain.CIRun, error)

RerunRun starts a new attempt of a completed run, from the stored workflow file: all the jobs, or with failedOnly the jobs that did not succeed and those that need them; the others are kept with their result, steps and log.

func (*CI) RescanPulls added in v0.14.0

func (c *CI) RescanPulls(ctx context.Context, repo domain.RepoName) error

RescanPulls rescans the merge requests of a repository now, on the calling goroutine, as the refresh worker does for a repository it marked dirty. It is for tests and operators' tooling; the server never needs to call it.

func (*CI) ResolveActions added in v0.9.0

func (c *CI) ResolveActions(ctx context.Context, secret, jobID string, uses []string) ([]ports.ResolvedAction, error)

ResolveActions turns the "uses" of a job into where to download the actions (ADR 0025, CI-2), as GitHub's action download info does: ermite resolves, the runner downloads from the sandbox.

  • owner = ERMITE_OWNER and the repository exists on ermite: ermite wins (as the actions of a GitHub Enterprise Server do). The ref is a tag, else a branch, else a full commit id that exists; the archive is served by the REST subset and downloaded with a read token for that repository only.
  • anything else: github.com. The ref is resolved with `git ls-remote` (tag, then branch, the peeled commit of an annotated tag); a full commit id is returned as it is, without checking. The archive is codeload's, with no token.

The commit a ref resolves to is kept for the run attempt: every job and every call of the attempt gets the same one, the first that was stored. A reference that cannot be resolved is not an error of the call: its answer carries GitHub's message.

func (*CI) RunIDs

func (c *CI) RunIDs(ctx context.Context, repo domain.RepoName) ([]int64, error)

RunIDs lists the runs of a repository, for the cleanup that follows its deletion.

func (*CI) ScheduleNow added in v0.15.0

func (c *CI) ScheduleNow(ctx context.Context)

ScheduleNow does one tick of the scheduler on the calling goroutine, with the clock of the configuration. It is for tests (with a ScheduleTick too long to fire) and operators' tooling; the server never needs to call it. It must not run at the same time as the scheduler goroutine.

func (*CI) SchedulePassed added in v0.15.0

func (c *CI) SchedulePassed() time.Time

SchedulePassed is the minute of the last pass of the scheduler that went through every repository (zero before the first). For tests and diagnostics.

func (*CI) SetAutoMergeTrigger added in v0.31.0

func (c *CI) SetAutoMergeTrigger(t ports.AutoMergeTrigger)

SetAutoMergeTrigger installs the evaluator after constructing the merge use cases.

func (*CI) SetDepsObserver added in v0.42.0

func (c *CI) SetDepsObserver(o ports.DepsRunObserver)

SetDepsObserver installs the observer of the dependency jobs after construction (Bernard is built after CI); nil turns the notifications off.

func (*CI) SetPullGate added in v0.14.0

func (c *CI) SetPullGate(g ports.PullGate)

SetPullGate gives the CI the gate of the merge requests (see PullGate). Call it before merge requests can be opened.

func (*CI) Settled added in v0.31.0

func (c *CI) Settled(_ context.Context, repo domain.RepoName, number int64) bool

Settled reports whether all push planning and pull activities have finished. Workflow-run events, schedules and dispatches are intentionally not counted. Repo-wide pushes conservatively delay requests even when an unrelated branch is updated.

func (*CI) Start

func (c *CI) Start()

Start runs the trigger worker and the watchdog, and plans again the runs that were open when the server stopped.

func (*CI) StartDepsRun added in v0.42.0

func (c *CI) StartDepsRun(ctx context.Context, p ports.DepsRunParams) (domain.CIRun, error)

StartDepsRun creates the run of a dependency job, ready for a runner of the label.

func (*CI) Tarball added in v0.9.0

func (c *CI) Tarball(ctx context.Context, p domain.Principal, owner, name, ref string, begin func(string) io.Writer) error

Tarball streams the archive of a ref as `git archive` makes it, in one response (GitHub answers 302 to codeload: ermite serves it itself). The files are in a directory <owner>-<repo>-<7 first characters of the commit>, as GitHub names it.

func (*CI) TarballName added in v0.9.0

func (c *CI) TarballName(ctx context.Context, p domain.Principal, owner, name, ref string) (string, error)

TarballName answers a HEAD of the archive: the access and the ref are checked as for the archive, and the name of the file is returned, without running git archive.

func (*CI) UpdateJob

func (c *CI) UpdateJob(ctx context.Context, secret, jobID string, steps []ports.StepUpdate, annotations []domain.CIAnnotation) error

UpdateJob applies the steps and the annotations of the request whole or not at all. The annotations of each step they name replace those stored, whether the step is completed or not.

func (*CI) UploadJobArtifactBlock added in v0.16.0

func (c *CI) UploadJobArtifactBlock(ctx context.Context, secret, jobID string, artifactID int64, blockID []byte, data io.Reader) (int64, error)

UploadJobArtifactBlock stores a block: the data goes to a new file, counted against the limits as it comes, then the record names it in place of the block of the same id, if any.

func (*CI) UploadSummary added in v0.9.0

func (c *CI) UploadSummary(ctx context.Context, secret, jobID string, step int32, markdown string) error

UploadSummary stores the summary of a step, replacing the one it has. An empty Markdown removes it.

func (*CI) WatchJob

func (c *CI) WatchJob(ctx context.Context, secret, jobID string, ready func(), send func(ports.JobSignal) error) error

WatchJob streams what the runner must react to: the cancel request (the first message when one is already pending), keepalives, and the end.

type CIConfig

type CIConfig struct {
	AutoMerge ports.AutoMergeTrigger
	Store     ports.CIStore
	Logs      ports.CILogStore
	// Summaries keeps the job summaries, deleted with the logs of their run.
	Summaries ports.CISummaryStore
	Meta      ports.RepoMetaStore
	Repos     ports.GitRepos
	Reader    ports.RepoReader
	Git       ports.MergeGit
	Diff      ports.CIGit
	// MRs, Pull and Committer give the workflows their pull_request event and
	// the refs/pull/<n>/{head,merge} refs of the merge requests (CI-3, D5);
	// without MRs or Pull the merge requests start no run and have no such
	// refs. Committer is the identity of the test merge commits (default
	// "ermite"). Fresh, when set, is told when the refs of a repository changed
	// so that the reader sees them at once.
	MRs       ports.MergeRequestStore
	Pull      ports.PullGit
	Committer domain.Identity
	Fresh     ports.RepoFreshener
	// PullStage, when set, is called by the workers of the merge requests at
	// the stages "event" (an activity is taken), "computed" (its merge is
	// computed, before it is published) and "refresh" (a refresh is taken).
	// Tests use it to interleave other changes.
	PullStage func(stage string)
	// PullRefreshes bounds the merge requests waiting for a refresh of their refs
	// (default DefaultCIPullRefreshes); past it repositories are rescanned. It
	// does not bound the activities (synchronize runs), which have their own queue
	// with the overload policy of the push triggers.
	PullRefreshes int
	// PullAttempts bounds the merge requests whose last conflict is remembered
	// (default DefaultCIPullAttempts), so that a rescan does not try a conflicting
	// merge again until a branch moves.
	PullAttempts int
	// PullSnapshot bounds the merge requests one push takes the snapshot of, in the
	// push handler (default DefaultCIPullSnapshot): past it the others are skipped
	// with an error in the log and the repository is rescanned for its refs.
	PullSnapshot int
	Bus          ports.EventBus
	Clock        ports.Clock
	// Auth re-validates the token of a log stream; nil skips it.
	Auth ports.Revalidator
	// Remote lists the refs of github.com repositories for ResolveActions; nil
	// answers every github.com action with unavailable.
	Remote ports.GitRemote
	// Workflows downloads reusable workflow files from github.com at a resolved
	// commit; nil fails such a call as unavailable.
	Workflows ports.WorkflowFetcher
	// Archive serves the tarballs of the REST subset.
	Archive ports.GitArchive
	// Tokens finds the stored token of the pusher of a run, to ask what it may
	// read when an action of another repository is resolved for the run. Nil
	// reads nothing for a token (fail closed).
	Tokens ports.TokenStore
	// OIDC signs the ID tokens of the jobs (CI-4); nil or disabled issues none: jobs get no
	// token endpoint, and the provider is not served.
	OIDC *OIDC
	// Settings resolves the variables and secrets of a job (CI-3). Nil gives
	// jobs none, and an empty "vars" context. Leave it unset rather than
	// assigning a typed nil pointer.
	Settings ports.CIJobSettings
	// RemoteConcurrency bounds the git ls-remote processes at once (default
	// DefaultCIRemoteConcurrency); ResolveTimeout bounds one ResolveActions call
	// (default DefaultCIResolveTimeout).
	RemoteConcurrency int
	ResolveTimeout    time.Duration
	// Owner is the owner login of the github context.
	Owner string
	// PublicURL is the base URL of the instance (github.server_url).
	PublicURL string
	// Retention is how long runs, jobs, checks and logs are kept.
	Retention time.Duration
	// Artifacts and Blobs keep the records and contents of the artifacts.
	// Without both the artifact services refuse
	// every call. ArtifactLimits are their settings; a zero field takes its
	// default (the run retention of domain.CIRetentionDays,
	// domain.DefaultCIArtifactQuota, domain.DefaultCIArtifactMaxSize). Leave
	// them unset rather than assigning typed nil pointers.
	Artifacts ports.CIArtifactStore
	// ArtifactFiles is the legacy reader; nil when $ERMITE_DATA_DIR/ci/artifacts
	// does not exist at startup; P8 wires it.
	ArtifactFiles  ports.CIArtifactFiles
	Blobs          ports.BlobStore
	ArtifactLimits ports.ArtifactLimits

	// The durations below default to the contract's; tests shorten them.
	PollTimeout       time.Duration
	Tick              time.Duration
	Keepalive         time.Duration
	AckTimeout        time.Duration
	HeartbeatDeadline time.Duration
	CancelGrace       time.Duration
	TimeoutSlack      time.Duration
	QueueTimeout      time.Duration
	TriggerQueue      int
	// ConcurrencyRetries bounds the retries of a delivery of the concurrency
	// groups that failed (a cancel, the planning of a promoted entry): 0 is the
	// default (8), a negative number none, which the tests use to leave the
	// recovery to the reconciliation.
	ConcurrencyRetries int
	// ConcurrencyPruneBatch bounds the stale marks of a concurrency group one
	// transaction removes (default 256, far below the size limit of a kv batch);
	// the others go with the next decisions and the reconciliation.
	ConcurrencyPruneBatch int
	// ConcurrencyQueueMax is how many entries may wait in a queue: max group:
	// 0 is GitHub's limit (domain.CIMaxConcurrencyQueue); the tests lower it so
	// as not to start a hundred runs.
	ConcurrencyQueueMax int
	// ScheduleTick is how often the scheduler looks at the clock (default one
	// second) and ScheduleBudget the repositories it handles per tick (default
	// 64); see ci_schedule.go.
	ScheduleTick   time.Duration
	ScheduleBudget int
	// RefTips holds who moves each branch and tag to its object, written before
	// the ref moves (Pushes, MergeRequests), for the actor and trust of scheduled
	// runs and the trust of dispatched ones (ci_tips.go). Nil: they are never
	// trusted. Leave it unset rather than assigning a typed nil pointer.
	RefTips ports.CIRefTipStore
	// WorkflowState keeps the workflows a repository disabled (ci_workflows.go).
	// Nil: none is. Leave it unset rather than assigning a typed nil pointer.
	WorkflowState ports.CIWorkflowStore
	// DispatchResolved, when set, is called by DispatchWorkflow after it resolved
	// its ref and before it judges the trust of the run. Tests use it to move the
	// ref in between.
	DispatchResolved func()
	// RunEventHandled, when set, is called after the workflows waiting for a
	// "requested" or "completed" event of a run were started (or none matched).
	// Tests use it as a barrier.
	RunEventHandled func(runID int64, action string)
	// ScheduleLoaded, when set, is called after the scheduler read the workflows
	// of a repository and before it caches and fires them. Tests use it to
	// change the disabled state in that window.
	ScheduleLoaded func(repo domain.RepoName)
	// ScheduleVisit, when set, is called with each repository the scheduler
	// looks at. Tests use it.
	ScheduleVisit func(repo domain.RepoName)
	// ReleaseSweeper collects abandoned release uploads during the daily sweep.
	ReleaseSweeper ports.ReleaseSweeper
}

CIConfig holds what the CI use cases need. Store, Logs, Summaries, Meta, Repos, Reader, Git, Diff, Bus, Clock, Owner and PublicURL are required (the config supplies the defaults of Owner and Retention).

type CISettings added in v0.11.0

type CISettings struct {
	// contains filtered or unexported fields
}

CISettings is the use cases of secrets, variables and environments (ADR 0019, CI-3): the owner side served by the API, and the resolution of what a job sees.

Secrets are write-only: no method returns a value but Resolve, which gives them to the planner for the job of a trusted run. Values are sealed with the sealer before they are stored; without one (no ERMITE_SECRETS_KEY) secrets cannot be set and jobs get none, while names can still be listed and deleted. The values are never logged.

func NewCISettings added in v0.11.0

func NewCISettings(ctx context.Context, meta ports.RepoMetaStore, store ports.CISettingsStore, sealer ports.SecretSealer, clock ports.Clock) (*CISettings, error)

NewCISettings builds the settings use cases. sealer is nil when the server has no key. When there is one, it records the key check beside the secrets or compares it with the recorded one: another key than the one the secrets were sealed with is domain.ErrSecretsKeyMismatch, and the server must not start.

func (*CISettings) CreateEnvironment added in v0.11.0

func (s *CISettings) CreateEnvironment(ctx context.Context, p domain.Principal, repo domain.RepoName, name string) (domain.Environment, error)

CreateEnvironment creates an environment; one that exists (without regard to case) is returned unchanged.

func (*CISettings) DeleteEnvironment added in v0.11.0

func (s *CISettings) DeleteEnvironment(ctx context.Context, p domain.Principal, repo domain.RepoName, name string) error

DeleteEnvironment deletes an environment with its secrets and variables.

func (*CISettings) DeleteSecret added in v0.11.0

func (s *CISettings) DeleteSecret(ctx context.Context, p domain.Principal, scope domain.SettingsScope, name string) error

DeleteSecret deletes a secret.

func (*CISettings) DeleteVariable added in v0.11.0

func (s *CISettings) DeleteVariable(ctx context.Context, p domain.Principal, scope domain.SettingsScope, name string) error

DeleteVariable deletes a variable.

func (*CISettings) GetVariable added in v0.11.0

func (s *CISettings) GetVariable(ctx context.Context, p domain.Principal, scope domain.SettingsScope, name string) (domain.Variable, error)

GetVariable returns one variable of one scope.

func (*CISettings) ListEnvironments added in v0.11.0

func (s *CISettings) ListEnvironments(ctx context.Context, p domain.Principal, repo domain.RepoName, size int, token string) (ports.EnvironmentPage, error)

ListEnvironments lists the environments of a repository, ordered by name.

func (*CISettings) ListSecrets added in v0.11.0

func (s *CISettings) ListSecrets(ctx context.Context, p domain.Principal, scope domain.SettingsScope, size int, token string) (ports.SecretPage, error)

ListSecrets lists the names of the secrets of one scope, ordered by name.

func (*CISettings) ListVariables added in v0.11.0

func (s *CISettings) ListVariables(ctx context.Context, p domain.Principal, scope domain.SettingsScope, size int, token string) (ports.VariablePage, error)

ListVariables lists the variables of one scope, ordered by name.

func (*CISettings) Resolve added in v0.11.0

Resolve gives the variables, and the secrets when wanted, that a job sees: the instance's, overridden by the repository's, overridden by the environment's. A job that names an environment creates it on first use. Secrets are opened only for the names asked (or all of them), and a secret that does not open is left out and logged by name, never by value.

func (*CISettings) SetSecret added in v0.11.0

func (s *CISettings) SetSecret(ctx context.Context, p domain.Principal, scope domain.SettingsScope, name, value string) (domain.Secret, error)

SetSecret creates the secret or replaces its value.

func (*CISettings) SetVariable added in v0.11.0

func (s *CISettings) SetVariable(ctx context.Context, p domain.Principal, scope domain.SettingsScope, name, value string) (domain.Variable, error)

SetVariable creates the variable or replaces its value.

type DisabledBernard added in v0.40.0

type DisabledBernard struct{}

DisabledBernard is the DepsService of a server that runs without Bernard (ERMITE_DEPS_ENABLED=0): it lists nothing and refuses every action.

func (DisabledBernard) Check added in v0.40.0

Check refuses: Bernard is disabled.

func (DisabledBernard) Ignore added in v0.40.0

Ignore refuses: Bernard is disabled.

func (DisabledBernard) List added in v0.40.0

List returns no record to the owner.

func (DisabledBernard) Open added in v0.40.0

Open refuses: Bernard is disabled.

func (DisabledBernard) Snooze added in v0.40.0

Snooze refuses: Bernard is disabled.

type Events

type Events struct {
	// contains filtered or unexported fields
}

Events keeps the last events of the bus in a ring buffer, numbers them, and lets callers watch them: live, or replayed from the id of the last event they saw. Events of a private repository only reach callers that may read it, checked again for every event.

Event ids are opaque to clients: "<boot>-<seq>", where boot identifies this run of the server, so that an id from before a restart is unknown rather than taken for a newer event.

The goroutine that feeds the buffer uses the logger of the context given to NewEvents and lives until Close.

func NewEvents

func NewEvents(ctx context.Context, repos *Repos, bus ports.EventBus, clock ports.Clock, cfg EventsConfig) *Events

NewEvents subscribes to the bus and starts remembering its events. ctx carries the logger; its cancellation is not followed, call Close.

func (*Events) Close

func (e *Events) Close(ctx context.Context)

Close ends every watch cleanly, stops feeding and waits for the feeder, until ctx ends. It is safe to call more than once.

func (*Events) Watch

func (e *Events) Watch(ctx context.Context, p domain.Principal, params ports.WatchParams, send func(ports.WatchEvent) error) error

Watch calls send for the events the principal may see until ctx ends, the buffer closes (both return nil) or send fails (its error is returned).

A principal with a stored token is checked again for every event, and every few seconds when idle: when the token expired or was revoked the watch ends with domain.ErrUnauthenticated and nothing more is sent. More watches than MaxWatchers, or than MaxWatchersPerClient for one Client, give domain.ErrResourceExhausted.

Without ResumeAfter only new events are sent. With it, the buffered events after that id are sent first, then new ones. When the id is no longer buffered, or was not issued by this run, a gap comes first and the oldest buffered event follows. A watcher that falls more than the buffer behind gets a gap too, and carries on with the oldest buffered event.

func (*Events) Watchers

func (e *Events) Watchers() int

Watchers returns the number of watches in progress.

type EventsConfig

type EventsConfig struct {
	// Size is the number of events remembered; DefaultEventBuffer when not
	// positive.
	Size int
	// Auth confirms that the token of a watcher still exists. Required.
	Auth ports.Revalidator
	// RevalidateEvery is how long the answer of Auth is kept for a watcher;
	// DefaultRevalidateEvery when zero, and negative to ask for every event.
	RevalidateEvery time.Duration
}

EventsConfig configures Events.

type Inbox added in v0.28.0

type Inbox struct {
	// contains filtered or unexported fields
}

Inbox is the owner's inbox (ADR 0031): the open merge requests and issues of every repository in one place. Only the owner may read it; anyone else gets domain.ErrNotFound, as for a page that does not exist. The issues of a repository whose issues mode is off are left out.

func NewInbox added in v0.28.0

func NewInbox(cfg InboxConfig) *Inbox

NewInbox builds the inbox use case.

func (*Inbox) Journal added in v0.41.0

func (in *Inbox) Journal(ctx context.Context, p domain.Principal, window time.Duration) (ports.InboxJournal, error)

Journal reads the entries of the window from the journal, leaving out those of repositories that are gone or were created again since, and the issues of a repository whose issues are off (as Overview does). The store applies that filter during its one scan and keeps the newest MaxJournalEntries: entries left out do not hide older ones. It also measures, for each repository with a tag, the commits of its default branch after the nearest tag: one walk per repository, bounded by domain.MaxTagDistance and served from the commit-graph, and cached until the refs of the repository move. A repository whose walk fails is logged and skipped.

func (*Inbox) ListActivity added in v0.41.0

ListActivity reads one page with one bounded scan of the journal: the page token is the ID of the last entry of the previous page, and the scan stops before it.

func (*Inbox) ListUnreleased added in v0.41.0

func (in *Inbox) ListUnreleased(ctx context.Context, p domain.Principal, repo domain.RepoName) ([]ports.UnreleasedRepo, error)

ListUnreleased serves the tag warnings of the inbox, from the same cache.

func (*Inbox) Overview added in v0.28.0

func (in *Inbox) Overview(ctx context.Context, p domain.Principal) (ports.InboxOverview, error)

Overview lists what is open across every repository through the open-item indexes.

func (*Inbox) RefsChanged added in v0.41.0

func (in *Inbox) RefsChanged(repo domain.RepoName)

RefsChanged forgets the tag warning of the repository: a push moved its refs, tags included.

type InboxConfig added in v0.28.0

type InboxConfig struct {
	Meta          ports.RepoMetaStore
	MergeRequests ports.MergeRequestStore
	Issues        ports.IssueStore
	// Pending lists the issues awaiting moderation. Optional: without it the
	// inbox has no pending section.
	Pending ports.PendingIssueStore
	CI      ports.CIStore
	// Git reads the head of each open request's source branch. Optional.
	Git ports.MergeGit
	// Deps and Clock add the Bernard section; optional.
	Deps  ports.DepsStore
	Clock ports.Clock
	// Journal, Repos and Reader give the activity journal and the commits
	// since the nearest tag. Optional; they need Clock.
	Journal ports.JournalStore
	Repos   ports.GitRepos
	Reader  ports.RepoReader
}

InboxConfig holds what the inbox needs; Pending and CI are optional.

type IssueKeys added in v0.28.0

type IssueKeys struct {
	// contains filtered or unexported fields
}

IssueKeys holds the subkeys of the public issue form, derived with HKDF-SHA256 from the server key of the IssueKeyStore.

func NewIssueKeys added in v0.28.0

func NewIssueKeys(ctx context.Context, store ports.IssueKeyStore) (*IssueKeys, error)

NewIssueKeys loads the server key (generated on the first call) and derives the subkeys.

func (*IssueKeys) FormTokenKey added in v0.28.0

func (k *IssueKeys) FormTokenKey() []byte

FormTokenKey returns a copy of the subkey that signs the form tokens.

func (*IssueKeys) IPHash added in v0.28.0

func (k *IssueKeys) IPHash(addrKey string) []byte

IPHash returns HMAC-SHA256 of the client key (clientlimit.Key of the address) under the ip-hash subkey: domain.IPHashLen bytes. It is stable across restarts and says nothing about the address without the key.

func (*IssueKeys) PoWKey added in v0.28.0

func (k *IssueKeys) PoWKey() []byte

PoWKey returns a copy of the subkey that signs the proof-of-work challenges.

type IssueLinks struct {
	// contains filtered or unexported fields
}

IssueLinks maintains the derived index and applies closing references on merge.

func NewIssueLinks(cfg IssueLinksConfig) *IssueLinks

func (*IssueLinks) OnBranchesMoved added in v0.31.0

func (l *IssueLinks) OnBranchesMoved(ctx context.Context, p domain.Principal, name domain.RepoName, updates []domain.RefUpdate)

func (*IssueLinks) OnMergeRequestChanged added in v0.31.0

func (l *IssueLinks) OnMergeRequestChanged(ctx context.Context, p domain.Principal, repo domain.Repo, number int64)

func (*IssueLinks) OnMergeRequestSettled added in v0.31.0

func (l *IssueLinks) OnMergeRequestSettled(ctx context.Context, repo domain.Repo, mr domain.MergeRequest)

func (*IssueLinks) OnMerged added in v0.31.0

func (l *IssueLinks) OnMerged(ctx context.Context, p domain.Principal, repo domain.Repo, mr domain.MergeRequest)

func (*IssueLinks) Reindex added in v0.31.0

func (l *IssueLinks) Reindex(ctx context.Context, p domain.Principal, repo domain.Repo, number int64) error

Reindex replaces links from the current texts and bounded commit range.

type IssueLinksConfig added in v0.31.0

type IssueLinksConfig struct {
	Meta   ports.RepoMetaStore
	Issues ports.IssueStore
	MRs    ports.MergeRequestStore
	Links  ports.IssueLinkStore
	Git    ports.MergeGit
	Bus    ports.EventBus
	Clock  ports.Clock
	// Journal records the issues a merge resolves as closed. Optional.
	Journal ports.JournalRecorder
}

type IssueModeration added in v0.28.0

type IssueModeration struct {
	// contains filtered or unexported fields
}

IssueModeration is the owner's moderation of the issues submitted through the public form (ADR 0031): list, approve and reject, with an optional ban of the submitter. Owner rights only; anyone else gets domain.ErrNotFound, as for a page that does not exist.

func NewIssueModeration added in v0.28.0

func NewIssueModeration(cfg IssueModerationConfig) *IssueModeration

NewIssueModeration builds the moderation use case.

func (*IssueModeration) Approve added in v0.28.0

Approve gives the pending issue the next number of its repository and opens it, then publishes IssueOpened. The repository must exist with issues on (else domain.ErrNotFound), and so must the pending issue.

func (*IssueModeration) ListPending added in v0.28.0

ListPending lists the pending issues of a repository, or of every repository when repo is zero, oldest first.

func (*IssueModeration) Reject added in v0.28.0

Reject deletes the pending issue. With ban, its submitter is first banned for domain.BanDuration.

type IssueModerationConfig added in v0.28.0

type IssueModerationConfig struct {
	Meta    ports.RepoMetaStore
	Pending ports.PendingIssueStore
	Bans    ports.IssueBanStore
	Bus     ports.EventBus
	Clock   ports.Clock
	// Journal records the approved issues as opened. Optional.
	Journal ports.JournalRecorder
}

IssueModerationConfig holds what the moderation needs. Every field is required.

type IssueSubmissions added in v0.28.0

type IssueSubmissions struct {
	// contains filtered or unexported fields
}

IssueSubmissions is the anonymous submission of issues: a signed challenge with an adaptive proof of work, and every server-side check before the issue awaits moderation.

func NewIssueSubmissions added in v0.28.0

func NewIssueSubmissions(cfg IssueSubmissionsConfig) *IssueSubmissions

NewIssueSubmissions builds the submission use case.

func (*IssueSubmissions) Challenge added in v0.28.0

func (s *IssueSubmissions) Challenge(ctx context.Context, repo domain.RepoName, addr string) (ports.IssueChallenge, error)

Challenge issues a challenge for a client of the repository.

func (*IssueSubmissions) Submit added in v0.28.0

Submit checks the submission and stores it as a pending issue. The checks run in a fixed order and stop at the first failure; every bot-related failure answers domain.ErrSubmissionInvalid, and the precise reason is only logged (without address, hash or text).

type IssueSubmissionsConfig added in v0.28.0

type IssueSubmissionsConfig struct {
	Meta    ports.RepoMetaStore
	Pending ports.PendingIssueStore
	Bans    ports.IssueBanStore
	Limiter ports.SubmissionLimiter
	Keys    *IssueKeys
	Bus     ports.EventBus
	Clock   ports.Clock
	// BaseDifficulty and MaxDifficulty replace the production constants; for
	// tests. Zero keeps the constants.
	BaseDifficulty, MaxDifficulty int
	// MinFillTime replaces minFillTime when positive; for tests.
	MinFillTime time.Duration
}

IssueSubmissionsConfig holds what the submission needs. Every field but the difficulties is required.

type Issues added in v0.28.0

type Issues struct {
	// contains filtered or unexported fields
}

Issues are the issue use cases (ADR 0031): open, comment on, close and reopen an issue. Issues share their numbers with the merge requests of the repository. A repository whose issues mode is off answers domain.ErrNotFound to everyone, the owner included, like a repository that does not exist. Reading needs the access of reading the repository (canRead), writing needs issue:write on it, which the branch prefixes of a token do not restrict: an issue writes no ref.

func NewIssues added in v0.28.0

func NewIssues(cfg IssuesConfig) *Issues

NewIssues builds the issue use cases.

func (*Issues) AddComment added in v0.28.0

func (i *Issues) AddComment(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, text string) (domain.IssueComment, error)

AddComment adds a comment and publishes IssueCommented. The principal needs issue:write. The text must not be blank. A comment may follow a closed issue; one that already holds MaxCommentsPerIssue comments gives domain.ErrResourceExhausted.

func (*Issues) Close added in v0.28.0

func (i *Issues) Close(ctx context.Context, p domain.Principal, name domain.RepoName, number int64) (domain.Issue, error)

Close closes an open issue and publishes IssueClosed. The principal needs issue:write. An issue that is not open gives domain.ErrPreconditionFailed.

func (*Issues) Create added in v0.28.0

Create opens an issue and publishes IssueOpened. The principal needs issue:write. Invalid texts give an *domain.InvalidFieldsError.

func (*Issues) Edit added in v0.38.0

func (i *Issues) Edit(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, params ports.EditIssueParams) (domain.Issue, error)

Edit changes only present fields, preserving concurrent writes through CAS retries.

func (*Issues) Get added in v0.28.0

func (i *Issues) Get(ctx context.Context, p domain.Principal, name domain.RepoName, number int64) (ports.IssueDetail, error)

Get returns an issue with its comments.

func (*Issues) Kind added in v0.28.0

Kind tells what number n of the repository names. The principal needs the access of reading the repository. Issues are resolved only when enabled; merge requests are resolved even when issues are off.

func (*Issues) List added in v0.28.0

List returns a page of the issues of a repository, highest number first.

func (*Issues) Reopen added in v0.28.0

func (i *Issues) Reopen(ctx context.Context, p domain.Principal, name domain.RepoName, number int64) (domain.Issue, error)

Reopen reopens a closed or resolved issue and publishes IssueReopened. The principal needs issue:write. An issue that is not done gives domain.ErrPreconditionFailed.

func (*Issues) Resolve added in v0.37.0

func (i *Issues) Resolve(ctx context.Context, p domain.Principal, name domain.RepoName, number int64) (domain.Issue, error)

Resolve marks an open issue resolved. The principal needs issue:write.

type IssuesConfig added in v0.28.0

type IssuesConfig struct {
	Links ports.IssueLinkStore
	Meta  ports.RepoMetaStore
	Store ports.IssueStore
	// MergeRequests tells which numbers name a merge request (Kind).
	MergeRequests ports.MergeRequestStore
	Bus           ports.EventBus
	Clock         ports.Clock
	// Journal records the opened and closed issues. Optional.
	Journal ports.JournalRecorder
}

IssuesConfig holds what the issue use cases need. Every field is required.

type Journal added in v0.41.0

type Journal struct {
	// contains filtered or unexported fields
}

Journal records the activity of the repositories for the owner's inbox.

The use cases call it where they publish their events, before publishing: the event bus drops events for a subscriber that falls behind (EventGap), which would lose entries for good, whereas a call cannot be dropped. The write is one ermite-kv batch; a failure is logged and never fails the action. Expired entries are pruned by Record itself, at most once per journalPruneEvery, so the journal needs no loop of its own.

The pushes do not reach the owner topic; OnPush publishes domain.JournalUpdated for the inbox when it wrote an entry.

func NewJournal added in v0.41.0

func NewJournal(store ports.JournalStore, git ports.GitRepos, commits ports.MergeGit, bus ports.EventBus, clock ports.Clock) *Journal

NewJournal builds the recorder. git gives the default branch of a repository and commits counts the commits of a push.

func (*Journal) OnPush added in v0.41.0

func (j *Journal) OnPush(ctx context.Context, actor string, repo domain.RepoName, updates []domain.RefUpdate)

OnPush records the tags a push created or deleted and what it brought to the default branch. Other branches are not recorded.

func (*Journal) Record added in v0.41.0

func (j *Journal) Record(ctx context.Context, e domain.JournalEntry)

Record appends the entry, then prunes when it is time.

type Maintainer

type Maintainer struct {
	// contains filtered or unexported fields
}

Maintainer runs repository maintenance after pushes, debounced per repository: the first Schedule starts a timer, further ones before it fires join the same run, and a repository is maintained by one run at a time. A Schedule during a run queues exactly one more run.

Its goroutines use the logger of the context given to NewMaintainer and live until Close. A run is cancelled by Close, and Git bounds it otherwise (gitcli applies its command timeout to a context without deadline).

func NewMaintainer

func NewMaintainer(ctx context.Context, git ports.GitRepos, delay time.Duration) *Maintainer

NewMaintainer builds a maintainer. ctx carries the logger; its cancellation is not followed, call Close. A zero delay means MaintenanceDelay.

func (*Maintainer) Close

func (m *Maintainer) Close(_ context.Context)

Close stops pending runs, cancels the one in progress and waits for it.

func (*Maintainer) Schedule

func (m *Maintainer) Schedule(ctx context.Context, name domain.RepoName)

Schedule queues maintenance of the repository.

func (*Maintainer) WithFreshener

func (m *Maintainer) WithFreshener(f ports.RepoFreshener) *Maintainer

WithFreshener makes every run tell the reader, when it ends, that the repository changed: the commit-graph it wrote is for the reader to pick up. Call it before the first Schedule; it returns m.

type MergeRequests

type MergeRequests struct {
	// contains filtered or unexported fields
}

MergeRequests are the merge request use cases (ADR 0017): review, comment, close and merge a branch. Reading needs the access of reading the repository: a repository the principal may not read, a missing one and an unknown number all answer domain.ErrNotFound. Writing needs mr:write, and merging mr:merge, on the repository (see scopeAccess), and that scope also makes a private repository visible for the action (see writable).

func NewMergeRequests

func NewMergeRequests(cfg MergeRequestsConfig) *MergeRequests

NewMergeRequests builds the merge request use cases.

func (*MergeRequests) AddComment

func (m *MergeRequests) AddComment(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, params ports.AddCommentParams) (domain.Comment, error)

AddComment adds a comment and publishes CommentAdded. The principal needs mr:write. The text must follow the rules of ADR 0032 and not be blank unless carrying a verdict; a comment on a line must name a line of the current diff of the request (see domain.Diff.HasLine), else the call fails with domain.ErrInvalid. A request that is not open gives domain.ErrPreconditionFailed, and one that already holds MaxCommentsPerMergeRequest comments domain.ErrResourceExhausted.

func (*MergeRequests) CancelAutoMerge added in v0.31.0

func (m *MergeRequests) CancelAutoMerge(ctx context.Context, p domain.Principal, name domain.RepoName, number int64) (domain.MergeRequest, error)

CancelAutoMerge clears an open request's intent with the same target access required to enable it.

func (*MergeRequests) Close

Close closes an open merge request without merging it and publishes MergeRequestClosed. The principal needs mr:write. A request that is not open gives domain.ErrPreconditionFailed. The request keeps the head it had and its merge base, so that its diff stays readable.

func (*MergeRequests) Compare added in v0.28.0

func (m *MergeRequests) Compare(ctx context.Context, p domain.Principal, repo domain.RepoName, target, source domain.ObjectID) (ports.CompareResult, error)

Compare lists at most ports.MaxCompareCommits source commits absent from target. Unlike bounded history intersection, ancestry remains accurate even for deep histories.

func (*MergeRequests) CountOpen added in v0.18.0

func (m *MergeRequests) CountOpen(ctx context.Context, p domain.Principal, name domain.RepoName) (int, error)

CountOpen implements ports.MergeRequestService.

func (*MergeRequests) Create

Create opens a merge request and publishes MergeRequestOpened. The principal needs mr:write. A request that breaks a rule of ADR 0017 (the texts, a branch that does not exist, source and target the same) is rejected whole with an *domain.InvalidFieldsError naming the fields. An open request with the same source and target gives domain.ErrAlreadyExists.

func (*MergeRequests) DeleteComment added in v0.28.0

func (m *MergeRequests) DeleteComment(ctx context.Context, p domain.Principal, name domain.RepoName, number, commentID int64) (domain.Comment, error)

DeleteComment removes text but retains the anchor, ID and verdict. Repeated deletion is a no-op.

func (*MergeRequests) Edit added in v0.38.0

Edit changes the title and/or description of an open or closed merge request.

func (*MergeRequests) EditComment added in v0.28.0

func (m *MergeRequests) EditComment(ctx context.Context, p domain.Principal, name domain.RepoName, number, commentID int64, text string) (domain.Comment, error)

EditComment changes only the author's text, preserving the immutable verdict.

func (*MergeRequests) EnableAutoMerge added in v0.31.0

EnableAutoMerge records the actor and the head the caller saw, which must be the current head of the source branch; re-enabling replaces the previous intent rather than preserving an older actor's authority.

func (*MergeRequests) Get

Get returns a merge request with its commits, its diff, its comments and the read position. The diff compares the merge base of the target and the head with the head ("target...head"), within domain.DefaultDiffLimits. With opts.SinceLastRead it compares the head recorded by MarkRead with the current head instead, which works after a force-push as long as the recorded commit still exists; when none was recorded the diff is the default one, and when the recorded commit is gone too, with LastReadLost set. A merged or closed request keeps the head and the base it had.

opts.Files lists every changed file and reads the hunks of a window of them only; opts.Base and opts.Head make Get fail with domain.ErrConflict when the diff is no longer the one the caller shows (before any diff is read); opts.DiffOnly skips the commits. An invalid window gives domain.ErrInvalid.

func (*MergeRequests) List

List returns a page of the merge requests of a repository, highest number first. The open ones carry the head of their source branch. The page token names the last number of the previous page, so requests created between two pages never shift the others.

func (*MergeRequests) MarkRead

func (m *MergeRequests) MarkRead(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, head domain.ObjectID) (domain.ObjectID, error)

MarkRead records the head the reviewer has read, for the since-last-read diff of Get. The principal needs mr:write. A zero head means the current head of the source branch (the stored one for a merged or closed request); one that is not a commit of the repository gives domain.ErrInvalid. It returns the head recorded.

func (*MergeRequests) MarkReady added in v0.18.0

func (m *MergeRequests) MarkReady(ctx context.Context, p domain.Principal, name domain.RepoName, number int64) (domain.MergeRequest, error)

MarkReady implements ports.MergeRequestService: the title of an open draft loses its draft prefix, so the request can be merged. The principal needs mr:write. A request that is not open, or not a draft, gives domain.ErrPreconditionFailed.

func (*MergeRequests) Merge

Merge merges an open merge request, publishes MergeRequestMerged then a PushEvent for the target branch, and returns the merged request. The principal needs mr:merge. Merges of one repository run one at a time.

It fails with domain.ErrInvalid for a zero head, and with domain.ErrPreconditionFailed when the request is not open, the mode is not allowed by the settings of the repository, the source branch no longer points to the head ("branch moved, read again") or is gone, the target is gone, the mode's own condition does not hold (see below), or the branch brings nothing. Checks on the head must pass: none may be failed, queued or running; a head with no checks may be merged. params.IgnoreChecks bypasses this requirement and needs mr:force-merge.

The conditions are always checked with Git, under the lock, never from the stored mergeability. A fast-forward needs the target to be an ancestor of the head (the author rebases and pushes again) and moves the target to the head, with the old value as a precondition. A squash and a merge commit need the head to merge cleanly into the target (a behind branch is fine). A squash writes one commit with the merged tree, the target as parent and the author and message of domain.NewSquashCommit; a merge commit has two parents (the target, then the head) and the message of domain.NewMergeCommit. Both are signed by the instance key and move the target the same way.

With DeleteSource the merge is also refused, before anything changes, when the source is the default branch or the source or target of another open request.

Only the merge itself runs before Merge returns, under the lock. Afterwards, off the request path and without the lock: the deletion of the source branch (if it still points to the head; a failure is logged and leaves the branch), MergeRequestMerged then a PushEvent (which carries the deletion too), and the CI and mergeability hooks. Wait waits for them. SourceDeleted therefore only says that the deletion was requested.

func (*MergeRequests) Mergeability added in v0.28.0

func (m *MergeRequests) Mergeability(ctx context.Context, p domain.Principal, name domain.RepoName, number int64) (domain.Mergeability, error)

Mergeability implements ports.MergeRequestService: the mergeability of an open merge request for the current tips, queued when there is no up-to-date result. It is the zero value for a request that is not open, or without a mergeability service.

func (*MergeRequests) PushSummary added in v0.28.0

func (m *MergeRequests) PushSummary(ctx context.Context, p domain.Principal, repo domain.RepoName, oldHead, newTip domain.ObjectID) (ports.PushSummary, error)

PushSummary detects rewrites by ancestry, not log ordering, and counts at most ports.MaxPushSummaryCommits new commits. More distinguishes a longer range from a rewrite.

func (*MergeRequests) ResolveComment

func (m *MergeRequests) ResolveComment(ctx context.Context, p domain.Principal, name domain.RepoName, number, commentID int64, resolved bool) (domain.Comment, error)

ResolveComment marks a comment resolved or unresolved. The principal needs mr:write. An unknown comment gives domain.ErrNotFound and a request that is not open domain.ErrPreconditionFailed. Setting the state the comment already has succeeds and writes nothing.

func (*MergeRequests) Wait added in v0.28.0

func (m *MergeRequests) Wait(ctx context.Context)

Wait waits for the post-merge side effects still running (source branch deletion, events, CI and mergeability hooks), or for ctx to end. Call it before closing what they use.

func (*MergeRequests) WhileOpen added in v0.14.0

func (m *MergeRequests) WhileOpen(ctx context.Context, name domain.RepoName, number int64, fn func() error) error

WhileOpen implements ports.PullGate: fn runs under the merge lock, so a close or a merge of the request cannot complete in the middle of it, and not at all when the request is no longer open.

func (*MergeRequests) WithAutoMerge added in v0.31.0

func (m *MergeRequests) WithAutoMerge(t ports.AutoMergeTrigger) *MergeRequests

WithAutoMerge installs the asynchronous evaluator before serving requests.

func (*MergeRequests) WithMergeLock added in v0.31.0

func (m *MergeRequests) WithMergeLock(ctx context.Context, repo domain.RepoName, fn func() error) error

WithMergeLock serializes fn with repository merges and closes.

func (*MergeRequests) WithMirror added in v0.30.0

func (m *MergeRequests) WithMirror(t ports.MirrorTrigger) *MergeRequests

WithMirror makes a merge notify outbound mirrors. Call before the first merge.

func (*MergeRequests) WithVerifier added in v0.26.0

func (m *MergeRequests) WithVerifier(v ports.CommitVerifier) *MergeRequests

WithVerifier makes GetMergeRequest fill the signature state of the commits it lists (ADR 0030).

type MergeRequestsConfig

type MergeRequestsConfig struct {
	// Issues and IssueLinks optionally enrich merge request details.
	Issues     ports.IssueStore
	IssueLinks ports.IssueLinkStore
	Links      ports.IssueLinkTrigger // Optional derived issue links.
	Meta       ports.RepoMetaStore
	Store      ports.MergeRequestStore
	// Repos gives the default branch of a repository.
	Repos ports.GitRepos
	Git   ports.MergeGit
	// Signer signs the squash commits.
	Signer ports.CommitSigner
	// Committer is recorded as the committer of the squash commits.
	Committer domain.Identity
	Bus       ports.EventBus
	Clock     ports.Clock
	// Maint queues the maintenance that follows a merge, as it does a push.
	Maint ports.MaintenanceScheduler
	// CI starts the workflows a merge triggers (GitHub runs "push" workflows
	// on a merge) and Checks gives the checks the merge rule reads. Both are
	// optional: without them there is no CI.
	CI     ports.WorkflowTrigger
	Checks ports.CIChecksReader
	// PullRequests starts the pull_request workflows when a request is opened,
	// closed or merged (the push of its source branch reaches CI instead).
	// Optional.
	PullRequests ports.PullRequestTrigger
	// RefTips records who moves the target branch (and deletes the record of a
	// deleted source branch) before the merge does, as a push does
	// (domain.CIRefTip). Optional.
	RefTips ports.CIRefTipStore
	// Mergeability is queued when a request is opened, closed or merged and
	// read by Get and List. Optional: without it Mergeability stays zero.
	Mergeability MergeabilityHook
	// Journal records the opened, merged and closed requests. Optional. A
	// merge is recorded as such, not as a push to its target.
	Journal ports.JournalRecorder
}

MergeRequestsConfig holds what the merge request use cases need. Every field is required.

type Mergeability added in v0.28.0

type Mergeability struct {
	// contains filtered or unexported fields
}

Mergeability reconciles derived records, with one writer per repository. Its work and retry state are bounded by the number of repositories.

func NewMergeability added in v0.28.0

func NewMergeability(ctx context.Context, cfg MergeabilityConfig) *Mergeability

NewMergeability builds a reconciler whose lifetime is controlled by Close.

func (*Mergeability) Close added in v0.28.0

func (s *Mergeability) Close(_ context.Context)

Close cancels workers and pending retry waits, then waits for them.

func (*Mergeability) OnBranchesMoved added in v0.28.0

func (s *Mergeability) OnBranchesMoved(_ context.Context, repo domain.RepoName, updates []domain.RefUpdate)

OnBranchesMoved dirties the repository for any branch update.

func (*Mergeability) OnOpened added in v0.28.0

func (s *Mergeability) OnOpened(_ context.Context, mr domain.MergeRequest)

OnOpened implements ports.MergeabilityTrigger.

func (*Mergeability) Read added in v0.28.0

Read returns a current record, or checking and requests reconciliation.

func (*Mergeability) Start added in v0.28.0

func (s *Mergeability) Start()

Start starts workers and discovers repositories, retrying failed discovery.

func (*Mergeability) WithAutoMerge added in v0.31.0

func (s *Mergeability) WithAutoMerge(t ports.AutoMergeTrigger) *Mergeability

WithAutoMerge installs the evaluator before Start.

type MergeabilityConfig added in v0.28.0

type MergeabilityConfig struct {
	AutoMerge ports.AutoMergeTrigger
	Store     ports.MergeRequestStore
	Meta      ports.RepoMetaStore
	Git       ports.MergeGit
	Bus       ports.EventBus
	Clock     ports.Clock
	Workers   int // defaults to 2
	// After is injectable to drive backoff without wall-clock waits in tests.
	After func(time.Duration) <-chan time.Time
}

MergeabilityConfig holds the dependencies of the repository reconciler.

type MergeabilityHook added in v0.28.0

type MergeabilityHook interface {
	ports.MergeabilityTrigger
	ports.MergeabilityReader
}

MergeabilityHook is the unchanged trigger and reader contract.

type Mirrors added in v0.30.0

type Mirrors struct {
	// contains filtered or unexported fields
}

Mirrors manages outbound repository mirrors.

func NewMirrors added in v0.30.0

func NewMirrors(cfg MirrorsConfig) *Mirrors

NewMirrors builds the mirror use case.

func (*Mirrors) Badge added in v0.30.0

Badge returns a viewer-safe state and optional administrative reason.

func (*Mirrors) Close added in v0.30.0

func (m *Mirrors) Close(ctx context.Context)

Close cancels workers and waits until they stop or the caller's deadline expires.

func (*Mirrors) GetGlobalConfig added in v0.30.0

func (m *Mirrors) GetGlobalConfig(ctx context.Context, p domain.Principal) (domain.MirrorGlobalConfig, error)

GetGlobalConfig returns server-wide policy to an owner.

func (*Mirrors) GetStatus added in v0.30.0

GetStatus returns a read-authorized status, redacting administrative diagnostics.

func (*Mirrors) ListStatus added in v0.30.0

func (m *Mirrors) ListStatus(ctx context.Context, p domain.Principal) ([]ports.MirrorStatusView, error)

ListStatus returns all statuses to an owner, from three reads instead of three per repository.

func (*Mirrors) OnPush added in v0.30.0

func (m *Mirrors) OnPush(_ context.Context, repo domain.RepoName, _ []domain.RefUpdate)

OnPush marks the repository dirty without I/O or waiting for a worker.

func (*Mirrors) RemoveRepoMirror added in v0.30.0

func (m *Mirrors) RemoveRepoMirror(ctx context.Context, p domain.Principal, repo domain.RepoName) error

RemoveRepoMirror restores inherited policy.

func (*Mirrors) RotateKey added in v0.30.0

RotateKey immediately replaces the mirror key and returns the replaced one (zero when there was none, or when it could not be opened). Remote authorization must be updated separately.

func (*Mirrors) ScheduleNow added in v0.30.0

func (m *Mirrors) ScheduleNow(ctx context.Context)

ScheduleNow examines the current minute; missed minutes are not replayed. A repository that was due but found the queue full is kept and retried on the next calls, so a full queue delays a sync instead of skipping a period. Workers must be running (Start).

func (*Mirrors) SetGlobalConfig added in v0.30.0

SetGlobalConfig patches server-wide policy and resets affected mirror incidents.

func (*Mirrors) SetRepoExcluded added in v0.30.0

func (m *Mirrors) SetRepoExcluded(ctx context.Context, p domain.Principal, repo domain.RepoName, excluded bool) (domain.MirrorTarget, error)

SetRepoExcluded updates exclusion without discarding an override.

func (*Mirrors) SetRepoMirror added in v0.30.0

SetRepoMirror sets a repository override.

func (*Mirrors) ShowKey added in v0.30.0

ShowKey lazily generates the server's mirror key for authenticated callers.

func (*Mirrors) Start added in v0.30.0

func (m *Mirrors) Start()

Start starts the bounded worker pool and UTC scheduler exactly once.

func (*Mirrors) Sync added in v0.30.0

Sync waits for its own run; overlapping requests coalesce into one sticky rerun. When the push outlasts the wait bound the result is Queued: the sync goes on in the background and its outcome is in the mirror status.

func (*Mirrors) SyncAll added in v0.30.0

func (m *Mirrors) SyncAll(ctx context.Context, p domain.Principal, force bool) ([]ports.MirrorSyncResult, error)

SyncAll syncs every repository whose mirror is enabled, as an owner. Every repository is enqueued first, then all outcomes are awaited together under one wait bound, so the total time is about one push, not one per repository. A failing repository does not stop the others: its outcome carries the error; one still running at the bound is Queued. The returned error is only for what prevents the whole request (authorization, listing, cancellation). Results are in listing order.

func (*Mirrors) Wait added in v0.30.0

func (m *Mirrors) Wait(ctx context.Context)

Wait blocks until no sync is queued or running, or ctx is done. It is a test barrier: call it only while nothing enqueues new syncs and before Close (queued jobs are not drained by Close).

func (*Mirrors) Warning added in v0.30.0

func (m *Mirrors) Warning(ctx context.Context, p domain.Principal, repo domain.RepoName) string

Warning provides the post-receive diagnostic of a failing or paused mirror to the admins of the repository only: it names the remote and the reason.

type MirrorsConfig added in v0.30.0

type MirrorsConfig struct {
	Store        ports.MirrorStore
	Keys         ports.MirrorKeyStore
	Sealer       ports.SecretSealer
	Pusher       ports.MirrorPusher
	Meta         ports.RepoMetaStore
	Git          ports.GitRepos
	Reader       ports.RepoReader
	Bus          ports.EventBus
	Clock        ports.Clock
	PublicURL    string
	ScheduleTick time.Duration
	Workers      int
	QueueSize    int // jobs waiting for a worker; default 64
	// SyncWait bounds how long a manual sync (Sync, SyncAll) holds its caller; past it the
	// result is Queued and the push continues. Default DefaultMirrorSyncWait.
	SyncWait time.Duration
	// ValidateURL checks a resolved mirror URL; default domain.ValidateMirrorURL. Tests of
	// local transports inject a permissive check.
	ValidateURL func(string) error
}

MirrorsConfig supplies the mirror use case's dependencies.

type OIDC added in v0.16.0

type OIDC struct {
	// contains filtered or unexported fields
}

OIDC is the key management of the provider and the signature of the tokens. It is safe for concurrent use.

func NewOIDC added in v0.16.0

func NewOIDC(ctx context.Context, cfg OIDCConfig) (*OIDC, error)

NewOIDC loads the signing keys, generates the first one when there is none, and forgets the retired keys whose publication is over. With cfg.Sealer nil it returns a disabled service: no ID tokens are issued. A stored key that does not open with the secrets key is an error: the server must not start (the secrets key check already refuses another key, so this is a damaged record).

func (*OIDC) Discovery added in v0.16.0

func (o *OIDC) Discovery() ([]byte, error)

Discovery returns the JSON of the OpenID Provider configuration, in the shape of GitHub's.

func (*OIDC) Enabled added in v0.28.0

func (o *OIDC) Enabled() bool

Enabled reports whether the provider issues ID tokens. A nil provider is disabled.

func (*OIDC) Issuer added in v0.16.0

func (o *OIDC) Issuer() string

Issuer is the iss of the tokens.

func (*OIDC) JWKS added in v0.16.0

func (o *OIDC) JWKS() ([]byte, error)

JWKS returns the JSON of the published keys.

func (*OIDC) RotateKey added in v0.16.0

func (o *OIDC) RotateKey(ctx context.Context, p domain.Principal) (ports.OIDCRotation, error)

RotateKey replaces the signing key by a new one. The old one signs no more and stays in the JWKS for domain.OIDCKeyRetention. Retired keys whose publication is over are deleted with it. Only the owner may rotate.

func (*OIDC) TokenURL added in v0.16.0

func (o *OIDC) TokenURL() string

TokenURL is the token endpoint as a job gets it (Job.id_token_url): it has a query already, so clients append "&audience=".

type OIDCConfig added in v0.16.0

type OIDCConfig struct {
	Store ports.OIDCKeyStore
	// Sealer seals the private keys (ERMITE_SECRETS_KEY). Leave it unset
	// rather than assigning a typed nil pointer.
	Sealer ports.SecretSealer
	// Tokens finds the request tokens of the jobs.
	Tokens    ports.CIRepoTokenStore
	Clock     ports.Clock
	PublicURL string
	// NewKey generates a signing key; nil generates an RSA key of OIDCKeyBits.
	// Tests give prepared keys.
	NewKey func() (*rsa.PrivateKey, error)
}

OIDCConfig holds what the OIDC provider needs. Store, Tokens, Clock and PublicURL are required; Sealer nil disables OIDC.

type Passkeys

type Passkeys struct {
	// contains filtered or unexported fields
}

Passkeys are the use cases of the owner's passkeys: registering, signing in, listing and removing them.

func NewPasskeys

func NewPasskeys(store ports.PasskeyStore, verifier ports.PasskeyVerifier, sessions *Sessions, clock ports.Clock) *Passkeys

NewPasskeys builds the passkey use cases. Sessions are issued and voided through sessions.

func (*Passkeys) BeginLogin

func (s *Passkeys) BeginLogin(_ context.Context, client string) (string, []byte, error)

BeginLogin starts a passkey sign-in. Anonymous callers may. client is the client key of the caller (clientlimit.Key of the address the failure limiter uses); it bounds how many sign-ins one client keeps waiting. It returns the ceremony identifier and the options for the browser.

func (*Passkeys) BeginRegistration

func (s *Passkeys) BeginRegistration(ctx context.Context, p domain.Principal, name string) (string, []byte, error)

BeginRegistration starts adding a passkey. Only the owner may. name is checked now (an empty name means the default), so that a name the owner would be prompted for and then see refused costs no ceremony; the ceremony keeps the checked name and FinishRegistration stores it. It gives domain.ErrResourceExhausted when MaxPasskeys passkeys exist or too many registrations wait. It returns the ceremony identifier and the options for the browser.

func (*Passkeys) FinishLogin

func (s *Passkeys) FinishLogin(ctx context.Context, ceremonyID string, response []byte) (token string, expiresAt time.Time, err error)

FinishLogin checks the browser's response and opens the owner's session, like a login link does. Whatever the reason of a refusal (unknown or expired ceremony, unknown passkey, bad signature, missing user verification, cloned authenticator), it gives domain.ErrUnauthenticated; a failure of the store is returned as it is, so that the caller fails closed.

func (*Passkeys) FinishRegistration

func (s *Passkeys) FinishRegistration(ctx context.Context, p domain.Principal, ceremonyID string, response []byte) error

FinishRegistration checks the browser's response and stores the passkey under the name BeginRegistration checked. A ceremony that is unknown, expired, used or of the wrong kind, and a response the verifier refuses, give domain.ErrUnauthenticated; a credential already registered gives domain.ErrAlreadyExists; too many passkeys, domain.ErrResourceExhausted.

func (*Passkeys) List

func (s *Passkeys) List(ctx context.Context, p domain.Principal) ([]domain.Passkey, error)

List returns the passkeys, oldest first. Only the owner may.

func (*Passkeys) Remove

func (s *Passkeys) Remove(ctx context.Context, p domain.Principal, id []byte) error

Remove deletes a passkey and signs every browser out, like revoking a token with owner rights does. Only the owner may. An unknown id gives domain.ErrNotFound.

type Pushes

type Pushes struct {
	// contains filtered or unexported fields
}

Pushes are the use cases around a Git push.

func NewPushes

func NewPushes(meta ports.RepoMetaStore, repos *Repos, git ports.GitRepos, bus ports.EventBus, clock ports.Clock, maint ports.MaintenanceScheduler, auth ports.Revalidator) *Pushes

NewPushes builds the push use cases. Every argument is required: auth re-validates the token of a push when its pre-receive hook runs, maint queues the maintenance that follows a push.

func (*Pushes) Check

func (u *Pushes) Check(ctx context.Context, p domain.Principal, name domain.RepoName, updates []domain.RefUpdate, objects ports.PushObjects) error

Check is the pre-receive decision. It accepts a push of a principal that may write the repository. A token limited to branch prefixes may only update (create, move or delete) branches under them: the first ref outside them refuses the whole push, tags and every other ref included. Further rules (protected branches, limits) will refuse here too, by returning an error that matches domain.ErrRejected.

The prefix rule is domain.Principal.AllowsRef and shares its assumptions: no symbolic ref under refs/heads besides HEAD (ermite never creates one and receive-pack cannot), and case-sensitive ref storage (Linux). The hook sees every ref of the push, so a tag or any other ref is judged like a branch.

The token of the push was valid when the request began, but a push can run for long: it is re-validated here, so a token revoked or expired meanwhile stops the push before Git applies it. A failure to tell refuses it too.

The token of a CI job (GITHUB_TOKEN with contents: write) may not change workflows (CI-3, D3). The push is refused as a whole, on any ref, branch or tag, when a commit it brings changes .github/workflows (it has workflows that differ from those of every parent; removing them is allowed), or when a ref would end with workflows other than it had: an update with other than the old ones, a new ref with other than those of the default branch head (or any, when there is no head). A new branch cut from existing commits that change nothing is therefore allowed; deleting a ref is not a change. objects locate the objects of the push, which are not in the repository yet.

func (*Pushes) Open

func (u *Pushes) Open(ctx context.Context, p domain.Principal, name domain.RepoName, create bool) error

Open decides whether the principal may push to the repository and, with create, creates it when it does not exist yet (private, published as RepoCreated by Repos.Create). create is for the first request of a push only.

Anonymous callers get domain.ErrUnauthenticated. A repository the caller cannot read and cannot write, or a missing one it may not create, answers domain.ErrNotFound, so that a private repository never shows itself. One it can read but not write answers domain.ErrForbidden.

func (*Pushes) Record

func (u *Pushes) Record(ctx context.Context, p domain.Principal, name domain.RepoName, updates []domain.RefUpdate) error

Record is the post-receive step of an accepted push: it sets pushed_at and active_at, repairs HEAD when it points to a missing branch, tells the reader that the repository changed, queues the workflows the push triggers, writes the journal, publishes a PushEvent and schedules maintenance. Every step runs even when an earlier one fails; the failures are returned together and the push itself is not affected.

func (*Pushes) WithFreshener

func (u *Pushes) WithFreshener(f ports.RepoFreshener) *Pushes

WithFreshener makes Record tell the reader that a repository changed, so that it serves the push at once. Call it before the first push; it returns u.

func (u *Pushes) WithIssueLinks(t ports.IssueLinkTrigger) *Pushes

WithIssueLinks enables synchronous best-effort issue indexing before the first push.

func (*Pushes) WithJournal added in v0.41.0

func (u *Pushes) WithJournal(j ports.JournalRecorder) *Pushes

WithJournal makes Record write the tags and the pushes to the default branch to the activity journal. Call it before the first push; it returns u.

func (*Pushes) WithMergeability added in v0.28.0

func (u *Pushes) WithMergeability(t ports.MergeabilityTrigger) *Pushes

WithMergeability makes Record queue the mergeability checks of the merge requests whose branches the push moved. Call it before the first push; it returns u.

func (*Pushes) WithMirror added in v0.30.0

func (u *Pushes) WithMirror(t ports.MirrorTrigger) *Pushes

WithMirror makes Record notify outbound mirrors. Call before the first push.

func (*Pushes) WithPushTreeReader added in v0.28.0

func (u *Pushes) WithPushTreeReader(g ports.PushTreeReader) *Pushes

WithPushTreeReader lets Check tell whether a push changes workflow files, which a push by a job token may not do. Without it a push by a job token is refused, as it cannot be judged. Call it before the first push; it returns u.

func (*Pushes) WithRefTips added in v0.15.0

func (u *Pushes) WithRefTips(t ports.CIRefTipStore) *Pushes

WithRefTips makes Check record who is about to move each branch and tag (domain.CIRefTip), before Git moves it. Call it before the first push; it returns u.

func (*Pushes) WithRefsObserver added in v0.41.0

func (u *Pushes) WithRefsObserver(o ports.RefsObserver) *Pushes

WithRefsObserver makes Record tell o that the refs moved, before the journal and the events. Call it before the first push; it returns u.

func (*Pushes) WithReleases added in v0.32.0

func (u *Pushes) WithReleases(t ports.ReleaseTagTrigger) *Pushes

WithReleases enables synchronous release tag-deletion handling before the first push.

func (*Pushes) WithWorkflowTrigger

func (u *Pushes) WithWorkflowTrigger(t ports.WorkflowTrigger) *Pushes

WithWorkflowTrigger makes Record start the workflows that match the push. Call it before the first push; it returns u.

type ReconcileReport

type ReconcileReport struct {
	// Registered are directories without metadata, now registered private.
	Registered []domain.RepoName
	// MissingDirs are repositories with metadata and no directory. They are
	// left untouched.
	MissingDirs []domain.RepoName
	// InvalidDirs are directory entries that cannot be repositories.
	InvalidDirs []string
}

ReconcileReport tells what Reconcile found.

type Releases added in v0.32.0

type Releases struct {
	// contains filtered or unexported fields
}

Releases manages release metadata and blob contents. Construct once per server.

func NewReleases added in v0.32.0

NewReleases constructs the release service. Zero limits select the defaults. pushes must be the shared Pushes instance, so created tags notify every hook.

func (*Releases) AssetsOf added in v0.32.0

func (r *Releases) AssetsOf(ctx context.Context, name domain.RepoName) ([]domain.ReleaseAsset, error)

AssetsOf snapshots assets before the repository's metadata is removed.

func (*Releases) CreateRelease added in v0.32.0

func (r *Releases) CreateRelease(ctx context.Context, p domain.Principal, name domain.RepoName, params ports.CreateReleaseParams) (domain.Release, error)

CreateRelease creates a release, optionally creating its tag when publishing.

func (*Releases) DeleteAsset added in v0.32.0

func (r *Releases) DeleteAsset(ctx context.Context, p domain.Principal, repo domain.RepoName, id int64) error

DeleteAsset refunds quota before deleting the blob.

func (*Releases) DeleteRelease added in v0.32.0

func (r *Releases) DeleteRelease(ctx context.Context, p domain.Principal, name domain.RepoName, id int64) error

DeleteRelease removes metadata first, then its contents.

func (*Releases) DownloadAsset added in v0.32.0

func (r *Releases) DownloadAsset(ctx context.Context, p domain.Principal, repo domain.RepoName, ref ports.AssetRef, offset, length int64, send func(ports.AssetChunk) error) error

DownloadAsset sends metadata followed by bounded chunks of the requested range.

func (*Releases) GetAsset added in v0.32.0

GetAsset resolves an uploaded asset, enforcing its release's draft rule.

func (*Releases) GetRelease added in v0.32.0

func (r *Releases) GetRelease(ctx context.Context, p domain.Principal, name domain.RepoName, ref ports.ReleaseRef) (domain.Release, error)

GetRelease resolves a visible release by exactly one selector.

func (*Releases) ListAssets added in v0.32.0

func (r *Releases) ListAssets(ctx context.Context, p domain.Principal, repo domain.RepoName, id int64) ([]domain.ReleaseAsset, error)

ListAssets returns completed assets only, ordered by name.

func (*Releases) ListReleases added in v0.32.0

func (r *Releases) ListReleases(ctx context.Context, p domain.Principal, name domain.RepoName, size int, token string) (ports.ReleasePage, error)

ListReleases lists visible releases newest first, with an ID cursor.

func (*Releases) OnRefsMoved added in v0.32.0

func (r *Releases) OnRefsMoved(ctx context.Context, name domain.RepoName, updates []domain.RefUpdate)

OnRefsMoved unpublishes releases of deleted tags; recreated tags stay drafts.

func (*Releases) OnRepoDeleted added in v0.32.0

func (r *Releases) OnRepoDeleted(ctx context.Context, _ domain.RepoName, assets []domain.ReleaseAsset)

OnRepoDeleted removes contents after repository metadata has been deleted.

func (*Releases) Sweep added in v0.32.0

func (r *Releases) Sweep(ctx context.Context)

Sweep collects abandoned uploads, missing contents, and old orphan blobs. A failed metadata scan never authorizes blob deletion. Sweeps are serialized so the two observations of a missing blob are always distinct sweeps.

func (*Releases) UpdateAsset added in v0.32.0

UpdateAsset changes an uploaded asset's name and label without moving bytes.

func (*Releases) UpdateRelease added in v0.32.0

func (r *Releases) UpdateRelease(ctx context.Context, p domain.Principal, name domain.RepoName, id int64, params ports.UpdateReleaseParams) (domain.Release, error)

UpdateRelease changes only fields whose pointers are non-nil.

func (*Releases) UploadAsset added in v0.32.0

func (r *Releases) UploadAsset(ctx context.Context, p domain.Principal, name domain.RepoName, params ports.UploadAssetParams, body io.Reader) (result domain.ReleaseAsset, err error)

UploadAsset reserves quota before reading, stores bytes, then publishes metadata.

func (*Releases) Usage added in v0.32.0

Usage reports quota and configured limits to a repository reader.

func (*Releases) WithJournal added in v0.41.0

func (r *Releases) WithJournal(j ports.JournalRecorder) *Releases

WithJournal makes the publication of a release a journal entry. Call it before the first release; it returns r.

type ReleasesGitHub added in v0.32.0

type ReleasesGitHub struct {
	// contains filtered or unexported fields
}

ReleasesGitHub builds the GitHub REST representations of releases. Authorization and all mutations remain in Releases; it owns no metadata or contents.

func NewReleasesGitHub added in v0.32.0

func NewReleasesGitHub(rel *Releases, owner, publicURL string) *ReleasesGitHub

NewReleasesGitHub wraps the shared release service. publicURL is the external server URL (without the API prefix), used in upload and browser download URLs.

func (*ReleasesGitHub) AssetByID added in v0.32.0

func (g *ReleasesGitHub) AssetByID(ctx context.Context, p domain.Principal, owner, repo string, id int64) (domain.ReleaseAsset, error)

AssetByID resolves a visible asset, including its repository ownership.

func (*ReleasesGitHub) CreateRelease added in v0.32.0

func (g *ReleasesGitHub) CreateRelease(ctx context.Context, p domain.Principal, owner, repo string, body []byte) ([]byte, error)

CreateRelease accepts GitHub's create body and returns its representation.

func (*ReleasesGitHub) DeleteAsset added in v0.32.0

func (g *ReleasesGitHub) DeleteAsset(ctx context.Context, p domain.Principal, owner, repo string, id int64) error

DeleteAsset removes the asset record and its blob.

func (*ReleasesGitHub) DeleteRelease added in v0.32.0

func (g *ReleasesGitHub) DeleteRelease(ctx context.Context, p domain.Principal, owner, repo string, id int64) error

DeleteRelease removes a release and all its assets.

func (*ReleasesGitHub) GetAsset added in v0.32.0

func (g *ReleasesGitHub) GetAsset(ctx context.Context, p domain.Principal, owner, repo string, id int64) ([]byte, error)

GetAsset returns the JSON representation, not the contents.

func (*ReleasesGitHub) GetRelease added in v0.32.0

func (g *ReleasesGitHub) GetRelease(ctx context.Context, p domain.Principal, owner, repo string, ref ports.ReleaseRef) ([]byte, error)

GetRelease resolves an id, tag or latest selector.

func (*ReleasesGitHub) ListAssets added in v0.32.0

func (g *ReleasesGitHub) ListAssets(ctx context.Context, p domain.Principal, owner, repo string, id int64, page, perPage int) ([]byte, string, error)

ListAssets lists uploaded assets with the same pagination conventions.

func (*ReleasesGitHub) ListReleases added in v0.32.0

func (g *ReleasesGitHub) ListReleases(ctx context.Context, p domain.Principal, owner, repo string, page, perPage int) ([]byte, string, error)

ListReleases returns visible releases newest first and GitHub Link pagination.

func (*ReleasesGitHub) UpdateAsset added in v0.32.0

func (g *ReleasesGitHub) UpdateAsset(ctx context.Context, p domain.Principal, owner, repo string, id int64, body []byte) ([]byte, error)

UpdateAsset changes the name and label fields that are present.

func (*ReleasesGitHub) UpdateRelease added in v0.32.0

func (g *ReleasesGitHub) UpdateRelease(ctx context.Context, p domain.Principal, owner, repo string, id int64, body []byte) ([]byte, error)

UpdateRelease changes only present fields, including explicit false and empty strings.

func (*ReleasesGitHub) UploadAsset added in v0.32.0

func (g *ReleasesGitHub) UploadAsset(ctx context.Context, p domain.Principal, owner, repo string, id int64, name, label, contentType string, size int64, body io.Reader) ([]byte, error)

UploadAsset streams an exact-sized raw body; duplicate names are not replaced.

type Repos

type Repos struct {
	// contains filtered or unexported fields
}

Repos are the repository use cases.

func NewRepos

func NewRepos(meta ports.RepoMetaStore, git ports.GitRepos, bus ports.EventBus, clock ports.Clock) *Repos

NewRepos builds the repository use cases over their ports. Changes are published on bus.

func (*Repos) Create

func (r *Repos) Create(ctx context.Context, p domain.Principal, params ports.CreateRepoParams) (domain.Repo, error)

Create makes a repository and publishes RepoCreated: it stages a bare repository, records the metadata (which fails with domain.ErrAlreadyExists if the name is taken, also when a concurrent Create wins) and publishes the directory. A failure after the metadata is written removes it again and discards the staged repository.

The principal needs repo:write on the name and no branch prefixes: anonymous callers get domain.ErrUnauthenticated, other callers without it (a token limited to some branches included) domain.ErrForbidden.

func (*Repos) Delete

func (r *Repos) Delete(ctx context.Context, p domain.Principal, name domain.RepoName) error

Delete moves the repository directory to the trash, removes its metadata and publishes RepoDeleted. The principal needs repo:admin on it (see adminAccess) and no branch prefixes. A repository whose directory is already gone, left by a delete that failed half way, loses its metadata.

func (*Repos) Get

func (r *Repos) Get(ctx context.Context, p domain.Principal, name domain.RepoName) (domain.Repo, error)

Get returns a repository. A repository the principal may not read answers domain.ErrNotFound, like a missing one.

func (*Repos) List

func (r *Repos) List(ctx context.Context, p domain.Principal) ([]domain.Repo, error)

List returns the repositories the principal may read, ordered by name.

func (*Repos) ListPage

func (r *Repos) ListPage(ctx context.Context, p domain.Principal, size int, token string) (ports.RepoPage, error)

ListPage returns a page of the repositories the principal may read, ordered by name. size is 0 for DefaultPageSize and lowered to MaxRepoPageSize; the page token names the last repository of the previous page, so repositories that appear or disappear between two pages never shift the others.

func (*Repos) Reconcile

func (r *Repos) Reconcile(ctx context.Context) (ReconcileReport, error)

Reconcile aligns the repository directories with the metadata at startup: it removes staging leftovers, registers a directory without metadata as private, skips invalid directory names, and reports metadata without a directory at error level without touching it.

func (*Repos) Update

Update changes the description, the topics, the visibility, the default branch and the merge settings and archived state of a repository, and publishes RepoUpdated. The principal needs repo:admin on it (see adminAccess for what the others learn) and no branch prefixes. At least one field must be set, else domain.ErrInvalid. A default branch that does not exist gives domain.ErrPreconditionFailed; nothing is written before every check passed. The metadata is written first, then HEAD, and RepoUpdated is published after both. When only the HEAD move fails, RepoUpdated is still published for the stored metadata and the HEAD error is returned.

func (*Repos) View

func (r *Repos) View(ctx context.Context, repo domain.Repo) (ports.RepoView, error)

View adds the default branch to a repository the caller has already been allowed to see. A repository without a directory has no default branch.

func (*Repos) WithAutoMerge added in v0.31.0

func (r *Repos) WithAutoMerge(t ports.AutoMergeTrigger) *Repos

WithAutoMerge installs the settings-change trigger before serving requests.

func (*Repos) WithCIRunCleaner

func (r *Repos) WithCIRunCleaner(c ports.CIRunCleaner) *Repos

WithCIRunCleaner makes Delete remove the CI logs of the repository. Call it before the first request; it returns r.

func (*Repos) WithMergeGate added in v0.31.0

func (r *Repos) WithMergeGate(g ports.MergeGate) *Repos

WithMergeGate installs the settings-write gate before serving requests.

func (*Repos) WithMirror added in v0.30.0

func (r *Repos) WithMirror(t ports.MirrorTrigger) *Repos

WithMirror makes a default-branch change notify outbound mirrors. Call it before the first request; it returns r.

func (*Repos) WithReleases added in v0.32.0

func (r *Repos) WithReleases(c ports.ReleaseRepoCleaner) *Repos

WithReleases makes Delete remove release blobs after metadata deletion. Call it before the first request; it returns r.

type Sessions

type Sessions struct {
	// contains filtered or unexported fields
}

Sessions are the use cases of the owner's browser sessions: login links, session tokens and signing out everywhere. Login links live in memory only, so a restart voids them; the signing key and the epoch are in the store.

func NewSessions

func NewSessions(ctx context.Context, store ports.SessionStore, clock ports.Clock, publicURL string) (*Sessions, error)

NewSessions builds the session use cases and loads the signing key, which the store generates on first start. publicURL is the base URL the login links are built on.

func (s *Sessions) CreateLoginLink(ctx context.Context, p domain.Principal) (ports.LoginLink, error)

CreateLoginLink makes a link valid for LoginLinkTTL and usable once. Only the owner may. The session the link opens never outlives the token that created it: it is capped at that token's expiry (no cap for root, or a token that never expires). It gives domain.ErrResourceExhausted when MaxPendingLoginLinks links wait. The nonce is never logged.

func (*Sessions) Login

func (s *Sessions) Login(ctx context.Context, nonce string) (token string, expiresAt time.Time, err error)

Login consumes the link and opens a session: it returns the signed session token, for the cookie, and when it expires. A link that is unknown, expired or already used gives domain.ErrUnauthenticated, whatever the reason. The nonce and the token are never logged. The session lasts SessionTTL, or until the expiry of the token that created the link when that comes first; a link whose token has expired since gives domain.ErrUnauthenticated.

func (*Sessions) LoginLinkValid

func (s *Sessions) LoginLinkValid(nonce string) bool

LoginLinkValid reports whether the nonce is a pending link, without using it: opening a link shows a confirmation, only confirming consumes it.

func (*Sessions) SignOutEverywhere

func (s *Sessions) SignOutEverywhere(ctx context.Context, p domain.Principal) error

SignOutEverywhere ends every session by incrementing the epoch. Only the owner may. Pending login links stay valid.

func (*Sessions) Verify

func (s *Sessions) Verify(ctx context.Context, token string) (domain.Principal, error)

Verify checks a session token and returns the owner. The signature must be HS256 with the session key (the algorithm is pinned, so "none" and any other are refused), the token must not be expired or issued in the future, and its epoch must be the current one. Any such failure gives domain.ErrUnauthenticated; a store failure is returned as it is, so that the caller fails closed.

type Signatures added in v0.26.0

type Signatures struct {
	// contains filtered or unexported fields
}

Signatures manages the trusted keys and tells whether commits are signed by them (ADR 0030). The keys are loaded once and kept in memory: this process is their only writer.

func NewSignatures added in v0.26.0

func NewSignatures(store ports.TrustedKeyStore, inspector ports.KeyInspector, verifiers map[domain.SignatureFormat]ports.SignatureVerifier,
	objects ports.CommitObjectReader, builtin domain.TrustedKey, clock ports.Clock) (*Signatures, error)

NewSignatures loads the stored trusted keys. builtin is the instance signing key (Builtin, SSH, ID = its fingerprint), built by the caller; a zero ID means there is none.

func (*Signatures) AddTrustedKey added in v0.26.0

func (s *Signatures) AddTrustedKey(ctx context.Context, p domain.Principal, format domain.SignatureFormat, title, publicKey string, emails []string) (domain.TrustedKey, error)

AddTrustedKey stores a key. Only the owner may. The instance key cannot be added again (domain.ErrAlreadyExists), and at most domain.MaxTrustedKeys keys are stored (domain.ErrResourceExhausted).

func (*Signatures) ListTrustedKeys added in v0.26.0

func (s *Signatures) ListTrustedKeys(_ context.Context, p domain.Principal) ([]domain.TrustedKey, error)

ListTrustedKeys lists the instance key first, then the stored keys oldest first. Any authenticated caller may.

func (*Signatures) RemoveTrustedKey added in v0.26.0

func (s *Signatures) RemoveTrustedKey(ctx context.Context, p domain.Principal, id string) error

RemoveTrustedKey deletes a key. Only the owner may. The instance key is refused with domain.ErrPreconditionFailed.

func (*Signatures) VerifyCommits added in v0.26.0

func (s *Signatures) VerifyCommits(ctx context.Context, repo domain.RepoName, commits []domain.Commit) map[domain.ObjectID]domain.CommitVerification

VerifyCommits checks the signature of the commits against the trusted keys. Results are cached by repository, commit and trust generation. When the raw commits cannot be read, only the cached results are returned.

type SigningKey

type SigningKey struct {
	// contains filtered or unexported fields
}

SigningKey is the public side of the key that signs the commits ermite creates (ADR 0022).

func NewSigningKey

func NewSigningKey(signer ports.CommitSigner, committer domain.Identity) *SigningKey

NewSigningKey builds the use case over the signer and the identity recorded as the committer of the signed commits.

func (*SigningKey) Info

func (s *SigningKey) Info() domain.SigningKeyInfo

Info returns what a verifier needs. It is public: no access rule applies, and the private key never leaves the signer.

type Tokens

type Tokens struct {
	// contains filtered or unexported fields
}

Tokens are the use cases that manage stored tokens.

func NewTokens

func NewTokens(store ports.TokenStore, clock ports.Clock, sessions ports.SessionStore) *Tokens

NewTokens builds the token use cases over their ports. sessions is where revoking a token with owner rights ends every web session (see Revoke).

func (*Tokens) Create

Create makes a stored token and returns it with its secret.

The principal needs token:admin: anonymous callers get domain.ErrUnauthenticated, others without it domain.ErrForbidden. A token cannot create a token more powerful than itself: every scope must be one it holds, the repositories must lie within its list (a limited token cannot create an unlimited one), the same for branch prefixes (each must start with one of its own) and the expiry must not outlast its own. Violations give domain.ErrForbidden. Root has no such limits. Invalid settings give domain.ErrInvalidToken (this includes the labels "owner" and "root", which are reserved, in any case and around spaces), an unknown scope domain.ErrInvalidScope.

func (*Tokens) List

func (t *Tokens) List(ctx context.Context, p domain.Principal) ([]domain.Token, error)

List returns the stored tokens the principal could have created, oldest first: those whose scopes, repositories, branch prefixes and expiry lie within its own (the no-escalation rule of Create). Root sees every token. It needs token:admin.

func (*Tokens) ListPage

func (t *Tokens) ListPage(ctx context.Context, p domain.Principal, size int, token string) (ports.TokenPage, error)

ListPage returns a page of the tokens List returns, newest first (then by id). size and the page token work like Repos.ListPage; the token names the last token of the previous page.

func (*Tokens) Revoke

func (t *Tokens) Revoke(ctx context.Context, p domain.Principal, id string) error

Revoke deletes a stored token; it stops working at once. It needs token:admin and returns domain.ErrNotFound for an unknown id, and also for a token more powerful than the principal (the no-escalation rule of Create), so that it does not show itself. Root may revoke any token.

A token that has owner rights (HasOwnerRights: every scope, no repository list and no branch prefixes) could open web sessions, so revoking it also signs every browser out, as `ermite web logout --all` does: web sessions are not tied to the token that opened them. The revocation holds even if signing out fails, which is then reported as an error.

Source Files

  • access.go
  • activity.go
  • automerge.go
  • bernard.go
  • bernard_disabled.go
  • bernard_jobs.go
  • bernard_open.go
  • browse.go
  • browse_stats.go
  • browsecache.go
  • ci.go
  • ci_actions.go
  • ci_artifacts.go
  • ci_cache.go
  • ci_concurrency.go
  • ci_deps.go
  • ci_dispatch.go
  • ci_events.go
  • ci_github.go
  • ci_githubapi.go
  • ci_oidc.go
  • ci_plan.go
  • ci_pull.go
  • ci_pullattempts.go
  • ci_pullcache.go
  • ci_pullfiles.go
  • ci_refevents.go
  • ci_repotoken.go
  • ci_results.go
  • ci_reusable.go
  • ci_runner.go
  • ci_runs.go
  • ci_schedule.go
  • ci_secrets.go
  • ci_settings.go
  • ci_tips.go
  • ci_trigger.go
  • ci_validate.go
  • ci_watch.go
  • ci_workflowrun.go
  • ci_workflows.go
  • edit.go
  • events.go
  • inbox.go
  • issue_keys.go
  • issue_links.go
  • issue_moderation.go
  • issue_submissions.go
  • issues.go
  • journal.go
  • maintenance.go
  • mergeability.go
  • mergerequests.go
  • mergerequests_auto.go
  • mirrors.go
  • mirrors_config.go
  • mirrors_key.go
  • mirrors_schedule.go
  • mirrors_sync.go
  • oidc.go
  • paging.go
  • passkeys.go
  • pushes.go
  • releases.go
  • releases_assets.go
  • releases_github.go
  • releases_sweep.go
  • repos.go
  • repos_write.go
  • sessions.go
  • signatures.go
  • signing.go
  • tokens.go

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL