A202-0009: Enforcement fidelity: every claimed check is a real check¶
Status: Fixtures and compatibility. Stage 3 of the five stages in README.md section 3. Every change in this proposal is implemented in the runner, the schemas, the fixture set, and the manifest, and the suite passes with the changes in place.
Date: 28 July 2026
Status of this document: Informative in full. It states no requirement on an implementation. The normative text this proposal amends is carried by the documents it names, each of which marks its own normative sections.
1. Problem¶
An external deep-dive review of the specification set, conducted 28 July 2026, found a consistent failure class: the prose claims machine enforcement that the machine layer does not provide. The individual findings differ, but the shape is one shape: a sentence says "this is checked", and nothing checks it.
The instances this proposal closes:
- Delegation narrowing was neither checked nor fixtured. The canonical model's section 12 said every listed invariant is checked by the runner with a negative fixture each;
A202-MANDATE-DELEGATION-WIDENINGhad neither. A child mandate that outlived, out-scoped, out-acted, and out-spent its parent tenfold validated clean. - Agreement hashes were never recomputed. The verification procedure requires checking
terms_hashandaccepted_offer_hash; no layer did. A dual-signed agreement whose hashes were unrelated to its own terms passed the schema, the runner, and the reference verifier. - Negative fixtures were not verified to fail for their declared reason. The manifest declares a
reason_codefor every negative fixture and the runner ignored the field. One declared code was already wrong, which is what an unasserted declaration decays into. - The rival-disclosure denylist did not reach the transaction stream, although the runner's own comment said it did. Post-commit the winning counterparty reads that stream.
- The event-chain check was weaker than the procedure it implements.
previous_event_hashwas checked to name some same-stream event, not the immediately preceding one. A chain that skipped an event replayed clean. - Quantity comparison used binary floating point in the runner, against the mandate specification's own decimal-arithmetic requirement, and failed open on unparseable input.
- The disclosure-bearing reason-code list had one member. A deny carrying
A202-LOT-ALREADY-AWARDED, which tells a bidder an award happened, validated. - A determination's
state_resultwas an unconstrained object, described as "the only field through which a determination touches state". A binding determination could carry a bag of transitions across three state machines in one unvalidated blob. - The mandate second layer did not exist. All eight mandate negatives were schema-caught only, so the codes an implementation must return for them were never exercised, and several codes used by the manifest resolved in no registry.
- The identifier-smuggling claim overstated the pattern layer. Canonical 12.1 said a value cannot be smuggled into an identifier field; a session event's
clarification_idcould carry a rival's price in its free characters under a generic prefix. - Restated fixture counts had gone stale in three documents, including the role-scope partition, which classified thirty-eight fixtures out of what had grown to one hundred and six, leaving the whole obligation, evidence, dispute, determination, and settlement families ungradeable in either scope.
2. Change¶
- Runner, mandate layer. A
mandate_checksevaluator enforces the interval, boundedness, scope, subject, constraint-registry, delegation-coherence, and status-transport rules independently of the schema, emitting the codes the manifest declares. Adelegation_checksevaluator and a newmandate_chainfixture kind carry a parent and child pair and refuse widening on every axis of the mandate specification section 7, under decimal comparison. One valid chain fixture and one widening fixture per axis are added. - Runner, hashes. Agreement
terms_hashis recomputed andaccepted_offer_hashand acceptanceoffer_hashare checked against the referenced objects wherever they are disclosed, refusing withA202-AGREEMENT-HASH-MISMATCH. The reference verifier gains the same checks in step 1. Negative fixtures cover the standalone and bundle directions. - Runner, reasons. The runner asserts that a negative fixture's raised codes include its declared
reason_codewherever the normative layer raises codes at all; a fixture the schema alone refuses remains legitimate. The one wrong declared code is corrected. - Runner, disclosure. The rival-key scan applies to every stream kind, recursively over nested members, with a negative fixture on the transaction stream.
A202-LOT-ALREADY-AWARDEDjoins the disclosure-bearing set, with a fixture, and the auction document states the same policy rule for it as forA202-BID-NO-IMPROVEMENT. - Runner, chains.
previous_event_hashmust name the immediately preceding same-stream event. A regression fixture carries continuous sequences with a skipping hash chain, which the previous runner passed. - Schema and evaluator,
state_result. The shape closes to one subject and one registered state token; the evaluator refuses a subject differing from the determined question, an unregistered state, and anystate_resultunder a non-binding effect. Fixtures cover both refusals. - Session reference prefixes.
clarification_idandapproval_idbind to the registeredclr_andapr_prefixes, theclr_prefix is registered in the canonical model's prefix table, and canonical 12.1 now states accurately that patterns constrain the claimed family while opacity is enforced at resolution. A fixture refuses a foreign-prefix reference. - Registry completeness. The state machine's section 10 table is declared the registry, jointly with the auction and role-scope tables, and absorbs every code the manifest, runner, and other documents were already using.
- Counts. The three documents that restated fixture counts now defer to the manifest as the single source. The role-scope partition is restated by family pattern so that it covers the whole manifest and does not go stale as the set grows; the reference package's suite test derives its expected total from the manifest instead of a hardcoded number.
3. What this proposal does not change¶
No object changes shape except state_result, whose previous shape was an open object nothing could rely on. No state, transition, or guard changes. No new aggregate capability is introduced; that is A202-0010. The changes make the specification's existing claims true, which is why they land as one proposal.
4. Compatibility¶
Under RELEASES.md section 2 the set is pre-release and pre-1.0. The change set is MAJOR-shaped where it tightens (state_result shape, session reference prefixes, the stricter chain check, hash recomputation): an implementation that relied on the untightened forms was relying on gaps the prose already denied it. It is MINOR-shaped where it adds (the mandate_chain kind, new fixtures, registry rows). Pre-1.0, the set may break on a MINOR increment with migration notes; this section is those notes, and the affected surfaces are exactly the four named in this paragraph.
5. Fixture plan¶
Implemented, not planned: valid-mandate-delegation-chain, four mandate-chain-* widening negatives, agreement-terms-hash-mismatch, transaction-event-data-discloses-rivals, policy-deny-award-disclosing-reason, session-event-clarification-foreign-prefix, determination-state-result-unregistered-state, determination-state-result-without-binding-effect, and evidence-bundle-event-chain-skips-predecessor. The suite passes at the new totals the manifest carries, with the reason assertion active.