Builds the producer `chat/curriculum_serve_license.py` already named as the only writer of the ledger it reads — `evals.curriculum_serve.practice.runner .seal_ledger` — which did not exist. Until now, authoring curriculum could not earn a license at any volume. A band's case space is ENUMERATED, not authored: every exam question that routes to that subject (resolve_domain) whose relation is in that family, one committed case per distinct query atom. So `committed == distinct` is not a property the generator maintains, it is the same fact twice — the structural form of ADR-0264 R9, and why this producer cannot develop the deduction producer's exposure (CASES_PER_BAND=720 filled from spaces as small as 28). CASES_PER_BAND=660 follows `evals/determination_estimation` per DIVISION-OF-WORK §4: a ceiling on committed volume, not a quota. 11 bands, wrong=0, inflation 1.0 everywhere. THE LEDGER IS DELIBERATELY NOT COMMITTED. The plan's Phase-1 exit criterion asked for "a real (still-unearned) ledger"; that state is unreachable. Reliability is commitment precision and a correct UNKNOWN is a commitment, so conservative_floor(660,660)=0.990046 clears theta_SERVE on non-commitments alone. Sealing today licenses FOUR bands on evidence that is 99.0%-99.98% correct non-commitment — which ADR-0262 §5.1 rules unacceptable and which ClassTally structurally cannot see (no verdict axis). Writing the artifact is therefore a ratification and belongs to Phase D's explicit human-run verb. The open outcome-mix ruling is now blocking, not academic. Two corrections to my own earlier records, both measured: - ADR-0264 §4.2 sized bands from per-TERM exclusivity; the router's predicate is per-PAIR, which is looser. Every ceiling was understated, and systems_software_causal is 720 not 630 — flipping it from "cannot reach 657" to "can". 4 of 11 bands can reach the floor, not 3. Now pinned as measured values in BAND_ATOM_SPACE. - `then` and `therefore` are taught philosophy_theology lemmas AND control words of the argument reader (`therefore` is the conclusion marker). 164 routable atoms refuse compiled_premises_unreadable — coverage misses, never wrongs — in the band Phase F retargets to. The vocabulary boundary is not screened against the reader's grammar; a pack teaching and/or/not/if would open a much larger hole the same way. Recorded, not fixed (§6.5), pinned as a two-lemma bound. Registers curriculum_serve in AUDIT_SOURCES, retiring the declared `None` that was Phase C's forcing function, and adds the producer to `deductive`. [Verification]: in-worktree on canonical CPython 3.12.13 with `uv sync --locked`: smoke 571 (569 + 1 test + 1 parametrization), deductive 310 (291 + 19). Mutation-checked: injecting the deduction padding pattern into band_cases turns 5 tests red including the R9 audit and the seal guard. curriculum_serve lane unchanged at n=32 correct=32 wrong=0 anti_recall=5; all 11 lane SHA pins match; no ledger written, so no serving change.
187 lines
7.9 KiB
Python
187 lines
7.9 KiB
Python
"""Sealed-practice runner for curriculum serving (ADR-0262/0264, Phase C).
|
||
|
||
Folds the ADR-0199 ``run_practice`` engine over the enumerated atom corpus
|
||
(``generator.py``) and reads each band's ``ClassTally`` through the reliability
|
||
gate. Two outputs, exactly mirroring ``evals/deduction_serve/practice/runner.py``:
|
||
|
||
- ``run()`` — the falsifiable report: which *(subject × relation family)* bands
|
||
earned SERVE, at what committed volume, and with what verdict MIX.
|
||
- ``seal_ledger()`` — writes the SHA-sealed artifact the serving reader
|
||
(``chat/curriculum_serve_license.py``) trusts. That module's docstring already
|
||
names this function as the only writer; this is the function it named.
|
||
|
||
**The artifact is NOT committed by this arc, and that is a finding, not an
|
||
omission.** Every band with a routable space ≥657 reaches θ_SERVE on the UNKNOWN
|
||
class alone: ``physics · causal`` commits 7 taught edges and 713 correct
|
||
non-commitments, and ``conservative_floor(660, 660) = 0.990046 ≥ 0.99``. So
|
||
sealing this ledger today would flip four bands from disclosed to authoritative on
|
||
a mix that is ~99% non-entailed — which ADR-0262 §5.1 explicitly rules
|
||
unacceptable, and which the still-open outcome-mix ruling (ADR-0264 R9's recorded
|
||
non-decision; ``docs/research/distinct-evidence-audit-2026-07-25.md``) exists to
|
||
settle. The plan of record's Phase-1 exit criterion asked for a ledger that is
|
||
"real (still-unearned)"; building the producer shows that state is unreachable.
|
||
Committing the artifact is therefore a ratification, and it is left to the
|
||
explicit ``core proposal-queue reseal`` verb a human runs (Phase D).
|
||
|
||
Determinism: the corpus is enumerated + sorted (no clock, no RNG) and
|
||
``run_practice`` is a pure fold, so the sealed ledger is byte-identical across
|
||
runs — safe to commit and SHA-verify on load, whenever it is committed.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
from pathlib import Path
|
||
from typing import Any
|
||
|
||
from core.learning_arena.engine import run_practice
|
||
from core.ratified_ledger import seal_artifact, write_sealed_ledger
|
||
from core.reliability_gate import Action, Ceilings, ClassTally, license_for
|
||
from evals.curriculum_serve.practice.generator import (
|
||
CASES_PER_BAND,
|
||
CurriculumOracleTether,
|
||
CurriculumSolver,
|
||
all_gold_problems,
|
||
assert_practice_atoms_distinct,
|
||
)
|
||
|
||
#: The committed sealed ledger lives next to its serving READER (chat/), the
|
||
#: topology the estimation and deduction ledgers already use.
|
||
_SEALED_LEDGER_PATH = (
|
||
Path(__file__).resolve().parents[3] / "chat" / "data" / "curriculum_serve_ledger.json"
|
||
)
|
||
|
||
|
||
def build_ledger(cap: int = CASES_PER_BAND) -> dict[str, ClassTally]:
|
||
"""Run sealed practice over the enumerated corpus → per-band ledger."""
|
||
report = run_practice(
|
||
all_gold_problems(cap), CurriculumSolver(), CurriculumOracleTether()
|
||
)
|
||
return dict(report.ledger)
|
||
|
||
|
||
def build_mix(cap: int = CASES_PER_BAND) -> dict[str, dict[str, int]]:
|
||
"""Per-band GOLD verdict mix — the figure the reliability gate cannot see.
|
||
|
||
``ClassTally`` carries ``correct``/``wrong``/``refused`` and no verdict axis, so
|
||
a correct UNKNOWN is indistinguishable from a correct ENTAILED once tallied.
|
||
That is precisely why a band can clear θ_SERVE on non-commitments alone, and
|
||
why the mix has to be reported alongside the license rather than inferred from
|
||
it.
|
||
"""
|
||
tether = CurriculumOracleTether()
|
||
mix: dict[str, dict[str, int]] = {}
|
||
for problem in all_gold_problems(cap):
|
||
band = mix.setdefault(problem.class_name, {})
|
||
gold = tether.gold_answer(problem)
|
||
band[gold] = band.get(gold, 0) + 1
|
||
return mix
|
||
|
||
|
||
def run(ceilings: Ceilings | None = None, cap: int = CASES_PER_BAND) -> dict[str, Any]:
|
||
"""Build the ledger and report the SERVE verdict + mix per band."""
|
||
ceilings = ceilings if ceilings is not None else Ceilings.default()
|
||
ledger = build_ledger(cap)
|
||
mix = build_mix(cap)
|
||
classes: dict[str, Any] = {}
|
||
for band, tally in sorted(ledger.items()):
|
||
serve = license_for(tally, Action.SERVE, ceilings)
|
||
band_mix = mix.get(band, {})
|
||
entailed = band_mix.get("entailed", 0)
|
||
classes[band] = {
|
||
"correct": tally.correct,
|
||
"wrong": tally.wrong,
|
||
"refused": tally.refused,
|
||
"committed": tally.committed,
|
||
"reliability": tally.reliability,
|
||
"coverage": tally.coverage,
|
||
"serve_licensed": serve.licensed,
|
||
"serve_ratio": serve.ratio,
|
||
"gold_mix": dict(sorted(band_mix.items())),
|
||
"entailed_share": (
|
||
round(entailed / tally.committed, 6) if tally.committed else 0.0
|
||
),
|
||
}
|
||
return {
|
||
"lane": "curriculum-serve-practice",
|
||
"classes": classes,
|
||
"any_band_serve_licensed": any(c["serve_licensed"] for c in classes.values()),
|
||
"wrong_is_zero": all(c["wrong"] == 0 for c in classes.values()),
|
||
}
|
||
|
||
|
||
def build_sealed_artifact(cap: int = CASES_PER_BAND) -> dict[str, Any]:
|
||
"""The sealed-ledger dict (self-verifying ``content_sha256``)."""
|
||
return seal_artifact(
|
||
build_ledger(cap),
|
||
schema="curriculum_serve_ledger_v1",
|
||
note=(
|
||
"Sealed-practice committed ledger for curriculum-grounded serving "
|
||
"(ADR-0262, sized per ADR-0264 R9). Engine reads, never writes. "
|
||
"Ceilings stay at safe defaults (theta_SERVE=0.99). One committed case "
|
||
"per DISTINCT routable query atom, so committed == distinct evidence by "
|
||
"construction. NOTE: reliability here is dominated by correct "
|
||
"non-commitments; read `gold_mix` from the runner report before "
|
||
"treating a licensed band as demonstrated curriculum competence."
|
||
),
|
||
provenance="evals.curriculum_serve.practice.runner.seal_ledger",
|
||
)
|
||
|
||
|
||
def seal_ledger(
|
||
path: Path = _SEALED_LEDGER_PATH, cap: int = CASES_PER_BAND
|
||
) -> dict[str, Any]:
|
||
"""Regenerate + write the committed sealed ledger.
|
||
|
||
Verifies the R9 distinct-evidence invariant first: a producer that padded
|
||
could never seal, rather than sealing and being caught by the audit later.
|
||
"""
|
||
assert_practice_atoms_distinct(cap)
|
||
return write_sealed_ledger(path, build_sealed_artifact(cap))
|
||
|
||
|
||
def _full_sweep() -> dict[str, Any]:
|
||
"""Every routable atom in every band — the exhaustive agreement measurement.
|
||
|
||
Not the sealed corpus (that is capped at :data:`CASES_PER_BAND`); this is the
|
||
evidence that the cap is a sampling choice and not a hiding place. Runs the
|
||
whole ~70k-atom space, so it is a deliberate CLI invocation and not a test.
|
||
"""
|
||
return run(cap=10**9)
|
||
|
||
|
||
def main(argv: list[str] | None = None) -> int:
|
||
parser = argparse.ArgumentParser(description=__doc__)
|
||
parser.add_argument(
|
||
"--seal", action="store_true",
|
||
help="regenerate + write chat/data/curriculum_serve_ledger.json "
|
||
"(a RATIFICATION — see the module docstring before running it)",
|
||
)
|
||
parser.add_argument(
|
||
"--full", action="store_true",
|
||
help="report over EVERY routable atom instead of the capped corpus (slow)",
|
||
)
|
||
args = parser.parse_args(argv)
|
||
|
||
if args.seal:
|
||
artifact = seal_ledger()
|
||
print(f"sealed {len(artifact['classes'])} bands -> {_SEALED_LEDGER_PATH}")
|
||
return 0
|
||
|
||
report = _full_sweep() if args.full else run()
|
||
for band, c in report["classes"].items():
|
||
print(
|
||
f" {band:44s} committed={c['committed']:6d} wrong={c['wrong']} "
|
||
f"refused={c['refused']:4d} reliability={c['reliability']:.5f} "
|
||
f"SERVE={str(c['serve_licensed']):5s} entailed={c['gold_mix'].get('entailed', 0):3d} "
|
||
f"({c['entailed_share']:.4%})"
|
||
)
|
||
print(
|
||
f"any_band_serve_licensed={report['any_band_serve_licensed']} "
|
||
f"wrong_is_zero={report['wrong_is_zero']}"
|
||
)
|
||
return 0 if report["wrong_is_zero"] else 1
|
||
|
||
|
||
if __name__ == "__main__":
|
||
raise SystemExit(main())
|