collin/anvil · 6410f75b
Record remote-runner status and next steps in CLAUDE.md
Collin Richards · 2026-08-24 08:14 UTC · 6410f75b6580db7498455d44c9c491a0d26f5fcd · parent 35ea3553 · browse files
modifiedCLAUDE.md+56 −0
| ⋯ 54 unchanged lines | |||
| 55 | 55 | ||
| 56 | 56 | note that we can't use portless for this app because of a websocets bug with http/2 | |
| 57 | 57 | ||
| 58 | + | ## Remote runners: where this stands | |
| 59 | + | ||
| 60 | + | anvil does **not** execute CI. Runners dial out, claim jobs, and run them on | |
| 61 | + | their own Docker daemon. Design and rationale: `docs/remote-runners.md`. | |
| 62 | + | ||
| 63 | + | M1 is done: `anvil-job` (wire format), `anvil-docker` (connect/ensure_image), | |
| 64 | + | `anvil-worker` (the runner binary), five endpoints under `/-/runner/`, | |
| 65 | + | in-memory leases, and no Docker socket on the deployed container. | |
| 66 | + | ||
| 67 | + | **Invariants to not break.** These are the reasons the split is shaped the way | |
| 68 | + | it is, and each is easy to undo by accident: | |
| 69 | + | ||
| 70 | + | - The runner never parses `.anvil/ci.yml`. Image resolution, the allowlist | |
| 71 | + | check and script assembly happen in `build_job`, so a runner cannot widen | |
| 72 | + | what it is permitted to run. | |
| 73 | + | - `store_artifact` stays server-side. The runner uploads a raw tar; `browse` | |
| 74 | + | decides whether that becomes a servable directory tree, which is anvil's | |
| 75 | + | call, not a runner's. | |
| 76 | + | - A job's secrets live on its lease, not the vault, once dispatched. | |
| 77 | + | `Vault::take` fails after the repo's unlock TTL lapses, so re-reading at | |
| 78 | + | finish time would silently skip log masking on exactly the long runs whose | |
| 79 | + | logs most need it. | |
| 80 | + | - `anvil-job` depends on serde and nothing else. It exists so the runner does | |
| 81 | + | not link toasty, SQLite and gix. | |
| 82 | + | ||
| 83 | + | **Next, in order:** | |
| 84 | + | ||
| 85 | + | 1. **M2, platform.** The plumbing is live end to end (`JobSpec.platform` reaches | |
| 86 | + | both bollard option structs; runners advertise their native platform on | |
| 87 | + | claim). What is missing is a source: a `platform:` key in `.anvil/ci.yml`, | |
| 88 | + | probably a `[ci] platform` default, and routing a job to a runner that has | |
| 89 | + | that architecture natively. This is what stops arm64 runners testing code | |
| 90 | + | that ships as amd64. | |
| 91 | + | 2. **M3, the deploy agent.** Image builds cannot be CI jobs, because job | |
| 92 | + | containers get no Docker socket by design. `docker build --platform | |
| 93 | + | linux/amd64` plus `docker push` belongs to a separate process on the build | |
| 94 | + | host at a different trust level, triggered by the existing `deploy_webhook`. | |
| 95 | + | Folding it into the dial-out channel as a privileged "publish" job kind, | |
| 96 | + | authorized by the `is_deploy_target` check that already scopes CD to one | |
| 97 | + | repo, would remove the last inbound path to the build host. | |
| 98 | + | 3. **CI concurrency.** "One job at a time" was a property of the old in-process | |
| 99 | + | loop and is gone. Nothing bounds in-flight jobs now beyond how many runners | |
| 100 | + | exist. Needs a `max_concurrent` equivalent. | |
| 101 | + | 4. **Per-runner credentials.** One shared `[ci] runner_token` means one | |
| 102 | + | revocation for every runner, no `last_used_at`, and any holder can claim any | |
| 103 | + | job and receive its secrets. Wants the API-token write scope first (see | |
| 104 | + | TODO.md). | |
| 105 | + | 5. **Live logs.** The result POST is a single write, matching the old | |
| 106 | + | behaviour. Streaming needs chunked append with offsets and a UI that | |
| 107 | + | tolerates gaps. Newly worth doing now that a producer exists. | |
| 108 | + | ||
| 109 | + | **Operationally:** nothing runs until a runner is started and `[ci] | |
| 110 | + | runner_token` is set; queued runs just sit, with a warning logged at startup. | |
| 111 | + | Agent sessions still drive Docker locally and are the one thing the dropped | |
| 112 | + | socket mount gives up. They are off by default. | |
| 113 | + | ||
| 58 | 114 | Current status, resume notes, the agreed next steps (a/b/c), and the roadmap live | |
| 59 | 115 | in the TODO. Read it first: | |
| 60 | 116 | ||
| ⋯ 1 unchanged line | |||