Phase 5.8 of ADR-0119. Composes the per-sub-phase substrate (5.1..5.6) into a single per-split lane verdict the eventual ADR-0120 expert promotion contract can consume. LANE_SHAPE_REGISTRY adds: "gsm8k_math": "gsm8k_capability_shape" _check_gsm8k_capability_shape refuses on any of: - missing cases_total / correct / wrong / refused fields - cases_total <= 0 - wrong != 0 (ADR-0114a Obligation #4) - correct + refused != cases_total (accounting incomplete) - overall_pass present and false Accepts otherwise. Edge: all-refused passes the shape gate (runner self-consistency). Capability bar (min correct-rate, depth-curve ε) lives in ADR-0120. Live measurement on main: dev 50/50 correct, 0 wrong, 0 refused → gate ✓ public 150/150 correct, 0 wrong, 0 refused → gate ✓ 21 invariant tests pin: registry mapping, shape checker presence, live runner passes, nonzero wrong refuses, incomplete accounting refuses, missing field refuses, clean metrics pass, all-refused edge passes, all Phase 5.1..5.6 substrate artifacts exist on disk. Phase 5 status: 5.1..5.6 + 5.8 ✓. Only 5.7 (sealed real GSM8K test) remains before ADR-0120 (first expert promotion contract) becomes feasible. ADR-0114a roll-up unchanged: 10/10 obligations discharged on main (modulo Phase 5.7's lane-specific GSM8K test sealing). Tests: 21 new + 80 prior across Phase 5 + adjacent suites = 101 green; 67/67 smoke. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
422 lines
15 KiB
Python
422 lines
15 KiB
Python
"""Domain-aware audit-passed promotion gate (ADR-0106, renamed by ADR-0113).
|
|
|
|
Historical note: this module is named ``expert_demo`` for backward
|
|
compatibility with ADR-0106..0112. ADR-0113 renamed the outward
|
|
semantics — ledger status string, YAML key, predicate key, and CLI
|
|
command — from "expert-demo" to "audit-passed" because the gate
|
|
verifies CORE *claim-shape compliance* (signed digest, replay
|
|
determinism, typed refusal, exact recall) which transformer LLMs
|
|
structurally cannot produce, NOT raw expert-level capability. The
|
|
internal module/function/class identifiers were intentionally left in
|
|
place under ADR-0113's "semantics only" scope to minimize churn.
|
|
|
|
|
|
Replaces the cognition-lane-only predicate previously embedded in
|
|
``core.capability.reporting``. A domain ``D`` is promoted to
|
|
``audit_passed=true`` iff:
|
|
|
|
1. ``D`` already passes the ``reasoning_capable`` predicate.
|
|
2. A signed ``ExpertDemoClaim`` exists in the reviewer registry for ``D``.
|
|
3. The reviewer named in ``claim.signed_by`` may review evals for ``D``
|
|
(ADR-0092 ``can_review`` check, scope ``"eval"``).
|
|
4. Every lane listed in ``claim.evidence_lanes`` is attached to at least
|
|
one of ``D``'s ratified packs (no cross-domain bleed).
|
|
5. Every named lane's threshold metrics meet the ADR-0106 §1 bar on both
|
|
``public`` and ``holdout``.
|
|
6. The canonical evidence-bundle digest reproduces ``claim.claim_digest``
|
|
byte-for-byte.
|
|
|
|
Any failure leaves the ledger row at ``reasoning-capable``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import json
|
|
from dataclasses import dataclass
|
|
from typing import Any, Callable, Mapping, Sequence
|
|
|
|
from core.capability.reviewers import ReviewerRegistry
|
|
|
|
_LaneResults = Mapping[str, Mapping[str, Mapping[str, Any]]]
|
|
|
|
|
|
SURFACE_GROUNDEDNESS_MIN: float = 0.95
|
|
TERM_CAPTURE_RATE_MIN: float = 0.85
|
|
INTENT_ACCURACY_MIN: float = 0.95
|
|
VERSOR_CLOSURE_RATE_MIN: float = 1.0
|
|
ACCURACY_MIN: float = 0.95
|
|
ALL_PASS_RATE_MIN: float = 0.95
|
|
REPLAY_DETERMINISM_MIN: float = 1.0
|
|
|
|
FABRICATION_CONTROL_LANE: str = "fabrication_control"
|
|
|
|
|
|
# ADR-0109 — lane-shape registry. New shapes require an ADR amendment;
|
|
# new lanes must be added here explicitly. Unknown lane ids fail closed.
|
|
LANE_SHAPE_REGISTRY: dict[str, str] = {
|
|
"cognition": "cognition_shape",
|
|
"elementary_mathematics_ood": "accuracy_shape",
|
|
"foundational_physics_ood": "accuracy_shape",
|
|
"symbolic_logic": "inference_shape",
|
|
"hebrew_fluency": "accuracy_shape",
|
|
"koine_greek_fluency": "accuracy_shape",
|
|
"inference_closure": "inference_shape",
|
|
"fabrication_control": "refusal_shape",
|
|
# ADR-0119.8 — gsm8k_math capability lane. Distinct shape because
|
|
# the gate composes ``wrong == 0`` (Obligation #4) with
|
|
# ``correct + refused == total`` and ``overall_pass == True``.
|
|
"gsm8k_math": "gsm8k_capability_shape",
|
|
}
|
|
|
|
|
|
def _check_cognition_shape(lane_id: str, metrics: Mapping[str, Any]) -> tuple[bool, str]:
|
|
checks = (
|
|
("surface_groundedness", SURFACE_GROUNDEDNESS_MIN),
|
|
("term_capture_rate", TERM_CAPTURE_RATE_MIN),
|
|
("intent_accuracy", INTENT_ACCURACY_MIN),
|
|
("versor_closure_rate", VERSOR_CLOSURE_RATE_MIN),
|
|
)
|
|
for key, minimum in checks:
|
|
if key not in metrics:
|
|
return False, f"lane {lane_id!r} missing required metric {key!r}"
|
|
value = float(metrics[key] or 0)
|
|
if value < minimum:
|
|
return False, (
|
|
f"lane {lane_id!r} {key}={value} below threshold {minimum}"
|
|
)
|
|
return True, ""
|
|
|
|
|
|
def _accuracy_value(metrics: Mapping[str, Any]) -> float | None:
|
|
"""Resolve accuracy from explicit key or passed/total fallback."""
|
|
if "accuracy" in metrics:
|
|
return float(metrics["accuracy"] or 0)
|
|
if "passed" in metrics and "total" in metrics:
|
|
total = float(metrics["total"] or 0)
|
|
if total <= 0:
|
|
return None
|
|
return float(metrics["passed"] or 0) / total
|
|
return None
|
|
|
|
|
|
def _check_accuracy_shape(lane_id: str, metrics: Mapping[str, Any]) -> tuple[bool, str]:
|
|
value = _accuracy_value(metrics)
|
|
if value is None:
|
|
return False, (
|
|
f"lane {lane_id!r} missing accuracy (and no passed/total fallback)"
|
|
)
|
|
if value < ACCURACY_MIN:
|
|
return False, (
|
|
f"lane {lane_id!r} accuracy={value} below threshold {ACCURACY_MIN}"
|
|
)
|
|
return True, ""
|
|
|
|
|
|
def _check_inference_shape(lane_id: str, metrics: Mapping[str, Any]) -> tuple[bool, str]:
|
|
rate_key = "all_pass_rate"
|
|
if rate_key not in metrics and "all_three_pass_rate" in metrics:
|
|
rate_key = "all_three_pass_rate"
|
|
if rate_key not in metrics:
|
|
return False, f"lane {lane_id!r} missing required metric 'all_pass_rate'"
|
|
rate = float(metrics[rate_key] or 0)
|
|
if rate < ALL_PASS_RATE_MIN:
|
|
return False, (
|
|
f"lane {lane_id!r} all_pass_rate={rate} below threshold "
|
|
f"{ALL_PASS_RATE_MIN}"
|
|
)
|
|
if "replay_determinism" not in metrics:
|
|
return False, f"lane {lane_id!r} missing required metric 'replay_determinism'"
|
|
det = float(metrics["replay_determinism"] or 0)
|
|
if det < REPLAY_DETERMINISM_MIN:
|
|
return False, (
|
|
f"lane {lane_id!r} replay_determinism={det} below threshold "
|
|
f"{REPLAY_DETERMINISM_MIN}"
|
|
)
|
|
overall = metrics.get("overall_pass")
|
|
if overall is not None and not bool(overall):
|
|
return False, f"lane {lane_id!r} overall_pass is false"
|
|
return True, ""
|
|
|
|
|
|
def _check_refusal_shape(lane_id: str, metrics: Mapping[str, Any]) -> tuple[bool, str]:
|
|
by_class = metrics.get("by_class")
|
|
if not isinstance(by_class, Mapping) or not by_class:
|
|
return False, f"lane {lane_id!r} missing 'by_class' refusal counts"
|
|
for class_id, bucket in by_class.items():
|
|
if not isinstance(bucket, Mapping):
|
|
return False, (
|
|
f"lane {lane_id!r} by_class[{class_id!r}] is not a mapping"
|
|
)
|
|
fabricated = int(bucket.get("fabricated", 0) or 0)
|
|
if fabricated != 0:
|
|
return False, (
|
|
f"lane {lane_id!r} by_class[{class_id!r}] fabricated="
|
|
f"{fabricated} (must be 0)"
|
|
)
|
|
n = int(bucket.get("n", 0) or 0)
|
|
refused = int(bucket.get("refused", 0) or 0)
|
|
if n <= 0 or refused != n:
|
|
return False, (
|
|
f"lane {lane_id!r} by_class[{class_id!r}] refused={refused} "
|
|
f"!= n={n}"
|
|
)
|
|
return True, ""
|
|
|
|
|
|
def _check_gsm8k_capability_shape(
|
|
lane_id: str, metrics: Mapping[str, Any]
|
|
) -> tuple[bool, str]:
|
|
"""ADR-0119.8 — overall gsm8k_math lane gate.
|
|
|
|
The lane runner (ADR-0119.3) emits a per-split metrics block with:
|
|
|
|
- ``cases_total`` (int)
|
|
- ``correct`` (int)
|
|
- ``wrong`` (int) — must be 0 per ADR-0114a Obligation #4
|
|
- ``refused`` (int)
|
|
- ``wrong_count_is_zero`` (bool)
|
|
- ``overall_pass`` (bool) — ``wrong == 0 AND correct + refused == total``
|
|
|
|
The checker validates all three load-bearing constraints. A missing
|
|
field, a nonzero wrong count, or an arithmetic discrepancy refuses
|
|
the lane.
|
|
"""
|
|
for required in ("cases_total", "correct", "wrong", "refused"):
|
|
if required not in metrics:
|
|
return False, (
|
|
f"lane {lane_id!r} missing required metric {required!r}"
|
|
)
|
|
total = int(metrics["cases_total"] or 0)
|
|
correct = int(metrics["correct"] or 0)
|
|
wrong = int(metrics["wrong"] or 0)
|
|
refused = int(metrics["refused"] or 0)
|
|
|
|
if total <= 0:
|
|
return False, f"lane {lane_id!r} cases_total={total} (must be > 0)"
|
|
if wrong != 0:
|
|
return False, (
|
|
f"lane {lane_id!r} wrong={wrong} (must be 0 — ADR-0114a Obligation #4)"
|
|
)
|
|
if correct + refused != total:
|
|
return False, (
|
|
f"lane {lane_id!r} correct({correct}) + refused({refused}) "
|
|
f"!= cases_total({total}); outcome accounting incomplete"
|
|
)
|
|
overall_pass = metrics.get("overall_pass")
|
|
if overall_pass is not None and not bool(overall_pass):
|
|
return False, (
|
|
f"lane {lane_id!r} overall_pass is False despite "
|
|
f"wrong=0 and accounting balanced"
|
|
)
|
|
return True, ""
|
|
|
|
|
|
SHAPE_CHECKERS: dict[str, Any] = {
|
|
"cognition_shape": _check_cognition_shape,
|
|
"accuracy_shape": _check_accuracy_shape,
|
|
"inference_shape": _check_inference_shape,
|
|
"refusal_shape": _check_refusal_shape,
|
|
"gsm8k_capability_shape": _check_gsm8k_capability_shape,
|
|
}
|
|
|
|
|
|
def resolve_lane_shape(lane_id: str) -> str | None:
|
|
"""Return the registered shape id for ``lane_id`` or ``None``."""
|
|
return LANE_SHAPE_REGISTRY.get(lane_id)
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class ExpertDemoVerdict:
|
|
passed: bool
|
|
reason: str
|
|
derived_digest: str | None
|
|
|
|
|
|
def derive_evidence_digest(
|
|
domain_id: str,
|
|
evidence_revision: str,
|
|
evidence_lanes: Sequence[str],
|
|
lane_results: _LaneResults,
|
|
) -> str:
|
|
"""Compute the canonical evidence-bundle SHA-256.
|
|
|
|
``lane_results`` maps ``lane_id -> split -> result_dict`` where the
|
|
result_dict is the raw JSON ``metrics`` block from
|
|
``evals/<lane>/results/v1_<split>.json``.
|
|
|
|
The bundle is deterministic in field order (sorted keys, compact
|
|
separators) so re-derivation reproduces the digest byte-for-byte.
|
|
"""
|
|
sorted_lanes = sorted(evidence_lanes)
|
|
bundle = {
|
|
"domain_id": domain_id,
|
|
"evidence_revision": evidence_revision,
|
|
"evidence_lanes": sorted_lanes,
|
|
"lane_metrics": {
|
|
lane: {
|
|
"public": dict(lane_results.get(lane, {}).get("public", {})),
|
|
"holdout": dict(lane_results.get(lane, {}).get("holdout", {})),
|
|
}
|
|
for lane in sorted_lanes
|
|
},
|
|
}
|
|
body = json.dumps(bundle, sort_keys=True, separators=(",", ":")).encode("utf-8")
|
|
return hashlib.sha256(body).hexdigest()
|
|
|
|
|
|
def _meets_thresholds(lane_id: str, metrics: Mapping[str, Any]) -> tuple[bool, str]:
|
|
"""Dispatch lane threshold check by registered shape (ADR-0109).
|
|
|
|
Unknown lane ids are fail-closed: adding a lane to the audit-passed
|
|
surface requires an explicit registry entry, which requires an ADR
|
|
amendment.
|
|
"""
|
|
shape_id = resolve_lane_shape(lane_id)
|
|
if shape_id is None:
|
|
return False, (
|
|
f"lane {lane_id!r} has no registered shape — introduce via ADR amendment"
|
|
)
|
|
checker = SHAPE_CHECKERS.get(shape_id)
|
|
if checker is None:
|
|
return False, (
|
|
f"lane {lane_id!r} resolves to shape {shape_id!r} with no checker"
|
|
)
|
|
return checker(lane_id, metrics)
|
|
|
|
|
|
def evaluate_expert_demo(
|
|
*,
|
|
domain_id: str,
|
|
reasoning_capable: bool,
|
|
registry: ReviewerRegistry,
|
|
domain_lanes: Sequence[str],
|
|
lane_results: Mapping[str, Mapping[str, Mapping[str, object]]],
|
|
) -> ExpertDemoVerdict:
|
|
"""Decide whether ``domain_id`` may carry ``audit_passed=true``.
|
|
|
|
``domain_lanes`` is the union of ``eval_lanes`` declared by the
|
|
ratified packs for ``domain_id`` — the only lanes legal as evidence
|
|
sources for this promotion.
|
|
|
|
``lane_results`` is the materialised per-split metrics; the caller
|
|
is responsible for resolving each lane id to its on-disk result.
|
|
"""
|
|
if not reasoning_capable:
|
|
return ExpertDemoVerdict(
|
|
passed=False,
|
|
reason="domain not yet reasoning-capable",
|
|
derived_digest=None,
|
|
)
|
|
|
|
claim = registry.expert_demo_claim_for(domain_id)
|
|
if claim is None:
|
|
return ExpertDemoVerdict(
|
|
passed=False,
|
|
reason="no audit_passed_claims entry for this domain",
|
|
derived_digest=None,
|
|
)
|
|
|
|
if not registry.can_review(claim.signed_by, domain_id=domain_id, scope="eval"):
|
|
return ExpertDemoVerdict(
|
|
passed=False,
|
|
reason=(
|
|
f"signer {claim.signed_by!r} cannot review eval-scope "
|
|
f"artifacts for domain {domain_id!r}"
|
|
),
|
|
derived_digest=None,
|
|
)
|
|
|
|
domain_lane_set = set(domain_lanes)
|
|
cross_domain = [
|
|
lane for lane in claim.evidence_lanes if lane not in domain_lane_set
|
|
]
|
|
if cross_domain:
|
|
return ExpertDemoVerdict(
|
|
passed=False,
|
|
reason=(
|
|
f"claim cites lanes not attached to domain {domain_id!r}: "
|
|
f"{sorted(cross_domain)}"
|
|
),
|
|
derived_digest=None,
|
|
)
|
|
|
|
for lane_id in claim.evidence_lanes:
|
|
for split in ("public", "holdout"):
|
|
metrics = lane_results.get(lane_id, {}).get(split)
|
|
if not metrics:
|
|
return ExpertDemoVerdict(
|
|
passed=False,
|
|
reason=(
|
|
f"lane {lane_id!r} split {split!r} has no results"
|
|
),
|
|
derived_digest=None,
|
|
)
|
|
ok, why = _meets_thresholds(lane_id, metrics)
|
|
if not ok:
|
|
return ExpertDemoVerdict(
|
|
passed=False,
|
|
reason=f"{why} (split={split})",
|
|
derived_digest=None,
|
|
)
|
|
|
|
derived = derive_evidence_digest(
|
|
domain_id=domain_id,
|
|
evidence_revision=claim.evidence_revision,
|
|
evidence_lanes=claim.evidence_lanes,
|
|
lane_results=lane_results,
|
|
)
|
|
if derived != claim.claim_digest:
|
|
return ExpertDemoVerdict(
|
|
passed=False,
|
|
reason=(
|
|
"evidence-bundle digest does not match claim_digest "
|
|
"(replay drift)"
|
|
),
|
|
derived_digest=derived,
|
|
)
|
|
|
|
return ExpertDemoVerdict(
|
|
passed=True,
|
|
reason="all audit-passed predicates satisfied",
|
|
derived_digest=derived,
|
|
)
|
|
|
|
|
|
def collect_domain_lanes(
|
|
pack_manifests: Sequence[Mapping[str, Any]],
|
|
) -> tuple[str, ...]:
|
|
"""Return the union of ``eval_lanes[].lane`` across pack manifests."""
|
|
lanes: list[str] = []
|
|
seen: set[str] = set()
|
|
for manifest in pack_manifests:
|
|
entries = manifest.get("eval_lanes") or []
|
|
if not isinstance(entries, list):
|
|
continue
|
|
for entry in entries:
|
|
if not isinstance(entry, Mapping):
|
|
continue
|
|
lane_id = entry.get("lane")
|
|
if isinstance(lane_id, str) and lane_id not in seen:
|
|
seen.add(lane_id)
|
|
lanes.append(lane_id)
|
|
return tuple(lanes)
|
|
|
|
|
|
def materialise_lane_results(
|
|
lane_ids: Sequence[str],
|
|
*,
|
|
fetch_split: Callable[[str, str], Mapping[str, Any]],
|
|
) -> dict[str, dict[str, Mapping[str, Any]]]:
|
|
"""Materialise ``lane -> split -> metrics`` for the named lanes.
|
|
|
|
``fetch_split(lane_id, split)`` returns the parsed ``metrics``
|
|
sub-dict from the latest result file (or ``{}`` if absent).
|
|
"""
|
|
out: dict[str, dict[str, Mapping[str, object]]] = {}
|
|
for lane_id in lane_ids:
|
|
out[lane_id] = {
|
|
"public": dict(fetch_split(lane_id, "public") or {}),
|
|
"holdout": dict(fetch_split(lane_id, "holdout") or {}),
|
|
}
|
|
return out
|