Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .console/log.md
Original file line number Diff line number Diff line change
@@ -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
`<workflow name> / <run-name> (<event>)`, 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
Expand Down
59 changes: 59 additions & 0 deletions deploy/forgejo/README.md
Original file line number Diff line number Diff line change
@@ -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 `<workflow name:> / <run-name> (<event>)`, 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.
81 changes: 79 additions & 2 deletions docs/specs/forgejo-pr-adapter.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

```
<workflow name:> / <run-name> (<event>)
```

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
Expand All @@ -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
Expand Down
Loading