Error Codes

August 23, 2026 ยท View on GitHub

Every failure the cA2A runtime and verifier raise is a subclass of CA2AError. Each subclass carries a stable code string and an http_status. The code is what you match on in tests and callers. The HTTP status is what a service should return when the error crosses an A2A boundary. Both are defined in ca2a_runtime/errors.py and are the authoritative values below.

An error also carries a human-readable message and an optional detail. The message and detail are not stable and are for diagnostics only. Match on code, never on message text.

Registry

ClasscodeHTTPWhen raised
CA2AErrorCA2A_ERROR500Base class for all cA2A runtime and verifier errors. Not raised directly; caught to handle any cA2A failure generically.
ConfigErrorCONFIG_ERROR500Ca2aConfig construction or verify_chain_file config load failed: unknown field, max_delegation_depth not a positive integer, missing config file, invalid YAML, or a non-mapping config root.
InvalidCredentialINVALID_CREDENTIAL400A DelegationCredential is malformed or its Ed25519 signature does not verify: unsigned credential, bad signature, malformed fields, or a chain document that is not a list or {"chain": [...]}, a missing chain file, or invalid JSON.
UntrustedDelegationRootUNTRUSTED_DELEGATION_ROOT403A chain is internally valid, but its root issuer is not pinned in the callee's trusted_root_issuers. Runtime authorization checks this before policy evaluation.
ScopeEscalationSCOPE_ESCALATION403A child grant claims authority its parent did not hold. Raised by verify_chain when a hop's scope is not a subset of its parent's scope.
BrokenDelegationLinkBROKEN_DELEGATION_LINK409A hop does not chain to its stated parent, or continuity is broken: empty chain, a root credential that names a parent or has nonzero depth, a hop whose parent link or subject does not match the previous hop, or a hop depth that is not previous + 1.
DelegationDepthExceededDELEGATION_DEPTH_EXCEEDED403A chain is longer than the configured max_delegation_depth. Raised by verify_chain.
CredentialReplayCREDENTIAL_REPLAY409A credential_id appears more than once in a single chain. Raised by verify_chain.
CredentialNotYetValidCREDENTIAL_NOT_YET_VALID403A hop's not_before bound is after the evaluation time. The chain is well formed and validly signed, but the grant is not yet in force. Raised by verify_chain.
CredentialExpiredCREDENTIAL_EXPIRED403A hop's not_after bound is before the evaluation time. Raised by verify_chain.
HolderProofInvalidHOLDER_PROOF_INVALID401The presenter of a delegation chain did not prove it controls the leaf subject: no proof was presented, the proof was malformed, it answered a challenge this callee did not issue or which has expired, or its signature did not verify over the exact request being made. 401 rather than 403 because the chain may well carry the authority requested while the caller has not shown it is the party that authority was delegated to. Distinct from ATTESTATION_FAILED, which is about what the caller is running: a caller can appraise perfectly and still fail this. Raised by verify_holder_proof, handle_peer_request, and the A2A adapter on a malformed proof. See profile P-4a.
AttestationUnsupportedATTESTATION_UNSUPPORTED500An attestation provider was requested that the host cannot supply. Raised by any provider's attest when the host lacks what its collector needs, and by OpaqueProvider, which has no collector. The detail names the missing piece. See Peer Attestation.
AttestationFailedATTESTATION_FAILED412Attestation evidence was present but did not verify. Raised by the SEV-SNP verifier on a malformed report, an untrusted or broken certificate chain, a bad report signature, or a measurement / report-data mismatch. See Peer Attestation.
SealedChannelErrorSEALED_CHANNEL_ERROR500The sealed peer channel could not construct or open a payload: an invalid peer public key, a malformed or unsupported sealed blob, a wrong key, or a tampered ciphertext (AEAD authentication failure). Fails closed; never returns unauthenticated plaintext. See Sealed Channel.
ProvenanceLinkBrokenPROVENANCE_LINK_BROKEN409A DelegationRecord does not chain to its stated parent record, or a record was tampered with so its hash no longer matches a child's link: empty provenance chain, duplicate record_id, a root record that references a parent, a broken parent hash link, or a record whose credential_id or subject does not match the chain. Raised by verify_dag and cross_check_chain.
ScopeNotPermittedSCOPE_NOT_PERMITTED403A requested capability is not in the effective scope (the delegated leaf scope intersected with the callee's local policy). Raised by enforce_peer_call.
TraceDigestUnsupportedTRACE_DIGEST_UNSUPPORTED501A delegation.parent_record_hash names a digest algorithm this verifier does not compute. The TRACE schema permits sha256: and sha384:; trace_record_hash computes LINK_DIGEST. Such a link is well formed and may be correct, so the chain is unverifiable at that hop rather than invalid, and is reported separately from PROVENANCE_LINK_BROKEN so an audit record does not carry a tampering finding that no tampering produced. Raised by verify_trace_dag, per hop.
TransportErrorTRANSPORT_ERROR400cA2A A2A-extension metadata was present but malformed or incomplete (missing delegation_chain, bad hop shape, non-base64url sealed_payload, etc.). Raised by ca2a_runtime.transport.parse_peer_request. Absence of all cA2A keys is not an error: that message is ordinary A2A input.

Which errors are live today

ConfigError, InvalidCredential, ScopeEscalation, BrokenDelegationLink, DelegationDepthExceeded, CredentialReplay, CredentialNotYetValid, CredentialExpired, , ProvenanceLinkBroken, and TraceDigestUnsupported are raised by shipping code paths: attenuated delegation, offline chain verification, and the provenance DAG. ScopeNotPermitted is raised by the peer-call enforcement decision core (enforce_peer_call), and SealedChannelError by the sealed channel (SealedChannel.seal, open_sealed), both of which are implemented. TransportError is raised by the A2A metadata adapter when cA2A keys are present but cannot be parsed into a PeerRequest.

AttestationFailed is raised by the SEV-SNP verifier (chain, report signature, and measurement binding), and by a collector whose hardware returned evidence that does not commit the key and nonce it asked for. AttestationUnsupported is raised where a host cannot collect at all: no TPM or tpm2-pytss for tpm, no configfs-TSM or guest device for sev-snp and tdx, and on Azure confidential VMs, where SEV-SNP runs behind a paravisor that owns REPORT_DATA. See Peer Attestation and ROADMAP.md.

Handling errors

Catch the base class to handle any cA2A failure, or a specific subclass to react to one condition. The code attribute gives you the stable identifier and http_status the status to surface.

from ca2a_runtime.errors import CA2AError, ScopeEscalation
from ca2a_verify.verify import verify_chain_file

try:
    verify_chain_file(
        "chain.json", trusted_root_issuers={"<trusted-root-issuer-hex>"}
    )
except ScopeEscalation as exc:
    # A hop claimed more than its parent granted.
    print(exc.code, exc.http_status)  # SCOPE_ESCALATION 403
except CA2AError as exc:
    # Any other cA2A failure.
    print(exc.code, exc.http_status, exc.detail)

Verification fails closed. verify_chain, verify_dag, and cross_check_chain raise the first error they find rather than returning a partial result, so a caught CA2AError means the chain or DAG was rejected.

See also

  • Delegation Chain for the checks behind ScopeEscalation, BrokenDelegationLink, DelegationDepthExceeded, CredentialReplay, CredentialNotYetValid, and CredentialExpired.
  • Provenance DAG for the checks behind ProvenanceLinkBroken.
  • Verification Library for verify_chain, verify_chain_file, verify_dag, and cross_check_chain.
  • Failure Modes for how these errors map to observable runtime behavior.