From 11ef7bee54b775236aa7aeb7802fad66a129557f Mon Sep 17 00:00:00 2001 From: ProtocolWarden <32967198+ProtocolWarden@users.noreply.github.com> Date: Wed, 19 Aug 2026 10:03:36 -0400 Subject: [PATCH] =?UTF-8?q?docs(spec):=20B4=20resolved=20=E2=80=94=20the?= =?UTF-8?q?=20audit=20context=20cannot=20match=20GitHub's?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Forgejo Actions is running on the live instance: runner 6.3.1 registered, probe workflow executed end to end (pulled node:20-bookworm, success in 91s). The spec's last open item said `audit` "must be reproduced under an identical context name, or every PR blocks forever". Running it shows the identical name is not achievable, and the reason is sharper than the inconvenience: GitHub uses the job id, so OC's required context is the bare string `audit`. Forgejo composes ` / ()` — and with no `run-name:` the middle segment is THE COMMIT MESSAGE: audit.yml / ci: probe the status context name (push) A required status check is a fixed string. A context carrying the commit message changes on every push, so the default configuration is not "mismatched" — it is unsatisfiable in principle. Setting `run-name: audit` pins it: custodian-audit / audit (push) verified stable by re-running under a deliberately different commit message. Second finding: `push` and `pull_request` each produce their own context on a PR head, so the GitHub workflow's dual trigger does the work twice and leaves a second context outstanding for any gate that requires nothing incomplete. The Forgejo workflow should trigger on `pull_request` only. Cutover configuration, now concrete: `run-name: audit`, `on: pull_request`, branch protection requiring `custodian-audit / audit (pull_request)` and `apply_to_admins: true`. Any checklist saying "use the same name" is wrong. deploy/forgejo/README.md records the runner setup so it is reproducible, including why it joins the docker group rather than running as root — and that either way it grants control of the Docker daemon. The probe also live-validated #517 against real status data rather than fixtures: completed=['...(pull_request)'], incomplete=['...(push)'], resolved from a three-entry posting history by the latest-per-context dedupe. Co-Authored-By: Claude Opus 5 --- .console/log.md | 25 ++++++++++ deploy/forgejo/README.md | 59 +++++++++++++++++++++++ docs/specs/forgejo-pr-adapter.md | 81 +++++++++++++++++++++++++++++++- 3 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 deploy/forgejo/README.md diff --git a/.console/log.md b/.console/log.md index 900d54338..b11d28dcb 100644 --- a/.console/log.md +++ b/.console/log.md @@ -1,3 +1,28 @@ +## 2026-08-19 — B4 resolved: the audit context cannot match GitHub's + +Forgejo Actions is running: runner 6.3.1 registered against the live instance, +a probe workflow executed end to end (pulled node:20-bookworm, success in 91s). + +The spec's B4 assumed `audit` "must be reproduced under an identical context +name". It cannot be. Forgejo composes the context as +` / ()`, and with no `run-name:` the middle +segment is the COMMIT MESSAGE — so the default context changes every push and +is unsatisfiable as a required check, not merely mismatched. Setting +`run-name: audit` pins it to `custodian-audit / audit (push)`, stable across +commit messages. + +Also: `push` and `pull_request` each produce their own context on a PR head, so +the GitHub workflow's dual trigger doubles the work and leaves a second context +outstanding. The Forgejo workflow should trigger on `pull_request` only. + +Cutover config is therefore: `run-name: audit`, `on: pull_request`, and +branch protection requiring `custodian-audit / audit (pull_request)` — +Forgejo's string, not GitHub's. + +Bonus: the probe live-validated #517's translation against real status data — +`completed: ['...(pull_request)']`, `incomplete: ['...(push)']`, resolved from +a three-entry posting history by the latest-per-context dedupe. + ## 2026-08-19 — CI found what 3.12 could not The new `test-rest` job went red on its first run, which is the job doing its diff --git a/deploy/forgejo/README.md b/deploy/forgejo/README.md new file mode 100644 index 000000000..3bea06b55 --- /dev/null +++ b/deploy/forgejo/README.md @@ -0,0 +1,59 @@ +# Forgejo Actions runner + +The `audit` status the fleet's branch protection requires is produced by a +workflow, and a workflow needs a runner. This is how the local one is set up. + +## Runner + +```bash +TOKEN=$(docker exec -u git forgejo forgejo forgejo-cli actions generate-runner-token) + +docker volume create forgejo-runner-data +docker run --rm -v forgejo-runner-data:/data --network host \ + code.forgejo.org/forgejo/runner:6.3.1 \ + forgejo-runner register --no-interactive \ + --instance http://localhost:3000 \ + --token "$TOKEN" \ + --name oc-local-runner \ + --labels docker:docker://node:20-bookworm,ubuntu-latest:docker://node:20-bookworm + +docker run -d --name forgejo-runner --restart unless-stopped \ + -v forgejo-runner-data:/data \ + -v /var/run/docker.sock:/var/run/docker.sock \ + --group-add "$(getent group docker | cut -d: -f3)" \ + --network host \ + code.forgejo.org/forgejo/runner:6.3.1 forgejo-runner daemon +``` + +`--group-add` rather than `--user 0:0`: the runner needs the docker socket to +spawn job containers, and joining the socket's group is narrower than running +the daemon as root. Note that either way this grants control of the Docker +daemon, which is root-equivalent on the host — run it only where that is +acceptable. + +## The status context is NOT the job name + +Forgejo composes it as ` / ()`, and with no +`run-name:` the middle segment is **the commit message** — so the context +changes on every push and can never satisfy a required status check. + +Always set `run-name:`, and trigger on `pull_request` only (a `push` trigger +produces a second, separate context on the same head): + +```yaml +name: custodian-audit +run-name: audit +on: + pull_request: + branches: [main] +jobs: + audit: + runs-on: ubuntu-latest +``` + +That yields the stable context `custodian-audit / audit (pull_request)`, which +is what branch protection's `status_check_contexts` must list. It is Forgejo's +format, not GitHub's `audit` — a checklist that says "use the same name" is +wrong. + +See `docs/specs/forgejo-pr-adapter.md` for the evidence behind both points. diff --git a/docs/specs/forgejo-pr-adapter.md b/docs/specs/forgejo-pr-adapter.md index aba18259d..8cd705c1e 100755 --- a/docs/specs/forgejo-pr-adapter.md +++ b/docs/specs/forgejo-pr-adapter.md @@ -224,6 +224,83 @@ negative-case verification). Three findings revise the adversarial sections: 3. **`check-runs` is a confirmed 404**, and `GET /pulls/{index}.diff` serves the raw diff. +## B4 resolved — `audit` on Forgejo Actions, 2026-08-19 + +Actions enabled on the live instance, `forgejo-runner` 6.3.1 registered, and a +probe workflow run end to end. B4 said the `audit` status "must be reproduced +under an identical context name, or every PR blocks forever". **The identical +name is not achievable**, and the reason matters more than the inconvenience. + +### The context format is not the job name + +GitHub uses the job id, so OC's required context is the bare string `audit`. +Forgejo composes it: + +``` + / () +``` + +With no `run-name:`, the middle segment is **the commit message**. The probe's +first run produced: + +``` +audit.yml / ci: probe the status context name (push) +``` + +A required status check must be a fixed string. A context containing the commit +message changes on every push, so under the default configuration branch +protection can never be satisfied — not "blocks until you rename it", but +unsatisfiable in principle. + +### `run-name:` makes it stable + +Setting `run-name: audit` pins the middle segment. Re-run with a deliberately +different commit message: + +``` +custodian-audit / audit (push) +``` + +Stable, deterministic, and independent of the commit. That is the property +branch protection needs. + +### Both events fire on a PR head + +`push` and `pull_request` each produce their own context, so the GitHub +workflow's dual trigger yields **two** runs and two contexts per PR head: + +``` +custodian-audit / audit (pull_request) -> success +custodian-audit / audit (push) -> pending +``` + +Two consequences: the work is done twice, and a gate that requires "nothing +incomplete" sees the slower run as outstanding. The Forgejo workflow should +trigger on `pull_request` only. + +### Cutover configuration + +* workflow: `name: custodian-audit`, `run-name: audit`, `on: pull_request` +* branch protection `status_check_contexts`: `custodian-audit / audit (pull_request)` +* `apply_to_admins: true` (per the earlier live finding) + +The required-context string is therefore **Forgejo's format, not a copy of +GitHub's**. Any cutover checklist that says "use the same name" is wrong. + +### Live validation of the adapter + +The same probe confirmed `ForgejoPRClient` reads real status data correctly — +not fixtures: + +``` +failed: [] +incomplete: ['custodian-audit / audit (push)'] +completed: ['custodian-audit / audit (pull_request)'] +``` + +The statuses endpoint returned the full posting history (pending, pending, +success) for one job, and the latest-per-context dedupe resolved it as designed. + ## Correctness criteria 1. Status→check translation is explicit about what it loses. A test asserts a @@ -248,8 +325,8 @@ negative-case verification). Three findings revise the adversarial sections: - [x] `PRClient` protocol extracted; all 17 callers migrated (#512–#515) - [x] `ForgejoPRClient` implementing the protocol (`adapters/forgejo/pr_client.py`) - [x] Status→check translation with the losses tested (`STATUS_TO_CHECK`; warning→neutral, error→failure with its own name) -- [ ] `audit` (or its replacement) produced on Forgejo under an identical name -- [ ] Live verification against a real instance, **negative cases first** +- [x] `audit` produced on Forgejo — under Forgejo's context format, which *cannot* be identical (see B4 resolved) +- [x] Live verification against a real instance, negative cases first (#517, and the Actions probe above) - [ ] Cutover, GitHub demoted to read-only mirror ## Phasing