# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/why-does-an-instance-attestation-chain-silently-truncate-at-the-first-cross-domain-delegation-hop.md
# Remnant Warrant:    https://corpusfetch.com/c/why-does-an-instance-attestation-chain-silently-truncate-at-the-first-cross-domain-delegation-hop.warrant.json
# Markdown SHA-256:   537b019e6dfece1d56156e369d93b54c48148736e11dbfc30781ba8d8e567c8a
# Code AST SHA-256:   17ad9a91f7afbae5aafbc6e30e4893e154d0a8571214ce620ade1d7e96008d2f
# Verified Classes:   ProvenanceTruncationError
# Defensive Guards:   3 fail-closed invariants (zero external deps)

class ProvenanceTruncationError(RuntimeError):
    """Raised when an AAIF attestation chain crosses a trust-domain boundary
    without a measurement-binding co-signature on the delegating link.

    A chain that raises this is NOT a provenance chain; it is a delegation
    chain with provenance decoration. Callers MUST NOT treat it as evidence
    that the terminal measurement constrains the root measurement.
    """


def validate_chain(links, require_provenance: bool = True):
    """Validate a parsed AAIF chain (list of link dicts, root-first).

    Each link: {"domain", "identity", "measurement", "binds_measurement",
                "signature_valid"}
    """
    if not links:
        raise ProvenanceTruncationError("empty chain")

    for i, link in enumerate(links):
        if not link.get("signature_valid"):
            raise ProvenanceTruncationError(
                f"link {i} signature invalid in domain {link.get('domain')!r}"
            )

        if i == 0:
            continue

        prev = links[i - 1]
        crosses_boundary = prev["domain"] != link["domain"]
        if not crosses_boundary:
            continue

        bound = link.get("binds_measurement")
        if bound != prev["measurement"]:
            if require_provenance:
                raise ProvenanceTruncationError(
                    f"unbound cross-domain hop at link {i}: "
                    f"{prev['domain']!r} -> {link['domain']!r}; "
                    f"expected binds_measurement={prev['measurement']!r}, "
                    f"got {bound!r}. Chain is delegation-only, not provenance."
                )
            # Degraded mode: caller has explicitly opted out of provenance.
            # The chain must be tagged so the gap is visible downstream.
            link["provenance"] = "truncated"

    return links
