The adapter emits a closed, value-free telemetry surface for its four signing
entry points (sign_report/3, sign_anchor/3, sign_grant/3,
sign_key_transition/3). The library does NOT attach a handler — a fresh
application sees nothing until it attaches one.
Events
| Event | Measurements | Metadata |
|---|---|---|
[:bounded_authority_report_adapter, :sign, :start] | %{count: 1} | %{object: object} |
[:bounded_authority_report_adapter, :sign, :stop] | %{duration: native_monotonic_delta} | %{object: object, result_class: result_class} |
duration is the monotonic time delta of the WHOLE signing span (entry point
to closed-atom return), in :erlang.monotonic_time/0 units — aggregate only,
never per-callback timing (a per-HSM-operation split would leak which custody
operation was slow or failing).
The closed axes
Objects (one per signing entry point):
| Object | Entry point |
|---|---|
:report | sign_report/3 |
:anchor | sign_anchor/3 |
:grant | sign_grant/3 |
:key_transition | sign_key_transition/3 |
Result classes (the classified outcome of a span):
| Class | Meaning | What to alert on |
|---|---|---|
:ok | The signing round-trip returned {:ok, _}. | Baseline; alert on rate DROPS. |
:invalid_input | The caller-supplied object failed validation (:invalid_report, :invalid_anchor, :invalid_grant, or :invalid_transition). | Rate = caller input quality. Not a custody problem. |
:invalid_key_handle | The key handle is malformed, or a handle callback (public_key/1, key_identity/1, signing_identity/1) rejected / returned an invalid value / exited. | Rate = handle wiring or custody-endpoint health. |
:signing_failed | sign/2 rejected, violated its contract, returned a non-64-byte signature, OR the signature did not verify against the resolved public key (wrong-key). | Rate = custody misconfiguration — the highest-priority signal this surface offers; a sustained nonzero rate means the handle is signing with the wrong key or failing outright. |
:producer_error | The protocol's producer or assembler rejected the signing input (bounds, field constraints). | Rate = caller input quality against the protocol contract. |
Error VALUES never ride along: {:producer_error, :invalid} is emitted as the
class :producer_error, full stop; a wrong-key failure is :signing_failed,
not the keys involved.
The value-free invariant (a named misuse)
Metadata carries exactly the two closed atoms above and NOTHING else — never
key ids, thumbprints, message bytes, report content, caller opts, or error
values. Adding a value-carrying field to an emission is a named misuse of
this surface, not an extension: the emitters (Telemetry.emit_start/1,
Telemetry.emit_stop/3) are shape-validated and refuse anything outside the
closed shapes with {:error, :telemetry_invalid} instead of emitting it. A
compacted JWS's bytes, a report's fields, and a key id are all caller material
the event stream must never carry — downstream sinks (logs, metrics labels)
are not trusted to redact.
Attaching a handler
# once at boot (e.g. your application's start/2):
:telemetry.attach_many(
"my-app-bara-sign-metrics",
[
[:bounded_authority_report_adapter, :sign, :start],
[:bounded_authority_report_adapter, :sign, :stop]
],
fn [:bounded_authority_report_adapter, :sign, phase], measurements, metadata, _config ->
case phase do
:start -> MyApp.Counter.inc(:sign_attempts, metadata.object)
:stop ->
MyApp.SignMetrics.record(
metadata.object,
metadata.result_class,
measurements.duration
)
end
end,
nil
)Or consume via a Telemetry.Metrics reporter:
summary("bounded_authority_report_adapter.sign.stop.duration",
unit: {:native, :millisecond},
tags: [:object, :result_class]
)Why no spans
There is deliberately NO :telemetry.span/3 usage and no span context in the
metadata: span contexts and exception reasons carry VALUES (messages, stacks,
caller terms), which is exactly what the value-free invariant forbids. A
two-event pair with closed-atom metadata gives the same timing signal without
a value channel. For the same reason the emitters never pass the signer's
error term into telemetry.
Telemetry never outranks the signature
Telemetry.sign_span/2 returns the signer's result unchanged. A failure
inside an emission is swallowed ({:error, :telemetry_invalid}); a raise
inside the SIGNER propagates — only the emission is guarded, never the crypto.
(Consequence for counters: a crashing signer leaves an unpaired :start — no
:stop is emitted after a raise. Treat start − stop accumulation as a
crash-rate signal, not a bug; the classes cannot classify a call that never
returned.)
If your handler raises
The :telemetry library (not the adapter) contains handler failures: :telemetry.execute/3
catches a raising handler, permanently detaches it, emits
[:telemetry, :handler, :failure], logs, and returns — the exception does NOT
reach the adapter's emitters. Operational consequence: a handler that raises
once silently stops receiving ALL subsequent sign telemetry — including the
:signing_failed custody-misconfiguration alarm this doc calls the
highest-priority signal. Monitor [:telemetry, :handler, :failure] wherever
you consume this surface, and make handlers total (never let report/label
formatting raise).