@cosmicdrift/kumiko-bundled-features 0.371.1 → 0.372.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 (92) hide show
  1. package/dist/auth-mfa/db/queries.d.ts +3 -2
  2. package/dist/auth-mfa/db/queries.js +2 -5
  3. package/dist/auth-mfa/schema/user-mfa.d.ts +10 -10
  4. package/dist/billing-foundation/entities.d.ts +10 -10
  5. package/dist/billing-foundation/projection.d.ts +10 -10
  6. package/dist/cap-counter/entity.d.ts +4 -4
  7. package/dist/cap-overview/web/cap-cards-panel.js +2 -1
  8. package/dist/compliance-profiles/schema/profile-selection.d.ts +4 -4
  9. package/dist/config/table.d.ts +6 -6
  10. package/dist/config/web/index.d.ts +1 -0
  11. package/dist/config/web/index.js +1 -0
  12. package/dist/crypto-shredding/handlers/forget-subject.write.js +55 -30
  13. package/dist/crypto-shredding/resolve-file-bytes-deleter.d.ts +3 -0
  14. package/dist/crypto-shredding/resolve-file-bytes-deleter.js +24 -0
  15. package/dist/custom-fields/entity.d.ts +9 -7
  16. package/dist/custom-fields/executor.d.ts +19 -1
  17. package/dist/data-retention/index.d.ts +1 -0
  18. package/dist/data-retention/index.js +1 -0
  19. package/dist/data-retention/run-retention-cleanup.js +27 -159
  20. package/dist/data-retention/schema/tenant-retention-override.d.ts +6 -6
  21. package/dist/delivery/delivery-service.d.ts +2 -1
  22. package/dist/delivery/delivery-service.js +3 -2
  23. package/dist/delivery/handlers/unsubscribe-page-chrome.query.js +2 -2
  24. package/dist/delivery/index.d.ts +1 -1
  25. package/dist/delivery/index.js +1 -1
  26. package/dist/delivery/jobs.js +21 -7
  27. package/dist/delivery/recipient-address-seal.d.ts +13 -0
  28. package/dist/delivery/recipient-address-seal.js +53 -0
  29. package/dist/delivery/tables.d.ts +8 -7
  30. package/dist/delivery/testing.d.ts +2 -1
  31. package/dist/delivery/testing.js +1 -0
  32. package/dist/delivery/unsubscribe.d.ts +13 -2
  33. package/dist/delivery/unsubscribe.js +9 -1
  34. package/dist/document-ingest-foundation/entity.d.ts +8 -8
  35. package/dist/document-ingest-foundation/executor.d.ts +16 -1
  36. package/dist/file-foundation/read-file-ref-content.js +2 -2
  37. package/dist/folders/entity.d.ts +8 -8
  38. package/dist/folders/executor.d.ts +29 -2
  39. package/dist/form-draft/entity.d.ts +3 -3
  40. package/dist/form-draft/executor.d.ts +15 -1
  41. package/dist/inbound-mail-foundation/entities.d.ts +31 -31
  42. package/dist/inbound-mail-foundation/projection.d.ts +24 -24
  43. package/dist/jobs/job-run-table.d.ts +10 -10
  44. package/dist/ledger/entity.d.ts +21 -21
  45. package/dist/ledger/executor.d.ts +57 -3
  46. package/dist/managed-pages/table.d.ts +16 -14
  47. package/dist/notes-history/entity.d.ts +14 -14
  48. package/dist/notes-history/executor.d.ts +31 -2
  49. package/dist/personal-access-tokens/schema/api-token.d.ts +9 -9
  50. package/dist/personal-access-tokens/screens.js +14 -9
  51. package/dist/secrets/table.d.ts +2 -2
  52. package/dist/sessions/schema/user-session.d.ts +8 -8
  53. package/dist/shared/index.d.ts +2 -0
  54. package/dist/shared/index.js +2 -0
  55. package/dist/shared/purge-files-reaching-subject.d.ts +25 -0
  56. package/dist/shared/purge-files-reaching-subject.js +146 -0
  57. package/dist/shared/purge-row-file-refs.d.ts +33 -0
  58. package/dist/shared/purge-row-file-refs.js +152 -0
  59. package/dist/tags/entity.d.ts +9 -9
  60. package/dist/tags/executor.d.ts +30 -2
  61. package/dist/template-resolver/handlers/shared.d.ts +28 -1
  62. package/dist/template-resolver/table.d.ts +32 -32
  63. package/dist/template-resolver/template-preview.js +13 -4
  64. package/dist/template-resolver/user-content-table.d.ts +16 -16
  65. package/dist/template-resolver/web/client-plugin.js +2 -1
  66. package/dist/template-resolver/web/document-editor-extensions.d.ts +1 -1
  67. package/dist/template-resolver/web/document-editor-extensions.js +2 -5
  68. package/dist/template-resolver/web/placeholder-line-editor.js +2 -2
  69. package/dist/tenant/handlers/list.query.d.ts +21 -1
  70. package/dist/tenant/invitation-table.d.ts +14 -14
  71. package/dist/tenant/membership-table.d.ts +4 -4
  72. package/dist/tenant/schema/tenant.d.ts +20 -18
  73. package/dist/tier-engine/entity.d.ts +2 -2
  74. package/dist/tier-engine/tier-resolver.d.ts +2 -2
  75. package/dist/user/handlers/me.query.d.ts +21 -0
  76. package/dist/user/schema/user.d.ts +24 -22
  77. package/dist/user-data-rights/run-export-jobs.d.ts +20 -1
  78. package/dist/user-data-rights/run-forget-cleanup.js +35 -1
  79. package/dist/user-data-rights/schema/download-attempt.d.ts +16 -16
  80. package/dist/user-data-rights/schema/download-token.d.ts +16 -16
  81. package/dist/user-data-rights/schema/export-job.d.ts +20 -20
  82. package/dist/user-data-rights/web/privacy-center-screen.js +2 -1
  83. package/dist/waitlist/entity.d.ts +24 -24
  84. package/dist/waitlist/lib.d.ts +24 -1
  85. package/package.json +11 -11
  86. package/src/crypto-shredding/changes.json +6 -0
  87. package/src/data-retention/changes.json +6 -0
  88. package/src/delivery/changes.json +13 -0
  89. package/src/files/changes.json +7 -0
  90. package/src/personal-access-tokens/changes.json +12 -0
  91. package/src/template-resolver/changes.json +6 -0
  92. package/src/user-data-rights/changes.json +6 -0
