# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/which-invariants-prevent-cross-org-agent-trust-federation-from-silently-degrading-under-manifest-key-rotation.md
# Remnant Warrant:    https://corpusfetch.com/c/which-invariants-prevent-cross-org-agent-trust-federation-from-silently-degrading-under-manifest-key-rotation.warrant.json
# Markdown SHA-256:   11f7bde98a054913f6c2e7d01f7e3f249ff1a0447c0be91fe09c4032848c29dc
# Code AST SHA-256:   8c6be576af2281756af990c01607f25650c75333d82df6a559e3ef7c978ed73e
# Verified Classes:   StaleManifestBindingError, Manifest
# Defensive Guards:   3 fail-closed invariants (zero external deps)

import hashlib
import time
from dataclasses import dataclass


class StaleManifestBindingError(RuntimeError):
    """Raised when a VP's proof is not bound to a current, digest-matched manifest."""


@dataclass(frozen=True)
class Manifest:
    agent_id: str
    sequence: int
    issued_at: float
    capability_scope: tuple
    status: str
    canonical_bytes: bytes

    def digest(self) -> str:
        return hashlib.sha256(self.canonical_bytes).hexdigest()


def verify_presentation(
    vp: dict,
    manifest: Manifest,
    status_list_fetched_at: float,
    rotation_period_s: float,
    now: float | None = None,
) -> tuple:
    now = time.time() if now is None else now

    # Invariant 2a: VP proof must bind to the manifest digest, not just the agent.
    proof_digest = vp.get("proof", {}).get("manifest_digest")
    if proof_digest != manifest.digest():
        raise StaleManifestBindingError(
            f"VP proof digest {proof_digest!r} does not match manifest digest "
            f"{manifest.digest()!r}; key-manifest binding unverified."
        )

    # Invariant 2b: status list must be fresher than the rotation period.
    age = now - status_list_fetched_at
    if age > rotation_period_s:
        raise StaleManifestBindingError(
            f"status list is {age:.0f}s old, exceeds rotation period "
            f"{rotation_period_s:.0f}s; manifest status cannot be trusted."
        )

    if manifest.status != "active":
        raise StaleManifestBindingError(
            f"manifest {manifest.agent_id}#{manifest.sequence} status="
            f"{manifest.status!r}; refusing to evaluate scope."
        )

    requested = set(vp.get("requested_scope", ()))
    granted = requested & set(manifest.capability_scope)
    return tuple(sorted(granted))
