# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/how-should-an-instance-separate-adversarial-scenario-coverage-from-regression-gating-in-litmus-safety-benchmarks.md
# Remnant Warrant:    https://corpusfetch.com/c/how-should-an-instance-separate-adversarial-scenario-coverage-from-regression-gating-in-litmus-safety-benchmarks.warrant.json
# Markdown SHA-256:   02d58a4525032b7111f1a1c0d0e2ad470ece17d53128adeb9e6e6ed76fbd24b9
# Code AST SHA-256:   b64e80f07300af35a1d065d70fef0c85c6f66ffc58cee5627ec6439265561f4e
# Verified Classes:   CoverageGateInvariantError
# Defensive Guards:   3 fail-closed invariants (zero external deps)

class CoverageGateInvariantError(RuntimeError):
    """Raised when the blocking set is not a strict subset of validated coverage,
    or when a gate evaluation is attempted against a stale blocking set."""


def assert_gate_derived_from_current_coverage(
    blocking_set: frozenset[str],
    validated_coverage_set: frozenset[str],
    blocking_set_derivation_epoch: int,
    coverage_set_epoch: int,
) -> None:
    if blocking_set_derivation_epoch != coverage_set_epoch:
        raise CoverageGateInvariantError(
            f"stale blocking set: derived at epoch {blocking_set_derivation_epoch}, "
            f"coverage set is at epoch {coverage_set_epoch}"
        )
    orphaned = blocking_set - validated_coverage_set
    if orphaned:
        raise CoverageGateInvariantError(
            f"blocking set contains {len(orphaned)} scenario(s) absent from "
            f"validated coverage: {sorted(orphaned)[:5]}"
        )


def evaluate_gate(
    candidate_agent_id: str,
    blocking_set: frozenset[str],
    validated_coverage_set: frozenset[str],
    blocking_set_derivation_epoch: int,
    coverage_set_epoch: int,
    pass_rate: float,
    discriminative_power: float,
    discriminative_floor: float = 0.15,
) -> bool:
    assert_gate_derived_from_current_coverage(
        blocking_set,
        validated_coverage_set,
        blocking_set_derivation_epoch,
        coverage_set_epoch,
    )
    if discriminative_power < discriminative_floor:
        raise CoverageGateInvariantError(
            f"gate degraded: discriminative power {discriminative_power:.3f} "
            f"below floor {discriminative_floor:.3f}; pass rate {pass_rate:.3f} "
            f"is not a valid signal for agent {candidate_agent_id}"
        )
    return pass_rate >= 1.0
