# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/when-must-a-federation-broker-refuse-to-issue-a-capability-grant-after-a-successful-policy-negotiation.md
# Remnant Warrant:    https://corpusfetch.com/c/when-must-a-federation-broker-refuse-to-issue-a-capability-grant-after-a-successful-policy-negotiation.warrant.json
# Markdown SHA-256:   ed06817332b90d50a0d383b0e4d0b8ce8270ab44e2f3516e5449d57e11520027
# Code AST SHA-256:   18a8a5169408221d14196dfb0c20fceff41df8c3eec4f3f9f9a179b257c9c9bb
# Verified Classes:   TrustContextDriftError, NegotiationSnapshot
# Defensive Guards:   2 fail-closed invariants (zero external deps)

import hashlib
from dataclasses import dataclass


class TrustContextDriftError(RuntimeError):
    """Raised when the trust context at issuance commit differs from the
    trust context captured at negotiation start. Issuance must be refused
    and negotiation restarted; the grant must not be re-bound."""


@dataclass(frozen=True)
class NegotiationSnapshot:
    policy_digest: str
    trust_context_digest: str
    subject_principal: str


def digest_trust_context(attestation: bytes, delegation_chain: tuple[bytes, ...]) -> str:
    h = hashlib.sha256()
    h.update(attestation)
    for link in delegation_chain:
        h.update(b"\x00")
        h.update(link)
    return h.hexdigest()


def commit_grant(
    snapshot: NegotiationSnapshot,
    current_attestation: bytes,
    current_delegation_chain: tuple[bytes, ...],
    current_subject_principal: str,
) -> str:
    current_digest = digest_trust_context(current_attestation, current_delegation_chain)
    if current_digest != snapshot.trust_context_digest:
        raise TrustContextDriftError(
            "trust context drifted between negotiation and issuance: "
            f"snapshot={snapshot.trust_context_digest[:12]} "
            f"current={current_digest[:12]}; refusing issuance, renegotiate"
        )
    if current_subject_principal != snapshot.subject_principal:
        raise TrustContextDriftError(
            "subject principal changed between negotiation and issuance: "
            f"snapshot={snapshot.subject_principal} "
            f"current={current_subject_principal}; refusing issuance"
        )
    return f"grant:{snapshot.policy_digest}:{current_digest[:16]}"
