@cosmicdrift/kumiko-bundled-features 0.356.0 → 0.357.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 (69) 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/entity.d.ts +1 -0
  7. package/dist/cap-counter/feature.js +7 -1
  8. package/dist/cap-overview/screens.js +3 -1
  9. package/dist/config/table.d.ts +6 -0
  10. package/dist/crypto-shredding/handlers/forget-subject.write.js +15 -3
  11. package/dist/custom-fields/entity.d.ts +4 -0
  12. package/dist/data-retention/schema/tenant-retention-override.d.ts +4 -0
  13. package/dist/delivery/chat-webhook-sender.js +1 -1
  14. package/dist/delivery/constants.d.ts +1 -0
  15. package/dist/delivery/constants.js +4 -0
  16. package/dist/delivery/delivery-service.js +62 -25
  17. package/dist/delivery/feature.js +7 -1
  18. package/dist/delivery/jobs.js +25 -1
  19. package/dist/delivery/tables.d.ts +6 -0
  20. package/dist/delivery/testing.d.ts +1 -0
  21. package/dist/document-ingest-foundation/entity.d.ts +4 -0
  22. package/dist/folders/entity.d.ts +8 -0
  23. package/dist/form-draft/entity.d.ts +2 -0
  24. package/dist/inbound-mail-foundation/entities.d.ts +25 -0
  25. package/dist/inbound-mail-foundation/projection.d.ts +20 -0
  26. package/dist/jobs/job-run-table.d.ts +6 -0
  27. package/dist/ledger/entity.d.ts +13 -0
  28. package/dist/managed-pages/table.d.ts +12 -0
  29. package/dist/notes-history/entity.d.ts +12 -0
  30. package/dist/notes-history/entity.js +7 -6
  31. package/dist/notes-history-user-data/hooks.d.ts +10 -1
  32. package/dist/notes-history-user-data/hooks.js +78 -61
  33. package/dist/notes-history-user-data/index.d.ts +1 -0
  34. package/dist/notes-history-user-data/index.js +2 -1
  35. package/dist/personal-access-tokens/schema/api-token.d.ts +6 -0
  36. package/dist/personal-access-tokens/screens.js +2 -0
  37. package/dist/secrets/table.d.ts +1 -0
  38. package/dist/sessions/schema/user-session.d.ts +4 -0
  39. package/dist/sessions/screens.js +2 -0
  40. package/dist/tags/entity.d.ts +9 -0
  41. package/dist/template-resolver/table.d.ts +10 -0
  42. package/dist/template-resolver/user-content-table.d.ts +10 -0
  43. package/dist/tenant/constants.d.ts +1 -0
  44. package/dist/tenant/constants.js +6 -3
  45. package/dist/tenant/feature.js +2 -0
  46. package/dist/tenant/handlers/member-role-availability.query.d.ts +7 -0
  47. package/dist/tenant/handlers/member-role-availability.query.js +18 -0
  48. package/dist/tenant/invitation-table.d.ts +6 -0
  49. package/dist/tenant/member-role-options.d.ts +2 -0
  50. package/dist/tenant/member-role-options.js +4 -0
  51. package/dist/tenant/membership-table.d.ts +4 -0
  52. package/dist/tenant/schema/tenant.d.ts +6 -0
  53. package/dist/tenant/screens.d.ts +4 -2
  54. package/dist/tenant/screens.js +10 -6
  55. package/dist/tier-engine/entity.d.ts +2 -0
  56. package/dist/tier-engine/tier-resolver.d.ts +2 -0
  57. package/dist/user/schema/user.d.ts +12 -0
  58. package/dist/user/screens.js +8 -1
  59. package/dist/user-data-rights/schema/download-attempt.d.ts +14 -0
  60. package/dist/user-data-rights/schema/download-token.d.ts +8 -0
  61. package/dist/user-data-rights/schema/export-job.d.ts +6 -0
  62. package/dist/user-data-rights/screens.js +8 -1
  63. package/dist/waitlist/entity.d.ts +14 -0
  64. package/dist/waitlist/screens.js +8 -1
  65. package/package.json +9 -9
  66. package/src/auth-mfa/changes.json +6 -0
  67. package/src/crypto-shredding/changes.json +6 -0
  68. package/src/delivery/changes.json +6 -0
  69. package/src/tenant/changes.json +6 -0
