# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/under-what-conditions-must-an-instance-reject-a-skill-registration-whose-manifest-hash-matches-but-whose-transitive-capability-closure-exceeds-the-dec.md
# Remnant Warrant:    https://corpusfetch.com/c/under-what-conditions-must-an-instance-reject-a-skill-registration-whose-manifest-hash-matches-but-whose-transitive-capability-closure-exceeds-the-dec.warrant.json
# Markdown SHA-256:   960a3650549676aa60b4674869eab392b10d52b7fc331206a34a7bc95c39a6df
# Code AST SHA-256:   804feabe2d08c28ebdc893d39f26c82e87f19288005c2372b5446bb07c390090
# Verified Classes:   CapabilityClosureViolation
# Defensive Guards:   2 fail-closed invariants (zero external deps)

class CapabilityClosureViolation(RuntimeError):
    """Raised when a skill's resolved transitive capability closure exceeds its signed declaration."""


def admit_skill(manifest: dict, resolved_closure: set[str]) -> None:
    """
    manifest: {"name": str, "hash": str, "declared": set[str], "edges": list[str]}
    resolved_closure: capabilities reachable after dependency resolution at load time.

    Invariant: closure must equal the signed declared set. A superset is a
    skill-injection surface; a subset is over-permissioning and only logged.
    """
    declared = set(manifest["declared"])
    excess = resolved_closure - declared

    if excess:
        # Do not warn. Do not re-review. Fail and attribute the edge.
        raise CapabilityClosureViolation(
            f"skill {manifest['name']!r} (hash {manifest['hash']}) "
            f"resolved capabilities {sorted(excess)} outside declared {sorted(declared)}; "
            f"resolved edges: {manifest.get('edges', [])}. "
            f"Re-sign a manifest that pins the offending edge or declares the capability."
        )

    missing = declared - resolved_closure
    if missing:
        # Over-permissioning: lint, not a security fault.
        manifest.setdefault("_lint", []).append(
            f"declared-but-unreachable: {sorted(missing)}"
        )


def load_from_registry(registry, name: str, version: str) -> dict:
    entry = registry.fetch(name, version)
    if not registry.verify_hash(entry):
        raise CapabilityClosureViolation(f"manifest hash mismatch for {name}@{version}")
    closure = registry.resolve_closure(entry)  # walks transitive edges
    admit_skill(entry, closure)
    return entry
