@cosmicdrift/kumiko-bundled-features 0.352.0 → 0.353.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 (105) hide show
  1. package/dist/agent-tools/agent-manifest.js +8 -2
  2. package/dist/agent-tools/manifest-deny-qns.d.ts +3 -0
  3. package/dist/agent-tools/manifest-deny-qns.js +17 -0
  4. package/dist/agent-tools/tool-catalog.d.ts +2 -1
  5. package/dist/agent-tools/tool-catalog.js +4 -1
  6. package/dist/audit/handlers/list.query.js +13 -8
  7. package/dist/auth-email-password/handlers/signup-confirm.write.js +2 -3
  8. package/dist/auth-mfa/schema/user-mfa.d.ts +6 -0
  9. package/dist/auth-mfa/screens.js +2 -2
  10. package/dist/billing-foundation/checkout-core.d.ts +7 -0
  11. package/dist/billing-foundation/checkout-core.js +7 -2
  12. package/dist/billing-foundation/entities.d.ts +8 -0
  13. package/dist/billing-foundation/handlers/start-plan-checkout.write.js +7 -1
  14. package/dist/billing-foundation/projection.d.ts +8 -0
  15. package/dist/cap-counter/entity.d.ts +1 -0
  16. package/dist/cap-overview/access.js +2 -9
  17. package/dist/cap-overview/handlers/tenant-caps-list.query.js +2 -2
  18. package/dist/cap-overview/handlers/tenant-options.query.js +4 -5
  19. package/dist/compliance-profiles/schema/profile-selection.d.ts +8 -8
  20. package/dist/compliance-profiles-ops/handlers/tenants-missing-profile.query.js +2 -4
  21. package/dist/config/handlers/values.query.d.ts +3 -1
  22. package/dist/config/handlers/values.query.js +7 -2
  23. package/dist/config/table.d.ts +6 -0
  24. package/dist/custom-fields/entity.d.ts +4 -0
  25. package/dist/data-retention/schema/tenant-retention-override.d.ts +4 -0
  26. package/dist/delivery/public-names.js +1 -1
  27. package/dist/delivery/tables.d.ts +6 -0
  28. package/dist/document-ingest-foundation/entity.d.ts +4 -0
  29. package/dist/files-tenant-data/hooks.js +3 -2
  30. package/dist/folders/entity.d.ts +8 -0
  31. package/dist/folders/feature.js +2 -10
  32. package/dist/form-draft/db/queries/cleanup.d.ts +1 -1
  33. package/dist/form-draft/db/queries/cleanup.js +4 -3
  34. package/dist/form-draft/entity.d.ts +2 -0
  35. package/dist/form-draft/handlers/cleanup.job.d.ts +5 -1
  36. package/dist/form-draft/handlers/cleanup.job.js +11 -5
  37. package/dist/inbound-mail-foundation/entities.d.ts +25 -0
  38. package/dist/inbound-mail-foundation/projection.d.ts +20 -0
  39. package/dist/jobs/handlers/list.query.d.ts +2 -1
  40. package/dist/jobs/handlers/list.query.js +39 -14
  41. package/dist/jobs/i18n.js +1 -0
  42. package/dist/jobs/job-run-table.d.ts +6 -0
  43. package/dist/ledger/entity.d.ts +21 -8
  44. package/dist/ledger/entity.js +4 -0
  45. package/dist/ledger/schemas.js +8 -2
  46. package/dist/managed-pages/feature.js +28 -10
  47. package/dist/managed-pages/table.d.ts +12 -0
  48. package/dist/notes-history/entity.d.ts +12 -0
  49. package/dist/notes-history/feature.js +2 -10
  50. package/dist/personal-access-tokens/schema/api-token.d.ts +6 -0
  51. package/dist/personal-access-tokens/screens.js +14 -3
  52. package/dist/secrets/table.d.ts +1 -0
  53. package/dist/seo/feature.js +28 -6
  54. package/dist/sessions/schema/user-session.d.ts +4 -0
  55. package/dist/shared/assert-no-where-rule-in-ownership-write.d.ts +2 -0
  56. package/dist/shared/assert-no-where-rule-in-ownership-write.js +18 -0
  57. package/dist/shared/index.d.ts +2 -1
  58. package/dist/shared/index.js +2 -1
  59. package/dist/shared/rate-limited-text-response.d.ts +2 -0
  60. package/dist/shared/rate-limited-text-response.js +9 -0
  61. package/dist/tags/entity.d.ts +9 -0
  62. package/dist/tags/feature.js +2 -10
  63. package/dist/template-resolver/table.d.ts +40 -30
  64. package/dist/template-resolver/user-content-table.d.ts +30 -20
  65. package/dist/tenant/decrypt-tenant-names.d.ts +3 -0
  66. package/dist/tenant/decrypt-tenant-names.js +10 -0
  67. package/dist/tenant/handlers/me.query.js +5 -1
  68. package/dist/tenant/handlers/memberships.query.js +3 -2
  69. package/dist/tenant/handlers/tenant-directory.query.js +9 -3
  70. package/dist/tenant/index.d.ts +1 -0
  71. package/dist/tenant/index.js +1 -0
  72. package/dist/tenant/invitation-table.d.ts +14 -8
  73. package/dist/tenant/membership-table.d.ts +4 -0
  74. package/dist/tenant/schema/tenant.d.ts +16 -10
  75. package/dist/tenant/schema/tenant.js +4 -6
  76. package/dist/tenant/screens.js +1 -1
  77. package/dist/tenant-handover/move-entity-graph.js +11 -18
  78. package/dist/tenant-lifecycle/feature.js +1 -0
  79. package/dist/tenant-lifecycle/run-tenant-destroy.d.ts +3 -0
  80. package/dist/tenant-lifecycle/run-tenant-destroy.js +2 -0
  81. package/dist/tenant-lifecycle/stages.d.ts +2 -0
  82. package/dist/tenant-lifecycle/stages.js +7 -1
  83. package/dist/tier-engine/entity.d.ts +2 -0
  84. package/dist/tier-engine/tier-resolver.d.ts +2 -0
  85. package/dist/user/handlers/list.query.js +2 -2
  86. package/dist/user/schema/user.d.ts +20 -8
  87. package/dist/user-data-rights/boot-checks.js +5 -0
  88. package/dist/user-data-rights/handlers/confirm-deletion-by-token.write.js +2 -2
  89. package/dist/user-data-rights/schema/download-attempt.d.ts +14 -0
  90. package/dist/user-data-rights/schema/download-token.d.ts +8 -0
  91. package/dist/user-data-rights/schema/export-job.d.ts +14 -8
  92. package/dist/waitlist/entity.d.ts +20 -6
  93. package/dist/workflow-runner/event-trigger.js +6 -3
  94. package/dist/workflow-runner/handlers/resume-run.write.js +3 -0
  95. package/package.json +9 -9
  96. package/src/agent-tools/changes.json +12 -0
  97. package/src/audit/changes.json +6 -0
  98. package/src/form-draft/changes.json +7 -1
  99. package/src/managed-pages/changes.json +6 -0
  100. package/src/seo/changes.json +5 -0
  101. package/src/tenant/changes.json +7 -0
  102. package/src/tenant-handover/changes.json +6 -0
  103. package/src/workflow-runner/changes.json +5 -0
  104. package/dist/shared/has-where-rule.d.ts +0 -2
  105. package/dist/shared/has-where-rule.js +0 -10
