diff --git a/CHANGELOG.md b/CHANGELOG.md index 7063c93..7296571 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ on the way to cutting its first release, and this file starts there. ### Added +- `drill/drill.sh` — the drill has an instrument: pinned-ref assertion, a mechanical idempotence diff, and a `drills/.md` record emitter (#105) - `kimi-box` joins the box tenant roles — the Kimi CLI agent guest (#109) - The `changelog-armed` guard returns, version-keyed (#112, ceremony#13) - The `.ceremony/` doctrine mirror, verified by `docs-sync` on every PR (#112, ceremony#19) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e0cf609..a7afb1a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -72,7 +72,8 @@ Bare `X.Y.Z` tags, no `v`; the tag's source tarball is the package `install.sh` downloads — rig ships no other artifact. What stays rig's is the **drill** — the real-hardware gate before the -handoff of a release PR: tenant guests minted and converged via box, +handoff of a release PR, run by `drill/drill.sh` (#105): `rig bootstrap` +converging the machine to its role twice with the second run diffed empty, `test/db-integration.sh`, the runner lifecycle against a fork, a coolify install. Rig's drill asserts **convergence** (a machine reaches its role, idempotently), it runs `--host yes` with `BOX_REF=release/` so diff --git a/drill/README.md b/drill/README.md new file mode 100644 index 0000000..bf0004a --- /dev/null +++ b/drill/README.md @@ -0,0 +1,91 @@ +# The drill — running it + +`drill/drill.sh` is the instrument; `drills/` is the record it feeds +(see [drills/README.md](../drills/README.md) for what a record means and +how the three repos' drills relate). rig's drill asserts **convergence**: +a machine reaches its role, idempotently. This file is the procedure — +written down so a run is repeatable, not reconstructed from memory each +release (#105, and #107's debt). + +## What you need + +- **A throwaway Debian 13 machine** you can format, reached as root. The + drill hardens its sshd, renames it, joins it to a tailnet, and installs + box/Incus, Coolify and a GitHub runner on it. It is not coming back. + The machine is its own reset — there is no teardown script and no need + for one. +- **The pinned candidate refs, both of them.** `--rig-ref` and + `--box-ref` are required; the harness refuses to run without them and + refuses to continue if what installed disagrees with what was asked + (`INSTALLED_FROM`, both trees). Until heavy-duty/rig#103 lands, both + installers default to `main` when unpinned — which is exactly why the + drill will not let a ref go unstated. +- **A single-use, tagged tailscale pre-auth key** in `TS_AUTHKEY` + (`tag:local` for the default `staging-server` role — bootstrap refuses + `tag:server` outside the control-plane shapes). +- **A users file** (`--users`) naming at least one operator — leg 1 + asserts the accounts and keys actually converged. +- **For leg 3** (runner lifecycle): a fork to register against + (`--runner-repo you/rig`) carrying a `workflow_dispatch` workflow — + default name `drill.yml` — whose job has `runs-on: [self-hosted, drill]` + and does something trivial (`echo drilled`). Tokens come from an + authenticated `gh`, or from `RUNNER_TOKEN` / `RUNNER_REMOVE_TOKEN`. + Without a fork the leg **skips, loudly, into the record**. +- **For leg 4** (coolify): a version pin, `--coolify-version 4.1.2`. + No pin, no leg — rig's own `coolify install` refuses to default a + version and so does its drill. The skip is recorded. +- **A run ID** (`--run-id`) when this drill shares a substrate with + box's or cast's — the shared ID is what lets the per-repo records be + joined afterwards. Defaults to `drill-`. + +## Running it + +From a checkout of this repo on the throwaway machine (the record lands +in the checkout's `drills/`): + +```sh +TS_AUTHKEY=tskey-... bash drill/drill.sh \ + --rig-ref release/0.4.0 --box-ref release/0.10.0 \ + --users ./drill-users --run-id drill-2026-07-24-a \ + --coolify-version 4.1.2 --runner-repo you/rig --yes +``` + +It runs unattended from there. Legs execute as 1, 4, 2, 3 — Coolify's +installer is what puts Docker on the box and the db leg needs a daemon — +and the record lists them as they ran. A failing check never aborts the +run (`set -u`, no `-e`: a failing check is data), and the summary counts +passes, failures and skips separately. + +## What it asserts + +1. **Convergence, and idempotence.** `rig bootstrap --users …` + reaches the declared role, asserted on *effective* state — the marker, + `sshd -T`, the granted tailnet tag, the operators' accounts and keys. + Then bootstrap runs **again**, and the state captured before and after + the re-run must diff **empty**. The diff is mechanical; "watched it + not obviously break" is exactly what this leg exists to replace. + Riding along, the `--host yes` assertions: the **pinned** box + installed (`INSTALLED_FROM` matches `--box-ref`, fatal if not), + `box doctor` passes. It stops there and says so in the output — the + isolation boundary is **box's** drill's assertion, never rig's. +2. **db** — `test/db-integration.sh` from the *installed* tree: a real + dump/restore round-trip. Its clean-skip contract (no Docker → loud + skip, exit 0) survives into the record as a SKIP, never a pass. +3. **Runner lifecycle** — register against the fork, dispatch the drill + workflow and watch the runner take it, deregister, and assert the + box's registration is actually gone. +4. **Coolify** — installed at the pin, `AUTOUPDATE=false` landed in the + effective `.env`, container running. + +## The record + +The run always ends by writing `drills/.md` (the version is the +installed tree's own `VERSION`) — on failures too: **a failed drill is a +valid record**; the gate wants evidence, not success. Skipped legs are +named as not-run so the record can never read as a clean sweep. Commit +the file on the release branch; the `drill-recorded` guard reads that +file and nothing else. + +The instrument's own honesty — the refusals, the skip accounting, the +capture-and-diff, the emitter — is `test/drill.sh`'s job, and CI runs it +on every PR. The live four-leg run is a release's job, once per cycle. diff --git a/drills/README.md b/drills/README.md index 3a8af5b..3928494 100644 --- a/drills/README.md +++ b/drills/README.md @@ -15,12 +15,13 @@ The directory is `drills/`, not `.drills/` — a dot-directory is invisible to any glob without `dotglob`, which is how #70 here and box#116 / box#118 all happened. -**This directory is the record, not the instrument.** rig has **no drill -harness script of its own**; its legs are run by following the documented -procedure, and the harness lives in heavy-duty/box's `drill/`. rig does not -reach into it to decide whether rig may ship: a cross-repo lookup that fails -silently degrades to "pass", which is the UNREADABLE-vs-NONE shape #90 fixed. -The gate reads a file in this repo, and nothing else. +**This directory is the record, not the instrument.** The instrument is +[`drill/drill.sh`](../drill/README.md) (#105): it runs the legs, asserts the +pinned refs actually landed, decides idempotence by a mechanical state diff, +and emits the record file this directory holds. rig does not reach into +another repo's harness to decide whether rig may ship: a cross-repo lookup +that fails silently degrades to "pass", which is the UNREADABLE-vs-NONE shape +#90 fixed. The gate reads a file in this repo, and nothing else. ## What the gate requires @@ -40,13 +41,16 @@ not success. ## The drill -rig's legs: +rig's legs (#105; `drill/drill.sh` runs them): -- tenant guests minted and converged **via box** +- `rig bootstrap ` converges the machine to its role — then runs + **again**, and the captured state must diff **empty** (idempotence, + decided mechanically). On a host=yes role this is also what installs the + pinned box and asserts its host stack stands. - `bash test/db-integration.sh` against a real Postgres on the machine - the GitHub runner lifecycle — register, take a job, deregister — against a fork -- a coolify install +- a coolify install, pinned, `AUTOUPDATE=false` box and rig are **mutually recursive**: `rig bootstrap --host yes` installs box and runs box's `setup-host`, while box's guests converge back through rig's @@ -100,11 +104,12 @@ Candidate refs: box@1a2b3c4 (BOX_REF=release/0.4.0), rig@5d6e7f8, cast@9a0b1c2. | Leg | Result | | --- | --- | -| tenant guests minted + converged via box | 3/3 | +| convergence — bootstrap staging-server reaches its role | PASS (312s) | | re-converge (idempotence) | clean, no changes | -| `test/db-integration.sh` | 14/14 | +| --host yes: pinned box installed, host stack up | PASS — box doctor clean | +| `test/db-integration.sh` | PASS — 14 passed, 0 failed | | runner lifecycle against a fork | PASS — registered, took a job, deregistered clean | -| coolify install | PASS, ~6 min | +| coolify install (4.1.2) | PASS (6 min) | Failed: `rig users apply` left one revoked key in `authorized_keys` (filed #NNN). Everything else clean.