@cosmicdrift/kumiko-types 0.165.1 → 0.165.3

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-types",
3
- "version": "0.165.1",
3
+ "version": "0.165.3",
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>",
@@ -1,5 +1,5 @@
1
1
  // Plain-data types for EntityTableMeta — split from the runtime
2
- // (buildEntityTableMeta, resolveTableName, defineUnmanagedTable) in
2
+ // (deriveEntityTableMeta, resolveTableName, defineUnmanagedTable) in
3
3
  // entity-table-meta.ts. Prep step for the types-only package extraction
4
4
  // (#1283) — this file must have ONLY `import type`, no value imports
5
5
  // (crypto/DB deps).
@@ -73,7 +73,7 @@ export type EntityTableMeta = {
73
73
  // discriminator to render warnings ("X tables are unmanaged").
74
74
  readonly source: "managed" | "unmanaged";
75
75
  // PII-subject-annotated field names (pii/userOwned/tenantOwned). Set by
76
- // buildEntityTableMeta so the registry can reject r.storeTable stores
76
+ // deriveEntityTableMeta so the registry can reject r.storeTable stores
77
77
  // whose direct writes would skip the executor's encryption (#820).
78
78
  readonly piiSubjectFields?: readonly string[];
79
79
  };
package/src/feature.ts CHANGED
@@ -791,7 +791,7 @@ export type FeatureRegistrar<TFeature extends string = string> = {
791
791
  //
792
792
  // EntityTableMeta carries the same column-shape that r.entity() builds,
793
793
  // minus the audit-trail + base-columns scaffolding. The `meta` argument
794
- // is the result of `defineUnmanagedTable(...)` / `buildEntityTableMeta(...)`
794
+ // is the result of `defineUnmanagedTable(...)` / `deriveEntityTableMeta(...)`
795
795
  // from `@cosmicdrift/kumiko-framework/db`.
796
796
  //
797
797
  // The required `reason` string is the marker that justifies the bypass —
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 (DSGVO Art. 17 — Crypto-Shredding) ---
24
- //
25
- // Felder die PII enthalten werden in Sprint 3 (crypto-shredding) mit einem
26
- // Subject-Schluessel encrypted gespeichert. Subject = die natuerliche Person
27
- // oder der Tenant der die Daten "besitzt". Loeschung erfolgt durch Vernichten
28
- // des Subject-Keys ("Crypto-Shredding") — der Datensatz bleibt physisch
29
- // (Audit-Trail bewahrt), ist aber nicht mehr entschluesselbar. Sprint 0
30
- // fuegt nur die Schema-Marker + Boot-Validation ein; Encrypt/Decrypt-Mechanik
31
- // kommt in Sprint 3.
32
- //
33
- // Drei orthogonale Markierungen:
34
- // - `pii: true` — Subject = die Entity selbst.
35
- // Beispiel: user.email gehoert User Marc.
36
- // - `userOwned: { ownerField }` Subject = der User der im genannten
37
- // Field referenziert ist.
38
- // Beispiel: comment.body gehoert
39
- // comment.authorId.
40
- // - `tenantOwned: true` Subject = der aktuelle Tenant
41
- // (ctx.tenantId zur Schreibzeit).
42
- // Beispiel: tenantBranding.brandColor.
43
- //
44
- // `anonymize` ist die Pro-Feld-Funktion die der retention-Cleanup-Job
45
- // (Sprint 2) aufruft wenn die Entity-Strategy "anonymize" lautet oder die
46
- // `blockDelete`-Frist abgelaufen ist. Beispiel: `() => "[ANONYMIZED]"` oder
47
- // `() => null`.
48
- //
49
- // `allowPlaintext` unterdrueckt PII-Heuristik-Boot-Warnings fuer Felder die
50
- // zwar PII-Naming haben (email, name, body) aber bewusst Klartext bleiben
51
- // sollen z.B. ticket.title als Geschaeftsdaten. Wert ist eine Begruendung
52
- // wie "is-business-data".
53
- //
54
- // `anonymize` darf sync oder async sein der Cleanup-Job (Sprint 2)
55
- // awaited den Return. Async-Funktionen sind sinnvoll wenn die Anonymisierung
56
- // einen Lookup braucht (z.B. konsistente Pseudonyme aus separater Tabelle).
57
- //
58
- // Siehe docs/plans/datenschutz/crypto-shredding.md und docs/plans/datenschutz/roadmap.md.
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 forbiddensorting 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 };