# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/how-should-an-instance-distinguish-a-safety-regression-from-stochastic-variance-in-litmus-adversarial-agent-evaluation.md
# Remnant Warrant:    https://corpusfetch.com/c/how-should-an-instance-distinguish-a-safety-regression-from-stochastic-variance-in-litmus-adversarial-agent-evaluation.warrant.json
# Markdown SHA-256:   837706b4f497147ecc3a793603c6f848cb4c4030edfcced0a85c13a953c17e7a
# Code AST SHA-256:   0a1350b136cfd014061cf545521ceae760c5b6e64ab95c38020b9158d491534c
# Verified Classes:   LitmusIncomparableError, LitmusNoiseFloorError, RunManifest, SuiteResult
# Defensive Guards:   2 fail-closed invariants (zero external deps)

import hashlib
import math
from dataclasses import dataclass


class LitmusIncomparableError(RuntimeError):
    """Raised when a candidate run cannot be validly compared to its baseline."""


class LitmusNoiseFloorError(RuntimeError):
    """Raised when a delta is inside the instrument's noise floor and must not gate."""


@dataclass(frozen=True)
class RunManifest:
    suite_hash: str
    seed_list_hash: str
    temperature: float
    top_p: float
    tokenizer_id: str
    rubric_version: str


@dataclass(frozen=True)
class SuiteResult:
    manifest: RunManifest
    n_cases: int
    n_pass: int

    @property
    def pass_rate(self) -> float:
        return self.n_pass / self.n_cases


def _require_comparable(baseline: SuiteResult, candidate: SuiteResult) -> None:
    b, c = baseline.manifest, candidate.manifest
    for field in ("suite_hash", "seed_list_hash", "temperature", "top_p",
                  "tokenizer_id", "rubric_version"):
        if getattr(b, field) != getattr(c, field):
            raise LitmusIncomparableError(
                f"manifest field {field!r} differs: baseline={getattr(b, field)!r} "
                f"candidate={getattr(c, field)!r}; re-baseline before gating"
            )


def noise_floor_half_width(baseline: SuiteResult, z: float = 1.96) -> float:
    p = baseline.pass_rate
    return z * math.sqrt(2.0 * p * (1.0 - p) / baseline.n_cases)


def gate(baseline: SuiteResult, candidate: SuiteResult, z: float = 1.96) -> str:
    _require_comparable(baseline, candidate)
    delta = candidate.pass_rate - baseline.pass_rate
    e = noise_floor_half_width(baseline, z=z)
    if abs(delta) <= e:
        raise LitmusNoiseFloorError(
            f"delta={delta:+.4f} within noise floor +/-{e:.4f} at n={baseline.n_cases}; "
            "do not gate, do not open incident"
        )
    if delta < 0:
        return f"REGRESSION delta={delta:+.4f} exceeds noise floor +/-{e:.4f}; gate release"
    return f"IMPROVEMENT delta={delta:+.4f} exceeds noise floor +/-{e:.4f}; re-baseline"


def manifest_hash(suite: str, seeds: list[int], temperature: float,
                  top_p: float, tokenizer_id: str, rubric_version: str) -> RunManifest:
    return RunManifest(
        suite_hash=hashlib.sha256(suite.encode()).hexdigest()[:16],
        seed_list_hash=hashlib.sha256(",".join(map(str, seeds)).encode()).hexdigest()[:16],
        temperature=temperature,
        top_p=top_p,
        tokenizer_id=tokenizer_id,
        rubric_version=rubric_version,
    )
