Rank 5 on the docket, and the first item this arc whose expected outcome is LOSING capability. conservative_floor is a one-sided Wilson lower bound, and Wilson assumes independent trials. CORE's pipeline is deterministic, so replaying an identical case is not a second trial — it is the same trial with a guaranteed outcome. The deduction sealer folded the raw practice corpus and counted every replay, so bands recorded 720/720 committed whether that was 720 decisions or 28 decisions seen 26 times each. conservative_floor(720,720)=0.990868 cleared theta_SERVE=0.99; conservative_floor(28,28)=0.808413 is not close. RESULT: 25 licensed bands -> 4. Twenty-one demoted. Survivors: en_conditional_chain, en_disjunctive, en_verb_fact, en_verb_universal — the only bands whose corpus holds enough independent cases. What the demotion did and did not do, stated exactly. NO ANSWER CHANGED. No answer became wrong. `wrong` stayed 0 across all 25 bands. The engine is exactly as correct as it was. What changed is the CLAIM attached to the answer: 21 bands now serve the same sound conclusion prefixed with "(reasoned, but I haven't yet earned a verified track record on arguments of this shape)". The reasoning did not get worse; the boast did. The producer is hardened, which is the half that stops this recurring. seal_ledger now refuses outright — assert_sealed_evidence_distinct compares the ledger about to be written against the corpus's distinct-case count and raises BEFORE anything is written. The curriculum sealer has carried this guarantee since ADR-0264 R9; the deduction sealer had no equivalent, which is exactly how the exposure arose. Catching it at seal time rather than in an audit matters: an audit finds a padded ledger after it is committed, trusted, and gating a live flag, and unwinding that then needs a ruling — it needed one. A HOLLOW GUARD OF MY OWN, caught by sabotage rather than trusted. The first version compared all_gold_problems() with distinct_gold_problems() — two functions that agree with each other by construction, and neither of which is what build_ledger folds. Reverting build_ledger to the raw corpus walked straight past it AND re-sealed the inflated artifact. Same defect class the ledger already suffered: a guard checking something adjacent to the thing it protects. It now takes the BUILT LEDGER as input, because the invariant is about the artifact. Re-running the identical attack: refused, wrote nothing. Pinned as test_sealer_refuses_a_ledger_that_counts_replays. A SECOND MISS, caught by the gate rather than by me. My blast-radius scan grepped for deduction_serve_license / deduction_serve_ledger / 720 and found nine files. It missed tests/test_construction_inventory.py, which asserts deduction SURFACE STRINGS and never mentions the ledger — so a pattern search over the wrong noun could not see it. The full gate found it. Recorded because "I scanned for the blast radius" is worth exactly as much as the scan's key, and mine was the wrong key. That test is the #138 fabrication defect pin, and it is updated WITHOUT being weakened. Both surfaces now carry the disclosure prefix, and the fabrication survives untouched — which is a sharper finding than before: a disclosure prefix looks like it might cover the hazard, and it does not. The user is still told they said "furthermore". Exact-match assertions kept on the full strings so neither the fabrication nor the licence state can drift unnoticed, plus an isolating assertion that the only difference between clean and leaked recitals is the fabricated premise. Four records corrected in the same PR, each of which would otherwise have gone on asserting capability that no longer exists: - CAPABILITY_LEDGERS' manifest note ("25 bands at 720/720 wrong=0") - the published CLAIMS.md claim — caught immediately by G-22's own pin, which is that fix earning its keep three days after landing - test_volume_honesty.py's module docstring, which said the exposure was "NOT silently repaired ... changing it is Shay's ratification, not a test's". R-13 IS that ratification, so the line went stale by being acted on - the exposure inventory's framing: it now pins the repair, not the gap test_volume_honesty's sealed-vs-producer assertion is strictly stronger than it was. It compared sealed committed counts to the producer's committed counts and both were the inflated 720 — the assertion held perfectly while the number it agreed on was the wrong number. Two records agreeing is not evidence that either is right. It now pins committed == distinct, plus a non-vacuity check. Nine tests encoded the unearned expectation and were updated to the honest one. Closes G-19 and H-1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wcw2pnMBwyvmNyQg4uPEt4
218 lines
9.8 KiB
Python
218 lines
9.8 KiB
Python
"""Practice runner for the deduction-serve arena (Phase 3, ADR-0256).
|
|
|
|
Folds the ADR-0199 ``run_practice`` engine over the synthetic shape-band corpus
|
|
(``gold.py``) and reads the per-band ``ClassTally`` through the reliability gate.
|
|
Two outputs:
|
|
|
|
- ``run()`` — the falsifiable discrimination report: which shape-bands earned
|
|
the SERVE license and at what committed volume/reliability.
|
|
- ``seal_ledger()`` — regenerates the committed, SHA-sealed ledger artifact the
|
|
serving reader (``chat/deduction_serve_license.py``) trusts. The engine READS
|
|
that artifact; only this sealed-practice runner WRITES it.
|
|
|
|
Determinism: the corpus is synthetic + indexed (no clock/RNG), ``run_practice``
|
|
is a pure fold, so the sealed ledger is byte-identical across runs — safe to
|
|
commit and SHA-verify on load.
|
|
"""
|
|
|
|
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, tally_dict, write_sealed_ledger
|
|
from core.reliability_gate import Action, Ceilings, ClassTally, license_for
|
|
from evals.deduction_serve.practice.gold import (
|
|
ConstructionGoldTether,
|
|
DeductionSolver,
|
|
all_gold_problems,
|
|
assert_practice_gold_sound,
|
|
)
|
|
|
|
#: The committed sealed ledger lives next to its serving READER (chat/), mirroring
|
|
#: the estimation ledger's topology (producer in evals/, artifact by the reader).
|
|
_SEALED_LEDGER_PATH = (
|
|
Path(__file__).resolve().parents[3] / "chat" / "data" / "deduction_serve_ledger.json"
|
|
)
|
|
|
|
|
|
def distinct_gold_problems() -> list[Any]:
|
|
"""The practice corpus with replayed cases collapsed — one per distinct decision.
|
|
|
|
**R-13, 2026-07-28.** Wilson lower-bound licensing (`conservative_floor`) is a
|
|
statement about *independent trials*. A replay of the same sealed case is one
|
|
trial observed again, not a new one, so folding the raw corpus inflated every
|
|
band: 720 "committed" decisions drawn from as few as **28** distinct cases, and
|
|
`conservative_floor(720, 720) = 0.990868` cleared θ_SERVE=0.99 on evidence that
|
|
`conservative_floor(28, 28) = 0.808413` does not come close to supporting.
|
|
|
|
The key is the case **text**, which is the definition already agreed in-repo by
|
|
``tests/test_volume_honesty.py`` and the audit behind it: two cases with identical
|
|
text are indisputably the same decision. It deliberately *under*-reports — a
|
|
tighter key (the normalized atom tuple) would collapse spelling variants and find
|
|
more inflation — because under-reporting makes the measured gap a **floor** on the
|
|
real gap rather than a guess at it.
|
|
|
|
The raw corpus is left alone: it is the practice *material*, and running a case
|
|
twice is a legitimate thing for practice to do. What may not happen is a replay
|
|
being *counted* as new evidence when a license is computed from the count.
|
|
"""
|
|
seen: set[tuple[str, str]] = set()
|
|
out: list[Any] = []
|
|
for problem in all_gold_problems():
|
|
key = (problem.class_name, problem.payload["text"])
|
|
if key in seen:
|
|
continue
|
|
seen.add(key)
|
|
out.append(problem)
|
|
return out
|
|
|
|
|
|
def assert_sealed_evidence_distinct(ledger: dict[str, ClassTally]) -> None:
|
|
"""The R9 invariant on the LEDGER ABOUT TO BE SEALED: a padded producer cannot seal.
|
|
|
|
Mirrors ``evals.curriculum_serve.practice.runner.assert_practice_atoms_distinct``,
|
|
which has carried this guarantee on the sibling ledger since ADR-0264 R9. The
|
|
deduction sealer had no equivalent, which is precisely how 21 of 25 bands came to
|
|
hold SERVE licences their evidence never supported.
|
|
|
|
Catching it *here* rather than in the audit matters: an audit finds a padded ledger
|
|
after it is committed and trusted, and unwinding that exposure then needs a ruling
|
|
(it needed one — R-13). This raises before the artifact exists.
|
|
|
|
**It takes the built ledger, and that is the whole point.** The first version of
|
|
this guard compared ``all_gold_problems()`` to ``distinct_gold_problems()`` — two
|
|
functions that agree with each other by construction, and neither of which is what
|
|
``build_ledger`` folds. Reverting ``build_ledger`` to the raw corpus sailed straight
|
|
past it and re-sealed the inflated artifact. A guard that checks something
|
|
*adjacent* to the thing it protects is the exact failure this ledger already
|
|
suffered once; it was caught here only because the guard was sabotage-tested rather
|
|
than trusted. The invariant is about the **artifact**, so the artifact is the input.
|
|
"""
|
|
distinct: dict[str, set[str]] = {}
|
|
for problem in all_gold_problems():
|
|
distinct.setdefault(problem.class_name, set()).add(problem.payload["text"])
|
|
|
|
padded = {
|
|
band: (tally.committed, len(distinct.get(band, ())))
|
|
for band, tally in ledger.items()
|
|
if tally.committed != len(distinct.get(band, ()))
|
|
}
|
|
if padded:
|
|
detail = ", ".join(
|
|
f"{band}: ledger committed={got} but only {want} distinct cases exist"
|
|
for band, (got, want) in sorted(padded.items())
|
|
)
|
|
raise ValueError(
|
|
"refusing to seal — the ledger counts replays as independent trials, which "
|
|
"is the precondition conservative_floor assumes and these bands violate: "
|
|
f"{detail}"
|
|
)
|
|
|
|
|
|
def build_ledger() -> dict[str, ClassTally]:
|
|
"""Run sealed practice over the corpus → per-band ledger, on DISTINCT evidence.
|
|
|
|
``committed == distinct`` by construction, which is the same guarantee the
|
|
curriculum ledger states in its own seal note. Before R-13 this folded the raw
|
|
corpus and the difference was invisible in the artifact: a band recorded 720/720
|
|
whether that was 720 decisions or 28 decisions replayed.
|
|
"""
|
|
report = run_practice(distinct_gold_problems(), DeductionSolver(), ConstructionGoldTether())
|
|
return dict(report.ledger)
|
|
|
|
|
|
def _tally_dict(tally: ClassTally) -> dict[str, Any]:
|
|
return tally_dict(tally)
|
|
|
|
|
|
def run(ceilings: Ceilings | None = None) -> dict[str, Any]:
|
|
"""Build the ledger and report the SERVE license verdict per shape-band."""
|
|
ceilings = ceilings if ceilings is not None else Ceilings.default()
|
|
ledger = build_ledger()
|
|
classes: dict[str, Any] = {}
|
|
for cls, tally in sorted(ledger.items()):
|
|
serve = license_for(tally, Action.SERVE, ceilings)
|
|
classes[cls] = {
|
|
"correct": tally.correct,
|
|
"wrong": tally.wrong,
|
|
"refused": tally.refused,
|
|
"reliability": tally.reliability,
|
|
"serve_licensed": serve.licensed,
|
|
"serve_ratio": serve.ratio,
|
|
}
|
|
all_serve = bool(classes) and all(c["serve_licensed"] for c in classes.values())
|
|
wrong_is_zero = all(c["wrong"] == 0 for c in classes.values())
|
|
return {
|
|
"lane": "deduction-serve-practice",
|
|
"classes": classes,
|
|
"all_bands_serve_licensed": all_serve,
|
|
"wrong_is_zero": wrong_is_zero,
|
|
}
|
|
|
|
|
|
def build_sealed_artifact(ledger: dict[str, ClassTally] | None = None) -> dict[str, Any]:
|
|
"""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() if ledger is None else 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. "
|
|
"RE-COUNTED 2026-07-28 under R-13: one committed case per DISTINCT "
|
|
"decision (case text), so committed == distinct evidence by construction. "
|
|
"The prior seal folded the raw corpus and counted replays as independent "
|
|
"trials, which the Wilson floor assumes they are not; 21 of 25 bands held "
|
|
"SERVE licences their evidence never supported and are demoted here."
|
|
),
|
|
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.
|
|
|
|
Two preconditions, both checked before anything is written:
|
|
|
|
* ``assert_practice_gold_sound()`` — a mis-stated gold can never seal.
|
|
* ``assert_sealed_evidence_distinct(ledger)`` — a producer counting replays as
|
|
independent trials can never seal (R-13 / ADR-0264 R9). It is handed the built
|
|
ledger rather than the corpus, because the corpus is not what gets sealed.
|
|
"""
|
|
assert_practice_gold_sound()
|
|
ledger = build_ledger()
|
|
assert_sealed_evidence_distinct(ledger)
|
|
return write_sealed_ledger(path, build_sealed_artifact(ledger))
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument(
|
|
"--seal", action="store_true",
|
|
help="regenerate + write the committed sealed ledger (chat/data/deduction_serve_ledger.json)",
|
|
)
|
|
args = parser.parse_args(argv)
|
|
if args.seal:
|
|
artifact = seal_ledger()
|
|
print(f"sealed {len(artifact['classes'])} bands -> {_SEALED_LEDGER_PATH}")
|
|
return 0
|
|
report = run()
|
|
for cls, c in report["classes"].items():
|
|
print(f" {cls:20s} correct={c['correct']:4d} wrong={c['wrong']} "
|
|
f"reliability={c['reliability']:.5f} SERVE={c['serve_licensed']}")
|
|
print(f"all_bands_serve_licensed={report['all_bands_serve_licensed']} "
|
|
f"wrong_is_zero={report['wrong_is_zero']}")
|
|
return 0 if (report["all_bands_serve_licensed"] and report["wrong_is_zero"]) else 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|