core/docs/adr/ADR-0263-ratified-ledger-bridge.md
Shay efd3bee9ce chore(governance): stamp nine merged ADRs Accepted + pin the invariant
Nine ADRs (0254, 0256-0263) were merged into main and left stamped Proposed.
Two carried an explicit ratify-on-merge predicate their own merge had already
discharged, and ADR-0256 governs deduction_serving_enabled, which was ratified
True on 2026-07-24 and is serving live traffic. So the governance record
asserted "not yet decided" about a decision already in force.

That is the asymmetry the assessment arc (#113) found one file over in
workbench/api.py: the honest path degrades, the stale record lies. An unwritten
ADR is a visible gap; a Proposed one that is actually in force is a false
statement.

Each stamp records the ACTUAL ratifying act -- "Accepted, ratified by Joshua
Shay via <merge> (<sha>, <date>)" -- derived from the commit that added the file
and verified an ancestor of main, not assumed. Merge authority is Shay's alone
(AGENTS.md: no merge automation), so the merge IS the ratifying act.

No ADR content changed. No flag changed. ADR-0262's stamp says so explicitly:
accepting it does NOT enable curriculum_serving_enabled, which stays False
pending ratified volume -- eleven bands re-measured today, still 24x-73x short.

tests/test_adr_status_governance.py pins two independent invariants in the
smoke (pre-push) suite:

1. A default-ON flag is not governed by a Proposed ADR. The flag -> ADR mapping
   is DERIVED by walking core/config.py for `<name>: bool = True` and reading
   ADR refs from the preceding comment block -- not a hand-written table, which
   would be the same second-copy-of-a-closed-set defect ADR-0256's arc fixed.
2. A ratify-on-merge predicate cannot coexist with Proposed. Self-discharging:
   the file being on main IS the merge having happened.

Plus a vacuity guard, because a derivation that parses zero flags would make
every other assertion pass on an empty set.

Registered two orphans found in passing: test_adr_index.py (5) and
test_ratification_ceremony.py (14) landed in #113 in NO curated suite, so 19
tests -- including the one mechanism that can move curriculum volume -- ran
only under `full`, which gates nothing. Fifth instance of this shape.

Deliberately NOT fixed, recorded in the research doc: the 312-file corpus has
27 unparseable status lines and draft/ratified/active variants. A closed-vocab
assertion would fail on ~35 pre-existing files and get muted, and a muted gate
reads as coverage.

[Verification]: smoke 555 passed in 137.73s (236 baseline + 314 + 5, +1.2s);
governance pin 314 passed in 1.18s standalone and MUTATION-CHECKED -- reverting
ADR-0256 to Proposed fails both invariants independently (2 failed/312 passed);
orphans 19 passed; ruff clean. Canonical Python 3.12.13, uv sync --locked.
2026-07-25 14:38:02 -07:00

4 KiB

ADR-0263 — The ratified-ledger bridge

  • Status: Accepted — ratified by Joshua Shay via the feat/curriculum-serve-physics merge (0ae54ebb, 2026-07-24). Amended 2026-07-25 with rule 5 (ledger-absence policy declared, not passed)
  • Date: 2026-07-24
  • Arc: generalization Phase 3.3 (docs/plans/generalization-arc-2026-07-24.md §2)
  • Governs: core/ratified_ledger.py and the three adapters over it — generate/determine/estimation_license.py, chat/deduction_serve_license.py, chat/curriculum_serve_license.py — plus the sealed-artifact writer in evals/deduction_serve/practice/runner.py.

1. Context

Three capabilities had independently converged on the same four-step pattern: sealed practice writes a per-class tally table → the table is committed as a ratified artifact → serving verifies its content_sha256 on load → a per-class gate decides SERVE against fixed ceilings.

Three near-identical implementations is the point at which a shared component is an extraction rather than a guess. It matters now because the generalization plan puts a per-subject arena behind every new subject: without a bridge, each subject copies fifty lines of verification, and the copies drift.

2. Decision

core/ratified_ledger.py owns the mechanics — seal_artifact, write_sealed_ledger, load_sealed_ledger, serve_license, tally_dict — and states the four rules once:

  1. The engine reads; only sealed practice writes.
  2. Tamper-evidence is structural — a load that cannot reproduce content_sha256 REFUSES. A hand-edited ledger is not a slightly-wrong ledger; it is an unratified one.
  3. Ceilings are not negotiable at the call site (ADR-0175 invariant #4).
  4. Absent evidence is never a license — a missing class yields None, and every caller's None branch serves the disclosed surface.

Each capability keeps a thin adapter that names its artifact, keeps its memoization, and preserves its public API. One genuine difference is now declared rather than implied: load_sealed_ledger(..., missing_ok=True) distinguishes a ledger a capability ships with (absence = broken deployment, refuse) from one whose practice volume is still being built (absence = no class has earned anything, serve disclosed). Curriculum serving is the second kind today (ADR-0262 §5.1).

3. Why this is safe

The extraction's safety property is byte-identity: seal_artifact and write_sealed_ledger reproduce exactly what the bespoke sealers wrote, so re-sealing through the bridge leaves every committed artifact and every lane pin unmoved. That is asserted, not assumed — test_committed_deduction_ledger_reseals_byte_identically re-derives the committed 25-band ledger through the bridge and compares the bytes.

4. What this unblocks

A new subject arena now needs a gold corpus and a band key. It does not need to re-implement ratification — which is what the plan meant by "so each subject arena plugs in without bespoke wiring", and why §3 sequenced this ahead of the second subject.

5. Scope-outs

  • The estimation adapter keeps its predicate → converse-class naming. That mapping is genuinely local to estimation; the bridge gates a class name and does not know how one is derived.
  • No ledger migration. All three artifacts already share the schema shape; nothing is rewritten, versioned, or moved.
  • core/learning_arena's practice engine is untouched. The bridge covers the consumption half (seal → serve); the production half (run practice, tally) is already shared.

6. Verification

tests/test_ratified_ledger_bridge.py — 8 tests: round-trip, hand-edit rejection, required-vs-optional absence, absent class never licensed, the Wilson volume floor, a single wrong costing the license, committed-ledger byte-identity, and a guard that all three adapters read through the bridge (they no longer import a hashing module of their own). Plus the existing consumers unchanged: core test --suite deductive 252 passed; the estimation and license test set 355 passed.