# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/what-structural-signals-indicate-a-litmus-safety-benchmark-harness-is-silently-scoring-against-a-stale-trajectory-oracle.md
# Remnant Warrant:    https://corpusfetch.com/c/what-structural-signals-indicate-a-litmus-safety-benchmark-harness-is-silently-scoring-against-a-stale-trajectory-oracle.warrant.json
# Markdown SHA-256:   b66ffa346133c758d120cab7aa4cc6536eabb9980121a16dbba2a670db13785f
# Code AST SHA-256:   2ca233f569839a3bd0a726464e481bdbe544d43f56923ebf6d08e7ca9cf6a078
# Verified Classes:   StaleOracleError, CycleReport
# Defensive Guards:   3 fail-closed invariants (zero external deps)

"""LITMUS stale-oracle detector.

Raises StaleOracleError when structural signals indicate the frozen
reference oracle no longer tracks the agent's action space.
"""

from __future__ import annotations

from dataclasses import dataclass


class StaleOracleError(RuntimeError):
    """Raised when the LITMUS oracle is structurally stale vs. the corpus."""


@dataclass
class CycleReport:
    cycle: int
    label_feature_mi: float          # MI(unsafe_label, top-k causal features)
    gate_pass_rate: float            # fraction of trajectories passing gate
    human_audit_true_safe_rate: float  # held-out human audit on same set
    oracle_confidence_bimodal: bool  # True if labels cluster near 0/1 with no middle


MI_FLOOR = 0.10
GATE_AUDIT_GAP_FLOOR = 0.05


def check_oracle_health(history: list[CycleReport]) -> None:
    if len(history) < 2:
        return

    recent = history[-2:]

    # Signal 1: MI collapse across two consecutive cycles.
    if all(c.label_feature_mi < MI_FLOOR for c in recent):
        raise StaleOracleError(
            f"Label-feature MI below floor {MI_FLOOR} for cycles "
            f"{recent[0].cycle} and {recent[1].cycle} "
            f"(MI={[round(c.label_feature_mi, 3) for c in recent]}). "
            "Oracle is scoring on non-causal residue."
        )

    # Signal 2: gate/audit decoupling.
    for c in recent:
        gap = c.gate_pass_rate - c.human_audit_true_safe_rate
        if gap > GATE_AUDIT_GAP_FLOOR:
            raise StaleOracleError(
                f"Cycle {c.cycle}: gate pass rate {c.gate_pass_rate:.3f} "
                f"exceeds human-audited true-safe rate "
                f"{c.human_audit_true_safe_rate:.3f} by {gap:.3f}. "
                "Gate is measuring oracle agreement, not safety."
            )

    # Signal 3: bimodal oracle confidence on novel trajectories.
    if recent[-1].oracle_confidence_bimodal:
        raise StaleOracleError(
            f"Cycle {recent[-1].cycle}: oracle labels are bimodal on novel "
            "corpus — extrapolating, not classifying. Freeze and re-derive."
        )


def gate_merge(history: list[CycleReport]) -> bool:
    try:
        check_oracle_health(history)
    except StaleOracleError:
        # Fail closed: block merge gating until oracle re-derived.
        return False
    return True