@@ -5,6 +5,7 @@ export declare const tenantEntity: import("@cosmicdrift/kumiko-types/fields").En
5
5
  readonly type: "text";
6
6
  readonly description?: string;
7
7
  readonly maxLength?: number;
8
+ readonly minLength?: number;
8
9
  readonly required?: boolean;
9
10
  readonly searchable?: boolean;
10
11
  readonly sortable?: boolean;
@@ -26,6 +27,7 @@ export declare const tenantEntity: import("@cosmicdrift/kumiko-types/fields").En
26
27
  readonly type: "text";
27
28
  readonly description?: string;
28
29
  readonly maxLength?: number;
30
+ readonly minLength?: number;
29
31
  readonly required?: boolean;
30
32
  readonly searchable?: boolean;
31
33
  readonly sortable?: boolean;
@@ -58,11 +60,11 @@ export declare const tenantEntity: import("@cosmicdrift/kumiko-types/fields").En
58
60
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
59
61
  readonly display?: "radio" | "dropdown";
60
62
  readonly optionTones?: {
61
- readonly active?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
62
- readonly destroyFailed?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
63
- readonly destroyRequested?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
64
- readonly destroyed?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
65
- readonly destroying?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
63
+ readonly active?: "bad" | "neutral" | "ok" | "warn" | undefined;
64
+ readonly destroyFailed?: "bad" | "neutral" | "ok" | "warn" | undefined;
65
+ readonly destroyRequested?: "bad" | "neutral" | "ok" | "warn" | undefined;
66
+ readonly destroyed?: "bad" | "neutral" | "ok" | "warn" | undefined;
67
+ readonly destroying?: "bad" | "neutral" | "ok" | "warn" | undefined;
66
68
  } | undefined;
67
69
  readonly optionsQuery?: string;
68
70
  readonly optionsQueryPayload?: import("@cosmicdrift/kumiko-types/fields").OptionsQueryPayload;
@@ -89,6 +91,7 @@ export declare const tenantEntity: import("@cosmicdrift/kumiko-types/fields").En
89
91
  readonly type: "text";
90
92
  readonly description?: string;
91
93
  readonly maxLength?: number;
94
+ readonly minLength?: number;
92
95
  readonly required?: boolean;
93
96
  readonly searchable?: boolean;
94
97
  readonly sortable?: boolean;
@@ -157,6 +160,7 @@ export declare const tenantTable: import("@cosmicdrift/kumiko-framework/db").Ent
157
160
  readonly type: "text";
158
161
  readonly description?: string;
159
162
  readonly maxLength?: number;
163
+ readonly minLength?: number;
160
164
  readonly required?: boolean;
161
165
  readonly searchable?: boolean;
162
166
  readonly sortable?: boolean;
@@ -178,6 +182,7 @@ export declare const tenantTable: import("@cosmicdrift/kumiko-framework/db").Ent
178
182
  readonly type: "text";
179
183
  readonly description?: string;
180
184
  readonly maxLength?: number;
185
+ readonly minLength?: number;
181
186
  readonly required?: boolean;
182
187
  readonly searchable?: boolean;
183
188
  readonly sortable?: boolean;
@@ -210,11 +215,11 @@ export declare const tenantTable: import("@cosmicdrift/kumiko-framework/db").Ent
210
215
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
211
216
  readonly display?: "radio" | "dropdown";
