@cosmicdrift/kumiko-bundled-features 0.356.0 → 0.358.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.
Files changed (84) hide show
  1. package/dist/auth-mfa/i18n.js +1 -0
  2. package/dist/auth-mfa/schema/user-mfa.d.ts +6 -0
  3. package/dist/auth-mfa/screens.js +8 -1
  4. package/dist/billing-foundation/entities.d.ts +8 -0
  5. package/dist/billing-foundation/projection.d.ts +8 -0
  6. package/dist/cap-counter/book-cap-usage.d.ts +11 -5
  7. package/dist/cap-counter/book-cap-usage.js +54 -13
  8. package/dist/cap-counter/calendar-cap-usage-reservation.d.ts +17 -0
  9. package/dist/cap-counter/calendar-cap-usage-reservation.js +53 -0
  10. package/dist/cap-counter/cap-reservation.d.ts +15 -6
  11. package/dist/cap-counter/cap-reservation.js +80 -31
  12. package/dist/cap-counter/enforce-cap.d.ts +5 -5
  13. package/dist/cap-counter/enforce-cap.js +7 -20
  14. package/dist/cap-counter/entity.d.ts +1 -0
  15. package/dist/cap-counter/feature.js +7 -1
  16. package/dist/cap-counter/index.d.ts +1 -0
  17. package/dist/cap-counter/index.js +1 -0
  18. package/dist/cap-counter/with-cap-enforcement.js +4 -19
  19. package/dist/cap-overview/screens.js +3 -1
  20. package/dist/config/table.d.ts +6 -0
  21. package/dist/crypto-shredding/handlers/forget-subject.write.js +14 -3
  22. package/dist/custom-fields/entity.d.ts +4 -0
  23. package/dist/data-retention/schema/tenant-retention-override.d.ts +4 -0
  24. package/dist/delivery/chat-webhook-sender.js +1 -1
  25. package/dist/delivery/constants.d.ts +1 -0
  26. package/dist/delivery/constants.js +4 -0
  27. package/dist/delivery/delivery-service.js +62 -25
  28. package/dist/delivery/feature.js +7 -1
  29. package/dist/delivery/jobs.js +25 -1
  30. package/dist/delivery/tables.d.ts +6 -0
  31. package/dist/delivery/testing.d.ts +1 -0
  32. package/dist/document-ingest-foundation/entity.d.ts +4 -0
  33. package/dist/folders/entity.d.ts +8 -0
  34. package/dist/form-draft/entity.d.ts +2 -0
  35. package/dist/inbound-mail-foundation/entities.d.ts +25 -0
  36. package/dist/inbound-mail-foundation/projection.d.ts +20 -0
  37. package/dist/jobs/job-run-logger.js +21 -17
  38. package/dist/jobs/job-run-table.d.ts +6 -0
  39. package/dist/jobs/job-run-table.js +10 -3
  40. package/dist/ledger/entity.d.ts +13 -0
  41. package/dist/managed-pages/table.d.ts +12 -0
  42. package/dist/notes-history/entity.d.ts +12 -0
  43. package/dist/notes-history/entity.js +9 -7
  44. package/dist/notes-history-user-data/hooks.d.ts +8 -0
  45. package/dist/notes-history-user-data/hooks.js +56 -72
  46. package/dist/notes-history-user-data/index.d.ts +1 -0
  47. package/dist/notes-history-user-data/index.js +2 -1
  48. package/dist/personal-access-tokens/schema/api-token.d.ts +6 -0
  49. package/dist/personal-access-tokens/screens.js +2 -0
  50. package/dist/secrets/table.d.ts +1 -0
  51. package/dist/sessions/schema/user-session.d.ts +4 -0
  52. package/dist/sessions/screens.js +2 -0
  53. package/dist/tags/entity.d.ts +9 -0
  54. package/dist/template-resolver/table.d.ts +10 -0
  55. package/dist/template-resolver/user-content-table.d.ts +10 -0
  56. package/dist/tenant/constants.d.ts +1 -0
  57. package/dist/tenant/constants.js +6 -3
  58. package/dist/tenant/feature.js +2 -0
  59. package/dist/tenant/handlers/member-role-availability.query.d.ts +7 -0
  60. package/dist/tenant/handlers/member-role-availability.query.js +18 -0
  61. package/dist/tenant/invitation-table.d.ts +6 -0
  62. package/dist/tenant/member-role-options.d.ts +2 -0
  63. package/dist/tenant/member-role-options.js +4 -0
  64. package/dist/tenant/membership-table.d.ts +4 -0
  65. package/dist/tenant/schema/tenant.d.ts +6 -0
  66. package/dist/tenant/screens.d.ts +4 -2
  67. package/dist/tenant/screens.js +10 -6
  68. package/dist/tier-engine/entity.d.ts +2 -0
  69. package/dist/tier-engine/tier-resolver.d.ts +2 -0
  70. package/dist/user/schema/user.d.ts +12 -0
  71. package/dist/user/screens.js +8 -1
  72. package/dist/user-data-rights/schema/download-attempt.d.ts +14 -0
  73. package/dist/user-data-rights/schema/download-token.d.ts +8 -0
  74. package/dist/user-data-rights/schema/export-job.d.ts +6 -0
  75. package/dist/user-data-rights/screens.js +8 -1
  76. package/dist/waitlist/entity.d.ts +14 -0
  77. package/dist/waitlist/screens.js +8 -1
  78. package/package.json +9 -9
  79. package/src/auth-mfa/changes.json +6 -0
  80. package/src/cap-counter/changes.json +7 -0
  81. package/src/crypto-shredding/changes.json +6 -0
  82. package/src/delivery/changes.json +6 -0
  83. package/src/notes-history/changes.json +6 -0
  84. package/src/tenant/changes.json +6 -0
