Commit d19e5181c5

d19e5181c5fae0d0eae2d7d48971e938e5e287c4

parent: dc10160221

Verified · cmc

cmc <hello@cleberg.net> · 2026-09-28 07:04 UTC

wiki: reserved ci/ statuses, trusted reuse, required contexts

Closes #258

Layout: unified · split

.gitbay/wiki/API.org +16 −2
@@ -112,8 +112,8 @@ CI reports results through the same command surface (over SSH or the
112112JSON API with a full-scope token; reporting requires write access):
113113
114114#+begin_src sh
115gitbay status set <owner/name> <sha> --context build --state pending
116gitbay status set <owner/name> <sha> --context build --state success --url https://ci.example/run/1
115gitbay status set <owner/name> <sha> --context ext/build --state pending
116gitbay status set <owner/name> <sha> --context ext/build --state success --url https://ci.example/run/1
117117gitbay status list <owner/name> <sha> --json # {"combined": "...", "statuses": [...]}
118118#+end_src
119119
@@ -130,6 +130,20 @@ from outside through =status set= looks like. A repository where
130130nothing has ever reported merges. Each
131131report also emits a =status= event to webhooks.
132132
133Contexts starting with =ci/= are the instance's own: its builds queue,
134reuse, skip and finish them, and =status set= refuses them with exit 4,
135so a writer cannot mark =ci/test= green on a head the build has not
136passed. Report under another prefix, such as =ext/=.
137
138=repo settings require-contexts <repo> ext/deploy ci/test= names
139statuses the checks gate waits for whether or not they have reported:
140one that has not is =pending=, and =mr show= lists it as
141=ext/deploy=missing=. Naming any context turns =require-checks= on;
142with no contexts the command clears the list and leaves
143=require-checks= as it was. =require-checks off= keeps the list, which
144waits for nothing until the gate is on again. =repo settings show=
145prints both.
146
133147* Webhooks
134148
135149Per-repository outbound POSTs for repository events. Managed by repo
.gitbay/wiki/Architecture/07-CI-and-Supply-Chain.org +2 −2
@@ -23,7 +23,7 @@ commit instead of failing silently.
23231. *Queue.* The post-receive hook calls =queueJobs=
2424 (=internal/control/build.go=). Each job gets a =ci/<job>= status:
2525 =pending= when queued, =skipped= when path filters exclude it, or
26 =success= copied from an earlier build of the same tree (#177).
26 =success= copied from an earlier trusted build of the same tree on the same image (#177, #258).
2727 Merge requests from forks are queued against the target repository
2828 with =trusted = false=.
29292. *Claim.* A runner calls =runner next= over SSH
@@ -55,7 +55,7 @@ Who may do what:
5555| =repo secret set/remove/list= | admin on the repository |
5656| =repo runner add/remove= | admin on the repository |
5757| =runner next/log/done= | =runner= key attached to the repository, or admin |
58| =status set= | write on the repository (any context name; #258) |
58| =status set= | write on the repository; =ci/*= contexts refused (=status.go=) |
5959
6060* Runner isolation
6161
.gitbay/wiki/Architecture/09-Controls.org +2 −2
@@ -36,7 +36,7 @@ chapter names of OWASP ASVS 4.0 where one fits.
3636| Private resources indistinguishable from missing | in place | =resolveRepo= (=internal/control/repo.go=), =runGit=, smart HTTP |
3737| Credential scopes narrow account rights | in place | key and token scopes (=control.go=, =policy/access.go=) |
3838| Server-side write protections | in place | pre-receive =CheckPush=, signed commits (=internal/hookd/hookd.go=) |
39| Merge gates | partial | =MergeGates=; any writer can post a =ci/*= status (#258) |
39| Merge gates | in place | =MergeGates=; =ci/*= statuses written only by the build subsystem; required contexts |
4040| Admin functions isolated | in place | =admin= noun gated in =Dispatch=; =audit= admin-only |
4141| CSRF protection | in place | SameSite=Lax plus =checkOrigin= (=accounts.go=) |
4242| Typed confirmation for destructive web actions | in place | =internal/httpd/confirm.go= |
@@ -89,7 +89,7 @@ chapter names of OWASP ASVS 4.0 where one fits.
8989| Runner limited to attached repositories | in place | =runnerMayBuild= (=build.go=) |
9090| Build images fixed by the operator | in place | =--pull=never= |
9191| Build network egress restricted | gap | #260 |
92| Build results reused only across equal trust | gap | tree reuse ignores trust and image (#258) |
92| Build results reused only across equal trust | in place | =SuccessBuildForTree=, =SuccessBuildFor= (=internal/store/builds.go=) |
9393
9494** Availability and operations
9595
.gitbay/wiki/Architecture/10-Known-Gaps.org −1
@@ -10,7 +10,6 @@ what the 2026-09-27 review found; remove a row when its issue closes.
1010
1111| Issue | Area | Gap | Severity |
1212|-------+------------------+-----------------------------------------------------------------------+----------|
13| #258 | CI integrity | Any writer can post a =ci/*= status; tree reuse ignores trust and image | high |
1413| #259 | Recovery | No restore has been exercised; verification does not check git connectivity | high |
1514| #260 | CI network | Builds share the runner's source address; no egress policy | medium |
1615| #261 | Various | Migration foreign-key check after commit; three web writes bypass dispatch; documentation drift | medium |
.gitbay/wiki/CI.org +10 −1
@@ -5,7 +5,16 @@ Three mechanisms decide what a push does to CI, and they interact:
55- *Dedupe.* A job's result is a property of the commit's tree. A commit
66 that already has a passed, queued or running build for a job is not
77 queued again; a commit whose tree already passed a job gets that
8 result as its status, naming the build it came from (#177). A failed,
8 result as its status, naming the build it came from (#177). Only a trusted build counts, and for tree reuse only one on the
9 image the job names: a fork's green build does not stand for the
10 repository's own, so its commit is built again when it lands on a
11 branch (#258). A job that names no =image:= is compared as naming
12 none: its reuse does not notice the runner's default image changing,
13 because reuse is decided when the push is queued, before any runner
14 claims the build, and runners can differ in their default. Name the
15 image in =ci.yml= to tie reuse to it; after an operator changes a
16 runner's =-image=, =build trigger= builds a job afresh, since a
17 triggered build is never reused. A failed,
918 cancelled or abandoned build does not count: that commit runs again.
1019- *Path filters.* =paths= and =paths-ignore= on a job are evaluated
1120 against the files the push changed. The diff base is the old tip when
.gitbay/wiki/Parity.org +1
@@ -197,6 +197,7 @@ rather than the one the web page shows.
197197| merge requests only | yes | yes | yes |
198198| protected tags | yes | yes | yes |
199199| require codeowners | yes | yes | yes |
200| require contexts | yes | yes | no |
200201| access grants | yes | no | yes |
201202| effective access | yes | no | yes |
202203| webhooks | yes | no | yes |
.gitbay/wiki/Users.org +3 −1
@@ -553,7 +553,9 @@ queues nothing, so look there when a push builds nothing.
553553
554554Each job becomes a build (=build list=, =build log=, the builds tab on
555555the web) and a =ci/<job>= commit status, which =repo settings
556require-checks= can gate merges on. =build list= takes =--ref=,
556require-checks= can gate merges on. =repo settings
557require-contexts= names statuses the gate waits for until they report,
558and turns the gate on. =build list= takes =--ref=,
557559=--status= and =--job= to narrow the listing, combinable; the builds
558560tab reads the same flags from its =?ref=, =?status= and =?job= query
559561parameters and groups the result into one row per commit. Steps run