ADR 0262: Receipt-eligible runs stop at the pre-group strand checkpoint
Status: accepted; extends ADR 0047 and ADR 0148, the remedial complement of ADR 0261; reads the tree pin from ADR 0116
Context
§Strand detection (ADR 0047, widened by ADR 0148) reported at the end of an otherwise-green run: the gate paid for standards, check∥test, and scope gates, then failed tree_drift over strands the fix or build group had left long before. For one class of run that tail is provably wasted. A done that begins on a clean, committed tree is seeking a receipt — the vouch accept honors — and a receipt is stamped only over a tree that is still clean at the end (ADR 0067/0116). The moment a pre-group dirties a committed-clean tracked file, the receipt is forfeit and the run's verdict is tree_drift no matter what the remaining stages find. Everything after that moment costs wall clock and, under the fleet test-run cap (ADR 0252), a test slot another worktree wanted.
ADR 0261 recorded the field pressure — three delegated streams in one day paying a doubled suite each for build-stage drift — and built the prevention half: prepare now runs the [generated] regenerations, so the finish order prepare → commit → done cannot drift on fix- or build-stage output. Its alternatives section deferred the remedial half, sketched as replaying a tree-drift-failed run's green verdict. The backlog carried that sketch as "verified-tree replay", pending a design discussion. This record is that discussion's outcome.
Dirty-start runs put a boundary on the fix. Agents run done on a dirty tree mid-iteration precisely to hear everything the gate has to say; those runs never had a receipt at stake, and cutting them short would trade wanted feedback for an unwanted saving.
Decision
§A receipt-eligible done run checks for tracked strands as soon as every fix/build pre-group has passed, and fails tree_drift there instead of running the standards, check∥test, and scope-gate work. A run that starts dirty keeps the full run and the end-of-run detection, unchanged.
- Eligibility is the validated tree pin, never the strand snapshot. The run is receipt-eligible when the pin captured at gate start has a readable HEAD and full cleanliness (
treePin.head !== undefined && treePin.clean). The pin counts untracked files; the strand machinery tracks only tracked paths — so an untracked-dirty start, whose tracked-dirty snapshot is empty, does not qualify for the checkpoint and keeps its full run. - One checkpoint after all pre-groups, never between them. A later build may consume or restore a fixer's edit, and convergence edits belong in one report. The check is free at that point: it reads the per-group snapshots the gate already took.
- One strand algorithm. The checkpoint and the final pass share
strandedByStageand a single failure routine, so the diagnostic, thefailed_stage: "tree_drift"label, and the fail-open behavior on an unreadable snapshot are identical at either site, and a run can never be judged twice. - Failure precedence is unchanged. A failed pre-group command, a merge or precondition refusal, and
generated_driftall keep their earlier verdicts; the checkpoint runs only on an otherwise-green tree. - The result stays complete. Scope classification still runs after the pre-groups (the aborted run's
scopes_changedis accurate), and every standards, check, test, and scope-gate step that never ran serializes as skipped, as it does for any earlier stage failure.
The explicit noes, each considered for this change and rejected: no content- or tree-keyed replay of green verdicts, no receipt carry-forward across the strand-absorbing commit, no engine identity or binary hashing, no attestation machinery, no --confirmed-style toggle or configuration switch, no auto-commit of gate output, and no change to the untracked-output policy or to dirty-start semantics.
Consequences
§- The tree-drift failure ADR 0261 could not prevent (a finish order that skips
prepare, or a mutating build job outside[generated]) now costs the pre-groups plus a diagnostic instead of a full gate. The commit-and-re-run loop the diagnostic prescribes is unchanged; only its first lap got cheap. - Under the fleet test-run cap, a doomed run no longer queues for — or holds — a test slot.
- Dirty-start runs pay what they always paid and hear everything they always heard. The two-run worst case survives for them by design: full feedback is what running dirty asks for.
- Strands from the check∥test or scope-gate stages still surface only at the end of a full run; those stages run after the checkpoint, so nothing earlier can see them.
- The "verified-tree replay" exploration leaves the backlog. The checkpoint removes the bulk of the waste replay was sketched to refund, with no cached verdict to keep correct.
Alternatives considered
§- Verified-tree replay (the deferred sketch in ADR 0261): record the verified tree's hash plus config identity on a tree-drift failure; when the next
donestarts on a clean HEAD whose tree matches, replay the green verdict and stamp the receipt without re-running the suite. Rejected: a gate verdict is not a pure function of the tree bytes — the trunk can move (the never-loosen comparison and merge check read it live), the engine and environment can change between runs, and test flake means "same tree" does not guarantee "same outcome" — so the replayed receipt would vouch for work the jobs never re-earned, and every one of those freshness conditions becomes cache-invalidation surface. The checkpoint deletes most of the same waste (the doomed post-pre-group tail) with nothing to invalidate. - Carry the receipt forward across the strand-absorbing commit, the way
standards --pincarries it (ADR 0106). Rejected: the pin carry is sound because the pin commit provably changes[standards]limits alone, and only to values the held measurements satisfy. A commit that absorbs arbitrary gate output has no such proof — the strands are the diff between the verified tree and the committed one. - Check after each pre-group instead of after all of them. Rejected: a build that consumes or restores a fixer's edit would abort a run whose finished tree is clean (a false positive ADR 0148 already refuses at the final pass), and an agent fixing convergence wants the full pre-group picture in one diagnostic.
- Apply the early abort to dirty starts too. Rejected: those runs cannot earn a receipt, so the abort saves nothing the agent asked to keep, and it would repeal the full-feedback contract that makes running
donedirty useful mid-iteration.