Deliver Card context one phase at a time - #139
Merged
Merged
Conversation
v3 made delivery provable and, in doing so, made every batch pay for the whole task's route union before it could be called running. AR-001 measured what that costs in practice: 23 pieces and 165KB over 560 seconds for a single-page corrective batch, of which the governance route it never entered was 15 pieces and 68% of the bytes. The Card that K00/01 defines as a faithful compression for routine work had become the trigger for shipping a manual. v4 changes when frozen bytes travel, not what is frozen or what is proved. Admission still resolves every identity at the same moment -- now including the phase each piece belongs to and the environment that resolved them, sealed together as phase_plan_sha256 -- and the bytes travel one phase at a time. Each phase gate is still an exact set equality, so the property v3 bought is kept per phase rather than spent all at once. Three things make that trade honest rather than a weakening. The authoritative pointer is derived, not stored. delivery_attempt_id was already the hash of (card_bundle_sha256, execution_context_id), so a consumer recomputes the one value the current bundle and the acting context could produce. A complete ack chain from a superseded bundle stays internally self-consistent and stops matching that derivation; so does one borrowed from another context. Consistency alone was never the test, and now nothing has to store a field for that to be true. The gates have consumers. v3's ack completeness had none: update_queue's own comment deferred to an Assignment gate that does not exist, and frozen_piece_ids had no production caller. The gate phase is now refused by record_batch_judgment (bound to the actor's context, because a judgment is somebody's judgment) and by the merge-ready edge (checking history, so unbound). Governance is enforced by what a batch changes rather than which tool it runs: a manifest touching kernel/, profiles/ or Tools/ owes the governance phase at merge-ready, an edge no file edit can route around. running itself is still spec-only and K13/20 now says so in its own text. Phases are packed, not fragmented. A part is one tool result with one trailing nonce -- v3's single-piece proof with the file count widened -- and every file keeps its own frozen hash and drift check. A standard phase that needs two parts fails admission: that is a plan cut too wide, not a transport problem. An oversized leaf still fails closed on its own, checked before packing so it can never be reported as a phase that was merely wide. Also here: host-conformance moves to schema 2, grouping registrations by channel with three independent dimensions, because a native file-read channel can prove identity and acknowledgement while proving no transport at all -- and a registry with one global "conformance_version" would have let it borrow an assurance it cannot earn. K00/01 carries the principle this all serves, as normative text rather than implication: a Card is the minimal executable checklist for the current phase, not the Standards in full, a knowledge container, a receipt, or a transport unit. K13/21 owns the phase plan; K13/19 keeps transport and budget; K13/20 keeps Assignment state. K00/16 records both size raises, and registers the split K13/19 must take instead of a fourth one. Sealed v1-v3 receipts replay under their own era untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adopting the branch into a real corpus found what the unit suite could not, and both defects were the same shape: a check that named the current protocol constant where it meant a class of shapes, and a value computed correctly and then dropped on the floor. Era detection compared against ACTIVATION_PROTOCOL. That constant now says v4, so every sealed v3 receipt was re-filed under the embedded-payload rules of v1 and the adopting runtime refused its own history -- "activation bundle must carry at least one Card" against a bundle that correctly carries pieces. The comparisons now test PIECE_DELIVERY_PROTOCOLS, which is what they always meant, and the error text quotes the record's own era rather than today's. The phase delivery reached the receipt writer with phase_context still unset, because _run's call site never passed it: an earlier edit replaced the same two lines twice inside _write_receipt and left the caller alone. The result was a receipt whose mode said deliver-phase and whose phase fields were all null -- exactly the "receipt claims what it did not carry" failure this protocol exists to prevent, reproduced in the protocol's own plumbing. Both gaps existed because the tests exercised the library and the adoption exercised the tool. So the suite now covers what the library round trip cannot see: a producer-era class that replays v3 contexts through validation, single-piece delivery and the phase refusal, plus a CLI class that runs the actual commands and asserts the receipt carries the phase and the payload does not. Also: the phase branch of the CLI now round-trips deliver and ack, verified end to end against a real corpus -- preflight for a live batch is 6 pieces and 42,946 bytes in one part, against 23 pieces and 180,120 bytes across 46 calls under v3, with the governance route's 15 pieces held out of startup. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The predicate is about the "profiles/" prefix, not about any one adopter, and a public test has no business naming a private instance's profile. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Aug 23, 2026
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 #138.
Implements an Accepted ADR held as a maintainer design record outside this
repository. Its decisions are restated below, so nothing here depends on
reading it.
The principle this restores
The ADR states it before any decision, and it is the whole point of the change:
v3 froze one manifest of the task's whole route union and made all of it a precondition of
running. That is a knowledge-loading container wearing a Card's name.What changes
Phases, not one pile. Admission still freezes a closed set — that is what makes
deliveredfalsifiable — but it now freezes it partitioned into five phases:batch-preflight,batch-running,batch-gate,governance,task-completion. Each is owed at the moment its work begins, not before the batch starts. Routes map to phases by a registered override table (R01→preflight, R09→governance, R12→gate, R08→task-completion); everything else lands in running. A batch that never enters governance never pays for R09.The partition is frozen by content, not by rule.
phase_plan_sha256seals four things together: the phase→piece assignment, the resolver version, the Standards/Profile snapshot the routes were resolved under, and the Work Spec fingerprint that narrowed them. Change any of them and the plan invalidates, because "the rules were the same" is not the same claim as "the set was the same".The current attempt has an authoritative pointer, and it is derived.
delivery_attempt_id = sha256([card_bundle_sha256, execution_context_id])[7:39]. Consumers recompute it rather than reading a stored field, so "this evidence is for the current bundle" and "this actor is the one who read it" are the same check, and no new mutable state exists to drift.Gates now have consumers. Previously
frozen_piece_idshad no production caller andupdate_queue's comment deferred to a gate that did not exist. Now:record_batch_judgmentrequires the gate phase bound to the judging actor's own context;update_taskrequires the task-completion phase likewise; theopen -> merge-readyedge requires the gate phase, plus the governance phase when the batch touches the control plane.Governance triggers on the object, not the tool name.
batch_touches_control_planereads the batch manifest forkernel/,profiles/,Tools/prefixes. A tool-name allowlist is bypassed by editing a file; an edge predicate over the manifest is not. Enforced at merge-ready rather than at each writer's admission, for the same reason.Proof is three independent dimensions, not a ladder.
host-conformance.yamlgoes toschema_version: 2with a channel list declaringproves_identity/proves_transport/proves_acknowledgementseparately.agent-native-file-readproves identity and acknowledgement and explicitly does not prove transport — under v3's monotonic states that channel had to be misfiled as something it was not.Kernel
New
K13/21 Phased Reading Planowns the phase set, the mapping, and the invalidation rules.K13/19andK13/20are revised to point at it rather than restate it;K13/20gains aPhase Scopesection that admits in writing which phase is spec-only today.K00/01carries the principle above. Two size-register entries were added, one of them (K13/19, 11KB→12KB) with a registered split condition namingFrozen Review Planas the next thing to leave.Verification
make ci: 2142 tests, 3 failures — all three pre-existing onmain(untrackedprofiles/_template-minimal/and a stray.DS_Store), proven unrelated bygit stash. New suiteTools/tests/test_phased_readback.py, 33 tests.Adopted into a real corpus as Standards 3.14.0 and run end to end (
require-ready→open→--deliver-phase batch-preflight→--ack-activation-phase), withrun_gatesat 12 pass / 0 fail / 3 standing holds:runningThe governance route's 15 pieces are still frozen and still owed — just not until governance begins.
Deviations from the ADR, and why
delivery_attempt_idwas already a deterministic function of the bundle and context, so a stored field would have added drift without adding information.check_batch_close.closedis reachable only frommerge-ready, which enforces the predicate. A second copy of the check would be a second thing to keep true.batch-runningphase is specified but has no consumer. There is no event that marks "running work begins" for a machine to hang a gate on.K13/20 Phase Scopesays so in the spec rather than leaving the gap for a reader to discover.