@voltro/plugin-governance 0.84.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/CHANGELOG.md +1215 -0
- package/THIRD-PARTY-NOTICES.md +118 -600
- package/dist/consent.js +55 -0
- package/dist/gdpr.js +116 -0
- package/dist/index.d.ts +26 -7
- package/dist/index.js +115 -359
- package/dist/retention.js +52 -0
- package/dist/rpc.d.ts +18 -18
- package/dist/rpc.js +2 -5
- package/dist/store.js +120 -0
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -1,338 +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 {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
delete: async (e, n) => {
|
|
38
|
-
await (t.hardDelete ?? t.delete).call(t, e, n);
|
|
39
|
-
},
|
|
40
|
-
update: async (e, n, r) => {
|
|
41
|
-
await t.update(e, n, r);
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
}, j = (e = {}) => {
|
|
45
|
-
let t = new Map(Object.entries(e).map(([e, t]) => [e, t.map((e) => ({ ...e }))])), n = (e) => {
|
|
46
|
-
let n = t.get(e);
|
|
47
|
-
return n || (n = [], t.set(e, n)), n;
|
|
48
|
-
};
|
|
49
|
-
return {
|
|
50
|
-
all: async (e) => [...n(e)],
|
|
51
|
-
where: async (e, t, r) => {
|
|
52
|
-
if (r.length === 0) return [];
|
|
53
|
-
let i = new Set(r.map((e) => String(e)));
|
|
54
|
-
return n(e).filter((e) => i.has(String(e[t]))).map((e) => ({ ...e }));
|
|
55
|
-
},
|
|
56
|
-
page: async (e, t, r) => n(e).slice(r, r + t).map((e) => ({ ...e })),
|
|
57
|
-
delete: async (e, t) => {
|
|
58
|
-
let r = n(e), i = r.findIndex((e) => String(e.id) === t);
|
|
59
|
-
i !== -1 && r.splice(i, 1);
|
|
60
|
-
},
|
|
61
|
-
update: async (e, t, r) => {
|
|
62
|
-
let i = n(e), a = i.findIndex((e) => String(e.id) === t);
|
|
63
|
-
a !== -1 && (i[a] = {
|
|
64
|
-
...i[a],
|
|
65
|
-
...r
|
|
66
|
-
});
|
|
67
|
-
},
|
|
68
|
-
dump: () => Object.fromEntries([...t.entries()].map(([e, t]) => [e, t.map((e) => ({ ...e }))]))
|
|
69
|
-
};
|
|
70
|
-
}, M = 500, N = 5e4, P = (e, t) => {
|
|
71
|
-
let n = [];
|
|
72
|
-
for (let r = 0; r < e.length; r += t) n.push(e.slice(r, r + t));
|
|
73
|
-
return n;
|
|
74
|
-
}, F = (e, t) => e.table === t && e.subjectField === "id" ? {
|
|
75
|
-
table: e.table,
|
|
76
|
-
hops: [],
|
|
77
|
-
depth: 0,
|
|
78
|
-
via: "declared"
|
|
79
|
-
} : {
|
|
80
|
-
table: e.table,
|
|
81
|
-
hops: [{
|
|
82
|
-
table: e.table,
|
|
83
|
-
foreignKey: e.subjectField,
|
|
84
|
-
parentKey: "id"
|
|
85
|
-
}],
|
|
86
|
-
depth: 1,
|
|
87
|
-
via: "declared"
|
|
88
|
-
}, I = async (e, t, n, r = N) => {
|
|
89
|
-
let i = /* @__PURE__ */ new Map(), a = /* @__PURE__ */ new Map(), o = async (t, n, r) => {
|
|
90
|
-
if (r.length === 0) return [];
|
|
91
|
-
let i = `${t}\u0000${n}\u0000${r.join(",")}`, o = a.get(i);
|
|
92
|
-
if (o !== void 0) return o;
|
|
93
|
-
let s = [];
|
|
94
|
-
for (let i of P(r, 500)) s.push(...await e.where(t, n, i));
|
|
95
|
-
return a.set(i, s), s;
|
|
96
|
-
}, s = (e, t) => {
|
|
97
|
-
let n = i.get(e);
|
|
98
|
-
n === void 0 && (n = {
|
|
99
|
-
rows: /* @__PURE__ */ new Map(),
|
|
100
|
-
truncated: !1
|
|
101
|
-
}, i.set(e, n));
|
|
102
|
-
for (let e of t) {
|
|
103
|
-
if (n.rows.size >= r) {
|
|
104
|
-
n.truncated = !0;
|
|
105
|
-
break;
|
|
106
|
-
}
|
|
107
|
-
n.rows.set(String(e.id ?? JSON.stringify(e)), e);
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
for (let e of t) {
|
|
111
|
-
if (e.hops.length === 0) {
|
|
112
|
-
s(e.table, await o(e.table, "id", [n]));
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
let t = [n];
|
|
116
|
-
for (let n of e.hops) {
|
|
117
|
-
let e = await o(n.table, n.foreignKey, t);
|
|
118
|
-
if (e.length === 0) {
|
|
119
|
-
t = [];
|
|
120
|
-
break;
|
|
121
|
-
}
|
|
122
|
-
s(n.table, e), t = [...new Set(e.map((e) => String(e[n.parentKey] ?? e.id)))];
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
return [...i.entries()].map(([e, t]) => ({
|
|
126
|
-
table: e,
|
|
127
|
-
rows: [...t.rows.values()],
|
|
128
|
-
...t.truncated ? { truncated: !0 } : {}
|
|
129
|
-
}));
|
|
130
|
-
}, L = async (e, t, n) => {
|
|
131
|
-
let r = await I(e, t, n), i = {};
|
|
132
|
-
for (let e of r) e.rows.length > 0 && (i[e.table] = e.rows);
|
|
133
|
-
return i;
|
|
134
|
-
}, R = (e, t) => {
|
|
135
|
-
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`);
|
|
136
|
-
}, z = async (e, t, n, r = {}, i = () => (/* @__PURE__ */ new Date()).toISOString()) => {
|
|
137
|
-
let a = r.mode ?? "delete";
|
|
138
|
-
a === "anonymize" && R("governance erasure", r.anonymizeFields);
|
|
139
|
-
let o = await I(e, t, n), s = /* @__PURE__ */ new Map();
|
|
140
|
-
for (let e of t) {
|
|
141
|
-
let t = s.get(e.table);
|
|
142
|
-
(t === void 0 || e.depth > t) && s.set(e.table, e.depth);
|
|
143
|
-
}
|
|
144
|
-
let c = [...o].sort((e, t) => (s.get(t.table) ?? 0) - (s.get(e.table) ?? 0)), l = [], u = [];
|
|
145
|
-
for (let t of c) if (t.rows.length !== 0) {
|
|
146
|
-
t.truncated === !0 && u.push(t.table);
|
|
147
|
-
for (let n of t.rows) {
|
|
148
|
-
let i = String(n.id);
|
|
149
|
-
if (a === "delete") await e.delete(t.table, i);
|
|
150
|
-
else {
|
|
151
|
-
let n = {};
|
|
152
|
-
for (let e of r.anonymizeFields ?? []) n[e] = null;
|
|
153
|
-
await e.update(t.table, i, n);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
l.push({
|
|
157
|
-
table: t.table,
|
|
158
|
-
count: t.rows.length
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
return {
|
|
162
|
-
subjectId: n,
|
|
163
|
-
at: i(),
|
|
164
|
-
mode: a,
|
|
165
|
-
affected: l,
|
|
166
|
-
...u.length > 0 ? { truncated: u } : {}
|
|
167
|
-
};
|
|
168
|
-
}, B = (e, t, n) => {
|
|
169
|
-
let r = e[t], i = r instanceof Date ? r.getTime() : typeof r == "string" || typeof r == "number" ? new Date(r).getTime() : NaN;
|
|
170
|
-
return Number.isNaN(i) ? null : n - i;
|
|
171
|
-
}, V = (e, t, n) => {
|
|
172
|
-
let r = t.dateField ?? "createdAt";
|
|
173
|
-
return e.filter((e) => {
|
|
174
|
-
let i = B(e, r, n);
|
|
175
|
-
return i !== null && i > t.ttlMs;
|
|
176
|
-
}).map((e) => String(e.id));
|
|
177
|
-
}, H = 1e3, U = async (e, t, n = Date.now(), r = H) => {
|
|
178
|
-
let i = [];
|
|
179
|
-
for (let a of t) {
|
|
180
|
-
let t = a.action ?? "delete";
|
|
181
|
-
t === "anonymize" && R(`retention policy '${a.table}'`, a.anonymizeFields);
|
|
182
|
-
let o = 0, s = 0, c = !1;
|
|
183
|
-
for (; !c;) {
|
|
184
|
-
let i = await e.page(a.table, r, s), l = V(i, a, n);
|
|
185
|
-
if (t === "delete") {
|
|
186
|
-
for (let t of l) await e.delete(a.table, t);
|
|
187
|
-
s += i.length - l.length;
|
|
188
|
-
} else {
|
|
189
|
-
for (let t of l) {
|
|
190
|
-
let n = {};
|
|
191
|
-
for (let e of a.anonymizeFields ?? []) n[e] = null;
|
|
192
|
-
await e.update(a.table, t, n);
|
|
193
|
-
}
|
|
194
|
-
s += i.length;
|
|
195
|
-
}
|
|
196
|
-
o += l.length, c = i.length < r;
|
|
197
|
-
}
|
|
198
|
-
i.push({
|
|
199
|
-
table: a.table,
|
|
200
|
-
action: t,
|
|
201
|
-
affected: o
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
return i;
|
|
205
|
-
}, W = 1e4, G = (e = W) => {
|
|
206
|
-
let t = /* @__PURE__ */ new Map(), n = [], r = (e, t) => `${e}|${t}`;
|
|
207
|
-
return {
|
|
208
|
-
record: async (i) => {
|
|
209
|
-
t.set(r(i.subjectId, i.purpose), i), n.unshift(i), n.length > e && (n.length = e);
|
|
210
|
-
},
|
|
211
|
-
has: async (e, n) => t.get(r(e, n))?.granted ?? !1,
|
|
212
|
-
list: async (e) => n.filter((t) => t.subjectId === e)
|
|
213
|
-
};
|
|
214
|
-
}, K = "_voltro_consent", q = O(K, {
|
|
215
|
-
id: E({ prefix: "consent" }),
|
|
216
|
-
subjectId: k(),
|
|
217
|
-
purpose: k(),
|
|
218
|
-
granted: x(),
|
|
219
|
-
at: k(),
|
|
220
|
-
tenantId: k().nullable()
|
|
221
|
-
}).index("byConsentSubjectPurpose", ["subjectId", "purpose"]).index("byConsentSubject", ["subjectId"]), J = [q], Y = (e) => ({
|
|
222
|
-
subjectId: String(e.subjectId),
|
|
223
|
-
purpose: String(e.purpose),
|
|
224
|
-
granted: e.granted === !0 || e.granted === 1 || e.granted === "true",
|
|
225
|
-
at: String(e.at),
|
|
226
|
-
tenantId: e.tenantId ?? null
|
|
227
|
-
}), X = (e) => {
|
|
228
|
-
let t = (t, n) => e.query({
|
|
229
|
-
table: K,
|
|
230
|
-
predicate: t,
|
|
231
|
-
order: [{
|
|
232
|
-
column: "at",
|
|
233
|
-
direction: "desc"
|
|
234
|
-
}],
|
|
235
|
-
take: n,
|
|
236
|
-
skip: void 0,
|
|
237
|
-
projection: void 0
|
|
238
|
-
});
|
|
239
|
-
return {
|
|
240
|
-
record: async (t) => {
|
|
241
|
-
await e.insert(K, {
|
|
242
|
-
subjectId: t.subjectId,
|
|
243
|
-
purpose: t.purpose,
|
|
244
|
-
granted: t.granted,
|
|
245
|
-
at: t.at,
|
|
246
|
-
tenantId: t.tenantId ?? null
|
|
247
|
-
});
|
|
248
|
-
},
|
|
249
|
-
has: async (e, n) => {
|
|
250
|
-
let r = await t(b(w("subjectId", e), w("purpose", n)), 1);
|
|
251
|
-
return r[0] ? Y(r[0]).granted : !1;
|
|
252
|
-
},
|
|
253
|
-
list: async (e) => (await t(w("subjectId", e))).map(Y)
|
|
254
|
-
};
|
|
255
|
-
}, Z = 1e3, Q = class extends a.Tag("@voltro/plugin-governance/GovernanceService")() {}, $ = "@voltro/plugin-governance", te = (a = {}) => {
|
|
256
|
-
let f = u({
|
|
257
|
-
base: $,
|
|
258
|
-
alias: a.alias,
|
|
259
|
-
instance: a.name
|
|
260
|
-
}), p = a.consent === "datastore", m = a.consent && a.consent !== "datastore" ? a.consent : p ? void 0 : G(), g, v = {
|
|
261
|
-
record: (e) => (m ?? g ?? G()).record(e),
|
|
262
|
-
has: (e, t) => (m ?? g ?? G()).has(e, t),
|
|
263
|
-
list: (e) => (m ?? g ?? G()).list(e)
|
|
264
|
-
}, y = a.subjectScopes ?? [], b = a.deriveSubjectScopes, x = () => {
|
|
265
|
-
let e = b?.subjectTable ?? "actors", t = y.map((t) => F(t, e));
|
|
266
|
-
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 {
|
|
267
23
|
subjectTable: e,
|
|
268
24
|
paths: t,
|
|
269
25
|
limitations: []
|
|
270
26
|
};
|
|
271
|
-
let n =
|
|
27
|
+
let n = R({
|
|
272
28
|
subjectTable: e,
|
|
273
|
-
...
|
|
274
|
-
...
|
|
29
|
+
...x.maxDepth === void 0 ? {} : { maxDepth: x.maxDepth },
|
|
30
|
+
...x.exclude === void 0 ? {} : { exclude: x.exclude }
|
|
275
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(">")}`));
|
|
276
32
|
return {
|
|
277
33
|
...n,
|
|
278
34
|
paths: [...n.paths, ...i]
|
|
279
35
|
};
|
|
280
|
-
},
|
|
281
|
-
for (let e of
|
|
282
|
-
|
|
283
|
-
let
|
|
284
|
-
name:
|
|
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,
|
|
285
41
|
required: !1,
|
|
286
42
|
secret: !0,
|
|
287
43
|
description: "AES-256-GCM key for `.encrypted()` columns (field encryption). Lose the key, lose the data — GCM fails closed."
|
|
288
|
-
}, ...
|
|
44
|
+
}, ...N.map((e) => ({
|
|
289
45
|
name: e,
|
|
290
46
|
required: !1,
|
|
291
47
|
secret: !0,
|
|
292
48
|
description: "A retired field-encryption key, read during a rotation so values written under it stay readable. Remove once they are re-encrypted."
|
|
293
|
-
}))] : [],
|
|
294
|
-
if (!
|
|
295
|
-
return
|
|
296
|
-
},
|
|
297
|
-
subjectGraph:
|
|
298
|
-
exportSubject: (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),
|
|
299
55
|
eraseSubject: async (e, t) => {
|
|
300
56
|
let n = {
|
|
301
|
-
...
|
|
57
|
+
...o.erasure,
|
|
302
58
|
...t
|
|
303
|
-
}, r = await
|
|
304
|
-
return
|
|
59
|
+
}, r = await d(q(), k(), e, n);
|
|
60
|
+
return I.unshift(r), I.length > 1e3 && (I.length = V), r;
|
|
305
61
|
},
|
|
306
|
-
recordConsent: (e) =>
|
|
307
|
-
hasConsent: (e, t) =>
|
|
62
|
+
recordConsent: (e) => g.record(e),
|
|
63
|
+
hasConsent: (e, t) => g.has(e, t),
|
|
308
64
|
runRetentionNow: async () => {
|
|
309
|
-
if (!
|
|
310
|
-
let e = await
|
|
311
|
-
|
|
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 });
|
|
312
68
|
}
|
|
313
|
-
},
|
|
69
|
+
}, Y = [
|
|
314
70
|
{
|
|
315
71
|
...n,
|
|
316
72
|
description: "GDPR data export for a subject (admin only).",
|
|
317
|
-
execute: (e, t) =>
|
|
318
|
-
return yield*
|
|
73
|
+
execute: (e, t) => S.gen(function* () {
|
|
74
|
+
return yield* D(t.request.subject, w), yield* S.promise(() => J.exportSubject(e.subjectId));
|
|
319
75
|
})
|
|
320
76
|
},
|
|
321
77
|
{
|
|
322
78
|
...t,
|
|
323
79
|
description: "GDPR erasure (right-to-be-forgotten) for a subject (admin only).",
|
|
324
|
-
execute: (e, t) =>
|
|
325
|
-
yield*
|
|
80
|
+
execute: (e, t) => S.gen(function* () {
|
|
81
|
+
yield* D(t.request.subject, w);
|
|
326
82
|
let { subjectId: n, mode: r } = e;
|
|
327
|
-
return yield*
|
|
83
|
+
return yield* S.promise(() => J.eraseSubject(n, r ? { mode: r } : void 0));
|
|
328
84
|
})
|
|
329
85
|
},
|
|
330
86
|
{
|
|
331
87
|
...e,
|
|
332
88
|
description: "Record the calling subject's consent for a purpose.",
|
|
333
|
-
execute: (e, t) =>
|
|
89
|
+
execute: (e, t) => S.promise(async () => {
|
|
334
90
|
let { purpose: n, granted: r } = e, i = t.request.subject;
|
|
335
|
-
return await
|
|
91
|
+
return await J.recordConsent({
|
|
336
92
|
subjectId: i.id ?? "anonymous",
|
|
337
93
|
purpose: n,
|
|
338
94
|
granted: r,
|
|
@@ -344,39 +100,39 @@ var A = (e) => {
|
|
|
344
100
|
{
|
|
345
101
|
...i,
|
|
346
102
|
description: "Whether the calling subject has granted consent for a purpose.",
|
|
347
|
-
execute: (e, t) =>
|
|
103
|
+
execute: (e, t) => S.promise(async () => {
|
|
348
104
|
let n = t.request.subject;
|
|
349
|
-
return { granted: await
|
|
105
|
+
return { granted: await J.hasConsent(n.id ?? "anonymous", e.purpose) };
|
|
350
106
|
})
|
|
351
107
|
}
|
|
352
|
-
],
|
|
108
|
+
], X = (e) => ({
|
|
353
109
|
kind: "json",
|
|
354
110
|
data: e
|
|
355
|
-
}),
|
|
111
|
+
}), Z = (e, t) => ({
|
|
356
112
|
kind: "json",
|
|
357
113
|
status: e,
|
|
358
114
|
data: { error: t }
|
|
359
|
-
}),
|
|
115
|
+
}), Q = (e, t) => new URLSearchParams(new URL(e, "http://x").search).get(t), $ = [
|
|
360
116
|
{
|
|
361
117
|
method: "GET",
|
|
362
118
|
path: "/status",
|
|
363
119
|
scope: "process",
|
|
364
120
|
description: "Retention policies, subject scopes, field-encryption state, last sweep.",
|
|
365
|
-
handler: () =>
|
|
366
|
-
retention: (
|
|
121
|
+
handler: () => S.succeed(X({
|
|
122
|
+
retention: (o.retention ?? []).map((e) => ({
|
|
367
123
|
table: e.table,
|
|
368
124
|
ttlMs: e.ttlMs,
|
|
369
125
|
action: e.action ?? "delete"
|
|
370
126
|
})),
|
|
371
|
-
scopes:
|
|
127
|
+
scopes: _.map((e) => ({
|
|
372
128
|
table: e.table,
|
|
373
129
|
subjectField: e.subjectField
|
|
374
130
|
})),
|
|
375
|
-
fieldEncryption:
|
|
376
|
-
sweepIntervalMs:
|
|
377
|
-
lastSweepAt:
|
|
378
|
-
lastSweep:
|
|
379
|
-
erasureCount:
|
|
131
|
+
fieldEncryption: K,
|
|
132
|
+
sweepIntervalMs: o.sweepIntervalMs ?? 36e5,
|
|
133
|
+
lastSweepAt: G,
|
|
134
|
+
lastSweep: W,
|
|
135
|
+
erasureCount: I.length
|
|
380
136
|
}))
|
|
381
137
|
},
|
|
382
138
|
{
|
|
@@ -384,18 +140,18 @@ var A = (e) => {
|
|
|
384
140
|
path: "/erasures",
|
|
385
141
|
scope: "process",
|
|
386
142
|
description: "GDPR erasure log (most recent first).",
|
|
387
|
-
handler: () =>
|
|
143
|
+
handler: () => S.succeed(X({ erasures: I.slice(0, 100) }))
|
|
388
144
|
},
|
|
389
145
|
{
|
|
390
146
|
method: "GET",
|
|
391
147
|
path: "/subject-graph",
|
|
392
148
|
scope: "declaration",
|
|
393
149
|
description: "The routes a DSAR export/erasure walks — AND what the derivation cannot see.",
|
|
394
|
-
handler: () =>
|
|
395
|
-
let e =
|
|
396
|
-
return
|
|
150
|
+
handler: () => S.sync(() => {
|
|
151
|
+
let e = O();
|
|
152
|
+
return X({
|
|
397
153
|
subjectTable: e.subjectTable,
|
|
398
|
-
derived:
|
|
154
|
+
derived: x !== void 0,
|
|
399
155
|
paths: e.paths.map((t) => ({
|
|
400
156
|
table: t.table,
|
|
401
157
|
depth: t.depth,
|
|
@@ -409,29 +165,29 @@ var A = (e) => {
|
|
|
409
165
|
{
|
|
410
166
|
method: "GET",
|
|
411
167
|
path: "/consent",
|
|
412
|
-
scope:
|
|
168
|
+
scope: c ? "shared-store" : "process",
|
|
413
169
|
description: "Consent ledger for a subject (?subjectId=…).",
|
|
414
170
|
handler: (e) => {
|
|
415
|
-
let t =
|
|
416
|
-
return t ?
|
|
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"));
|
|
417
173
|
}
|
|
418
174
|
},
|
|
419
175
|
{
|
|
420
176
|
method: "POST",
|
|
421
177
|
path: "/export",
|
|
422
178
|
description: "Run a GDPR subject export.",
|
|
423
|
-
handler: (e) =>
|
|
179
|
+
handler: (e) => S.promise(async () => {
|
|
424
180
|
let t;
|
|
425
181
|
try {
|
|
426
182
|
t = JSON.parse(e.body || "{}");
|
|
427
183
|
} catch {
|
|
428
|
-
return
|
|
184
|
+
return Z(400, "invalid JSON body");
|
|
429
185
|
}
|
|
430
|
-
if (typeof t != "object" || !t || Array.isArray(t) || !("subjectId" in t) || typeof t.subjectId != "string" || !t.subjectId.trim()) return
|
|
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");
|
|
431
187
|
try {
|
|
432
|
-
return
|
|
188
|
+
return X({ bundle: await J.exportSubject(t.subjectId) });
|
|
433
189
|
} catch (e) {
|
|
434
|
-
return
|
|
190
|
+
return Z(500, e.message);
|
|
435
191
|
}
|
|
436
192
|
})
|
|
437
193
|
},
|
|
@@ -439,20 +195,20 @@ var A = (e) => {
|
|
|
439
195
|
method: "POST",
|
|
440
196
|
path: "/erase",
|
|
441
197
|
description: "Run a GDPR erasure (right-to-be-forgotten).",
|
|
442
|
-
handler: (e) =>
|
|
198
|
+
handler: (e) => S.promise(async () => {
|
|
443
199
|
let t;
|
|
444
200
|
try {
|
|
445
201
|
t = JSON.parse(e.body || "{}");
|
|
446
202
|
} catch {
|
|
447
|
-
return
|
|
203
|
+
return Z(400, "invalid JSON body");
|
|
448
204
|
}
|
|
449
|
-
if (typeof t != "object" || !t || Array.isArray(t) || !("subjectId" in t) || typeof t.subjectId != "string" || !t.subjectId.trim()) return
|
|
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");
|
|
450
206
|
let n = "mode" in t ? t.mode : void 0;
|
|
451
|
-
if (n !== void 0 && n !== "delete" && n !== "anonymize") return
|
|
207
|
+
if (n !== void 0 && n !== "delete" && n !== "anonymize") return Z(400, "mode must be delete or anonymize");
|
|
452
208
|
try {
|
|
453
|
-
return
|
|
209
|
+
return X({ entry: await J.eraseSubject(t.subjectId, n ? { mode: n } : void 0) });
|
|
454
210
|
} catch (e) {
|
|
455
|
-
return
|
|
211
|
+
return Z(500, e.message);
|
|
456
212
|
}
|
|
457
213
|
})
|
|
458
214
|
},
|
|
@@ -460,64 +216,64 @@ var A = (e) => {
|
|
|
460
216
|
method: "POST",
|
|
461
217
|
path: "/sweep",
|
|
462
218
|
description: "Run the retention sweep now.",
|
|
463
|
-
handler: () =>
|
|
464
|
-
lastSweepAt:
|
|
465
|
-
lastSweep:
|
|
219
|
+
handler: () => S.promise(async () => (await J.runRetentionNow(), X({
|
|
220
|
+
lastSweepAt: G,
|
|
221
|
+
lastSweep: W
|
|
466
222
|
})))
|
|
467
223
|
}
|
|
468
224
|
];
|
|
469
|
-
return
|
|
470
|
-
name:
|
|
471
|
-
baseName:
|
|
225
|
+
return T({
|
|
226
|
+
name: s,
|
|
227
|
+
baseName: U,
|
|
472
228
|
description: "Data governance — retention TTL sweep, GDPR export/erasure, consent ledger.",
|
|
473
229
|
permissions: [
|
|
474
230
|
"store:write",
|
|
475
231
|
"inspect:read",
|
|
476
232
|
"inspect:write"
|
|
477
233
|
],
|
|
478
|
-
declaredEnv:
|
|
479
|
-
services:
|
|
480
|
-
routes:
|
|
234
|
+
declaredEnv: F,
|
|
235
|
+
services: C.succeed(H, J),
|
|
236
|
+
routes: Y,
|
|
481
237
|
rpcClientDescriptors: r,
|
|
482
|
-
inspectEndpoints:
|
|
483
|
-
...
|
|
238
|
+
inspectEndpoints: $,
|
|
239
|
+
...c ? { extendSchema: { tables: v } } : {},
|
|
484
240
|
bindDataStore: (e, t) => {
|
|
485
|
-
if (
|
|
486
|
-
let e =
|
|
487
|
-
|
|
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());
|
|
488
244
|
}
|
|
489
245
|
},
|
|
490
|
-
onActivate: (e) =>
|
|
491
|
-
|
|
246
|
+
onActivate: (e) => S.gen(function* () {
|
|
247
|
+
B = (t, n) => e.logger.warn(t, n);
|
|
492
248
|
let t = !1;
|
|
493
|
-
if (
|
|
494
|
-
let e =
|
|
495
|
-
if (!n) return yield*
|
|
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`));
|
|
496
252
|
let r = [];
|
|
497
|
-
for (let e of
|
|
498
|
-
let t = yield*
|
|
253
|
+
for (let e of N) {
|
|
254
|
+
let t = yield* S.promise(() => M(e));
|
|
499
255
|
t !== void 0 && t.trim() !== "" && r.push(t);
|
|
500
256
|
}
|
|
501
|
-
|
|
257
|
+
P(j(n, r)), t = !0, K = !0;
|
|
502
258
|
}
|
|
503
|
-
!
|
|
504
|
-
let n =
|
|
505
|
-
if (
|
|
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) {
|
|
506
262
|
let t = n.limitations.filter((e) => e.kind === "unreachable").length;
|
|
507
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.`);
|
|
508
264
|
}
|
|
509
265
|
e.logger.info("governance active", {
|
|
510
|
-
retention:
|
|
266
|
+
retention: o.retention?.length ?? 0,
|
|
511
267
|
scopes: n.paths.length,
|
|
512
|
-
scopeSource:
|
|
268
|
+
scopeSource: x === void 0 ? "declared" : "derived",
|
|
513
269
|
fieldEncryption: t,
|
|
514
|
-
consent:
|
|
270
|
+
consent: c ? "datastore" : o.consent ? "custom" : "memory"
|
|
515
271
|
});
|
|
516
272
|
}),
|
|
517
|
-
onDeactivate: () =>
|
|
518
|
-
|
|
273
|
+
onDeactivate: () => S.sync(() => {
|
|
274
|
+
z?.stop(), z = void 0, o.fieldEncryption && P(void 0);
|
|
519
275
|
})
|
|
520
276
|
});
|
|
521
277
|
};
|
|
522
278
|
//#endregion
|
|
523
|
-
export {
|
|
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 };
|