# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/where-must-an-instance-of-the-litmus-safety-harness-refuse-to-score-a-trajectory-before-the-judge-model-has-been-pinned-to-a-content-hash.md
# Remnant Warrant:    https://corpusfetch.com/c/where-must-an-instance-of-the-litmus-safety-harness-refuse-to-score-a-trajectory-before-the-judge-model-has-been-pinned-to-a-content-hash.warrant.json
# Markdown SHA-256:   a0a075249a6f4818ce31705548b13c4e42b1fccbe1b33ad60cac7253779c7983
# Code AST SHA-256:   e2b18bc7b30365c880c107a5ef3d60d7867912210e78c2e15e14882c30721a0d
# Verified Classes:   JudgePinMismatchError, JudgeIdentity
# Defensive Guards:   3 fail-closed invariants (zero external deps)

import hashlib
from dataclasses import dataclass


class JudgePinMismatchError(RuntimeError):
    """Raised when the resolved judge weights hash diverges from the pinned baseline.

    A LITMUS score produced under a mismatched judge is not comparable to the
    baseline and MUST NOT be used for regression gating.
    """


@dataclass(frozen=True)
class JudgeIdentity:
    tag: str
    weights_hash: str
    decoding_hash: str
    scaffold_hash: str


def _digest(payload: bytes) -> str:
    return hashlib.sha256(payload).hexdigest()


def resolve_judge(tag: str, weights: bytes, decoding: bytes, scaffold: bytes) -> JudgeIdentity:
    return JudgeIdentity(
        tag=tag,
        weights_hash=_digest(weights),
        decoding_hash=_digest(decoding),
        scaffold_hash=_digest(scaffold),
    )


def assert_comparable(candidate: JudgeIdentity, baseline: JudgeIdentity) -> None:
    """Fail closed before any trajectory is scored if the judge is not pinned to baseline."""
    if candidate.weights_hash != baseline.weights_hash:
        raise JudgePinMismatchError(
            f"judge weights hash mismatch under tag {candidate.tag!r}: "
            f"candidate={candidate.weights_hash[:12]} baseline={baseline.weights_hash[:12]}; "
            "scores are not comparable, gate fails closed"
        )
    if candidate.decoding_hash != baseline.decoding_hash:
        raise JudgePinMismatchError(
            f"decoding params changed under tag {candidate.tag!r}: "
            f"candidate={candidate.decoding_hash[:12]} baseline={baseline.decoding_hash[:12]}"
        )
    if candidate.scaffold_hash != baseline.scaffold_hash:
        raise JudgePinMismatchError(
            f"prompt scaffold changed under tag {candidate.tag!r}: "
            f"candidate={candidate.scaffold_hash[:12]} baseline={baseline.scaffold_hash[:12]}"
        )


def score_corpus(candidate: JudgeIdentity, baseline: JudgeIdentity, trajectories: list) -> list:
    # Gate check happens before a single trajectory is dispatched.
    assert_comparable(candidate, baseline)
    return [{"trajectory_id": t.get("id"), "scored": True} for t in trajectories]
