1. Treat sign_report/3 output as a cryptographic artifact, not an authorization decision. The envelope proves the holder signed THIS report under THAT grant — whether the action is allowed is the verifier's authority decision (consumer integration §8).
  2. The private key never enters this library. You hold it behind a {module, term} key handle implementing the BoundedAuthorityReportAdapter behaviour (sign/2, public_key/1, thumbprint/1, key_identity/1, signing_identity/1). If you find yourself passing key bytes INTO the adapter, the integration is wrong — see Getting started.
  3. sign_grant/3's role gate is declaration-rejection, NOT cryptographic role separation. A handle whose signing_identity/1 does not resolve {:issuer, _, _} is rejected with :invalid_key_handle BEFORE sign/2 is called — but a handle that consistently lies (returns :issuer while holding a holder key) passes the gate. Key-role separation is a custody property, not something this gate adds to. (ADR-0006/0007.)
  4. Never trust caller-supplied key_id or public_key content. sign_anchor/3 and sign_key_transition/3 resolve BOTH key identifiers from ONE atomic key_identity/1 snapshot on the handle — a caller-supplied :current_key_id is ignored. Keep it that way: a caller-named kid over a differently-signed anchor is the forgery that shape enables.
  5. Pin :issued_at (and :anchored_at) when the verifier's evaluation time is far from your wall clock. The default is System.system_time(:second) — replayed tests and offline flows against a pinned evaluation_time need the explicit option, or the proof falls outside proof_max_age.
  6. cast_arguments must be BAP's tagged Json.value() form, produced by BoundedAuthorityProtocol.V1.Json.decode/2 of the SAME raw bytes on BOTH sides. A raw map is rejected; feeding the two sides DIFFERENT bytes (or a re-encoding, instead of the original bytes) is the divergence this rule prevents — same bytes + same deterministic decode is byte-agreement by construction (consumer integration §3–§4).
  7. Consumer-side identity binding and nonce-ledger replay protection are obligations, not options. This library signs; the consuming verifier must bind the holder thumbprint to its own identity source and dedupe nonces (consumer integration §8/§9).
  8. Errors are closed atoms — there is no value-echoing. {:producer_error, :invalid} is exactly that tuple; key ids, message bytes, and report content never appear in an error. Do not wrap losses into logs by inspecting inputs on failure — the atoms are the whole story (Errors).
  9. A signature that does not verify against the resolved public key is :signing_failed — the wrong-key guard runs on EVERY object. If your handle signs with a different key than public_key/1 reports, you get a red, never a false success. Fix the handle, never the guard.
  10. Attach telemetry BEFORE the first production sign if you want the custody alarm: [:bounded_authority_report_adapter, :sign, :stop] with result_class: :signing_failed is the custody-misconfiguration signal. Metadata is value-free; never extend it with key material (Telemetry).
  11. Pin the protocol dependency and treat a version bump as a reviewed change — the dependency-direction wall pins the locked version, and a silent mix deps.update crosses an unreviewed protocol span (ADR-0010).
  12. Production handles never come from this library. The {pub, priv} reference handle in the source repo's test/support/ is TEST-ONLY and deliberately not shipped in the package — shipping it would pave the road to exactly the custody failure the separate key-handle contract exists to prevent (design C5, ADR-0014).

See Getting started, Errors, Telemetry, and Consumer integration for the long forms.