# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/where-must-an-instance-prove-its-own-sub-tree-before-it-may-re-delegate-attestation-depth-vs-delegation-breadth-in-agent-spawn-trees.md
# Remnant Warrant:    https://corpusfetch.com/c/where-must-an-instance-prove-its-own-sub-tree-before-it-may-re-delegate-attestation-depth-vs-delegation-breadth-in-agent-spawn-trees.warrant.json
# Markdown SHA-256:   e2a2819b72b23df153cfbc10ebe4e646b4d70b67bae29398a00cc1e1930f058d
# Code AST SHA-256:   ba882d88f8cc4ba14ab540704fcc9bf4015b926b3fbc4eb403baf18868fe5f5c
# Verified Classes:   AttestationChainError
# Defensive Guards:   4 fail-closed invariants (zero external deps)

class AttestationChainError(RuntimeError):
    """Raised when a delegated credential's authority cannot be justified by its issuer chain."""
    pass


def issue_child_credential(parent_cred, requested_scope, root_key):
    # Rule 1: issuance must be an explicit grant, not inferred from validity.
    if "may_delegate" not in parent_cred.get("rights", ()):
        raise AttestationChainError(
            f"credential {parent_cred['subject']} lacks may_delegate; "
            "it may present attestation but may not issue it"
        )

    # Rule 2: scope must be monotonically narrowed at issuance time.
    parent_scope = set(parent_cred["scope"])
    if not set(requested_scope).issubset(parent_scope):
        widened = set(requested_scope) - parent_scope
        raise AttestationChainError(
            f"scope escalation at issuance: {sorted(widened)} not held by "
            f"{parent_cred['subject']}"
        )

    # Rule 3: embed the full issuer chain so verifiers can reconstruct it.
    chain = list(parent_cred.get("chain", ())) + [parent_cred["subject"]]

    return {
        "subject": requested_scope_owner(requested_scope),
        "scope": sorted(requested_scope),
        "rights": (),  # delegation right is not inherited by default
        "issuer": parent_cred["subject"],
        "chain": chain,
        "signature": root_key.sign(chain, requested_scope),
    }


def verify_credential(cred, revoked_subjects, root_key):
    # Rule 4: revocation is subtree-aware — reject if any ancestor is revoked.
    for ancestor in cred.get("chain", ()):
        if ancestor in revoked_subjects:
            raise AttestationChainError(
                f"credential {cred['subject']} descends from revoked node {ancestor}"
            )
    if not root_key.verify(cred["signature"], cred["chain"], cred["scope"]):
        raise AttestationChainError(f"bad signature on {cred['subject']}")
    return True


def requested_scope_owner(scope):
    return scope.get("subject", "unnamed-child")
