Skip to content

Close three gaps the pre-adoption audit found in v4 - #140

Merged
KimGLee merged 1 commit into
mainfrom
fix/v4-phase-gate-defects
Aug 23, 2026
Merged

Close three gaps the pre-adoption audit found in v4#140
KimGLee merged 1 commit into
mainfrom
fix/v4-phase-gate-defects

Conversation

@KimGLee

@KimGLee KimGLee commented Aug 23, 2026

Copy link
Copy Markdown
Owner

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/20 states that which writer refuses which phase is owned by K13/21. K13/21 never said.

Three enforcement points exist in code — the judgment writer, update_task's completion-candidate transition, and the open -> merge-ready edge — 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, and semantic_stale cannot see it: it compares source hashes, not whether the Card's claims trace to source.

K13/21 gains a Phase Consumers section 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 why prepared/degraded are exempt.

Every other delegation #139 introduced was walked (K13/19K13/21, K00/02K13/21, K00/06K13/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 continue on 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_ids is exactly where a batch says that. Narrowing now reaches R12 and nothing else, via a NARROWABLE_PHASE_OVERRIDES set 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_ids unioned every attempt, and attempt purity was checked separately afterward, so the two never composed. Both failure directions were live:

  • 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 own error text already claimed it tested. The unbound branch then refused on len(attempts) > 1. Neither context could judge, and no integrator could pass the batch.
  • Two attempts covering half the phase each read as complete, because the union covered it and no single reader ever had.

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/21 grows 5421 → 8138 bytes and takes a K00/16 exception 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 owes batch-gate cannot act without knowing what batch-gate contains. The registered split condition names Phase Packages as the nearest future candidate, and only once packing gains rules beyond the budget K13/19 already owns.

Verification

make ci: 2148 tests, 3 failures, all three pre-existing on main from untracked template WIP and proven unrelated by git stash. stamp_cards --check stale=0 after re-reviewing the two Cards whose sources changed; check_moc 0 candidates.

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>
@KimGLee KimGLee added bug Something isn't working documentation Improvements or additions to documentation agent-interface Host-neutral agent interface: contract, projection, MCP server, per-host configs labels Aug 23, 2026
@KimGLee
KimGLee merged commit be2f376 into main Aug 23, 2026
12 checks passed
@KimGLee
KimGLee deleted the fix/v4-phase-gate-defects branch August 23, 2026 02:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent-interface Host-neutral agent interface: contract, projection, MCP server, per-host configs bug Something isn't working documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Three v4 decisions landed halfway, with no machine check over the other half

1 participant