@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.
@@ -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 (52 packages).
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
- /** Erase a subject across `scopes`. Returns an immutable erasure-log entry. */
73
- export declare const eraseSubject: (store: GovStore, scopes: ReadonlyArray<SubjectScope>, subjectId: string, opts?: ErasureOptions, now?: () => string) => Promise<ErasureLogEntry>;
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 across `scopes` that belongs to a subject → a portable bundle. */
102
- export declare const exportSubject: (store: GovStore, scopes: ReadonlyArray<SubjectScope>, subjectId: string) => Promise<Record<string, ReadonlyArray<Record<string, unknown>>>>;
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
- /** Tables (+ subject column) the GDPR export/erase walk. */
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. It is used by the
157
- * GDPR export/erase walks, which are admin-gated, occasional, single-subject
158
- * operations fine in practice. Do NOT use it on the retention path (the
159
- * retention table is the one that grows unbounded) use {@link page} +
160
- * a bounded loop there instead.
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
- /** One table that references a subject, by which column. */
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'). */