hazo_audit 1.0.0
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/CHANGE_LOG.md +53 -0
- package/README.md +269 -0
- package/SETUP_CHECKLIST.md +186 -0
- package/bin/hazo-audit-migrate.js +169 -0
- package/config/hazo_audit_config.ini.sample +71 -0
- package/dist/client/FieldAuditIcon.d.ts +11 -0
- package/dist/client/FieldAuditIcon.d.ts.map +1 -0
- package/dist/client/FieldAuditIcon.js +46 -0
- package/dist/client/FieldAuditIcon.js.map +1 -0
- package/dist/client/FieldAuditTrail.d.ts +8 -0
- package/dist/client/FieldAuditTrail.d.ts.map +1 -0
- package/dist/client/FieldAuditTrail.js +136 -0
- package/dist/client/FieldAuditTrail.js.map +1 -0
- package/dist/client/cn.d.ts +3 -0
- package/dist/client/cn.d.ts.map +1 -0
- package/dist/client/cn.js +6 -0
- package/dist/client/cn.js.map +1 -0
- package/dist/client/index.d.ts +11 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +9 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/types.d.ts +19 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +2 -0
- package/dist/client/types.js.map +1 -0
- package/dist/index.d.ts +76 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/nextjs/index.d.ts +78 -0
- package/dist/nextjs/index.d.ts.map +1 -0
- package/dist/nextjs/index.js +229 -0
- package/dist/nextjs/index.js.map +1 -0
- package/dist/server/audited_crud.d.ts +42 -0
- package/dist/server/audited_crud.d.ts.map +1 -0
- package/dist/server/audited_crud.js +57 -0
- package/dist/server/audited_crud.js.map +1 -0
- package/dist/server/config.d.ts +70 -0
- package/dist/server/config.d.ts.map +1 -0
- package/dist/server/config.js +203 -0
- package/dist/server/config.js.map +1 -0
- package/dist/server/context.d.ts +28 -0
- package/dist/server/context.d.ts.map +1 -0
- package/dist/server/context.js +49 -0
- package/dist/server/context.js.map +1 -0
- package/dist/server/diff.d.ts +32 -0
- package/dist/server/diff.d.ts.map +1 -0
- package/dist/server/diff.js +152 -0
- package/dist/server/diff.js.map +1 -0
- package/dist/server/index.d.ts +23 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +26 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/outbox.d.ts +29 -0
- package/dist/server/outbox.d.ts.map +1 -0
- package/dist/server/outbox.js +53 -0
- package/dist/server/outbox.js.map +1 -0
- package/dist/server/worker.d.ts +43 -0
- package/dist/server/worker.d.ts.map +1 -0
- package/dist/server/worker.js +248 -0
- package/dist/server/worker.js.map +1 -0
- package/dist/server/wrap.d.ts +32 -0
- package/dist/server/wrap.d.ts.map +1 -0
- package/dist/server/wrap.js +232 -0
- package/dist/server/wrap.js.map +1 -0
- package/migrations/001_init.sql +149 -0
- package/package.json +101 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audit worker: drains hazo_audit_outbox into hazo_audit_field /
|
|
3
|
+
* hazo_audit_intent. Uses hazo_connect's claimRows() primitive for
|
|
4
|
+
* concurrency-safe row claiming with attempt-count + backoff.
|
|
5
|
+
*/
|
|
6
|
+
import 'server-only';
|
|
7
|
+
import { randomUUID } from 'node:crypto';
|
|
8
|
+
import { createCrudService, QueryBuilder } from 'hazo_connect/server';
|
|
9
|
+
import { loadAuditConfig, getOverride } from './config.js';
|
|
10
|
+
import { diffRows } from './diff.js';
|
|
11
|
+
/** JSONB-shaped value → JSON string for adapters that can't bind objects. */
|
|
12
|
+
function toJsonText(v) {
|
|
13
|
+
if (v == null)
|
|
14
|
+
return null;
|
|
15
|
+
if (typeof v === 'string')
|
|
16
|
+
return v;
|
|
17
|
+
try {
|
|
18
|
+
return JSON.stringify(v);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** Tries to parse a JSON column that may already be parsed as an object. */
|
|
25
|
+
function parseJson(v) {
|
|
26
|
+
if (v == null)
|
|
27
|
+
return null;
|
|
28
|
+
if (typeof v === 'object')
|
|
29
|
+
return v;
|
|
30
|
+
if (typeof v === 'string') {
|
|
31
|
+
try {
|
|
32
|
+
const parsed = JSON.parse(v);
|
|
33
|
+
return parsed && typeof parsed === 'object' ? parsed : null;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Find the most-recent drained outbox row for a subject (used to chain
|
|
43
|
+
* before_row when the producer didn't supply one).
|
|
44
|
+
*/
|
|
45
|
+
async function findPriorAfterRow(adapter, subjectKind, subjectId, excludeId) {
|
|
46
|
+
try {
|
|
47
|
+
const qb = new QueryBuilder()
|
|
48
|
+
.from('hazo_audit_outbox')
|
|
49
|
+
.select('after_row, created_at')
|
|
50
|
+
.where('subject_kind', 'eq', subjectKind)
|
|
51
|
+
.where('subject_id', 'eq', subjectId)
|
|
52
|
+
.where('id', 'neq', excludeId)
|
|
53
|
+
.where('drained_at', 'is', 'not.null')
|
|
54
|
+
.order('created_at', 'desc')
|
|
55
|
+
.limit(1);
|
|
56
|
+
const rows = await adapter.query(qb, 'GET');
|
|
57
|
+
if (Array.isArray(rows) && rows.length > 0) {
|
|
58
|
+
return parseJson(rows[0].after_row);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// Best-effort; if chaining fails, treat as no prior state.
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
async function processRow(row, appAdapter, auditAdapter, cfg, resolvers) {
|
|
67
|
+
const override = getOverride(row.table_name, cfg);
|
|
68
|
+
const resolver = override.resolver ? resolvers[override.resolver] : undefined;
|
|
69
|
+
// Hydrate before/after — they may arrive as JSON strings on some adapters.
|
|
70
|
+
let after = parseJson(row.after_row);
|
|
71
|
+
let before = parseJson(row.before_row);
|
|
72
|
+
// Lazy before-row chain (PRD §7.3) — only for UPDATE.
|
|
73
|
+
if (row.op === 'update' && !before) {
|
|
74
|
+
before = await findPriorAfterRow(appAdapter, row.subject_kind, row.subject_id, row.id);
|
|
75
|
+
}
|
|
76
|
+
// For UPDATE, after may be just the patch slice; merge atop the chained
|
|
77
|
+
// before so unchanged fields aren't reported as changes.
|
|
78
|
+
if (row.op === 'update' && before && after) {
|
|
79
|
+
after = { ...before, ...after };
|
|
80
|
+
}
|
|
81
|
+
const changes = diffRows({
|
|
82
|
+
before,
|
|
83
|
+
after,
|
|
84
|
+
op: row.op,
|
|
85
|
+
override,
|
|
86
|
+
resolver,
|
|
87
|
+
});
|
|
88
|
+
// Emit field events.
|
|
89
|
+
const fieldSvc = createCrudService(auditAdapter, 'hazo_audit_field', {
|
|
90
|
+
autoId: { enabled: true, column: 'id' },
|
|
91
|
+
});
|
|
92
|
+
if (changes.length > 0) {
|
|
93
|
+
await fieldSvc.insert(changes.map((c) => ({
|
|
94
|
+
id: randomUUID(),
|
|
95
|
+
scope_id: row.scope_id,
|
|
96
|
+
correlation_id: row.correlation_id,
|
|
97
|
+
subject_kind: row.subject_kind,
|
|
98
|
+
subject_id: row.subject_id,
|
|
99
|
+
field_path: c.field_path,
|
|
100
|
+
op: c.op,
|
|
101
|
+
before_value: toJsonText(c.before_value),
|
|
102
|
+
after_value: toJsonText(c.after_value),
|
|
103
|
+
is_sensitive: c.is_sensitive ? 1 : 0,
|
|
104
|
+
actor_kind: row.actor_kind,
|
|
105
|
+
actor_user_id: row.actor_user_id,
|
|
106
|
+
actor_label: row.actor_label,
|
|
107
|
+
})));
|
|
108
|
+
}
|
|
109
|
+
// Emit intent event if attached to this outbox row.
|
|
110
|
+
if (row.intent_event) {
|
|
111
|
+
const intentSvc = createCrudService(auditAdapter, 'hazo_audit_intent', {
|
|
112
|
+
autoId: { enabled: true, column: 'id' },
|
|
113
|
+
});
|
|
114
|
+
await intentSvc.insert({
|
|
115
|
+
id: randomUUID(),
|
|
116
|
+
scope_id: row.scope_id,
|
|
117
|
+
correlation_id: row.correlation_id,
|
|
118
|
+
event_name: row.intent_event,
|
|
119
|
+
payload: toJsonText(row.intent_payload),
|
|
120
|
+
subject_kind: row.subject_kind,
|
|
121
|
+
subject_id: row.subject_id,
|
|
122
|
+
actor_kind: row.actor_kind,
|
|
123
|
+
actor_user_id: row.actor_user_id,
|
|
124
|
+
actor_label: row.actor_label,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
// Mark outbox row drained.
|
|
128
|
+
const markQb = new QueryBuilder()
|
|
129
|
+
.from('hazo_audit_outbox')
|
|
130
|
+
.where('id', 'eq', row.id);
|
|
131
|
+
await appAdapter.query(markQb, 'PATCH', {
|
|
132
|
+
drained_at: new Date().toISOString(),
|
|
133
|
+
failed_at: null,
|
|
134
|
+
failure_reason: null,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
async function markFailure(adapter, id, attemptCount, maxAttempts, reason) {
|
|
138
|
+
const patch = {
|
|
139
|
+
failure_reason: reason.slice(0, 1000),
|
|
140
|
+
last_attempt_at: new Date().toISOString(),
|
|
141
|
+
};
|
|
142
|
+
if (attemptCount >= maxAttempts) {
|
|
143
|
+
patch.failed_at = new Date().toISOString();
|
|
144
|
+
}
|
|
145
|
+
const qb = new QueryBuilder()
|
|
146
|
+
.from('hazo_audit_outbox')
|
|
147
|
+
.where('id', 'eq', id);
|
|
148
|
+
await adapter.query(qb, 'PATCH', patch);
|
|
149
|
+
}
|
|
150
|
+
async function reapOnce(adapter, cfg) {
|
|
151
|
+
const drainedCutoff = new Date(Date.now() - cfg.retention.outbox_drained_ttl_days * 86400000).toISOString();
|
|
152
|
+
const failedCutoff = new Date(Date.now() - cfg.retention.outbox_failed_ttl_days * 86400000).toISOString();
|
|
153
|
+
try {
|
|
154
|
+
const drainedQb = new QueryBuilder()
|
|
155
|
+
.from('hazo_audit_outbox')
|
|
156
|
+
.where('drained_at', 'lt', drainedCutoff);
|
|
157
|
+
await adapter.query(drainedQb, 'DELETE');
|
|
158
|
+
const failedQb = new QueryBuilder()
|
|
159
|
+
.from('hazo_audit_outbox')
|
|
160
|
+
.where('failed_at', 'lt', failedCutoff);
|
|
161
|
+
await adapter.query(failedQb, 'DELETE');
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
/* best-effort */
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
export function startAuditWorker(opts) {
|
|
168
|
+
const cfg = opts.config ?? loadAuditConfig(opts.config_path);
|
|
169
|
+
const auditAdapter = opts.audit_adapter ?? opts.app_adapter;
|
|
170
|
+
const pollMs = opts.poll_ms ?? cfg.worker.poll_ms;
|
|
171
|
+
const maxAttempts = opts.max_attempts ?? cfg.worker.max_attempts;
|
|
172
|
+
const resolvers = opts.resolvers ?? {};
|
|
173
|
+
async function runOnce() {
|
|
174
|
+
let processed = 0;
|
|
175
|
+
let failed = 0;
|
|
176
|
+
let claimed = [];
|
|
177
|
+
try {
|
|
178
|
+
claimed = (await opts.app_adapter.claimRows({
|
|
179
|
+
table: 'hazo_audit_outbox',
|
|
180
|
+
where: {
|
|
181
|
+
drained_at: null,
|
|
182
|
+
failed_at: null,
|
|
183
|
+
attempt_count: { lt: maxAttempts },
|
|
184
|
+
},
|
|
185
|
+
set: {
|
|
186
|
+
attempt_count: { increment: 1 },
|
|
187
|
+
last_attempt_at: { $now: true },
|
|
188
|
+
},
|
|
189
|
+
returning: ['*'],
|
|
190
|
+
limit: 50,
|
|
191
|
+
}));
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
195
|
+
opts.onWorkerError?.(e);
|
|
196
|
+
return { processed: 0, failed: 0 };
|
|
197
|
+
}
|
|
198
|
+
for (const row of claimed) {
|
|
199
|
+
try {
|
|
200
|
+
await processRow(row, opts.app_adapter, auditAdapter, cfg, resolvers);
|
|
201
|
+
processed++;
|
|
202
|
+
}
|
|
203
|
+
catch (err) {
|
|
204
|
+
failed++;
|
|
205
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
206
|
+
opts.onWorkerError?.(e, row);
|
|
207
|
+
try {
|
|
208
|
+
await markFailure(opts.app_adapter, row.id, row.attempt_count + 1, maxAttempts, e.message);
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
/* swallow — next tick will retry */
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return { processed, failed };
|
|
216
|
+
}
|
|
217
|
+
let pollHandle = null;
|
|
218
|
+
let reaperHandle = null;
|
|
219
|
+
let stopped = false;
|
|
220
|
+
if (pollMs > 0) {
|
|
221
|
+
pollHandle = setInterval(() => {
|
|
222
|
+
if (stopped)
|
|
223
|
+
return;
|
|
224
|
+
runOnce().catch((err) => {
|
|
225
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
226
|
+
opts.onWorkerError?.(e);
|
|
227
|
+
});
|
|
228
|
+
}, pollMs);
|
|
229
|
+
}
|
|
230
|
+
if (cfg.worker.reaper_interval_ms > 0) {
|
|
231
|
+
reaperHandle = setInterval(() => {
|
|
232
|
+
if (stopped)
|
|
233
|
+
return;
|
|
234
|
+
reapOnce(opts.app_adapter, cfg).catch(() => { });
|
|
235
|
+
}, cfg.worker.reaper_interval_ms);
|
|
236
|
+
}
|
|
237
|
+
return {
|
|
238
|
+
async stop() {
|
|
239
|
+
stopped = true;
|
|
240
|
+
if (pollHandle)
|
|
241
|
+
clearInterval(pollHandle);
|
|
242
|
+
if (reaperHandle)
|
|
243
|
+
clearInterval(reaperHandle);
|
|
244
|
+
},
|
|
245
|
+
drainOnce: runOnce,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
//# sourceMappingURL=worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker.js","sourceRoot":"","sources":["../../src/server/worker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* wrapWithAudit — returns an adapter proxy that auto-enqueues outbox rows
|
|
3
|
+
* for every write to a captured table. Reads/getConfig pass through.
|
|
4
|
+
*
|
|
5
|
+
* Failure semantics (PRD §7.4): outbox enqueue failures are reported via
|
|
6
|
+
* onAuditFailure but never throw into the user write path. Subject extraction
|
|
7
|
+
* failures log a skipped-warning and continue without auditing.
|
|
8
|
+
*/
|
|
9
|
+
import 'server-only';
|
|
10
|
+
import type { HazoConnectAdapter } from 'hazo_connect';
|
|
11
|
+
import { type AuditConfig } from './config.js';
|
|
12
|
+
import { type OutboxFailureHandler } from './outbox.js';
|
|
13
|
+
import type { PathResolver } from '../index.js';
|
|
14
|
+
export interface WrapWithAuditOptions {
|
|
15
|
+
/** Adapter used to write outbox rows. Defaults to the wrapped adapter. */
|
|
16
|
+
auditAdapter?: HazoConnectAdapter;
|
|
17
|
+
/** Named JSONB path resolvers, referenced by name in INI per-table config. */
|
|
18
|
+
resolvers?: Record<string, PathResolver>;
|
|
19
|
+
/** Called for any outbox enqueue failure; never re-thrown. */
|
|
20
|
+
onAuditFailure?: OutboxFailureHandler;
|
|
21
|
+
/** Optional INI path override. Defaults to standard search order. */
|
|
22
|
+
config_path?: string;
|
|
23
|
+
/** Pre-loaded config (escape hatch for tests). Bypasses file IO. */
|
|
24
|
+
config?: AuditConfig;
|
|
25
|
+
/** Optional logger for skip warnings / debug. */
|
|
26
|
+
logger?: {
|
|
27
|
+
debug?: (msg: string, data?: Record<string, unknown>) => void;
|
|
28
|
+
warn?: (msg: string, data?: Record<string, unknown>) => void;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export declare function wrapWithAudit(adapter: HazoConnectAdapter, opts?: WrapWithAuditOptions): HazoConnectAdapter;
|
|
32
|
+
//# sourceMappingURL=wrap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wrap.d.ts","sourceRoot":"","sources":["../../src/server/wrap.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,aAAa,CAAC;AAErB,OAAO,KAAK,EACV,kBAAkB,EAEnB,MAAM,cAAc,CAAC;AACtB,OAAO,EAML,KAAK,WAAW,EACjB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAiB,KAAK,oBAAoB,EAAkB,MAAM,aAAa,CAAC;AACvF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,WAAW,oBAAoB;IACnC,0EAA0E;IAC1E,YAAY,CAAC,EAAE,kBAAkB,CAAC;IAClC,8EAA8E;IAC9E,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACzC,8DAA8D;IAC9D,cAAc,CAAC,EAAE,oBAAoB,CAAC;IACtC,qEAAqE;IACrE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oEAAoE;IACpE,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,iDAAiD;IACjD,MAAM,CAAC,EAAE;QACP,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;QAC9D,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;KAC9D,CAAC;CACH;AAmLD,wBAAgB,aAAa,CAC3B,OAAO,EAAE,kBAAkB,EAC3B,IAAI,GAAE,oBAAyB,GAC9B,kBAAkB,CA8GpB"}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* wrapWithAudit — returns an adapter proxy that auto-enqueues outbox rows
|
|
3
|
+
* for every write to a captured table. Reads/getConfig pass through.
|
|
4
|
+
*
|
|
5
|
+
* Failure semantics (PRD §7.4): outbox enqueue failures are reported via
|
|
6
|
+
* onAuditFailure but never throw into the user write path. Subject extraction
|
|
7
|
+
* failures log a skipped-warning and continue without auditing.
|
|
8
|
+
*/
|
|
9
|
+
import 'server-only';
|
|
10
|
+
import { randomUUID } from 'node:crypto';
|
|
11
|
+
import { loadAuditConfig, shouldCapture, getSubjectKind, getSubjectIdColumn, getOverride, } from './config.js';
|
|
12
|
+
import { getAuditContext, takeAuditHints } from './context.js';
|
|
13
|
+
import { enqueueOutbox } from './outbox.js';
|
|
14
|
+
const WRITE_METHODS = new Set(['POST', 'PATCH', 'PUT', 'DELETE']);
|
|
15
|
+
function defaultActor() {
|
|
16
|
+
const ctx = getAuditContext();
|
|
17
|
+
return {
|
|
18
|
+
actor_kind: ctx?.actor_kind ?? 'background_job',
|
|
19
|
+
actor_user_id: ctx?.actor_user_id ?? null,
|
|
20
|
+
actor_label: ctx?.actor_label ?? null,
|
|
21
|
+
scope_id: ctx?.scope_id ?? null,
|
|
22
|
+
correlation_id: ctx?.correlation_id ?? randomUUID(),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Find the value of the configured subject id column in a WHERE clause
|
|
27
|
+
* containing an `eq` filter. Returns undefined if not present.
|
|
28
|
+
*/
|
|
29
|
+
function extractSubjectIdFromWhere(conditions, idColumn) {
|
|
30
|
+
const match = conditions.find((c) => c.field === idColumn && (c.operator === 'eq' || c.operator === 'in'));
|
|
31
|
+
if (!match)
|
|
32
|
+
return undefined;
|
|
33
|
+
if (match.operator === 'in') {
|
|
34
|
+
return Array.isArray(match.value) && match.value.length === 1
|
|
35
|
+
? String(match.value[0])
|
|
36
|
+
: undefined;
|
|
37
|
+
}
|
|
38
|
+
return match.value != null ? String(match.value) : undefined;
|
|
39
|
+
}
|
|
40
|
+
function extractSubjectIdFromRow(row, idColumn) {
|
|
41
|
+
if (!row)
|
|
42
|
+
return undefined;
|
|
43
|
+
const v = row[idColumn];
|
|
44
|
+
return v == null ? undefined : String(v);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Best-effort: derive the list of (subject_id, before_row, after_row) tuples
|
|
48
|
+
* from one underlying write call. R1 supports single-row writes; bulk writes
|
|
49
|
+
* are skipped unless the table opts into `fanout_bulk_writes`.
|
|
50
|
+
*/
|
|
51
|
+
async function deriveOutboxRows(args) {
|
|
52
|
+
const { method, table, body, result, whereConditions, cfg, logger } = args;
|
|
53
|
+
const override = getOverride(table, cfg);
|
|
54
|
+
const idColumn = getSubjectIdColumn(table, cfg);
|
|
55
|
+
const op = method === 'POST' ? 'insert' : method === 'DELETE' ? 'delete' : 'update';
|
|
56
|
+
// ── INSERT ───────────────────────────────────────────────────────────────
|
|
57
|
+
if (op === 'insert') {
|
|
58
|
+
const inserted = Array.isArray(result)
|
|
59
|
+
? result
|
|
60
|
+
: result && typeof result === 'object'
|
|
61
|
+
? [result]
|
|
62
|
+
: [];
|
|
63
|
+
const bodyArr = Array.isArray(body)
|
|
64
|
+
? body
|
|
65
|
+
: body && typeof body === 'object'
|
|
66
|
+
? [body]
|
|
67
|
+
: [];
|
|
68
|
+
// If response is empty (no Prefer: return=representation), we can only
|
|
69
|
+
// recover ids from the body. For single-row POST, this works if the caller
|
|
70
|
+
// provided an id explicitly.
|
|
71
|
+
const rows = inserted.length > 0 ? inserted : bodyArr;
|
|
72
|
+
if (rows.length === 0) {
|
|
73
|
+
logger?.warn?.('audit.skipped_unsupported_shape', {
|
|
74
|
+
table,
|
|
75
|
+
op,
|
|
76
|
+
reason: 'insert returned no rows and body is empty',
|
|
77
|
+
});
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
if (rows.length > 1 && !override.fanout_bulk_writes) {
|
|
81
|
+
logger?.warn?.('audit.skipped_unsupported_shape', {
|
|
82
|
+
table,
|
|
83
|
+
op,
|
|
84
|
+
reason: 'bulk insert; enable fanout_bulk_writes to capture',
|
|
85
|
+
count: rows.length,
|
|
86
|
+
});
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
const out = [];
|
|
90
|
+
for (const row of rows) {
|
|
91
|
+
const id = extractSubjectIdFromRow(row, idColumn);
|
|
92
|
+
if (!id) {
|
|
93
|
+
logger?.warn?.('audit.skipped_unsupported_shape', {
|
|
94
|
+
table,
|
|
95
|
+
op,
|
|
96
|
+
reason: `insert response missing ${idColumn}`,
|
|
97
|
+
});
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
out.push({ op, subject_id: id, after_row: row, before_row: null });
|
|
101
|
+
}
|
|
102
|
+
return out;
|
|
103
|
+
}
|
|
104
|
+
// ── UPDATE / DELETE ──────────────────────────────────────────────────────
|
|
105
|
+
const id = extractSubjectIdFromWhere(whereConditions, idColumn);
|
|
106
|
+
if (!id) {
|
|
107
|
+
// Non-id WHERE clause — bulk update/delete shape. Skip unless opted in.
|
|
108
|
+
if (!override.fanout_bulk_writes) {
|
|
109
|
+
logger?.warn?.('audit.skipped_unsupported_shape', {
|
|
110
|
+
table,
|
|
111
|
+
op,
|
|
112
|
+
reason: `where clause lacks eq on ${idColumn}`,
|
|
113
|
+
});
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
// Fanout path not implemented for R1 (PRD §7.5 marks as opt-in extension).
|
|
117
|
+
// Would require pre-write GET; punt with a warning.
|
|
118
|
+
logger?.warn?.('audit.skipped_unsupported_shape', {
|
|
119
|
+
table,
|
|
120
|
+
op,
|
|
121
|
+
reason: 'fanout_bulk_writes via pre-write GET not implemented in R1',
|
|
122
|
+
});
|
|
123
|
+
return [];
|
|
124
|
+
}
|
|
125
|
+
if (op === 'delete') {
|
|
126
|
+
return [{ op, subject_id: id, after_row: null, before_row: null }];
|
|
127
|
+
}
|
|
128
|
+
// update — body is the patch; expose it as after_row's patch slice.
|
|
129
|
+
// Note: it's a partial; worker chains before_row from prior outbox row.
|
|
130
|
+
const patch = body && typeof body === 'object' && !Array.isArray(body)
|
|
131
|
+
? body
|
|
132
|
+
: {};
|
|
133
|
+
const merged = {
|
|
134
|
+
...patch,
|
|
135
|
+
[idColumn]: id,
|
|
136
|
+
};
|
|
137
|
+
// If caller used `Prefer: return=representation`, prefer the actual row.
|
|
138
|
+
const resultRow = Array.isArray(result)
|
|
139
|
+
? result[0]
|
|
140
|
+
: result;
|
|
141
|
+
const after = resultRow && Object.keys(resultRow).length > 0 ? resultRow : merged;
|
|
142
|
+
return [{ op, subject_id: id, after_row: after, before_row: null }];
|
|
143
|
+
}
|
|
144
|
+
export function wrapWithAudit(adapter, opts = {}) {
|
|
145
|
+
const cfg = opts.config ?? loadAuditConfig(opts.config_path);
|
|
146
|
+
const auditAdapter = opts.auditAdapter ?? adapter;
|
|
147
|
+
const wrapped = {
|
|
148
|
+
async query(builder, method = 'GET', body) {
|
|
149
|
+
const m = method.toUpperCase();
|
|
150
|
+
const result = await adapter.query(builder, method, body);
|
|
151
|
+
if (!WRITE_METHODS.has(m))
|
|
152
|
+
return result;
|
|
153
|
+
const qb = builder;
|
|
154
|
+
const table = qb.getTable?.() ?? null;
|
|
155
|
+
if (!table || !shouldCapture(table, cfg))
|
|
156
|
+
return result;
|
|
157
|
+
try {
|
|
158
|
+
const whereConditions = qb.getWhereConditions?.() ?? [];
|
|
159
|
+
const rows = await deriveOutboxRows({
|
|
160
|
+
method: m,
|
|
161
|
+
table,
|
|
162
|
+
body,
|
|
163
|
+
result,
|
|
164
|
+
whereConditions,
|
|
165
|
+
cfg,
|
|
166
|
+
baseAdapter: adapter,
|
|
167
|
+
logger: opts.logger,
|
|
168
|
+
});
|
|
169
|
+
const actor = defaultActor();
|
|
170
|
+
const hints = takeAuditHints();
|
|
171
|
+
await Promise.all(rows.map((r) => enqueueOutbox(auditAdapter, {
|
|
172
|
+
...actor,
|
|
173
|
+
table_name: table,
|
|
174
|
+
subject_kind: getSubjectKind(table, cfg),
|
|
175
|
+
subject_id: r.subject_id,
|
|
176
|
+
op: r.op,
|
|
177
|
+
before_row: hints?.before_row ?? r.before_row,
|
|
178
|
+
after_row: r.after_row,
|
|
179
|
+
intent_event: hints?.intent_event ?? null,
|
|
180
|
+
intent_payload: hints?.intent_payload ?? null,
|
|
181
|
+
}, opts.onAuditFailure)));
|
|
182
|
+
}
|
|
183
|
+
catch (err) {
|
|
184
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
185
|
+
opts.onAuditFailure?.(e, { table, op: m });
|
|
186
|
+
}
|
|
187
|
+
return result;
|
|
188
|
+
},
|
|
189
|
+
async rawQuery(endpoint, options) {
|
|
190
|
+
// Raw queries bypass the QueryBuilder so we can't infer table/intent.
|
|
191
|
+
// Pass through — apps using raw queries opt out of audit by design.
|
|
192
|
+
return adapter.rawQuery(endpoint, options);
|
|
193
|
+
},
|
|
194
|
+
async getConfig() {
|
|
195
|
+
return adapter.getConfig();
|
|
196
|
+
},
|
|
197
|
+
async claimRows(claimOpts) {
|
|
198
|
+
const result = await adapter.claimRows(claimOpts);
|
|
199
|
+
if (!shouldCapture(claimOpts.table, cfg))
|
|
200
|
+
return result;
|
|
201
|
+
// claimRows acts as a witnessed UPDATE on each row. Emit one outbox row
|
|
202
|
+
// per claimed row (id-keyed). after_row = the returned row.
|
|
203
|
+
try {
|
|
204
|
+
const idColumn = getSubjectIdColumn(claimOpts.table, cfg);
|
|
205
|
+
const actor = defaultActor();
|
|
206
|
+
const rows = Array.isArray(result) ? result : [];
|
|
207
|
+
await Promise.all(rows.map((row) => {
|
|
208
|
+
const r = row;
|
|
209
|
+
const id = extractSubjectIdFromRow(r, idColumn);
|
|
210
|
+
if (!id)
|
|
211
|
+
return Promise.resolve();
|
|
212
|
+
return enqueueOutbox(auditAdapter, {
|
|
213
|
+
...actor,
|
|
214
|
+
table_name: claimOpts.table,
|
|
215
|
+
subject_kind: getSubjectKind(claimOpts.table, cfg),
|
|
216
|
+
subject_id: id,
|
|
217
|
+
op: 'update',
|
|
218
|
+
after_row: r,
|
|
219
|
+
before_row: null,
|
|
220
|
+
}, opts.onAuditFailure);
|
|
221
|
+
}));
|
|
222
|
+
}
|
|
223
|
+
catch (err) {
|
|
224
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
225
|
+
opts.onAuditFailure?.(e, { table: claimOpts.table, op: 'claimRows' });
|
|
226
|
+
}
|
|
227
|
+
return result;
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
return wrapped;
|
|
231
|
+
}
|
|
232
|
+
//# sourceMappingURL=wrap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wrap.js","sourceRoot":"","sources":["../../src/server/wrap.ts"],"names":[],"mappings":"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"}
|