@@ -1,5 +1,6 @@
1
1
  import { type TenantDb } from "@cosmicdrift/kumiko-framework/db";
2
2
  import type { SessionUser } from "@cosmicdrift/kumiko-framework/engine";
3
+ import type { Temporal } from "@cosmicdrift/kumiko-types/temporal";
3
4
  export type UserMfaRow = {
4
5
  readonly id: string;
5
6
  readonly version: number;
@@ -8,7 +9,7 @@ export type UserMfaRow = {
8
9
  readonly recoveryCodes: {
9
10
  readonly hashes: readonly string[];
10
11
  };
11
- readonly enabledAt: string;
12
- readonly lastUsedAt: string | null;
12
+ readonly enabledAt: Temporal.Instant;
13
+ readonly lastUsedAt: Temporal.Instant | null;
13
14
  };
14
15
  export declare function findUserMfaRow(db: TenantDb, user: SessionUser): Promise<UserMfaRow | null>;
@@ -22,9 +22,6 @@ export async function findUserMfaRow(db, user) {
22
22
  const detail = await executor.detail({ id: idLookup.id }, user, db);
23
23
  if (!detail)
24
24
  return null;
25
- // @cast-boundary engine-payload — executor.detail returns a decrypted,
26
- // untyped Record; the userMfa entity's own field shape narrows it.
27
- const row = detail;
28
- const recoveryCodes = parseJsonOrThrow(row.recoveryCodes, "user-mfa.recoveryCodes");
29
- return { ...row, recoveryCodes };
25
+ const recoveryCodes = parseJsonOrThrow(detail.recoveryCodes, "user-mfa.recoveryCodes");
26
+ return { ...detail, recoveryCodes };
30
27
  }
@@ -1,5 +1,5 @@
1
1
  export declare const userMfaEntity: import("@cosmicdrift/kumiko-types/fields").EntityDefinition<{
2
- userId: {
2
+ readonly userId: {
3
3
  readonly type: "text";
4
4
  readonly description?: string;
5
5
  readonly maxLength?: number;
@@ -22,7 +22,7 @@ export declare const userMfaEntity: import("@cosmicdrift/kumiko-types/fields").E
22
22
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
23
23
  required: true;
24
24
  };
25
- totpSecret: {
25
+ readonly totpSecret: {
26
26
  readonly type: "text";
27
27
  readonly description?: string;
28
28
  readonly maxLength?: number;
@@ -45,7 +45,7 @@ export declare const userMfaEntity: import("@cosmicdrift/kumiko-types/fields").E
45
45
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
46
46
  required: true;
47
47
  };
48
- recoveryCodes: {
48
+ readonly recoveryCodes: {
49
49
  readonly type: "text";
50
50
  readonly description?: string;
51
51
  readonly maxLength?: number;
@@ -68,7 +68,7 @@ export declare const userMfaEntity: import("@cosmicdrift/kumiko-types/fields").E
68
68
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
69
69
  required: true;
70
70
  };
71
- enabledAt: {
71
+ readonly enabledAt: {
72
72
  readonly type: "timestamp";
73
73
  readonly description?: string;
74
74
  readonly required?: boolean;
@@ -83,7 +83,7 @@ export declare const userMfaEntity: import("@cosmicdrift/kumiko-types/fields").E
83
83
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
84
84
  required: true;
85
85
  };
86
- lastUsedAt: {
86
+ readonly lastUsedAt: {
87
87
  readonly type: "timestamp";
88
88
  readonly description?: string;
89
89
  readonly required?: boolean;
@@ -100,7 +100,7 @@ export declare const userMfaEntity: import("@cosmicdrift/kumiko-types/fields").E
100
100
  };
101
101
  }, "tenant">;
102
102
  export declare const userMfaTable: import("@cosmicdrift/kumiko-framework/db").EntityTable<import("@cosmicdrift/kumiko-types/fields").EntityDefinition<{
103
- userId: {
103
+ readonly userId: {
104
104
  readonly type: "text";
105
105
  readonly description?: string;
106
106
  readonly maxLength?: number;
@@ -123,7 +123,7 @@ export declare const userMfaTable: import("@cosmicdrift/kumiko-framework/db").En
123
123
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
124
124
  required: true;
125
125
  };
126
- totpSecret: {
126
+ readonly totpSecret: {
127
127
  readonly type: "text";
128
128
  readonly description?: string;
129
129
  readonly maxLength?: number;
@@ -146,7 +146,7 @@ export declare const userMfaTable: import("@cosmicdrift/kumiko-framework/db").En
146
146
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
147
147
  required: true;
148
148
  };
149
- recoveryCodes: {
149
+ readonly recoveryCodes: {
150
150
  readonly type: "text";
151
151
  readonly description?: string;
152
152
  readonly maxLength?: number;
@@ -169,7 +169,7 @@ export declare const userMfaTable: import("@cosmicdrift/kumiko-framework/db").En
169
169
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
170
170
  required: true;
171
171
  };
172
- enabledAt: {
172
+ readonly enabledAt: {
173
173
  readonly type: "timestamp";
174
174
  readonly description?: string;
175
175
  readonly required?: boolean;
@@ -184,7 +184,7 @@ export declare const userMfaTable: import("@cosmicdrift/kumiko-framework/db").En
184
184
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
185
185
  required: true;
186
186
  };
187
- lastUsedAt: {
187
+ readonly lastUsedAt: {
188
188
  readonly type: "timestamp";
189
189
  readonly description?: string;
190
190
  readonly required?: boolean;
@@ -1,5 +1,5 @@
1
1
  export declare const subscriptionEntity: import("@cosmicdrift/kumiko-types/fields").EntityDefinition<{
2
- providerName: {
2
+ readonly providerName: {
3
3
  readonly type: "text";
4
4
  readonly description?: string;
5
5
  readonly maxLength?: number;
@@ -22,7 +22,7 @@ export declare const subscriptionEntity: import("@cosmicdrift/kumiko-types/field
22
22
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
23
23
  required: true;
24
24
  };
25
- providerCustomerId: {
25
+ readonly providerCustomerId: {
26
26
  readonly type: "text";
27
27
  readonly description?: string;
28
28
  readonly maxLength?: number;
@@ -45,7 +45,7 @@ export declare const subscriptionEntity: import("@cosmicdrift/kumiko-types/field
45
45
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
46
46
  required: true;
47
47
  };
48
- providerSubscriptionId: {
48
+ readonly providerSubscriptionId: {
49
49
  readonly type: "text";
50
50
  readonly description?: string;
51
51
  readonly maxLength?: number;
@@ -68,7 +68,7 @@ export declare const subscriptionEntity: import("@cosmicdrift/kumiko-types/field
68
68
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
69
69
  required: true;
70
70
  };
71
- status: {
71
+ readonly status: {
72
72
  readonly type: "text";
73
73
  readonly description?: string;
74
74
  readonly maxLength?: number;
@@ -91,7 +91,7 @@ export declare const subscriptionEntity: import("@cosmicdrift/kumiko-types/field
91
91
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
92
92
  required: true;
93
93
  };
94
- tier: {
94
+ readonly tier: {
95
95
  readonly type: "text";
96
96
  readonly description?: string;
97
97
  readonly maxLength?: number;
@@ -114,7 +114,7 @@ export declare const subscriptionEntity: import("@cosmicdrift/kumiko-types/field
114
114
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
115
115
  required: true;
116
116
  };
117
- currentPeriodEnd: {
117
+ readonly currentPeriodEnd: {
118
118
  readonly type: "timestamp";
119
119
  readonly description?: string;
120
120
  readonly required?: boolean;
@@ -129,7 +129,7 @@ export declare const subscriptionEntity: import("@cosmicdrift/kumiko-types/field
129
129
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
130
130
  required: true;
131
131
  };
132
- cancelAt: {
132
+ readonly cancelAt: {
133
133
  readonly type: "timestamp";
134
134
  readonly description?: string;
135
135
  readonly required?: boolean;
@@ -147,7 +147,7 @@ export declare const subscriptionEntity: import("@cosmicdrift/kumiko-types/field
147
147
  }, "tenant">;
148
148
  export declare const SUBSCRIPTION_PII_FIELDS: readonly string[];
149
149
  export declare const paymentEntity: import("@cosmicdrift/kumiko-types/fields").EntityDefinition<{
150
- providerName: {
150
+ readonly providerName: {
151
151
  readonly type: "text";
152
152
  readonly description?: string;
153
153
  readonly maxLength?: number;
@@ -170,7 +170,7 @@ export declare const paymentEntity: import("@cosmicdrift/kumiko-types/fields").E
170
170
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
171
171
  required: true;
172
172
  };
173
- providerCustomerId: {
173
+ readonly providerCustomerId: {
174
174
  readonly type: "text";
175
175
  readonly description?: string;
176
176
  readonly maxLength?: number;
@@ -193,7 +193,7 @@ export declare const paymentEntity: import("@cosmicdrift/kumiko-types/fields").E
193
193
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
194
194
  required: true;
195
195
  };
196
- priceId: {
196
+ readonly priceId: {
197
197
  readonly type: "text";
198
198
  readonly description?: string;
199
199
  readonly maxLength?: number;
@@ -1,5 +1,5 @@
1
1
  export declare const subscriptionsProjectionTable: import("@cosmicdrift/kumiko-framework/db").EntityTable<import("@cosmicdrift/kumiko-types/fields").EntityDefinition<{
2
- providerName: {
2
+ readonly providerName: {
3
3
  readonly type: "text";
4
4
  readonly description?: string;
5
5
  readonly maxLength?: number;
@@ -22,7 +22,7 @@ export declare const subscriptionsProjectionTable: import("@cosmicdrift/kumiko-f
22
22
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
23
23
  required: true;
24
24
  };
25
- providerCustomerId: {
25
+ readonly providerCustomerId: {
26
26
  readonly type: "text";
27
27
  readonly description?: string;
28
28
  readonly maxLength?: number;
@@ -45,7 +45,7 @@ export declare const subscriptionsProjectionTable: import("@cosmicdrift/kumiko-f
45
45
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
46
46
  required: true;
47
47
  };
48
- providerSubscriptionId: {
48
+ readonly providerSubscriptionId: {
49
49
  readonly type: "text";
50
50
  readonly description?: string;
51
51
  readonly maxLength?: number;
@@ -68,7 +68,7 @@ export declare const subscriptionsProjectionTable: import("@cosmicdrift/kumiko-f
68
68
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
69
69
  required: true;
70
70
  };
71
- status: {
71
+ readonly status: {
72
72
  readonly type: "text";
73
73
  readonly description?: string;
74
74
  readonly maxLength?: number;
@@ -91,7 +91,7 @@ export declare const subscriptionsProjectionTable: import("@cosmicdrift/kumiko-f
91
91
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
92
92
  required: true;
93
93
  };
94
- tier: {
94
+ readonly tier: {
95
95
  readonly type: "text";
96
96
  readonly description?: string;
97
97
  readonly maxLength?: number;
@@ -114,7 +114,7 @@ export declare const subscriptionsProjectionTable: import("@cosmicdrift/kumiko-f
114
114
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
115
115
  required: true;
116
116
  };
117
- currentPeriodEnd: {
117
+ readonly currentPeriodEnd: {
118
118
  readonly type: "timestamp";
119
119
  readonly description?: string;
120
120
  readonly required?: boolean;
@@ -129,7 +129,7 @@ export declare const subscriptionsProjectionTable: import("@cosmicdrift/kumiko-f
129
129
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
130
130
  required: true;
131
131
  };
132
- cancelAt: {
132
+ readonly cancelAt: {
133
133
  readonly type: "timestamp";
134
134
  readonly description?: string;
135
135
  readonly required?: boolean;
@@ -146,7 +146,7 @@ export declare const subscriptionsProjectionTable: import("@cosmicdrift/kumiko-f
146
146
  };
147
147
  }, "tenant">>;
148
148
  export declare const paymentsProjectionTable: import("@cosmicdrift/kumiko-framework/db").EntityTable<import("@cosmicdrift/kumiko-types/fields").EntityDefinition<{
149
- providerName: {
149
+ readonly providerName: {
150
150
  readonly type: "text";
151
151
  readonly description?: string;
152
152
  readonly maxLength?: number;
@@ -169,7 +169,7 @@ export declare const paymentsProjectionTable: import("@cosmicdrift/kumiko-framew
169
169
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
170
170
  required: true;
171
171
  };
172
- providerCustomerId: {
172
+ readonly providerCustomerId: {
173
173
  readonly type: "text";
174
174
  readonly description?: string;
175
175
  readonly maxLength?: number;
@@ -192,7 +192,7 @@ export declare const paymentsProjectionTable: import("@cosmicdrift/kumiko-framew
192
192
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
193
193
  required: true;
194
194
  };
195
- priceId: {
195
+ readonly priceId: {
196
196
  readonly type: "text";
197
197
  readonly description?: string;
198
198
  readonly maxLength?: number;
@@ -1,5 +1,5 @@
1
1
  export declare const capCounterEntity: import("@cosmicdrift/kumiko-types/fields").EntityDefinition<{
2
- capName: {
2
+ readonly capName: {
3
3
  readonly type: "text";
4
4
  readonly description?: string;
5
5
  readonly maxLength?: number;
@@ -22,7 +22,7 @@ export declare const capCounterEntity: import("@cosmicdrift/kumiko-types/fields"
22
22
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
23
23
  required: true;
24
24
  };
25
- value: {
25
+ readonly value: {
26
26
  readonly type: "bigInt";
27
27
  readonly description?: string;
28
28
  readonly required?: boolean;
@@ -34,7 +34,7 @@ export declare const capCounterEntity: import("@cosmicdrift/kumiko-types/fields"
34
34
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
35
35
  required: true;
36
36
  };
37
- periodStart: {
37
+ readonly periodStart: {
38
38
  readonly type: "timestamp";
39
39
  readonly description?: string;
40
40
  readonly required?: boolean;
@@ -49,7 +49,7 @@ export declare const capCounterEntity: import("@cosmicdrift/kumiko-types/fields"
49
49
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
50
50
  required: true;
51
51
  };
52
- lastSoftWarnedAt: {
52
+ readonly lastSoftWarnedAt: {
53
53
  readonly type: "timestamp";
54
54
  readonly description?: string;
55
55
  readonly required?: boolean;
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { EMPTY_VALUE_PLACEHOLDER } from "@cosmicdrift/kumiko-headless";
2
3
  import { usePrimitives, useQuery, useTranslation } from "@cosmicdrift/kumiko-renderer";
3
4
  import { EmptyState, StatCard, StatusBadge, } from "@cosmicdrift/kumiko-renderer-web";
4
5
  import { Database, FileText, Gauge, Hash, Mail, Users } from "lucide-react";
@@ -56,7 +57,7 @@ export function CapCardsPanel({ filterParams }) {
56
57
  // wraps any defined child in a spacing div, so pass undefined then.
57
58
  const body = !isMeasured(cap) || cap.limit !== null ? (_jsxs("div", { className: "flex flex-col gap-2", children: [cap.percent !== null && (_jsx("div", { className: "flex items-center justify-between gap-2", children: _jsx(StatusBadge, { tone: TONE_TO_STATUS[cap.tone], children: `${cap.percent}%` }) })), _jsx(CapUsageBar, { usage: cap, showLabel: false })] })) : undefined;
58
59
  return (_jsx(StatCard, { icon: Icon !== undefined ? _jsx(Icon, { className: "size-4" }) : undefined, label: t(cap.label), value: !isMeasured(cap)
59
- ? "—"
60
+ ? EMPTY_VALUE_PLACEHOLDER
60
61
  : cap.limit === null
61
62
  ? String(cap.used)
62
63
  : `${cap.used} / ${cap.limit}`, ...(cap.accentColor !== undefined && { accentColor: cap.accentColor }), testId: "cap-card", children: body }, cap.id));
@@ -1,5 +1,5 @@
1
1
  export declare const tenantComplianceProfileEntity: import("@cosmicdrift/kumiko-types/fields").EntityDefinition<{
2
- profileKey: {
2
+ readonly profileKey: {
3
3
  readonly type: "select";
4
4
  readonly description?: string;
5
5
  readonly options: readonly ["eu-dsgvo", "swiss-dsg", "de-hr-dsgvo-hgb", "minimal-no-region"];
@@ -23,7 +23,7 @@ export declare const tenantComplianceProfileEntity: import("@cosmicdrift/kumiko-
23
23
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
24
24
  required: true;
25
25
  };
26
- override: {
26
+ readonly override: {
27
27
  readonly type: "longText";
28
28
  readonly description?: string;
29
29
  readonly maxLength?: number;
@@ -41,7 +41,7 @@ export declare const tenantComplianceProfileEntity: import("@cosmicdrift/kumiko-
41
41
  };
42
42
  }, "tenant">;
43
43
  export declare const tenantComplianceProfileTable: import("@cosmicdrift/kumiko-framework/db").EntityTable<import("@cosmicdrift/kumiko-types/fields").EntityDefinition<{
44
- profileKey: {
44
+ readonly profileKey: {
45
45
  readonly type: "select";
46
46
  readonly description?: string;
47
47
  readonly options: readonly ["eu-dsgvo", "swiss-dsg", "de-hr-dsgvo-hgb", "minimal-no-region"];
@@ -65,7 +65,7 @@ export declare const tenantComplianceProfileTable: import("@cosmicdrift/kumiko-f
65
65
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
66
66
  required: true;
67
67
  };
68
- override: {
68
+ readonly override: {
69
69
  readonly type: "longText";
70
70
  readonly description?: string;
71
71
  readonly maxLength?: number;
@@ -1,5 +1,5 @@
1
1
  export declare const configValueEntity: import("@cosmicdrift/kumiko-types/fields").EntityDefinition<{
2
- key: {
2
+ readonly key: {
3
3
  readonly type: "text";
4
4
  readonly description?: string;
5
5
  readonly maxLength?: number;
@@ -22,7 +22,7 @@ export declare const configValueEntity: import("@cosmicdrift/kumiko-types/fields
22
22
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
23
23
  required: true;
24
24
  };
25
- value: {
25
+ readonly value: {
26
26
  readonly type: "text";
27
27
  readonly description?: string;
28
28
  readonly maxLength?: number;
@@ -45,7 +45,7 @@ export declare const configValueEntity: import("@cosmicdrift/kumiko-types/fields
45
45
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
46
46
  required: false;
47
47
  };
48
- userId: {
48
+ readonly userId: {
49
49
  readonly type: "text";
50
50
  readonly description?: string;
51
51
  readonly maxLength?: number;
@@ -70,7 +70,7 @@ export declare const configValueEntity: import("@cosmicdrift/kumiko-types/fields
70
70
  };
71
71
  }, "tenant">;
72
72
  export declare const configValuesTable: import("@cosmicdrift/kumiko-framework/db").EntityTable<import("@cosmicdrift/kumiko-types/fields").EntityDefinition<{
73
- key: {
73
+ readonly key: {
74
74
  readonly type: "text";
75
75
  readonly description?: string;
76
76
  readonly maxLength?: number;
@@ -93,7 +93,7 @@ export declare const configValuesTable: import("@cosmicdrift/kumiko-framework/db
93
93
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
94
94
  required: true;
95
95
  };
96
- value: {
96
+ readonly value: {
97
97
  readonly type: "text";
98
98
  readonly description?: string;
99
99
  readonly maxLength?: number;
@@ -116,7 +116,7 @@ export declare const configValuesTable: import("@cosmicdrift/kumiko-framework/db
116
116
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
117
117
  required: false;
118
118
  };
119
- userId: {
119
+ readonly userId: {
120
120
  readonly type: "text";
121
121
  readonly description?: string;
122
122
  readonly maxLength?: number;
@@ -1,2 +1,3 @@
1
+ export { ConfigQueries } from "../constants.ts";
1
2
  export { type ConfigClientFeature, type ConfigClientOptions, configClient, } from "./client-plugin.ts";
2
3
  export { defaultTranslations } from "./i18n.ts";
@@ -1,3 +1,4 @@
1
1
  // @runtime client
2
+ export { ConfigQueries } from "../constants.js";
2
3
  export { configClient, } from "./client-plugin.js";
3
4
  export { defaultTranslations } from "./i18n.js";
@@ -2,7 +2,7 @@ import { requestContext } from "@cosmicdrift/kumiko-framework/api";
2
2
  import { ROLES } from "@cosmicdrift/kumiko-framework/auth";
3
3
  import { computeBlindIndex, configuredBlindIndexKey, configuredPiiSubjectKms, subjectIdSchema, subjectIdToKey, } from "@cosmicdrift/kumiko-framework/crypto";
4
4
  import { fetchOne, nullBlindIndexesForSubject, recordEventsOwnedExclusivelyByTenant, recordRowExistsInTenant, recordRowOwningTenantId, runInSavepointIfSupported, runnerCanBeginTransaction, subjectRowExistsInTenant, transaction, WritePurposes, } from "@cosmicdrift/kumiko-framework/db";
5
- import { defineWriteHandler, } from "@cosmicdrift/kumiko-framework/engine";
5
+ import { defineWriteHandler, parseTenantId, } from "@cosmicdrift/kumiko-framework/engine";
6
6
  import { AccessDeniedError, InternalError, writeFailure, } from "@cosmicdrift/kumiko-framework/errors";
7
7
  import { appendDomainEventCore, assertIrreversibleOperationAllowed, } from "@cosmicdrift/kumiko-framework/pipeline";
8
8
  import { purgeSearchDocumentsForSubject } from "@cosmicdrift/kumiko-framework/search";
@@ -11,9 +11,11 @@ import * as z from "zod";
11
11
  import { resolveRetentionPolicyForTenant } from "../../data-retention/index.js";
12
12
  import { eraseNoteBodiesReachingSubject } from "../../notes-history-user-data/index.js";
13
13
  import { revokeAllPatTokensForUser } from "../../personal-access-tokens/index.js";
14
+ import { purgeFilesReachingSubject } from "../../shared/index.js";
14
15
  import { USER_STATUS, userTable } from "../../user/index.js";
15
16
  import { denyIfTargetOutsideAdminTenant, isSystemAdminActor, updateUserLifecycle, } from "../../user-data-rights/index.js";
16
17
  import { CRYPTO_SHREDDING_AGGREGATE_TYPE, CRYPTO_SHREDDING_FEATURE_NAME, RECORD_ENTITY_NOT_REGISTERED, SUBJECT_FORGET_DENIED_EVENT_NAME, SUBJECT_FORGOTTEN_EVENT_NAME, TARGET_RECORD_NOT_ADMIN_TENANT, TARGET_RECORD_RETENTION_BLOCK_DELETE, TARGET_TENANT_NOT_ADMIN_TENANT, } from "../constants.js";
18
+ import { resolveFileBytesDeleter } from "../resolve-file-bytes-deleter.js";
17
19
  export { subjectIdSchema };
18
20
  export const forgetSubjectSchema = z.object({
19
21
  subject: subjectIdSchema,
@@ -142,6 +144,13 @@ getRunner) {
142
144
  });
143
145
  return effective.policy?.strategy === "blockDelete" ? deny() : undefined;
144
146
  }
147
+ // The tenant that owns a record subject's row; the file purge is scoped to it.
148
+ async function owningTenantOfRecordSubject(runner, features, raw) {
149
+ if (raw.kind !== "record")
150
+ return undefined;
151
+ const owner = await recordRowOwningTenantId(runner, features, raw.entity, raw.id);
152
+ return parseTenantId(owner) ?? undefined;
153
+ }
145
154
  // AccessDeniedError.code is the generic "access_denied" for every branch;
146
155
  // the specific branch lives in details.reason.
147
156
  function denialReasonOf(failure) {
@@ -212,33 +221,49 @@ function sweepErrorLabel(err) {
212
221
  }
213
222
  // The key is already erased when this runs, so a failing sweep must not skip the
214
223
  // audit event: the failure is returned as a label for the event instead of thrown.
215
- // The savepoint keeps a database error from aborting the handler transaction.
216
- async function sweepDerivedSubjectData(ctx, subject, subjectKey, crossTenantRunner, audit) {
217
- try {
218
- await runInSavepointIfSupported(crossTenantRunner, async (crossTenantSubjectRunner) => {
219
- // Blind-index sweep (#818): nulls bidx columns now so the deterministic
220
- // HMAC doesn't stay equality-matchable; raw because the ciphertext prefix addresses the subject across tenants.
221
- await nullBlindIndexesForSubject(crossTenantSubjectRunner, ctx.registry.features, subjectKey);
222
- // Derived search index still holds plaintext (#1610) — purge next to the
223
- // blind-index sweep. No adapter → no-op (apps without search).
224
- if (ctx.searchAdapter) {
225
- await purgeSearchDocumentsForSubject(crossTenantSubjectRunner, ctx.registry.features, ctx.searchAdapter, subjectKey, subject);
226
- }
227
- // Note bodies are keyed per note, so the subject's own key never reaches them.
228
- if (ctx.registry.features.has("notes-history")) {
229
- await eraseNoteBodiesReachingSubject({
230
- db: crossTenantSubjectRunner,
231
- subject,
232
- erasedBy: audit.erasedBy,
233
- requestId: audit.requestId,
234
- });
235
- }
236
- });
237
- return undefined;
238
- }
239
- catch (err) {
240
- return sweepErrorLabel(err);
241
- }
224
+ // Each step runs in its own savepoint: a database error must not abort the handler
225
+ // transaction, and one failing step must not skip or roll back the others.
226
+ async function sweepDerivedSubjectData(ctx, subject, subjectKey, crossTenantRunner, audit, rawSubject) {
227
+ const recordTenantId = await owningTenantOfRecordSubject(crossTenantRunner, ctx.registry.features, rawSubject);
228
+ const failureLabels = [];
229
+ const runStep = async (step) => {
230
+ try {
231
+ await runInSavepointIfSupported(crossTenantRunner, step);
232
+ }
233
+ catch (err) {
234
+ failureLabels.push(sweepErrorLabel(err));
235
+ }
236
+ };
237
+ await runStep(async (crossTenantSubjectRunner) => {
238
+ // Blind-index sweep (#818): nulls bidx columns now so the deterministic
239
+ // HMAC doesn't stay equality-matchable; raw because the ciphertext prefix addresses the subject across tenants.
240
+ await nullBlindIndexesForSubject(crossTenantSubjectRunner, ctx.registry.features, subjectKey);
241
+ // Derived search index still holds plaintext (#1610) — purge next to the
242
+ // blind-index sweep. No adapter → no-op (apps without search).
243
+ if (ctx.searchAdapter) {
244
+ await purgeSearchDocumentsForSubject(crossTenantSubjectRunner, ctx.registry.features, ctx.searchAdapter, subjectKey, subject);
245
+ }
246
+ // Note bodies are keyed per note, so the subject's own key never reaches them.
247
+ if (ctx.registry.features.has("notes-history")) {
248
+ await eraseNoteBodiesReachingSubject({
249
+ db: crossTenantSubjectRunner,
250
+ subject,
251
+ erasedBy: audit.erasedBy,
252
+ requestId: audit.requestId,
253
+ });
254
+ }
255
+ });
256
+ // File columns hold only the fileRef id, so the key erase never reaches the bytes.
257
+ await runStep((crossTenantSubjectRunner) => purgeFilesReachingSubject({
258
+ registry: ctx.registry,
259
+ db: crossTenantSubjectRunner,
260
+ subject,
261
+ subjectKey,
262
+ ...(recordTenantId !== undefined && { recordTenantId }),
263
+ resolveDeleteBytes: resolveFileBytesDeleter(ctx),
264
+ missingStorage: { mode: "fail" },
265
+ }));
266
+ return failureLabels[0];
242
267
  }
243
268
  // Email subscribers and other non-user entities use user-style subject keys
244
269
  // without a read_users row — the key erase is the GDPR-relevant part, so no row
@@ -319,7 +344,7 @@ export const forgetSubjectWrite = defineWriteHandler({
319
344
  grants: ["unsafeRaw"],
320
345
  reason: "the denial and SUBJECT_FORGOTTEN audit appends name the caller's own tenant stream on the outside-transaction db; " +
321
346
  "the tenant-scope and retention checks run against the subject's tenant, not necessarily the caller's; " +
322
- "the blind-index sweep and search purge address the subject across tenants; the user " +
347
+ "the blind-index sweep, search purge and file purge address the subject across tenants; the user " +
323
348
  "lifecycle update and PAT revoke run on the SYSTEM user stream.",
324
349
  },
325
350
  handler: async (event, ctx) => {
@@ -382,7 +407,7 @@ export const forgetSubjectWrite = defineWriteHandler({
382
407
  if (raw.kind === "user" && ctx.registry.features.has("user")) {
383
408
  await closeUserLoginDoor(ctx, raw.userId, ctx.dbOutsideTransaction?.unsafeRaw(), ctx.db.unsafeRaw());
384
409
  }
385
- const cleanupError = await sweepDerivedSubjectData(ctx, subject, subjectKey, ctx.db.unsafeRaw(), { requestId, erasedBy: event.user.id });
410
+ const cleanupError = await sweepDerivedSubjectData(ctx, subject, subjectKey, ctx.db.unsafeRaw(), { requestId, erasedBy: event.user.id }, raw);
386
411
  await appendSubjectForgotten(ctx, event, subjectAggregateId(raw), {
387
412
  subjectKey,
388
413
  reason: event.payload.reason,
@@ -0,0 +1,3 @@
1
+ import type { HandlerContext, TenantId } from "@cosmicdrift/kumiko-framework/engine";
2
+ import type { DeleteFileBytes } from "../shared/index.ts";
3
+ export declare function resolveFileBytesDeleter(ctx: HandlerContext): (tenantId: TenantId) => Promise<DeleteFileBytes | undefined>;
@@ -0,0 +1,24 @@
1
+ import { deleteFileRefBytes } from "@cosmicdrift/kumiko-framework/derivatives";
2
+ import { createFileContext } from "@cosmicdrift/kumiko-framework/files";
3
+ const ERROR_CONTEXT = "crypto-shredding:forget-subject";
4
+ // The upload resolver, not ctx.files: ctx.files is bound to the acting user's tenant, while a
5
+ // subject's rows can live in any tenant. Resolved eagerly so a missing provider surfaces as
6
+ // missing_file_storage instead of a failed delete.
7
+ export function resolveFileBytesDeleter(ctx) {
8
+ return async (tenantId) => {
9
+ const resolver = ctx._fileProviderResolver;
10
+ if (resolver === undefined) {
11
+ const own = tenantId === ctx.user.tenantId ? ctx.files : undefined;
12
+ return own && ((key) => deleteFileRefBytes(own, { storageKey: key }, ERROR_CONTEXT));
13
+ }
14
+ try {
15
+ const provider = await resolver(tenantId);
16
+ const files = createFileContext(() => Promise.resolve(provider), tenantId);
17
+ return (key) => deleteFileRefBytes(files, { storageKey: key }, ERROR_CONTEXT);
18
+ }
19
+ catch {
20
+ // skip: no provider resolvable for this tenant; surfaces as missing_file_storage when files exist
21
+ return undefined;
22
+ }
23
+ };
24
+ }
@@ -1,5 +1,5 @@
1
1
  export declare const fieldDefinitionEntity: import("@cosmicdrift/kumiko-types/fields").EntityDefinition<{
2
- entityName: {
2
+ readonly entityName: {
3
3
  readonly type: "text";
4
4
  readonly description?: string;
5
5
  readonly maxLength?: number;
@@ -22,7 +22,7 @@ export declare const fieldDefinitionEntity: import("@cosmicdrift/kumiko-types/fi
22
22
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
23
23
  required: true;
24
24
  };
25
- fieldKey: {
25
+ readonly fieldKey: {
26
26
  readonly type: "text";
27
27
  readonly description?: string;
28
28
  readonly maxLength?: number;
@@ -45,7 +45,7 @@ export declare const fieldDefinitionEntity: import("@cosmicdrift/kumiko-types/fi
45
45
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
46
46
  required: true;
47
47
  };
48
- type: {
48
+ readonly type: {
49
49
  readonly type: "text";
50
50
  readonly description?: string;
51
51
  readonly maxLength?: number;
@@ -68,13 +68,15 @@ export declare const fieldDefinitionEntity: import("@cosmicdrift/kumiko-types/fi
68
68
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
69
69
  required: true;
70
70
  };
71
- required: import("@cosmicdrift/kumiko-types/fields").BooleanFieldDef & {
71
+ readonly required: import("@cosmicdrift/kumiko-types/fields").BooleanFieldDef & {
72
72
  required: true;
73
+ default: boolean;
73
74
  };
74
- searchable: import("@cosmicdrift/kumiko-types/fields").BooleanFieldDef & {
75
+ readonly searchable: import("@cosmicdrift/kumiko-types/fields").BooleanFieldDef & {
75
76
  required: true;
77
+ default: boolean;
76
78
  };
77
- displayOrder: {
79
+ readonly displayOrder: {
78
80
  readonly type: "number";
79
81
  readonly description?: string;
80
82
  readonly required?: boolean;
@@ -93,7 +95,7 @@ export declare const fieldDefinitionEntity: import("@cosmicdrift/kumiko-types/fi
93
95
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
94
96
  required: true;
95
97
  };
96
- serializedField: {
98
+ readonly serializedField: {
97
99
  readonly type: "text";
98
100
  readonly description?: string;
99
101
  readonly maxLength?: number;