Close three gaps the pre-adoption audit found in v4 - #140
Merged
Conversation
Auditing the change before an adopter took it turned up three defects, all from the same shape: a decision recorded in the design and then landed halfway, with no machine check covering the other half. K13/20 says which writer refuses which phase is owned by K13/21, and K13/21 never said. Three enforcement points existed in code and were asserted operationally by two Cards, with no owner in the Standards -- a Card claiming a rule its own source does not state, which `semantic_stale` cannot see because it compares source hashes, not source meaning. K13/21 gains `Phase Consumers`. Every other delegation the change introduced was walked and does resolve, so this was one gap and not a class of them. R12 entered the gate phase unconditionally because the override table hit `continue` and short-circuited Work Spec narrowing, so every batch of a task that selects R12 paid for a targeted audit it was not running -- the waste the protocol exists to remove, reintroduced by the protocol. The cause was treating two kinds of override as one: R01, R08 and R09 are reached by task-level facts no batch may waive, while R12's predicate is about what this batch is doing, and the Work Spec is exactly where a batch says that. Narrowing now reaches R12 and nothing else. The phase gate computed coverage over the union of every attempt and then checked attempt purity separately, so the two never composed. Two contexts that each delivered and acked in full wedged each other permanently -- the bound branch tested for the presence of a foreign attempt rather than the absence of the actor's own, which is what its error text already claimed -- while two attempts covering half each read as complete. Coverage is now judged per attempt: a bound edge needs the actor's own attempt to cover the phase alone, an unbound edge needs some one attempt to. Neither the second nor the third defect could appear with one context and one Work Spec shape, which is all the fixture ever had. The new tests aim at that blind spot rather than at the lines: the four narrowing combinations, two complete chains not blocking each other, and two half chains not summing to a delivery. That last one first sat on the gate phase, where the fixture has a single piece, and skipped silently; it now sits on preflight and asserts it has something to split, so a shrunken fixture fails loudly instead of quietly passing. K13/21 grows 5421 -> 8138 bytes and takes a K00/16 exception at 8.5KB. Each of its six sections fails the split test on the same clause: no consumer reaches any one side without the phase set. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #141. Follow-up to #139. Found while auditing that change before an adopter took it, and fixed before the adoption rather than after, so the adopter takes one Standards revision instead of two.
All three are the same shape: a decision recorded in the design, landed halfway, with no machine check covering the other half.
1. A dangling ownership pointer
K13/20states that which writer refuses which phase is owned byK13/21.K13/21never said.Three enforcement points exist in code — the judgment writer,
update_task's completion-candidate transition, and theopen -> merge-readyedge — and two Runtime Cards assert them operationally. Nothing in the Standards owned them. A Card asserting a rule its own source text does not state is exactly the drift the compiled-Card contract exists to prevent, andsemantic_stalecannot see it: it compares source hashes, not whether the Card's claims trace to source.K13/21gains aPhase Consumerssection carrying the edge/phase/binding table and the three reasons behind it — why two edges are actor-bound and two are not, why the governance condition is a property of the manifest rather than of which tool runs, and whyprepared/degradedare exempt.Every other delegation #139 introduced was walked (
K13/19→K13/21,K00/02→K13/21,K00/06→K13/21) and each resolves. This was one gap, not a class of them.2. R12 entered the gate phase unconditionally
The spec says R12 materializes only when its Card's self-stated scenario holds. The code hit
continueon an override-table match and short-circuited Work Spec narrowing, so R12 landed in the gate phase whenever it was a selected route at all.For an adopter that selects R12 once at task level, every batch then paid for a targeted audit it was not running — the waste this protocol exists to remove, reintroduced by the protocol.
The cause was treating two kinds of override as one kind. R01, R08 and R09 are reached by task-level facts that no batch may waive: R01 is presumed by every phase, and the other two are entered by a transition no Work Spec decides. R12 is different in kind — its predicate is about what this batch is doing, and
required_route_idsis exactly where a batch says that. Narrowing now reaches R12 and nothing else, via aNARROWABLE_PHASE_OVERRIDESset that names the distinction instead of leaving it implicit. An unrevised Work Spec is unaffected.3. The phase gate computed coverage and identity over different groupings
acked_idsunioned every attempt, and attempt purity was checked separately afterward, so the two never composed. Both failure directions were live:len(attempts) > 1. Neither context could judge, and no integrator could pass the batch.Coverage is now judged per attempt at both kinds of edge. A bound edge requires the actor's own attempt to cover the phase by itself; an unbound edge requires that some one attempt does. Another context's chain sitting in the same history is no longer a fault at a bound edge — only the actor's own absence is.
Why the suite did not catch 2 or 3
Neither can appear with one execution context and one Work Spec shape, which is all the fixture ever had. The new tests aim at that blind spot rather than at the changed lines: the four narrowing combinations including "an unrevised Work Spec still owes R12", two complete chains not blocking each other, and two half chains not summing to a delivery.
The last of those initially sat on the gate phase, where the fixture has a single piece, and skipped silently — the most important new assertion was not running. It now sits on preflight and asserts the phase has something to split, so a future fixture shrink fails loudly instead of quietly passing.
Size register
K13/21grows 5421 → 8138 bytes and takes aK00/16exception at 8.5KB. Each of its six sections was put through the split test and fails on the same clause: no consumer reaches any one side without the phase set — a writer told it owesbatch-gatecannot act without knowing whatbatch-gatecontains. The registered split condition namesPhase Packagesas the nearest future candidate, and only once packing gains rules beyond the budgetK13/19already owns.Verification
make ci: 2148 tests, 3 failures, all three pre-existing onmainfrom untracked template WIP and proven unrelated bygit stash.stamp_cards --checkstale=0 after re-reviewing the two Cards whose sources changed;check_moc0 candidates.