@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.
- package/dist/auth-mfa/i18n.js +1 -0
- package/dist/auth-mfa/schema/user-mfa.d.ts +6 -0
- package/dist/auth-mfa/screens.js +8 -1
- package/dist/billing-foundation/entities.d.ts +8 -0
- package/dist/billing-foundation/projection.d.ts +8 -0
- package/dist/cap-counter/entity.d.ts +1 -0
- package/dist/cap-counter/feature.js +7 -1
- package/dist/cap-overview/screens.js +3 -1
- package/dist/config/table.d.ts +6 -0
- package/dist/crypto-shredding/handlers/forget-subject.write.js +15 -3
- package/dist/custom-fields/entity.d.ts +4 -0
- package/dist/data-retention/schema/tenant-retention-override.d.ts +4 -0
- package/dist/delivery/chat-webhook-sender.js +1 -1
- package/dist/delivery/constants.d.ts +1 -0
- package/dist/delivery/constants.js +4 -0
- package/dist/delivery/delivery-service.js +62 -25
- package/dist/delivery/feature.js +7 -1
- package/dist/delivery/jobs.js +25 -1
- package/dist/delivery/tables.d.ts +6 -0
- package/dist/delivery/testing.d.ts +1 -0
- package/dist/document-ingest-foundation/entity.d.ts +4 -0
- package/dist/folders/entity.d.ts +8 -0
- package/dist/form-draft/entity.d.ts +2 -0
- package/dist/inbound-mail-foundation/entities.d.ts +25 -0
- package/dist/inbound-mail-foundation/projection.d.ts +20 -0
- package/dist/jobs/job-run-table.d.ts +6 -0
- package/dist/ledger/entity.d.ts +13 -0
- package/dist/managed-pages/table.d.ts +12 -0
- package/dist/notes-history/entity.d.ts +12 -0
- package/dist/notes-history/entity.js +7 -6
- package/dist/notes-history-user-data/hooks.d.ts +10 -1
- package/dist/notes-history-user-data/hooks.js +78 -61
- package/dist/notes-history-user-data/index.d.ts +1 -0
- package/dist/notes-history-user-data/index.js +2 -1
- package/dist/personal-access-tokens/schema/api-token.d.ts +6 -0
- package/dist/personal-access-tokens/screens.js +2 -0
- package/dist/secrets/table.d.ts +1 -0
- package/dist/sessions/schema/user-session.d.ts +4 -0
- package/dist/sessions/screens.js +2 -0
- package/dist/tags/entity.d.ts +9 -0
- package/dist/template-resolver/table.d.ts +10 -0
- package/dist/template-resolver/user-content-table.d.ts +10 -0
- package/dist/tenant/constants.d.ts +1 -0
- package/dist/tenant/constants.js +6 -3
- package/dist/tenant/feature.js +2 -0
- package/dist/tenant/handlers/member-role-availability.query.d.ts +7 -0
- package/dist/tenant/handlers/member-role-availability.query.js +18 -0
- package/dist/tenant/invitation-table.d.ts +6 -0
- package/dist/tenant/member-role-options.d.ts +2 -0
- package/dist/tenant/member-role-options.js +4 -0
- package/dist/tenant/membership-table.d.ts +4 -0
- package/dist/tenant/schema/tenant.d.ts +6 -0
- package/dist/tenant/screens.d.ts +4 -2
- package/dist/tenant/screens.js +10 -6
- package/dist/tier-engine/entity.d.ts +2 -0
- package/dist/tier-engine/tier-resolver.d.ts +2 -0
- package/dist/user/schema/user.d.ts +12 -0
- package/dist/user/screens.js +8 -1
- package/dist/user-data-rights/schema/download-attempt.d.ts +14 -0
- package/dist/user-data-rights/schema/download-token.d.ts +8 -0
- package/dist/user-data-rights/schema/export-job.d.ts +6 -0
- package/dist/user-data-rights/screens.js +8 -1
- package/dist/waitlist/entity.d.ts +14 -0
- package/dist/waitlist/screens.js +8 -1
- package/package.json +9 -9
- package/src/auth-mfa/changes.json +6 -0
- package/src/crypto-shredding/changes.json +6 -0
- package/src/delivery/changes.json +6 -0
- 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
|
|
25
|
-
//
|
|
26
|
-
//
|
|
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.
|
|
33
|
-
//
|
|
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
|
|
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
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
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
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
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
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
|
|
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 —
|
|
69
|
+
// skip: no KMS adapter mounted — forget is a true no-op
|
|
52
70
|
if (!kms)
|
|
53
71
|
return;
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
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:
|
|
78
|
-
registry:
|
|
79
|
-
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
|
-
|
|
99
|
+
strategyByTenantAndEntityType.set(strategyKey, strategy);
|
|
85
100
|
}
|
|
86
101
|
if (strategy === "anonymize")
|
|
87
102
|
continue;
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
|
97
|
-
//
|
|
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
|
|
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
|
|
112
|
-
entityType: note
|
|
113
|
-
entityId: note
|
|
114
|
-
body: note
|
|
115
|
-
insertedAt: note
|
|
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
|
};
|
|
@@ -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
|
|
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
|
],
|
package/dist/secrets/table.d.ts
CHANGED
|
@@ -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;
|
package/dist/sessions/screens.js
CHANGED
|
@@ -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
|
],
|
package/dist/tags/entity.d.ts
CHANGED
|
@@ -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;
|