"""Honest benchmark gate for the verified agent. Two guarantees are enforced here, both grounded in reality rather than hope: 1. **Non-regression by construction (offline, CI-runnable).** The verified agent seeds with the deterministic floor, so with the ``deterministic`` policy it must produce *exactly* the legacy pipeline's verified fixes, and with any policy it must produce a *superset*. :func:`compare_agent_vs_deterministic` checks this directly against bundled fixtures — no model or network needed. 2. **Promotion bar (fed real eval numbers).** The agent may only become the default repair path if a real evaluation shows it beats the deterministic baseline F1 with zero safety regressions. :func:`agent_promotion_verdict` encodes that decision as pure, testable logic. """ from __future__ import annotations from dataclasses import dataclass, field from pathlib import Path from dataforge.agent.controller import AgentRepairRequest, run_agent_repair from dataforge.detectors.base import Schema from dataforge.engine.repair import RepairPipelineRequest, run_repair_pipeline __all__ = [ "AgentGateReport", "FixtureParity", "PromotionVerdict", "agent_promotion_verdict", "compare_agent_vs_deterministic", "default_gate_fixtures", ] @dataclass(frozen=True) class FixtureParity: """Per-fixture comparison of the deterministic floor and the agent. Args: fixture: The fixture path that was evaluated. floor_fix_count: Verified fixes from the legacy deterministic pipeline. agent_fix_count: Verified fixes from the agent (deterministic policy). agent_floor_count: The agent's own reported deterministic-floor count. parity: Whether the agent reproduced the floor exactly (no regression). """ fixture: str floor_fix_count: int agent_fix_count: int agent_floor_count: int parity: bool @dataclass(frozen=True) class AgentGateReport: """Aggregate non-regression report across fixtures.""" fixtures: tuple[FixtureParity, ...] = field(default_factory=tuple) @property def all_parity(self) -> bool: """True when the agent matches the deterministic floor on every fixture.""" return all(item.parity for item in self.fixtures) and bool(self.fixtures) def default_gate_fixtures() -> list[Path]: """Return offline, bundled CSV fixtures suitable for the parity gate.""" root = Path(__file__).resolve().parents[1] candidates = [ root / "fixtures" / "hospital_10rows.csv", root / "datasets" / "embedded" / "hospital" / "dirty.csv", ] return [path for path in candidates if path.is_file()] def compare_agent_vs_deterministic( fixture_paths: list[Path] | None = None, *, schema: Schema | None = None, ) -> AgentGateReport: """Compare the agent (deterministic policy) to the legacy pipeline. Args: fixture_paths: CSV fixtures to evaluate. Defaults to bundled fixtures. schema: Optional schema applied to both paths identically. Returns: An :class:`AgentGateReport`. ``all_parity`` is the CI assertion: the verified agent never regresses the deterministic baseline. """ paths = fixture_paths if fixture_paths is not None else default_gate_fixtures() results: list[FixtureParity] = [] for path in paths: resolved = path.resolve() legacy = run_repair_pipeline( RepairPipelineRequest(source_path=resolved, mode="dry_run", schema=schema) ) agent = run_agent_repair( AgentRepairRequest( source_path=resolved, mode="dry_run", schema=schema, policy="deterministic" ) ) floor_count = len(legacy.fixes) results.append( FixtureParity( fixture=str(resolved), floor_fix_count=floor_count, agent_fix_count=agent.fixes_count, agent_floor_count=agent.floor_fix_count, parity=agent.fixes_count == floor_count and agent.floor_fix_count == floor_count, ) ) return AgentGateReport(fixtures=tuple(results)) @dataclass(frozen=True) class PromotionVerdict: """Whether the agent may be promoted to the default repair path.""" promote: bool reason: str def agent_promotion_verdict( *, agent_f1: float, baseline_f1: float, safety_regressions: int, parity_ok: bool, min_margin: float = 0.0, ) -> PromotionVerdict: """Decide whether the agent may become the default, from real eval numbers. The agent is promoted only when it (1) reproduces the deterministic floor (no structural regression), (2) introduces zero safety regressions, and (3) beats the deterministic baseline F1 by at least ``min_margin``. Args: agent_f1: Measured F1 of the agent policy on the benchmark. baseline_f1: Measured F1 of the deterministic baseline. safety_regressions: Count of adversarial/safety cases the agent newly failed (must be zero). parity_ok: Whether :func:`compare_agent_vs_deterministic` passed. min_margin: Minimum F1 improvement required over the baseline. Returns: A :class:`PromotionVerdict` with a human-readable reason. """ if not parity_ok: return PromotionVerdict( promote=False, reason="Agent does not reproduce the deterministic floor; structural regression.", ) if safety_regressions > 0: return PromotionVerdict( promote=False, reason=f"Agent introduced {safety_regressions} safety regression(s); blocked.", ) if agent_f1 < baseline_f1 + min_margin: return PromotionVerdict( promote=False, reason=( f"Agent F1 {agent_f1:.4f} does not beat baseline {baseline_f1:.4f} " f"by the required margin {min_margin:.4f}." ), ) return PromotionVerdict( promote=True, reason=( f"Agent F1 {agent_f1:.4f} beats baseline {baseline_f1:.4f} " f"(margin >= {min_margin:.4f}) with no safety regression and floor parity." ), )