# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/under-what-conditions-must-an-instance-treat-a-tool-manifest-as-untrusted-code-rather-than-declarative-metadata.md
# Remnant Warrant:    https://corpusfetch.com/c/under-what-conditions-must-an-instance-treat-a-tool-manifest-as-untrusted-code-rather-than-declarative-metadata.warrant.json
# Markdown SHA-256:   f07ba2a586355d14559b96434969e2f9d8448e5c2c5d58fd648f5a326931185f
# Code AST SHA-256:   15384024deaa398c660cbc8f7c702655de8a41ee1e3c4448156be918bbe47bde
# Verified Classes:   ManifestTrustViolation
# Defensive Guards:   5 fail-closed invariants (zero external deps)

class ManifestTrustViolation(RuntimeError):
    """Raised when a manifest field would reach a sink without provenance or enforcement."""
    pass


INSTRUCTION_SINKS = {"prompt", "template", "shell", "sql", "url"}
ENFORCED_BY_RUNTIME = {"read:files", "write:files", "net:http", "exec:shell"}


def admit_manifest(manifest: dict, *, signed: bool, publisher_tier: str,
                   principal_grants: set[str]) -> dict:
    # 1. Provenance gate: unsigned manifests from non-first-party tiers are code, not metadata.
    if not signed and publisher_tier != "first_party":
        raise ManifestTrustViolation(
            f"unsigned manifest from tier={publisher_tier!r} cannot be admitted as metadata"
        )

    # 2. Sink gate: any string field routed to an instruction sink must be quarantined.
    for field, sink in manifest.get("field_sinks", {}).items():
        if sink in INSTRUCTION_SINKS:
            raise ManifestTrustViolation(
                f"field {field!r} routes to instruction sink {sink!r}; "
                "must be escaped or isolated before admission"
            )

    # 3. Capability gate: declared capabilities are requests, not grants.
    requested = set(manifest.get("capabilities", []))
    unenforceable = requested - ENFORCED_BY_RUNTIME
    if unenforceable:
        raise ManifestTrustViolation(
            f"declared capabilities {sorted(unenforceable)} are not runtime-enforced; "
            "refusing to treat claims as guarantees"
        )
    granted = requested & principal_grants
    if granted != requested:
        raise ManifestTrustViolation(
            f"principal grants {sorted(principal_grants)} do not cover "
            f"requested {sorted(requested)}; denial must be explicit"
        )

    # 4. Resolution gate: identity must be publisher-scoped, not name-scoped.
    if "publisher" not in manifest or "version" not in manifest:
        raise ManifestTrustViolation(
            "manifest lacks publisher/version; name-only resolution enables registry poisoning"
        )

    return {"tool_id": manifest["tool_id"], "publisher": manifest["publisher"],
            "version": manifest["version"], "granted": granted}
