Skip to content

Proposes ADR-0006: telemetry and observability for the package - #24

Merged
johnnyt merged 1 commit into
mainfrom
enc-wpy-telemetry-adr
Aug 28, 2026
Merged

Proposes ADR-0006: telemetry and observability for the package#24
johnnyt merged 1 commit into
mainfrom
enc-wpy-telemetry-adr

Conversation

@johnnyt

@johnnyt johnnyt commented Aug 28, 2026

Copy link
Copy Markdown
Member

What this is

ADR-0006, at status proposed, answering ADR-0002's open question 3, which
declined to decide telemetry and asked for "a record of its own covering the
whole package". Design only: nothing is implemented, no dependency is added,
and acceptance is the operator's call as it was for the founding five.

The shape of the decision

Ten event names under [:encryptor, ...], defined once in an
Encryptor.Telemetry module with an events/0 that is the single definition
site:

  • four point events - [:encryptor, :vault, :started | :stopped | :start_refused] and [:encryptor, :cache, :recycled]
  • three operation spans - encrypt, decrypt, rekey
  • one nested provider span, which is what turns {:key_unavailable, _} into
    the metric the bead asks for

Naming rule: :start/:stop name a span pair and nothing else; every point
event is past tense.

The part that matters

Measurements are numbers and metadata is a closed allow-list. This is the
one place the record departs from the family precedent rather than inheriting
it. st-ADR-0040 lets the raw effect struct and datamodel values ride in
metadata for in-VM consumers and draws the redaction line at the bridge, which
is right there, where the worst case is a verbose span. It is not available
here, where the worst case is a data key in a log line.

So the record states what is never emitted, and why each one:

  • plaintext, in any form; size is the only thing said about it, on ADR-0004
    decision 12's argument that the ciphertext already discloses the length
  • any key material, mechanically - no descriptor and no Config is ever a
    metadata value, so there is no field for it to ride in
  • an encryption context value (a key name may appear inside a reason a
    caller already holds)
  • the raw :key selector - ADR-0004's acceptance amendment 1 exists because
    publishing the raw tenant identifier voided ADR-0003 decision 5's keying,
    and a metrics vendor is a worse place for it than a header
  • the partition id, which is the one a well-meaning implementation would
    get wrong: ADR-0001 decision 7 truthfully says it is not secret, and it is
    also an unkeyed SHA-256 of the selector, so it is confirmable by
    guess-and-confirm - ADR-0004 open question 1's own argument, applied to the
    other unkeyed derivation in the package
  • Encryptor.Error's :engine term, because a handler is not a log line

Shortest form of the rule, and the one for the moduledoc: no event carries a
per-tenant dimension, keyed or unkeyed.

ADR-0001 decision 10's oracle collapse holds in telemetry too, and harder: an
event reaches every attached handler whether or not anyone asked, so a
decrypt-side event that distinguished a context mismatch from a wrong key
would be a worse oracle than an error return. The two failures ADR-0002
decision 6 carved out - :unknown_key and :key_unavailable - stay distinct
here for the same reason they do there.

Written against the code on main, not the plan

Four of the ten events can be emitted against 92886bc today; the record says
plainly that the other six specify paths that do not exist yet (encrypt/2,
decrypt/2 and rekey/2 are undefined, and nothing in lib/ calls a
provider callback). Specifying ahead is deliberate - without the record, the
encrypt-path bead either emits nothing or invents a vocabulary.

It also declines to promise a cache hit rate. LocalCache's ETS table is
:private and the hit-or-miss decision is three frames deep in the engine,
which is the same fact ADR-0001 decision 6 gave as the reason the recycler
exists at all. That becomes a third upstream ask rather than a wrapper CMM
whose only job is to count.

Open questions worth a reader's attention

  1. Does the provider span count calls or cold misses? ADR-0002 decision 2
    says the materials cache "collapses provider round trips to one per
    partition per max_age"; ADR-0001 decision 2 says encrypt and decrypt
    "build the engine's keyring, CMM, and Client structs per call", and a
    keyring needs descriptors, which needs the provider. Those are not
    obviously compatible. The seam belongs to the encrypt path, so decision 8
    is deliberately neutral and stays correct either way - but it is a real
    tension between two accepted records and it is flagged, not resolved here.
  2. Whether size should exist at all (open question 5 in the record) is a
    disclosure judgement rather than a mechanism question, and it is the
    operator's: a length distribution over a column of short encrypted values
    is not nothing.

Seven in the record in total; the rest are narrower (the upstream hit-rate
ask, an optional dependency, an opt-in keyed dimension, whether
:start_refused is reachable in the case it is for, and whether anyone writes
a bridge).

Gate

Docs only - gate.rb reports applicable: false, carve_out_reason: "no changes under lib/, test/, config/, mix.exs, mix.lock - nothing for the gate to measure". No Elixir code changed, no dependency added, no changelog
fragment (the fragments README excludes ADRs).

Refs: enc-wpy

ADR-0006, at status proposed, answers ADR-0002's open question 3: it
fixes the package's telemetry surface before any of it is written, as
the repository's ADR-first rule requires.

Ten event names under [:encryptor, ...], defined once in an
Encryptor.Telemetry module, split into four point events and three
operation spans plus a provider span. Measurements are numbers and
metadata is a closed allow-list - the inversion of st-ADR-0040, which
can afford to let structs ride in metadata because its worst case is a
verbose span rather than a data key in a log line.

The record states what is never emitted and why each item: plaintext,
any key material, an encryption context value, the raw :key selector,
and the cache partition id, which ADR-0001 decision 7 correctly calls
non-secret but which is an unkeyed hash of the selector and so is
confirmable by guess-and-confirm. No event carries a per-tenant
dimension. ADR-0001 decision 10's oracle collapse holds in telemetry
too, and harder, because an event reaches every attached handler
rather than the caller who made the call.

Written against the code on main rather than the implementation graph:
four of the ten events can be emitted today, and the record says which
six specify paths that do not exist yet. It also declines to promise a
cache hit rate, because LocalCache's table is private and the decision
is three frames deep in the engine.

Seven open questions, including whether the provider span counts calls
or cold misses - a seam ADR-0001 decision 2 and ADR-0002 decision 2
describe differently, and one the encrypt path owns.

Refs: enc-wpy
@johnnyt
johnnyt merged commit 53d8687 into main Aug 28, 2026
1 check passed
@johnnyt
johnnyt deleted the enc-wpy-telemetry-adr branch August 28, 2026 00:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant