Deduction serving stops being a bare flag and becomes an EARNED capability governed by the ADR-0175 reliability gate over the ADR-0199 learning arena -- the arena's second concrete instance (GSM8K math is the first) and the reliability substrate's first non-estimation serving consumer, a concrete dent in that zone's standing 'designed, not wired' critique. Non-circular thesis: the ROBDD engine is sound+complete (never wrong on the problem it's handed), so the license does NOT certify it. It certifies the full pipeline (reader -> projector -> engine) per argument shape -- the FALLIBLE part is the template reader, which can misparse an argument and hand the engine the wrong problem. The arena catches that as a 'wrong' by comparing the pipeline's committed outcome to by-construction gold. New: generate/proof_chain/shape.py (4 exhaustive structural shape-bands, the capability axis, shared by arena + serving), evals/deduction_serve/practice/ (the deduction arena instance: deterministic synthetic corpus, by-construction gold independent of the reader per ADR-0199 L-2, cross-checked against the INDEPENDENT truth-table oracle before sealing), chat/data/deduction_serve_ ledger.json (committed SHA-sealed ledger, 4 bands x 720 correct/0 wrong), chat/deduction_serve_license.py (tamper-evident serving reader, mirrors estimation_license.py), tests/test_deduction_serve_license.py (13 tests), docs/adr/ADR-0256 (+ resolves the ADR-0206 numbering collision in doc form). Changed: chat/deduction_surface.py (composer consults the license: earned band -> authoritative Phase-1 surface; unearned/stripped ledger -> same sound answer served DISCLOSED/hedged -- authority now rests on committed evidence, not a boolean), core/config.py (flag docstring: enables an EARNED path), core/cli_test.py (deductive suite runs the license test). All four structural bands earn SERVE at reliability 0.99087 (>= theta_SERVE 0.99) with wrong=0, so Phase-1 behavior is preserved byte-for-byte; the gate's teeth are proven by injecting an empty ledger (the same sound answer degrades to a disclosed hedge). Did NOT reuse the ADR-0206 govern_response bridge (its STRICT/APPROXIMATE 'widen-past-strict' semantics are the opposite shape from deduction's always-sound answer); did NOT rewrite report.json's stale adr field (SHA-pinned bytes, documented in ADR-0256 s2a instead). [Verification]: smoke 180 passed; cognition 122 passed/1 skipped; core test --suite deductive 38 passed; architectural_invariants 75 passed; practice runner 4 bands all SERVE wrong=0.
132 lines
5.1 KiB
Python
132 lines
5.1 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
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from core.learning_arena.engine import run_practice
|
|
from core.reliability_gate import Action, Ceilings, ClassTally, license_for
|
|
from evals.deduction_serve.practice.gold import (
|
|
ConstructionGoldTether,
|
|
DeductionSolver,
|
|
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).
|
|
_SEALED_LEDGER_PATH = (
|
|
Path(__file__).resolve().parents[3] / "chat" / "data" / "deduction_serve_ledger.json"
|
|
)
|
|
|
|
|
|
def build_ledger() -> dict[str, ClassTally]:
|
|
"""Run sealed practice over the synthetic corpus → per-band ledger."""
|
|
report = run_practice(all_gold_problems(), DeductionSolver(), ConstructionGoldTether())
|
|
return dict(report.ledger)
|
|
|
|
|
|
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,
|
|
}
|
|
|
|
|
|
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() -> 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": (
|
|
"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",
|
|
}
|
|
|
|
|
|
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
|
|
|
|
|
|
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())
|