# ARC on k3s — Actions as verifier, conductor keeps authority

status: ACTIVE
board: manual-a8c620493a9731b2
source request: 2026-08-16 owner ratified the codex ARC+Coder proposal WITH these amendments.

## Ruling

- GitHub Actions via ARC (actions-runner-controller) replaces homegrown CI substrate:
  the remote-build SSH transport, e2e-remote host selection, buildbox gate execution.
  These produced this week's worst incidents (silent 270-290s kills, host skew, queue
  depth 83) and are undifferentiated machinery.
- **Actions is the VERIFIER, never the gatekeeper, until parity is proven.** The land
  conductor keeps merge authority and consumes the Checks API instead of running gates.
  Direct-land survives (od-emergency requires it). Branch protection flips on LAST.
- **S3 fail-closed contract:** when `OVERDECK_LAND_GATE_MODE=checks`, a Checks API that is
  unreachable or a token that is absent makes the conductor REFUSE the land, naming
  `mode=local` as the recovery — it never silently passes the gate and never falls back
  to running the local gate on its own initiative.
- **Repo-wide GitHub token convention:** ONE config key, `github-checks.tokenPath` (the
  collector's `github-checks` scheduler adapter owns it), is reused by the conductor's
  Checks-mode credential resolver and is THE convention any future ARC/k3s lane inherits
  — never introduce a second GitHub-token config key.
- **Check events carry `work_key`/`request_id`/sha from slice one** and project into the
  collector so the board stays the single owner surface. CI that is invisible on the
  board recreates the darkness the task-lifecycle plan just closed.
- **Coder: deferred, likely permanently.** Its product is multi-dev self-service; our
  shape is one owner + agent fleet, already served by od-worktree + the hardened sandbox
  image (fail-closed egress, immutable images, journaled seat rotation). Coder Agents is
  rejected outright: it routes around seats, skills, permissions, and factory phases —
  the keep-list. Re-evaluate only if workspace provisioning is still a bottleneck after
  S1-S3.
- Ephemeral runners are cold runners: cache architecture is a first-class slice, not a
  footnote. The registry gateway (proven 2026-08-16 with real @platform-modules
  artifacts) is the credential-free package source for runner pods.
- Isolation: dedicated `arc-ci` namespace, unprivileged service accounts, default-deny
  egress with named allows (github, registries, the gateway), no host mounts, per-run
  volumes, quotas. Never register runners for fork PRs.

## Slices

- S1 — ARC installed in `arc-ci`; ONE deterministic advisory gate (collector `bun test`:
  hermetic, fast, no fleet deps) as a GitHub check on push; check events projected into
  the collector keyed by sha + work_key. Evidence: a real check run visible on the board.
- S2 — cache architecture for ephemeral runners: content-keyed pnpm store volume,
  lockfile-hash-keyed venv restore, prebuilt runner image carrying toolchain; installs
  resolve @platform-modules through the registry gateway. Evidence: cold-pod gate time
  within 2x warm-laptop time, measured.
- S3 — conductor consumes Checks API for gate verdicts (keeps merge authority); retire
  remote-build transport + e2e-remote selection for migrated gates only after
  cancellation/timeout/log/limit/recovery parity is demonstrated.
- S4 — decide branch protection / merge queue from S1-S3 evidence. Not before.

## Receipts

2026-08-16: plan authored; S1 repo side + S2 cache job dispatched to codex; cluster-side
ARC helm install runs from the workstation (owner-side GitHub runner registration
credential required — .secrets carries candidates; validate scope before use).

2026-08-16: S1 repo side implemented. `.github/workflows/collector-tests.yml` defines
the advisory `collector-tests` job for `main` and `wt/**` on `[self-hosted, arc-k3s]`,
with per-ref supersession cancellation and Bun 1.3.14 pinned. The collector's
`github-checks` scheduler adapter polls GitHub workflow/check APIs when both `repo` and
`tokenPath` are configured, persists deduplicated check runs, maps SHA or branch evidence
to the request ID as `work_key`, and retains unmatched runs on the authenticated
`/requests` checks projection. Focused tests: 20 pass, 0 fail; request projection: 18
pass, 0 fail; typecheck passed; full `collector/` `bun test` exited 0 with no
deploy-status flake observed. A real check remains unverified until ARC is installed.

2026-08-16: S2 repo architecture added under `modules/ci`: pinned runner toolchain,
PVC-backed pnpm content store, input-hash-keyed immutable venv archives, anonymous
`@platform-modules` gateway routing, default-deny/named egress policies, and a
rerunnable collector cold-pod/warm-laptop timing harness. Local receipt: module tests
pass (`bash` syntax: 4 scripts; cache-key and venv archive tests; manifest dry parse:
10 resources). The timing harness honestly reports measurement unavailable without a
cold-pod command. A standalone warm collector attempt took 9.995s but was invalid as
gate evidence (46 missing-dependency errors). Cluster timing, image build, and CNI FQDN
enforcement remain installed proofs.

2026-08-16: **S3 conductor side implemented DARK (default remains `local`).** The controller's
standing duty still spawns `finish-branch.sh landq-conduct`; a ticket is decoded by `_landq_serve`
and reaches `_stage_a_land_core`. Only the captured test-command seam changes when
`OVERDECK_LAND_GATE_MODE=checks`: it polls the Checks API for the ticket's frozen submitted head
SHA and configured check name (`collector-tests` by default), accepting only conclusion `success`.
Failure verdicts retain the check summary; timeout, missing credentials, malformed/unreachable API,
and missing repository identity fail closed and point at `mode=local`, with no silent fallback.
Runtime config is `~/.config/overdeck/land-gate.env`; same-name environment variables override it.
Credentials follow the collector adapter's resolver: `GH_TOKEN`, `GITHUB_TOKEN`, then
`gh auth token`; they are never written to the verdict or log.

Hermetic fake-API acceptance in `test-finish-branch.sh`: mode unset used `local-gate` and landed;
Checks success landed without invoking `local-gate`; conclusion `failure` refused and quoted
`"collector assertion 42 failed"`; loopback API down refused, named `API unreachable`, and pointed
at `mode=local`. Those four cases pass. Full suite output after the implementation:
`PASS=140 FAIL=11`; all 11 failures are the pre-existing docs-lane tool gap in this offload snapshot
(`node_modules/.bin/markdownlint-cli2` and `gitleaks` absent), also reproduced before the final
config-file/token-resolver tightening. Per the same-failure-twice rule, the gap was recorded rather
than weakening or repeatedly rerunning that gate. No live GitHub API, installed controller service,
timeout wait, or real ARC check was exercised; flag remains off.

2026-08-16: **S3 guard-scoping correction.** The land-guard installer now writes only the target
repository's `.git/harness/hooks/pre-push`, local `core.hooksPath`, and local
`harness.landGuardRef`; drift inspection likewise ignores a global-only hooks path. The finish-branch
suite now isolates HOME/XDG configuration and preserves a canary in a fake global dispatcher slot.
