Life-cycle of a run
A run is a run-state.json file and the commands that move it. This page follows one from its first byte to a live site, and stops at each thing that can go wrong on the way.
Step states
Each step in the state file is in exactly one of four states. The gate computes a fifth — open — from the others.
| State | status shows | Set by | Meaning |
|---|---|---|---|
| open | blank | computed | Every prerequisite is done or blocked; next lists it. |
| in progress | ... | start | Someone is on it; the active-run pointer names this site. |
| done | OK | done | Every exit predicate passed at closedAt; the predicate results are stored. |
| blocked | BLK | block | A blocker from the step's vocabulary was validated; reason and evidence stored. Counts as satisfied downstream. |
start is optional. done re-checks the prerequisites itself, so skipping start is a convenience, not a bypass — but without it a file written before the first done may not be attributed to the run.
The state file
{
"site": "example",
"profile": "site",
"specVersion": 1,
"halt": null,
"steps": {
"preflight": { "status": "done", "startedAt": "…", "closedAt": "…", "actor": "orchestrator",
"predicates": [ { "kind": "tool", "ok": true, "detail": "preflight ok — 5/5" } ] },
"brief": { "status": "done", "…": "…" },
"repo": { "status": "in_progress", "startedAt": "…" }
}
}
Only start, done, block and sign write it. status and next never do, and the predicates never do either — a predicate run by hand leaves no trace.
Halts
A halt is a field in the state file, not an exit code. When done finds a failing predicate, when a prerequisite is missing, or when a blocker fails its validator, the gate writes:
"halt": { "step": "build", "kind": "exit_predicate",
"reason": "cannot close build: 2 exit predicate(s) failed",
"details": ["console-clean: 1 failed request on /about (favicon.ico 404)", "…"],
"at": "2026-08-29T10:02:17.551Z" }
While it stands:
nextprints only the halt block, never a ready step;start,done,blockandsignare accepted for the halted step and its prerequisites, and refused for anything else;site-guarddenies deploy commands in the agent's shell;- an older halt stands — a second refusal does not overwrite the first.
A halt is lifted by returning to the halted step: start on it, done closing it, or a block surviving its validator. The output then includes halt lifted — …. Lifting is cheap on purpose — it is a stop sign, not a lock. What it protects is the order: the failing predicates are still there the next time done runs, and nothing after the step opens until they pass.
Sign-offs
Two steps carry a signoff predicate: commercial-review (role owner) and legal-check (role legal_reviewer). sign <site-dir> <step> --by "Name" records the person, the role and a ledger line, and prints a reminder to run done. It closes nothing, because a human approval is one predicate among several — the jury medians or the 23 legal checks still have to hold.
Freshness
Three records describe a specific build and become worthless when the build changes: the ux-review, the jury, and the receipt. Each is compared against newestChange — the newest modification time under src/, public/ and dist/, ignoring node_modules, .git, .astro and .DS_Store.
Edit a page after the review, and done ux-review refuses again the next time it is run. The review must be re-filed against the build that is there now.
Nothing re-opens a closed step by itself. A step stays OK in status until something re-runs done on it — publish-live does, through live.mjs, for the receipt.
<site>/.claude/state/ is excluded from the scan on purpose. Otherwise writing a receipt would make the site look newer than the receipt, and nothing could ever ship.
Receipts and waivers
A receipt is the file that says the quality bar was measured, not asserted. Only scripts/verify-receipt.mjs audit writes it, and only after a real Lighthouse run — pinned version, mobile emulation — grades within budgets.json. The scores in the receipt are parsed from the raw report saved next to it, and check re-reads that report: a receipt whose numbers differ from its report is refused. Faking one would mean fabricating a full Lighthouse report.
Three commands:
| Command | Does | Success looks like |
|---|---|---|
audit <site-dir> <url> | Runs Lighthouse, grades, writes receipt.json on pass. | A scorecard and PASS — receipt written. Safe to claim done. |
check <site-dir> | Re-validates without a browser: report still meets budget, receipt matches report, under 24 h old. | No output, exit 0. |
waive <site-dir> "why" | Records a 24-hour deferral with a reason. Only check — the receipt re-validation — honours it; step 12's measurement does not. | Waived ship-check for <site> for 24h — reason: … |
The receipt is separate from step 12's measurement. measure.mjs records five runs and their spread into the state file for the gate; verify-receipt audit writes the receipt that publish-live reads. Both call the same Lighthouse and the same grading code.
Declarations
Some facts no program can discover: whether the domain sends mail, who the hosting processor is, which jurisdiction applies, whether the site is in BFSG scope, what legal form the owner has. The machine asks for them once, through site-context.mjs, and validates them where they are read. A missing declaration fails closed — dns.mjs refuses to judge SPF without a mail posture, rather than assuming one.
After launch
reverify.mjs --sites-root <dir> re-runs the live predicates against every published site, monthly, out of band. It writes its reports under <site>/.claude/state/reverify/ and never touches run state, halts, or the active-run pointer. Exit 1 means drift or an unreachable site.
Self-check: the two ways a run stops
A halt and a blocker both stop forward motion. Which one is a state the gate raises, which one is a state you request — and which of the two lets the steps after it proceed?
One run, end to end
A worked example that touches every part. The site is a one-page portfolio for a photographer, ~/sites/lena, built by an agent in one afternoon with a person watching.
- Preflight.
done preflightruns the doctor: 5/5.preflight.jsonwritten. TheSessionStarthook will now announce this run at every session start. - Brief. The
site-briefskill asks who the site is for and what the one action is — "book a shoot".brief.mdwritten;brief-complete.mjsfinds one primary action. Closed. - Repo, guidance.
gh repo create --private;mwg-install.mjs installvendors the guidance and pins2026_05_16-c5e78707. Two steps closed. - Design. The skill writes
tokens.cssand declares four pairs.contrast.mjsfails one: the muted caption colour is 4.1:1 in dark mode. Halt. The agent darkens the token, runsdone designagain, halt lifted. - Scaffold, build. Astro builds. The agent searches the guidance for "image gallery" and "dialog" — two
mwg_searchlines in the ledger — then writesGallery.astroandBooking.astro.done build: the ledger claim holds;console-cleanfails on a missingfavicon.ico. Halt, fix, closed. - Security, flows, ux-review, ship-check — the fan-out.
_headersgets a CSP;deps.mjsis clean. The booking flow is frozen asflows/book.cli.mjsand replays green. Theux-reviewskill files 31 verdicts with the fingerprint.measure.mjs: median 96, spread 3. Four steps closed. - Commercial review. Three blind scorers — three fresh agent sessions given only the preview URL — file ballots;
jury.mjs recordcomputes medians, the lowest is 3. Lena signs:sign … commercial-review --by "Lena". Thendone. Closed. - Publish preview.
wrangler pages deploy— the guard lets it through, no halt stands.publish.mjs proberecordsnoindexand the CSP from the real response.licences.jsoncovers the 12 photos. Closed. - Legal check.
legal.mjsfinds the Datenschutz does not name Cloudflare, whichsite-context.mjs hostingdeclared. Halt, text fixed, redeployed, re-observed. A reviewer signs. Closed. - Publish live. Lena edits one caption before launch.
verify-receipt auditis run again becauselive.mjswould refuse the older receipt.dns.mjschecks the parked-domain SPF/DMARC trio, since the site declared it sends no mail.done publish-live: closed.status: 15 of 15.
Four halts, none of them a judgement call, each lifted by evidence. The agent never marked anything done; it only made the predicates true.