@voltro/plugin-governance 0.32.0 → 0.34.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 +2006 -0
- package/THIRD-PARTY-NOTICES.md +1 -29
- package/dist/index.d.ts +174 -11
- package/dist/index.js +254 -117
- package/package.json +6 -5
package/THIRD-PARTY-NOTICES.md
CHANGED
|
@@ -5,7 +5,7 @@ property of its respective copyright holders and is used under the terms of
|
|
|
5
5
|
its license. This file is provided for attribution; it grants no rights in
|
|
6
6
|
@voltro/plugin-governance itself, which is proprietary (see LICENSE).
|
|
7
7
|
|
|
8
|
-
Generated from the resolved runtime dependency closure (
|
|
8
|
+
Generated from the resolved runtime dependency closure (51 packages).
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -37,34 +37,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
37
37
|
SOFTWARE.
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
## @effect/opentelemetry@0.64.0
|
|
41
|
-
|
|
42
|
-
License: MIT
|
|
43
|
-
|
|
44
|
-
```
|
|
45
|
-
MIT License
|
|
46
|
-
|
|
47
|
-
Copyright (c) 2020-present The Contributors
|
|
48
|
-
|
|
49
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
50
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
51
|
-
in the Software without restriction, including without limitation the rights
|
|
52
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
53
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
54
|
-
furnished to do so, subject to the following conditions:
|
|
55
|
-
|
|
56
|
-
The above copyright notice and this permission notice shall be included in all
|
|
57
|
-
copies or substantial portions of the Software.
|
|
58
|
-
|
|
59
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
60
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
61
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
62
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
63
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
64
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
65
|
-
SOFTWARE.
|
|
66
|
-
```
|
|
67
|
-
|
|
68
40
|
## @effect/platform-node@0.108.0
|
|
69
41
|
|
|
70
42
|
License: MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1,41 @@
|
|
|
1
1
|
import { Context } from 'effect';
|
|
2
|
+
import { DEFAULT_SUBJECT_GRAPH_DEPTH } from '@voltro/database';
|
|
2
3
|
import { deriveKey } from '@voltro/runtime';
|
|
4
|
+
import { deriveSubjectGraph } from '@voltro/database';
|
|
5
|
+
import { DeriveSubjectGraphInput } from '@voltro/database';
|
|
3
6
|
import { ENCRYPTED_PREFIX } from '@voltro/database';
|
|
4
7
|
import { FieldCipher } from '@voltro/database';
|
|
8
|
+
import { formatSubjectGraph } from '@voltro/database';
|
|
5
9
|
import { isEncrypted } from '@voltro/database';
|
|
6
10
|
import { makeAesCipher } from '@voltro/runtime';
|
|
7
11
|
import { makeFieldCipher } from '@voltro/runtime';
|
|
8
12
|
import { SchemaTable } from '@voltro/database';
|
|
9
13
|
import { setFieldCipher } from '@voltro/runtime';
|
|
14
|
+
import { SubjectGraph } from '@voltro/database';
|
|
15
|
+
import { SubjectGraphLimitation } from '@voltro/database';
|
|
16
|
+
import { SubjectHop } from '@voltro/database';
|
|
17
|
+
import { SubjectPath } from '@voltro/database';
|
|
10
18
|
import { VoltroPlugin } from '@voltro/protocol';
|
|
11
19
|
|
|
20
|
+
export declare interface CollectedTable {
|
|
21
|
+
readonly table: string;
|
|
22
|
+
readonly rows: ReadonlyArray<Record<string, unknown>>;
|
|
23
|
+
/** Set when the row cap cut this table short. */
|
|
24
|
+
readonly truncated?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Resolve every path against the store and return the rows per TABLE (merged
|
|
29
|
+
* and de-duplicated by `id` — two routes can legitimately reach the same table).
|
|
30
|
+
*
|
|
31
|
+
* Hop resolution: hop 0's filter is the subject id itself; each later hop is
|
|
32
|
+
* filtered by the `id`s (or `parentKey`s) found at the previous one. A hop that
|
|
33
|
+
* finds nothing terminates that path — there is no point asking for the children
|
|
34
|
+
* of an empty set, and asking anyway with an empty `IN` is how a filter
|
|
35
|
+
* accidentally becomes "all rows".
|
|
36
|
+
*/
|
|
37
|
+
export declare const collectSubjectRows: (store: GovStore, paths: ReadonlyArray<SubjectPath>, subjectId: string, rowCap?: number) => Promise<ReadonlyArray<CollectedTable>>;
|
|
38
|
+
|
|
12
39
|
export declare const CONSENT_TABLE = "_voltro_consent";
|
|
13
40
|
|
|
14
41
|
/** Narrow slice of the framework DataStore the durable consent store needs. */
|
|
@@ -65,12 +92,30 @@ export declare const dataStoreConsentStore: (store: ConsentDataStore) => Consent
|
|
|
65
92
|
/** Adapt a framework `DataStore` (untyped here) to `GovStore`. */
|
|
66
93
|
export declare const dataStoreGovStore: (store: unknown) => GovStore;
|
|
67
94
|
|
|
95
|
+
export { DEFAULT_SUBJECT_GRAPH_DEPTH }
|
|
96
|
+
|
|
68
97
|
export { deriveKey }
|
|
69
98
|
|
|
99
|
+
export { deriveSubjectGraph }
|
|
100
|
+
|
|
101
|
+
export { DeriveSubjectGraphInput }
|
|
102
|
+
|
|
70
103
|
export { ENCRYPTED_PREFIX }
|
|
71
104
|
|
|
72
|
-
/**
|
|
73
|
-
|
|
105
|
+
/**
|
|
106
|
+
* Erase a subject across `paths`. Returns an immutable erasure-log entry.
|
|
107
|
+
*
|
|
108
|
+
* DELETION ORDER IS DEEPEST-FIRST. The paths describe a parent→child graph and
|
|
109
|
+
* the branch's own foreign keys are real, so deleting a parent before its
|
|
110
|
+
* children either fails on `RESTRICT` or silently widens the erasure through
|
|
111
|
+
* `CASCADE` — and a cascade is an erasure nobody counted, which the log entry
|
|
112
|
+
* would then under-report.
|
|
113
|
+
*
|
|
114
|
+
* An `anonymize` erasure only touches columns the caller named, so it has no
|
|
115
|
+
* ordering requirement; it is run in the same order for one behaviour rather
|
|
116
|
+
* than two.
|
|
117
|
+
*/
|
|
118
|
+
export declare const eraseSubject: (store: GovStore, paths: ReadonlyArray<SubjectPath>, subjectId: string, opts?: ErasureOptions, now?: () => string) => Promise<ErasureLogEntry>;
|
|
74
119
|
|
|
75
120
|
/**
|
|
76
121
|
* Most-recent GDPR erasure-log entries kept in memory (most-recent-first).
|
|
@@ -89,6 +134,13 @@ export declare interface ErasureLogEntry {
|
|
|
89
134
|
table: string;
|
|
90
135
|
count: number;
|
|
91
136
|
}>;
|
|
137
|
+
/**
|
|
138
|
+
* Tables whose row set hit {@link SUBJECT_ROW_CAP}. NON-EMPTY MEANS THE
|
|
139
|
+
* ERASURE IS INCOMPLETE and must be re-run — recorded in the log entry rather
|
|
140
|
+
* than warned about, because the log entry is the compliance artefact and a
|
|
141
|
+
* warning on a console is not.
|
|
142
|
+
*/
|
|
143
|
+
readonly truncated?: ReadonlyArray<string>;
|
|
92
144
|
}
|
|
93
145
|
|
|
94
146
|
export declare interface ErasureOptions {
|
|
@@ -98,17 +150,48 @@ export declare interface ErasureOptions {
|
|
|
98
150
|
readonly anonymizeFields?: ReadonlyArray<string>;
|
|
99
151
|
}
|
|
100
152
|
|
|
101
|
-
/** Collect every row
|
|
102
|
-
export declare const exportSubject: (store: GovStore,
|
|
153
|
+
/** Collect every row belonging to a subject across `paths` → a portable bundle. */
|
|
154
|
+
export declare const exportSubject: (store: GovStore, paths: ReadonlyArray<SubjectPath>, subjectId: string) => Promise<Record<string, ReadonlyArray<Record<string, unknown>>>>;
|
|
103
155
|
|
|
104
156
|
export { FieldCipher }
|
|
105
157
|
|
|
158
|
+
export { formatSubjectGraph }
|
|
159
|
+
|
|
106
160
|
export declare const governancePlugin: (options?: GovernancePluginOptions) => VoltroPlugin;
|
|
107
161
|
|
|
108
162
|
export declare interface GovernancePluginOptions {
|
|
109
163
|
readonly retention?: ReadonlyArray<RetentionPolicy>;
|
|
110
|
-
/**
|
|
164
|
+
/**
|
|
165
|
+
* Tables (+ subject column) the GDPR export/erase walk — the EXPLICIT form.
|
|
166
|
+
*
|
|
167
|
+
* Still first-class, and the only way to reach data the schema does not
|
|
168
|
+
* encode: a subject id in a plain (non-`reference()`) column, a polymorphic
|
|
169
|
+
* `(ownerType, ownerId)` pair, an id inside JSON. Merged with anything
|
|
170
|
+
* `deriveSubjectScopes` finds.
|
|
171
|
+
*/
|
|
111
172
|
readonly subjectScopes?: ReadonlyArray<SubjectScope>;
|
|
173
|
+
/**
|
|
174
|
+
* DERIVE the subject scope from the schema instead of hand-listing it.
|
|
175
|
+
*
|
|
176
|
+
* Give it the table whose primary key is the subject id and the walk follows
|
|
177
|
+
* the relations registry + the `reference()` column graph outward, so a table
|
|
178
|
+
* added tomorrow is in the DSAR tomorrow — which a hand-written list is not.
|
|
179
|
+
* Only CHILD edges are followed (a table holding a reference to the subject's
|
|
180
|
+
* row), never parent/lookup edges: walking from a person to their organisation
|
|
181
|
+
* would export, and then ERASE, everybody else in it.
|
|
182
|
+
*
|
|
183
|
+
* It is not a substitute for reading `GovernanceService.subjectGraph()`. Some
|
|
184
|
+
* links are structurally invisible to it (see `SubjectGraph.limitations`) and
|
|
185
|
+
* the derivation says so rather than implying completeness.
|
|
186
|
+
*/
|
|
187
|
+
readonly deriveSubjectScopes?: {
|
|
188
|
+
readonly subjectTable: string;
|
|
189
|
+
/** Hop ceiling; default 4. Anything cut is reported as `depth-truncated`. */
|
|
190
|
+
readonly maxDepth?: number;
|
|
191
|
+
/** Tables never walked into. A framework audit trail is the usual entry —
|
|
192
|
+
* Art. 17(3)(b) frequently REQUIRES retaining it. */
|
|
193
|
+
readonly exclude?: ReadonlyArray<string>;
|
|
194
|
+
};
|
|
112
195
|
/** Default erasure mode + anonymise fields. */
|
|
113
196
|
readonly erasure?: ErasureOptions;
|
|
114
197
|
/**
|
|
@@ -133,6 +216,24 @@ export declare interface GovernancePluginOptions {
|
|
|
133
216
|
readonly fieldEncryption?: boolean | {
|
|
134
217
|
readonly secretKey?: string;
|
|
135
218
|
};
|
|
219
|
+
/**
|
|
220
|
+
* Namespace for this plugin's rpc tags + inspect endpoints. Default `governance`.
|
|
221
|
+
*
|
|
222
|
+
* Set it when your app already publishes under that name — an exact tag
|
|
223
|
+
* collision is fatal at codegen, and this is the way out. Orthogonal to
|
|
224
|
+
* `name` below: `alias` REPLACES the namespace, `name` distinguishes two
|
|
225
|
+
* installations within it.
|
|
226
|
+
*
|
|
227
|
+
* The cost, stated because nothing else states it: the local and cloud
|
|
228
|
+
* dashboards fetch this plugin's panel at the DEFAULT slug, so an aliased
|
|
229
|
+
* install keeps working while its dashboard panel 404s. Alias to escape a
|
|
230
|
+
* collision, not for taste.
|
|
231
|
+
*/
|
|
232
|
+
readonly alias?: string;
|
|
233
|
+
/**
|
|
234
|
+
* Discriminator for a SECOND installation of this plugin, when one app runs
|
|
235
|
+
* two (`@voltro/plugin-governance#analytics`). Not a rename — for that use `alias`.
|
|
236
|
+
*/
|
|
136
237
|
readonly name?: string;
|
|
137
238
|
}
|
|
138
239
|
|
|
@@ -147,19 +248,44 @@ export declare interface GovernanceServiceShape {
|
|
|
147
248
|
readonly recordConsent: (entry: ConsentRecord) => Promise<void>;
|
|
148
249
|
readonly hasConsent: (subjectId: string, purpose: string) => Promise<boolean>;
|
|
149
250
|
readonly runRetentionNow: () => Promise<void>;
|
|
251
|
+
/**
|
|
252
|
+
* The routes a DSAR will walk, plus what the derivation CANNOT see. Read this
|
|
253
|
+
* before signing off on an erasure process: the completeness of a right-to-be-
|
|
254
|
+
* forgotten claim is exactly the completeness of this list, and the honest
|
|
255
|
+
* parts of it are the `limitations`.
|
|
256
|
+
*/
|
|
257
|
+
readonly subjectGraph: () => SubjectGraph;
|
|
150
258
|
}
|
|
151
259
|
|
|
152
260
|
export declare interface GovStore {
|
|
153
261
|
/**
|
|
154
262
|
* All rows of a table.
|
|
155
263
|
*
|
|
156
|
-
* CEILING: this materialises the WHOLE table in memory.
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
264
|
+
* CEILING: this materialises the WHOLE table in memory. Nothing on the GDPR
|
|
265
|
+
* path uses it any more — export and erasure go through {@link where}, which
|
|
266
|
+
* pushes the subject filter into SQL. It survives for callers that genuinely
|
|
267
|
+
* want the whole table, and for the in-memory test store.
|
|
268
|
+
*
|
|
269
|
+
* It USED to back the DSAR walk, and the doc comment here argued that was fine
|
|
270
|
+
* because the operation is "admin-gated and occasional". That argument is
|
|
271
|
+
* wrong in the way scale arguments usually are: the cost is not per REQUEST,
|
|
272
|
+
* it is per TABLE IN THE SUBJECT SCOPE, and a derived scope has many more
|
|
273
|
+
* tables than a hand-listed one. One erasure meant one full scan of every
|
|
274
|
+
* table holding personal data.
|
|
161
275
|
*/
|
|
162
276
|
readonly all: (table: string) => Promise<ReadonlyArray<Record<string, unknown>>>;
|
|
277
|
+
/**
|
|
278
|
+
* Rows of `table` whose `column` is one of `values` — the indexed read the
|
|
279
|
+
* subject walk runs, one per hop.
|
|
280
|
+
*
|
|
281
|
+
* `values` is expected to be chunked by the caller ({@link SUBJECT_KEY_CHUNK})
|
|
282
|
+
* so the emitted `IN (…)` list stays inside every dialect's parameter ceiling.
|
|
283
|
+
* An EMPTY `values` must return no rows without touching the database: an
|
|
284
|
+
* unbounded `IN ()` is a syntax error on some engines and "every row" if a
|
|
285
|
+
* caller papers over it, and "every row" on an erasure path is the worst
|
|
286
|
+
* possible failure.
|
|
287
|
+
*/
|
|
288
|
+
readonly where: (table: string, column: string, values: ReadonlyArray<unknown>) => Promise<ReadonlyArray<Record<string, unknown>>>;
|
|
163
289
|
/**
|
|
164
290
|
* One bounded page of rows (`limit` rows starting at `offset`, ordered by a
|
|
165
291
|
* stable key). Lets the retention sweep scan a large table in capped batches
|
|
@@ -233,12 +359,49 @@ declare interface RetentionReport {
|
|
|
233
359
|
*/
|
|
234
360
|
export declare const runRetention: (store: GovStore, policies: ReadonlyArray<RetentionPolicy>, now?: number, batchSize?: number) => Promise<ReadonlyArray<RetentionReport>>;
|
|
235
361
|
|
|
362
|
+
/** Normalise a hand-listed scope into the path form the walk runs on. A scope on
|
|
363
|
+
* the subject table keyed by its own id column is depth 0; anything else is a
|
|
364
|
+
* single hop off the subject table. */
|
|
365
|
+
export declare const scopeToPath: (scope: SubjectScope, subjectTable: string) => SubjectPath;
|
|
366
|
+
|
|
236
367
|
/** Pure — the ids of rows past their TTL. */
|
|
237
368
|
export declare const selectExpired: (rows: ReadonlyArray<Record<string, unknown>>, policy: RetentionPolicy, now: number) => ReadonlyArray<string>;
|
|
238
369
|
|
|
239
370
|
export { setFieldCipher }
|
|
240
371
|
|
|
241
|
-
/**
|
|
372
|
+
/**
|
|
373
|
+
* Keys per `IN (…)` chunk. Well under every dialect's parameter ceiling (SQL
|
|
374
|
+
* Server's 2100 is the tightest) and small enough that one subject with a very
|
|
375
|
+
* large child set does not build a single statement megabytes wide.
|
|
376
|
+
*/
|
|
377
|
+
export declare const SUBJECT_KEY_CHUNK = 500;
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Rows collected per table, per subject. A ceiling exists because a DSAR is an
|
|
381
|
+
* unauthenticated-user-triggerable operation in most apps, and "export
|
|
382
|
+
* everything reachable" against a pathological graph is a memory exhaustion the
|
|
383
|
+
* caller cannot see coming. Exceeding it is REPORTED (`truncated`), never
|
|
384
|
+
* silently dropped — a short export presented as complete is the failure this
|
|
385
|
+
* whole module exists to avoid.
|
|
386
|
+
*/
|
|
387
|
+
export declare const SUBJECT_ROW_CAP = 50000;
|
|
388
|
+
|
|
389
|
+
export { SubjectGraph }
|
|
390
|
+
|
|
391
|
+
export { SubjectGraphLimitation }
|
|
392
|
+
|
|
393
|
+
export { SubjectHop }
|
|
394
|
+
|
|
395
|
+
export { SubjectPath }
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* One table that references a subject, by which column — the hand-written form.
|
|
399
|
+
*
|
|
400
|
+
* Still supported and still first-class: a subject id carried in a plain column,
|
|
401
|
+
* or reached by a rule the schema does not encode, can only be declared. It
|
|
402
|
+
* normalises into a depth-0/1 {@link SubjectPath}, so both forms run through one
|
|
403
|
+
* walk.
|
|
404
|
+
*/
|
|
242
405
|
export declare interface SubjectScope {
|
|
243
406
|
readonly table: string;
|
|
244
407
|
/** Column holding the subject id (e.g. 'userId', 'createdBy', 'id'). */
|