Contracts
gate-extension.md
Contract: Gate Extension, Sanctions, and Floor Transfer
Binding contract for FR-001 / FR-005 / FR-006 / FR-007 / FR-012 and NFR-004/005/007 (IC-01, IC-05, IC-06).
E1 — Close the holes BEFORE migrating (FR-001, IC-01)
Two holes exist today; migrating into them would trade visible semi-compliance for invisible non-enforcement.
| Hole | Requirement |
|---|---|
| One site passes a handle from a canonicalizer the fold set does not recognise — neither routed nor allow-listed, passing by omission | either route the site or teach the gate that canonicalizer; the choice MUST be recorded, not implicit |
| The fold-prescription gate's three allow/flag sets bless only the blind primitive, so a directory obtained from the seam is in neither the sanctioned nor the flagged set | widen the sets to know the tier-1 seam idiom, so migrated sites are affirmatively sanctioned |
Loosening guard: widening a blessing set is a relaxation unless paired with a bite test proving the gate still flags a bad read in a migrated module (NFR-005, SC-004).
E2 — Censused callees (FR-012)
gate covers (resolve_feature_dir_for_mission), and the primary primitive, which inherits the guarantee transferred from the retired use-count floors (E5).
primary primitive on the grounds that its fail-loud surface is zero and it was already censused on the anchoring axis. That held while the floors survived. Once the operator prescribed delegate-then-remove and FR-007 retired the floors, exclusion would leave the primitive policed by nothing — so it is censused here, with its end-state sanctions (resolver-internal + the four named foundation sites of FR-005). The ~34 in-flight sites are expected red between the gate landing and the migration completing; that red is the mission's acceptance signal (US8 / FR-023), not a defect.
primitive, on which axis, with its sized residual (FR-016/FR-017).
- The censused-callee set grows 2 → 4: the kind-blind, topology-routed resolver that no
- Superseded framing (recorded so it is not re-derived): an earlier draft excluded the
- The ledger's Known-gap section still records which gate covers each non-censused
- Alias resolution MUST hold: an aliased import cannot defeat the census.
E3 — Sanctions: asserted, never silently skipped
| Requirement | Detail |
|---|---|
| Per-file rationale | each sanctioned module carries a written reason |
| Per-primitive non-vacuity | the meta-test MUST prove a sanctioned module carries a real finding for the newly censused primitive, not merely for a previously censused one — otherwise the new primitive's sanctions are vacuously "proved" |
| Blanket-excluded seam internals | sites under the pinned scan-scope prefix cannot be brought into scope (the prefix set is frozen and guarded); accountability is a per-file rationale entry plus the per-primitive assertion above |
| Foundation sites (FR-005) | the four sites beneath the seam are recorded by name with their recursion rationale and remain unrouted (NFR-009) |
E4 — Allow-list
routed or removed is deleted.
- Entries are per-site content descriptors; no path-scoped blankets (C-003).
- Shrink-only, with a staleness twin-guard that reds until an entry whose site was
- For preserved sites, the existing production comment is the rationale of record.
E5 — Floor retirement / transfer (FR-007, NFR-007, IC-06)
The two use-count floors count uses of the blind primitive. After Step 2 their subject population is only resolver-internal and named-sanctioned sites, where a raw handle is correct by contract — so the floors would guard nothing and instead oblige the primitive to keep being used, inverting their purpose.
| Requirement | Detail |
|---|---|
| Preferred end state | retire both floors and transfer the guarantee to the bypass census (E2/E3) |
| Mechanical fallback | re-pin to the honest post-migration numbers, respecting the existing margin rule |
| Either way | record the before/after integers and the reason ("a routing shrink"), per the floors' own doctrine — never relax silently |
| Two-file edit | the floor values are asserted as bare literals in a second module too; both MUST move together |
| Companion bookkeeping | the blessed-name allow-list in the trio gate shrinks (its own failure text prescribes this — a tightening); the pin-existence test paired with the retired residual pin is retired with it (FR-014) |
E6 — Acceptance
| Check | How verified |
|---|---|
| Bite | planted direct call reds; planted aliased call reds; prose mention stays green |
| No green-by-omission | planted bad read in a migrated module still flagged |
| Per-primitive non-vacuity | assertion parameterised by primitive; passes for the new one on its own merits |
| Allow-list integrity | stale entry reds; no path blanket present |
| Ledger parsed | mutation per primitive reds (see ledger-grammar.md G3) |
| Floors | either absent with the guarantee transferred, or re-pinned with before/after recorded; no test obliges the primitive to remain in use |
| Foundation sites | recorded by name, still unrouted, no resolution cycle |
| Verification scope | only the named gates run locally (C-008); the exhaustive sweep is CI's |
ledger-grammar.md
Contract: Ledger Grammar and Per-Site Index
Binding contract for FR-008 / FR-009 (IC-02). This is a hard prerequisite (C-009): no classification row for a newly censused primitive may be written until these hold.
Why this is a prerequisite, not a finishing touch
The originally-prescribed "per-primitive sub-tables" shape was executed against the real parser. Result: only the first table under a parsed heading is read; the second primitive's rows vanish; and the reconciliation test stays green. That is a silently vacuous gate — the exact failure a previous landing pass had to repair on this same gate. Adding rows first would bake it in.
G1 — Parse constraints (normative)
| Rule | Failure it prevents |
|---|---|
| Exactly one table under each machine-parsed heading | second table silently dropped |
| Parsed headings kept verbatim (located by exact full-line match) | section not found → parses empty |
Verdict / rel_path / qualname remain at their current leading column positions | positional readers silently skip or mis-read rows |
A primitive discriminator is appended as a trailing column | a leading/middle insertion breaks the positional contract; appending fails loudly if mis-specified |
| No duplicate key within a counts table | duplicate keys overwrite silently, last-wins |
| Non-numeric cells in count columns are an error, not a skip | silent row skipping |
G2 — Index grammar (per-site addressing)
The stay-lenient index is keyed per site, not per function. It MUST be able to address several censused sites inside one qualname — a known case carries four (one existing primitive plus three of the newly censused one).
composite) so each site is distinct.
otherwise it reds by construction.
- The key gains a discriminator (trailing
primitive+ site token, or an equivalent - The uniqueness assertion MUST be updated in the same change as the key shape;
- Acceptance fixture: the known four-site qualname.
G3 — New gate assertions (this is what makes the grammar enforceable)
1. Row-count reconciliation — parsed row count equals the summed per-primitive census. A dropped table or shifted column makes this red rather than parse-empty. 2. Per-primitive mutation — mutating a row belonging to each primitive independently MUST red the gate. A single-primitive mutation test would pass while the second primitive is unenforced. 3. Counts scoping — reconciliation covers the live residual / lenient totals, which are the figures the gate parses. The historical pre-migration totals are preserved and labelled as an audit record, never rewritten to satisfy a check.
G4 — Acceptance
| Check | How verified |
|---|---|
| G1 holds | the reconciliation and mutation tests above, plus a review of the section shapes |
| G2 holds | the four-site qualname is representable and the uniqueness assertion passes |
| Mutation reds per primitive | scratch-copy mutation per primitive, each expected red |
| Historical totals intact | the pre-migration figures still present and labelled |
| Ordering respected | no new classification row exists in any commit before the commit that lands G1–G3 |
placement-layering.md
Contract: Placement Layering (what the architecture page must state)
Binding contract for FR-018 / FR-020 (IC-09). The page is docs/architecture/artifact-placement-seam.md. This contract fixes what must be true of the page, not its prose.
C1 — Required content
The page MUST contain, each as a named section:
1. What "routing" means here — the placement sense: mapping an artifact kind plus the mission's topology to a TopologySurface. It MUST point at the glossary's Routing disambiguation for the other senses rather than restating them. 2. The layer table — one row per layer (L0 entry, L1 partition classification, L2a declared decision, L2b affirmative decision, L3 discovery/assembly, L4 translation), each naming its owning module and what it is aware of. See data-model.md §1. 3. Both composition roots — the read root and the write root, shown as separate roots reached through one seam object (INV-4). 4. The compliance taxonomy — compliant tier-1 / delegating-but-lenient / semi-compliant / non-compliant, with the shape of each (data-model.md §1). 5. Honest bounds — the surface members with no production producer, and the frozenset still carrying a retired word as residual rename debt (INV-5). Named, not laundered. 6. Citations — ADR 2026-06-24-1 (kind-and-topology-aware placement) and ADR 2026-07-23-1 (TopologySurface vocabulary + the forbidden-conditioning rule) as the governing decisions.
C2 — Required accuracy (the load-bearing corrections)
is materialization-blind so that it can disagree with an already-resolved stamp; that divergence is what allows the existing surface-cannot-hold guard to fire. Describing "one decision module" is a defect.
already-discovered location and refuses when absent. Assembly belongs to L3. Getting this wrong teaches readers to strangle through L4 and re-add discovery at the call site — the misappropriation the page exists to prevent.
reading, and so a future rename shows up as a broken citation rather than silent rot.
- The L2 split is deliberate and MUST be shown as two functions. The declared decision
- Translation MUST NOT be described as assembling a path. It selects an
- Every code-shape claim MUST carry a
module:symbolcitation so drift is detectable by
C3 — Prohibitions
forbidden-conditioning rule, alias retirement) — link to them. The page is explanatory.
PRIMARY partition, COORD partition).
- MUST NOT restate normative rules the ADRs own (the placement invariant, the
- MUST NOT introduce a synonym for a concept the glossary already names (
TopologySurface, - MUST NOT be named
*-routing.md— the word is already overloaded across ≥10 senses.
C4 — The competing authority MUST be narrowed in the same slice
docs/architecture/branch-target-routing.md currently asserts per-artifact-kind placement rules (the new page's core claim) under a branch-sense title, in vocabulary the glossary retires, with no read path. In the same slice it MUST be reduced to the branch sense and link to the new page for placement. Leaving both is the two-authority failure this mission exists to end.
C5 — Acceptance
| Check | How verified |
|---|---|
| Sections C1.1–C1.6 present | docs test asserting the named sections and the citations |
| Layer table matches the code | reviewer reads each row's cited module:symbol |
| No prohibited synonym; terminology guard green | terminology + glossary-canonical-terms gates |
| Registered and discoverable | docs/architecture/index.md entry; both gated registries regenerated; links resolve; check_docs_freshness --ci zero errors |
| Competing page narrowed | the old page no longer asserts kind-level placement rules, no longer uses the retired alias, and links out (SC-017) |
| Comprehension | a reader who has not followed the programme can state the three questions in User Story 7's Independent Test |