@voltro/plugin-governance 0.85.0 → 0.86.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/dist/index.js CHANGED
@@ -1,418 +1,94 @@
1
1
  import { consentDescriptor as e, eraseDescriptor as t, exportDescriptor as n, governanceRpcClientImports as r, hasConsentDescriptor as i } from "./rpc.js";
2
- import { Context as a, Effect as o, Layer as s } from "effect";
3
- import { ADMIN_SCOPE as c, definePlugin as l, pluginInstanceName as u, requireScope as d } from "@voltro/protocol";
4
- import { deriveKey as f, makeAesCipher as p, makeFieldCipher as m, makeFieldCipher as ee, resolveSecret as h, setFieldCipher as g, setFieldCipher as _ } from "@voltro/runtime";
5
- import { DEFAULT_SUBJECT_GRAPH_DEPTH as v, ENCRYPTED_PREFIX as y, and as b, boolean as x, deriveSubjectGraph as S, deriveSubjectGraph as te, eq as C, formatSubjectGraph as w, getTable as T, gt as E, id as D, inSet as O, isEncrypted as k, lt as A, table as j, text as M } from "@voltro/database";
6
- //#region src/store.ts
7
- var N = (e) => {
8
- let t = e, n = (e, t) => ({
9
- table: void 0,
10
- predicate: void 0,
11
- order: [{
12
- column: "id",
13
- direction: "asc"
14
- }],
15
- take: e,
16
- skip: t,
17
- projection: void 0
18
- });
19
- return {
20
- all: (e) => t.query({
21
- ...n(void 0, void 0),
22
- table: e
23
- }),
24
- where: async (e, r, i) => i.length === 0 ? [] : t.query({
25
- ...n(void 0, void 0),
26
- table: e,
27
- predicate: {
28
- column: r,
29
- op: "in",
30
- value: [...i]
31
- }
32
- }),
33
- page: (e, r, i) => t.query({
34
- ...n(r, i),
35
- table: e
36
- }),
37
- scan: (e, r) => {
38
- if (r.column !== void 0 && r.values?.length === 0) return Promise.resolve([]);
39
- let i = [];
40
- if (r.after !== void 0 && i.push(E("id", r.after)), r.column !== void 0) {
41
- if (r.values === void 0) throw Error("governance scan needs values for its subject column");
42
- i.push(O(r.column, r.values));
43
- }
44
- return r.expired && T(e)?.fields[r.expired.column]?.type === "timestamp" && i.push(A(r.expired.column, r.expired.before)), t.query({
45
- ...n(r.limit, void 0),
46
- table: e,
47
- predicate: i.length ? b(...i) : void 0,
48
- projection: r.projection
49
- });
50
- },
51
- ...t.deleteMany && t.updateMany ? { writeMany: async (e, n, r) => {
52
- if (n.length === 0) return 0;
53
- let i = O("id", n);
54
- return r === void 0 ? t.deleteMany(e, { where: i }) : t.updateMany(e, r, { where: i });
55
- } } : {},
56
- delete: async (e, n) => {
57
- await (t.hardDelete ?? t.delete).call(t, e, n);
58
- },
59
- update: async (e, n, r) => {
60
- await t.update(e, n, r);
61
- }
62
- };
63
- }, P = (e = {}) => {
64
- let t = new Map(Object.entries(e).map(([e, t]) => [e, t.map((e) => ({ ...e }))])), n = (e) => {
65
- let n = t.get(e);
66
- return n || (n = [], t.set(e, n)), n;
67
- };
68
- return {
69
- all: async (e) => [...n(e)],
70
- where: async (e, t, r) => {
71
- if (r.length === 0) return [];
72
- let i = new Set(r.map((e) => String(e)));
73
- return n(e).filter((e) => i.has(String(e[t]))).map((e) => ({ ...e }));
74
- },
75
- page: async (e, t, r) => n(e).slice(r, r + t).map((e) => ({ ...e })),
76
- scan: async (e, t) => {
77
- let r = t.values === void 0 ? void 0 : new Set(t.values.map(String));
78
- return n(e).filter((e) => (t.after === void 0 || String(e.id) > String(t.after)) && (t.column === void 0 || r?.has(String(e[t.column])))).sort((e, t) => String(e.id) < String(t.id) ? -1 : +(String(e.id) > String(t.id))).slice(0, t.limit).map((e) => t.projection ? Object.fromEntries(t.projection.map((t) => [t, e[t]])) : { ...e });
79
- },
80
- writeMany: async (e, r, i) => {
81
- let a = new Set(r), o = n(e), s = 0;
82
- if (i === void 0) {
83
- let n = o.filter((e) => !a.has(String(e.id)) || (s++, !1));
84
- t.set(e, n);
85
- } else for (let e = 0; e < o.length; e++) a.has(String(o[e].id)) && (o[e] = {
86
- ...o[e],
87
- ...i
88
- }, s++);
89
- return s;
90
- },
91
- delete: async (e, t) => {
92
- let r = n(e), i = r.findIndex((e) => String(e.id) === t);
93
- i !== -1 && r.splice(i, 1);
94
- },
95
- update: async (e, t, r) => {
96
- let i = n(e), a = i.findIndex((e) => String(e.id) === t);
97
- a !== -1 && (i[a] = {
98
- ...i[a],
99
- ...r
100
- });
101
- },
102
- dump: () => Object.fromEntries([...t.entries()].map(([e, t]) => [e, t.map((e) => ({ ...e }))]))
103
- };
104
- }, F = async (e, t, n, r) => {
105
- let i = 0;
106
- if (e.writeMany) {
107
- for (let a = 0; a < n.length; a += 500) i += await e.writeMany(t, n.slice(a, a + 500), r);
108
- return i;
109
- }
110
- let a = 0, o = !1, s = await Promise.allSettled(Array.from({ length: Math.min(8, n.length) }, async () => {
111
- for (; !o && a < n.length;) {
112
- let s = n[a++];
113
- try {
114
- r === void 0 ? await e.delete(t, s) : await e.update(t, s, r), i++;
115
- } catch (e) {
116
- throw o = !0, e;
117
- }
118
- }
119
- }));
120
- for (let e of s) if (e.status === "rejected") throw e.reason;
121
- return i;
122
- }, I = 500, L = 5e4, R = (e, t) => {
123
- let n = [];
124
- for (let r = 0; r < e.length; r += t) n.push(e.slice(r, r + t));
125
- return n;
126
- }, z = (e, t) => e.table === t && e.subjectField === "id" ? {
127
- table: e.table,
128
- hops: [],
129
- depth: 0,
130
- via: "declared"
131
- } : {
132
- table: e.table,
133
- hops: [{
134
- table: e.table,
135
- foreignKey: e.subjectField,
136
- parentKey: "id"
137
- }],
138
- depth: 1,
139
- via: "declared"
140
- }, B = async (e, t, n, r = L) => {
141
- let i = /* @__PURE__ */ new Map();
142
- if (!Number.isSafeInteger(r) || r < 1) throw Error("subject rowCap must be a positive safe integer");
143
- let a = /* @__PURE__ */ new Map();
144
- for (let e of t) {
145
- let t = e.hops.length ? e.hops : [{
146
- table: e.table,
147
- foreignKey: "id",
148
- parentKey: "id"
149
- }], n = a;
150
- for (let e of t) {
151
- let t = JSON.stringify([
152
- e.table,
153
- e.foreignKey,
154
- e.parentKey
155
- ]), r = n.get(t);
156
- r || (r = {
157
- hop: e,
158
- children: /* @__PURE__ */ new Map()
159
- }, n.set(t, r)), n = r.children;
160
- }
161
- }
162
- let o = (e) => {
163
- let t = i.get(e);
164
- return t || (t = {
165
- rows: /* @__PURE__ */ new Map(),
166
- truncated: !1
167
- }, i.set(e, t)), t;
168
- }, s = (e, t) => {
169
- let n = o(e);
170
- for (let e of t) {
171
- let t = String(e.id ?? JSON.stringify(e));
172
- if (!n.rows.has(t)) {
173
- if (n.rows.size >= r) {
174
- n.truncated = !0;
175
- continue;
176
- }
177
- n.rows.set(t, e);
178
- }
179
- }
180
- }, c = async (t, n) => {
181
- let { hop: i, children: a } = t, l = o(i.table);
182
- for (let t of R(n, 500)) {
183
- let n;
184
- for (;;) {
185
- if (a.size === 0 && l.truncated) return;
186
- let o = a.size ? 500 : Math.min(500, r - l.rows.size + 1), u = e.scan ? await e.scan(i.table, {
187
- column: i.foreignKey,
188
- values: t,
189
- limit: o,
190
- ...n === void 0 ? {} : { after: n }
191
- }) : await e.where(i.table, i.foreignKey, t);
192
- if (s(i.table, u), u.length === 0) break;
193
- let d = [...new Set(u.map((e) => String(e[i.parentKey] ?? e.id)))];
194
- for (let e of a.values()) await c(e, d);
195
- if (!e.scan || u.length < o) break;
196
- let f = u[u.length - 1].id;
197
- if (f === void 0 || f === n) throw Error("governance scan must advance a stable id cursor");
198
- n = f;
199
- }
200
- }
201
- };
202
- for (let e of a.values()) await c(e, [n]);
203
- return [...i.entries()].map(([e, t]) => ({
204
- table: e,
205
- rows: [...t.rows.values()],
206
- ...t.truncated ? { truncated: !0 } : {}
207
- }));
208
- }, V = async (e, t, n) => {
209
- let r = await B(e, t, n), i = {};
210
- for (let e of r) e.rows.length > 0 && (i[e.table] = e.rows);
211
- return i;
212
- }, H = (e, t) => {
213
- if (t === void 0 || t.length === 0) throw Error(`${e}: mode 'anonymize' needs a non-empty 'anonymizeFields' — with none, nothing is written and the result would report rows as affected that were not changed`);
214
- }, U = async (e, t, n, r = {}, i = () => (/* @__PURE__ */ new Date()).toISOString()) => {
215
- let a = r.mode ?? "delete";
216
- a === "anonymize" && H("governance erasure", r.anonymizeFields);
217
- let o = await B(e, t, n), s = /* @__PURE__ */ new Map();
218
- for (let e of t) {
219
- let t = s.get(e.table);
220
- (t === void 0 || e.depth > t) && s.set(e.table, e.depth);
221
- }
222
- let c = [...o].sort((e, t) => (s.get(t.table) ?? 0) - (s.get(e.table) ?? 0)), l = [], u = [];
223
- for (let t of c) {
224
- if (t.rows.length === 0) continue;
225
- t.truncated === !0 && u.push(t.table);
226
- let n = a === "delete" ? void 0 : Object.fromEntries((r.anonymizeFields ?? []).map((e) => [e, null])), i = await F(e, t.table, t.rows.map((e) => String(e.id)), n);
227
- l.push({
228
- table: t.table,
229
- count: i
230
- });
231
- }
232
- return {
233
- subjectId: n,
234
- at: i(),
235
- mode: a,
236
- affected: l,
237
- ...u.length > 0 ? { truncated: u } : {}
238
- };
239
- }, W = (e, t, n) => {
240
- let r = e[t], i = r instanceof Date ? r.getTime() : typeof r == "string" || typeof r == "number" ? new Date(r).getTime() : NaN;
241
- return Number.isNaN(i) ? null : n - i;
242
- }, G = (e, t, n) => {
243
- let r = t.dateField ?? "createdAt";
244
- return e.filter((e) => {
245
- let i = W(e, r, n);
246
- return i !== null && i > t.ttlMs;
247
- }).map((e) => String(e.id));
248
- }, ne = 1e3, K = async (e, t, n = Date.now(), r = ne) => {
249
- if (!Number.isSafeInteger(r) || r < 1) throw Error("retention batchSize must be a positive safe integer");
250
- let i = [];
251
- for (let a of t) {
252
- let t = a.action ?? "delete";
253
- t === "anonymize" && H(`retention policy '${a.table}'`, a.anonymizeFields);
254
- let o = 0, s = 0, c, l = !1;
255
- for (; !l;) {
256
- let i = a.dateField ?? "createdAt", u = new Date(Math.ceil(n - a.ttlMs)), d = e.scan ? await e.scan(a.table, {
257
- limit: r,
258
- ...c === void 0 ? {} : { after: c },
259
- projection: ["id", i],
260
- ...Number.isNaN(u.getTime()) ? {} : { expired: {
261
- column: i,
262
- before: u
263
- } }
264
- }) : await e.page(a.table, r, s), f = G(d, a, n);
265
- if (t === "delete") o += await F(e, a.table, f), s += d.length - f.length;
266
- else {
267
- let t = {};
268
- for (let e of a.anonymizeFields ?? []) t[e] = null;
269
- o += await F(e, a.table, f, t), s += d.length;
270
- }
271
- if (d.length && e.scan) {
272
- let e = d[d.length - 1].id;
273
- if (e === void 0 || e === c) throw Error("retention scan must advance a stable id cursor");
274
- c = e;
275
- }
276
- l = d.length < r;
277
- }
278
- i.push({
279
- table: a.table,
280
- action: t,
281
- affected: o
282
- });
283
- }
284
- return i;
285
- }, re = 1e4, q = (e = re) => {
286
- let t = /* @__PURE__ */ new Map(), n = [], r = (e, t) => `${e}|${t}`;
287
- return {
288
- record: async (i) => {
289
- t.set(r(i.subjectId, i.purpose), i), n.unshift(i), n.length > e && (n.length = e);
290
- },
291
- has: async (e, n) => t.get(r(e, n))?.granted ?? !1,
292
- list: async (e) => n.filter((t) => t.subjectId === e)
293
- };
294
- }, J = "_voltro_consent", Y = j(J, {
295
- id: D({ prefix: "consent" }),
296
- subjectId: M(),
297
- purpose: M(),
298
- granted: x(),
299
- at: M(),
300
- tenantId: M().nullable()
301
- }).index("byConsentSubjectPurpose", ["subjectId", "purpose"]).index("byConsentSubject", ["subjectId"]), X = [Y], Z = (e) => ({
302
- subjectId: String(e.subjectId),
303
- purpose: String(e.purpose),
304
- granted: e.granted === !0 || e.granted === 1 || e.granted === "true",
305
- at: String(e.at),
306
- tenantId: e.tenantId ?? null
307
- }), Q = (e) => {
308
- let t = (t, n) => e.query({
309
- table: J,
310
- predicate: t,
311
- order: [{
312
- column: "at",
313
- direction: "desc"
314
- }],
315
- take: n,
316
- skip: void 0,
317
- projection: void 0
318
- });
319
- return {
320
- record: async (t) => {
321
- await e.insert(J, {
322
- subjectId: t.subjectId,
323
- purpose: t.purpose,
324
- granted: t.granted,
325
- at: t.at,
326
- tenantId: t.tenantId ?? null
327
- });
328
- },
329
- has: async (e, n) => {
330
- let r = await t(b(C("subjectId", e), C("purpose", n)), 1);
331
- return r[0] ? Z(r[0]).granted : !1;
332
- },
333
- list: async (e) => (await t(C("subjectId", e))).map(Z)
334
- };
335
- }, $ = 1e3, ie = class extends a.Service()("@voltro/plugin-governance/GovernanceService") {}, ae = "@voltro/plugin-governance", oe = (a = {}) => {
336
- let f = u({
337
- base: ae,
338
- alias: a.alias,
339
- instance: a.name
340
- }), p = a.consent === "datastore", m = a.consent && a.consent !== "datastore" ? a.consent : p ? void 0 : q(), g, v = {
341
- record: (e) => (m ?? g ?? q()).record(e),
342
- has: (e, t) => (m ?? g ?? q()).has(e, t),
343
- list: (e) => (m ?? g ?? q()).list(e)
344
- }, y = a.subjectScopes ?? [], b = a.deriveSubjectScopes, x = () => {
345
- let e = b?.subjectTable ?? "actors", t = y.map((t) => z(t, e));
346
- if (b === void 0) return {
2
+ import { dataStoreGovStore as a, memoryGovStore as o } from "./store.js";
3
+ import { SUBJECT_KEY_CHUNK as s, SUBJECT_ROW_CAP as c, assertAnonymizable as l, collectSubjectRows as u, eraseSubject as d, exportSubject as f, scopeToPath as p } from "./gdpr.js";
4
+ import { runRetention as m, selectExpired as h } from "./retention.js";
5
+ import { CONSENT_TABLE as g, consentTable as _, consentTables as v, dataStoreConsentStore as y, memoryConsentStore as b } from "./consent.js";
6
+ import { Context as x, Effect as S, Layer as C } from "effect";
7
+ import { ADMIN_SCOPE as w, definePlugin as T, pluginInstanceName as E, requireScope as D } from "@voltro/protocol";
8
+ import { deriveKey as O, makeAesCipher as k, makeFieldCipher as A, makeFieldCipher as j, resolveSecret as M, setFieldCipher as N, setFieldCipher as P } from "@voltro/runtime";
9
+ import { DEFAULT_SUBJECT_GRAPH_DEPTH as F, ENCRYPTED_PREFIX as I, deriveSubjectGraph as L, deriveSubjectGraph as R, formatSubjectGraph as z, isEncrypted as B } from "@voltro/database";
10
+ //#region src/index.ts
11
+ var V = 1e3, H = class extends x.Service()("@voltro/plugin-governance/GovernanceService") {}, U = "@voltro/plugin-governance", W = (o = {}) => {
12
+ let s = E({
13
+ base: U,
14
+ alias: o.alias,
15
+ instance: o.name
16
+ }), c = o.consent === "datastore", u = o.consent && o.consent !== "datastore" ? o.consent : c ? void 0 : b(), h, g = {
17
+ record: (e) => (u ?? h ?? b()).record(e),
18
+ has: (e, t) => (u ?? h ?? b()).has(e, t),
19
+ list: (e) => (u ?? h ?? b()).list(e)
20
+ }, _ = o.subjectScopes ?? [], x = o.deriveSubjectScopes, O = () => {
21
+ let e = x?.subjectTable ?? "actors", t = _.map((t) => p(t, e));
22
+ if (x === void 0) return {
347
23
  subjectTable: e,
348
24
  paths: t,
349
25
  limitations: []
350
26
  };
351
- let n = te({
27
+ let n = R({
352
28
  subjectTable: e,
353
- ...b.maxDepth === void 0 ? {} : { maxDepth: b.maxDepth },
354
- ...b.exclude === void 0 ? {} : { exclude: b.exclude }
29
+ ...x.maxDepth === void 0 ? {} : { maxDepth: x.maxDepth },
30
+ ...x.exclude === void 0 ? {} : { exclude: x.exclude }
355
31
  }), r = new Set(n.paths.map((e) => `${e.table}|${e.hops.map((e) => e.foreignKey).join(">")}`)), i = t.filter((e) => !r.has(`${e.table}|${e.hops.map((e) => e.foreignKey).join(">")}`));
356
32
  return {
357
33
  ...n,
358
34
  paths: [...n.paths, ...i]
359
35
  };
360
- }, S = () => x().paths;
361
- for (let e of a.retention ?? []) (e.action ?? "delete") === "anonymize" && H(`retention policy '${e.table}'`, e.anonymizeFields);
362
- a.erasure?.mode === "anonymize" && H("erasure defaults", a.erasure.anonymizeFields);
363
- let C = typeof a.fieldEncryption == "object" ? a.fieldEncryption.secretKey ?? "VOLTRO_FIELD_ENCRYPTION_KEY" : "VOLTRO_FIELD_ENCRYPTION_KEY", w = typeof a.fieldEncryption == "object" ? a.fieldEncryption.previousSecretKeys ?? ["VOLTRO_FIELD_ENCRYPTION_KEY_PREVIOUS"] : ["VOLTRO_FIELD_ENCRYPTION_KEY_PREVIOUS"], T = a.fieldEncryption ? [{
364
- name: C,
36
+ }, k = () => O().paths;
37
+ for (let e of o.retention ?? []) (e.action ?? "delete") === "anonymize" && l(`retention policy '${e.table}'`, e.anonymizeFields);
38
+ o.erasure?.mode === "anonymize" && l("erasure defaults", o.erasure.anonymizeFields);
39
+ let A = typeof o.fieldEncryption == "object" ? o.fieldEncryption.secretKey ?? "VOLTRO_FIELD_ENCRYPTION_KEY" : "VOLTRO_FIELD_ENCRYPTION_KEY", N = typeof o.fieldEncryption == "object" ? o.fieldEncryption.previousSecretKeys ?? ["VOLTRO_FIELD_ENCRYPTION_KEY_PREVIOUS"] : ["VOLTRO_FIELD_ENCRYPTION_KEY_PREVIOUS"], F = o.fieldEncryption ? [{
40
+ name: A,
365
41
  required: !1,
366
42
  secret: !0,
367
43
  description: "AES-256-GCM key for `.encrypted()` columns (field encryption). Lose the key, lose the data — GCM fails closed."
368
- }, ...w.map((e) => ({
44
+ }, ...N.map((e) => ({
369
45
  name: e,
370
46
  required: !1,
371
47
  secret: !0,
372
48
  description: "A retired field-encryption key, read during a rotation so values written under it stay readable. Remove once they are re-encrypted."
373
- }))] : [], E = [], D, O, k = () => void 0, A = [], j = null, M = !1, P = () => {
374
- if (!D) throw Error("governance: data store not bound yet");
375
- return D;
376
- }, F = {
377
- subjectGraph: x,
378
- exportSubject: (e) => V(P(), S(), e),
49
+ }))] : [], I = [], L, z, B = () => void 0, W = [], G = null, K = !1, q = () => {
50
+ if (!L) throw Error("governance: data store not bound yet");
51
+ return L;
52
+ }, J = {
53
+ subjectGraph: O,
54
+ exportSubject: (e) => f(q(), k(), e),
379
55
  eraseSubject: async (e, t) => {
380
56
  let n = {
381
- ...a.erasure,
57
+ ...o.erasure,
382
58
  ...t
383
- }, r = await U(P(), S(), e, n);
384
- return E.unshift(r), E.length > 1e3 && (E.length = $), r;
59
+ }, r = await d(q(), k(), e, n);
60
+ return I.unshift(r), I.length > 1e3 && (I.length = V), r;
385
61
  },
386
- recordConsent: (e) => v.record(e),
387
- hasConsent: (e, t) => v.has(e, t),
62
+ recordConsent: (e) => g.record(e),
63
+ hasConsent: (e, t) => g.has(e, t),
388
64
  runRetentionNow: async () => {
389
- if (!D || !a.retention) return;
390
- let e = await K(D, a.retention);
391
- A = e, j = (/* @__PURE__ */ new Date()).toISOString(), k("retention sweep", { report: e });
65
+ if (!L || !o.retention) return;
66
+ let e = await m(L, o.retention);
67
+ W = e, G = (/* @__PURE__ */ new Date()).toISOString(), B("retention sweep", { report: e });
392
68
  }
393
- }, I = [
69
+ }, Y = [
394
70
  {
395
71
  ...n,
396
72
  description: "GDPR data export for a subject (admin only).",
397
- execute: (e, t) => o.gen(function* () {
398
- return yield* d(t.request.subject, c), yield* o.promise(() => F.exportSubject(e.subjectId));
73
+ execute: (e, t) => S.gen(function* () {
74
+ return yield* D(t.request.subject, w), yield* S.promise(() => J.exportSubject(e.subjectId));
399
75
  })
400
76
  },
401
77
  {
402
78
  ...t,
403
79
  description: "GDPR erasure (right-to-be-forgotten) for a subject (admin only).",
404
- execute: (e, t) => o.gen(function* () {
405
- yield* d(t.request.subject, c);
80
+ execute: (e, t) => S.gen(function* () {
81
+ yield* D(t.request.subject, w);
406
82
  let { subjectId: n, mode: r } = e;
407
- return yield* o.promise(() => F.eraseSubject(n, r ? { mode: r } : void 0));
83
+ return yield* S.promise(() => J.eraseSubject(n, r ? { mode: r } : void 0));
408
84
  })
409
85
  },
410
86
  {
411
87
  ...e,
412
88
  description: "Record the calling subject's consent for a purpose.",
413
- execute: (e, t) => o.promise(async () => {
89
+ execute: (e, t) => S.promise(async () => {
414
90
  let { purpose: n, granted: r } = e, i = t.request.subject;
415
- return await F.recordConsent({
91
+ return await J.recordConsent({
416
92
  subjectId: i.id ?? "anonymous",
417
93
  purpose: n,
418
94
  granted: r,
@@ -424,39 +100,39 @@ var N = (e) => {
424
100
  {
425
101
  ...i,
426
102
  description: "Whether the calling subject has granted consent for a purpose.",
427
- execute: (e, t) => o.promise(async () => {
103
+ execute: (e, t) => S.promise(async () => {
428
104
  let n = t.request.subject;
429
- return { granted: await F.hasConsent(n.id ?? "anonymous", e.purpose) };
105
+ return { granted: await J.hasConsent(n.id ?? "anonymous", e.purpose) };
430
106
  })
431
107
  }
432
- ], L = (e) => ({
108
+ ], X = (e) => ({
433
109
  kind: "json",
434
110
  data: e
435
- }), R = (e, t) => ({
111
+ }), Z = (e, t) => ({
436
112
  kind: "json",
437
113
  status: e,
438
114
  data: { error: t }
439
- }), B = (e, t) => new URLSearchParams(new URL(e, "http://x").search).get(t), W = [
115
+ }), Q = (e, t) => new URLSearchParams(new URL(e, "http://x").search).get(t), $ = [
440
116
  {
441
117
  method: "GET",
442
118
  path: "/status",
443
119
  scope: "process",
444
120
  description: "Retention policies, subject scopes, field-encryption state, last sweep.",
445
- handler: () => o.succeed(L({
446
- retention: (a.retention ?? []).map((e) => ({
121
+ handler: () => S.succeed(X({
122
+ retention: (o.retention ?? []).map((e) => ({
447
123
  table: e.table,
448
124
  ttlMs: e.ttlMs,
449
125
  action: e.action ?? "delete"
450
126
  })),
451
- scopes: y.map((e) => ({
127
+ scopes: _.map((e) => ({
452
128
  table: e.table,
453
129
  subjectField: e.subjectField
454
130
  })),
455
- fieldEncryption: M,
456
- sweepIntervalMs: a.sweepIntervalMs ?? 36e5,
457
- lastSweepAt: j,
458
- lastSweep: A,
459
- erasureCount: E.length
131
+ fieldEncryption: K,
132
+ sweepIntervalMs: o.sweepIntervalMs ?? 36e5,
133
+ lastSweepAt: G,
134
+ lastSweep: W,
135
+ erasureCount: I.length
460
136
  }))
461
137
  },
462
138
  {
@@ -464,18 +140,18 @@ var N = (e) => {
464
140
  path: "/erasures",
465
141
  scope: "process",
466
142
  description: "GDPR erasure log (most recent first).",
467
- handler: () => o.succeed(L({ erasures: E.slice(0, 100) }))
143
+ handler: () => S.succeed(X({ erasures: I.slice(0, 100) }))
468
144
  },
469
145
  {
470
146
  method: "GET",
471
147
  path: "/subject-graph",
472
148
  scope: "declaration",
473
149
  description: "The routes a DSAR export/erasure walks — AND what the derivation cannot see.",
474
- handler: () => o.sync(() => {
475
- let e = x();
476
- return L({
150
+ handler: () => S.sync(() => {
151
+ let e = O();
152
+ return X({
477
153
  subjectTable: e.subjectTable,
478
- derived: b !== void 0,
154
+ derived: x !== void 0,
479
155
  paths: e.paths.map((t) => ({
480
156
  table: t.table,
481
157
  depth: t.depth,
@@ -489,29 +165,29 @@ var N = (e) => {
489
165
  {
490
166
  method: "GET",
491
167
  path: "/consent",
492
- scope: p ? "shared-store" : "process",
168
+ scope: c ? "shared-store" : "process",
493
169
  description: "Consent ledger for a subject (?subjectId=…).",
494
170
  handler: (e) => {
495
- let t = B(e.url, "subjectId");
496
- return t ? o.promise(async () => L({ records: await v.list(t) })) : o.succeed(R(400, "subjectId query param required"));
171
+ let t = Q(e.url, "subjectId");
172
+ return t ? S.promise(async () => X({ records: await g.list(t) })) : S.succeed(Z(400, "subjectId query param required"));
497
173
  }
498
174
  },
499
175
  {
500
176
  method: "POST",
501
177
  path: "/export",
502
178
  description: "Run a GDPR subject export.",
503
- handler: (e) => o.promise(async () => {
179
+ handler: (e) => S.promise(async () => {
504
180
  let t;
505
181
  try {
506
182
  t = JSON.parse(e.body || "{}");
507
183
  } catch {
508
- return R(400, "invalid JSON body");
184
+ return Z(400, "invalid JSON body");
509
185
  }
510
- if (typeof t != "object" || !t || Array.isArray(t) || !("subjectId" in t) || typeof t.subjectId != "string" || !t.subjectId.trim()) return R(400, "subjectId must be a non-empty string");
186
+ if (typeof t != "object" || !t || Array.isArray(t) || !("subjectId" in t) || typeof t.subjectId != "string" || !t.subjectId.trim()) return Z(400, "subjectId must be a non-empty string");
511
187
  try {
512
- return L({ bundle: await F.exportSubject(t.subjectId) });
188
+ return X({ bundle: await J.exportSubject(t.subjectId) });
513
189
  } catch (e) {
514
- return R(500, e.message);
190
+ return Z(500, e.message);
515
191
  }
516
192
  })
517
193
  },
@@ -519,20 +195,20 @@ var N = (e) => {
519
195
  method: "POST",
520
196
  path: "/erase",
521
197
  description: "Run a GDPR erasure (right-to-be-forgotten).",
522
- handler: (e) => o.promise(async () => {
198
+ handler: (e) => S.promise(async () => {
523
199
  let t;
524
200
  try {
525
201
  t = JSON.parse(e.body || "{}");
526
202
  } catch {
527
- return R(400, "invalid JSON body");
203
+ return Z(400, "invalid JSON body");
528
204
  }
529
- if (typeof t != "object" || !t || Array.isArray(t) || !("subjectId" in t) || typeof t.subjectId != "string" || !t.subjectId.trim()) return R(400, "subjectId must be a non-empty string");
205
+ if (typeof t != "object" || !t || Array.isArray(t) || !("subjectId" in t) || typeof t.subjectId != "string" || !t.subjectId.trim()) return Z(400, "subjectId must be a non-empty string");
530
206
  let n = "mode" in t ? t.mode : void 0;
531
- if (n !== void 0 && n !== "delete" && n !== "anonymize") return R(400, "mode must be delete or anonymize");
207
+ if (n !== void 0 && n !== "delete" && n !== "anonymize") return Z(400, "mode must be delete or anonymize");
532
208
  try {
533
- return L({ entry: await F.eraseSubject(t.subjectId, n ? { mode: n } : void 0) });
209
+ return X({ entry: await J.eraseSubject(t.subjectId, n ? { mode: n } : void 0) });
534
210
  } catch (e) {
535
- return R(500, e.message);
211
+ return Z(500, e.message);
536
212
  }
537
213
  })
538
214
  },
@@ -540,64 +216,64 @@ var N = (e) => {
540
216
  method: "POST",
541
217
  path: "/sweep",
542
218
  description: "Run the retention sweep now.",
543
- handler: () => o.promise(async () => (await F.runRetentionNow(), L({
544
- lastSweepAt: j,
545
- lastSweep: A
219
+ handler: () => S.promise(async () => (await J.runRetentionNow(), X({
220
+ lastSweepAt: G,
221
+ lastSweep: W
546
222
  })))
547
223
  }
548
224
  ];
549
- return l({
550
- name: f,
551
- baseName: ae,
225
+ return T({
226
+ name: s,
227
+ baseName: U,
552
228
  description: "Data governance — retention TTL sweep, GDPR export/erasure, consent ledger.",
553
229
  permissions: [
554
230
  "store:write",
555
231
  "inspect:read",
556
232
  "inspect:write"
557
233
  ],
558
- declaredEnv: T,
559
- services: s.succeed(ie, F),
560
- routes: I,
234
+ declaredEnv: F,
235
+ services: C.succeed(H, J),
236
+ routes: Y,
561
237
  rpcClientDescriptors: r,
562
- inspectEndpoints: W,
563
- ...p ? { extendSchema: { tables: X } } : {},
238
+ inspectEndpoints: $,
239
+ ...c ? { extendSchema: { tables: v } } : {},
564
240
  bindDataStore: (e, t) => {
565
- if (D = N(e), p && (g = Q(e)), a.retention && a.retention.length > 0) {
566
- let e = a.sweepIntervalMs ?? 36e5;
567
- O = t?.scheduleCoordinated("governance.retention.sweep", e, () => F.runRetentionNow());
241
+ if (L = a(e), c && (h = y(e)), o.retention && o.retention.length > 0) {
242
+ let e = o.sweepIntervalMs ?? 36e5;
243
+ z = t?.scheduleCoordinated("governance.retention.sweep", e, () => J.runRetentionNow());
568
244
  }
569
245
  },
570
- onActivate: (e) => o.gen(function* () {
571
- k = (t, n) => e.logger.warn(t, n);
246
+ onActivate: (e) => S.gen(function* () {
247
+ B = (t, n) => e.logger.warn(t, n);
572
248
  let t = !1;
573
- if (a.fieldEncryption) {
574
- let e = C, n = yield* o.promise(() => h(e));
575
- if (!n) return yield* o.die(/* @__PURE__ */ Error(`governance: fieldEncryption is enabled but secret "${e}" did not resolve — set it via the Secrets-Resolver / env`));
249
+ if (o.fieldEncryption) {
250
+ let e = A, n = yield* S.promise(() => M(e));
251
+ if (!n) return yield* S.die(/* @__PURE__ */ Error(`governance: fieldEncryption is enabled but secret "${e}" did not resolve — set it via the Secrets-Resolver / env`));
576
252
  let r = [];
577
- for (let e of w) {
578
- let t = yield* o.promise(() => h(e));
253
+ for (let e of N) {
254
+ let t = yield* S.promise(() => M(e));
579
255
  t !== void 0 && t.trim() !== "" && r.push(t);
580
256
  }
581
- _(ee(n, r)), t = !0, M = !0;
257
+ P(j(n, r)), t = !0, K = !0;
582
258
  }
583
- !p && !a.consent && e.logger.warn("governance: consent ledger uses the in-memory store — per-process + restart-cleared. Under >1 replica a consent recorded on one replica is invisible to others. Pass consent: 'datastore' (durable, cross-instance) in production.");
584
- let n = x();
585
- if (b !== void 0) {
259
+ !c && !o.consent && e.logger.warn("governance: consent ledger uses the in-memory store — per-process + restart-cleared. Under >1 replica a consent recorded on one replica is invisible to others. Pass consent: 'datastore' (durable, cross-instance) in production.");
260
+ let n = O();
261
+ if (x !== void 0) {
586
262
  let t = n.limitations.filter((e) => e.kind === "unreachable").length;
587
263
  n.paths.length <= 1 && e.logger.warn(`governance: the derived subject scope reached ${n.paths.length} table(s) from '${n.subjectTable}'. A DSAR would export almost nothing. Check that subjectTable names a real table and that the app declares relations() or reference() columns pointing at it.`), t > 0 && e.logger.info(`governance: ${t} registered table(s) are NOT linked to '${n.subjectTable}' and are outside every DSAR. GET /_voltro/inspect/plugins/governance/subject-graph lists them; add subjectScopes for any that hold subject data.`);
588
264
  }
589
265
  e.logger.info("governance active", {
590
- retention: a.retention?.length ?? 0,
266
+ retention: o.retention?.length ?? 0,
591
267
  scopes: n.paths.length,
592
- scopeSource: b === void 0 ? "declared" : "derived",
268
+ scopeSource: x === void 0 ? "declared" : "derived",
593
269
  fieldEncryption: t,
594
- consent: p ? "datastore" : a.consent ? "custom" : "memory"
270
+ consent: c ? "datastore" : o.consent ? "custom" : "memory"
595
271
  });
596
272
  }),
597
- onDeactivate: () => o.sync(() => {
598
- O?.stop(), O = void 0, a.fieldEncryption && _(void 0);
273
+ onDeactivate: () => S.sync(() => {
274
+ z?.stop(), z = void 0, o.fieldEncryption && P(void 0);
599
275
  })
600
276
  });
601
277
  };
602
278
  //#endregion
603
- export { J as CONSENT_TABLE, v as DEFAULT_SUBJECT_GRAPH_DEPTH, y as ENCRYPTED_PREFIX, $ as ERASURE_LOG_CAP, ie as GovernanceService, I as SUBJECT_KEY_CHUNK, L as SUBJECT_ROW_CAP, B as collectSubjectRows, Y as consentTable, X as consentTables, Q as dataStoreConsentStore, N as dataStoreGovStore, f as deriveKey, S as deriveSubjectGraph, U as eraseSubject, V as exportSubject, w as formatSubjectGraph, oe as governancePlugin, k as isEncrypted, p as makeAesCipher, m as makeFieldCipher, q as memoryConsentStore, P as memoryGovStore, K as runRetention, z as scopeToPath, G as selectExpired, g as setFieldCipher };
279
+ export { g as CONSENT_TABLE, F as DEFAULT_SUBJECT_GRAPH_DEPTH, I as ENCRYPTED_PREFIX, V as ERASURE_LOG_CAP, H as GovernanceService, s as SUBJECT_KEY_CHUNK, c as SUBJECT_ROW_CAP, u as collectSubjectRows, _ as consentTable, v as consentTables, y as dataStoreConsentStore, a as dataStoreGovStore, O as deriveKey, L as deriveSubjectGraph, d as eraseSubject, f as exportSubject, z as formatSubjectGraph, W as governancePlugin, B as isEncrypted, k as makeAesCipher, A as makeFieldCipher, b as memoryConsentStore, o as memoryGovStore, m as runRetention, p as scopeToPath, h as selectExpired, N as setFieldCipher };