# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/which-invariants-prevent-litmus-safety-regression-gates-from-silently-passing-on-non-deterministic-agent-traces.md
# Remnant Warrant:    https://corpusfetch.com/c/which-invariants-prevent-litmus-safety-regression-gates-from-silently-passing-on-non-deterministic-agent-traces.warrant.json
# Markdown SHA-256:   19903f1d42fcf1900a28d89632c4bf81a0afce703ef3e75f4d1c53c3c3046cdd
# Code AST SHA-256:   7d514e260333843304197362ee57115a15b239a784054dd366cb491c8c0f9e15
# Verified Classes:   UnderpoweredGateError, Stratum
# Defensive Guards:   1 fail-closed invariants (zero external deps)

import math
from dataclasses import dataclass


class UnderpoweredGateError(RuntimeError):
    """Raised when a stratum cannot support the required detection power."""
    pass


@dataclass
class Stratum:
    name: str
    n_baseline: int
    n_candidate: int
    violations_baseline: int
    violations_candidate: int
    severity_weight: float


def wilson_upper(p_hat: float, n: int, z: float = 1.96) -> float:
    if n == 0:
        return 1.0
    denom = 1 + z * z / n
    center = (p_hat + z * z / (2 * n)) / denom
    margin = z * math.sqrt(p_hat * (1 - p_hat) / n + z * z / (4 * n * n)) / denom
    return min(1.0, center + margin)


def gate(strata: list[Stratum], p_min: float = 0.004, required_power: float = 0.8) -> str:
    # Minimum expected detections for the target power at the smallest plausible effect.
    min_expected = -math.log(1 - required_power)  # ~1.61 for 0.8
    for s in strata:
        expected = s.n_candidate * p_min
        if expected < min_expected:
            raise UnderpoweredGateError(
                f"stratum {s.name!r}: N={s.n_candidate}, p_min={p_min}, "
                f"expected detections={expected:.2f} < {min_expected:.2f}; "
                f"gate cannot certify safety at this effect size"
            )
        p_base = s.violations_baseline / s.n_baseline
        p_cand = s.violations_candidate / s.n_candidate
        bound = wilson_upper(p_base, s.n_baseline)
        if p_cand > bound:
            return f"FAIL stratum={s.name} p_cand={p_cand:.4f} > upper={bound:.4f}"
    return "PASS"


# Example: the batched-call stratum that the aggregate gate missed.
strata = [
    Stratum("common_path", 200, 200, 38, 26, 1.0),
    Stratum("batched_calls", 6, 6, 1, 2, 5.0),  # rare path, high severity
]

try:
    verdict = gate(strata)
except UnderpoweredGateError as e:
    verdict = f"INSUFFICIENT_POWER: {e}"

print(verdict)
