`RatificationReceipt.pending_stages` has named `ledger_reseal` since the ratification ceremony was written and nothing performed it, so a sealed ledger went stale the moment curriculum changed. This is that stage, plus the CLI verb a human runs, and it is deliberately NOT reachable from `ratify_chain` (doctrine forbids ratification automation, and a per-row reseal would churn the ledger's content_sha256). The load-bearing property is not "the ledger can be rewritten" — it is that rewriting it cannot grant a serving license by accident. Reliability is commitment precision, so a curriculum band clears theta_SERVE on correct NON-COMMITMENTS alone (Phase C, PR #121). A plain regenerate verb would flip four bands from disclosed to authoritative as a side effect of housekeeping, on evidence that is ~99% non-entailed, which ADR-0262 §5.1 rules unacceptable. So: - `plan_reseal` computes the license DELTA before writing anything, reading the current ledger through `load_capability_ledger` — the production entry point — so a tampered ledger refuses here instead of being overwritten and losing the evidence, and the stage inherits the capability's DECLARED absence policy rather than choosing its own (bridge rule 5). - `apply_reseal` REFUSES by default when any band would become newly licensed, naming the bands and their `entailed` counts. `--allow-new- licenses` is the ratification. Revocations are never gated: losing a license is the gate becoming more conservative and needs no authorization. - `deduction_serve` is deliberately absent from `_RESEALABLE`. Its ledger is SHA-sealed, ratified, and gating a live flag, and 21 of its 25 bands do not clear theta_SERVE on distinct evidence. A verb that regenerated it would erase a measured exposure as housekeeping. - `ratify`'s output now names the command that performs its pending stage. Naming a stage without naming its command is how this one stayed unperformed. Exit codes: 0 dry-run/applied, 1 refused, 2 unknown capability. [Verification]: in-worktree on canonical CPython 3.12.13 with `uv sync --locked`: smoke 571 (unchanged), deductive 327 (310 + 17). `chat/data/curriculum_serve_ledger.json` still absent after the full suite — no test writes it. Caught and fixed one real gate failure: `test_no_production_adapter_passes_missing_ok` correctly rejected an explicit `missing_ok=True`; the fix routes through the manifest instead.
195 lines
7.8 KiB
Python
195 lines
7.8 KiB
Python
"""teaching/ledger_reseal.py — the ``ledger_reseal`` stage of the ceremony.
|
|
|
|
``RatificationReceipt.pending_stages`` has named ``ledger_reseal`` since the
|
|
ratification ceremony was written, and nothing performed it. Ratifying a chain
|
|
grows the corpus; it does not re-run practice, so the sealed ledger the serving
|
|
gate reads goes stale the moment curriculum changes. This module is that stage,
|
|
and it is deliberately **not** reachable from ``ratify_chain``.
|
|
|
|
Why it is a separate, human-run act
|
|
-----------------------------------
|
|
``AGENTS.md`` forbids ratification automation, and an auto-reseal on every
|
|
``ratify`` would churn the ledger's ``content_sha256`` once per row. But the
|
|
stronger reason is what Phase C measured: **resealing can grant licenses.**
|
|
Reliability is commitment precision and a correct UNKNOWN is a commitment, so
|
|
any band with ≥657 routable atoms clears θ_SERVE on non-commitments alone. A
|
|
plain "regenerate the artifact" verb would therefore flip four curriculum bands
|
|
from disclosed to authoritative as a side effect of housekeeping, on evidence
|
|
that is ~99% non-entailed — which ADR-0262 §5.1 rules unacceptable
|
|
(``docs/research/curriculum-practice-producer-2026-07-26.md`` §1).
|
|
|
|
So :func:`plan_reseal` computes the license DELTA before writing anything, and
|
|
the caller must opt in explicitly to any band that would become newly licensed.
|
|
The default is refusal. Regenerating an artifact is housekeeping; changing what
|
|
the engine will assert authoritatively is a ratification, and the two must not
|
|
share a keystroke.
|
|
|
|
Scope: this module writes a ledger and nothing else. It has no opinion on
|
|
whether curriculum content is true, cannot ratify a chain, and cannot flip a
|
|
serving flag.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
from typing import Any, Callable
|
|
|
|
from core.ratified_ledger import (
|
|
CAPABILITY_LEDGERS,
|
|
load_capability_ledger,
|
|
write_sealed_ledger,
|
|
)
|
|
from core.reliability_gate import Action, Ceilings, ClassTally, license_for
|
|
|
|
|
|
class ReadyToReseal(ValueError):
|
|
"""A reseal was requested that cannot proceed as asked."""
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class ResealPlan:
|
|
"""What a reseal would do, computed before it does it."""
|
|
|
|
capability: str
|
|
path: Path
|
|
#: Bands licensed by the ledger currently on disk (empty when absent).
|
|
licensed_before: tuple[str, ...]
|
|
#: Bands the freshly-built artifact would license.
|
|
licensed_after: tuple[str, ...]
|
|
#: Per-band committed volume in the new artifact.
|
|
committed: dict[str, int]
|
|
#: Per-band gold verdict mix — the axis ``ClassTally`` cannot carry.
|
|
mix: dict[str, dict[str, int]]
|
|
artifact: dict[str, Any]
|
|
|
|
@property
|
|
def newly_licensed(self) -> tuple[str, ...]:
|
|
"""Bands this reseal would grant SERVE that do not hold it today.
|
|
|
|
The figure that decides whether the operation is housekeeping or a
|
|
ratification.
|
|
"""
|
|
before = set(self.licensed_before)
|
|
return tuple(b for b in self.licensed_after if b not in before)
|
|
|
|
@property
|
|
def revoked(self) -> tuple[str, ...]:
|
|
"""Bands that would LOSE a license — always allowed, never gated.
|
|
|
|
Losing a license is the gate becoming more conservative, which needs no
|
|
authorization. Only granting one does.
|
|
"""
|
|
after = set(self.licensed_after)
|
|
return tuple(b for b in self.licensed_before if b not in after)
|
|
|
|
@property
|
|
def is_housekeeping(self) -> bool:
|
|
return not self.newly_licensed
|
|
|
|
|
|
#: capability -> (build artifact, build ledger, build mix).
|
|
#:
|
|
#: ``deduction_serve`` is deliberately ABSENT. Its ledger is SHA-sealed,
|
|
#: ratified, and gating a live flag, and 21 of its 25 bands do not clear
|
|
#: θ_SERVE on distinct evidence (``tests/test_volume_honesty.py``). Re-sealing it
|
|
#: is Shay's ratification, not a CLI verb's, and a reseal that silently
|
|
#: regenerated it would erase the exposure this arc measured. ``estimation``
|
|
#: ships sealed with its gate and has no reason to move.
|
|
_RESEALABLE: dict[str, tuple[Callable[[], dict[str, Any]], Callable[[], dict[str, ClassTally]], Callable[[], dict[str, dict[str, int]]]]] = {}
|
|
|
|
|
|
def _register_curriculum() -> None:
|
|
from evals.curriculum_serve.practice.runner import (
|
|
build_ledger,
|
|
build_mix,
|
|
build_sealed_artifact,
|
|
)
|
|
|
|
_RESEALABLE["curriculum_serve"] = (build_sealed_artifact, build_ledger, build_mix)
|
|
|
|
|
|
def resealable_capabilities() -> tuple[str, ...]:
|
|
"""Capabilities this stage will rebuild, in a stable order."""
|
|
if not _RESEALABLE:
|
|
_register_curriculum()
|
|
return tuple(sorted(_RESEALABLE))
|
|
|
|
|
|
def _licensed_bands(ledger: dict[str, ClassTally], ceilings: Ceilings) -> tuple[str, ...]:
|
|
return tuple(
|
|
sorted(
|
|
band
|
|
for band, tally in ledger.items()
|
|
if license_for(tally, Action.SERVE, ceilings).licensed
|
|
)
|
|
)
|
|
|
|
|
|
def plan_reseal(capability: str, *, ceilings: Ceilings | None = None) -> ResealPlan:
|
|
"""Compute the reseal and its license delta — writing nothing.
|
|
|
|
The read of the CURRENT ledger goes through :func:`load_capability_ledger`,
|
|
the same production entry point the serving adapters use, so two properties
|
|
come for free. A hand-edited ledger refuses here rather than being silently
|
|
overwritten (losing the evidence that it was tampered with), and this stage
|
|
inherits the capability's DECLARED absence policy instead of choosing its own
|
|
— bridge rule 5: ``missing_ok`` is a fact about the capability, not about the
|
|
call, and no production path may pass it.
|
|
"""
|
|
if capability not in resealable_capabilities():
|
|
raise ReadyToReseal(
|
|
f"{capability!r} is not resealable — known: {list(resealable_capabilities())}. "
|
|
"Ledgers absent from that list are sealed by deliberate ratification only."
|
|
)
|
|
spec = CAPABILITY_LEDGERS[capability]
|
|
ceilings = ceilings if ceilings is not None else Ceilings.default()
|
|
build_artifact, build_ledger, build_mix = _RESEALABLE[capability]
|
|
|
|
current = load_capability_ledger(capability)
|
|
fresh_ledger = build_ledger()
|
|
artifact = build_artifact()
|
|
|
|
return ResealPlan(
|
|
capability=capability,
|
|
path=spec.path,
|
|
licensed_before=_licensed_bands(current, ceilings),
|
|
licensed_after=_licensed_bands(fresh_ledger, ceilings),
|
|
committed={band: t.committed for band, t in sorted(fresh_ledger.items())},
|
|
mix=build_mix(),
|
|
artifact=artifact,
|
|
)
|
|
|
|
|
|
def apply_reseal(plan: ResealPlan, *, allow_new_licenses: bool = False) -> Path:
|
|
"""Write the planned artifact, refusing to grant a license by accident.
|
|
|
|
``allow_new_licenses`` is the ratification. Without it, a plan that would
|
|
newly license any band refuses and names them — the operator then either
|
|
authorizes the grant or fixes the producer, but never discovers afterwards
|
|
that a serving surface became authoritative.
|
|
"""
|
|
if plan.newly_licensed and not allow_new_licenses:
|
|
detail = ", ".join(
|
|
f"{band} (committed={plan.committed.get(band, 0)}, "
|
|
f"entailed={plan.mix.get(band, {}).get('entailed', 0)})"
|
|
for band in plan.newly_licensed
|
|
)
|
|
raise ReadyToReseal(
|
|
f"{plan.capability}: this reseal would newly license {len(plan.newly_licensed)} "
|
|
f"band(s) — {detail}. That is a ratification, not a reseal: those bands "
|
|
"would begin serving authoritatively. Re-run with --allow-new-licenses "
|
|
"only if the grant is intended, and read the `entailed` counts first — a "
|
|
"band can clear theta_SERVE on correct NON-COMMITMENTS alone (ADR-0262 §5.1)."
|
|
)
|
|
write_sealed_ledger(plan.path, plan.artifact)
|
|
return plan.path
|
|
|
|
|
|
__all__ = [
|
|
"ReadyToReseal",
|
|
"ResealPlan",
|
|
"apply_reseal",
|
|
"plan_reseal",
|
|
"resealable_capabilities",
|
|
]
|