Every entry point returns either {:ok, map} or {:error, reason} where reason is a closed atom (or one fixed tuple). There is no value-echoing: no key ids, no message bytes, no report content ever appear in an error — an atom is the whole story.

The four error sets are identical except for their per-object input atom:

Entry pointInput atom@type
sign_report/3:invalid_reportsign_error/0
sign_anchor/3:invalid_anchoranchor_sign_error/0
sign_grant/3:invalid_grantgrant_sign_error/0
sign_key_transition/3:invalid_transitiontransition_sign_error/0

The shared atoms

AtomMeaningWhat to checkRecovery
:invalid_key_handleThe handle is malformed ({module, term} shape, defined module), or a handle callback (public_key/1, key_identity/1, signing_identity/1) rejected, returned an invalid value (e.g. a non-32-byte public key), or exited/raised. For sign_grant/3 this ALSO covers the C1 role gate: a handle that does not resolve {:issuer, _, _}.The handle module implements the full BoundedAuthorityReportAdapter behaviour; the handle term is what its callbacks expect; (grants) the handle really is the issuer's.Fix the handle wiring. This is never caller-input — it is your key custody configuration.
:signing_failedThe sign/2 callback rejected, violated its {:ok, binary} | {:error, term} contract, returned a non-64-byte signature, OR the signature did not verify against the resolved public key (the wrong-key guard).Whether public_key/1/key_identity/1 describe the SAME key sign/2 actually uses; whether the HSM/KMS call is healthy.Fix the custody side. A sustained rate of this is the custody-misconfiguration alarm — see Telemetry.
{:producer_error, :invalid}The protocol's producer or assembler rejected the signing input — a bound or field constraint (bad URI shapes, out-of-bounds sizes, a self-transition, a non-Json.value() cast_arguments).The input map against the entry point's @type and the protocol's V1 field contracts; cast_arguments is the tagged form from V1.Json.decode/2.Fix the caller's input.

The per-object input atoms

The adapter's input validation is SHAPE-only (required fields present, right basic types). SEMANTIC constraints — digest sizes, time-window ordering, thumbprint width, selector shapes — are enforced by the protocol's producer and surface as {:producer_error, :invalid} (see that row above). Do not debug a wrong-size or semantically-invalid field against these rows.

AtomMeaningWhat to checkRecovery
:invalid_reportA required report field is missing or of the wrong basic type (grant_compact, operation, method, target_uri, invocation_id, cast_arguments, nonce), or cast_arguments is nil.The map against report() in the moduledoc.Fix the report fields.
:invalid_anchorAn anchor_input content field is missing, or chain_hash is not a binary.anchor_id, chain_id, sequence, chain_hash presence and basic types.Fix the anchor fields.
:invalid_grantA grant_input field is missing or of the wrong basic type.issuer, grant_id, audiences, issued_at/not_before/expires_at presence and types.Fix the grant fields.
:invalid_transitionA transition_input field is missing, or next_public_key is not a 32-byte Ed25519 key (adapter-checked).transition_id, chain_id, effective_at, next_key_id, next_public_key.Fix the transition fields.

The {:producer_error, :invalid} row covers, among others: a chain_hash that is not 32 bytes or a zero hash at a nonzero sequence; an inverted grant time window; a holder_thumbprint that is not a raw 32-byte thumbprint; a non-tagged cast_arguments. All pass the adapter's shape checks and are rejected by the protocol producer.

Where the atoms live

Each set is a @type on BoundedAuthorityReportAdapter (sign_error/0, anchor_sign_error/0, grant_sign_error/0, transition_sign_error/0) — dialyze your caller against them; this table is authored against those types.