# `observability/metrics` ## Purpose OpenTelemetry meter + instrument registry for `@suaveplan/crypto-rsa`. Stage 1 defines the full instrument set the proposal pins (so dashboards and SLOs can be authored ahead of Stages 2-4) and wires up the parse/serialise duration histograms and the typed-error counter — the only paths that have a hot production call site at this stage. The instruments are lazily constructed on first use to keep the module import-side-effect-free; this matters because some downstream consumers import the package without ever installing a `MeterProvider`, and constructing real instruments against the no-op global meter is harmless but unnecessary. ## Features - Lazy `meter()` and `instruments()` accessors — first access constructs, subsequent accesses reuse cached instruments. - `resetMetricsForTesting()` — drops the caches so a fresh `MeterProvider` installed by a test takes effect. - `recordParseDuration(format, ms)`, `recordSerializeDuration(format, ms)`, `recordError(code)` — production helpers wrapping the instrument calls. - `getInstrumentsForTesting()` — exposes the bag for test assertions. - Full instrument set (8 histograms + 6 counters) declared up-front so the metric names cannot drift between Stages. ## Usage: Basic Example ```ts import { recordError, recordParseDuration, } from "@suaveplan/crypto-rsa/observability"; const start = performance.now(); try { parse(); recordParseDuration("pkcs1", performance.now() - start); } catch (error) { recordError(error.code); throw error; } ``` ## Usage: Advanced Example ```ts import { getInstrumentsForTesting, resetMetricsForTesting, } from "@suaveplan/crypto-rsa/observability"; import { metrics } from "@opentelemetry/api"; import { MeterProvider } from "@opentelemetry/sdk-metrics"; resetMetricsForTesting(); metrics.setGlobalMeterProvider(new MeterProvider({ readers: [reader] })); const bag = getInstrumentsForTesting(); // bag.errorsTotal, bag.outcomeTotal, … ``` ## API Reference - `recordParseDuration(format: string, durationMs: number): void` - `recordSerializeDuration(format: string, durationMs: number): void` - `recordError(code: string): void` - `resetMetricsForTesting(): void` - `getInstrumentsForTesting(): InstrumentBag` The instrument bag carries (counters): `errorsTotal`, `outcomeTotal`, `engineSelectedTotal`, `fingerprintTotal`, `profileViolationTotal`, `blindingDisabledTotal`; (histograms): `verifyDurationMs`, `signDurationMs`, `encryptDurationMs`, `decryptDurationMs`, `keygenDurationMs`, `modpowDurationMs`, `parseDurationMs`, `serializeDurationMs`. ## Implementation Notes The instruments are declared even though most are not yet wired into production paths because the proposal pins them as the canonical observable surface; declaring them lets dashboards reference the metric names by build time, not by feature-arrival time, and prevents naming drift when Stages 2-4 wire them up. `resetMetricsForTesting` is the documented seam for swapping `MeterProvider`s under test. Production code should never call it.