# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/which-invariants-prevent-signature-substitution-when-agent-skill-packages-are-re-signed-during-hot-reload.md
# Remnant Warrant:    https://corpusfetch.com/c/which-invariants-prevent-signature-substitution-when-agent-skill-packages-are-re-signed-during-hot-reload.warrant.json
# Markdown SHA-256:   e96baffcdc809e3ddc1388c598d8acda58e8e866c7228dae4a69cf9e7c1746d8
# Code AST SHA-256:   e891da9cc4f1bbd8a907c751a23b50a24358dc874608b1bd7e7280e12215dcc0
# Verified Classes:   ProvenanceBindingError, TrustAnchor
# Defensive Guards:   5 fail-closed invariants (zero external deps)

import hashlib
from dataclasses import dataclass


class ProvenanceBindingError(RuntimeError):
    """Raised when an artifact selects its own trust anchor or a reload
    changes the key fingerprint without authorization."""
    pass


@dataclass(frozen=True)
class TrustAnchor:
    publisher: str
    fingerprint: str  # hex sha256 over raw public key bytes


def fingerprint(public_key_bytes: bytes) -> str:
    return hashlib.sha256(public_key_bytes).hexdigest()


def resolve_anchor(publisher: str, manifest_key_id: str, trust_store: dict) -> TrustAnchor:
    # The manifest may NAME a key; it may not SELECT one. Resolution is by
    # publisher identity against the verifier-side store only.
    anchors = trust_store.get(publisher)
    if not anchors:
        raise ProvenanceBindingError(
            f"no trust anchor registered for publisher {publisher!r}"
        )
    for anchor in anchors:
        if anchor.fingerprint == manifest_key_id:
            return anchor
    raise ProvenanceBindingError(
        f"manifest key id {manifest_key_id!r} is not an authorized anchor "
        f"for publisher {publisher!r}; artifact cannot select its own key"
    )


def verify_and_activate(
    publisher: str,
    manifest_key_id: str,
    public_key_bytes: bytes,
    signature_valid: bool,
    digest: str,
    trust_store: dict,
    reload_cache: dict,
) -> str:
    anchor = resolve_anchor(publisher, manifest_key_id, trust_store)

    actual = fingerprint(public_key_bytes)
    if actual != anchor.fingerprint:
        raise ProvenanceBindingError(
            f"signature verified against key {actual} but anchor for "
            f"{publisher!r} is {anchor.fingerprint}; substitution detected"
        )

    cache_key = (publisher, anchor.fingerprint, digest)
    if cache_key in reload_cache:
        return reload_cache[cache_key]

    if not signature_valid:
        raise ProvenanceBindingError(
            f"signature invalid for {publisher!r} digest {digest}"
        )

    # Reload boundary: a prior entry for this (publisher, skill) with a
    # different fingerprint is a rotation, not a routine reload.
    prior = reload_cache.get(("fp", publisher))
    if prior is not None and prior != anchor.fingerprint:
        raise ProvenanceBindingError(
            f"reload changed key fingerprint for {publisher!r} from "
            f"{prior} to {anchor.fingerprint}; requires out-of-band rotation"
        )

    reload_cache[("fp", publisher)] = anchor.fingerprint
    reload_cache[cache_key] = digest
    return digest
