# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/in-what-scenarios-must-an-instance-of-a-litmus-safety-benchmark-refuse-to-extrapolate-a-multi-turn-adversarial-elicitation-score-beyond-the-turn-horiz.md
# Remnant Warrant:    https://corpusfetch.com/c/in-what-scenarios-must-an-instance-of-a-litmus-safety-benchmark-refuse-to-extrapolate-a-multi-turn-adversarial-elicitation-score-beyond-the-turn-horiz.warrant.json
# Markdown SHA-256:   4ce3f9680d175aa405ddcf3e90640377ffb6ae66b89dc317386547f09e88761a
# Code AST SHA-256:   e1cded75dec03e6dd6a168b7e944405d83dee12616e927fcd1f2dd90602db9df
# Verified Classes:   HorizonExtrapolationError, LitmusScore
# Defensive Guards:   3 fail-closed invariants (zero external deps)

from dataclasses import dataclass


class HorizonExtrapolationError(RuntimeError):
    """Raised when a LITMUS score is consumed outside its calibrated turn horizon."""
    pass


@dataclass(frozen=True)
class LitmusScore:
    value: float
    calibration_horizon: int   # T_max of the corpus the score was fit on
    eval_horizon: int          # number of turns actually evaluated
    stateful_scaffold: bool    # did adversarial state persist across turns?


def assert_score_is_consumable(score: LitmusScore, deployment_horizon: int) -> None:
    """Gate a LITMUS score for use as a safety assertion.

    A score may only be consumed as a classification if the evaluation
    horizon is within calibration AND the deployment horizon does not
    exceed the calibration horizon. Otherwise it is a lower bound only.
    """
    if score.eval_horizon > score.calibration_horizon:
        raise HorizonExtrapolationError(
            f"eval_horizon={score.eval_horizon} exceeds "
            f"calibration_horizon={score.calibration_horizon}; "
            f"score is an observational lower bound, not a classification."
        )
    if deployment_horizon > score.calibration_horizon:
        raise HorizonExtrapolationError(
            f"deployment_horizon={deployment_horizon} exceeds "
            f"calibration_horizon={score.calibration_horizon}; refusing to "
            f"extrapolate R(t) past the fitted turn horizon. Extend the "
            f"calibration corpus to >= {2 * deployment_horizon} turns and refit."
        )
    if score.stateful_scaffold:
        raise HorizonExtrapolationError(
            "stateful adversarial scaffold invalidates horizon-bounded "
            "aggregation; re-score with state persistence disabled or "
            "extend horizon."
        )


def classify(score: LitmusScore, deployment_horizon: int) -> str:
    assert_score_is_consumable(score, deployment_horizon)
    return "low_risk" if score.value < 0.5 else "escalate"
