"""ADR-0159 / W-025 — read-only contemplation quality evaluation. The lane scores the structured report emitted by ``core demo learning-arc --json``. It intentionally does not create proposals, accept proposals, mutate corpora, mutate packs, or write engine_state. Replay-equivalence is measured as a quality signal only; it is never treated as permission to ratify. """ from __future__ import annotations import hashlib import json from dataclasses import dataclass from typing import Any from evals.learning_arc.run_demo import run_demo as run_learning_arc_demo _REQUIRED_SCENES: tuple[str, ...] = ( "S1_cold_session", "S2_checkpoint_enrichment", "S3_engine_authored_proposal", "S4_operator_ratifies", "S5_grounded_session", ) @dataclass(frozen=True, slots=True) class QualityMetric: """One deterministic, non-mutating quality gate.""" name: str passed: bool value: Any expected: Any reason: str def as_dict(self) -> dict[str, Any]: return { "name": self.name, "passed": self.passed, "value": self.value, "expected": self.expected, "reason": self.reason, } @dataclass(frozen=True, slots=True) class ContemplationQualityReport: """Read-only quality report over one learning-arc output.""" lane: str source: str source_digest: str metrics: tuple[QualityMetric, ...] @property def passed(self) -> bool: return all(metric.passed for metric in self.metrics) def as_dict(self) -> dict[str, Any]: passed_count = sum(1 for metric in self.metrics if metric.passed) total = len(self.metrics) return { "lane": self.lane, "source": self.source, "source_digest": self.source_digest, "passed": self.passed, "score": { "passed": passed_count, "total": total, "rate": passed_count / total if total else 0.0, }, "metrics": [metric.as_dict() for metric in self.metrics], } @dataclass(frozen=True, slots=True) class LaneReport: """Adapter shape expected by evals.framework.run_lane.""" metrics: dict[str, Any] case_details: list[dict[str, Any]] def _canonical_json(payload: dict[str, Any]) -> str: return json.dumps( payload, ensure_ascii=False, sort_keys=True, separators=(",", ":"), ) def _digest(payload: dict[str, Any]) -> str: return hashlib.sha256(_canonical_json(payload).encode("utf-8")).hexdigest() def _scene(report: dict[str, Any], scene_name: str) -> dict[str, Any]: if not isinstance(report, dict): return {} scenes = report.get("scenes") if not isinstance(scenes, list): return {} for scene in scenes: if isinstance(scene, dict) and scene.get("scene") == scene_name: detail = scene.get("detail", {}) return detail if isinstance(detail, dict) else {} return {} def _metric( name: str, passed: bool, value: Any, expected: Any, reason: str, ) -> QualityMetric: return QualityMetric( name=name, passed=bool(passed), value=value, expected=expected, reason=reason, ) def evaluate_report(report: dict[str, Any]) -> ContemplationQualityReport: """Score a ``core demo learning-arc --json`` report. This function is pure over the provided dictionary. It is suitable for testing stored CI contemplation reports without touching runtime state. """ if not isinstance(report, dict): raise TypeError("report must be a dictionary") scenes = report.get("scenes") scenes_list = scenes if isinstance(scenes, list) else [] scene_names = tuple( scene.get("scene") for scene in scenes_list if isinstance(scene, dict) ) s1 = _scene(report, "S1_cold_session") s2 = _scene(report, "S2_checkpoint_enrichment") s3 = _scene(report, "S3_engine_authored_proposal") s4 = _scene(report, "S4_operator_ratifies") s5 = _scene(report, "S5_grounded_session") replay = s3.get("replay_evidence", {}) if not isinstance(replay, dict): replay = {} proposed_chain = s3.get("proposed_chain", {}) if not isinstance(proposed_chain, dict): proposed_chain = {} engine_chain = s2.get("engine_chain", {}) if not isinstance(engine_chain, dict): engine_chain = {} before = report.get("before", {}) after = report.get("after", {}) if not isinstance(before, dict): before = {} if not isinstance(after, dict): after = {} metrics = ( _metric( "scene_contract", scene_names == _REQUIRED_SCENES, scene_names, _REQUIRED_SCENES, "ADR-0152 learning-arc output must retain the five audited scenes in order.", ), _metric( "deterministic_replay_integrity", replay.get("replay_equivalent") is True and replay.get("regressed_metrics") == [], { "replay_equivalent": replay.get("replay_equivalent"), "regressed_metrics": replay.get("regressed_metrics"), }, {"replay_equivalent": True, "regressed_metrics": []}, "ADR-0057 replay-equivalence must pass before proposal review eligibility.", ), _metric( "typed_contemplation_provenance", s3.get("source_kind") == "contemplation", s3.get("source_kind"), "contemplation", "ADR-0151/0152 require engine-authored proposals to carry contemplation provenance.", ), _metric( "engine_authored_specificity", s2.get("engine_chain_found") is True and engine_chain.get("connective") == report.get("engine_connective") and engine_chain.get("object") == report.get("engine_object") and proposed_chain.get("connective") == report.get("engine_connective") and proposed_chain.get("object") == report.get("engine_object"), { "engine_chain_found": s2.get("engine_chain_found"), "engine_chain": engine_chain, "proposed_chain": proposed_chain, }, "engine chain and proposed chain share the same engine-derived connective/object", "The W-025 eval scores specificity, not generic proposal existence.", ), _metric( "grounding_transition", s1.get("grounding_source") != "teaching" and s5.get("grounding_source") == "teaching" and report.get("learning_arc_closed") is True, { "before_grounding_source": s1.get("grounding_source"), "after_grounding_source": s5.get("grounding_source"), "learning_arc_closed": report.get("learning_arc_closed"), }, {"before_not": "teaching", "after": "teaching", "learning_arc_closed": True}, "The proposal must produce a measured same-prompt transition into teaching-grounded output.", ), _metric( "downstream_gain_observed", before.get("surface") != after.get("surface"), {"before": before.get("surface"), "after": after.get("surface")}, "before surface differs from after surface", "The accepted transient chain must have an observable effect on the same prompt.", ), _metric( "active_corpus_boundary", report.get("active_corpus_byte_identical") is True and s4.get("active_corpus_byte_identical") is True, { "report_active_corpus_byte_identical": report.get("active_corpus_byte_identical"), "s4_active_corpus_byte_identical": s4.get("active_corpus_byte_identical"), }, True, "ADR-0152/0155: contemplation-quality scoring must never imply active corpus mutation.", ), _metric( "pending_not_auto_accepted", s3.get("state") == "pending", s3.get("state"), "pending", "ADR-0057: replay-equivalence is a precondition, never permission to auto-accept.", ), _metric( "stable_proposal_identity_present", bool(str(s3.get("proposal_id", "")).strip()), s3.get("proposal_id"), "non-empty deterministic proposal_id", "ADR-0151 idempotency requires stable proposal identity to avoid duplicate pressure.", ), ) return ContemplationQualityReport( lane="contemplation-quality", source="core demo learning-arc --json", source_digest=_digest(report), metrics=metrics, ) def run_eval() -> ContemplationQualityReport: """Run the source demo and score its output. ``run_demo(emit_json=True)`` uses tempdirs/transient corpus paths per ADR-0152. This eval adds no write path of its own. """ return evaluate_report(run_learning_arc_demo(emit_json=True)) def run_lane( cases: list[dict[str, Any]], *, config: Any = None, workers: int | None = None, ) -> LaneReport: """Generic eval-framework entry point. The case set is a versioned invocation contract, not external data. The current lane supports exactly one source: ``core demo learning-arc --json``. ``workers`` is accepted for framework compatibility and ignored to preserve the synchronous/no-concurrency doctrine from ADR-0056. """ del config, workers if not isinstance(cases, list) or len(cases) != 1: raise ValueError("contemplation-quality expects exactly one invocation case") case = cases[0] if not isinstance(case, dict): raise TypeError("contemplation-quality case must be a dictionary") source = case.get("source") if source != "learning_arc_demo": raise ValueError(f"unsupported contemplation-quality source: {source!r}") report = run_eval() payload = report.as_dict() return LaneReport( metrics={ "total": len(report.metrics), "passed": sum(1 for metric in report.metrics if metric.passed), "failed": sum(1 for metric in report.metrics if not metric.passed), "pass_rate": payload["score"]["rate"], "all_passed": report.passed, "source_digest": report.source_digest, }, case_details=[ { "case_id": case.get("case_id", "learning_arc_demo"), "source": report.source, "passed": report.passed, "source_digest": report.source_digest, "metrics": [metric.as_dict() for metric in report.metrics], } ], ) __all__ = [ "ContemplationQualityReport", "LaneReport", "QualityMetric", "evaluate_report", "run_eval", "run_lane", ]