@@ -4,6 +4,7 @@ export declare const accountEntity: import("@cosmicdrift/kumiko-types/fields").E
4
4
  readonly description?: string;
5
5
  readonly maxLength?: number;
6
6
  readonly minLength?: number;
7
+ readonly autoComplete?: string;
7
8
  readonly required?: boolean;
8
9
  readonly searchable?: boolean;
9
10
  readonly sortable?: boolean;
@@ -51,6 +52,7 @@ export declare const accountEntity: import("@cosmicdrift/kumiko-types/fields").E
51
52
  readonly description?: string;
52
53
  readonly maxLength?: number;
53
54
  readonly minLength?: number;
55
+ readonly autoComplete?: string;
54
56
  readonly required?: boolean;
55
57
  readonly searchable?: boolean;
56
58
  readonly sortable?: boolean;
@@ -73,6 +75,7 @@ export declare const accountEntity: import("@cosmicdrift/kumiko-types/fields").E
73
75
  readonly description?: string;
74
76
  readonly maxLength?: number;
75
77
  readonly minLength?: number;
78
+ readonly autoComplete?: string;
76
79
  readonly required?: boolean;
77
80
  readonly searchable?: boolean;
78
81
  readonly sortable?: boolean;
@@ -111,6 +114,7 @@ export declare const transactionEntity: import("@cosmicdrift/kumiko-types/fields
111
114
  readonly description?: string;
112
115
  readonly maxLength?: number;
113
116
  readonly minLength?: number;
117
+ readonly autoComplete?: string;
114
118
  readonly required?: boolean;
115
119
  readonly searchable?: boolean;
116
120
  readonly sortable?: boolean;
@@ -133,6 +137,7 @@ export declare const transactionEntity: import("@cosmicdrift/kumiko-types/fields
133
137
  readonly description?: string;
134
138
  readonly maxLength?: number;
135
139
  readonly minLength?: number;
140
+ readonly autoComplete?: string;
136
141
  readonly required?: boolean;
137
142
  readonly searchable?: boolean;
138
143
  readonly sortable?: boolean;
@@ -193,6 +198,7 @@ export declare const transactionEntity: import("@cosmicdrift/kumiko-types/fields
193
198
  readonly description?: string;
194
199
  readonly maxLength?: number;
195
200
  readonly minLength?: number;
201
+ readonly autoComplete?: string;
196
202
  readonly required?: boolean;
197
203
  readonly searchable?: boolean;
198
204
  readonly sortable?: boolean;
@@ -215,6 +221,7 @@ export declare const transactionEntity: import("@cosmicdrift/kumiko-types/fields
215
221
  readonly description?: string;
216
222
  readonly maxLength?: number;
217
223
  readonly minLength?: number;
224
+ readonly autoComplete?: string;
218
225
  readonly required?: boolean;
219
226
  readonly searchable?: boolean;
220
227
  readonly sortable?: boolean;
@@ -239,6 +246,7 @@ export declare const scheduleEntity: import("@cosmicdrift/kumiko-types/fields").
239
246
  readonly description?: string;
240
247
  readonly maxLength?: number;
241
248
  readonly minLength?: number;
249
+ readonly autoComplete?: string;
242
250
  readonly required?: boolean;
243
251
  readonly searchable?: boolean;
244
252
  readonly sortable?: boolean;
@@ -329,6 +337,7 @@ export declare const scheduleEntity: import("@cosmicdrift/kumiko-types/fields").
329
337
  readonly description?: string;
330
338
  readonly maxLength?: number;
331
339
  readonly minLength?: number;
340
+ readonly autoComplete?: string;
332
341
  readonly required?: boolean;
333
342
  readonly searchable?: boolean;
334
343
  readonly sortable?: boolean;
@@ -351,6 +360,7 @@ export declare const scheduleEntity: import("@cosmicdrift/kumiko-types/fields").
351
360
  readonly description?: string;
352
361
  readonly maxLength?: number;
353
362
  readonly minLength?: number;
363
+ readonly autoComplete?: string;
354
364
  readonly required?: boolean;
355
365
  readonly searchable?: boolean;
356
366
  readonly sortable?: boolean;
@@ -373,6 +383,7 @@ export declare const scheduleEntity: import("@cosmicdrift/kumiko-types/fields").
373
383
  readonly description?: string;
374
384
  readonly maxLength?: number;
375
385
  readonly minLength?: number;
386
+ readonly autoComplete?: string;
376
387
  readonly required?: boolean;
377
388
  readonly searchable?: boolean;
378
389
  readonly sortable?: boolean;
@@ -395,6 +406,7 @@ export declare const scheduleEntity: import("@cosmicdrift/kumiko-types/fields").
395
406
  readonly description?: string;
396
407
  readonly maxLength?: number;
397
408
  readonly minLength?: number;
409
+ readonly autoComplete?: string;
398
410
  readonly required?: boolean;
399
411
  readonly searchable?: boolean;
400
412
  readonly sortable?: boolean;
@@ -417,6 +429,7 @@ export declare const scheduleEntity: import("@cosmicdrift/kumiko-types/fields").
417
429
  readonly description?: string;
418
430
  readonly maxLength?: number;
419
431
  readonly minLength?: number;
432
+ readonly autoComplete?: string;
420
433
  readonly required?: boolean;
421
434
  readonly searchable?: boolean;
422
435
  readonly sortable?: boolean;
@@ -4,6 +4,7 @@ export declare const pageEntity: import("@cosmicdrift/kumiko-types/fields").Enti
4
4
  readonly description?: string;
5
5
  readonly maxLength?: number;
6
6
  readonly minLength?: number;
7
+ readonly autoComplete?: string;
7
8
  readonly required?: boolean;
8
9
  readonly searchable?: boolean;
9
10
  readonly sortable?: boolean;
@@ -26,6 +27,7 @@ export declare const pageEntity: import("@cosmicdrift/kumiko-types/fields").Enti
26
27
  readonly description?: string;
27
28
  readonly maxLength?: number;
28
29
  readonly minLength?: number;
30
+ readonly autoComplete?: string;
29
31
  readonly required?: boolean;
30
32
  readonly searchable?: boolean;
31
33
  readonly sortable?: boolean;
@@ -48,6 +50,7 @@ export declare const pageEntity: import("@cosmicdrift/kumiko-types/fields").Enti
48
50
  readonly description?: string;
49
51
  readonly maxLength?: number;
50
52
  readonly minLength?: number;
53
+ readonly autoComplete?: string;
51
54
  readonly required?: boolean;
52
55
  readonly searchable?: boolean;
53
56
  readonly sortable?: boolean;
@@ -70,6 +73,7 @@ export declare const pageEntity: import("@cosmicdrift/kumiko-types/fields").Enti
70
73
  readonly description?: string;
71
74
  readonly maxLength?: number;
72
75
  readonly minLength?: number;
76
+ readonly autoComplete?: string;
73
77
  readonly required?: boolean;
74
78
  readonly searchable?: boolean;
75
79
  readonly sortable?: boolean;
@@ -92,6 +96,7 @@ export declare const pageEntity: import("@cosmicdrift/kumiko-types/fields").Enti
92
96
  readonly description?: string;
93
97
  readonly maxLength?: number;
94
98
  readonly minLength?: number;
99
+ readonly autoComplete?: string;
95
100
  readonly required?: boolean;
96
101
  readonly searchable?: boolean;
97
102
  readonly sortable?: boolean;
@@ -114,6 +119,7 @@ export declare const pageEntity: import("@cosmicdrift/kumiko-types/fields").Enti
114
119
  readonly description?: string;
115
120
  readonly maxLength?: number;
116
121
  readonly minLength?: number;
122
+ readonly autoComplete?: string;
117
123
  readonly required?: boolean;
118
124
  readonly searchable?: boolean;
119
125
  readonly sortable?: boolean;
@@ -141,6 +147,7 @@ export declare const pagesTable: import("@cosmicdrift/kumiko-framework/db").Enti
141
147
  readonly description?: string;
142
148
  readonly maxLength?: number;
143
149
  readonly minLength?: number;
150
+ readonly autoComplete?: string;
144
151
  readonly required?: boolean;
145
152
  readonly searchable?: boolean;
146
153
  readonly sortable?: boolean;
@@ -163,6 +170,7 @@ export declare const pagesTable: import("@cosmicdrift/kumiko-framework/db").Enti
163
170
  readonly description?: string;
164
171
  readonly maxLength?: number;
165
172
  readonly minLength?: number;
173
+ readonly autoComplete?: string;
166
174
  readonly required?: boolean;
167
175
  readonly searchable?: boolean;
168
176
  readonly sortable?: boolean;
@@ -185,6 +193,7 @@ export declare const pagesTable: import("@cosmicdrift/kumiko-framework/db").Enti
185
193
  readonly description?: string;
186
194
  readonly maxLength?: number;
187
195
  readonly minLength?: number;
196
+ readonly autoComplete?: string;
188
197
  readonly required?: boolean;
189
198
  readonly searchable?: boolean;
190
199
  readonly sortable?: boolean;
@@ -207,6 +216,7 @@ export declare const pagesTable: import("@cosmicdrift/kumiko-framework/db").Enti
207
216
  readonly description?: string;
208
217
  readonly maxLength?: number;
209
218
  readonly minLength?: number;
219
+ readonly autoComplete?: string;
210
220
  readonly required?: boolean;
211
221
  readonly searchable?: boolean;
212
222
  readonly sortable?: boolean;
@@ -229,6 +239,7 @@ export declare const pagesTable: import("@cosmicdrift/kumiko-framework/db").Enti
229
239
  readonly description?: string;
230
240
  readonly maxLength?: number;
231
241
  readonly minLength?: number;
242
+ readonly autoComplete?: string;
232
243
  readonly required?: boolean;
233
244
  readonly searchable?: boolean;
234
245
  readonly sortable?: boolean;
@@ -251,6 +262,7 @@ export declare const pagesTable: import("@cosmicdrift/kumiko-framework/db").Enti
251
262
  readonly description?: string;
252
263
  readonly maxLength?: number;
253
264
  readonly minLength?: number;
265
+ readonly autoComplete?: string;
254
266
  readonly required?: boolean;
255
267
  readonly searchable?: boolean;
256
268
  readonly sortable?: boolean;
@@ -5,6 +5,7 @@ export declare function createNoteEntryEntity(access?: EntityDefinition["access"
5
5
  readonly description?: string;
6
6
  readonly maxLength?: number;
7
7
  readonly minLength?: number;
8
+ readonly autoComplete?: string;
8
9
  readonly required?: boolean;
9
10
  readonly searchable?: boolean;
10
11
  readonly sortable?: boolean;
@@ -27,6 +28,7 @@ export declare function createNoteEntryEntity(access?: EntityDefinition["access"
27
28
  readonly description?: string;
28
29
  readonly maxLength?: number;
29
30
  readonly minLength?: number;
31
+ readonly autoComplete?: string;
30
32
  readonly required?: boolean;
31
33
  readonly searchable?: boolean;
32
34
  readonly sortable?: boolean;
@@ -49,6 +51,7 @@ export declare function createNoteEntryEntity(access?: EntityDefinition["access"
49
51
  readonly description?: string;
50
52
  readonly maxLength?: number;
51
53
  readonly minLength?: number;
54
+ readonly autoComplete?: string;
52
55
  readonly required?: boolean;
53
56
  readonly searchable?: boolean;
54
57
  readonly sortable?: boolean;
@@ -71,6 +74,7 @@ export declare function createNoteEntryEntity(access?: EntityDefinition["access"
71
74
  readonly description?: string;
72
75
  readonly maxLength?: number;
73
76
  readonly minLength?: number;
77
+ readonly autoComplete?: string;
74
78
  readonly required?: boolean;
75
79
  readonly searchable?: boolean;
76
80
  readonly sortable?: boolean;
@@ -111,6 +115,7 @@ export declare const noteEntryEntity: EntityDefinition<{
111
115
  readonly description?: string;
112
116
  readonly maxLength?: number;
113
117
  readonly minLength?: number;
118
+ readonly autoComplete?: string;
114
119
  readonly required?: boolean;
115
120
  readonly searchable?: boolean;
116
121
  readonly sortable?: boolean;
@@ -133,6 +138,7 @@ export declare const noteEntryEntity: EntityDefinition<{
133
138
  readonly description?: string;
134
139
  readonly maxLength?: number;
135
140
  readonly minLength?: number;
141
+ readonly autoComplete?: string;
136
142
  readonly required?: boolean;
137
143
  readonly searchable?: boolean;
138
144
  readonly sortable?: boolean;
@@ -155,6 +161,7 @@ export declare const noteEntryEntity: EntityDefinition<{
155
161
  readonly description?: string;
156
162
  readonly maxLength?: number;
157
163
  readonly minLength?: number;
164
+ readonly autoComplete?: string;
158
165
  readonly required?: boolean;
159
166
  readonly searchable?: boolean;
160
167
  readonly sortable?: boolean;
@@ -177,6 +184,7 @@ export declare const noteEntryEntity: EntityDefinition<{
177
184
  readonly description?: string;
178
185
  readonly maxLength?: number;
179
186
  readonly minLength?: number;
187
+ readonly autoComplete?: string;
180
188
  readonly required?: boolean;
181
189
  readonly searchable?: boolean;
182
190
  readonly sortable?: boolean;
@@ -217,6 +225,7 @@ export declare function createNoteMentionEntity(): EntityDefinition<{
217
225
  readonly description?: string;
218
226
  readonly maxLength?: number;
219
227
  readonly minLength?: number;
228
+ readonly autoComplete?: string;
220
229
  readonly required?: boolean;
221
230
  readonly searchable?: boolean;
222
231
  readonly sortable?: boolean;
@@ -239,6 +248,7 @@ export declare function createNoteMentionEntity(): EntityDefinition<{
239
248
  readonly description?: string;
240
249
  readonly maxLength?: number;
241
250
  readonly minLength?: number;
251
+ readonly autoComplete?: string;
242
252
  readonly required?: boolean;
243
253
  readonly searchable?: boolean;
244
254
  readonly sortable?: boolean;
@@ -263,6 +273,7 @@ export declare const noteMentionEntity: EntityDefinition<{
263
273
  readonly description?: string;
264
274
  readonly maxLength?: number;
265
275
  readonly minLength?: number;
276
+ readonly autoComplete?: string;
266
277
  readonly required?: boolean;
267
278
  readonly searchable?: boolean;
268
279
  readonly sortable?: boolean;
@@ -285,6 +296,7 @@ export declare const noteMentionEntity: EntityDefinition<{
285
296
  readonly description?: string;
286
297
  readonly maxLength?: number;
287
298
  readonly minLength?: number;
299
+ readonly autoComplete?: string;
288
300
  readonly required?: boolean;
289
301
  readonly searchable?: boolean;
290
302
  readonly sortable?: boolean;
@@ -20,17 +20,18 @@ import { createEntity, createLongTextField, createTextField, } from "@cosmicdrif
20
20
  //
21
21
  // `body` is instead `personal: { of: "id" }` (Row-Subject, fw#2596/#2786):
22
22
  // the note's OWN row is its content's subject, so `body` is encrypted under a
23
- // per-note key that nothing else shares. That key gets erased only when
24
- // `note-mention` (below) names this note as reached by a forgotten subject
25
- // (see `notes-history-user-data/hooks.ts`) — a note with no mention on the
26
- // forgotten subject is untouched. GDPR erasure of the AUTHOR still separately
23
+ // per-note key that nothing else shares. That key gets erased only when the
24
+ // note is reached by a forgotten subject: `note-mention` (below) names the
25
+ // subject, or the note hangs on the subject as host (entityId, plus entityType
26
+ // for record subjects). See `notes-history-user-data/hooks.ts`; any other note
27
+ // is untouched. GDPR erasure of the AUTHOR still separately
27
28
  // shreds `authorName` (`personal: { of: "authorId" }`, the name as stamped at
28
29
  // write time); the two erasures are independent.
29
30
  //
30
31
  // Known gap (fw#2596, not fixed by this entity): prose that names a third
31
32
  // party without a structured @-mention is not tracked — regex/NLP over
32
- // free text is unreliable. The operator's manual `forget-subject` path
33
- // covers what this structured trigger misses.
33
+ // free text is unreliable. An operator must erase such a note's own record
34
+ // subject (`forget-subject` kind "record", entity "note-entry") to cover it.
34
35
  export function createNoteEntryEntity(access, parents) {
35
36
  return createEntity({
36
37
  table: "read_note_entries",
@@ -66,9 +67,10 @@ export function createNoteEntryEntity(access, parents) {
66
67
  }),
67
68
  // Stamped once at write time, never re-resolved later — the history
68
69
  // needs the name as it was then, not whatever the account is called now.
70
+ // whenAbsent: authorless notes (system-written) encrypt under the tenant key.
69
71
  authorName: createTextField({
70
72
  maxLength: 200,
71
- personal: { of: "authorId" },
73
+ personal: { of: "authorId", whenAbsent: "tenant" },
72
74
  find: "none",
73
75
  }),
74
76
  body: createLongTextField({
@@ -1,5 +1,13 @@
1
+ import { type SubjectId } from "@cosmicdrift/kumiko-framework/crypto";
2
+ import type { DbRunner, TenantDb } from "@cosmicdrift/kumiko-framework/db";
1
3
  import type { UserDataDeleteHook, UserDataExportHook } from "@cosmicdrift/kumiko-framework/engine";
2
4
  export declare const noteEntryExportHook: UserDataExportHook;
5
+ export declare function eraseNoteBodiesReachingSubject(args: {
6
+ readonly db: DbRunner | TenantDb;
7
+ readonly subject: SubjectId;
8
+ readonly erasedBy: string;
9
+ readonly requestId: string;
10
+ }): Promise<void>;
3
11
  export declare const noteEntryDeleteHook: UserDataDeleteHook;
4
12
  export declare const noteMentionExportHook: UserDataExportHook;
5
13
  export declare const noteMentionDeleteHook: UserDataDeleteHook;
@@ -3,10 +3,9 @@
3
3
  // notes consumers without the user-data-rights pipeline don't pull a hard
4
4
  // dependency. Mirrors job-run/delivery-attempt (user-data-rights-defaults):
5
5
  // export-only, erasure via crypto-shredding.
6
+ import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
6
7
  import { configuredPiiSubjectKms } from "@cosmicdrift/kumiko-framework/crypto";
7
- import { resolveRetentionPolicyForTenant, resolveTenantRetentionPreset, } from "../data-retention/index.js";
8
8
  import { noteEntryTable, noteMentionTable } from "../notes-history/index.js";
9
- import { policyToStrategy } from "../user-data-rights/index.js";
10
9
  // note-entry has no per-tenant scope quirk (unlike folders) — it's genuinely
11
10
  // per-user content, so the export filters by authorId directly.
12
11
  export const noteEntryExportHook = async (ctx) => {
@@ -26,93 +25,78 @@ export const noteEntryExportHook = async (ctx) => {
26
25
  })),
27
26
  };
28
27
  };
28
+ // What erasing a subject's key reaches: notes that @-mention the subject plus notes
29
+ // attached to it as host. Export and erasure both go through this, so Art. 15
30
+ // lists exactly what Art. 17 shreds.
31
+ async function selectNotesReachingSubject(db, subject) {
32
+ if (subject.kind === "tenant")
33
+ return [];
34
+ const subjectId = subject.kind === "user" ? subject.userId : subject.id;
35
+ const attachedWhere = subject.kind === "record"
36
+ ? { entityType: subject.entity, entityId: subjectId }
37
+ : { entityId: subjectId };
38
+ const mentions = await selectMany(db, noteMentionTable, { subjectId });
39
+ const mentioned = mentions.length === 0
40
+ ? []
41
+ : await selectMany(db, noteEntryTable, {
42
+ id: { in: [...new Set(mentions.map((m) => m.noteId))] },
43
+ });
44
+ const attached = await selectMany(db, noteEntryTable, attachedWhere);
45
+ const byId = new Map();
46
+ for (const note of [...mentioned, ...attached])
47
+ byId.set(note.id, note);
48
+ return [...byId.values()];
49
+ }
29
50
  // `authorName` is separately annotated `personal: { of: "authorId" }`
30
51
  // (entity.ts), so forgetting the AUTHOR already crypto-shreds that field on
31
- // its own — unrelated to this hook. `body` is Row-Subject (`personal: { of:
32
- // "id" }`): its key is per-note, not per-user, so forgetting a user only
33
- // reaches a note's body when that note structurally @-mentions the user via
34
- // a `note-mention` row (fw#2787). Free-text mentions are NOT detected —
35
- // that's the documented gap in entity.ts, not a bug here.
52
+ // its own. `body` is Row-Subject (`personal: { of: "id" }`): its key is per
53
+ // note, so forgetting a subject only reaches a body when the note structurally
54
+ // @-mentions the subject (`note-mention`, fw#2787) or hangs on it as host.
55
+ // Free-text mentions are NOT detected — the documented gap in entity.ts.
36
56
  //
37
- // Per reached note: consult the HOST entity's (entityType, not note-entry's
38
- // own) retention strategy first — blockDelete/anonymize must win over
39
- // erasure, same rule the automated forget-cleanup pipeline applies to every
40
- // other entity. The `strategy` this hook itself is called with is resolved
41
- // for note-entry, not the host, so it is deliberately unused here.
57
+ // Reached bodies are shredded regardless of the host entity's retention strategy:
58
+ // the note text is free-form PII, and a host's anonymize/blockDelete protects the
59
+ // row, not its content, just like the host's own PII fields get anonymized.
42
60
  //
43
- // Precondition: this ONLY erases anything if the app mounts a KMS adapter —
44
- // without one, record-owned fields fall back to plaintext storage
45
- // framework-wide (see pii-field-encryption.ts) and forget is a true no-op.
46
- // That gap is a property of the framework's crypto-shredding design, not
47
- // specific to this hook; apps that need Art.17 coverage without KMS must
48
- // mount one.
49
- export const noteEntryDeleteHook = async (ctx) => {
61
+ // Without a mounted KMS this erases nothing: record-owned fields fall back to
62
+ // plaintext framework-wide (see pii-field-encryption.ts), so apps that need
63
+ // Art. 17 coverage must mount one.
64
+ export async function eraseNoteBodiesReachingSubject(args) {
50
65
  const kms = configuredPiiSubjectKms();
51
- // skip: no KMS adapter mounted — record-owned fields stay plaintext framework-wide, forget is a true no-op
66
+ // skip: no KMS adapter mounted — forget is a true no-op
52
67
  if (!kms)
53
68
  return;
54
- const mentions = await ctx.db.selectMany(noteMentionTable, {
55
- subjectId: ctx.userId,
56
- });
57
- // skip: no note-mention rows for this user — nothing structurally reaches this user's data to shred
58
- if (mentions.length === 0)
59
- return;
60
- const noteIds = new Set(mentions.map((m) => m.noteId));
61
- // Once per hook run: the preset depends on the tenant only, not the note.
62
- const tenantPreset = await resolveTenantRetentionPreset({
63
- db: ctx.db,
64
- registry: ctx.registry,
65
- tenantId: ctx.tenantId,
66
- });
67
- // Append-only rows are never hard-deleted, so every mentioned note should
68
- // resolve; a missing one has no host and thus no retention policy to consult.
69
- const notes = await ctx.db.selectMany(noteEntryTable, {
70
- id: { in: [...noteIds] },
71
- });
72
- const strategyByEntityType = new Map();
69
+ const notes = await selectNotesReachingSubject(args.db, args.subject);
73
70
  for (const note of notes) {
74
- let strategy = strategyByEntityType.get(note.entityType);
75
- if (strategy === undefined) {
76
- const hostPolicy = await resolveRetentionPolicyForTenant({
77
- db: ctx.db,
78
- registry: ctx.registry,
79
- tenantId: ctx.tenantId,
80
- entityName: note.entityType,
81
- preloadedTenantPreset: tenantPreset,
82
- });
83
- strategy = policyToStrategy(hostPolicy.policy?.strategy ?? null);
84
- strategyByEntityType.set(note.entityType, strategy);
85
- }
86
- if (strategy === "anonymize")
87
- continue;
88
- const noteId = note.id;
89
- await kms.eraseKey({ kind: "record", entity: "note-entry", id: noteId }, {
90
- requestId: "notes-history-user-data:forget-mentioned-notes",
91
- userId: ctx.userId,
92
- eraseReason: "notes-history:mention-forget",
71
+ await kms.eraseKey({ kind: "record", entity: "note-entry", id: note.id }, {
72
+ requestId: args.requestId,
73
+ userId: args.erasedBy,
74
+ eraseReason: "notes-history:subject-forget",
93
75
  });
94
76
  }
77
+ }
78
+ export const noteEntryDeleteHook = async (ctx) => {
79
+ await eraseNoteBodiesReachingSubject({
80
+ db: ctx.db,
81
+ subject: { kind: "user", userId: ctx.userId },
82
+ erasedBy: ctx.userId,
83
+ requestId: "notes-history-user-data:forget-reached-notes",
84
+ });
95
85
  };
96
- // Erasure reaches every note that structurally mentions the user (see
97
- // noteEntryDeleteHook), so the Art. 15/20 export must list those notes too.
86
+ // Erasure reaches every note that mentions the user or hangs on the user as host
87
+ // (see eraseNoteBodiesReachingSubject), so the Art. 15/20 export lists those too.
98
88
  export const noteMentionExportHook = async (ctx) => {
99
- const mentions = await ctx.db.selectMany(noteMentionTable, {
100
- subjectId: ctx.userId,
101
- });
102
- if (mentions.length === 0)
103
- return null;
104
- const noteIds = [...new Set(mentions.map((m) => m.noteId))];
105
- const notes = await ctx.db.selectMany(noteEntryTable, { id: { in: noteIds } });
89
+ const notes = await selectNotesReachingSubject(ctx.db, { kind: "user", userId: ctx.userId });
106
90
  if (notes.length === 0)
107
91
  return null;
108
92
  return {
109
93
  entity: "note-mention",
110
94
  rows: notes.map((note) => ({
111
- noteId: note["id"],
112
- entityType: note["entityType"],
113
- entityId: note["entityId"],
114
- body: note["body"],
115
- insertedAt: note["insertedAt"],
95
+ noteId: note.id,
96
+ entityType: note.entityType,
97
+ entityId: note.entityId,
98
+ body: note.body,
99
+ insertedAt: note.insertedAt,
116
100
  })),
117
101
  };
118
102
  };
@@ -1,3 +1,4 @@
1
+ export { eraseNoteBodiesReachingSubject } from "./hooks.js";
1
2
  export declare const notesHistoryUserDataFeature: import("@cosmicdrift/kumiko-types/feature").FeatureDefinition & {
2
3
  readonly exports: void;
3
4
  };
@@ -6,8 +6,9 @@
6
6
  // user-data-rights stack. Mirrors folders-user-data.
7
7
  import { defineFeature, EXT_USER_DATA } from "@cosmicdrift/kumiko-framework/engine";
8
8
  import { noteEntryDeleteHook, noteEntryExportHook, noteMentionDeleteHook, noteMentionExportHook, } from "./hooks.js";
9
+ export { eraseNoteBodiesReachingSubject } from "./hooks.js";
9
10
  export const notesHistoryUserDataFeature = defineFeature("notes-history-user-data", (r) => {
10
- r.describe("GDPR (Art. 20 export / Art. 17 erasure) coverage for the `notes-history` feature's `note-entry` and `note-mention` entities. Mounts the export hook so a user's authored notes are included in the user-data export bundle; the note-entry delete hook looks up `note-mention` for notes that structurally @-mention the forgotten user and crypto-shreds each reached note's row-subject key (after consulting that note's host entity's retention strategy — blockDelete/anonymize win over erasure), keeping the append-only history intact for notes without such a mention. `note-mention`'s own hooks are a no-op — it is a plain FK pointer, not separately exportable content. Kept separate from `notes-history` so notes consumers without the user-data-rights pipeline don't pull a hard dependency — requires `user-data-rights`, optionalRequires `notes-history`.");
11
+ r.describe("GDPR (Art. 20 export / Art. 17 erasure) coverage for the `notes-history` feature's `note-entry` and `note-mention` entities. Mounts the export hook so a user's authored notes are included in the user-data export bundle; the note-entry delete hook crypto-shreds the row-subject key of each note that structurally @-mentions the forgotten user (`note-mention`) or hangs on the user as host entity (regardless of the host entity's retention strategy: the note text is free PII, the host's anonymize/blockDelete protects the row, not the content), keeping the append-only history intact for all other notes. The manual `crypto-shredding` forget-subject command reaches the same notes for user and record subjects through the exported `eraseNoteBodiesReachingSubject`, and the export lists them under `note-mention`. `note-mention`'s own hooks are a no-op — it is a plain FK pointer, not separately exportable content. Kept separate from `notes-history` so notes consumers without the user-data-rights pipeline don't pull a hard dependency — requires `user-data-rights`, optionalRequires `notes-history`.");
11
12
  // user-data-rights is the hard dependency (EXT_USER_DATA host). notes-history
12
13
  // is OPTIONAL: if it's mounted toggleable(default=false), a hard r.requires
13
14
  // would throw an "effectively disabled" boot warning even though the
@@ -4,6 +4,7 @@ export declare const apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
4
4
  readonly description?: string;
5
5
  readonly maxLength?: number;
6
6
  readonly minLength?: number;
7
+ readonly autoComplete?: string;
7
8
  readonly required?: boolean;
8
9
  readonly searchable?: boolean;
9
10
  readonly sortable?: boolean;
@@ -26,6 +27,7 @@ export declare const apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
26
27
  readonly description?: string;
27
28
  readonly maxLength?: number;
28
29
  readonly minLength?: number;
30
+ readonly autoComplete?: string;
29
31
  readonly required?: boolean;
30
32
  readonly searchable?: boolean;
31
33
  readonly sortable?: boolean;
@@ -48,6 +50,7 @@ export declare const apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
48
50
  readonly description?: string;
49
51
  readonly maxLength?: number;
50
52
  readonly minLength?: number;
53
+ readonly autoComplete?: string;
51
54
  readonly required?: boolean;
52
55
  readonly searchable?: boolean;
53
56
  readonly sortable?: boolean;
@@ -70,6 +73,7 @@ export declare const apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
70
73
  readonly description?: string;
71
74
  readonly maxLength?: number;
72
75
  readonly minLength?: number;
76
+ readonly autoComplete?: string;
73
77
  readonly required?: boolean;
74
78
  readonly searchable?: boolean;
75
79
  readonly sortable?: boolean;
@@ -92,6 +96,7 @@ export declare const apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
92
96
  readonly description?: string;
93
97
  readonly maxLength?: number;
94
98
  readonly minLength?: number;
99
+ readonly autoComplete?: string;
95
100
  readonly required?: boolean;
96
101
  readonly searchable?: boolean;
97
102
  readonly sortable?: boolean;
@@ -114,6 +119,7 @@ export declare const apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
114
119
  readonly description?: string;
115
120
  readonly maxLength?: number;
116
121
  readonly minLength?: number;
122
+ readonly autoComplete?: string;
117
123
  readonly required?: boolean;
118
124
  readonly searchable?: boolean;
119
125
  readonly sortable?: boolean;
@@ -41,11 +41,13 @@ export const patListScreen = {
41
41
  {
42
42
  field: "createdAt",
43
43
  label: i18nKey("pat.list.col.created"),
44
+ hideOnNarrow: true,
44
45
  renderer: { format: "timestamp" },
45
46
  },
46
47
  {
47
48
  field: "expiresAt",
48
49
  label: i18nKey("pat.list.col.expires"),
50
+ hideOnNarrow: true,
49
51
  renderer: { format: "timestamp" },
50
52
  },
51
53
  ],
@@ -9,6 +9,7 @@ export declare const tenantSecretEntity: import("@cosmicdrift/kumiko-types/field
9
9
  readonly description?: string;
10
10
  readonly maxLength?: number;
11
11
  readonly minLength?: number;
12
+ readonly autoComplete?: string;
12
13
  readonly required?: boolean;
13
14
  readonly searchable?: boolean;
14
15
  readonly sortable?: boolean;
@@ -4,6 +4,7 @@ export declare const userSessionEntity: import("@cosmicdrift/kumiko-types/fields
4
4
  readonly description?: string;
5
5
  readonly maxLength?: number;
6
6
  readonly minLength?: number;
7
+ readonly autoComplete?: string;
7
8
  readonly required?: boolean;
8
9
  readonly searchable?: boolean;
9
10
  readonly sortable?: boolean;
@@ -26,6 +27,7 @@ export declare const userSessionEntity: import("@cosmicdrift/kumiko-types/fields
26
27
  readonly description?: string;
27
28
  readonly maxLength?: number;
28
29
  readonly minLength?: number;
30
+ readonly autoComplete?: string;
29
31
  readonly required?: boolean;
30
32
  readonly searchable?: boolean;
31
33
  readonly sortable?: boolean;
@@ -108,6 +110,7 @@ export declare const userSessionEntity: import("@cosmicdrift/kumiko-types/fields
108
110
  readonly description?: string;
109
111
  readonly maxLength?: number;
110
112
  readonly minLength?: number;
113
+ readonly autoComplete?: string;
111
114
  readonly required?: boolean;
112
115
  readonly searchable?: boolean;
113
116
  readonly sortable?: boolean;
@@ -130,6 +133,7 @@ export declare const userSessionEntity: import("@cosmicdrift/kumiko-types/fields
130
133
  readonly description?: string;
131
134
  readonly maxLength?: number;
132
135
  readonly minLength?: number;
136
+ readonly autoComplete?: string;
133
137
  readonly required?: boolean;
134
138
  readonly searchable?: boolean;
135
139
  readonly sortable?: boolean;
@@ -33,6 +33,7 @@ export const createSessionListScreen = (adminRoles = access.admin) => ({
33
33
  {
34
34
  field: "revokedAt",
35
35
  label: i18nKey("sessions.list.col.revokedAt"),
36
+ hideOnNarrow: true,
36
37
  renderer: { format: "timestamp" },
37
38
  },
38
39
  ],
@@ -104,6 +105,7 @@ export const sessionMineScreen = {
104
105
  {
105
106
  field: "current",
106
107
  label: i18nKey("sessions.mine.col.current"),
108
+ hideOnNarrow: true,
107
109
  renderer: { format: "boolean" },
108
110
  },
109
111
  ],