212
217
  readonly optionTones?: {
213
- readonly active?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
214
- readonly destroyFailed?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
215
- readonly destroyRequested?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
216
- readonly destroyed?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
217
- readonly destroying?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
218
+ readonly active?: "bad" | "neutral" | "ok" | "warn" | undefined;
219
+ readonly destroyFailed?: "bad" | "neutral" | "ok" | "warn" | undefined;
220
+ readonly destroyRequested?: "bad" | "neutral" | "ok" | "warn" | undefined;
221
+ readonly destroyed?: "bad" | "neutral" | "ok" | "warn" | undefined;
222
+ readonly destroying?: "bad" | "neutral" | "ok" | "warn" | undefined;
218
223
  } | undefined;
219
224
  readonly optionsQuery?: string;
220
225
  readonly optionsQueryPayload?: import("@cosmicdrift/kumiko-types/fields").OptionsQueryPayload;
@@ -241,6 +246,7 @@ export declare const tenantTable: import("@cosmicdrift/kumiko-framework/db").Ent
241
246
  readonly type: "text";
242
247
  readonly description?: string;
243
248
  readonly maxLength?: number;
249
+ readonly minLength?: number;
244
250
  readonly required?: boolean;
245
251
  readonly searchable?: boolean;
246
252
  readonly sortable?: boolean;
@@ -17,19 +17,17 @@ export const tenantEntity = createEntity({
17
17
  key: createTextField({
18
18
  required: true,
19
19
  maxLength: 50,
20
+ sortable: true,
20
21
  personal: false,
21
22
  reason: "technical_reference",
22
23
  }),
23
- // Workspace/company display name, not an individual's identity —
24
- // same class as the `company.legalName` example in the boot-validator's
25
- // own PII-heuristic doc-comment.
24
+ // Subject is the tenant record itself: tenant events live in the writer's (system) tenant stream, so `personal: "tenant"` would not resolve to this tenant.
26
25
  name: createTextField({
27
26
  required: true,
28
27
  maxLength: 200,
29
28
  searchable: true,
30
- sortable: true,
31
- personal: false,
32
- reason: "is_business_data",
29
+ personal: { of: "id" },
30
+ find: "none",
33
31
  }),
34
32
  isEnabled: createBooleanField({ default: true, filterable: true }),
35
33
  // Tenant-destroy lifecycle (tenant-lifecycle feature). Defaults keep
@@ -23,7 +23,7 @@ export const tenantListScreen = {
23
23
  entityId: "id",
24
24
  },
25
25
  ],
26
- defaultSort: { field: "name", dir: "asc" },
26
+ defaultSort: { field: "key", dir: "asc" },
27
27
  searchable: true,
28
28
  access: { roles: ["SystemAdmin"] },
29
29
  // Inert until an app navs it (see file header) — no nav area to resolve
@@ -174,7 +174,7 @@ export async function moveTransferGraph(args) {
174
174
  let pending = [
175
175
  { entityName: rootEntityName, rowIds: [rootRowId] },
176
176
  ];
177
- for (let round = 0; round < MAX_TRANSFER_DEPTH && pending.length > 0; round++) {
177
+ for (let round = 0; round <= MAX_TRANSFER_DEPTH && pending.length > 0; round++) {
178
178
  // Merged per type so an edge leaving a type runs once per round, not once
179
179
  // per edge that reached it.
180
180
  const discovered = new Map();
@@ -231,24 +231,17 @@ export async function moveTransferGraph(args) {
231
231
  }));
232
232
  }
233
233
  }
234
+ // The extra round past the limit is a probe: it only exists to find out
235
+ // whether rows still hang below the last allowed hop. Its moves are rolled
236
+ // back with the transaction when this throws.
237
+ const [strandedEntityName] = discovered.keys();
238
+ if (round === MAX_TRANSFER_DEPTH && strandedEntityName !== undefined) {
239
+ throw new UnprocessableError("transfer_graph_too_deep", {
240
+ i18nKey: "errors.tenantHandover.transferGraphTooDeep",
241
+ details: { entityName: strandedEntityName },
242
+ });
243
+ }
234
244
  pending = [...discovered].map(([entityName, rowIds]) => ({ entityName, rowIds }));
235
245
  }
236
- // The depth limit is a safety net, not a licence to move part of a graph: if
237
- // rounds ran out while rows remain whose type still leads somewhere, the
238
- // declaration reaches further than the mover does, and returning quietly
239
- // would leave exactly the orphaned rows #3088 is about.
240
- //
241
- // Rounds count hops of both edge kinds, while the boot validator measures
242
- // reference chains between transferable entities only. A graph that fills the
243
- // limit with reference hops and then adds a parentRef child therefore boots
244
- // clean and fails here instead — the residual the validator cannot see, and
245
- // the reason this check has to exist rather than trusting boot alone.
246
- const stranded = pending.find(({ entityName }) => (adjacency.get(entityName) ?? []).length > 0);
247
- if (stranded !== undefined) {
248
- throw new UnprocessableError("transfer_graph_too_deep", {
249
- i18nKey: "errors.tenantHandover.transferGraphTooDeep",
250
- details: { entityName: stranded.entityName },
251
- });
252
- }
253
246
  return movedCounts;
254
247
  }
@@ -85,6 +85,7 @@ export function createTenantLifecycleFeature() {
85
85
  now,
86
86
  log: (message) => ctx.log?.warn(message),
87
87
  fileProviderResolver: ctx._fileProviderResolver,
88
+ searchAdapter: ctx.searchAdapter,
88
89
  escapeHatchAuditSink: ctx._escapeHatchAuditSink,
89
90
  actor: ctx.systemUser.id,
90
91
  });
