@agenticprimitives/audit 0.1.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Agentic Trust Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,49 @@
1
+ # @agenticprimitives/audit
2
+
3
+ Append-only audit-event schema + sink interface for the agentic primitives stack. Transport-agnostic primitive — consumers wire concrete persistent sinks (D1, Postgres, Cloud Logging, SIEM) behind a single interface.
4
+
5
+ Closes system-audit finding **C3** (forensic trail) from `docs/architecture/product-readiness-audit.md`.
6
+
7
+ See [`spec.md`](./spec.md) → [`specs/206-audit.md`](../../specs/206-audit.md).
8
+
9
+ ## Quick start
10
+
11
+ ```ts
12
+ import {
13
+ buildEvent,
14
+ createConsoleAuditSink,
15
+ composeSinks,
16
+ type AuditSink,
17
+ } from '@agenticprimitives/audit';
18
+
19
+ const sink: AuditSink = composeSinks(
20
+ createConsoleAuditSink({ prefix: '[AUDIT]' }),
21
+ // ...add a persistent sink from your app layer (D1, Postgres, etc.)
22
+ );
23
+
24
+ await sink.write(
25
+ buildEvent({
26
+ action: 'delegation.verify.accept',
27
+ outcome: 'success',
28
+ actor: { type: 'agent', id: 'demo-a2a' },
29
+ subject: { type: 'jti', id: claims.jti },
30
+ audience: claims.aud,
31
+ correlationId,
32
+ }),
33
+ );
34
+ ```
35
+
36
+ ## Boundaries
37
+
38
+ - Ships **in-band sinks only** (console + memory + composeSinks). Concrete persistent sinks (D1, Postgres, Cloud Logging) live in consumer apps.
39
+ - Sits at the **base of the dep graph** alongside `@agenticprimitives/types`. Forbidden from importing connect-auth / agent-account / delegation / key-custody / tool-policy / mcp-runtime — those import us.
40
+
41
+ ## Invariants
42
+
43
+ - **No secret material in events.** Emitters hash sessionIds, omit raw keys / tokens. `createPiiGuardrailSink` (v0.1) is defense-in-depth.
44
+ - **Append-only by interface.** `AuditSink` has `write` only — no `delete` / `update`. Persistent sinks must enforce at the schema level.
45
+ - **Fail-soft for the trail.** Emit failures never throw to the caller. The decision (verify, sign) is the security boundary.
46
+
47
+ ## Status
48
+
49
+ Pre-alpha. Schema versioned (`schemaVersion: 1`); bumps are major.
@@ -0,0 +1,347 @@
1
+ /**
2
+ * @agenticprimitives/audit — append-only audit/forensics primitives.
3
+ *
4
+ * Owns:
5
+ * - The canonical `AuditEvent` schema every other package emits.
6
+ * - The `AuditSink` interface a consumer implements (or composes from
7
+ * one of the provided sinks).
8
+ * - In-band sinks: `createConsoleAuditSink`, `createMemoryAuditSink`.
9
+ *
10
+ * Does NOT own:
11
+ * - Concrete persistence backends (D1 / Cloud Logging / Splunk / etc.).
12
+ * Those live with the consumer that needs them.
13
+ * - Domain semantics — `action` is a free-string the emitter chooses
14
+ * (e.g. `<package>.<surface>.<outcome>`).
15
+ *
16
+ * Doctrine (per CLAUDE.md): zero domain knowledge. The schema is
17
+ * deliberately structural — every package's emitter knows what to put
18
+ * in `subject` / `context`; this package only stamps the shape.
19
+ *
20
+ * Closes system audit finding C3.
21
+ */
22
+ import type { Hex } from '@agenticprimitives/types';
23
+ /**
24
+ * One audit event. Designed to be JSON-serialisable for sinks that
25
+ * persist or forward over HTTP. All fields are strings or numbers so
26
+ * the on-wire shape matches the in-memory shape with no codec needed.
27
+ *
28
+ * `id` is per-event (UUID-ish); `correlationId` ties multiple events
29
+ * to a single request / flow / session.
30
+ */
31
+ /**
32
+ * H7-F.2 / PKG-audit-002 / EXT-037 closure — canonical action registry.
33
+ *
34
+ * Free-string `action` previously let each emitter pick its own name
35
+ * with no central catalog — drift hazard the moment more than one
36
+ * package emits the same conceptual event. The registry below is the
37
+ * canonical list. `action: AuditAction` keeps the string convention
38
+ * (still indexes cleanly in structured-log backends) but type-checks
39
+ * the caller against the registry.
40
+ *
41
+ * Convention: `<package>.<surface>.<outcome?>`.
42
+ *
43
+ * Each emitting package documents WHICH of these names IT emits in
44
+ * its `AUDIT.md` "Audit events emitted" section.
45
+ *
46
+ * To add a new action:
47
+ * 1. Add the string to `AUDIT_ACTION_REGISTRY` (below).
48
+ * 2. Add it to the owning package's `AUDIT.md` list.
49
+ * 3. The TypeScript union `AuditAction` is derived automatically.
50
+ *
51
+ * `action` STILL accepts a raw `string` (the legacy escape hatch) so a
52
+ * package can ship a one-off event without churn. Production paths
53
+ * should use the typed name + extend the registry.
54
+ */
55
+ export declare const AUDIT_ACTION_REGISTRY: readonly ["delegation.mint", "delegation.verify.accept", "delegation.verify.reject", "delegation.revoke", "key-custody.sign", "key-custody.rotate", "key-custody.envelope.encrypt", "key-custody.envelope.decrypt", "key-custody.session-data-key.generate", "account-custody.schedule", "account-custody.apply", "account-custody.cancel", "account-custody.credential.add", "account-custody.credential.remove", "account-custody.credential.replace", "agent-account.admin.propose", "agent-account.admin.execute", "agent-account.admin.cancel", "connect-auth.session.mint", "connect-auth.session.verify.accept", "connect-auth.session.verify.reject", "connect.issue.accept", "connect.issue.reject", "mcp-runtime.with-delegation.accept", "mcp-runtime.with-delegation.reject", "mcp-runtime.service-mac.issue", "mcp-runtime.service-mac.accept", "mcp-runtime.service-mac.reject", "agent-naming.resolve", "agent-naming.register", "agent-naming.records.update", "agent-naming.primary-name.update", "agent-naming.subregistry.update"];
56
+ export type AuditAction = (typeof AUDIT_ACTION_REGISTRY)[number];
57
+ /** Type guard for callers that need to validate an arbitrary string. */
58
+ export declare function isCanonicalAuditAction(s: string): s is AuditAction;
59
+ export interface AuditEvent {
60
+ /** Per-event unique ID. UUID v4 (or v7 if you have it) recommended. */
61
+ id: string;
62
+ /** Ties events from the same browser request / job / pipeline. */
63
+ correlationId?: string;
64
+ /** ISO-8601 timestamp (UTC). */
65
+ timestamp: string;
66
+ /**
67
+ * Dotted action name. Prefer {@link AuditAction} from the canonical
68
+ * {@link AUDIT_ACTION_REGISTRY}; raw `string` remains accepted for
69
+ * legacy callers + one-off events. Convention:
70
+ * `<package>.<surface>.<outcome?>`. Each emitting package documents
71
+ * its actions in its `AUDIT.md` under "Audit events emitted".
72
+ */
73
+ action: AuditAction | (string & {});
74
+ /**
75
+ * Outcome class. `success` for normal operations, `denied` for
76
+ * authority/policy rejects (NOT errors — denial IS a security
77
+ * outcome that must be auditable), `error` for unexpected failures.
78
+ */
79
+ outcome: 'success' | 'denied' | 'error';
80
+ /**
81
+ * Who initiated. Use stable identifiers — smart-account address,
82
+ * service identity (`a2a-to-mcp`), system label (`bundler`, etc.).
83
+ */
84
+ actor?: {
85
+ type: 'user' | 'service' | 'system' | 'unknown';
86
+ /** Smart-account address, service name, or system label. */
87
+ id?: string;
88
+ };
89
+ /**
90
+ * What was acted on. Free-form `type` discriminator + opaque `id` —
91
+ * consumers choose conventions like `tool`/`session`/`tx`/etc. The
92
+ * audit package deliberately does not enumerate types because that
93
+ * would couple it to domain vocabulary.
94
+ */
95
+ subject?: {
96
+ type: string;
97
+ id: string;
98
+ };
99
+ /** Human-readable + structured reason for non-success outcomes. */
100
+ reason?: string;
101
+ /**
102
+ * Free-form additional structured context. Keep keys flat (no nested
103
+ * objects) so structured-log backends (Cloud Logging, Datadog) index
104
+ * cleanly.
105
+ */
106
+ context?: Record<string, string | number | boolean | null>;
107
+ /** Audience / route for service-to-service events. */
108
+ audience?: string;
109
+ /** Chain ID for on-chain events. */
110
+ chainId?: number;
111
+ /** On-chain tx / digest, when relevant. */
112
+ digest?: Hex;
113
+ }
114
+ /**
115
+ * The minimal contract every audit sink implements. `write` is async
116
+ * because the most useful sinks are persistent (D1, HTTP, etc.); even
117
+ * in-memory sinks return a resolved promise for shape symmetry.
118
+ *
119
+ * Sinks MUST be append-only — events never edited or deleted. The
120
+ * sink's persistence backend (D1 schema, log retention, etc.) should
121
+ * enforce this, but the interface assumes it.
122
+ *
123
+ * Sinks SHOULD be fail-soft: an audit-emission failure should NOT
124
+ * propagate as a request failure. Instead, log the failure to a
125
+ * fallback channel (the in-band console). Implementations decide
126
+ * how to handle the rare double-failure.
127
+ */
128
+ export interface AuditSink {
129
+ /** Emit one event. Resolves on durable acceptance; rejects only on
130
+ * programmer error (malformed event), never on transient backend
131
+ * failure (those are absorbed inside the sink). */
132
+ write(event: AuditEvent): Promise<void>;
133
+ }
134
+ /**
135
+ * Emit events as one-line JSON to `console.log`. Suitable for
136
+ * Cloudflare Workers (where `wrangler tail` surfaces console output)
137
+ * and for local dev. Production deploys typically PAIR this with a
138
+ * durable sink — see `composeSinks` below.
139
+ */
140
+ export declare function createConsoleAuditSink(opts?: {
141
+ /** Prefix used to filter the audit events out of regular logs.
142
+ * Default: `'[AUDIT]'`. */
143
+ prefix?: string;
144
+ }): AuditSink;
145
+ /**
146
+ * In-memory ring buffer. **Test-only.** Production sinks must be
147
+ * durable; this is for unit tests + the demo's recent-events UI.
148
+ */
149
+ export interface MemoryAuditSink extends AuditSink {
150
+ /** Read out the captured events (oldest first). */
151
+ events(): AuditEvent[];
152
+ /** Clear the buffer. */
153
+ reset(): void;
154
+ }
155
+ export declare function createMemoryAuditSink(opts?: {
156
+ /** Maximum events retained; older events discarded FIFO. Default 1024. */
157
+ capacity?: number;
158
+ }): MemoryAuditSink;
159
+ /**
160
+ * Combine multiple sinks into one with **fail-soft** semantics — each
161
+ * emit fans out to all sinks sequentially; a failure in one sink does NOT
162
+ * short-circuit the others, AND does NOT propagate to the caller. The
163
+ * caller's `await write(event)` always resolves successfully, even if every
164
+ * sink failed. Best for telemetry / metrics-grade events where dropping
165
+ * one event is acceptable.
166
+ *
167
+ * **H7-B.7 / PKG-AUDIT-001 closure** — security-critical events (authority
168
+ * changes, custody operations, signing-side actions) need durable-before-
169
+ * commit semantics; use {@link composeFailHardSinks} for those. The
170
+ * audit package stays domain-agnostic — emitting packages document which
171
+ * of their action names belong to the fail-hard class in their own docs.
172
+ *
173
+ * Production wiring is typically `composeSinks(d1Sink, consoleSink)` for
174
+ * non-critical events, and `composeFailHardSinks(d1Sink)` for events that
175
+ * MUST be persisted before the action commits.
176
+ */
177
+ export declare function composeSinks(...sinks: AuditSink[]): AuditSink;
178
+ /** Explicit alias of {@link composeSinks} — fail-soft, no propagation. */
179
+ export declare function composeFailSoftSinks(...sinks: AuditSink[]): AuditSink;
180
+ /**
181
+ * H7-B.7 — Combine multiple sinks with **fail-hard** semantics for
182
+ * security-critical events. The first sink failure THROWS the original
183
+ * error (after the remaining sinks have been attempted — every sink still
184
+ * gets a chance to record, but the caller's `await write(event)` rejects
185
+ * if any failed).
186
+ *
187
+ * Use for events whose absence from durable storage would be a security
188
+ * regression — the precise action vocabulary is each emitting package's
189
+ * responsibility to document. The caller's flow MUST treat a thrown audit
190
+ * as the action FAILING — do not commit the on-chain or off-chain effect
191
+ * if the audit didn't persist.
192
+ *
193
+ * Closure: PKG-AUDIT-001 / EXT-022 / CT-11.
194
+ */
195
+ export declare function composeFailHardSinks(...sinks: AuditSink[]): AuditSink;
196
+ export interface MetricsSink {
197
+ /**
198
+ * Increment a monotonic counter. `value` defaults to 1.
199
+ * Typical use: request count, error count, retry count.
200
+ */
201
+ increment(name: string, value?: number, tags?: Record<string, string>): void;
202
+ /**
203
+ * Record a single observation in a histogram / distribution.
204
+ * Typical use: latency, payload size, response bytes.
205
+ */
206
+ observe(name: string, value: number, tags?: Record<string, string>): void;
207
+ /**
208
+ * Set a gauge — a value that can go up or down. The latest value wins.
209
+ * Typical use: queue depth, in-flight session count, active connections.
210
+ */
211
+ gauge(name: string, value: number, tags?: Record<string, string>): void;
212
+ }
213
+ /**
214
+ * No-op sink. The default when consumers haven't wired metrics yet —
215
+ * runtime always succeeds, never observably affects throughput.
216
+ */
217
+ export declare const noopMetricsSink: MetricsSink;
218
+ /**
219
+ * Console-out metrics sink for local dev. NOT a production sink — the
220
+ * volume in production overwhelms console output and provides no
221
+ * aggregation. Production wires `composeMetricsSinks(prometheusSink, …)`.
222
+ */
223
+ export declare function createConsoleMetricsSink(opts?: {
224
+ prefix?: string;
225
+ }): MetricsSink;
226
+ /**
227
+ * Fan-out to multiple metrics sinks. Same fail-soft semantics as
228
+ * `composeSinks` for audit: a failure in one sink doesn't short-circuit
229
+ * the others. Metrics emissions are synchronous + must never block
230
+ * the request path; sinks that need async work MUST queue internally.
231
+ */
232
+ export declare function composeMetricsSinks(...sinks: MetricsSink[]): MetricsSink;
233
+ /**
234
+ * In-memory metrics sink for test assertions. Counts + histograms +
235
+ * gauges are captured in plain Maps; `.snapshot()` returns the
236
+ * accumulated state for assertions. NOT a production sink.
237
+ */
238
+ export interface MemoryMetricsSink extends MetricsSink {
239
+ snapshot(): {
240
+ counts: Map<string, number>;
241
+ observations: Map<string, number[]>;
242
+ gauges: Map<string, number>;
243
+ };
244
+ reset(): void;
245
+ }
246
+ export declare function createMemoryMetricsSink(): MemoryMetricsSink;
247
+ /**
248
+ * One detected probable-leak. Reported to `onDetect` and embedded in
249
+ * the redacted event's `_pii` context field so reviewers can trace
250
+ * where the leak came from without seeing the value.
251
+ */
252
+ export interface PiiFinding {
253
+ /** Dotted path within the event (e.g. `context.token`, `subject.id`, `reason`). */
254
+ path: string;
255
+ /** Why we flagged it (`long-hex`, `jwt-shape`, `pem-block`, `secret-substring`). */
256
+ reason: string;
257
+ /** Safe descriptor of the original value (e.g. `hex-130`, `jwt-3-seg`). */
258
+ preview: string;
259
+ }
260
+ export interface PiiGuardrailOpts {
261
+ /**
262
+ * What to do on detection.
263
+ * - `'redact'` (default): replace the offending value in-place with a
264
+ * safe descriptor (`<redacted:<reason>:<preview>>`); forward the
265
+ * sanitized event to the inner sink. Preserves forensics minus the
266
+ * leaked field.
267
+ * - `'drop'`: do NOT forward the event at all. Strictest setting;
268
+ * loses the row from the trail entirely.
269
+ * - `'warn'`: forward unchanged, just notify via `onDetect`. Useful
270
+ * during initial roll-out so reviewers see what would be flagged
271
+ * before enforcement kicks in.
272
+ */
273
+ mode?: 'redact' | 'drop' | 'warn';
274
+ /**
275
+ * Maximum hex string length (chars, including any `0x` prefix) tolerated
276
+ * in non-allowlisted positions. Default 80 — leaves Ethereum addresses
277
+ * (42), keccak/sha256 digests (66), and tx hashes (66) all comfortably
278
+ * below the threshold while catching raw private keys (130) and longer
279
+ * session secrets.
280
+ */
281
+ maxHexLength?: number;
282
+ /**
283
+ * `context` keys (and well-known top-level fields) where long hex is
284
+ * legitimately expected. Merged with the built-in safe set
285
+ * (`signerAddress`, `address`, `paymaster`, `entryPoint`, `keyId`,
286
+ * `nonceHash`, `sessionHash`, `digest`, `txHash`, `blockHash`, `jti`,
287
+ * `eventId`).
288
+ */
289
+ allowKeys?: string[];
290
+ /**
291
+ * `subject.type` values whose `subject.id` is allowed to be any length
292
+ * of hex (e.g. `sign-digest` carries a 32-byte digest). Merged with
293
+ * the built-in safe set (`jti`, `sign-digest`, `tx-hash`, `address`,
294
+ * `event-id`).
295
+ */
296
+ allowSubjectTypes?: string[];
297
+ /**
298
+ * Callback invoked on every detection, regardless of `mode`. The
299
+ * sanitized event (or original, in `warn` mode) is passed alongside
300
+ * the findings. Useful for routing a "guardrail caught a leak" signal
301
+ * to a separate alerting channel.
302
+ */
303
+ onDetect?: (info: {
304
+ event: AuditEvent;
305
+ findings: PiiFinding[];
306
+ }) => void;
307
+ }
308
+ /**
309
+ * Wrapper sink that scans event payloads for likely-secret material and
310
+ * either redacts / drops / warns before forwarding to `inner`. **This is
311
+ * defense-in-depth**, not a substitute for the emitter discipline rule
312
+ * "MUST hash or omit raw secrets" (per the audit package CLAUDE.md
313
+ * invariant). It catches accidental leaks at the sink boundary.
314
+ *
315
+ * Production wiring example:
316
+ * ```ts
317
+ * const sink = composeSinks(
318
+ * createConsoleAuditSink({ prefix: '[AUDIT]' }),
319
+ * createPiiGuardrailSink(createD1AuditSink(env.DB), { mode: 'redact' }),
320
+ * );
321
+ * ```
322
+ *
323
+ * The guardrail's own decisions are not themselves audit events — they
324
+ * surface through `onDetect`. Use that to wire alerts ("found a leak in
325
+ * action=X") if you want them in your own metrics pipeline.
326
+ */
327
+ export declare function createPiiGuardrailSink(inner: AuditSink, opts?: PiiGuardrailOpts): AuditSink;
328
+ /**
329
+ * Generate a UUID-shaped event ID. Uses Web Crypto when available
330
+ * (Workers, modern Node); falls back to a deterministic ordering for
331
+ * legacy Node (warning: NOT cryptographically random — tests + dev only).
332
+ */
333
+ export declare function generateEventId(): string;
334
+ /** Convenience: produce an ISO-8601 UTC timestamp. */
335
+ export declare function nowIso(): string;
336
+ /**
337
+ * Build an `AuditEvent` with sane defaults. Saves callers from
338
+ * repeatedly writing `id: generateEventId(), timestamp: nowIso()`.
339
+ *
340
+ * Required: `action`, `outcome`. Everything else optional + spreads
341
+ * through.
342
+ */
343
+ export declare function buildEvent(partial: Omit<AuditEvent, 'id' | 'timestamp'> & {
344
+ id?: string;
345
+ timestamp?: string;
346
+ }): AuditEvent;
347
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,496 @@
1
+ /**
2
+ * @agenticprimitives/audit — append-only audit/forensics primitives.
3
+ *
4
+ * Owns:
5
+ * - The canonical `AuditEvent` schema every other package emits.
6
+ * - The `AuditSink` interface a consumer implements (or composes from
7
+ * one of the provided sinks).
8
+ * - In-band sinks: `createConsoleAuditSink`, `createMemoryAuditSink`.
9
+ *
10
+ * Does NOT own:
11
+ * - Concrete persistence backends (D1 / Cloud Logging / Splunk / etc.).
12
+ * Those live with the consumer that needs them.
13
+ * - Domain semantics — `action` is a free-string the emitter chooses
14
+ * (e.g. `<package>.<surface>.<outcome>`).
15
+ *
16
+ * Doctrine (per CLAUDE.md): zero domain knowledge. The schema is
17
+ * deliberately structural — every package's emitter knows what to put
18
+ * in `subject` / `context`; this package only stamps the shape.
19
+ *
20
+ * Closes system audit finding C3.
21
+ */
22
+ // ─── Event schema ─────────────────────────────────────────────────────
23
+ /**
24
+ * One audit event. Designed to be JSON-serialisable for sinks that
25
+ * persist or forward over HTTP. All fields are strings or numbers so
26
+ * the on-wire shape matches the in-memory shape with no codec needed.
27
+ *
28
+ * `id` is per-event (UUID-ish); `correlationId` ties multiple events
29
+ * to a single request / flow / session.
30
+ */
31
+ /**
32
+ * H7-F.2 / PKG-audit-002 / EXT-037 closure — canonical action registry.
33
+ *
34
+ * Free-string `action` previously let each emitter pick its own name
35
+ * with no central catalog — drift hazard the moment more than one
36
+ * package emits the same conceptual event. The registry below is the
37
+ * canonical list. `action: AuditAction` keeps the string convention
38
+ * (still indexes cleanly in structured-log backends) but type-checks
39
+ * the caller against the registry.
40
+ *
41
+ * Convention: `<package>.<surface>.<outcome?>`.
42
+ *
43
+ * Each emitting package documents WHICH of these names IT emits in
44
+ * its `AUDIT.md` "Audit events emitted" section.
45
+ *
46
+ * To add a new action:
47
+ * 1. Add the string to `AUDIT_ACTION_REGISTRY` (below).
48
+ * 2. Add it to the owning package's `AUDIT.md` list.
49
+ * 3. The TypeScript union `AuditAction` is derived automatically.
50
+ *
51
+ * `action` STILL accepts a raw `string` (the legacy escape hatch) so a
52
+ * package can ship a one-off event without churn. Production paths
53
+ * should use the typed name + extend the registry.
54
+ */
55
+ export const AUDIT_ACTION_REGISTRY = [
56
+ // ─── delegation (spec 202)
57
+ 'delegation.mint',
58
+ 'delegation.verify.accept',
59
+ 'delegation.verify.reject',
60
+ 'delegation.revoke',
61
+ // ─── key-custody (spec 203)
62
+ 'key-custody.sign',
63
+ 'key-custody.rotate',
64
+ 'key-custody.envelope.encrypt',
65
+ 'key-custody.envelope.decrypt',
66
+ 'key-custody.session-data-key.generate',
67
+ // ─── account-custody (spec 213)
68
+ 'account-custody.schedule',
69
+ 'account-custody.apply',
70
+ 'account-custody.cancel',
71
+ 'account-custody.credential.add',
72
+ 'account-custody.credential.remove',
73
+ 'account-custody.credential.replace',
74
+ // ─── agent-account (spec 201)
75
+ 'agent-account.admin.propose',
76
+ 'agent-account.admin.execute',
77
+ 'agent-account.admin.cancel',
78
+ // ─── connect-auth (spec 200)
79
+ 'connect-auth.session.mint',
80
+ 'connect-auth.session.verify.accept',
81
+ 'connect-auth.session.verify.reject',
82
+ // ─── connect (spec 224)
83
+ 'connect.issue.accept',
84
+ 'connect.issue.reject',
85
+ // ─── mcp-runtime (spec 205)
86
+ 'mcp-runtime.with-delegation.accept',
87
+ 'mcp-runtime.with-delegation.reject',
88
+ 'mcp-runtime.service-mac.issue',
89
+ 'mcp-runtime.service-mac.accept',
90
+ 'mcp-runtime.service-mac.reject',
91
+ // ─── agent-naming (spec 215)
92
+ 'agent-naming.resolve',
93
+ 'agent-naming.register',
94
+ 'agent-naming.records.update',
95
+ 'agent-naming.primary-name.update',
96
+ 'agent-naming.subregistry.update',
97
+ ];
98
+ /** Type guard for callers that need to validate an arbitrary string. */
99
+ export function isCanonicalAuditAction(s) {
100
+ return AUDIT_ACTION_REGISTRY.includes(s);
101
+ }
102
+ // ─── Provided sinks ───────────────────────────────────────────────────
103
+ /**
104
+ * Emit events as one-line JSON to `console.log`. Suitable for
105
+ * Cloudflare Workers (where `wrangler tail` surfaces console output)
106
+ * and for local dev. Production deploys typically PAIR this with a
107
+ * durable sink — see `composeSinks` below.
108
+ */
109
+ export function createConsoleAuditSink(opts) {
110
+ const prefix = opts?.prefix ?? '[AUDIT]';
111
+ return {
112
+ async write(event) {
113
+ // One-line JSON; Cloudflare's log line limit is 1KB so we stay
114
+ // compact. If `context` overflows, the line gets clipped — that's
115
+ // the sink's responsibility to handle (e.g. by pre-truncating).
116
+ const line = `${prefix} ${JSON.stringify(event)}`;
117
+ // eslint-disable-next-line no-console
118
+ console.log(line);
119
+ },
120
+ };
121
+ }
122
+ export function createMemoryAuditSink(opts) {
123
+ const capacity = opts?.capacity ?? 1024;
124
+ const buf = [];
125
+ return {
126
+ async write(event) {
127
+ buf.push(event);
128
+ if (buf.length > capacity)
129
+ buf.shift();
130
+ },
131
+ events() {
132
+ return buf.slice();
133
+ },
134
+ reset() {
135
+ buf.length = 0;
136
+ },
137
+ };
138
+ }
139
+ /**
140
+ * Combine multiple sinks into one with **fail-soft** semantics — each
141
+ * emit fans out to all sinks sequentially; a failure in one sink does NOT
142
+ * short-circuit the others, AND does NOT propagate to the caller. The
143
+ * caller's `await write(event)` always resolves successfully, even if every
144
+ * sink failed. Best for telemetry / metrics-grade events where dropping
145
+ * one event is acceptable.
146
+ *
147
+ * **H7-B.7 / PKG-AUDIT-001 closure** — security-critical events (authority
148
+ * changes, custody operations, signing-side actions) need durable-before-
149
+ * commit semantics; use {@link composeFailHardSinks} for those. The
150
+ * audit package stays domain-agnostic — emitting packages document which
151
+ * of their action names belong to the fail-hard class in their own docs.
152
+ *
153
+ * Production wiring is typically `composeSinks(d1Sink, consoleSink)` for
154
+ * non-critical events, and `composeFailHardSinks(d1Sink)` for events that
155
+ * MUST be persisted before the action commits.
156
+ */
157
+ export function composeSinks(...sinks) {
158
+ return composeFailSoftSinks(...sinks);
159
+ }
160
+ /** Explicit alias of {@link composeSinks} — fail-soft, no propagation. */
161
+ export function composeFailSoftSinks(...sinks) {
162
+ return {
163
+ async write(event) {
164
+ const errors = [];
165
+ for (const sink of sinks) {
166
+ try {
167
+ await sink.write(event);
168
+ }
169
+ catch (e) {
170
+ errors.push(e);
171
+ }
172
+ }
173
+ if (errors.length > 0) {
174
+ // Surface to console as a last resort — the audit event itself
175
+ // may have been written to some sinks but not others.
176
+ // eslint-disable-next-line no-console
177
+ console.error(`[audit] ${errors.length} sink(s) failed for event ${event.id}:`, errors);
178
+ }
179
+ },
180
+ };
181
+ }
182
+ /**
183
+ * H7-B.7 — Combine multiple sinks with **fail-hard** semantics for
184
+ * security-critical events. The first sink failure THROWS the original
185
+ * error (after the remaining sinks have been attempted — every sink still
186
+ * gets a chance to record, but the caller's `await write(event)` rejects
187
+ * if any failed).
188
+ *
189
+ * Use for events whose absence from durable storage would be a security
190
+ * regression — the precise action vocabulary is each emitting package's
191
+ * responsibility to document. The caller's flow MUST treat a thrown audit
192
+ * as the action FAILING — do not commit the on-chain or off-chain effect
193
+ * if the audit didn't persist.
194
+ *
195
+ * Closure: PKG-AUDIT-001 / EXT-022 / CT-11.
196
+ */
197
+ export function composeFailHardSinks(...sinks) {
198
+ return {
199
+ async write(event) {
200
+ const errors = [];
201
+ for (const sink of sinks) {
202
+ try {
203
+ await sink.write(event);
204
+ }
205
+ catch (e) {
206
+ errors.push(e);
207
+ }
208
+ }
209
+ if (errors.length > 0) {
210
+ // eslint-disable-next-line no-console
211
+ console.error(`[audit:fail-hard] ${errors.length} sink(s) failed for event ${event.id}:`, errors);
212
+ const err = errors[0];
213
+ if (err instanceof Error)
214
+ throw err;
215
+ throw new Error(`[audit:fail-hard] sink failure for event ${event.id}: ${String(err)}`);
216
+ }
217
+ },
218
+ };
219
+ }
220
+ /**
221
+ * No-op sink. The default when consumers haven't wired metrics yet —
222
+ * runtime always succeeds, never observably affects throughput.
223
+ */
224
+ export const noopMetricsSink = {
225
+ increment: () => undefined,
226
+ observe: () => undefined,
227
+ gauge: () => undefined,
228
+ };
229
+ /**
230
+ * Console-out metrics sink for local dev. NOT a production sink — the
231
+ * volume in production overwhelms console output and provides no
232
+ * aggregation. Production wires `composeMetricsSinks(prometheusSink, …)`.
233
+ */
234
+ export function createConsoleMetricsSink(opts) {
235
+ const prefix = opts?.prefix ?? '[METRIC]';
236
+ const fmt = (kind, name, value, tags) => `${prefix} ${kind} ${name}=${value}${tags ? ' ' + JSON.stringify(tags) : ''}`;
237
+ return {
238
+ increment: (name, value = 1, tags) => {
239
+ // eslint-disable-next-line no-console
240
+ console.log(fmt('count', name, value, tags));
241
+ },
242
+ observe: (name, value, tags) => {
243
+ // eslint-disable-next-line no-console
244
+ console.log(fmt('hist', name, value, tags));
245
+ },
246
+ gauge: (name, value, tags) => {
247
+ // eslint-disable-next-line no-console
248
+ console.log(fmt('gauge', name, value, tags));
249
+ },
250
+ };
251
+ }
252
+ /**
253
+ * Fan-out to multiple metrics sinks. Same fail-soft semantics as
254
+ * `composeSinks` for audit: a failure in one sink doesn't short-circuit
255
+ * the others. Metrics emissions are synchronous + must never block
256
+ * the request path; sinks that need async work MUST queue internally.
257
+ */
258
+ export function composeMetricsSinks(...sinks) {
259
+ const safely = (fn) => {
260
+ try {
261
+ fn();
262
+ }
263
+ catch { /* fail-soft */ }
264
+ };
265
+ return {
266
+ increment: (name, value, tags) => {
267
+ for (const s of sinks)
268
+ safely(() => s.increment(name, value, tags));
269
+ },
270
+ observe: (name, value, tags) => {
271
+ for (const s of sinks)
272
+ safely(() => s.observe(name, value, tags));
273
+ },
274
+ gauge: (name, value, tags) => {
275
+ for (const s of sinks)
276
+ safely(() => s.gauge(name, value, tags));
277
+ },
278
+ };
279
+ }
280
+ export function createMemoryMetricsSink() {
281
+ const counts = new Map();
282
+ const observations = new Map();
283
+ const gauges = new Map();
284
+ const key = (name, tags) => tags && Object.keys(tags).length > 0
285
+ ? `${name}|${Object.keys(tags).sort().map((k) => `${k}=${tags[k]}`).join(',')}`
286
+ : name;
287
+ return {
288
+ increment(name, value = 1, tags) {
289
+ const k = key(name, tags);
290
+ counts.set(k, (counts.get(k) ?? 0) + value);
291
+ },
292
+ observe(name, value, tags) {
293
+ const k = key(name, tags);
294
+ const arr = observations.get(k) ?? [];
295
+ arr.push(value);
296
+ observations.set(k, arr);
297
+ },
298
+ gauge(name, value, tags) {
299
+ gauges.set(key(name, tags), value);
300
+ },
301
+ snapshot() {
302
+ return { counts: new Map(counts), observations: new Map(observations), gauges: new Map(gauges) };
303
+ },
304
+ reset() {
305
+ counts.clear();
306
+ observations.clear();
307
+ gauges.clear();
308
+ },
309
+ };
310
+ }
311
+ const DEFAULT_ALLOW_KEYS = new Set([
312
+ 'signerAddress',
313
+ 'address',
314
+ 'paymaster',
315
+ 'entryPoint',
316
+ 'keyId',
317
+ 'nonceHash',
318
+ 'sessionHash',
319
+ 'digest',
320
+ 'txHash',
321
+ 'blockHash',
322
+ 'jti',
323
+ 'eventId',
324
+ ]);
325
+ const DEFAULT_ALLOW_SUBJECT_TYPES = new Set([
326
+ 'jti',
327
+ 'sign-digest',
328
+ 'tx-hash',
329
+ 'address',
330
+ 'event-id',
331
+ ]);
332
+ const JWT_SHAPE = /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;
333
+ const PEM_BLOCK = /-----BEGIN /;
334
+ const SECRET_SUBSTRING = /(private[_-]?key|client[_-]?secret|api[_-]?key|access[_-]?token|refresh[_-]?token)/i;
335
+ /**
336
+ * Wrapper sink that scans event payloads for likely-secret material and
337
+ * either redacts / drops / warns before forwarding to `inner`. **This is
338
+ * defense-in-depth**, not a substitute for the emitter discipline rule
339
+ * "MUST hash or omit raw secrets" (per the audit package CLAUDE.md
340
+ * invariant). It catches accidental leaks at the sink boundary.
341
+ *
342
+ * Production wiring example:
343
+ * ```ts
344
+ * const sink = composeSinks(
345
+ * createConsoleAuditSink({ prefix: '[AUDIT]' }),
346
+ * createPiiGuardrailSink(createD1AuditSink(env.DB), { mode: 'redact' }),
347
+ * );
348
+ * ```
349
+ *
350
+ * The guardrail's own decisions are not themselves audit events — they
351
+ * surface through `onDetect`. Use that to wire alerts ("found a leak in
352
+ * action=X") if you want them in your own metrics pipeline.
353
+ */
354
+ export function createPiiGuardrailSink(inner, opts) {
355
+ const mode = opts?.mode ?? 'redact';
356
+ const maxHexLength = opts?.maxHexLength ?? 80;
357
+ const allowKeys = new Set([...DEFAULT_ALLOW_KEYS, ...(opts?.allowKeys ?? [])]);
358
+ const allowSubjectTypes = new Set([
359
+ ...DEFAULT_ALLOW_SUBJECT_TYPES,
360
+ ...(opts?.allowSubjectTypes ?? []),
361
+ ]);
362
+ const longHex = new RegExp(`^0x[0-9a-fA-F]{${maxHexLength - 2},}$`);
363
+ function classify(value) {
364
+ if (longHex.test(value)) {
365
+ return { reason: 'long-hex', preview: `hex-${value.length}` };
366
+ }
367
+ if (value.length > 100 && JWT_SHAPE.test(value)) {
368
+ const segs = value.split('.').length;
369
+ return { reason: 'jwt-shape', preview: `jwt-${segs}-seg-len-${value.length}` };
370
+ }
371
+ if (PEM_BLOCK.test(value)) {
372
+ return { reason: 'pem-block', preview: 'pem' };
373
+ }
374
+ if (SECRET_SUBSTRING.test(value)) {
375
+ return { reason: 'secret-substring', preview: `match-len-${value.length}` };
376
+ }
377
+ return null;
378
+ }
379
+ function redactToken(reason, preview) {
380
+ return `<redacted:${reason}:${preview}>`;
381
+ }
382
+ return {
383
+ async write(event) {
384
+ const findings = [];
385
+ // Deep-ish copy of the event so we can mutate fields without
386
+ // affecting the caller's reference.
387
+ const sanitized = {
388
+ ...event,
389
+ context: event.context ? { ...event.context } : undefined,
390
+ subject: event.subject ? { ...event.subject } : undefined,
391
+ actor: event.actor ? { ...event.actor } : undefined,
392
+ };
393
+ const scan = (path, value, redactInPlace) => {
394
+ if (typeof value !== 'string')
395
+ return;
396
+ const hit = classify(value);
397
+ if (!hit)
398
+ return;
399
+ findings.push({ path, reason: hit.reason, preview: hit.preview });
400
+ if (mode === 'redact')
401
+ redactInPlace(redactToken(hit.reason, hit.preview));
402
+ };
403
+ // context.* — skip allowlisted keys (legitimate hex carriers).
404
+ if (sanitized.context) {
405
+ for (const [k, v] of Object.entries(sanitized.context)) {
406
+ if (allowKeys.has(k))
407
+ continue;
408
+ scan(`context.${k}`, v, (token) => {
409
+ sanitized.context[k] = token;
410
+ });
411
+ }
412
+ }
413
+ // subject.id — skip when subject.type is a known hex-carrier.
414
+ if (sanitized.subject && !allowSubjectTypes.has(sanitized.subject.type)) {
415
+ scan('subject.id', sanitized.subject.id, (token) => {
416
+ sanitized.subject.id = token;
417
+ });
418
+ }
419
+ // actor.id — same rules; addresses + service names are typically short.
420
+ if (sanitized.actor?.id) {
421
+ scan('actor.id', sanitized.actor.id, (token) => {
422
+ sanitized.actor.id = token;
423
+ });
424
+ }
425
+ // reason / audience — free-string fields that could carry stack traces.
426
+ if (sanitized.reason) {
427
+ scan('reason', sanitized.reason, (token) => {
428
+ sanitized.reason = token;
429
+ });
430
+ }
431
+ if (sanitized.audience) {
432
+ scan('audience', sanitized.audience, (token) => {
433
+ sanitized.audience = token;
434
+ });
435
+ }
436
+ if (findings.length > 0) {
437
+ opts?.onDetect?.({ event: sanitized, findings });
438
+ }
439
+ if (findings.length > 0 && mode === 'drop') {
440
+ // Don't forward at all. The onDetect callback above is the
441
+ // only signal that a row was dropped.
442
+ return;
443
+ }
444
+ if (findings.length > 0 && mode === 'warn') {
445
+ // Forward unchanged.
446
+ await inner.write(event);
447
+ return;
448
+ }
449
+ await inner.write(sanitized);
450
+ },
451
+ };
452
+ }
453
+ // ─── Helpers ──────────────────────────────────────────────────────────
454
+ /**
455
+ * Generate a UUID-shaped event ID. Uses Web Crypto when available
456
+ * (Workers, modern Node); falls back to a deterministic ordering for
457
+ * legacy Node (warning: NOT cryptographically random — tests + dev only).
458
+ */
459
+ export function generateEventId() {
460
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
461
+ const g = globalThis;
462
+ if (g.crypto?.randomUUID)
463
+ return g.crypto.randomUUID();
464
+ // Fallback: 16 random-ish bytes formatted as a UUID v4.
465
+ const bytes = new Uint8Array(16);
466
+ if (g.crypto?.getRandomValues) {
467
+ g.crypto.getRandomValues(bytes);
468
+ }
469
+ else {
470
+ for (let i = 0; i < 16; i++)
471
+ bytes[i] = Math.floor(Math.random() * 256);
472
+ }
473
+ bytes[6] = (bytes[6] & 0x0f) | 0x40;
474
+ bytes[8] = (bytes[8] & 0x3f) | 0x80;
475
+ const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
476
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
477
+ }
478
+ /** Convenience: produce an ISO-8601 UTC timestamp. */
479
+ export function nowIso() {
480
+ return new Date().toISOString();
481
+ }
482
+ /**
483
+ * Build an `AuditEvent` with sane defaults. Saves callers from
484
+ * repeatedly writing `id: generateEventId(), timestamp: nowIso()`.
485
+ *
486
+ * Required: `action`, `outcome`. Everything else optional + spreads
487
+ * through.
488
+ */
489
+ export function buildEvent(partial) {
490
+ return {
491
+ id: partial.id ?? generateEventId(),
492
+ timestamp: partial.timestamp ?? nowIso(),
493
+ ...partial,
494
+ };
495
+ }
496
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@agenticprimitives/audit",
3
+ "version": "0.1.0-alpha.2",
4
+ "description": "Append-only audit event schema + sink interface. Transport-agnostic; consumers wire concrete sinks (console / D1 / Cloud Logging / etc.).",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/agentictrustlabs/agenticprimitives.git",
9
+ "directory": "packages/audit"
10
+ },
11
+ "homepage": "https://github.com/agentictrustlabs/agenticprimitives/tree/master/packages/audit",
12
+ "bugs": {
13
+ "url": "https://github.com/agentictrustlabs/agenticprimitives/issues"
14
+ },
15
+ "type": "module",
16
+ "main": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.js"
22
+ }
23
+ },
24
+ "files": [
25
+ "LICENSE",
26
+ "dist",
27
+ "spec.md",
28
+ "README.md"
29
+ ],
30
+ "scripts": {
31
+ "build": "tsc -p tsconfig.build.json",
32
+ "typecheck": "tsc -p tsconfig.json --noEmit",
33
+ "test": "vitest run",
34
+ "test:unit": "vitest run test/unit",
35
+ "test:integration": "vitest run test/integration --passWithNoTests",
36
+ "test:watch": "vitest",
37
+ "clean": "rm -rf dist"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "peerDependencies": {
43
+ "@agenticprimitives/types": "workspace:*"
44
+ },
45
+ "devDependencies": {
46
+ "vitest": "^2.1.0"
47
+ },
48
+ "keywords": [
49
+ "audit",
50
+ "forensics",
51
+ "observability",
52
+ "agentic"
53
+ ]
54
+ }
package/spec.md ADDED
@@ -0,0 +1,6 @@
1
+ # @agenticprimitives/audit — spec
2
+
3
+ The authoritative specification lives at:
4
+ **[`../../specs/206-audit.md`](../../specs/206-audit.md)**
5
+
6
+ Do not edit a divergent copy here.