@@ -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",
@@ -1,5 +1,14 @@
1
- import type { UserDataDeleteHook, UserDataExportHook } from "@cosmicdrift/kumiko-framework/engine";
1
+ import { type SubjectId } from "@cosmicdrift/kumiko-framework/crypto";
2
+ import type { DbRunner, TenantDb } from "@cosmicdrift/kumiko-framework/db";
3
+ import type { Registry, 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 registry: Registry;
8
+ readonly subject: SubjectId;
9
+ readonly erasedBy: string;
10
+ readonly requestId: string;
11
+ }): Promise<void>;
3
12
  export declare const noteEntryDeleteHook: UserDataDeleteHook;
4
13
  export declare const noteMentionExportHook: UserDataExportHook;
5
14
  export declare const noteMentionDeleteHook: UserDataDeleteHook;
@@ -3,6 +3,7 @@
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
8
  import { resolveRetentionPolicyForTenant, resolveTenantRetentionPreset, } from "../data-retention/index.js";
8
9
  import { noteEntryTable, noteMentionTable } from "../notes-history/index.js";
@@ -26,93 +27,109 @@ export const noteEntryExportHook = async (ctx) => {
26
27
  })),
27
28
  };
28
29
  };
30
+ // What erasing a subject's key reaches: notes that @-mention the subject plus notes
31
+ // attached to it as host. Export and erasure both go through this, so Art. 15
32
+ // lists exactly what Art. 17 shreds.
33
+ async function selectNotesReachingSubject(db, subject) {
34
+ if (subject.kind === "tenant")
35
+ return [];
36
+ const subjectId = subject.kind === "user" ? subject.userId : subject.id;
37
+ const attachedWhere = subject.kind === "record"
38
+ ? { entityType: subject.entity, entityId: subjectId }
39
+ : { entityId: subjectId };
40
+ const mentions = await selectMany(db, noteMentionTable, { subjectId });
41
+ const mentioned = mentions.length === 0
42
+ ? []
43
+ : await selectMany(db, noteEntryTable, {
44
+ id: { in: [...new Set(mentions.map((m) => m.noteId))] },
45
+ });
46
+ const attached = await selectMany(db, noteEntryTable, attachedWhere);
47
+ const byId = new Map();
48
+ for (const note of [...mentioned, ...attached])
49
+ byId.set(note.id, note);
50
+ return [...byId.values()];
51
+ }
29
52
  // `authorName` is separately annotated `personal: { of: "authorId" }`
30
53
  // (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.
54
+ // its own. `body` is Row-Subject (`personal: { of: "id" }`): its key is per
55
+ // note, so forgetting a subject only reaches a body when the note structurally
56
+ // @-mentions the subject (`note-mention`, fw#2787) or hangs on it as host.
57
+ // Free-text mentions are NOT detected — the documented gap in entity.ts.
36
58
  //
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.
59
+ // Per reached note the HOST entity's retention strategy (entityType, resolved
60
+ // for the note's own tenant — the manual forget-subject path runs cross-tenant)
61
+ // is consulted first: anonymize/blockDelete win over erasure, same rule the
62
+ // automated forget-cleanup applies to every other entity.
42
63
  //
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) => {
64
+ // Without a mounted KMS this erases nothing: record-owned fields fall back to
65
+ // plaintext framework-wide (see pii-field-encryption.ts), so apps that need
66
+ // Art. 17 coverage must mount one.
67
+ export async function eraseNoteBodiesReachingSubject(args) {
50
68
  const kms = configuredPiiSubjectKms();
51
- // skip: no KMS adapter mounted — record-owned fields stay plaintext framework-wide, forget is a true no-op
69
+ // skip: no KMS adapter mounted — forget is a true no-op
52
70
  if (!kms)
53
71
  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();
72
+ const notes = await selectNotesReachingSubject(args.db, args.subject);
73
+ const dataRetentionMounted = args.registry.features.has("data-retention");
74
+ const presetByTenant = new Map();
75
+ const strategyByTenantAndEntityType = new Map();
73
76
  for (const note of notes) {
74
- let strategy = strategyByEntityType.get(note.entityType);
77
+ const strategyKey = `${note.tenantId}:${note.entityType}`;
78
+ let strategy = strategyByTenantAndEntityType.get(strategyKey);
75
79
  if (strategy === undefined) {
80
+ let tenantPreset = presetByTenant.get(note.tenantId);
81
+ if (tenantPreset === undefined) {
82
+ tenantPreset = await resolveTenantRetentionPreset({
83
+ db: args.db,
84
+ registry: args.registry,
85
+ tenantId: note.tenantId,
86
+ });
87
+ presetByTenant.set(note.tenantId, tenantPreset);
88
+ }
76
89
  const hostPolicy = await resolveRetentionPolicyForTenant({
77
- db: ctx.db,
78
- registry: ctx.registry,
79
- tenantId: ctx.tenantId,
90
+ db: args.db,
91
+ registry: args.registry,
92
+ tenantId: note.tenantId,
80
93
  entityName: note.entityType,
81
94
  preloadedTenantPreset: tenantPreset,
95
+ // Without data-retention there is no override table to read.
96
+ ...(!dataRetentionMounted && { preloadedOverride: null }),
82
97
  });
83
98
  strategy = policyToStrategy(hostPolicy.policy?.strategy ?? null);
84
- strategyByEntityType.set(note.entityType, strategy);
99
+ strategyByTenantAndEntityType.set(strategyKey, strategy);
85
100
  }
86
101
  if (strategy === "anonymize")
87
102
  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",
103
+ await kms.eraseKey({ kind: "record", entity: "note-entry", id: note.id }, {
104
+ requestId: args.requestId,
105
+ userId: args.erasedBy,
106
+ eraseReason: "notes-history:subject-forget",
93
107
  });
94
108
  }
109
+ }
110
+ export const noteEntryDeleteHook = async (ctx) => {
111
+ await eraseNoteBodiesReachingSubject({
112
+ db: ctx.db,
113
+ registry: ctx.registry,
114
+ subject: { kind: "user", userId: ctx.userId },
115
+ erasedBy: ctx.userId,
116
+ requestId: "notes-history-user-data:forget-reached-notes",
117
+ });
95
118
  };
96
- // Erasure reaches every note that structurally mentions the user (see
97
- // noteEntryDeleteHook), so the Art. 15/20 export must list those notes too.
119
+ // Erasure reaches every note that mentions the user or hangs on the user as host
120
+ // (see eraseNoteBodiesReachingSubject), so the Art. 15/20 export lists those too.
98
121
  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 } });
