@nodii/telemetry 0.1.1 → 0.3.1
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/dist/audit-types.d.ts +15 -0
- package/dist/audit-types.d.ts.map +1 -0
- package/dist/audit-types.js +39 -0
- package/dist/audit-types.js.map +1 -0
- package/dist/audit.d.ts +206 -27
- package/dist/audit.d.ts.map +1 -1
- package/dist/audit.js +228 -41
- package/dist/audit.js.map +1 -1
- package/dist/configure.d.ts +34 -0
- package/dist/configure.d.ts.map +1 -0
- package/dist/configure.js +23 -0
- package/dist/configure.js.map +1 -0
- package/dist/drizzle/audit-schema.d.ts +884 -0
- package/dist/drizzle/audit-schema.d.ts.map +1 -0
- package/dist/drizzle/audit-schema.js +268 -0
- package/dist/drizzle/audit-schema.js.map +1 -0
- package/dist/drizzle/index.d.ts +3 -0
- package/dist/drizzle/index.d.ts.map +1 -0
- package/dist/drizzle/index.js +5 -0
- package/dist/drizzle/index.js.map +1 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/package.json +20 -5
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Doctrine `02-audit-doctrine.md § 4` — the canonical `actor_kind` enum.
|
|
3
|
+
* 10 values, in this exact order; tests assert the order, so don't
|
|
4
|
+
* reorder without a drift entry. The SQL enum type is `audit_actor_kind`
|
|
5
|
+
* (one Postgres type shared across every service DB).
|
|
6
|
+
*
|
|
7
|
+
* `unknown` is the security marker per § 4.2 — emitted when no actor
|
|
8
|
+
* stamp can be derived; surfaces as a security alert via obs M17.
|
|
9
|
+
*/
|
|
10
|
+
export declare const AUDIT_ACTOR_KINDS: readonly ["employee_user", "tenant_user", "tenant_customer", "service", "worker", "webhook_provider", "external_system", "agent", "system", "unknown"];
|
|
11
|
+
export type ActorKind = (typeof AUDIT_ACTOR_KINDS)[number];
|
|
12
|
+
/** Doctrine § 7 — 7-value `outcome` enum. Order matches the doctrine. */
|
|
13
|
+
export declare const AUDIT_OUTCOMES: readonly ["success", "failure", "partial", "queued", "dry_run", "denied_by_authz", "denied_by_mfa"];
|
|
14
|
+
export type AuditOutcome = (typeof AUDIT_OUTCOMES)[number];
|
|
15
|
+
//# sourceMappingURL=audit-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-types.d.ts","sourceRoot":"","sources":["../src/audit-types.ts"],"names":[],"mappings":"AAQA;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,wJAWpB,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE3D,yEAAyE;AACzE,eAAO,MAAM,cAAc,qGAQjB,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Type + enum constants for the audit signal. Kept separate from
|
|
2
|
+
// `audit.ts` so the top-level `@nodii/telemetry` barrel does not
|
|
3
|
+
// transitively pull `drizzle-orm` (which lives under `./drizzle/`).
|
|
4
|
+
//
|
|
5
|
+
// Consumers who don't run Drizzle still get `ActorKind`, `AuditOutcome`,
|
|
6
|
+
// and the value arrays via the main barrel — and the Drizzle schema
|
|
7
|
+
// fragment that uses these is reachable via `@nodii/telemetry/drizzle`.
|
|
8
|
+
/**
|
|
9
|
+
* Doctrine `02-audit-doctrine.md § 4` — the canonical `actor_kind` enum.
|
|
10
|
+
* 10 values, in this exact order; tests assert the order, so don't
|
|
11
|
+
* reorder without a drift entry. The SQL enum type is `audit_actor_kind`
|
|
12
|
+
* (one Postgres type shared across every service DB).
|
|
13
|
+
*
|
|
14
|
+
* `unknown` is the security marker per § 4.2 — emitted when no actor
|
|
15
|
+
* stamp can be derived; surfaces as a security alert via obs M17.
|
|
16
|
+
*/
|
|
17
|
+
export const AUDIT_ACTOR_KINDS = [
|
|
18
|
+
"employee_user",
|
|
19
|
+
"tenant_user",
|
|
20
|
+
"tenant_customer",
|
|
21
|
+
"service",
|
|
22
|
+
"worker",
|
|
23
|
+
"webhook_provider",
|
|
24
|
+
"external_system",
|
|
25
|
+
"agent",
|
|
26
|
+
"system",
|
|
27
|
+
"unknown",
|
|
28
|
+
];
|
|
29
|
+
/** Doctrine § 7 — 7-value `outcome` enum. Order matches the doctrine. */
|
|
30
|
+
export const AUDIT_OUTCOMES = [
|
|
31
|
+
"success",
|
|
32
|
+
"failure",
|
|
33
|
+
"partial",
|
|
34
|
+
"queued",
|
|
35
|
+
"dry_run",
|
|
36
|
+
"denied_by_authz",
|
|
37
|
+
"denied_by_mfa",
|
|
38
|
+
];
|
|
39
|
+
//# sourceMappingURL=audit-types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-types.js","sourceRoot":"","sources":["../src/audit-types.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,iEAAiE;AACjE,oEAAoE;AACpE,EAAE;AACF,yEAAyE;AACzE,oEAAoE;AACpE,wEAAwE;AAExE;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,eAAe;IACf,aAAa;IACb,iBAAiB;IACjB,SAAS;IACT,QAAQ;IACR,kBAAkB;IAClB,iBAAiB;IACjB,OAAO;IACP,QAAQ;IACR,SAAS;CACD,CAAC;AAIX,yEAAyE;AACzE,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,SAAS;IACT,SAAS;IACT,SAAS;IACT,QAAQ;IACR,SAAS;IACT,iBAAiB;IACjB,eAAe;CACP,CAAC"}
|
package/dist/audit.d.ts
CHANGED
|
@@ -1,47 +1,226 @@
|
|
|
1
|
+
import { AUDIT_ACTOR_KINDS, AUDIT_OUTCOMES, type ActorKind, type AuditOutcome } from "./audit-types";
|
|
2
|
+
export { AUDIT_ACTOR_KINDS, AUDIT_OUTCOMES };
|
|
3
|
+
export type { ActorKind, AuditOutcome };
|
|
4
|
+
/**
|
|
5
|
+
* Structured `actor` jsonb extension per doctrine § 4.1. Open-ended map;
|
|
6
|
+
* each `actor_kind` defines its own well-known fields (employee/user:
|
|
7
|
+
* `capacity`/`mfa`/`mfa_at`/`jti`; service: `instance_id`/`scopes`;
|
|
8
|
+
* worker: `queue_name`/`job_id`; webhook_provider: `transaction_id`/
|
|
9
|
+
* `signature_verified`; agent: `on_behalf_of`/`intent_id`/`tool`). NOT
|
|
10
|
+
* type-checked here — services that need stricter typing can layer their
|
|
11
|
+
* own discriminated union on top.
|
|
12
|
+
*/
|
|
13
|
+
export type ActorStamp = Record<string, unknown>;
|
|
14
|
+
/**
|
|
15
|
+
* The rich audit-emit input shape per doctrine § 3 + § 6.0. Every field
|
|
16
|
+
* except `action` and `resourceType` is optional; the SDK resolves missing
|
|
17
|
+
* fields from `NodiiContext` + the active OTel span before handing the row
|
|
18
|
+
* to the writer.
|
|
19
|
+
*
|
|
20
|
+
* Field name convention: camelCase on the input; the SDK normalises to
|
|
21
|
+
* snake_case for the persisted row (`AuditEventRow`) matching the
|
|
22
|
+
* `<service>_audit_event` column names. This mirrors how Drizzle / pgx /
|
|
23
|
+
* SQLAlchemy schemas are typically declared in service code.
|
|
24
|
+
*/
|
|
25
|
+
export interface AuditArgs {
|
|
26
|
+
tenantId?: string | null;
|
|
27
|
+
action: string;
|
|
28
|
+
resourceType: string;
|
|
29
|
+
resourceId?: string | null;
|
|
30
|
+
resourceLabel?: string | null;
|
|
31
|
+
secondaryResourceType?: string | null;
|
|
32
|
+
secondaryResourceId?: string | null;
|
|
33
|
+
actorKind?: ActorKind | null;
|
|
34
|
+
actorId?: string | null;
|
|
35
|
+
actorExternalId?: string | null;
|
|
36
|
+
actorDisplay?: string | null;
|
|
37
|
+
actorIp?: string | null;
|
|
38
|
+
actorUserAgent?: string | null;
|
|
39
|
+
actor?: ActorStamp | null;
|
|
40
|
+
outcome?: AuditOutcome;
|
|
41
|
+
httpStatus?: number | null;
|
|
42
|
+
grpcCode?: string | null;
|
|
43
|
+
errorCode?: string | null;
|
|
44
|
+
errorMessage?: string | null;
|
|
45
|
+
errorDetails?: Record<string, unknown> | null;
|
|
46
|
+
before?: Record<string, unknown> | null;
|
|
47
|
+
after?: Record<string, unknown> | null;
|
|
48
|
+
patch?: Record<string, unknown> | null;
|
|
49
|
+
changedFields?: string[] | null;
|
|
50
|
+
reasonCode?: string | null;
|
|
51
|
+
reasonText?: string | null;
|
|
52
|
+
requestId?: string | null;
|
|
53
|
+
correlationId?: string | null;
|
|
54
|
+
traceId?: string | null;
|
|
55
|
+
spanId?: string | null;
|
|
56
|
+
intentId?: string | null;
|
|
57
|
+
sagaId?: string | null;
|
|
58
|
+
stepId?: string | null;
|
|
59
|
+
idempotencyKey?: string | null;
|
|
60
|
+
queueName?: string | null;
|
|
61
|
+
jobId?: string | null;
|
|
62
|
+
parentAuditEventId?: string | null;
|
|
63
|
+
readCount?: number | null;
|
|
64
|
+
firstReadAt?: Date | null;
|
|
65
|
+
lastReadAt?: Date | null;
|
|
66
|
+
metadata?: Record<string, unknown>;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The fully-resolved row handed to the writer (and, if wired, to the chain
|
|
70
|
+
* adapter). Snake_case names match the `<service>_audit_event` columns 1:1
|
|
71
|
+
* per doctrine § 3 — the writer can copy fields into the SQL INSERT without
|
|
72
|
+
* a translation layer.
|
|
73
|
+
*/
|
|
74
|
+
export interface AuditEventRow {
|
|
75
|
+
id: string;
|
|
76
|
+
tenant_id: string;
|
|
77
|
+
at: Date;
|
|
78
|
+
actor_kind: ActorKind;
|
|
79
|
+
actor_id: string | null;
|
|
80
|
+
actor_external_id: string | null;
|
|
81
|
+
actor_display: string | null;
|
|
82
|
+
actor_ip: string | null;
|
|
83
|
+
actor_user_agent: string | null;
|
|
84
|
+
actor: ActorStamp | null;
|
|
85
|
+
action: string;
|
|
86
|
+
resource_type: string;
|
|
87
|
+
resource_id: string | null;
|
|
88
|
+
resource_label: string | null;
|
|
89
|
+
secondary_resource_type: string | null;
|
|
90
|
+
secondary_resource_id: string | null;
|
|
91
|
+
outcome: AuditOutcome;
|
|
92
|
+
http_status: number | null;
|
|
93
|
+
grpc_code: string | null;
|
|
94
|
+
error_code: string | null;
|
|
95
|
+
error_message: string | null;
|
|
96
|
+
error_details: Record<string, unknown> | null;
|
|
97
|
+
before: Record<string, unknown> | null;
|
|
98
|
+
after: Record<string, unknown> | null;
|
|
99
|
+
patch: Record<string, unknown> | null;
|
|
100
|
+
changed_fields: string[] | null;
|
|
101
|
+
reason_code: string | null;
|
|
102
|
+
reason_text: string | null;
|
|
103
|
+
request_id: string | null;
|
|
104
|
+
correlation_id: string | null;
|
|
105
|
+
trace_id: string | null;
|
|
106
|
+
span_id: string | null;
|
|
107
|
+
intent_id: string | null;
|
|
108
|
+
saga_id: string | null;
|
|
109
|
+
step_id: string | null;
|
|
110
|
+
idempotency_key: string | null;
|
|
111
|
+
queue_name: string | null;
|
|
112
|
+
job_id: string | null;
|
|
113
|
+
parent_audit_event_id: string | null;
|
|
114
|
+
read_count: number | null;
|
|
115
|
+
first_read_at: Date | null;
|
|
116
|
+
last_read_at: Date | null;
|
|
117
|
+
prev_hash: Uint8Array | null;
|
|
118
|
+
row_hash: Uint8Array | null;
|
|
119
|
+
tenant_audit_seq: bigint | null;
|
|
120
|
+
metadata: Record<string, unknown>;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Legacy minimal shape from v0.1.0–v0.2.0. Still accepted by `audit.emit`
|
|
124
|
+
* so call sites in `@nodii/pii`, `@nodii/saga`, `@nodii/grpc-interceptors`
|
|
125
|
+
* continue to work unchanged. New callers should use `AuditArgs`.
|
|
126
|
+
*
|
|
127
|
+
* @deprecated Use `AuditArgs`. Mapping: `target_kind` → `resource_type`,
|
|
128
|
+
* `target_id` → `resource_id`, `payload` → `metadata`.
|
|
129
|
+
*/
|
|
1
130
|
export interface AuditEvent {
|
|
2
131
|
/** D33-locked vocabulary, e.g. `payment.refund.issued`. */
|
|
3
132
|
action: string;
|
|
4
|
-
/**
|
|
133
|
+
/** Maps to `resource_type` per doctrine § 3. */
|
|
5
134
|
target_kind: string;
|
|
6
|
-
/**
|
|
135
|
+
/** Maps to `resource_id` per doctrine § 3. */
|
|
7
136
|
target_id: string | null;
|
|
8
|
-
/**
|
|
137
|
+
/** Maps to `metadata` per doctrine § 3. */
|
|
9
138
|
payload?: Record<string, unknown>;
|
|
10
139
|
}
|
|
11
140
|
/**
|
|
12
|
-
* The pluggable Postgres-write path
|
|
13
|
-
*
|
|
14
|
-
* calling `_setAuditWriter` from a
|
|
15
|
-
*
|
|
141
|
+
* The pluggable Postgres-write path per doctrine § 15.1 + § 15.4.
|
|
142
|
+
*
|
|
143
|
+
* Services wire this at boot by calling `_setAuditWriter(...)` from a
|
|
144
|
+
* `@nodii/db-rls` adapter (or any service-supplied writer that implements
|
|
145
|
+
* this signature). The writer receives the canonical `AuditEventRow`
|
|
146
|
+
* matching `<service>_audit_event` columns 1:1.
|
|
147
|
+
*
|
|
148
|
+
* `tx` argument carries the in-flight transaction handle when `audit.emit`
|
|
149
|
+
* is called via Pattern A or B (in-tx); `undefined` for Pattern C
|
|
150
|
+
* (out-of-band — writer is responsible for opening its own short-lived tx
|
|
151
|
+
* scoped to `row.tenant_id`).
|
|
152
|
+
*
|
|
153
|
+
* v0.1.2 (drift `4cc80f62`) — the Noop default is GONE. If a service calls
|
|
154
|
+
* `audit.emit(...)` before wiring a real writer, the SDK throws
|
|
155
|
+
* `TelemetryAuditWriterMissing`. This closes the R1 violation in which
|
|
156
|
+
* audit rows were silently dropped because the lib defaulted to a no-op
|
|
157
|
+
* writer in production paths.
|
|
16
158
|
*/
|
|
17
|
-
export type AuditWriter = (row:
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
159
|
+
export type AuditWriter = (row: AuditEventRow, tx?: unknown) => Promise<void> | void;
|
|
160
|
+
/**
|
|
161
|
+
* Opt-in tamper-evident chain layer per doctrine § 13 + § 15.3. Wired via
|
|
162
|
+
* `configureTelemetry({audit: {chain}})` — typically from
|
|
163
|
+
* `@nodii/audit-chain`'s boot adapter.
|
|
164
|
+
*
|
|
165
|
+
* `augment` receives the canonical row (with `prev_hash` / `row_hash` /
|
|
166
|
+
* `tenant_audit_seq` all `null`) plus the in-flight tx (or `undefined`)
|
|
167
|
+
* and returns the computed chain triple. The implementation MUST acquire
|
|
168
|
+
* `pg_advisory_xact_lock(hash(tenant_id || 'audit'))` inside the tx so
|
|
169
|
+
* sequence allocation is per-tenant monotonic.
|
|
170
|
+
*
|
|
171
|
+
* Services that don't run the chain leave the three columns NULL; the
|
|
172
|
+
* library writes them as `null` to Postgres.
|
|
173
|
+
*/
|
|
174
|
+
export interface AuditChainAdapter {
|
|
175
|
+
augment(row: AuditEventRow, tx?: unknown): Promise<{
|
|
176
|
+
prev_hash: Uint8Array | null;
|
|
177
|
+
row_hash: Uint8Array;
|
|
178
|
+
tenant_audit_seq: bigint;
|
|
179
|
+
}>;
|
|
180
|
+
}
|
|
28
181
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
182
|
+
* Thrown by `audit.emit` when no `AuditWriter` has been wired. v0.1.2
|
|
183
|
+
* replaces the v0.1.0–v0.1.1 silent-Noop failure mode (R1 violation,
|
|
184
|
+
* drift `4cc80f62`). Services MUST wire a writer via `_setAuditWriter(...)`
|
|
185
|
+
* before the first `audit.emit` fires.
|
|
186
|
+
*/
|
|
187
|
+
export declare class TelemetryAuditWriterMissing extends Error {
|
|
188
|
+
constructor();
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* SDK-internal: services or the `@nodii/db-rls` adapter sets the writer.
|
|
192
|
+
* Public callers MUST NOT call this; the override exists for the adapter
|
|
193
|
+
* layer.
|
|
32
194
|
*/
|
|
33
195
|
export declare function _setAuditWriter(w: AuditWriter): void;
|
|
34
|
-
/** Test-only
|
|
196
|
+
/** Test-only reset. Clears the wired writer; the next `audit.emit` will throw. */
|
|
35
197
|
export declare function _resetAuditWriterForTests(): void;
|
|
36
198
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
|
|
40
|
-
|
|
199
|
+
* SDK-internal: `configureTelemetry({audit: {chain}})` calls this to wire
|
|
200
|
+
* the opt-in chain adapter. Setting `null` removes it.
|
|
201
|
+
*/
|
|
202
|
+
export declare function _setAuditChain(adapter: AuditChainAdapter | null): void;
|
|
203
|
+
/** Test-only reset. Removes the wired chain adapter. */
|
|
204
|
+
export declare function _resetAuditChainForTests(): void;
|
|
205
|
+
/** SDK-internal accessor for tests + chain-layer integration probes. */
|
|
206
|
+
export declare function _getAuditChain(): AuditChainAdapter | null;
|
|
207
|
+
/**
|
|
208
|
+
* Pattern A / B (in-tx) — atomic with the mutation. Writer receives the
|
|
209
|
+
* caller's transaction handle and writes the audit row inside it.
|
|
210
|
+
*/
|
|
211
|
+
declare function emit(tx: unknown, args: AuditArgs): Promise<void>;
|
|
212
|
+
/** Pattern C (out-of-band) — writer opens its own tenant-scoped tx. */
|
|
213
|
+
declare function emit(args: AuditArgs): Promise<void>;
|
|
214
|
+
/**
|
|
215
|
+
* Backward-compatible overload for the legacy minimal shape. Maps onto
|
|
216
|
+
* `AuditArgs` per doctrine § 3 before entering the shared write path.
|
|
217
|
+
*
|
|
218
|
+
* @deprecated Use the rich `AuditArgs` form. This overload exists so the
|
|
219
|
+
* v0.2.x call sites in `@nodii/pii`, `@nodii/saga`, `@nodii/grpc-
|
|
220
|
+
* interceptors` keep working unchanged while their authors migrate.
|
|
41
221
|
*/
|
|
42
222
|
declare function emit(event: AuditEvent): Promise<void>;
|
|
43
223
|
export declare const audit: {
|
|
44
224
|
readonly emit: typeof emit;
|
|
45
225
|
};
|
|
46
|
-
export {};
|
|
47
226
|
//# sourceMappingURL=audit.d.ts.map
|
package/dist/audit.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"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"}
|
package/dist/audit.js
CHANGED
|
@@ -1,77 +1,264 @@
|
|
|
1
|
-
// Audit signal class
|
|
1
|
+
// Audit signal class — the canonical adapter per `02-audit-doctrine.md`
|
|
2
|
+
// § 3 (row shape) + § 6.0 (Patterns A/B/C tx-bound) + § 13 (opt-in chain).
|
|
2
3
|
//
|
|
3
4
|
// "If a tenant could legitimately need to see this row, it's audit, not
|
|
4
|
-
// log." — D17 cultural rule.
|
|
5
|
+
// log." — D17 cultural rule, doctrine § 1.
|
|
5
6
|
//
|
|
6
7
|
// The library ships the adapter (the write path); the audit emission
|
|
7
8
|
// policy (D33 three paths: method-level interceptor, saga lifecycle,
|
|
8
|
-
// agent-layer double-emission) and the
|
|
9
|
-
// audit
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
9
|
+
// agent-layer double-emission) and the `<service>_audit_event` schema
|
|
10
|
+
// live in `02-audit-doctrine.md`, not here. The Drizzle schema fragment
|
|
11
|
+
// for the table ships from `@nodii/telemetry/drizzle`.
|
|
12
|
+
//
|
|
13
|
+
// Three emission patterns per § 6.0:
|
|
14
|
+
// - Pattern A — explicit tx pass: `audit.emit(tx, args)`
|
|
15
|
+
// - Pattern B — Hono `c.set("txn", tx)` middleware: also `audit.emit(tx, args)`
|
|
16
|
+
// - Pattern C — out-of-band: `audit.emit(args)` (writer owns its tx)
|
|
17
|
+
import { AUDIT_ACTOR_KINDS, AUDIT_OUTCOMES, } from "./audit-types";
|
|
13
18
|
import { getContext } from "./context";
|
|
14
19
|
import { requireTelemetry } from "./init";
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
import { uuidv7 } from "./uuid";
|
|
21
|
+
export { AUDIT_ACTOR_KINDS, AUDIT_OUTCOMES };
|
|
22
|
+
/**
|
|
23
|
+
* Thrown by `audit.emit` when no `AuditWriter` has been wired. v0.1.2
|
|
24
|
+
* replaces the v0.1.0–v0.1.1 silent-Noop failure mode (R1 violation,
|
|
25
|
+
* drift `4cc80f62`). Services MUST wire a writer via `_setAuditWriter(...)`
|
|
26
|
+
* before the first `audit.emit` fires.
|
|
27
|
+
*/
|
|
28
|
+
export class TelemetryAuditWriterMissing extends Error {
|
|
29
|
+
constructor() {
|
|
30
|
+
super("@nodii/telemetry: audit.emit called before an AuditWriter was wired. " +
|
|
31
|
+
"Drift 4cc80f62 (R1, breaking) removed the v0.1.0–v0.1.1 Noop default " +
|
|
32
|
+
"because it silently dropped audit rows. Wire a real writer via " +
|
|
33
|
+
"`_setAuditWriter(...)` (typically from `@nodii/db-rls`'s adapter " +
|
|
34
|
+
"writing to <serviceName>_audit_event per 02-audit-doctrine.md § 6.0).");
|
|
35
|
+
this.name = "TelemetryAuditWriterMissing";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
let writer = null;
|
|
39
|
+
let chain = null;
|
|
19
40
|
/**
|
|
20
|
-
* SDK-internal: services or the `@nodii/db-rls` adapter sets the
|
|
21
|
-
*
|
|
22
|
-
*
|
|
41
|
+
* SDK-internal: services or the `@nodii/db-rls` adapter sets the writer.
|
|
42
|
+
* Public callers MUST NOT call this; the override exists for the adapter
|
|
43
|
+
* layer.
|
|
23
44
|
*/
|
|
24
45
|
export function _setAuditWriter(w) {
|
|
25
46
|
writer = w;
|
|
26
47
|
}
|
|
27
|
-
/** Test-only
|
|
48
|
+
/** Test-only reset. Clears the wired writer; the next `audit.emit` will throw. */
|
|
28
49
|
export function _resetAuditWriterForTests() {
|
|
29
|
-
writer =
|
|
50
|
+
writer = null;
|
|
30
51
|
}
|
|
31
52
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* NOT best-effort the way logs are — D33 requires durable emission for
|
|
35
|
-
* mutations).
|
|
53
|
+
* SDK-internal: `configureTelemetry({audit: {chain}})` calls this to wire
|
|
54
|
+
* the opt-in chain adapter. Setting `null` removes it.
|
|
36
55
|
*/
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
56
|
+
export function _setAuditChain(adapter) {
|
|
57
|
+
chain = adapter;
|
|
58
|
+
}
|
|
59
|
+
/** Test-only reset. Removes the wired chain adapter. */
|
|
60
|
+
export function _resetAuditChainForTests() {
|
|
61
|
+
chain = null;
|
|
62
|
+
}
|
|
63
|
+
/** SDK-internal accessor for tests + chain-layer integration probes. */
|
|
64
|
+
export function _getAuditChain() {
|
|
65
|
+
return chain;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Type guard distinguishing the legacy `AuditEvent` from the rich `AuditArgs`.
|
|
69
|
+
* The discriminator is `target_kind`: present on the legacy shape, absent on
|
|
70
|
+
* the rich shape (which uses `resourceType`). Both shapes carry `action`.
|
|
71
|
+
*/
|
|
72
|
+
function isLegacyEvent(arg) {
|
|
73
|
+
return ("target_kind" in arg &&
|
|
74
|
+
typeof arg.target_kind === "string" &&
|
|
75
|
+
!("resourceType" in arg));
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Map the legacy shape onto the rich `AuditArgs` so the rest of the
|
|
79
|
+
* pipeline only handles one input form. Mapping per doctrine § 3:
|
|
80
|
+
* target_kind → resource_type
|
|
81
|
+
* target_id → resource_id
|
|
82
|
+
* payload → metadata
|
|
83
|
+
* Other rich fields are left undefined for `resolveRow` to fill from ctx.
|
|
84
|
+
*/
|
|
85
|
+
function legacyToRich(event) {
|
|
86
|
+
// Back-compat — v0.1.x callers conventionally put `tenant_id` inside
|
|
87
|
+
// the `payload` object. v0.3.0's resolveRow requires it at the top
|
|
88
|
+
// level (or via NodiiContext). Pull it out here so existing callers
|
|
89
|
+
// keep working without forcing a 3-lang migration.
|
|
90
|
+
const payload = event.payload ?? {};
|
|
91
|
+
const tenantIdRaw = payload.tenant_id;
|
|
92
|
+
const tenantId = typeof tenantIdRaw === "string" && tenantIdRaw.length > 0
|
|
93
|
+
? tenantIdRaw
|
|
94
|
+
: undefined;
|
|
95
|
+
return {
|
|
43
96
|
action: event.action,
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
intent_id: ctx?.intent_id ?? null,
|
|
50
|
-
ts: new Date().toISOString(),
|
|
51
|
-
});
|
|
97
|
+
resourceType: event.target_kind,
|
|
98
|
+
resourceId: event.target_id,
|
|
99
|
+
tenantId,
|
|
100
|
+
metadata: payload,
|
|
101
|
+
};
|
|
52
102
|
}
|
|
53
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Derive the canonical `actor_kind` from a `NodiiContext`. Falls back to
|
|
105
|
+
* `'unknown'` per doctrine § 4.2 — the SDK MUST emit `unknown` when no
|
|
106
|
+
* actor stamp can be derived; this is a security-event marker, not a
|
|
107
|
+
* silent default.
|
|
108
|
+
*
|
|
109
|
+
* Mapping rules per § 4 + auth-sdk § 5.6:
|
|
110
|
+
* ctx.actor_type='user' → 'tenant_user' (callers override to 'employee_user' when platform-admin)
|
|
111
|
+
* ctx.actor_type='agent' → 'agent'
|
|
112
|
+
* ctx.actor_type='service' → 'service'
|
|
113
|
+
* ctx.actor_type='system' → 'system'
|
|
114
|
+
* ctx absent → 'unknown' (security marker)
|
|
115
|
+
*/
|
|
116
|
+
function deriveActorKind(ctx) {
|
|
54
117
|
if (!ctx)
|
|
55
|
-
return "
|
|
118
|
+
return "unknown";
|
|
56
119
|
switch (ctx.actor_type) {
|
|
57
120
|
case "user":
|
|
58
|
-
return
|
|
121
|
+
return "tenant_user";
|
|
59
122
|
case "agent":
|
|
60
|
-
return
|
|
61
|
-
? `agent:on_behalf_of:${ctx.on_behalf_of}`
|
|
62
|
-
: "agent:unknown";
|
|
123
|
+
return "agent";
|
|
63
124
|
case "service":
|
|
64
125
|
return "service";
|
|
65
126
|
case "system":
|
|
66
127
|
return "system";
|
|
67
128
|
default: {
|
|
68
|
-
// Exhaustiveness check.
|
|
129
|
+
// Exhaustiveness check + future-proofing if ActorType grows.
|
|
69
130
|
const _exhaustive = ctx.actor_type;
|
|
70
131
|
void _exhaustive;
|
|
71
132
|
return "unknown";
|
|
72
133
|
}
|
|
73
134
|
}
|
|
74
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* Derive the canonical `actor_id` from `NodiiContext`. For `actor_type='user'`
|
|
138
|
+
* use `user_id`; for `actor_type='agent'` use `on_behalf_of` (the human who
|
|
139
|
+
* authorised the dispatch — per doctrine § 4.1 agent extension); for service
|
|
140
|
+
* + system contexts no `actor_id` is derivable and the caller must supply
|
|
141
|
+
* one if needed.
|
|
142
|
+
*/
|
|
143
|
+
function deriveActorId(ctx) {
|
|
144
|
+
if (!ctx)
|
|
145
|
+
return null;
|
|
146
|
+
switch (ctx.actor_type) {
|
|
147
|
+
case "user":
|
|
148
|
+
return ctx.user_id;
|
|
149
|
+
case "agent":
|
|
150
|
+
return ctx.on_behalf_of;
|
|
151
|
+
case "service":
|
|
152
|
+
case "system":
|
|
153
|
+
return null;
|
|
154
|
+
default:
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Build the canonical `AuditEventRow` from `AuditArgs` + the active
|
|
160
|
+
* `NodiiContext`. Resolution rules (in order):
|
|
161
|
+
* 1. `tenant_id`: args.tenantId → ctx.tenant_id → throw
|
|
162
|
+
* 2. `actor_kind`: args.actorKind → derive from ctx → 'unknown'
|
|
163
|
+
* 3. `actor_id`: args.actorId → derive from ctx → null
|
|
164
|
+
* 4. `request_id`, `intent_id`: args → ctx → null
|
|
165
|
+
* 5. Other fields: pass through args (null when undefined)
|
|
166
|
+
*
|
|
167
|
+
* Throws `Error` when neither args nor ctx carry a `tenant_id` — the
|
|
168
|
+
* audit row cannot be RLS-bound without one, so silently dropping it
|
|
169
|
+
* would be a doctrine § 3 + R1 violation.
|
|
170
|
+
*/
|
|
171
|
+
function resolveRow(args) {
|
|
172
|
+
const ctx = getContext();
|
|
173
|
+
const tenantId = args.tenantId ?? ctx?.tenant_id ?? null;
|
|
174
|
+
if (!tenantId) {
|
|
175
|
+
throw new Error("audit.emit: tenant_id is required — pass `tenantId` in args or " +
|
|
176
|
+
"invoke inside a `withContext({tenant_id: ...})` scope. The audit " +
|
|
177
|
+
"row cannot be RLS-bound without it (02-audit-doctrine.md § 3).");
|
|
178
|
+
}
|
|
179
|
+
const actorKind = args.actorKind ?? deriveActorKind(ctx);
|
|
180
|
+
const actorId = args.actorId ?? deriveActorId(ctx);
|
|
181
|
+
return {
|
|
182
|
+
id: uuidv7(),
|
|
183
|
+
tenant_id: tenantId,
|
|
184
|
+
at: new Date(),
|
|
185
|
+
actor_kind: actorKind,
|
|
186
|
+
actor_id: actorId,
|
|
187
|
+
actor_external_id: args.actorExternalId ?? null,
|
|
188
|
+
actor_display: args.actorDisplay ?? null,
|
|
189
|
+
actor_ip: args.actorIp ?? null,
|
|
190
|
+
actor_user_agent: args.actorUserAgent ?? null,
|
|
191
|
+
actor: args.actor ?? null,
|
|
192
|
+
action: args.action,
|
|
193
|
+
resource_type: args.resourceType,
|
|
194
|
+
resource_id: args.resourceId ?? null,
|
|
195
|
+
resource_label: args.resourceLabel ?? null,
|
|
196
|
+
secondary_resource_type: args.secondaryResourceType ?? null,
|
|
197
|
+
secondary_resource_id: args.secondaryResourceId ?? null,
|
|
198
|
+
outcome: args.outcome ?? "success",
|
|
199
|
+
http_status: args.httpStatus ?? null,
|
|
200
|
+
grpc_code: args.grpcCode ?? null,
|
|
201
|
+
error_code: args.errorCode ?? null,
|
|
202
|
+
error_message: args.errorMessage ?? null,
|
|
203
|
+
error_details: args.errorDetails ?? null,
|
|
204
|
+
before: args.before ?? null,
|
|
205
|
+
after: args.after ?? null,
|
|
206
|
+
patch: args.patch ?? null,
|
|
207
|
+
changed_fields: args.changedFields ?? null,
|
|
208
|
+
reason_code: args.reasonCode ?? null,
|
|
209
|
+
reason_text: args.reasonText ?? null,
|
|
210
|
+
request_id: args.requestId ?? ctx?.request_id ?? null,
|
|
211
|
+
correlation_id: args.correlationId ?? null,
|
|
212
|
+
trace_id: args.traceId ?? null,
|
|
213
|
+
span_id: args.spanId ?? null,
|
|
214
|
+
intent_id: args.intentId ?? ctx?.intent_id ?? null,
|
|
215
|
+
saga_id: args.sagaId ?? null,
|
|
216
|
+
step_id: args.stepId ?? null,
|
|
217
|
+
idempotency_key: args.idempotencyKey ?? null,
|
|
218
|
+
queue_name: args.queueName ?? null,
|
|
219
|
+
job_id: args.jobId ?? null,
|
|
220
|
+
parent_audit_event_id: args.parentAuditEventId ?? null,
|
|
221
|
+
read_count: args.readCount ?? null,
|
|
222
|
+
first_read_at: args.firstReadAt ?? null,
|
|
223
|
+
last_read_at: args.lastReadAt ?? null,
|
|
224
|
+
// Chain columns left null unless an adapter is wired (see emitImpl).
|
|
225
|
+
prev_hash: null,
|
|
226
|
+
row_hash: null,
|
|
227
|
+
tenant_audit_seq: null,
|
|
228
|
+
metadata: args.metadata ?? {},
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Shared write path. Resolves the row, optionally runs the chain adapter,
|
|
233
|
+
* and hands the row + tx to the writer. Throws `TelemetryAuditWriterMissing`
|
|
234
|
+
* when no writer is wired (drift `4cc80f62` enforcement).
|
|
235
|
+
*/
|
|
236
|
+
async function emitImpl(args, tx) {
|
|
237
|
+
requireTelemetry();
|
|
238
|
+
if (writer === null) {
|
|
239
|
+
throw new TelemetryAuditWriterMissing();
|
|
240
|
+
}
|
|
241
|
+
const row = resolveRow(args);
|
|
242
|
+
if (chain !== null) {
|
|
243
|
+
const augmented = await chain.augment(row, tx);
|
|
244
|
+
row.prev_hash = augmented.prev_hash;
|
|
245
|
+
row.row_hash = augmented.row_hash;
|
|
246
|
+
row.tenant_audit_seq = augmented.tenant_audit_seq;
|
|
247
|
+
}
|
|
248
|
+
await writer(row, tx);
|
|
249
|
+
}
|
|
250
|
+
async function emit(a, b) {
|
|
251
|
+
if (b !== undefined) {
|
|
252
|
+
// emit(tx, args) — Pattern A / B.
|
|
253
|
+
return emitImpl(b, a);
|
|
254
|
+
}
|
|
255
|
+
const arg = a;
|
|
256
|
+
if (isLegacyEvent(arg)) {
|
|
257
|
+
return emitImpl(legacyToRich(arg));
|
|
258
|
+
}
|
|
259
|
+
// emit(args) — Pattern C.
|
|
260
|
+
return emitImpl(arg);
|
|
261
|
+
}
|
|
75
262
|
export const audit = {
|
|
76
263
|
emit,
|
|
77
264
|
};
|