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,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hazo_audit/nextjs — Next.js route handler factory for the audit API.
|
|
3
|
+
*
|
|
4
|
+
* The factory takes an injectable `getAuth(req)` callback so apps can plug
|
|
5
|
+
* their existing auth system (typically hazo_auth's hazo_get_tenant_auth)
|
|
6
|
+
* without a hard module dep at this layer.
|
|
7
|
+
*
|
|
8
|
+
* Mounting (Next.js App Router):
|
|
9
|
+
*
|
|
10
|
+
* // app/api/audit/events/route.ts
|
|
11
|
+
* import { createAuditEventsRoutes } from 'hazo_audit/nextjs'
|
|
12
|
+
* import { getHazoConnectSingleton } from 'hazo_connect/nextjs/setup'
|
|
13
|
+
* import { hazo_get_tenant_auth } from 'hazo_auth/lib/auth/hazo_get_tenant_auth.server'
|
|
14
|
+
*
|
|
15
|
+
* const audit = createAuditEventsRoutes({
|
|
16
|
+
* audit_adapter: await getHazoConnectSingleton(),
|
|
17
|
+
* getAuth: async (req) => {
|
|
18
|
+
* const a = await hazo_get_tenant_auth(req)
|
|
19
|
+
* if (!a.authenticated) return null
|
|
20
|
+
* return {
|
|
21
|
+
* user_id: a.user.id,
|
|
22
|
+
* scope_id: a.selected_scope_id,
|
|
23
|
+
* permissions: a.permissions,
|
|
24
|
+
* }
|
|
25
|
+
* },
|
|
26
|
+
* })
|
|
27
|
+
* export const GET = audit.GET
|
|
28
|
+
*
|
|
29
|
+
* // app/api/audit/events/[id]/reveal/route.ts
|
|
30
|
+
* export const POST = audit.reveal.POST
|
|
31
|
+
*/
|
|
32
|
+
import 'server-only';
|
|
33
|
+
import { NextResponse } from 'next/server';
|
|
34
|
+
import { randomUUID } from 'node:crypto';
|
|
35
|
+
import { QueryBuilder, createCrudService } from 'hazo_connect/server';
|
|
36
|
+
import { AUDIT_VIEW_HISTORY, AUDIT_VIEW_SENSITIVE, AUDIT_VIEW_ACROSS_SCOPES, } from '../index.js';
|
|
37
|
+
function jsonError(message, status) {
|
|
38
|
+
return NextResponse.json({ error: message }, { status });
|
|
39
|
+
}
|
|
40
|
+
/** Parse a JSON-text column back into a JS value (no-op if already parsed). */
|
|
41
|
+
function parseJsonColumn(v) {
|
|
42
|
+
if (v == null)
|
|
43
|
+
return null;
|
|
44
|
+
if (typeof v !== 'string')
|
|
45
|
+
return v;
|
|
46
|
+
try {
|
|
47
|
+
return JSON.parse(v);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return v;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function normaliseField(event) {
|
|
54
|
+
return {
|
|
55
|
+
...event,
|
|
56
|
+
before_value: parseJsonColumn(event.before_value),
|
|
57
|
+
after_value: parseJsonColumn(event.after_value),
|
|
58
|
+
is_sensitive: Boolean(event.is_sensitive),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function maskField(event) {
|
|
62
|
+
if (!event.is_sensitive)
|
|
63
|
+
return event;
|
|
64
|
+
return { ...event, before_value: null, after_value: null };
|
|
65
|
+
}
|
|
66
|
+
async function resolveParams(ctx) {
|
|
67
|
+
if (!ctx?.params)
|
|
68
|
+
return {};
|
|
69
|
+
const p = ctx.params;
|
|
70
|
+
return p instanceof Promise ? await p : p;
|
|
71
|
+
}
|
|
72
|
+
export function createAuditEventsRoutes(opts) {
|
|
73
|
+
const historyPerm = opts.history_permission ?? AUDIT_VIEW_HISTORY;
|
|
74
|
+
const revealPerm = opts.reveal_permission ?? AUDIT_VIEW_SENSITIVE;
|
|
75
|
+
const crossScopePerm = opts.cross_scope_permission ?? AUDIT_VIEW_ACROSS_SCOPES;
|
|
76
|
+
const defaultLimit = opts.default_limit ?? 50;
|
|
77
|
+
const maxLimit = opts.max_limit ?? 200;
|
|
78
|
+
const GET = async (req) => {
|
|
79
|
+
const auth = await Promise.resolve(opts.getAuth(req));
|
|
80
|
+
if (!auth)
|
|
81
|
+
return jsonError('Unauthenticated', 401);
|
|
82
|
+
if (!auth.permissions.includes(historyPerm)) {
|
|
83
|
+
return jsonError('Forbidden', 403);
|
|
84
|
+
}
|
|
85
|
+
const url = new URL(req.url);
|
|
86
|
+
const subject_kind = url.searchParams.get('subject_kind');
|
|
87
|
+
const subject_id = url.searchParams.get('subject_id');
|
|
88
|
+
if (!subject_kind || !subject_id) {
|
|
89
|
+
return jsonError('subject_kind and subject_id are required', 400);
|
|
90
|
+
}
|
|
91
|
+
const field_path = url.searchParams.get('field_path');
|
|
92
|
+
const actor_user_id = url.searchParams.get('actor_user_id');
|
|
93
|
+
const since = url.searchParams.get('since');
|
|
94
|
+
const until = url.searchParams.get('until');
|
|
95
|
+
const include = url.searchParams.get('include') || '';
|
|
96
|
+
const wantIntent = include.split(',').map((s) => s.trim()).includes('intent');
|
|
97
|
+
const limit = Math.min(Number.parseInt(url.searchParams.get('limit') || '', 10) || defaultLimit, maxLimit);
|
|
98
|
+
const cursor = url.searchParams.get('cursor');
|
|
99
|
+
const canSeeSensitive = auth.permissions.includes(revealPerm);
|
|
100
|
+
const canCrossScope = auth.permissions.includes(crossScopePerm);
|
|
101
|
+
// ── Field events ─────────────────────────────────────────────────────
|
|
102
|
+
let qb = new QueryBuilder()
|
|
103
|
+
.from('hazo_audit_field')
|
|
104
|
+
.select('*')
|
|
105
|
+
.where('subject_kind', 'eq', subject_kind)
|
|
106
|
+
.where('subject_id', 'eq', subject_id)
|
|
107
|
+
.order('occurred_at', 'desc')
|
|
108
|
+
.limit(limit);
|
|
109
|
+
if (field_path)
|
|
110
|
+
qb = qb.where('field_path', 'eq', field_path);
|
|
111
|
+
if (actor_user_id)
|
|
112
|
+
qb = qb.where('actor_user_id', 'eq', actor_user_id);
|
|
113
|
+
if (since)
|
|
114
|
+
qb = qb.where('occurred_at', 'gte', since);
|
|
115
|
+
if (until)
|
|
116
|
+
qb = qb.where('occurred_at', 'lte', until);
|
|
117
|
+
if (!canCrossScope && auth.scope_id) {
|
|
118
|
+
qb = qb.where('scope_id', 'eq', auth.scope_id);
|
|
119
|
+
}
|
|
120
|
+
if (cursor)
|
|
121
|
+
qb = qb.where('occurred_at', 'lt', cursor);
|
|
122
|
+
let field_events = [];
|
|
123
|
+
try {
|
|
124
|
+
const rows = await opts.audit_adapter.query(qb, 'GET');
|
|
125
|
+
field_events = Array.isArray(rows)
|
|
126
|
+
? rows
|
|
127
|
+
.map(normaliseField)
|
|
128
|
+
.map((r) => (canSeeSensitive ? r : maskField(r)))
|
|
129
|
+
: [];
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
return jsonError(`Failed to read field events: ${err instanceof Error ? err.message : 'unknown'}`, 500);
|
|
133
|
+
}
|
|
134
|
+
let intent_events;
|
|
135
|
+
if (wantIntent) {
|
|
136
|
+
let iqb = new QueryBuilder()
|
|
137
|
+
.from('hazo_audit_intent')
|
|
138
|
+
.select('*')
|
|
139
|
+
.where('subject_kind', 'eq', subject_kind)
|
|
140
|
+
.where('subject_id', 'eq', subject_id)
|
|
141
|
+
.order('occurred_at', 'desc')
|
|
142
|
+
.limit(limit);
|
|
143
|
+
if (since)
|
|
144
|
+
iqb = iqb.where('occurred_at', 'gte', since);
|
|
145
|
+
if (until)
|
|
146
|
+
iqb = iqb.where('occurred_at', 'lte', until);
|
|
147
|
+
if (!canCrossScope && auth.scope_id) {
|
|
148
|
+
iqb = iqb.where('scope_id', 'eq', auth.scope_id);
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
const irows = await opts.audit_adapter.query(iqb, 'GET');
|
|
152
|
+
intent_events = Array.isArray(irows) ? irows : [];
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
intent_events = [];
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const next_cursor = field_events.length === limit
|
|
159
|
+
? field_events[field_events.length - 1].occurred_at
|
|
160
|
+
: null;
|
|
161
|
+
const body = { field_events, intent_events, next_cursor };
|
|
162
|
+
return NextResponse.json(body);
|
|
163
|
+
};
|
|
164
|
+
const reveal = async (req, ctx) => {
|
|
165
|
+
const auth = await Promise.resolve(opts.getAuth(req));
|
|
166
|
+
if (!auth)
|
|
167
|
+
return jsonError('Unauthenticated', 401);
|
|
168
|
+
if (!auth.permissions.includes(historyPerm)) {
|
|
169
|
+
return jsonError('Forbidden', 403);
|
|
170
|
+
}
|
|
171
|
+
if (!auth.permissions.includes(revealPerm)) {
|
|
172
|
+
return jsonError('Forbidden — sensitive reveal not permitted', 403);
|
|
173
|
+
}
|
|
174
|
+
const params = await resolveParams(ctx);
|
|
175
|
+
const id = params.id;
|
|
176
|
+
if (!id)
|
|
177
|
+
return jsonError('Missing id param', 400);
|
|
178
|
+
// Fetch the row directly (no scope filter on reveal — caller already
|
|
179
|
+
// proved permission and we want consistent behavior with the list query).
|
|
180
|
+
const qb = new QueryBuilder()
|
|
181
|
+
.from('hazo_audit_field')
|
|
182
|
+
.select('*')
|
|
183
|
+
.where('id', 'eq', id)
|
|
184
|
+
.limit(1);
|
|
185
|
+
if (!auth.permissions.includes(crossScopePerm) &&
|
|
186
|
+
auth.scope_id) {
|
|
187
|
+
qb.where('scope_id', 'eq', auth.scope_id);
|
|
188
|
+
}
|
|
189
|
+
let row = null;
|
|
190
|
+
try {
|
|
191
|
+
const rows = await opts.audit_adapter.query(qb, 'GET');
|
|
192
|
+
row = Array.isArray(rows) && rows.length > 0
|
|
193
|
+
? rows[0]
|
|
194
|
+
: null;
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
return jsonError(`Failed to read field event: ${err instanceof Error ? err.message : 'unknown'}`, 500);
|
|
198
|
+
}
|
|
199
|
+
if (!row)
|
|
200
|
+
return jsonError('Not found', 404);
|
|
201
|
+
// Reveal is itself audited — write an intent row tagged 'audit_reveal'.
|
|
202
|
+
try {
|
|
203
|
+
const intentSvc = createCrudService(opts.audit_adapter, 'hazo_audit_intent', { autoId: { enabled: true, column: 'id' } });
|
|
204
|
+
await intentSvc.insert({
|
|
205
|
+
id: randomUUID(),
|
|
206
|
+
scope_id: row.scope_id,
|
|
207
|
+
correlation_id: row.correlation_id,
|
|
208
|
+
event_name: 'audit_reveal',
|
|
209
|
+
payload: JSON.stringify({ audit_field_id: id }),
|
|
210
|
+
subject_kind: row.subject_kind,
|
|
211
|
+
subject_id: row.subject_id,
|
|
212
|
+
actor_kind: 'user',
|
|
213
|
+
actor_user_id: auth.user_id,
|
|
214
|
+
actor_label: auth.user_label ?? null,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
/* swallow: reveal still succeeds; log loss is acceptable here */
|
|
219
|
+
}
|
|
220
|
+
const body = {
|
|
221
|
+
id: row.id,
|
|
222
|
+
before_value: parseJsonColumn(row.before_value),
|
|
223
|
+
after_value: parseJsonColumn(row.after_value),
|
|
224
|
+
};
|
|
225
|
+
return NextResponse.json(body);
|
|
226
|
+
};
|
|
227
|
+
return { GET, reveal: { POST: reveal } };
|
|
228
|
+
}
|
|
229
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/nextjs/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audit-aware wrapper around hazo_connect's createCrudService. Adds an
|
|
3
|
+
* optional `{audit: {before_row, intent_event, intent_payload}}` third arg
|
|
4
|
+
* to insert/updateById/deleteById; hints are stashed on the AsyncLocalStorage
|
|
5
|
+
* context and picked up by wrapWithAudit for the duration of the call.
|
|
6
|
+
*/
|
|
7
|
+
import 'server-only';
|
|
8
|
+
import type { HazoConnectAdapter } from 'hazo_connect';
|
|
9
|
+
import { type CrudService, type CrudServiceOptions } from 'hazo_connect/server';
|
|
10
|
+
import { runWithAuditContext } from './context.js';
|
|
11
|
+
export interface AuditHints {
|
|
12
|
+
before_row?: Record<string, unknown> | null;
|
|
13
|
+
intent_event?: string;
|
|
14
|
+
intent_payload?: Record<string, unknown>;
|
|
15
|
+
}
|
|
16
|
+
export interface AuditedCrudServiceOptions extends CrudServiceOptions {
|
|
17
|
+
}
|
|
18
|
+
export interface AuditedCrudService<T extends Record<string, unknown> = Record<string, unknown>> extends CrudService<T> {
|
|
19
|
+
insert(data: Partial<T> | Partial<T>[], opts?: {
|
|
20
|
+
audit?: AuditHints;
|
|
21
|
+
}): Promise<T[]>;
|
|
22
|
+
updateById(id: unknown, patch: Partial<T>, opts?: {
|
|
23
|
+
audit?: AuditHints;
|
|
24
|
+
}): Promise<T[]>;
|
|
25
|
+
deleteById(id: unknown, opts?: {
|
|
26
|
+
audit?: AuditHints;
|
|
27
|
+
}): Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
export declare function createAuditedCrudService<T extends Record<string, unknown> = Record<string, unknown>>(adapter: HazoConnectAdapter, table: string, opts?: AuditedCrudServiceOptions): AuditedCrudService<T>;
|
|
30
|
+
/**
|
|
31
|
+
* Emit a standalone Layer-1 intent event (no piggyback). Writes directly to
|
|
32
|
+
* the outbox with op='update' and a null after_row so the worker treats it as
|
|
33
|
+
* a pure intent emission. R1 helper — the `withAudit` ergonomic is Phase 2.
|
|
34
|
+
*/
|
|
35
|
+
export declare function emitIntentEvent(adapter: HazoConnectAdapter, args: {
|
|
36
|
+
event_name: string;
|
|
37
|
+
payload?: Record<string, unknown>;
|
|
38
|
+
subject_kind?: string;
|
|
39
|
+
subject_id?: string;
|
|
40
|
+
}): Promise<void>;
|
|
41
|
+
export { runWithAuditContext };
|
|
42
|
+
//# sourceMappingURL=audited_crud.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audited_crud.d.ts","sourceRoot":"","sources":["../../src/server/audited_crud.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,aAAa,CAAC;AAErB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACxB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAmC,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAEpF,MAAM,WAAW,UAAU;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC5C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC1C;AAED,MAAM,WAAW,yBAA0B,SAAQ,kBAAkB;CAEpE;AAED,MAAM,WAAW,kBAAkB,CACjC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAC3D,SAAQ,WAAW,CAAC,CAAC,CAAC;IACtB,MAAM,CACJ,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,EAAE,EAC/B,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,UAAU,CAAA;KAAE,GAC5B,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;IAChB,UAAU,CACR,EAAE,EAAE,OAAO,EACX,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,EACjB,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,UAAU,CAAA;KAAE,GAC5B,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;IAChB,UAAU,CAAC,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,UAAU,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvE;AAED,wBAAgB,wBAAwB,CACtC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAE3D,OAAO,EAAE,kBAAkB,EAC3B,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE,yBAAyB,GAC/B,kBAAkB,CAAC,CAAC,CAAC,CAkBvB;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,kBAAkB,EAC3B,IAAI,EAAE;IACJ,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GACA,OAAO,CAAC,IAAI,CAAC,CAiBf;AAGD,OAAO,EAAE,mBAAmB,EAAE,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audit-aware wrapper around hazo_connect's createCrudService. Adds an
|
|
3
|
+
* optional `{audit: {before_row, intent_event, intent_payload}}` third arg
|
|
4
|
+
* to insert/updateById/deleteById; hints are stashed on the AsyncLocalStorage
|
|
5
|
+
* context and picked up by wrapWithAudit for the duration of the call.
|
|
6
|
+
*/
|
|
7
|
+
import 'server-only';
|
|
8
|
+
import { randomUUID } from 'node:crypto';
|
|
9
|
+
import { createCrudService, } from 'hazo_connect/server';
|
|
10
|
+
import { withAuditHints, getAuditContext, runWithAuditContext } from './context.js';
|
|
11
|
+
export function createAuditedCrudService(adapter, table, opts) {
|
|
12
|
+
const base = createCrudService(adapter, table, opts);
|
|
13
|
+
return {
|
|
14
|
+
...base,
|
|
15
|
+
async insert(data, callOpts) {
|
|
16
|
+
if (!callOpts?.audit)
|
|
17
|
+
return base.insert(data);
|
|
18
|
+
return withAuditHints(callOpts.audit, () => base.insert(data));
|
|
19
|
+
},
|
|
20
|
+
async updateById(id, patch, callOpts) {
|
|
21
|
+
if (!callOpts?.audit)
|
|
22
|
+
return base.updateById(id, patch);
|
|
23
|
+
return withAuditHints(callOpts.audit, () => base.updateById(id, patch));
|
|
24
|
+
},
|
|
25
|
+
async deleteById(id, callOpts) {
|
|
26
|
+
if (!callOpts?.audit)
|
|
27
|
+
return base.deleteById(id);
|
|
28
|
+
return withAuditHints(callOpts.audit, () => base.deleteById(id));
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Emit a standalone Layer-1 intent event (no piggyback). Writes directly to
|
|
34
|
+
* the outbox with op='update' and a null after_row so the worker treats it as
|
|
35
|
+
* a pure intent emission. R1 helper — the `withAudit` ergonomic is Phase 2.
|
|
36
|
+
*/
|
|
37
|
+
export async function emitIntentEvent(adapter, args) {
|
|
38
|
+
const ctx = getAuditContext();
|
|
39
|
+
const intentSvc = createCrudService(adapter, 'hazo_audit_intent', {
|
|
40
|
+
autoId: { enabled: true, column: 'id' },
|
|
41
|
+
});
|
|
42
|
+
await intentSvc.insert({
|
|
43
|
+
id: randomUUID(),
|
|
44
|
+
scope_id: ctx?.scope_id ?? null,
|
|
45
|
+
correlation_id: ctx?.correlation_id ?? randomUUID(),
|
|
46
|
+
event_name: args.event_name,
|
|
47
|
+
payload: args.payload == null ? null : JSON.stringify(args.payload),
|
|
48
|
+
subject_kind: args.subject_kind ?? null,
|
|
49
|
+
subject_id: args.subject_id ?? null,
|
|
50
|
+
actor_kind: ctx?.actor_kind ?? 'background_job',
|
|
51
|
+
actor_user_id: ctx?.actor_user_id ?? null,
|
|
52
|
+
actor_label: ctx?.actor_label ?? null,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
// Re-export the actor-context entry points for convenience.
|
|
56
|
+
export { runWithAuditContext };
|
|
57
|
+
//# sourceMappingURL=audited_crud.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audited_crud.js","sourceRoot":"","sources":["../../src/server/audited_crud.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,aAAa,CAAC;AACrB,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EACL,iBAAiB,GAGlB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AA2BpF,MAAM,UAAU,wBAAwB,CAGtC,OAA2B,EAC3B,KAAa,EACb,IAAgC;IAEhC,MAAM,IAAI,GAAG,iBAAiB,CAAI,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;IAExD,OAAO;QACL,GAAG,IAAI;QACP,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,QAAQ;YACzB,IAAI,CAAC,QAAQ,EAAE,KAAK;gBAAE,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAC/C,OAAO,cAAc,CAAC,QAAQ,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;QACjE,CAAC;QACD,KAAK,CAAC,UAAU,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ;YAClC,IAAI,CAAC,QAAQ,EAAE,KAAK;gBAAE,OAAO,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;YACxD,OAAO,cAAc,CAAC,QAAQ,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC;QAC1E,CAAC;QACD,KAAK,CAAC,UAAU,CAAC,EAAE,EAAE,QAAQ;YAC3B,IAAI,CAAC,QAAQ,EAAE,KAAK;gBAAE,OAAO,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;YACjD,OAAO,cAAc,CAAC,QAAQ,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC;QACnE,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,OAA2B,EAC3B,IAKC;IAED,MAAM,GAAG,GAAG,eAAe,EAAE,CAAC;IAC9B,MAAM,SAAS,GAAG,iBAAiB,CAAC,OAAO,EAAE,mBAAmB,EAAE;QAChE,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;KACxC,CAAC,CAAC;IACH,MAAM,SAAS,CAAC,MAAM,CAAC;QACrB,EAAE,EAAE,UAAU,EAAE;QAChB,QAAQ,EAAE,GAAG,EAAE,QAAQ,IAAI,IAAI;QAC/B,cAAc,EAAE,GAAG,EAAE,cAAc,IAAI,UAAU,EAAE;QACnD,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC;QACnE,YAAY,EAAE,IAAI,CAAC,YAAY,IAAI,IAAI;QACvC,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,IAAI;QACnC,UAAU,EAAE,GAAG,EAAE,UAAU,IAAI,gBAAgB;QAC/C,aAAa,EAAE,GAAG,EAAE,aAAa,IAAI,IAAI;QACzC,WAAW,EAAE,GAAG,EAAE,WAAW,IAAI,IAAI;KACtC,CAAC,CAAC;AACL,CAAC;AAED,4DAA4D;AAC5D,OAAO,EAAE,mBAAmB,EAAE,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* INI-driven capture configuration for hazo_audit.
|
|
3
|
+
*
|
|
4
|
+
* Loaded once at wrapWithAudit() / startAuditWorker() startup. The capture
|
|
5
|
+
* decision is deterministic (PRD §6.1): exclude wins, include opens, default
|
|
6
|
+
* deny. The audit pipeline tables themselves are hardcoded as never-capture.
|
|
7
|
+
*/
|
|
8
|
+
import 'server-only';
|
|
9
|
+
export interface CaptureConfig {
|
|
10
|
+
include_table_prefixes: string[];
|
|
11
|
+
include_tables: string[];
|
|
12
|
+
exclude_table_prefixes: string[];
|
|
13
|
+
exclude_tables: string[];
|
|
14
|
+
}
|
|
15
|
+
export interface RetentionConfig {
|
|
16
|
+
outbox_drained_ttl_days: number;
|
|
17
|
+
outbox_failed_ttl_days: number;
|
|
18
|
+
}
|
|
19
|
+
export interface WorkerConfig {
|
|
20
|
+
poll_ms: number;
|
|
21
|
+
max_attempts: number;
|
|
22
|
+
backoff_initial_ms: number;
|
|
23
|
+
reaper_interval_ms: number;
|
|
24
|
+
}
|
|
25
|
+
export interface DefaultsConfig {
|
|
26
|
+
subject_id_column: string;
|
|
27
|
+
}
|
|
28
|
+
export interface TableOverride {
|
|
29
|
+
subject_kind?: string;
|
|
30
|
+
subject_id_column?: string;
|
|
31
|
+
sensitive_columns: string[];
|
|
32
|
+
jsonb_columns: string[];
|
|
33
|
+
jsonb_path_include: string[];
|
|
34
|
+
jsonb_path_exclude: string[];
|
|
35
|
+
resolver?: string;
|
|
36
|
+
fanout_bulk_writes: boolean;
|
|
37
|
+
}
|
|
38
|
+
export interface AuditConfig {
|
|
39
|
+
capture: CaptureConfig;
|
|
40
|
+
retention: RetentionConfig;
|
|
41
|
+
worker: WorkerConfig;
|
|
42
|
+
defaults: DefaultsConfig;
|
|
43
|
+
overrides: Record<string, TableOverride>;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Resolve the config file path. Search order:
|
|
47
|
+
* 1. Explicit `config_path` arg
|
|
48
|
+
* 2. HAZO_AUDIT_CONFIG env var
|
|
49
|
+
* 3. ./config/hazo_audit_config.ini (relative to cwd)
|
|
50
|
+
* 4. ./hazo_audit_config.ini (relative to cwd)
|
|
51
|
+
* Returns null if none found — caller falls back to DEFAULT_CONFIG.
|
|
52
|
+
*/
|
|
53
|
+
export declare function resolveConfigPath(explicit?: string): string | null;
|
|
54
|
+
export declare function loadAuditConfig(explicitPath?: string): AuditConfig;
|
|
55
|
+
/**
|
|
56
|
+
* Decide whether a write to `table` should be captured (PRD §6.1).
|
|
57
|
+
* Order: pipeline → exclude_tables → exclude_prefixes → include_tables →
|
|
58
|
+
* include_prefixes → default deny.
|
|
59
|
+
*/
|
|
60
|
+
export declare function shouldCapture(table: string, cfg: AuditConfig): boolean;
|
|
61
|
+
/** Get effective per-table override (empty defaults if none configured). */
|
|
62
|
+
export declare function getOverride(table: string, cfg: AuditConfig): TableOverride;
|
|
63
|
+
export declare function getSubjectKind(table: string, cfg: AuditConfig): string;
|
|
64
|
+
export declare function getSubjectIdColumn(table: string, cfg: AuditConfig): string;
|
|
65
|
+
/**
|
|
66
|
+
* Match a JSONB leaf path against include/exclude prefix rules.
|
|
67
|
+
* Exclude wins; include narrows (if non-empty); otherwise allow.
|
|
68
|
+
*/
|
|
69
|
+
export declare function matchesJsonbPath(path: string, include: string[], exclude: string[]): boolean;
|
|
70
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/server/config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,aAAa,CAAC;AAYrB,MAAM,WAAW,aAAa;IAC5B,sBAAsB,EAAE,MAAM,EAAE,CAAC;IACjC,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,sBAAsB,EAAE,MAAM,EAAE,CAAC;IACjC,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,uBAAuB,EAAE,MAAM,CAAC;IAChC,sBAAsB,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,cAAc;IAC7B,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,aAAa;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,aAAa,CAAC;IACvB,SAAS,EAAE,eAAe,CAAC;IAC3B,MAAM,EAAE,YAAY,CAAC;IACrB,QAAQ,EAAE,cAAc,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;CAC1C;AA4DD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAalE;AAED,wBAAgB,eAAe,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,WAAW,CAkElE;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,GAAG,OAAO,CAYtE;AAED,4EAA4E;AAC5E,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,GAAG,aAAa,CAU1E;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,GAAG,MAAM,CAEtE;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,GAAG,MAAM,CAI1E;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,EAAE,MAAM,EAAE,GAChB,OAAO,CAMT"}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* INI-driven capture configuration for hazo_audit.
|
|
3
|
+
*
|
|
4
|
+
* Loaded once at wrapWithAudit() / startAuditWorker() startup. The capture
|
|
5
|
+
* decision is deterministic (PRD §6.1): exclude wins, include opens, default
|
|
6
|
+
* deny. The audit pipeline tables themselves are hardcoded as never-capture.
|
|
7
|
+
*/
|
|
8
|
+
import 'server-only';
|
|
9
|
+
import fs from 'node:fs';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import ini from 'ini';
|
|
12
|
+
/** Tables that must never be captured (would create infinite loops). */
|
|
13
|
+
const PIPELINE_TABLES = new Set([
|
|
14
|
+
'hazo_audit_outbox',
|
|
15
|
+
'hazo_audit_field',
|
|
16
|
+
'hazo_audit_intent',
|
|
17
|
+
]);
|
|
18
|
+
const DEFAULT_CONFIG = {
|
|
19
|
+
capture: {
|
|
20
|
+
include_table_prefixes: ['app_', 'hazo_'],
|
|
21
|
+
include_tables: [],
|
|
22
|
+
exclude_table_prefixes: ['_'],
|
|
23
|
+
exclude_tables: [],
|
|
24
|
+
},
|
|
25
|
+
retention: {
|
|
26
|
+
outbox_drained_ttl_days: 30,
|
|
27
|
+
outbox_failed_ttl_days: 90,
|
|
28
|
+
},
|
|
29
|
+
worker: {
|
|
30
|
+
poll_ms: 2000,
|
|
31
|
+
max_attempts: 5,
|
|
32
|
+
backoff_initial_ms: 500,
|
|
33
|
+
reaper_interval_ms: 3600000,
|
|
34
|
+
},
|
|
35
|
+
defaults: {
|
|
36
|
+
subject_id_column: 'id',
|
|
37
|
+
},
|
|
38
|
+
overrides: {},
|
|
39
|
+
};
|
|
40
|
+
function splitCsv(raw) {
|
|
41
|
+
if (typeof raw !== 'string')
|
|
42
|
+
return [];
|
|
43
|
+
return raw
|
|
44
|
+
.split(',')
|
|
45
|
+
.map((s) => s.trim())
|
|
46
|
+
.filter((s) => s.length > 0);
|
|
47
|
+
}
|
|
48
|
+
function toInt(raw, fallback) {
|
|
49
|
+
if (raw == null || raw === '')
|
|
50
|
+
return fallback;
|
|
51
|
+
const n = Number.parseInt(String(raw), 10);
|
|
52
|
+
return Number.isFinite(n) ? n : fallback;
|
|
53
|
+
}
|
|
54
|
+
function readOverride(body) {
|
|
55
|
+
return {
|
|
56
|
+
subject_kind: body['subject_kind'] || undefined,
|
|
57
|
+
subject_id_column: body['subject_id_column'] || undefined,
|
|
58
|
+
sensitive_columns: splitCsv(body['sensitive_columns']),
|
|
59
|
+
jsonb_columns: splitCsv(body['jsonb_columns']),
|
|
60
|
+
jsonb_path_include: splitCsv(body['jsonb_path_include']),
|
|
61
|
+
jsonb_path_exclude: splitCsv(body['jsonb_path_exclude']),
|
|
62
|
+
resolver: body['resolver'] || undefined,
|
|
63
|
+
fanout_bulk_writes: toBool(body['fanout_bulk_writes'], false),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function toBool(raw, fallback) {
|
|
67
|
+
if (raw == null || raw === '')
|
|
68
|
+
return fallback;
|
|
69
|
+
const v = String(raw).trim().toLowerCase();
|
|
70
|
+
if (['true', '1', 'yes', 'on'].includes(v))
|
|
71
|
+
return true;
|
|
72
|
+
if (['false', '0', 'no', 'off'].includes(v))
|
|
73
|
+
return false;
|
|
74
|
+
return fallback;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Resolve the config file path. Search order:
|
|
78
|
+
* 1. Explicit `config_path` arg
|
|
79
|
+
* 2. HAZO_AUDIT_CONFIG env var
|
|
80
|
+
* 3. ./config/hazo_audit_config.ini (relative to cwd)
|
|
81
|
+
* 4. ./hazo_audit_config.ini (relative to cwd)
|
|
82
|
+
* Returns null if none found — caller falls back to DEFAULT_CONFIG.
|
|
83
|
+
*/
|
|
84
|
+
export function resolveConfigPath(explicit) {
|
|
85
|
+
if (explicit && fs.existsSync(explicit))
|
|
86
|
+
return path.resolve(explicit);
|
|
87
|
+
if (process.env.HAZO_AUDIT_CONFIG && fs.existsSync(process.env.HAZO_AUDIT_CONFIG)) {
|
|
88
|
+
return path.resolve(process.env.HAZO_AUDIT_CONFIG);
|
|
89
|
+
}
|
|
90
|
+
const candidates = [
|
|
91
|
+
path.resolve(process.cwd(), 'config/hazo_audit_config.ini'),
|
|
92
|
+
path.resolve(process.cwd(), 'hazo_audit_config.ini'),
|
|
93
|
+
];
|
|
94
|
+
for (const c of candidates) {
|
|
95
|
+
if (fs.existsSync(c))
|
|
96
|
+
return c;
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
export function loadAuditConfig(explicitPath) {
|
|
101
|
+
const filePath = resolveConfigPath(explicitPath);
|
|
102
|
+
if (!filePath)
|
|
103
|
+
return DEFAULT_CONFIG;
|
|
104
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
105
|
+
const parsed = ini.parse(raw);
|
|
106
|
+
const cap = parsed['capture'] ?? {};
|
|
107
|
+
const ret = parsed['retention'] ?? {};
|
|
108
|
+
const wrk = parsed['worker'] ?? {};
|
|
109
|
+
const def = parsed['defaults'] ?? {};
|
|
110
|
+
// The `ini` package nests dotted sections: [overrides.app_clients] becomes
|
|
111
|
+
// parsed.overrides.app_clients. Also accept the flat form `overrides.<table>`
|
|
112
|
+
// in case a different parser is used.
|
|
113
|
+
const overrides = {};
|
|
114
|
+
const nestedOverrides = parsed['overrides'] || {};
|
|
115
|
+
for (const [table, body] of Object.entries(nestedOverrides)) {
|
|
116
|
+
if (!body || typeof body !== 'object')
|
|
117
|
+
continue;
|
|
118
|
+
overrides[table] = readOverride(body);
|
|
119
|
+
}
|
|
120
|
+
for (const [section, body] of Object.entries(parsed)) {
|
|
121
|
+
if (!section.startsWith('overrides.'))
|
|
122
|
+
continue;
|
|
123
|
+
const table = section.slice('overrides.'.length);
|
|
124
|
+
if (!table || overrides[table])
|
|
125
|
+
continue;
|
|
126
|
+
overrides[table] = readOverride(body);
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
capture: {
|
|
130
|
+
include_table_prefixes: splitCsv(cap['include_table_prefixes']).length > 0
|
|
131
|
+
? splitCsv(cap['include_table_prefixes'])
|
|
132
|
+
: DEFAULT_CONFIG.capture.include_table_prefixes,
|
|
133
|
+
include_tables: splitCsv(cap['include_tables']),
|
|
134
|
+
exclude_table_prefixes: splitCsv(cap['exclude_table_prefixes']),
|
|
135
|
+
exclude_tables: splitCsv(cap['exclude_tables']),
|
|
136
|
+
},
|
|
137
|
+
retention: {
|
|
138
|
+
outbox_drained_ttl_days: toInt(ret['outbox_drained_ttl_days'], DEFAULT_CONFIG.retention.outbox_drained_ttl_days),
|
|
139
|
+
outbox_failed_ttl_days: toInt(ret['outbox_failed_ttl_days'], DEFAULT_CONFIG.retention.outbox_failed_ttl_days),
|
|
140
|
+
},
|
|
141
|
+
worker: {
|
|
142
|
+
poll_ms: toInt(wrk['poll_ms'], DEFAULT_CONFIG.worker.poll_ms),
|
|
143
|
+
max_attempts: toInt(wrk['max_attempts'], DEFAULT_CONFIG.worker.max_attempts),
|
|
144
|
+
backoff_initial_ms: toInt(wrk['backoff_initial_ms'], DEFAULT_CONFIG.worker.backoff_initial_ms),
|
|
145
|
+
reaper_interval_ms: toInt(wrk['reaper_interval_ms'], DEFAULT_CONFIG.worker.reaper_interval_ms),
|
|
146
|
+
},
|
|
147
|
+
defaults: {
|
|
148
|
+
subject_id_column: def['subject_id_column'] ||
|
|
149
|
+
DEFAULT_CONFIG.defaults.subject_id_column,
|
|
150
|
+
},
|
|
151
|
+
overrides,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Decide whether a write to `table` should be captured (PRD §6.1).
|
|
156
|
+
* Order: pipeline → exclude_tables → exclude_prefixes → include_tables →
|
|
157
|
+
* include_prefixes → default deny.
|
|
158
|
+
*/
|
|
159
|
+
export function shouldCapture(table, cfg) {
|
|
160
|
+
if (PIPELINE_TABLES.has(table))
|
|
161
|
+
return false;
|
|
162
|
+
const { capture } = cfg;
|
|
163
|
+
if (capture.exclude_tables.includes(table))
|
|
164
|
+
return false;
|
|
165
|
+
if (capture.exclude_table_prefixes.some((p) => p && table.startsWith(p))) {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
if (capture.include_tables.includes(table))
|
|
169
|
+
return true;
|
|
170
|
+
if (capture.include_table_prefixes.some((p) => p && table.startsWith(p))) {
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
/** Get effective per-table override (empty defaults if none configured). */
|
|
176
|
+
export function getOverride(table, cfg) {
|
|
177
|
+
return (cfg.overrides[table] ?? {
|
|
178
|
+
sensitive_columns: [],
|
|
179
|
+
jsonb_columns: [],
|
|
180
|
+
jsonb_path_include: [],
|
|
181
|
+
jsonb_path_exclude: [],
|
|
182
|
+
fanout_bulk_writes: false,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
export function getSubjectKind(table, cfg) {
|
|
186
|
+
return cfg.overrides[table]?.subject_kind || table;
|
|
187
|
+
}
|
|
188
|
+
export function getSubjectIdColumn(table, cfg) {
|
|
189
|
+
return (cfg.overrides[table]?.subject_id_column ?? cfg.defaults.subject_id_column);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Match a JSONB leaf path against include/exclude prefix rules.
|
|
193
|
+
* Exclude wins; include narrows (if non-empty); otherwise allow.
|
|
194
|
+
*/
|
|
195
|
+
export function matchesJsonbPath(path, include, exclude) {
|
|
196
|
+
if (exclude.some((p) => p && (path === p || path.startsWith(p + '.')))) {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
if (include.length === 0)
|
|
200
|
+
return true;
|
|
201
|
+
return include.some((p) => p && (path === p || path.startsWith(p + '.')));
|
|
202
|
+
}
|
|
203
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/server/config.ts"],"names":[],"mappings":"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"}
|