# Bounded Authority Report Adapter v0.5.0 - Table of Contents

> Holder-side companion signer for the Bounded Authority Protocol — signs protocol objects (holder proofs, boundary anchors, grants, key transitions) through a local key handle over the protocol's deterministic signing inputs. The private key never enters the library.

## Pages

- [Bounded Authority Report Adapter](readme.md)
- [Usage rules](usage-rules.md)
- [Changelog](changelog.md)
- [Contributor Covenant Code of Conduct](code_of_conduct.md)
- [Contributing](contributing.md)
- [LICENSE](license.md)
- [NOTICE](notice.md)
- [Security policy](security-1.md)
- [Consuming an envelope from this adapter](consumer-integration.md)
- [Errors](errors.md)
- [Getting started](getting-started.md)
- [Recipes](recipes.md)
- [Security model](security-2.md)
- [Sign telemetry](telemetry.md)
- [Upgrading](upgrading.md)

## Modules

- [AnchorCapturingKeyHandle](AnchorCapturingKeyHandle.md): Captures the message handed to sign/2 — the no-canonical-bytes-fork tripwire
for sign_anchor/3. Implements key_identity/1 so sign_anchor reaches sign/2.

- [AnchorExitingKeyHandle](AnchorExitingKeyHandle.md): key_identity/1 calls exit/1 (a simulated HSM/key-server timeout). safe_callback's
catch clause must contain it -> :invalid_key_handle, not a crash.

- [AnchorFailingKeyHandle](AnchorFailingKeyHandle.md): key_identity/1 + public_key/1 succeed; sign/2 returns {:error, _} -> :signing_failed.

- [AnchorWrongKeyHandle](AnchorWrongKeyHandle.md): key_identity/1 + public_key/1 return key A's material, but sign/2 signs with a
DIFFERENT key B (a rotation/misconfiguration race). The verify_signature
guard in the shared signing tail must reject this -> :signing_failed.

- [BadContractHandle](BadContractHandle.md): A key-handle whose sign/2 violates the {:ok,_}|{:error,_} contract (returns :ok).
Cross-vendor closeout finding: sign_via_handle/2 was non-total; a non-tuple
return raised CaseClauseError. The catch-all now maps it to :signing_failed.

- [BoundedAuthorityReportAdapter](BoundedAuthorityReportAdapter.md): Universal companion signer to `BoundedAuthorityProtocol`.
- [BoundedAuthorityReportAdapter.Conformance.LocalProfileCase](BoundedAuthorityReportAdapter.Conformance.LocalProfileCase.md): TEST-ONLY loader for BAP's packaged local-loopback profile corpus (0.3.0).
- [BoundedAuthorityReportAdapter.Conformance.Tag](BoundedAuthorityReportAdapter.Conformance.Tag.md): TEST-ONLY translator: the vector's typed-JSON array form to BAP's tagged
`Json.value()` tuple form (design §1.6 A).
- [BoundedAuthorityReportAdapter.Conformance.VectorCase](BoundedAuthorityReportAdapter.Conformance.VectorCase.md): TEST-ONLY loader + builder for RA2's conformance round-trip harness.
- [BoundedAuthorityReportAdapter.Keys.RawKey](BoundedAuthorityReportAdapter.Keys.RawKey.md): TEST-ONLY reference implementation of the `BoundedAuthorityReportAdapter`
key-handle behaviour.
- [BoundedAuthorityReportAdapter.Telemetry](BoundedAuthorityReportAdapter.Telemetry.md): The closed, value-free telemetry surface for the five signing entry points.
- [BoundedAuthorityReportAdapter.TestHandles](BoundedAuthorityReportAdapter.TestHandles.md): Test-only key-handle modules for the sign_report (RA1) + sign_anchor (RA4) suites.
- [BoundedAuthorityReportAdapter.TestKeys](BoundedAuthorityReportAdapter.TestKeys.md): TEST-ONLY keypair fixtures + an issuer-side grant-signing helper for RA1's
round-trip test.
- [CapturingKeyHandle](CapturingKeyHandle.md): Captures the message handed to sign/2 — the no-canonical-bytes-fork tripwire.
- [CountingKeyHandle](CountingKeyHandle.md): Counts sign/2 calls — the C1 tripwire (asserts exactly one signing call).
- [ExitingKeyHandle](ExitingKeyHandle.md): A key-handle whose public_key/1 calls exit/1 (simulating a GenServer.call
timeout in a production HSM/key-server callback). Cross-vendor round 2
blocking finding: rescue did not catch exits, so this crashed the caller.
The catch clauses now contain it -> :invalid_key_handle.

- [FailingKeyHandle](FailingKeyHandle.md): A key-handle whose sign/2 always fails — exercises the :signing_failed path.
- [GrantCapturingKeyHandle](GrantCapturingKeyHandle.md): Captures the sign/2 message — the no-canonical-bytes-fork tripwire for sign_grant.
- [GrantExitingHandle](GrantExitingHandle.md): signing_identity/1 calls exit/1 (simulated HSM/key-server timeout). safe_callback's
catch clause must contain it -> :invalid_key_handle, not a crash.