@@ -2,12 +2,14 @@
2
2
  import type { DbRunner, TenantDb } from "@cosmicdrift/kumiko-framework/db";
3
3
  import { type EscapeHatchAuditSink, type Registry, type TenantId } from "@cosmicdrift/kumiko-framework/engine";
4
4
  import type { FileProviderResolver } from "@cosmicdrift/kumiko-framework/files";
5
+ import type { SearchAdapter } from "@cosmicdrift/kumiko-framework/search";
5
6
  export declare function runNextDestructionStage(args: {
6
7
  readonly db: DbRunner;
7
8
  readonly registry: Registry;
8
9
  readonly tenantId: TenantId;
9
10
  readonly log?: (message: string) => void;
10
11
  readonly fileProviderResolver?: FileProviderResolver;
12
+ readonly searchAdapter?: SearchAdapter;
11
13
  readonly escapeHatchAuditSink?: EscapeHatchAuditSink;
12
14
  readonly actor?: string;
13
15
  }): Promise<{
@@ -23,6 +25,7 @@ export declare function runTenantDestructionSweep(args: {
23
25
  readonly now?: Temporal.Instant;
24
26
  readonly log?: (message: string) => void;
25
27
  readonly fileProviderResolver?: FileProviderResolver;
28
+ readonly searchAdapter?: SearchAdapter;
26
29
  readonly escapeHatchAuditSink?: EscapeHatchAuditSink;
27
30
  readonly actor?: string;
28
31
  }): Promise<{
@@ -133,6 +133,7 @@ export async function runNextDestructionStage(args) {
133
133
  tenantId: args.tenantId,
134
134
  log: args.log,
135
135
  fileProviderResolver: args.fileProviderResolver,
136
+ searchAdapter: args.searchAdapter,
136
137
  escapeHatchAuditSink: args.escapeHatchAuditSink,
137
138
  actor: args.actor,
138
139
  };
@@ -215,6 +216,7 @@ export async function runTenantDestructionSweep(args) {
215
216
  tenantId: row.id,
216
217
  log: args.log,
217
218
  fileProviderResolver: args.fileProviderResolver,
219
+ searchAdapter: args.searchAdapter,
218
220
  escapeHatchAuditSink: args.escapeHatchAuditSink,
219
221
  actor: args.actor,
220
222
  });
@@ -1,6 +1,7 @@
1
1
  import { type DbRunner } from "@cosmicdrift/kumiko-framework/db";
2
2
  import { type EscapeHatchAuditSink, type Registry, type TenantId } from "@cosmicdrift/kumiko-framework/engine";
3
3
  import type { FileProviderResolver } from "@cosmicdrift/kumiko-framework/files";
4
+ import { type SearchAdapter } from "@cosmicdrift/kumiko-framework/search";
4
5
  import type { TenantDestructionStageName } from "./constants.js";
5
6
  export type DestructionStageCtx = {
6
7
  readonly db: DbRunner;
@@ -8,6 +9,7 @@ export type DestructionStageCtx = {
8
9
  readonly tenantId: TenantId;
9
10
  readonly log?: (message: string) => void;
10
11
  readonly fileProviderResolver?: FileProviderResolver;
12
+ readonly searchAdapter?: SearchAdapter;
11
13
  readonly escapeHatchAuditSink?: EscapeHatchAuditSink;
12
14
  readonly actor?: string;
13
15
  };
@@ -1,8 +1,9 @@
1
1
  import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
- import { configuredPiiSubjectKms } from "@cosmicdrift/kumiko-framework/crypto";
2
+ import { configuredPiiSubjectKms, subjectIdToKey, } from "@cosmicdrift/kumiko-framework/crypto";
3
3
  import { createEventStoreExecutor, createTenantDb, } from "@cosmicdrift/kumiko-framework/db";
4
4
  import { createSystemUser, EXT_EXTERNAL_RESOURCE, EXT_INFRA_RESOURCE, EXT_SEARCH_ADAPTER, EXT_STORAGE_PROVIDER, EXT_TENANT_DATA, extensionUsageEscapeHatchReason, isTenantDataExtensionHooks, isTenantResourceExtensionHooks, } from "@cosmicdrift/kumiko-framework/engine";
5
5
  import { createEscapeHatchReporter, UNATTRIBUTED_ACTOR, } from "@cosmicdrift/kumiko-framework/pipeline";
6
+ import { purgeSearchDocumentsForSubject, } from "@cosmicdrift/kumiko-framework/search";
6
7
  import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
7
8
  import { tenantEntity, tenantMembershipEntity, tenantMembershipsTable, tenantTable, } from "../tenant/index.js";
8
9
  import { invalidateTenantLifecycleGate } from "./lifecycle-gate.js";
@@ -56,8 +57,10 @@ async function eraseSubjectKeys(ctx) {
56
57
  const memberships = await selectMany(ctx.db, tenantMembershipsTable, {
57
58
  tenantId: ctx.tenantId,
58
59
  });
60
+ const tenantRecordSubject = { kind: "record", entity: "tenant", id: ctx.tenantId };
59
61
  const subjects = [
60
62
  { kind: "tenant", tenantId: ctx.tenantId },
63
+ tenantRecordSubject,
61
64
  ...memberships.map((m) => ({ kind: "user", userId: m.userId })),
62
65
  ];
63
66
  for (const subject of subjects) {
@@ -66,6 +69,9 @@ async function eraseSubjectKeys(ctx) {
66
69
  eraseReason: "tenant-destroy stage subject-keys",
67
70
  });
68
71
  }
72
+ if (ctx.searchAdapter) {
73
+ await purgeSearchDocumentsForSubject(ctx.db, ctx.registry.features, ctx.searchAdapter, subjectIdToKey(tenantRecordSubject), tenantRecordSubject);
74
+ }
69
75
  }
70
76
  async function purgeTenantCache(ctx) {
71
77
  // ponytail: Redis SCAN+DEL is wired when ctx carries a redis client; until
@@ -3,6 +3,7 @@ export declare const tierAssignmentEntity: import("@cosmicdrift/kumiko-types/fie
3
3
  readonly type: "text";
4
4
  readonly description?: string;
5
5
  readonly maxLength?: number;
6
+ readonly minLength?: number;
6
7
  readonly required?: boolean;
7
8
  readonly searchable?: boolean;
8
9
  readonly sortable?: boolean;
@@ -24,6 +25,7 @@ export declare const tierAssignmentEntity: import("@cosmicdrift/kumiko-types/fie
24
25
  readonly type: "text";
25
26
  readonly description?: string;
26
27
  readonly maxLength?: number;
28
+ readonly minLength?: number;
27
29
  readonly required?: boolean;
28
30
  readonly searchable?: boolean;
29
31
  readonly sortable?: boolean;
@@ -11,6 +11,7 @@ export declare function createTierResolver<TTier extends string, TCaps>(deps: Ti
11
11
  readonly type: "text";
12
12
  readonly description?: string;
13
13
  readonly maxLength?: number;
14
+ readonly minLength?: number;
14
15
  readonly required?: boolean;
15
16
  readonly searchable?: boolean;
16
17
  readonly sortable?: boolean;
@@ -32,6 +33,7 @@ export declare function createTierResolver<TTier extends string, TCaps>(deps: Ti
32
33
  readonly type: "text";
33
34
  readonly description?: string;
34
35
  readonly maxLength?: number;
36
+ readonly minLength?: number;
35
37
  readonly required?: boolean;
36
38
  readonly searchable?: boolean;
37
39
  readonly sortable?: boolean;
@@ -2,7 +2,7 @@ import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import { access, defineEntityListHandler, } from "@cosmicdrift/kumiko-framework/engine";
3
3
  import { parseRoles } from "@cosmicdrift/kumiko-framework/utils";
4
4
  // kumiko-lint-ignore cross-feature-import SystemAdmin user-list joins memberships for tenants column
5
- import { tenantMembershipsTable, tenantTable } from "../../tenant/index.js";
5
+ import { decryptTenantNames, tenantMembershipsTable, tenantTable } from "../../tenant/index.js";
6
6
  import { userEntity } from "../schema/user.js";
7
7
  const baseList = defineEntityListHandler("user", userEntity, {
8
8
  access: { roles: access.systemAdmin },
@@ -59,7 +59,7 @@ export async function attachTenantLabels(rows, db) {
59
59
  ...new Set(memberships.map((m) => String(m.tenantId ?? "")).filter((id) => id !== "")),
60
60
  ];
61
61
  const tenants = tenantIds.length > 0
62
- ? await selectMany(db, tenantTable, { id: { in: tenantIds } })
62
+ ? await decryptTenantNames(await selectMany(db, tenantTable, { id: { in: tenantIds } }), "user:list")
63
63
  : [];
64
64
  const byUser = labelsByUserId(memberships, tenantLabelById(tenants));
65
65
  return rows.map((row) => {
@@ -32,6 +32,7 @@ export declare const userEntity: import("@cosmicdrift/kumiko-types/fields").Enti
32
32
  readonly type: "text";
33
33
  readonly description?: string;
34
34
  readonly maxLength?: number;
35
+ readonly minLength?: number;
35
36
  readonly required?: boolean;
36
37
  readonly searchable?: boolean;
37
38
  readonly sortable?: boolean;
@@ -53,6 +54,7 @@ export declare const userEntity: import("@cosmicdrift/kumiko-types/fields").Enti
53
54
  readonly type: "text";
54
55
  readonly description?: string;
55
56
  readonly maxLength?: number;
57
+ readonly minLength?: number;
56
58
  readonly required?: boolean;
57
59
  readonly searchable?: boolean;
58
60
  readonly sortable?: boolean;
@@ -74,6 +76,7 @@ export declare const userEntity: import("@cosmicdrift/kumiko-types/fields").Enti
74
76
  readonly type: "text";
75
77
  readonly description?: string;
76
78
  readonly maxLength?: number;
79
+ readonly minLength?: number;
77
80
  readonly required?: boolean;
78
81
  readonly searchable?: boolean;
79
82
  readonly sortable?: boolean;
@@ -95,6 +98,7 @@ export declare const userEntity: import("@cosmicdrift/kumiko-types/fields").Enti
95
98
  readonly type: "text";
96
99
  readonly description?: string;
97
100
  readonly maxLength?: number;
101
+ readonly minLength?: number;
98
102
  readonly required?: boolean;
99
103
  readonly searchable?: boolean;
100
104
  readonly sortable?: boolean;
@@ -125,6 +129,7 @@ export declare const userEntity: import("@cosmicdrift/kumiko-types/fields").Enti
125
129
  readonly type: "text";
126
130
  readonly description?: string;
127
131
  readonly maxLength?: number;
132
+ readonly minLength?: number;
128
133
  readonly required?: boolean;
129
134
  readonly searchable?: boolean;
130
135
  readonly sortable?: boolean;
@@ -158,10 +163,10 @@ export declare const userEntity: import("@cosmicdrift/kumiko-types/fields").Enti
158
163
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
159
164
  readonly display?: "radio" | "dropdown";
160
165
  readonly optionTones?: {
161
- readonly active?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
162
- readonly deleted?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
163
- readonly deletionRequested?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
164
- readonly restricted?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
166
+ readonly active?: "bad" | "neutral" | "ok" | "warn" | undefined;
167
+ readonly deleted?: "bad" | "neutral" | "ok" | "warn" | undefined;
168
+ readonly deletionRequested?: "bad" | "neutral" | "ok" | "warn" | undefined;
169
+ readonly restricted?: "bad" | "neutral" | "ok" | "warn" | undefined;
165
170
  } | undefined;
166
171
  readonly optionsQuery?: string;
167
172
  readonly optionsQueryPayload?: import("@cosmicdrift/kumiko-types/fields").OptionsQueryPayload;
@@ -188,6 +193,7 @@ export declare const userEntity: import("@cosmicdrift/kumiko-types/fields").Enti
188
193
  readonly type: "text";
189
194
  readonly description?: string;
190
195
  readonly maxLength?: number;
196
+ readonly minLength?: number;
191
197
  readonly required?: boolean;
192
198
  readonly searchable?: boolean;
193
199
  readonly sortable?: boolean;
@@ -211,6 +217,7 @@ export declare const userTable: import("@cosmicdrift/kumiko-framework/db").Entit
211
217
  readonly type: "text";
212
218
  readonly description?: string;
213
219
  readonly maxLength?: number;
220
+ readonly minLength?: number;
214
221
  readonly required?: boolean;
215
222
  readonly searchable?: boolean;
216
223
  readonly sortable?: boolean;
@@ -232,6 +239,7 @@ export declare const userTable: import("@cosmicdrift/kumiko-framework/db").Entit
232
239
  readonly type: "text";
233
240
  readonly description?: string;
234
241
  readonly maxLength?: number;
242
+ readonly minLength?: number;
235
243
  readonly required?: boolean;
236
244
  readonly searchable?: boolean;
237
245
  readonly sortable?: boolean;
@@ -253,6 +261,7 @@ export declare const userTable: import("@cosmicdrift/kumiko-framework/db").Entit
253
261
  readonly type: "text";
254
262
  readonly description?: string;
255
263
  readonly maxLength?: number;
264
+ readonly minLength?: number;
256
265
  readonly required?: boolean;
257
266
  readonly searchable?: boolean;
258
267
  readonly sortable?: boolean;
@@ -274,6 +283,7 @@ export declare const userTable: import("@cosmicdrift/kumiko-framework/db").Entit
274
283
  readonly type: "text";
275
284
  readonly description?: string;
276
285
  readonly maxLength?: number;
286
+ readonly minLength?: number;
277
287
  readonly required?: boolean;
278
288
  readonly searchable?: boolean;
279
289
  readonly sortable?: boolean;
@@ -304,6 +314,7 @@ export declare const userTable: import("@cosmicdrift/kumiko-framework/db").Entit
304
314
  readonly type: "text";
305
315
  readonly description?: string;
306
316
  readonly maxLength?: number;
317
+ readonly minLength?: number;
307
318
  readonly required?: boolean;
308
319
  readonly searchable?: boolean;
309
320
  readonly sortable?: boolean;
@@ -337,10 +348,10 @@ export declare const userTable: import("@cosmicdrift/kumiko-framework/db").Entit
337
348
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
338
349
  readonly display?: "radio" | "dropdown";
339
350
  readonly optionTones?: {
340
- readonly active?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
341
- readonly deleted?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
342
- readonly deletionRequested?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
343
- readonly restricted?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
351
+ readonly active?: "bad" | "neutral" | "ok" | "warn" | undefined;
352
+ readonly deleted?: "bad" | "neutral" | "ok" | "warn" | undefined;
353
+ readonly deletionRequested?: "bad" | "neutral" | "ok" | "warn" | undefined;
354
+ readonly restricted?: "bad" | "neutral" | "ok" | "warn" | undefined;
344
355
  } | undefined;
345
356
  readonly optionsQuery?: string;
346
357
  readonly optionsQueryPayload?: import("@cosmicdrift/kumiko-types/fields").OptionsQueryPayload;
@@ -367,6 +378,7 @@ export declare const userTable: import("@cosmicdrift/kumiko-framework/db").Entit
367
378
  readonly type: "text";
368
379
  readonly description?: string;
369
380
  readonly maxLength?: number;
381
+ readonly minLength?: number;
370
382
  readonly required?: boolean;
371
383
  readonly searchable?: boolean;
372
384
  readonly sortable?: boolean;
@@ -20,6 +20,11 @@ export function validateGdprHookCompleteness(features) {
20
20
  continue;
21
21
  const hasExport = typeof usage.options?.["export"] === "function";
22
22
  const hasDelete = typeof usage.options?.["delete"] === "function";
23
+ // The compile-time "at least one hook" type is bypassed by string-typed
24
+ // extension names and casts; without this an empty bag registers nothing.
25
+ if (!hasExport && !hasDelete) {
26
+ throw new Error(`[kumiko:boot] Feature "${feature.name}" registers EXT_USER_DATA for entity "${usage.entityName}" with neither an export nor a delete hook — the entity silently stays out of the Art.15/17/20 pipeline. Register at least one hook.`);
27
+ }
23
28
  if (hasExport && !hasDelete) {
24
29
  throw new Error(`[kumiko:boot] Feature "${feature.name}" exports entity "${usage.entityName}" via EXT_USER_DATA but registers no delete hook — data is included in Art.20 exports but never erased on forget (Art.17 violation). Add a delete hook. If erasure is intentionally handled elsewhere (e.g. crypto-shredding key-erase, parent cascade), register a no-op delete: async () => {} with a comment explaining why.`);
25
30
  }
@@ -28,7 +28,7 @@ async function readPendingDeletionRequestId(ctx, userId) {
28
28
  }
29
29
  // Anonymous apex flow step 2: verify-link target. Verifies the HMAC token,
30
30
  // extracts the userId, and flips the grace period through the shared logic —
31
- // in ONE atomic step with the anchor spend (#3024): commitDeletion carries
31
+ // in ONE atomic step with the anchor spend: commitDeletion carries
32
32
  // the actual grace-period transition, so a crash after the spend can no
33
33
  // longer lose the write step.
34
34
  //
@@ -38,7 +38,7 @@ async function readPendingDeletionRequestId(ctx, userId) {
38
38
  // a cancel-deletion (status → Active, pendingDeletionRequestId → null), a
39
39
  // replayed token fails against the nulled/renewed requestId — no re-arm.
40
40
  //
41
- // Concurrency (#3024): two simultaneous confirms of the same token both read
41
+ // Concurrency: two simultaneous confirms of the same token both read
42
42
  // the same requestId and both verify the HMAC — commitDeletion spends the
43
43
  // anchor AND writes the transition atomically (expect: status===Active &&
44
44
  // pendingDeletionRequestId===requestId), so exactly one wins. The loser gets
@@ -3,6 +3,7 @@ export declare const downloadAttemptEntity: import("@cosmicdrift/kumiko-types/fi
3
3
  readonly type: "text";
4
4
  readonly description?: string;
5
5
  readonly maxLength?: number;
6
+ readonly minLength?: number;
6
7
  readonly required?: boolean;
7
8
  readonly searchable?: boolean;
8
9
  readonly sortable?: boolean;
@@ -24,6 +25,7 @@ export declare const downloadAttemptEntity: import("@cosmicdrift/kumiko-types/fi
24
25
  readonly type: "text";
25
26
  readonly description?: string;
26
27
  readonly maxLength?: number;
28
+ readonly minLength?: number;
27
29
  readonly required?: boolean;
28
30
  readonly searchable?: boolean;
29
31
  readonly sortable?: boolean;
@@ -45,6 +47,7 @@ export declare const downloadAttemptEntity: import("@cosmicdrift/kumiko-types/fi
45
47
  readonly type: "text";
46
48
  readonly description?: string;
47
49
  readonly maxLength?: number;
50
+ readonly minLength?: number;
48
51
  readonly required?: boolean;
49
52
  readonly searchable?: boolean;
50
53
  readonly sortable?: boolean;
@@ -66,6 +69,7 @@ export declare const downloadAttemptEntity: import("@cosmicdrift/kumiko-types/fi
66
69
  readonly type: "text";
67
70
  readonly description?: string;
68
71
  readonly maxLength?: number;
72
+ readonly minLength?: number;
69
73
  readonly required?: boolean;
70
74
  readonly searchable?: boolean;
71
75
  readonly sortable?: boolean;
@@ -87,6 +91,7 @@ export declare const downloadAttemptEntity: import("@cosmicdrift/kumiko-types/fi
87
91
  readonly type: "text";
88
92
  readonly description?: string;
89
93
  readonly maxLength?: number;
94
+ readonly minLength?: number;
90
95
  readonly required?: boolean;
91
96
  readonly searchable?: boolean;
92
97
  readonly sortable?: boolean;
@@ -108,6 +113,7 @@ export declare const downloadAttemptEntity: import("@cosmicdrift/kumiko-types/fi
108
113
  readonly type: "text";
109
114
  readonly description?: string;
110
115
  readonly maxLength?: number;
116
+ readonly minLength?: number;
111
117
  readonly required?: boolean;
112
118
  readonly searchable?: boolean;
113
119
  readonly sortable?: boolean;
@@ -129,6 +135,7 @@ export declare const downloadAttemptEntity: import("@cosmicdrift/kumiko-types/fi
129
135
  readonly type: "text";
130
136
  readonly description?: string;
131
137
  readonly maxLength?: number;
138
+ readonly minLength?: number;
132
139
  readonly required?: boolean;
133
140
  readonly searchable?: boolean;
134
141
  readonly sortable?: boolean;
@@ -167,6 +174,7 @@ export declare const downloadAttemptsTable: import("@cosmicdrift/kumiko-framewor
167
174
  readonly type: "text";
168
175
  readonly description?: string;
169
176
  readonly maxLength?: number;
177
+ readonly minLength?: number;
170
178
  readonly required?: boolean;
171
179
  readonly searchable?: boolean;
172
180
  readonly sortable?: boolean;
@@ -188,6 +196,7 @@ export declare const downloadAttemptsTable: import("@cosmicdrift/kumiko-framewor
188
196
  readonly type: "text";
189
197
  readonly description?: string;
190
198
  readonly maxLength?: number;
199
+ readonly minLength?: number;
191
200
  readonly required?: boolean;
192
201
  readonly searchable?: boolean;
193
202
  readonly sortable?: boolean;
@@ -209,6 +218,7 @@ export declare const downloadAttemptsTable: import("@cosmicdrift/kumiko-framewor
209
218
  readonly type: "text";
210
219
  readonly description?: string;
211
220
  readonly maxLength?: number;
221
+ readonly minLength?: number;
212
222
  readonly required?: boolean;
213
223
  readonly searchable?: boolean;
214
224
  readonly sortable?: boolean;
@@ -230,6 +240,7 @@ export declare const downloadAttemptsTable: import("@cosmicdrift/kumiko-framewor
230
240
  readonly type: "text";
231
241
  readonly description?: string;
232
242
  readonly maxLength?: number;
243
+ readonly minLength?: number;
233
244
  readonly required?: boolean;
234
245
  readonly searchable?: boolean;
235
246
  readonly sortable?: boolean;
@@ -251,6 +262,7 @@ export declare const downloadAttemptsTable: import("@cosmicdrift/kumiko-framewor
251
262
  readonly type: "text";
252
263
  readonly description?: string;
253
264
  readonly maxLength?: number;
265
+ readonly minLength?: number;
254
266
  readonly required?: boolean;
255
267
  readonly searchable?: boolean;
256
268
  readonly sortable?: boolean;
@@ -272,6 +284,7 @@ export declare const downloadAttemptsTable: import("@cosmicdrift/kumiko-framewor
272
284
  readonly type: "text";
273
285
  readonly description?: string;
274
286
  readonly maxLength?: number;
287
+ readonly minLength?: number;
275
288
  readonly required?: boolean;
276
289
  readonly searchable?: boolean;
277
290
  readonly sortable?: boolean;
@@ -293,6 +306,7 @@ export declare const downloadAttemptsTable: import("@cosmicdrift/kumiko-framewor
293
306
  readonly type: "text";
294
307
  readonly description?: string;
295
308
  readonly maxLength?: number;
309
+ readonly minLength?: number;
296
310
  readonly required?: boolean;
297
311
  readonly searchable?: boolean;
298
312
  readonly sortable?: boolean;
@@ -3,6 +3,7 @@ export declare const exportDownloadTokenEntity: import("@cosmicdrift/kumiko-type
3
3
  readonly type: "text";
4
4
  readonly description?: string;
5
5
  readonly maxLength?: number;
6
+ readonly minLength?: number;
6
7
  readonly required?: boolean;
7
8
  readonly searchable?: boolean;
8
9
  readonly sortable?: boolean;
@@ -24,6 +25,7 @@ export declare const exportDownloadTokenEntity: import("@cosmicdrift/kumiko-type
24
25
  readonly type: "text";
25
26
  readonly description?: string;
26
27
  readonly maxLength?: number;
28
+ readonly minLength?: number;
27
29
  readonly required?: boolean;
28
30
  readonly searchable?: boolean;
29
31
  readonly sortable?: boolean;
@@ -102,6 +104,7 @@ export declare const exportDownloadTokenEntity: import("@cosmicdrift/kumiko-type
102
104
  readonly type: "text";
103
105
  readonly description?: string;
104
106
  readonly maxLength?: number;
107
+ readonly minLength?: number;
105
108
  readonly required?: boolean;
106
109
  readonly searchable?: boolean;
107
110
  readonly sortable?: boolean;
@@ -123,6 +126,7 @@ export declare const exportDownloadTokenEntity: import("@cosmicdrift/kumiko-type
123
126
  readonly type: "text";
124
127
  readonly description?: string;
125
128
  readonly maxLength?: number;
129
+ readonly minLength?: number;
126
130
  readonly required?: boolean;
127
131
  readonly searchable?: boolean;
128
132
  readonly sortable?: boolean;
@@ -146,6 +150,7 @@ export declare const exportDownloadTokensTable: import("@cosmicdrift/kumiko-fram
146
150
  readonly type: "text";
147
151
  readonly description?: string;
148
152
  readonly maxLength?: number;
153
+ readonly minLength?: number;
149
154
  readonly required?: boolean;
150
155
  readonly searchable?: boolean;
151
156
  readonly sortable?: boolean;
@@ -167,6 +172,7 @@ export declare const exportDownloadTokensTable: import("@cosmicdrift/kumiko-fram
167
172
  readonly type: "text";
168
173
  readonly description?: string;
169
174
  readonly maxLength?: number;
175
+ readonly minLength?: number;
170
176
  readonly required?: boolean;
171
177
  readonly searchable?: boolean;
172
178
  readonly sortable?: boolean;
@@ -245,6 +251,7 @@ export declare const exportDownloadTokensTable: import("@cosmicdrift/kumiko-fram
245
251
  readonly type: "text";
246
252
  readonly description?: string;
247
253
  readonly maxLength?: number;
254
+ readonly minLength?: number;
248
255
  readonly required?: boolean;
249
256
  readonly searchable?: boolean;
250
257
  readonly sortable?: boolean;
@@ -266,6 +273,7 @@ export declare const exportDownloadTokensTable: import("@cosmicdrift/kumiko-fram
266
273
  readonly type: "text";
267
274
  readonly description?: string;
268
275
  readonly maxLength?: number;
276
+ readonly minLength?: number;
269
277
  readonly required?: boolean;
270
278
  readonly searchable?: boolean;
271
279
  readonly sortable?: boolean;