Expensive verification is declared once, as SOW contracts paid at 'Publish: iter publish refuses on red, and custody's watch re-pays them on the published trunk and reports
Proposed
Context
bl-075 (2026-08-13) measured that the spine has no plane for expensive verification: `adr validate` runs for minutes and cannot be a pre-commit hook, `just ci-adrs` exists and nothing invokes it, the expensive tests (test_describe_invocations, ~102 s) and `positioning audit` are run by hand. It left the SHAPE undecided on purpose, as the decision it exists to force.
What changed since, measured 2026-10-18:
- The jj path now has a gate with a trigger: `iter accept` pays the contracts of live SOWs covering the touched paths, at 'Commit (iter/lib/accept.nu:248). C13 of sow-artifact-routes-v2 costs about 8 s there, which an accept can afford; `adr validate` (85 s on this tree) cannot. - A contract can be paid at 'Refute, 'Commit, 'Receipt or 'Turn (code/ontology/schemas/sow.ncl:88, sows.nu MOMENTS). 'Turn is the end of every agent turn. There is no moment at publication. - `iter publish` judges the range it pushes "as accept judges it"; it has no moment of its own. - The spine root still has no CI (.woodpecker, .github, .forgejo absent). Under jj a client hook never runs (custody qa custody-git-ops-under-jj), and a forge pipeline runs after the accept. - custody already runs a periodic plane: the signed_history_watch component is a taskserv that runs a witness hourly, as `custodian`, over the forge's trunk, and reports.
Decision
EXPENSIVE VERIFICATION IS DECLARED ONCE, AS SOW CONTRACTS PAID AT 'Publish. A check too costly for every accept — the ADR corpus validation, the expensive test suites, the positioning audit — is a contract of a standing SOW with `pay_at = ['Publish]`. The SOW is where it is declared, scoped, refuted and signed, like any other contract; no second list of checks exists anywhere.
TWO PAYERS, ONE DECLARATION.
- `iter publish` pays the 'Publish contracts that cover the range it publishes, and refuses to publish on red. This is the blocking half: nothing reaches the forge's trunk with a failing expensive contract. A contract already paid over the same bytes is not paid again. - custody's verification watch re-pays every live 'Publish contract over each registered level's PUBLISHED trunk on a cadence, as `custodian`, and reports to the owner. It never blocks and writes nothing in the level. It catches what no commit caused: another project that moved, a tool that updated, a machine that differs.
THE MOMENT IS A PROTOCOL FACT. 'Publish joins the moments of a contract (code/ontology/schemas/ sow.ncl) and `form due-sow --at Publish` returns exactly the 'Publish contracts covering the paths it is given; iter and custody ask ontoref, as accept does at 'Commit, and implement no selection of their own.
EACH PROJECT GOVERNS ITS PIECE. The moment and its selection are ontoref's and are constrained here. Paying at publish is iter's, and the watch is custody's: each lands under its own project's governance and declares its own contracts. This ADR fixes the contract between them — the moment, its meaning, and that the watch reports while publish refuses.
Cheap verification stays at 'Commit, where it already runs at every accept.
Constraints
- Hard A SOW contract may declare pay_at = ['Publish]: the schema accepts it
- Hard Publish is one of the moments `form due-sow --at` accepts, so iter and custody select 'Publish contracts by asking ontoref
Alternatives considered
- A CI pipeline for the spine at the forge — rejected: On the jj path it runs after the accept, so it cannot refuse what was accepted; and it would hold a second definition of the gates beside the SOWs.
- Expensive checks at 'Commit — rejected: Every accept would take minutes; the gate would be paid around rather than paid.
- A check list kept by custody's watch — rejected: Two declarations of the same checks; adr-074 forbids the replicating carrier.
- Only a periodic watch, nothing blocking — rejected: Red would reach the forge's trunk first and be reported after others had built on it.
- Only blocking at publish, no watch — rejected: A trunk that turns red without a commit — another project, a tool — would never be seen.
Anti-patterns
- A list of expensive checks kept by the runner — The watch, a CI file or a recipe enumerating which checks are expensive, beside the SOW contracts that already declare them.
- The watch made to block — Turning a periodic finding into a refusal of the next accept or publish. Nobody committed what made it red; refusing the next unrelated change punishes the wrong act.
- Paying minutes at every accept — Declaring a corpus validation or a long suite at 'Commit, which makes every accept slow until the gate is routed around.
Related ADRs
· reads
Perspectives