- [GrantFailingKeyHandle](GrantFailingKeyHandle.md): signing_identity/1 + public_key/1 succeed; sign/2 -> {:error, _} -> :signing_failed.
- [GrantHolderCountingHandle](GrantHolderCountingHandle.md): The C1 tripwire for sign_grant: signing_identity/1 -> {:holder, ...}, and sign/2
counts calls. sign_grant MUST reject a :holder handle BEFORE sign/2, so
sign_call_count/0 stays 0. (The mirror of sign_report's CountingKeyHandle, which
asserts sign/2 is called exactly once — pointed the other way.)

- [GrantIssuerHandle](GrantIssuerHandle.md): The issuer-role handle for sign_grant tests: signing_identity/1 -> {:issuer, 'issuer-2026-07', pub}.
- [GrantRacingIdentityHandle](GrantRacingIdentityHandle.md): The atomic-snapshot drift tripwire for sign_grant (the design-adversarial TOCTOU fix).
signing_identity/1 returns a consistent {:issuer, kid_a, pub_a} snapshot, then flips
internal state so sign/2 signs with priv_b (a simulated post-snapshot rotation). The
atomic snapshot means role+kid+pub cannot drift apart; the verify_signature guard
catches the sign/2-vs-snapshot mismatch -> :signing_failed, never a silent false-success.
The handle term is an Agent pid whose state is %{kid:, pub_a:, priv_a:, priv_b:, rotated:}.

- [GrantRolelessHandle](GrantRolelessHandle.md): The roleless tripwire: implements sign/2 + public_key/1 + thumbprint/1 +
key_identity/1 but OMITS signing_identity/1. sign_grant must reject it as
:invalid_key_handle (the UndefinedFunctionError from apply/3 is caught by
safe_callback). key_identity/1 is included to prove the rejection is
role-specific (signing_identity absent), not key-identity-specific.

- [GrantWrongKeyHandle](GrantWrongKeyHandle.md): signing_identity/1 returns the handle's pub A; sign/2 signs with a DIFFERENT key B.
The verify_signature guard in the shared tail catches the sign/2-vs-snapshot mismatch
-> :signing_failed.

- [RacingKeyIdentityHandle](RacingKeyIdentityHandle.md): Defense-in-depth tripwire for the atomic `key_identity/1` snapshot.
`key_identity/1` returns a consistent `{key_id, pub_a}` snapshot, then flips
internal state so `sign/2` signs with key-b (a simulated post-snapshot
rotation). The atomic snapshot means `key_id`+`public_key` cannot drift apart;
the `verify_signature` guard catches the `sign/2`-vs-snapshot mismatch ->
`:signing_failed`. This is the rotation race a cross-vendor (Codex) probe
exploited under the separate-callback design, now caught at sign time.
- [ShortKeyHandle](ShortKeyHandle.md): A key-handle whose public_key/1 returns a short (non-32-byte) key.
Cross-vendor closeout finding: a short key passed the is_binary guard then
failed downstream as {:producer_error, :invalid}. The guard now requires 32
bytes, so this fails fast as :invalid_key_handle.

- [TransitionCapturingKeyHandle](TransitionCapturingKeyHandle.md): Captures the message handed to sign/2 — the no-canonical-bytes-fork tripwire for
sign_key_transition/3. Implements key_identity/1 so sign_key_transition reaches sign/2.

- [TransitionExitingKeyHandle](TransitionExitingKeyHandle.md): key_identity/1 calls exit/1 (a simulated HSM/key-server timeout). safe_callback's
catch clause must contain it -> :invalid_key_handle, not a crash (mirror of
AnchorExitingKeyHandle).

- [TransitionRacingKeyIdentityHandle](TransitionRacingKeyIdentityHandle.md): Defense-in-depth tripwire for the atomic `key_identity/1` snapshot (the transition
analogue of RacingKeyIdentityHandle). `key_identity/1` returns a consistent
`{key_id, pub_a}` snapshot, then flips internal state so `sign/2` signs with priv_b
(a simulated post-snapshot rotation). The atomic snapshot means key_id+public_key
cannot drift apart; the `verify_signature` guard catches the sign/2-vs-snapshot
mismatch -> `:signing_failed`.
- [TransitionWrongKeyHandle](TransitionWrongKeyHandle.md): key_identity/1 returns the handle's pub A; sign/2 signs with a DIFFERENT key B (a
rotation/misconfiguration race). The verify_signature guard in the shared tail must
reject this -> :signing_failed (the mirror of AnchorWrongKeyHandle).

- [WrongKeyHandle](WrongKeyHandle.md): A key-handle whose public_key/1 returns key A but whose sign/2 signs with a
DIFFERENT key B (a rotation/misconfiguration race). Cross-vendor round 2
should-fix finding: the adapter validated only 64-byte length, not that the
signature verifies against the resolved public key. The adapter now verifies
the signature against the public key -> :signing_failed.

## Mix Tasks

- [mix bounded_authority_report_adapter.doctor](Mix.Tasks.BoundedAuthorityReportAdapter.Doctor.md): Preflight-checks a key-handle module against the adapter's behaviour contract.
- [mix bounded_authority_report_adapter.install](Mix.Tasks.BoundedAuthorityReportAdapter.Install.md): Scaffolds a starter key-handle module — the holder-side integration point.

