@cosmicdrift/kumiko-types 0.165.2 → 0.165.4
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/package.json +1 -1
- package/src/fields.ts +80 -36
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-types",
|
|
3
|
-
"version": "0.165.
|
|
3
|
+
"version": "0.165.4",
|
|
4
4
|
"description": "Framework-Type-Definitions für Kumiko — FeatureDefinition, BootCheck-Types und die reinen Engine-Types. Erlaubt Downstream-Konsumenten, gegen die Type-Contracts zu bauen, ohne das ganze Framework-Package zu importieren. Enthaelt auch die identitaets-sensitiven Error-Klassen (event-store-errors.ts, kms-adapter-types.ts) als Runtime-Code — kumiko-framework/kumiko-bundled-features deklarieren dieses Package deshalb als peerDependency (Single-Copy-Zwang), nicht als plain dependency.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
package/src/fields.ts
CHANGED
|
@@ -20,42 +20,86 @@ export type FieldAccess = {
|
|
|
20
20
|
// permanent history. Use for password hashes, API tokens, bank details,
|
|
21
21
|
// tax IDs. See docs/plans/architecture/projections.md.
|
|
22
22
|
|
|
23
|
-
// --- PII / Subject-Key Annotations (
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
// - `
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
// `
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
// `
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
//
|
|
23
|
+
// --- PII / Subject-Key Annotations (GDPR Art. 17) ---
|
|
24
|
+
//
|
|
25
|
+
// Four independent mechanisms, NOT interchangeable. Picking the wrong one is
|
|
26
|
+
// the recurring mistake this table exists to prevent: `encrypted: true` looks
|
|
27
|
+
// like the strongest option and is the only one with NO erasure guarantee.
|
|
28
|
+
//
|
|
29
|
+
// flag | at rest | searchable | Art. 17 erasure
|
|
30
|
+
// -------------------------------|------------|------------|----------------
|
|
31
|
+
// (none) | plaintext | yes | no
|
|
32
|
+
// allowPlaintext + anonymize | plaintext | yes | read side only
|
|
33
|
+
// pii / userOwned / tenantOwned | ciphertext | yes * | yes, key erase
|
|
34
|
+
// encrypted: true | ciphertext | no | NO
|
|
35
|
+
//
|
|
36
|
+
// * Subject-annotated + `searchable: true` (#1610): search consumer
|
|
37
|
+
// decrypts into Meilisearch. Events/projection stay ciphertext.
|
|
38
|
+
// eraseKey paths purge docs via purgeSearchDocumentsForSubject.
|
|
39
|
+
// `lookupable: true` still covers equality (blind index). `sortable`
|
|
40
|
+
// stays forbidden — sorting reads the ciphertext column.
|
|
41
|
+
//
|
|
42
|
+
// Encrypting a field is NOT a reason to make it unfindable. A user table you
|
|
43
|
+
// cannot search by name is a user table with no working search. The line that
|
|
44
|
+
// actually forbids search is `sensitive: true` — nobody may read those values
|
|
45
|
+
// back, and the event-store executor already strips them before the search
|
|
46
|
+
// consumer ever sees a payload. Password hashes, API tokens, bank details:
|
|
47
|
+
// never indexed. Email, username, display name: encrypted at rest, findable.
|
|
48
|
+
//
|
|
49
|
+
// Why `encrypted: true` erases nothing: it uses the app-wide master key,
|
|
50
|
+
// which is never destroyed per subject. Encryption-at-rest, nothing more.
|
|
51
|
+
// Only a subject annotation binds the field to a per-subject key that
|
|
52
|
+
// `crypto-shredding:write:forget-subject` (or the tenant-destroy hook's
|
|
53
|
+
// eraseSubjectKeys) can destroy, which makes every event and every
|
|
54
|
+
// projection copy of that subject unreadable at once.
|
|
55
|
+
//
|
|
56
|
+
// Why `anonymize` only covers the read side: it overwrites projection rows.
|
|
57
|
+
// `kumiko_events` is append-only and keeps the original payload forever
|
|
58
|
+
// (archiveStream stops REPLAY, it does not delete the row). A plaintext
|
|
59
|
+
// field stays permanently readable in the event log for anyone with database
|
|
60
|
+
// access — an access + retention question, not a crypto one. Choose it
|
|
61
|
+
// deliberately, not by forgetting to annotate.
|
|
62
|
+
//
|
|
63
|
+
// Which subject owns the field:
|
|
64
|
+
// - `pii: true` — the entity itself. user.email.
|
|
65
|
+
// - `userOwned: { ownerField }` — the user referenced in that field.
|
|
66
|
+
// comment.body → comment.authorId.
|
|
67
|
+
// - `tenantOwned: true` — the current tenant (ctx.tenantId at write
|
|
68
|
+
// time). tenantBranding.brandColor.
|
|
69
|
+
// `piiEncrypted: true` is a declarative alias over these for values the user
|
|
70
|
+
// may legitimately see back (their own IBAN), unlike `r.secret()`. Text
|
|
71
|
+
// fields only. `sortable` + subject annotation still throws; `searchable` is allowed (#1610). `sensitive` + `searchable` throws.
|
|
72
|
+
//
|
|
73
|
+
// Worked examples, all live in this repo:
|
|
74
|
+
//
|
|
75
|
+
// user.email (user/schema/user.ts) — login identifier, must stay findable.
|
|
76
|
+
// `pii: true, lookupable: true`. Ciphertext everywhere, exact lookup via
|
|
77
|
+
// the blind index, gone when the user's subject key dies.
|
|
78
|
+
//
|
|
79
|
+
// user.displayName (same file) — real name; substring search via Meili.
|
|
80
|
+
// `pii: true, searchable: true` (#1610). Ciphertext in events/projection;
|
|
81
|
+
// plaintext only in the derived index; purged on forget.
|
|
82
|
+
//
|
|
83
|
+
// ledger.description (ledger/entity.ts) — "Miete Januar" is accounting
|
|
84
|
+
// data that happens to read like PII, and full-text search over it is the
|
|
85
|
+
// point. `allowPlaintext: "is-business-data"`, cleaned up via retention,
|
|
86
|
+
// event log keeps the original. Accepted deliberately.
|
|
87
|
+
//
|
|
88
|
+
// subscription.providerCustomerId (billing-foundation/entities.ts) — a
|
|
89
|
+
// Mollie/Stripe id, never searched. `tenantOwned: true`, explicitly NOT
|
|
90
|
+
// `encrypted: true`: tenant-destroy erases the tenant subject key, and
|
|
91
|
+
// only the subject path shreds along with it (#800). Budget maxLength for
|
|
92
|
+
// ciphertext (`kumiko-pii:v1:<subject>:<blob>`, roughly plaintext × 2.3).
|
|
93
|
+
//
|
|
94
|
+
// `sensitive: true` is orthogonal to all of this — see its own note above.
|
|
95
|
+
//
|
|
96
|
+
// `anonymize` is the per-field function the retention-cleanup job calls when
|
|
97
|
+
// the entity strategy is "anonymize" or a `blockDelete` deadline expired.
|
|
98
|
+
// Sync or async (async when the pseudonym needs a lookup); the job awaits it.
|
|
99
|
+
// Example: `() => "[ANONYMIZED]"` or `() => null`.
|
|
100
|
+
//
|
|
101
|
+
// Background specs (both shipped, kumiko-platform/docs/archive/plans/):
|
|
102
|
+
// datenschutz/crypto-shredding.md, datenschutz/blind-index.md.
|
|
59
103
|
export type PiiAnnotations = {
|
|
60
104
|
readonly pii?: boolean;
|
|
61
105
|
readonly userOwned?: { readonly ownerField: string };
|