refactor(learning): extract the ratified-ledger bridge from its three instances (ADR-0263)
Phase 3.3 of the generalization arc. Estimation (ADR-0175), deduction serving (ADR-0256) and curriculum serving (ADR-0262) had each written the same seal -> ratify -> SHA-verify -> serve-gate machinery. core/ratified_ledger.py now owns it and states the four rules once: only sealed practice writes; tamper-evidence is structural (a load that cannot reproduce content_sha256 REFUSES); ceilings are not negotiable at the call site; absent evidence is never a license. Each capability keeps a thin adapter that names its artifact and preserves its public API. One real difference is now declared rather than implied: missing_ok distinguishes a ledger a capability SHIPS with (absence = broken deployment, refuse) from one whose practice volume is still being built (absence = nothing earned yet, serve disclosed) — curriculum serving is the second kind today. Safety property is byte-identity, asserted not assumed: re-sealing the committed 25-band deduction ledger through the bridge reproduces it byte-for-byte, so no artifact and no lane pin moves. Effect: a new subject arena now needs a gold corpus and a band key, not a re-implementation of ratification — which is what §3 meant by sequencing the bridge ahead of the second subject. [Verification]: tests/test_ratified_ledger_bridge.py 8 passed; core test --suite deductive 252 passed; estimation/license test set 355 passed; committed deduction ledger byte-identical after reseal.
This commit is contained in:
parent
44e78aa438
commit
0a17c49693
8 changed files with 409 additions and 146 deletions
|
|
@ -10,63 +10,40 @@ never writes it — the sealed-practice output of
|
|||
its ``content_sha256`` is verified on load so a hand-edited ledger is rejected
|
||||
rather than silently trusted.
|
||||
|
||||
A deliberate near-copy of ``chat.deduction_serve_license``: the second
|
||||
instance of the seal → ratify → SHA-verify → serve-gate pattern in this arc,
|
||||
kept explicit here so the shared bridge (generalization plan Phase 3.3) is
|
||||
extracted from two working instances rather than designed from one.
|
||||
The third instance of the seal → ratify → SHA-verify → serve-gate pattern, and
|
||||
the one that made the shared bridge worth extracting: this module is now an
|
||||
ADAPTER over ``core.ratified_ledger`` (ADR-0263). It names the artifact, keeps
|
||||
the memoization, and declares the one thing that genuinely differs — this
|
||||
ledger is legitimately ABSENT, because no curriculum band has earned anything
|
||||
yet (ADR-0262 §5.1: the binding constraint is ratified curriculum volume). An
|
||||
absent ledger reads as an empty table, so every answer is served DISCLOSED.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
|
||||
from core.reliability_gate import Action, Ceilings, ClassTally, LicenseDecision, license_for
|
||||
from formation.hashing import sha256_of
|
||||
from core.ratified_ledger import (
|
||||
RatifiedLedgerError as RatifiedCurriculumLedgerError,
|
||||
load_sealed_ledger,
|
||||
serve_license,
|
||||
)
|
||||
from core.reliability_gate import Ceilings, ClassTally, LicenseDecision
|
||||
|
||||
_LEDGER_PATH = Path(__file__).resolve().parent / "data" / "curriculum_serve_ledger.json"
|
||||
|
||||
|
||||
class RatifiedCurriculumLedgerError(ValueError):
|
||||
"""The committed curriculum-serve ledger is missing, malformed, or tampered with."""
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def load_ratified_ledger() -> dict[str, ClassTally]:
|
||||
"""Load + verify the ratified curriculum-serve ledger → per-band tallies.
|
||||
|
||||
An ABSENT ledger is not an error: it means no curriculum band has earned
|
||||
anything yet, and every answer is served DISCLOSED. That is the honest
|
||||
default for a capability whose practice volume is still being built, and
|
||||
it keeps the composer usable before any license exists.
|
||||
An ABSENT ledger is not an error here: no curriculum band has earned
|
||||
anything yet, and the honest reading of "no file" is "no committed
|
||||
evidence", which the gate turns into a disclosed answer rather than a
|
||||
withheld one.
|
||||
"""
|
||||
if not _LEDGER_PATH.exists():
|
||||
return {}
|
||||
try:
|
||||
artifact = json.loads(_LEDGER_PATH.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError) as exc: # pragma: no cover - defensive
|
||||
raise RatifiedCurriculumLedgerError(f"cannot read ratified ledger: {exc}") from exc
|
||||
|
||||
classes = artifact.get("classes")
|
||||
if not isinstance(classes, dict):
|
||||
raise RatifiedCurriculumLedgerError("ratified ledger has no 'classes' table")
|
||||
if sha256_of(classes) != artifact.get("content_sha256"):
|
||||
raise RatifiedCurriculumLedgerError(
|
||||
"ratified ledger content_sha256 mismatch — not the sealed-practice output"
|
||||
)
|
||||
|
||||
ledger: dict[str, ClassTally] = {}
|
||||
for cls, counts in classes.items():
|
||||
ledger[cls] = ClassTally(
|
||||
class_name=cls,
|
||||
correct=int(counts.get("correct", 0)),
|
||||
wrong=int(counts.get("wrong", 0)),
|
||||
refused=int(counts.get("refused", 0)),
|
||||
t2_verified=int(counts.get("t2_verified", 0)),
|
||||
t2_agrees_gold=int(counts.get("t2_agrees_gold", 0)),
|
||||
)
|
||||
return ledger
|
||||
return load_sealed_ledger(_LEDGER_PATH, missing_ok=True)
|
||||
|
||||
|
||||
def curriculum_serve_license(
|
||||
|
|
@ -81,11 +58,7 @@ def curriculum_serve_license(
|
|||
caller serves a disclosed (hedged) surface, the safe default.
|
||||
"""
|
||||
ledger = ledger if ledger is not None else load_ratified_ledger()
|
||||
tally = ledger.get(band)
|
||||
if tally is None:
|
||||
return None
|
||||
ceilings = ceilings if ceilings is not None else Ceilings.default()
|
||||
return license_for(tally, Action.SERVE, ceilings)
|
||||
return serve_license(band, ledger, ceilings=ceilings)
|
||||
|
||||
|
||||
__all__ = [
|
||||
|
|
|
|||
|
|
@ -10,27 +10,27 @@ artifact is the sealed-practice output of
|
|||
is verified on load, so a hand-edited (un-ratified) ledger is rejected rather
|
||||
than silently trusted.
|
||||
|
||||
Mirrors ``generate.determine.estimation_license`` exactly: immutable ratified
|
||||
data parsed once and cached; the gate (``license_for``) is pure; ceilings stay
|
||||
at the safe defaults (invariant #4 — the engine cannot raise its own bar).
|
||||
The load/verify/gate mechanics live in ``core.ratified_ledger`` (ADR-0263) —
|
||||
this module is the deduction-serve ADAPTER over that bridge: it names the
|
||||
artifact, keeps the memoization, and preserves its own public API. Ceilings
|
||||
stay at the safe defaults (invariant #4 — the engine cannot raise its own bar).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
|
||||
from core.reliability_gate import Action, Ceilings, ClassTally, LicenseDecision, license_for
|
||||
from formation.hashing import sha256_of
|
||||
from core.ratified_ledger import (
|
||||
RatifiedLedgerError,
|
||||
load_sealed_ledger,
|
||||
serve_license,
|
||||
)
|
||||
from core.reliability_gate import Ceilings, ClassTally, LicenseDecision
|
||||
|
||||
_LEDGER_PATH = Path(__file__).resolve().parent / "data" / "deduction_serve_ledger.json"
|
||||
|
||||
|
||||
class RatifiedLedgerError(ValueError):
|
||||
"""The committed deduction-serve ledger is missing, malformed, or tampered with."""
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def load_ratified_ledger() -> dict[str, ClassTally]:
|
||||
"""Load + verify the ratified deduction-serve ledger → per-band ``ClassTally``.
|
||||
|
|
@ -39,30 +39,7 @@ def load_ratified_ledger() -> dict[str, ClassTally]:
|
|||
recomputed ``content_sha256`` does not match the committed one (tamper-evidence:
|
||||
only the sealed-practice output is trusted, never a hand-edited ledger).
|
||||
"""
|
||||
try:
|
||||
artifact = json.loads(_LEDGER_PATH.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError) as exc: # pragma: no cover - defensive
|
||||
raise RatifiedLedgerError(f"cannot read ratified ledger: {exc}") from exc
|
||||
|
||||
classes = artifact.get("classes")
|
||||
if not isinstance(classes, dict):
|
||||
raise RatifiedLedgerError("ratified ledger has no 'classes' table")
|
||||
if sha256_of(classes) != artifact.get("content_sha256"):
|
||||
raise RatifiedLedgerError(
|
||||
"ratified ledger content_sha256 mismatch — not the sealed-practice output"
|
||||
)
|
||||
|
||||
ledger: dict[str, ClassTally] = {}
|
||||
for cls, counts in classes.items():
|
||||
ledger[cls] = ClassTally(
|
||||
class_name=cls,
|
||||
correct=int(counts.get("correct", 0)),
|
||||
wrong=int(counts.get("wrong", 0)),
|
||||
refused=int(counts.get("refused", 0)),
|
||||
t2_verified=int(counts.get("t2_verified", 0)),
|
||||
t2_agrees_gold=int(counts.get("t2_agrees_gold", 0)),
|
||||
)
|
||||
return ledger
|
||||
return load_sealed_ledger(_LEDGER_PATH)
|
||||
|
||||
|
||||
def deduction_serve_license(
|
||||
|
|
@ -79,11 +56,7 @@ def deduction_serve_license(
|
|||
safe default ceilings.
|
||||
"""
|
||||
ledger = ledger if ledger is not None else load_ratified_ledger()
|
||||
tally = ledger.get(shape_band)
|
||||
if tally is None:
|
||||
return None
|
||||
ceilings = ceilings if ceilings is not None else Ceilings.default()
|
||||
return license_for(tally, Action.SERVE, ceilings)
|
||||
return serve_license(shape_band, ledger, ceilings=ceilings)
|
||||
|
||||
|
||||
__all__ = ["RatifiedLedgerError", "deduction_serve_license", "load_ratified_ledger"]
|
||||
|
|
|
|||
|
|
@ -184,6 +184,7 @@ TEST_SUITES: dict[str, tuple[str, ...]] = {
|
|||
"tests/test_exist_argument_reader.py",
|
||||
"tests/test_deduction_serve_e2e.py",
|
||||
"tests/test_curriculum_serve.py",
|
||||
"tests/test_ratified_ledger_bridge.py",
|
||||
),
|
||||
"full": ("tests/",),
|
||||
}
|
||||
|
|
|
|||
157
core/ratified_ledger.py
Normal file
157
core/ratified_ledger.py
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
"""The ratified-ledger bridge — seal → ratify → SHA-verify → serve-gate.
|
||||
|
||||
ADR-0175 Phase 5's consumption bridge (generalization plan Phase 3.3),
|
||||
extracted from three working instances rather than designed ahead of them:
|
||||
|
||||
- ``generate/determine/estimation_license.py`` (ADR-0175, the first)
|
||||
- ``chat/deduction_serve_license.py`` (ADR-0256, the second)
|
||||
- ``chat/curriculum_serve_license.py`` (ADR-0262, the third)
|
||||
|
||||
All three had converged on the same artifact and the same four rules, which is
|
||||
what makes this an extraction and not a speculation. The rules, stated once:
|
||||
|
||||
1. **The engine reads; only sealed practice writes.** A ledger is the output
|
||||
of a practice run over a gold corpus, never of a serving turn. Nothing in a
|
||||
serving path may call :func:`write_sealed_ledger`.
|
||||
2. **Tamper-evidence is structural.** The artifact carries
|
||||
``content_sha256`` over its ``classes`` table; a load that cannot reproduce
|
||||
it 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.** The gate always runs at
|
||||
the safe defaults unless a caller passes ceilings explicitly, and no
|
||||
production path does — ADR-0175 invariant #4: an engine cannot raise its own
|
||||
bar.
|
||||
4. **Absent evidence is never a license.** A class missing from the ledger
|
||||
yields ``None``, and every caller's ``None`` branch serves the disclosed
|
||||
(hedged) surface. A capability with no track record is served honestly, not
|
||||
withheld and not asserted.
|
||||
|
||||
Byte-compatibility is deliberate: :func:`seal_artifact` and
|
||||
:func:`write_sealed_ledger` reproduce the exact bytes the three existing
|
||||
sealers wrote, so adopting the bridge re-seals every committed ledger
|
||||
identically and no lane pin moves.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from core.reliability_gate import (
|
||||
Action,
|
||||
Ceilings,
|
||||
ClassTally,
|
||||
LicenseDecision,
|
||||
license_for,
|
||||
)
|
||||
from formation.hashing import sha256_of
|
||||
|
||||
|
||||
class RatifiedLedgerError(ValueError):
|
||||
"""A committed ledger is malformed or does not verify against its own hash."""
|
||||
|
||||
|
||||
def tally_dict(tally: ClassTally) -> dict[str, Any]:
|
||||
"""The committed per-class row. The field set is the contract — a reader
|
||||
of an older ledger must be able to name every field it finds."""
|
||||
return {
|
||||
"correct": tally.correct,
|
||||
"wrong": tally.wrong,
|
||||
"refused": tally.refused,
|
||||
"t2_verified": tally.t2_verified,
|
||||
"t2_agrees_gold": tally.t2_agrees_gold,
|
||||
}
|
||||
|
||||
|
||||
def seal_artifact(
|
||||
ledger: dict[str, ClassTally], *, schema: str, note: str, provenance: str
|
||||
) -> dict[str, Any]:
|
||||
"""The self-verifying sealed-ledger dict for *ledger*.
|
||||
|
||||
Classes are sorted, so the artifact is a pure function of the practice
|
||||
result: the same corpus and solver seal byte-identically, which is what
|
||||
makes a committed ledger reviewable as a diff.
|
||||
"""
|
||||
classes = {name: tally_dict(tally) for name, tally in sorted(ledger.items())}
|
||||
return {
|
||||
"schema": schema,
|
||||
"classes": classes,
|
||||
"content_sha256": sha256_of(classes),
|
||||
"note": note,
|
||||
"provenance": provenance,
|
||||
}
|
||||
|
||||
|
||||
def write_sealed_ledger(path: Path, artifact: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Write *artifact* to *path* in the committed formatting."""
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(
|
||||
json.dumps(artifact, indent=2, sort_keys=True) + "\n", encoding="utf-8"
|
||||
)
|
||||
return artifact
|
||||
|
||||
|
||||
def load_sealed_ledger(path: Path, *, missing_ok: bool = False) -> dict[str, ClassTally]:
|
||||
"""Load + verify a sealed ledger → per-class ``ClassTally``.
|
||||
|
||||
``missing_ok`` distinguishes two genuinely different situations. A ledger
|
||||
that a capability *ships with* is required: its absence means the
|
||||
deployment is broken, and refusing is right. A ledger for a capability
|
||||
whose practice volume is still being built is legitimately absent, and the
|
||||
honest reading of "no file" is "no class has earned anything yet" — an
|
||||
empty table, every answer disclosed. Neither case may be answered by
|
||||
guessing a license.
|
||||
"""
|
||||
if not path.exists():
|
||||
if missing_ok:
|
||||
return {}
|
||||
raise RatifiedLedgerError(f"ratified ledger not found: {path}")
|
||||
try:
|
||||
artifact = json.loads(path.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError) as exc:
|
||||
raise RatifiedLedgerError(f"cannot read ratified ledger: {exc}") from exc
|
||||
|
||||
classes = artifact.get("classes") if isinstance(artifact, dict) else None
|
||||
if not isinstance(classes, dict):
|
||||
raise RatifiedLedgerError("ratified ledger has no 'classes' table")
|
||||
if sha256_of(classes) != artifact.get("content_sha256"):
|
||||
raise RatifiedLedgerError(
|
||||
"ratified ledger content_sha256 mismatch — not the sealed-practice output"
|
||||
)
|
||||
|
||||
return {
|
||||
name: ClassTally(
|
||||
class_name=name,
|
||||
correct=int(counts.get("correct", 0)),
|
||||
wrong=int(counts.get("wrong", 0)),
|
||||
refused=int(counts.get("refused", 0)),
|
||||
t2_verified=int(counts.get("t2_verified", 0)),
|
||||
t2_agrees_gold=int(counts.get("t2_agrees_gold", 0)),
|
||||
)
|
||||
for name, counts in classes.items()
|
||||
}
|
||||
|
||||
|
||||
def serve_license(
|
||||
class_name: str,
|
||||
ledger: dict[str, ClassTally],
|
||||
*,
|
||||
ceilings: Ceilings | None = None,
|
||||
) -> LicenseDecision | None:
|
||||
"""The ``Action.SERVE`` verdict for *class_name*, or ``None`` when the
|
||||
class has no committed evidence (never a license — rule 4)."""
|
||||
tally = ledger.get(class_name)
|
||||
if tally is None:
|
||||
return None
|
||||
return license_for(tally, Action.SERVE, ceilings or Ceilings.default())
|
||||
|
||||
|
||||
__all__ = [
|
||||
"RatifiedLedgerError",
|
||||
"load_sealed_ledger",
|
||||
"seal_artifact",
|
||||
"serve_license",
|
||||
"tally_dict",
|
||||
"write_sealed_ledger",
|
||||
]
|
||||
80
docs/adr/ADR-0263-ratified-ledger-bridge.md
Normal file
80
docs/adr/ADR-0263-ratified-ledger-bridge.md
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
# ADR-0263 — The ratified-ledger bridge
|
||||
|
||||
- **Status:** Proposed
|
||||
- **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.
|
||||
|
|
@ -18,11 +18,11 @@ commit and SHA-verify on load.
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from core.learning_arena.engine import run_practice
|
||||
from core.ratified_ledger import seal_artifact, tally_dict, write_sealed_ledger
|
||||
from core.reliability_gate import Action, Ceilings, ClassTally, license_for
|
||||
from evals.deduction_serve.practice.gold import (
|
||||
ConstructionGoldTether,
|
||||
|
|
@ -30,7 +30,6 @@ from evals.deduction_serve.practice.gold import (
|
|||
all_gold_problems,
|
||||
assert_corpus_sound,
|
||||
)
|
||||
from formation.hashing import sha256_of
|
||||
|
||||
#: The committed sealed ledger lives next to its serving READER (chat/), mirroring
|
||||
#: the estimation ledger's topology (producer in evals/, artifact by the reader).
|
||||
|
|
@ -46,13 +45,7 @@ def build_ledger() -> dict[str, ClassTally]:
|
|||
|
||||
|
||||
def _tally_dict(tally: ClassTally) -> dict[str, Any]:
|
||||
return {
|
||||
"correct": tally.correct,
|
||||
"wrong": tally.wrong,
|
||||
"refused": tally.refused,
|
||||
"t2_verified": tally.t2_verified,
|
||||
"t2_agrees_gold": tally.t2_agrees_gold,
|
||||
}
|
||||
return tally_dict(tally)
|
||||
|
||||
|
||||
def run(ceilings: Ceilings | None = None) -> dict[str, Any]:
|
||||
|
|
@ -81,31 +74,30 @@ def run(ceilings: Ceilings | None = None) -> dict[str, Any]:
|
|||
|
||||
|
||||
def build_sealed_artifact() -> dict[str, Any]:
|
||||
"""The committed sealed-ledger dict (self-verifying ``content_sha256``)."""
|
||||
ledger = build_ledger()
|
||||
classes = {cls: _tally_dict(tally) for cls, tally in sorted(ledger.items())}
|
||||
return {
|
||||
"schema": "deduction_serve_ledger_v1",
|
||||
"classes": classes,
|
||||
"content_sha256": sha256_of(classes),
|
||||
"note": (
|
||||
"""The committed sealed-ledger dict (self-verifying ``content_sha256``).
|
||||
|
||||
Formatting and hashing come from the shared bridge (ADR-0263) — byte
|
||||
-identical to what this module wrote before the extraction, which is how
|
||||
the refactor is proven safe: re-sealing must not move the artifact.
|
||||
"""
|
||||
return seal_artifact(
|
||||
build_ledger(),
|
||||
schema="deduction_serve_ledger_v1",
|
||||
note=(
|
||||
"Sealed-practice committed ledger for deduction serving (ADR-0256). "
|
||||
"Engine reads, never writes. Ceilings stay at safe defaults "
|
||||
"(theta_SERVE=0.99). A band earns SERVE by demonstrated pipeline "
|
||||
"reliability (reader+projector+engine) at volume >= 657 committed."
|
||||
),
|
||||
"provenance": "evals.deduction_serve.practice.runner.seal_ledger",
|
||||
}
|
||||
provenance="evals.deduction_serve.practice.runner.seal_ledger",
|
||||
)
|
||||
|
||||
|
||||
def seal_ledger(path: Path = _SEALED_LEDGER_PATH) -> dict[str, Any]:
|
||||
"""Regenerate + write the committed sealed ledger. Verifies corpus soundness
|
||||
against the independent oracle first (a mis-stated gold can never seal)."""
|
||||
assert_corpus_sound()
|
||||
artifact = build_sealed_artifact()
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(json.dumps(artifact, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
||||
return artifact
|
||||
return write_sealed_ledger(path, build_sealed_artifact())
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
|
|
|
|||
|
|
@ -8,27 +8,28 @@ never writes it. The artifact is the sealed-practice output of
|
|||
load, so a hand-edited (un-ratified) ledger is rejected rather than silently trusted.
|
||||
|
||||
Determinism: the ledger is immutable ratified data, parsed once and cached; the gate
|
||||
(``license_for``) is pure. No engine self-authorization — ceilings stay at the safe
|
||||
defaults (raising one's own bar is structurally impossible, ADR-0175 invariant #4).
|
||||
is pure. No engine self-authorization — ceilings stay at the safe defaults (raising
|
||||
one's own bar is structurally impossible, ADR-0175 invariant #4).
|
||||
|
||||
The load/verify/gate mechanics live in ``core.ratified_ledger`` (ADR-0263), the
|
||||
bridge extracted from this module and its two successors; this is the estimation
|
||||
ADAPTER over it, preserving its own public API (including the predicate →
|
||||
converse-class naming, which is the one thing genuinely local to estimation).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
|
||||
from core.reliability_gate import Action, Ceilings, ClassTally, LicenseDecision, license_for
|
||||
from formation.hashing import sha256_of
|
||||
from core.ratified_ledger import RatifiedLedgerError, load_sealed_ledger
|
||||
from core.ratified_ledger import serve_license as _serve_license
|
||||
from core.reliability_gate import Ceilings, ClassTally, LicenseDecision
|
||||
from generate.determine.estimate import converse_class_name
|
||||
|
||||
_LEDGER_PATH = Path(__file__).resolve().parent / "data" / "estimation_ledger.json"
|
||||
|
||||
|
||||
class RatifiedLedgerError(ValueError):
|
||||
"""The committed estimation ledger is missing, malformed, or tampered with."""
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def load_ratified_ledger() -> dict[str, ClassTally]:
|
||||
"""Load + verify the ratified estimation ledger → per-class ``ClassTally``.
|
||||
|
|
@ -37,30 +38,7 @@ def load_ratified_ledger() -> dict[str, ClassTally]:
|
|||
recomputed ``content_sha256`` does not match the committed one (tamper-evidence:
|
||||
only the sealed-practice output is trusted, never a hand-edited ledger).
|
||||
"""
|
||||
try:
|
||||
artifact = json.loads(_LEDGER_PATH.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError) as exc: # pragma: no cover - defensive
|
||||
raise RatifiedLedgerError(f"cannot read ratified ledger: {exc}") from exc
|
||||
|
||||
classes = artifact.get("classes")
|
||||
if not isinstance(classes, dict):
|
||||
raise RatifiedLedgerError("ratified ledger has no 'classes' table")
|
||||
if sha256_of(classes) != artifact.get("content_sha256"):
|
||||
raise RatifiedLedgerError(
|
||||
"ratified ledger content_sha256 mismatch — not the sealed-practice output"
|
||||
)
|
||||
|
||||
ledger: dict[str, ClassTally] = {}
|
||||
for cls, counts in classes.items():
|
||||
ledger[cls] = ClassTally(
|
||||
class_name=cls,
|
||||
correct=int(counts.get("correct", 0)),
|
||||
wrong=int(counts.get("wrong", 0)),
|
||||
refused=int(counts.get("refused", 0)),
|
||||
t2_verified=int(counts.get("t2_verified", 0)),
|
||||
t2_agrees_gold=int(counts.get("t2_agrees_gold", 0)),
|
||||
)
|
||||
return ledger
|
||||
return load_sealed_ledger(_LEDGER_PATH)
|
||||
|
||||
|
||||
def serve_license(
|
||||
|
|
@ -76,11 +54,7 @@ def serve_license(
|
|||
deterministic ``license_for`` verdict under the safe default ceilings.
|
||||
"""
|
||||
ledger = ledger if ledger is not None else load_ratified_ledger()
|
||||
tally = ledger.get(converse_class_name(predicate))
|
||||
if tally is None:
|
||||
return None
|
||||
ceilings = ceilings if ceilings is not None else Ceilings.default()
|
||||
return license_for(tally, Action.SERVE, ceilings)
|
||||
return _serve_license(converse_class_name(predicate), ledger, ceilings=ceilings)
|
||||
|
||||
|
||||
__all__ = ["RatifiedLedgerError", "load_ratified_ledger", "serve_license"]
|
||||
|
|
|
|||
113
tests/test_ratified_ledger_bridge.py
Normal file
113
tests/test_ratified_ledger_bridge.py
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
"""The ratified-ledger bridge (ADR-0263) — seal → ratify → SHA-verify → gate.
|
||||
|
||||
These tests pin the four rules the three adapters depend on, and the property
|
||||
that made the extraction safe: re-sealing through the bridge reproduces the
|
||||
committed artifacts byte-for-byte.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
from core.ratified_ledger import (
|
||||
RatifiedLedgerError,
|
||||
load_sealed_ledger,
|
||||
seal_artifact,
|
||||
serve_license,
|
||||
write_sealed_ledger,
|
||||
)
|
||||
from core.reliability_gate import ClassTally
|
||||
|
||||
|
||||
def _tally(name: str, correct: int, wrong: int = 0) -> ClassTally:
|
||||
return ClassTally(class_name=name, correct=correct, wrong=wrong, refused=0)
|
||||
|
||||
|
||||
def test_seal_then_load_round_trips(tmp_path) -> None:
|
||||
ledger = {"alpha": _tally("alpha", 700), "beta": _tally("beta", 12)}
|
||||
path = tmp_path / "ledger.json"
|
||||
write_sealed_ledger(
|
||||
path, seal_artifact(ledger, schema="t_v1", note="n", provenance="p")
|
||||
)
|
||||
loaded = load_sealed_ledger(path)
|
||||
assert set(loaded) == {"alpha", "beta"}
|
||||
assert loaded["alpha"].correct == 700
|
||||
|
||||
|
||||
def test_a_hand_edited_ledger_is_rejected(tmp_path) -> None:
|
||||
"""Rule 2 — tamper-evidence is structural. Editing a tally without
|
||||
re-sealing does not produce a slightly-wrong ledger; it produces an
|
||||
unratified one, and loading REFUSES."""
|
||||
path = tmp_path / "ledger.json"
|
||||
write_sealed_ledger(
|
||||
path,
|
||||
seal_artifact({"alpha": _tally("alpha", 10)}, schema="t_v1", note="n", provenance="p"),
|
||||
)
|
||||
artifact = json.loads(path.read_text())
|
||||
artifact["classes"]["alpha"]["correct"] = 9_999
|
||||
path.write_text(json.dumps(artifact, indent=2, sort_keys=True) + "\n")
|
||||
with pytest.raises(RatifiedLedgerError):
|
||||
load_sealed_ledger(path)
|
||||
|
||||
|
||||
def test_missing_ledger_refuses_unless_declared_optional(tmp_path) -> None:
|
||||
"""A capability that SHIPS with a ledger is broken without it; one whose
|
||||
practice volume is still being built legitimately has none. Neither is
|
||||
answered by guessing a license."""
|
||||
path = tmp_path / "absent.json"
|
||||
with pytest.raises(RatifiedLedgerError):
|
||||
load_sealed_ledger(path)
|
||||
assert load_sealed_ledger(path, missing_ok=True) == {}
|
||||
|
||||
|
||||
def test_absent_class_is_never_licensed() -> None:
|
||||
"""Rule 4 — no evidence is not a license; the caller's ``None`` branch is
|
||||
what serves the disclosed surface."""
|
||||
assert serve_license("nobody", {"alpha": _tally("alpha", 700)}) is None
|
||||
|
||||
|
||||
def test_volume_floor_is_enforced_by_the_gate() -> None:
|
||||
"""A perfect but small record does not clear θ_SERVE=0.99 — the Wilson
|
||||
floor is what makes a license *earned* rather than merely clean."""
|
||||
small = serve_license("alpha", {"alpha": _tally("alpha", 12)})
|
||||
large = serve_license("alpha", {"alpha": _tally("alpha", 720)})
|
||||
assert small is not None and not small.licensed
|
||||
assert large is not None and large.licensed
|
||||
|
||||
|
||||
def test_a_single_wrong_costs_the_license() -> None:
|
||||
dirty = serve_license("alpha", {"alpha": _tally("alpha", 720, wrong=1)})
|
||||
assert dirty is not None and not dirty.licensed
|
||||
|
||||
|
||||
def test_committed_deduction_ledger_reseals_byte_identically() -> None:
|
||||
"""The extraction's safety property: the bridge writes what the bespoke
|
||||
sealers wrote, so adopting it moves no committed artifact and no lane pin."""
|
||||
from pathlib import Path
|
||||
|
||||
from evals.deduction_serve.practice.runner import build_sealed_artifact
|
||||
|
||||
committed = Path("chat/data/deduction_serve_ledger.json")
|
||||
expected = json.dumps(build_sealed_artifact(), indent=2, sort_keys=True) + "\n"
|
||||
assert committed.read_text(encoding="utf-8") == expected
|
||||
|
||||
|
||||
def test_every_adapter_reads_through_the_bridge() -> None:
|
||||
"""All three instances now share one loader — the property that makes a
|
||||
future change to the ratification rule land everywhere at once."""
|
||||
import chat.curriculum_serve_license as curriculum
|
||||
import chat.deduction_serve_license as deduction
|
||||
import generate.determine.estimation_license as estimation
|
||||
|
||||
for module in (deduction, estimation, curriculum):
|
||||
source = module.__file__ or ""
|
||||
assert source
|
||||
text = open(source, encoding="utf-8").read()
|
||||
assert "core.ratified_ledger" in text
|
||||
# The signal that an adapter still verifies for itself is that it
|
||||
# hashes for itself; docstrings may (and do) still explain the rule.
|
||||
assert "formation.hashing" not in text, (
|
||||
f"{module.__name__} still re-implements verification"
|
||||
)
|
||||
Loading…
Reference in a new issue