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