122
+ const notes = await selectNotesReachingSubject(ctx.db, { kind: "user", userId: ctx.userId });
106
123
  if (notes.length === 0)
107
124
  return null;
108
125
  return {
109
126
  entity: "note-mention",
110
127
  rows: notes.map((note) => ({
111
- noteId: note["id"],
112
- entityType: note["entityType"],
113
- entityId: note["entityId"],
114
- body: note["body"],
115
- insertedAt: note["insertedAt"],
128
+ noteId: note.id,
129
+ entityType: note.entityType,
130
+ entityId: note.entityId,
131
+ body: note.body,
132
+ insertedAt: note.insertedAt,
116
133
  })),
117
134
  };
118
135
  };
@@ -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 (after consulting that note's host entity's retention strategy — blockDelete/anonymize win over erasure), 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
  ],
@@ -5,6 +5,7 @@ export declare const tagEntity: EntityDefinition<{
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 const tagEntity: EntityDefinition<{
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 const tagEntity: EntityDefinition<{
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;
@@ -73,6 +76,7 @@ export declare function createTagAssignmentEntity(access?: EntityDefinition["acc
73
76
  readonly description?: string;
74
77
  readonly maxLength?: number;
75
78
  readonly minLength?: number;
79
+ readonly autoComplete?: string;
76
80
  readonly required?: boolean;
77
81
  readonly searchable?: boolean;
78
82
  readonly sortable?: boolean;
@@ -95,6 +99,7 @@ export declare function createTagAssignmentEntity(access?: EntityDefinition["acc
95
99
  readonly description?: string;
96
100
  readonly maxLength?: number;
97
101
  readonly minLength?: number;
102
+ readonly autoComplete?: string;
98
103
  readonly required?: boolean;
99
104
  readonly searchable?: boolean;
100
105
  readonly sortable?: boolean;
@@ -117,6 +122,7 @@ export declare function createTagAssignmentEntity(access?: EntityDefinition["acc
117
122
  readonly description?: string;
118
123
  readonly maxLength?: number;
119
124
  readonly minLength?: number;
125
+ readonly autoComplete?: string;
120
126
  readonly required?: boolean;
121
127
  readonly searchable?: boolean;
122
128
  readonly sortable?: boolean;
@@ -141,6 +147,7 @@ export declare const tagAssignmentEntity: EntityDefinition<{
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 tagAssignmentEntity: EntityDefinition<{
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 tagAssignmentEntity: EntityDefinition<{
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;