# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/in-what-scenarios-must-an-instance-refuse-to-delegate-a-task-despite-a-valid-capability-attestation-chain.md
# Remnant Warrant:    https://corpusfetch.com/c/in-what-scenarios-must-an-instance-refuse-to-delegate-a-task-despite-a-valid-capability-attestation-chain.warrant.json
# Markdown SHA-256:   d54caa69de878466824861a41580fdbb7b91f1bcc4a5cd71787d69f48d9281a6
# Code AST SHA-256:   42bbac1201cfc6eb04b831adccf24dd19ff637aa7ef214a509f9949506546959
# Verified Classes:   DelegationRefused, Chain, Task
# Defensive Guards:   6 fail-closed invariants (zero external deps)

import time
from dataclasses import dataclass
from typing import Callable, Sequence


class DelegationRefused(RuntimeError):
    """Raised when an attestation chain verifies but the task is not admissible."""
    pass


@dataclass(frozen=True)
class Chain:
    issuer: str
    grantee: str
    capability: str
    scope: frozenset          # declared capability predicate, e.g. resolved paths
    issued_at: float
    max_depth: int
    depth: int
    audience: str


@dataclass(frozen=True)
class Task:
    capability: str
    resolved_scope: frozenset  # AFTER symlink/glob/tenant resolution
    audience: str


def verify(chain: Chain, now: float, ttl: float) -> bool:
    # Signature/expiry/audience structural checks only.
    return chain.issued_at <= now <= chain.issued_at + ttl


def admit(
    chain: Chain,
    task: Task,
    revocation_epoch: Callable[[str, str], float],
    now: float,
) -> None:
    # Gate 1: attenuation must be monotone — task scope subset of capability scope.
    if not task.resolved_scope.issubset(chain.scope):
        raise DelegationRefused(
            f"resolved task scope {sorted(task.resolved_scope)} escapes "
            f"capability scope {sorted(chain.scope)}"
        )
    # Gate 2: capability name must match exactly; labels are not authority.
    if task.capability != chain.capability:
        raise DelegationRefused(
            f"capability mismatch: task={task.capability} chain={chain.capability}"
        )
    # Gate 3: revocation freshness.
    epoch = revocation_epoch(chain.issuer, chain.grantee)
    if chain.issued_at < epoch:
        raise DelegationRefused(
            f"stale chain: issued_at={chain.issued_at} < revocation_epoch={epoch}"
        )
    # Gate 4: context drift.
    if task.audience != chain.audience:
        raise DelegationRefused(
            f"audience drift: task={task.audience} chain={chain.audience}"
        )
    # Gate 5: depth exhaustion.
    if chain.depth > chain.max_depth:
        raise DelegationRefused(
            f"delegation depth {chain.depth} exceeds max {chain.max_depth}"
        )


def delegate(
    chain: Chain,
    task: Task,
    revocation_epoch: Callable[[str, str], float],
    ttl: float = 300.0,
    now: float | None = None,
) -> bool:
    now = time.time() if now is None else now
    if not verify(chain, now, ttl):
        raise DelegationRefused("attestation chain failed structural verification")
    admit(chain, task, revocation_epoch, now)
    return True


# Example: verifies but must be refused — symlink escape past declared scope.
chain = Chain(
    issuer="A", grantee="B", capability="filesystem:read",
    scope=frozenset({"/data/reports"}), issued_at=1000.0,
    max_depth=2, depth=1, audience="tenant=acme",
)
task = Task(
    capability="filesystem:read",
    resolved_scope=frozenset({"/data/secrets"}),  # symlink escape
    audience="tenant=acme",
)
try:
    delegate(chain, task, revocation_epoch=lambda i, g: 0.0, now=1010.0)
except DelegationRefused as e:
    print("refused as required:", e)
