import type { Decision } from "../decision.js"; import type { Refusal } from "../refusal.js"; import type { DivergenceClass, LegacyDecisionResult } from "./shadow.js"; export interface MetricsSink { /** Ledger hit / miss / record / latency. */ recordLedgerOp(op: LedgerOpEvent): void; /** Final Decision per intent kind. */ recordDecision(event: DecisionEvent): void; /** REFUSE Decisions, broken out by refusal.kind/code. */ recordRefusal(event: RefusalEvent): void; /** Audit sink failures (NATS, console, Postgres). */ recordSinkFailure(event: SinkFailureEvent): void; /** * Optional. Shadow-mode divergence (one of the four DivergenceClass * values). Optional so downstream consumers running always-on kernels * with no shadow path can omit the method without keeping a no-op stub. * Framework call sites use `?.()` and the shadow-telemetry wiring in * `setMetricsSink` no-ops when the method is absent. */ recordShadowDivergence?(event: ShadowDivergenceEvent): void; /** * Optional. Resource-limit events (parked-envelope quota exceeded, future * back-pressure events). Optional so adopters with hand-written * MetricsSink implementations don't need to update for back-compat — the * helper `recordResourceLimit` no-ops when the method is absent. */ recordResourceLimit?(event: ResourceLimitEvent): void; } export interface ResourceLimitEvent { /** "defer_quota" today; future kinds add to this union. */ readonly resource: "defer_quota"; /** Subject namespace, typically a session id. */ readonly subject: string; /** Max allowed within the window. */ readonly limit: number; /** Observed value that triggered the event. */ readonly observed: number; } export interface LedgerOpEvent { readonly op: "check" | "record"; readonly outcome: "hit" | "miss" | "ok" | "duplicate" | "error"; readonly intentKind: string; readonly latencyMs: number; /** * SHA-256 hex hash of the envelope. Observability-only — not part of the * hashed audit record. Enables cross-referencing a ledger hit/miss event * with its corresponding DecisionEvent, RefusalEvent, and AuditRecord in * dashboards and incident tooling (SecurityReviewer-015). */ readonly intentHash: string; } export interface DecisionEvent { readonly intentKind: string; readonly decision: Decision["kind"]; readonly latencyMs: number; readonly basisCount: number; /** Audit subject for cross-referencing the durable trail. */ readonly intentHash: string; } export interface RefusalEvent { readonly intentKind: string; readonly refusal: Refusal; readonly intentHash: string; } export interface SinkFailureEvent { /** * Well-known sink identifiers for `SinkFailureEvent.sink`: * - `"console"` — the built-in console sink (dev / fallback) * - `"nats"` — NATS JetStream audit sink * - `"postgres"` — Postgres audit sink * - `"multi"` — composite `multiSink` / `multiSinkLossy` wrapper * - `"buffered"` — `bufferedSink` wrapper (async fan-out with replay) * * Custom sinks may supply any string label; the union is widened to * string for forward-compatibility while the well-known values serve * as canonical labels for dashboards and alerts. */ readonly sink: "console" | "nats" | "postgres" | "multi" | "buffered" | (string & {}); readonly subject: string; readonly errorClass: string; readonly consecutiveFailures: number; } export interface ShadowDivergenceEvent { readonly intentKind: string; readonly divergence: DivergenceClass; readonly legacy: LegacyDecisionResult; readonly adjudicate: Decision; } export declare function setMetricsSink(sink: MetricsSink): void; /** * Has a MetricsSink been explicitly installed via setMetricsSink? * Used by `installPack` to decide whether to install a default console sink. */ export declare function hasMetricsSink(): boolean; /** @internal — for tests. */ export declare function _resetMetricsSink(): void; export declare function recordLedgerOp(event: LedgerOpEvent): void; export declare function recordDecision(event: DecisionEvent): void; export declare function recordRefusal(event: RefusalEvent): void; export declare function recordSinkFailure(event: SinkFailureEvent): void; /** * Resource-limit hook. No-ops gracefully when the installed MetricsSink does * not implement `recordResourceLimit`. New code should always call this * helper rather than the method directly. */ export declare function recordResourceLimit(event: ResourceLimitEvent): void; /** * Reference sink that logs to console. Production replaces this with a sink * that emits Sentry breadcrumbs and posts to the analytics pipeline. Useful * out-of-the-box: `setMetricsSink(createConsoleMetricsSink())` at boot gives * full operator visibility with no extra dependencies. * * ⚠️ PII WARNING (SecurityReviewer-016): this DEV-ONLY sink logs whole event * objects verbatim via `JSON.stringify`. Two fields are a session-id PII * proxy and are emitted in clear: `LedgerOpEvent`/`SinkFailureEvent`/ * `ResourceLimitEvent.subject` (documented as "typically a session id") and * `ResourceLimitEvent.subject` for quota events. `intentHash` is NOT PII (it * is a content digest, deliberately truncated to 8 chars on the decision/ * refusal lines). Do NOT ship this sink to a production log pipeline that * leaves logs at rest: a real MetricsSink MUST redact or hash `subject` * before egress (e.g. `hash(subject)` or a tenant-scoped pseudonym). Changing * the bytes emitted here is intentionally avoided — the metrics contract and * field set are unchanged; this is the documented convention every adopter * sink is expected to honour. See also the `subject` field docs above. */ export declare function createConsoleMetricsSink(): MetricsSink; //# sourceMappingURL=metrics.d.ts.map