@cosmicdrift/kumiko-bundled-features 0.381.0 → 0.383.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 (57) hide show
  1. package/dist/audit/run-escape-hatch-retention.d.ts +1 -0
  2. package/dist/audit/run-escape-hatch-retention.js +54 -27
  3. package/dist/auth-mfa/schema/user-mfa.d.ts +6 -6
  4. package/dist/billing-foundation/entities.d.ts +8 -8
  5. package/dist/billing-foundation/projection.d.ts +8 -8
  6. package/dist/cap-counter/entity.d.ts +1 -1
  7. package/dist/cap-overview/handlers/tenant-caps-list.query.js +21 -6
  8. package/dist/compliance-profiles-ops/handlers/tenants-missing-profile.query.js +6 -4
  9. package/dist/config/table.d.ts +6 -6
  10. package/dist/custom-fields/entity.d.ts +4 -4
  11. package/dist/data-retention/schema/tenant-retention-override.d.ts +4 -4
  12. package/dist/delivery/attempt-log.d.ts +2 -0
  13. package/dist/delivery/attempt-log.js +12 -1
  14. package/dist/delivery/delivery-service.js +8 -3
  15. package/dist/delivery/feature.js +7 -9
  16. package/dist/delivery/mask-recipient-address.d.ts +1 -0
  17. package/dist/delivery/mask-recipient-address.js +16 -0
  18. package/dist/delivery/tables.d.ts +6 -6
  19. package/dist/document-ingest-foundation/entity.d.ts +4 -4
  20. package/dist/folders/entity.d.ts +8 -8
  21. package/dist/form-draft/entity.d.ts +2 -2
  22. package/dist/inbound-mail-foundation/entities.d.ts +25 -25
  23. package/dist/inbound-mail-foundation/projection.d.ts +20 -20
  24. package/dist/jobs/job-run-logger.d.ts +1 -1
  25. package/dist/jobs/job-run-logger.js +20 -0
  26. package/dist/jobs/job-run-table.d.ts +6 -6
  27. package/dist/ledger/entity.d.ts +13 -13
  28. package/dist/managed-pages/table.d.ts +12 -12
  29. package/dist/notes-history/entity.d.ts +12 -12
  30. package/dist/personal-access-tokens/schema/api-token.d.ts +6 -6
  31. package/dist/secrets/table.d.ts +1 -1
  32. package/dist/sessions/schema/user-session.d.ts +4 -4
  33. package/dist/sessions/session-callbacks.js +23 -16
  34. package/dist/tags/entity.d.ts +9 -9
  35. package/dist/template-resolver/table.d.ts +10 -10
  36. package/dist/template-resolver/user-content-table.d.ts +10 -10
  37. package/dist/tenant/decrypt-tenant-names.d.ts +4 -0
  38. package/dist/tenant/decrypt-tenant-names.js +15 -0
  39. package/dist/tenant/handlers/tenant-directory.query.js +6 -7
  40. package/dist/tenant/invitation-table.d.ts +6 -6
  41. package/dist/tenant/membership-table.d.ts +4 -4
  42. package/dist/tenant/schema/tenant.d.ts +6 -6
  43. package/dist/tier-engine/entity.d.ts +1 -1
  44. package/dist/tier-engine/tier-resolver.d.ts +1 -1
  45. package/dist/user/schema/user.d.ts +12 -12
  46. package/dist/user-data-rights/schema/download-attempt.d.ts +14 -14
  47. package/dist/user-data-rights/schema/download-token.d.ts +8 -8
  48. package/dist/user-data-rights/schema/export-job.d.ts +6 -6
  49. package/dist/waitlist/entity.d.ts +14 -14
  50. package/package.json +9 -9
  51. package/src/audit/changes.json +6 -0
  52. package/src/auth-email-password/changes.json +6 -0
  53. package/src/cap-overview/changes.json +6 -0
  54. package/src/delivery/changes.json +6 -0
  55. package/src/jobs/changes.json +7 -0
  56. package/src/sessions/changes.json +6 -0
  57. package/src/tenant/changes.json +6 -0
@@ -13,7 +13,7 @@ export declare const apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
13
13
  readonly sensitive?: boolean;
14
14
  readonly writeOnly?: boolean;
15
15
  readonly readAsInstruction?: boolean;
16
- readonly format?: "email" | "url" | "phone" | "password" | "month";
16
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
17
17
  readonly default?: string;
18
18
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
19
19
  readonly multiline?: boolean | {
@@ -36,7 +36,7 @@ export declare const apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
36
36
  readonly sensitive?: boolean;
37
37
  readonly writeOnly?: boolean;
38
38
  readonly readAsInstruction?: boolean;
39
- readonly format?: "email" | "url" | "phone" | "password" | "month";
39
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
40
40
  readonly default?: string;
41
41
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
42
42
  readonly multiline?: boolean | {
@@ -59,7 +59,7 @@ export declare const apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
59
59
  readonly sensitive?: boolean;
60
60
  readonly writeOnly?: boolean;
61
61
  readonly readAsInstruction?: boolean;
62
- readonly format?: "email" | "url" | "phone" | "password" | "month";
62
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
63
63
  readonly default?: string;
64
64
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
65
65
  readonly multiline?: boolean | {
@@ -82,7 +82,7 @@ export declare const apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
82
82
  readonly sensitive?: boolean;
83
83
  readonly writeOnly?: boolean;
84
84
  readonly readAsInstruction?: boolean;
85
- readonly format?: "email" | "url" | "phone" | "password" | "month";
85
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
86
86
  readonly default?: string;
87
87
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
88
88
  readonly multiline?: boolean | {
@@ -105,7 +105,7 @@ export declare const apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
105
105
  readonly sensitive?: boolean;
106
106
  readonly writeOnly?: boolean;
107
107
  readonly readAsInstruction?: boolean;
108
- readonly format?: "email" | "url" | "phone" | "password" | "month";
108
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
109
109
  readonly default?: string;
110
110
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
111
111
  readonly multiline?: boolean | {
@@ -128,7 +128,7 @@ export declare const apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
128
128
  readonly sensitive?: boolean;
129
129
  readonly writeOnly?: boolean;
130
130
  readonly readAsInstruction?: boolean;
131
- readonly format?: "email" | "url" | "phone" | "password" | "month";
131
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
132
132
  readonly default?: string;
133
133
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
134
134
  readonly multiline?: boolean | {
@@ -18,7 +18,7 @@ export declare const tenantSecretEntity: import("@cosmicdrift/kumiko-types/field
18
18
  readonly sensitive?: boolean;
19
19
  readonly writeOnly?: boolean;
20
20
  readonly readAsInstruction?: boolean;
21
- readonly format?: "email" | "url" | "phone" | "password" | "month";
21
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
22
22
  readonly default?: string;
23
23
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
24
24
  readonly multiline?: boolean | {
@@ -13,7 +13,7 @@ export declare const userSessionEntity: import("@cosmicdrift/kumiko-types/fields
13
13
  readonly sensitive?: boolean;
14
14
  readonly writeOnly?: boolean;
15
15
  readonly readAsInstruction?: boolean;
16
- readonly format?: "email" | "url" | "phone" | "password" | "month";
16
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
17
17
  readonly default?: string;
18
18
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
19
19
  readonly multiline?: boolean | {
@@ -36,7 +36,7 @@ export declare const userSessionEntity: import("@cosmicdrift/kumiko-types/fields
36
36
  readonly sensitive?: boolean;
37
37
  readonly writeOnly?: boolean;
38
38
  readonly readAsInstruction?: boolean;
39
- readonly format?: "email" | "url" | "phone" | "password" | "month";
39
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
40
40
  readonly default?: string;
41
41
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
42
42
  readonly multiline?: boolean | {
@@ -119,7 +119,7 @@ export declare const userSessionEntity: import("@cosmicdrift/kumiko-types/fields
119
119
  readonly sensitive?: boolean;
120
120
  readonly writeOnly?: boolean;
121
121
  readonly readAsInstruction?: boolean;
122
- readonly format?: "email" | "url" | "phone" | "password" | "month";
122
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
123
123
  readonly default?: string;
124
124
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
125
125
  readonly multiline?: boolean | {
@@ -142,7 +142,7 @@ export declare const userSessionEntity: import("@cosmicdrift/kumiko-types/fields
142
142
  readonly sensitive?: boolean;
143
143
  readonly writeOnly?: boolean;
144
144
  readonly readAsInstruction?: boolean;
145
- readonly format?: "email" | "url" | "phone" | "password" | "month";
145
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
146
146
  readonly default?: string;
147
147
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
148
148
  readonly multiline?: boolean | {
@@ -11,6 +11,9 @@ import { userSessionEntity, userSessionTable } from "./schema/user-session.js";
11
11
  import { SESSION_REVOKED_AGGREGATE_TYPE, SESSION_REVOKED_EVENT_QN, sessionRevokedSchema, } from "./session-revoked-event.js";
12
12
  // Re-exported so existing `../sessions` importers keep working unchanged.
13
13
  export { isPrincipalBlocked };
14
+ function settleLookup(lookup) {
15
+ return lookup.then((value) => ({ ok: true, value }), () => ({ ok: false }));
16
+ }
14
17
  export function createSessionCallbacks(opts) {
15
18
  const ttlMs = opts.expiresInMs ?? DEFAULT_SESSION_EXPIRY_MS;
16
19
  const { db } = opts;
@@ -69,6 +72,12 @@ export function createSessionCallbacks(opts) {
69
72
  },
70
73
  // kumiko-lint-ignore complexity-budget session check + lastSeen refresh on hot path
71
74
  async sessionChecker(sid, expectedUserId) {
75
+ // The user lookup is keyed by the JWT's userId, not the session row, so it
76
+ // overlaps the session read. Settled eagerly: a throwing session read must
77
+ // not leave this promise as an unhandled rejection.
78
+ const userLookup = settleLookup(fetchOne(db, userTable, {
79
+ id: expectedUserId,
80
+ }));
72
81
  const row = await fetchOne(db, userSessionTable, { id: sid });
73
82
  if (!row)
74
83
  return "missing";
@@ -85,6 +94,12 @@ export function createSessionCallbacks(opts) {
85
94
  if (row.expiresAt.epochMilliseconds <= Temporal.Now.instant().epochMilliseconds) {
86
95
  return "expired";
87
96
  }
97
+ // Membership is keyed by the session's tenantId, so it can only start now;
98
+ // it overlaps the user lookup and the lastSeen refresh.
99
+ const membershipLookup = settleLookup(fetchOne(db, tenantMembershipsTable, {
100
+ userId: expectedUserId,
101
+ tenantId: row.tenantId,
102
+ }));
88
103
  // Hourly, not per-request: this check runs on every authenticated
89
104
  // request, so a write here has to stay off the hot path.
90
105
  const nowMs = Temporal.Now.instant().epochMilliseconds;
@@ -110,13 +125,10 @@ export function createSessionCallbacks(opts) {
110
125
  // missing row is a legitimate "no tenant roles" outcome. Both branches
111
126
  // return the bare "live" string (no re-derived roles) — the middleware
112
127
  // falls back to the JWT's frozen roles claim.
113
- let user;
114
- try {
115
- user = await fetchOne(db, userTable, { id: expectedUserId });
116
- }
117
- catch {
128
+ const userResult = await userLookup;
129
+ if (!userResult.ok)
118
130
  return "live";
119
- }
131
+ const user = userResult.value;
120
132
  if (!user)
121
133
  return "live";
122
134
  if (isPrincipalRowBlocked(user))
@@ -125,17 +137,12 @@ export function createSessionCallbacks(opts) {
125
137
  // DB error on the membership read must not lock the user out. A
126
138
  // missing row (not a throw) is a valid outcome: the user genuinely has
127
139
  // no tenant-scoped roles for row.tenantId, so membershipRoles is [].
128
- let membershipRoles;
129
- try {
130
- const membership = await fetchOne(db, tenantMembershipsTable, {
131
- userId: expectedUserId,
132
- tenantId: row.tenantId,
133
- });
134
- membershipRoles = membership ? parseRoles(membership.roles) : [];
135
- }
136
- catch {
140
+ const membershipResult = await membershipLookup;
141
+ if (!membershipResult.ok)
137
142
  return "live";
138
- }
143
+ const membershipRoles = membershipResult.value
144
+ ? parseRoles(membershipResult.value.roles)
145
+ : [];
139
146
  const globalRoles = parseRoles(user.roles);
140
147
  const roles = buildSessionRoles(globalRoles, membershipRoles);
141
148
  return { status: "live", roles };
@@ -14,7 +14,7 @@ export declare const tagEntity: EntityDefinition<{
14
14
  readonly sensitive?: boolean;
15
15
  readonly writeOnly?: boolean;
16
16
  readonly readAsInstruction?: boolean;
17
- readonly format?: "email" | "url" | "phone" | "password" | "month";
17
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
18
18
  readonly default?: string;
19
19
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
20
20
  readonly multiline?: boolean | {
@@ -37,7 +37,7 @@ export declare const tagEntity: EntityDefinition<{
37
37
  readonly sensitive?: boolean;
38
38
  readonly writeOnly?: boolean;
39
39
  readonly readAsInstruction?: boolean;
40
- readonly format?: "email" | "url" | "phone" | "password" | "month";
40
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
41
41
  readonly default?: string;
42
42
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
43
43
  readonly multiline?: boolean | {
@@ -60,7 +60,7 @@ export declare const tagEntity: EntityDefinition<{
60
60
  readonly sensitive?: boolean;
61
61
  readonly writeOnly?: boolean;
62
62
  readonly readAsInstruction?: boolean;
63
- readonly format?: "email" | "url" | "phone" | "password" | "month";
63
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
64
64
  readonly default?: string;
65
65
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
66
66
  readonly multiline?: boolean | {
@@ -85,7 +85,7 @@ export declare function createTagAssignmentEntity(access?: EntityDefinition["acc
85
85
  readonly sensitive?: boolean;
86
86
  readonly writeOnly?: boolean;
87
87
  readonly readAsInstruction?: boolean;
88
- readonly format?: "email" | "url" | "phone" | "password" | "month";
88
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
89
89
  readonly default?: string;
90
90
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
91
91
  readonly multiline?: boolean | {
@@ -108,7 +108,7 @@ export declare function createTagAssignmentEntity(access?: EntityDefinition["acc
108
108
  readonly sensitive?: boolean;
109
109
  readonly writeOnly?: boolean;
110
110
  readonly readAsInstruction?: boolean;
111
- readonly format?: "email" | "url" | "phone" | "password" | "month";
111
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
112
112
  readonly default?: string;
113
113
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
114
114
  readonly multiline?: boolean | {
@@ -131,7 +131,7 @@ export declare function createTagAssignmentEntity(access?: EntityDefinition["acc
131
131
  readonly sensitive?: boolean;
132
132
  readonly writeOnly?: boolean;
133
133
  readonly readAsInstruction?: boolean;
134
- readonly format?: "email" | "url" | "phone" | "password" | "month";
134
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
135
135
  readonly default?: string;
136
136
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
137
137
  readonly multiline?: boolean | {
@@ -156,7 +156,7 @@ export declare const tagAssignmentEntity: EntityDefinition<{
156
156
  readonly sensitive?: boolean;
157
157
  readonly writeOnly?: boolean;
158
158
  readonly readAsInstruction?: boolean;
159
- readonly format?: "email" | "url" | "phone" | "password" | "month";
159
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
160
160
  readonly default?: string;
161
161
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
162
162
  readonly multiline?: boolean | {
@@ -179,7 +179,7 @@ export declare const tagAssignmentEntity: EntityDefinition<{
179
179
  readonly sensitive?: boolean;
180
180
  readonly writeOnly?: boolean;
181
181
  readonly readAsInstruction?: boolean;
182
- readonly format?: "email" | "url" | "phone" | "password" | "month";
182
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
183
183
  readonly default?: string;
184
184
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
185
185
  readonly multiline?: boolean | {
@@ -202,7 +202,7 @@ export declare const tagAssignmentEntity: EntityDefinition<{
202
202
  readonly sensitive?: boolean;
203
203
  readonly writeOnly?: boolean;
204
204
  readonly readAsInstruction?: boolean;
205
- readonly format?: "email" | "url" | "phone" | "password" | "month";
205
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
206
206
  readonly default?: string;
207
207
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
208
208
  readonly multiline?: boolean | {
@@ -14,7 +14,7 @@ export declare const templateResourceEntity: import("@cosmicdrift/kumiko-types/f
14
14
  readonly sensitive?: boolean;
15
15
  readonly writeOnly?: boolean;
16
16
  readonly readAsInstruction?: boolean;
17
- readonly format?: "email" | "url" | "phone" | "password" | "month";
17
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
18
18
  readonly default?: string;
19
19
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
20
20
  readonly multiline?: boolean | {
@@ -66,7 +66,7 @@ export declare const templateResourceEntity: import("@cosmicdrift/kumiko-types/f
66
66
  readonly sensitive?: boolean;
67
67
  readonly writeOnly?: boolean;
68
68
  readonly readAsInstruction?: boolean;
69
- readonly format?: "email" | "url" | "phone" | "password" | "month";
69
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
70
70
  readonly default?: string;
71
71
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
72
72
  readonly multiline?: boolean | {
@@ -89,7 +89,7 @@ export declare const templateResourceEntity: import("@cosmicdrift/kumiko-types/f
89
89
  readonly sensitive?: boolean;
90
90
  readonly writeOnly?: boolean;
91
91
  readonly readAsInstruction?: boolean;
92
- readonly format?: "email" | "url" | "phone" | "password" | "month";
92
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
93
93
  readonly default?: string;
94
94
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
95
95
  readonly multiline?: boolean | {
@@ -112,7 +112,7 @@ export declare const templateResourceEntity: import("@cosmicdrift/kumiko-types/f
112
112
  readonly sensitive?: boolean;
113
113
  readonly writeOnly?: boolean;
114
114
  readonly readAsInstruction?: boolean;
115
- readonly format?: "email" | "url" | "phone" | "password" | "month";
115
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
116
116
  readonly default?: string;
117
117
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
118
118
  readonly multiline?: boolean | {
@@ -245,7 +245,7 @@ export declare const templateResourceEntity: import("@cosmicdrift/kumiko-types/f
245
245
  readonly sensitive?: boolean;
246
246
  readonly writeOnly?: boolean;
247
247
  readonly readAsInstruction?: boolean;
248
- readonly format?: "email" | "url" | "phone" | "password" | "month";
248
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
249
249
  readonly default?: string;
250
250
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
251
251
  readonly multiline?: boolean | {
@@ -343,7 +343,7 @@ export declare const templateResourcesTable: import("@cosmicdrift/kumiko-framewo
343
343
  readonly sensitive?: boolean;
344
344
  readonly writeOnly?: boolean;
345
345
  readonly readAsInstruction?: boolean;
346
- readonly format?: "email" | "url" | "phone" | "password" | "month";
346
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
347
347
  readonly default?: string;
348
348
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
349
349
  readonly multiline?: boolean | {
@@ -395,7 +395,7 @@ export declare const templateResourcesTable: import("@cosmicdrift/kumiko-framewo
395
395
  readonly sensitive?: boolean;
396
396
  readonly writeOnly?: boolean;
397
397
  readonly readAsInstruction?: boolean;
398
- readonly format?: "email" | "url" | "phone" | "password" | "month";
398
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
399
399
  readonly default?: string;
400
400
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
401
401
  readonly multiline?: boolean | {
@@ -418,7 +418,7 @@ export declare const templateResourcesTable: import("@cosmicdrift/kumiko-framewo
418
418
  readonly sensitive?: boolean;
419
419
  readonly writeOnly?: boolean;
420
420
  readonly readAsInstruction?: boolean;
421
- readonly format?: "email" | "url" | "phone" | "password" | "month";
421
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
422
422
  readonly default?: string;
423
423
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
424
424
  readonly multiline?: boolean | {
@@ -441,7 +441,7 @@ export declare const templateResourcesTable: import("@cosmicdrift/kumiko-framewo
441
441
  readonly sensitive?: boolean;
442
442
  readonly writeOnly?: boolean;
443
443
  readonly readAsInstruction?: boolean;
444
- readonly format?: "email" | "url" | "phone" | "password" | "month";
444
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
445
445
  readonly default?: string;
446
446
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
447
447
  readonly multiline?: boolean | {
@@ -574,7 +574,7 @@ export declare const templateResourcesTable: import("@cosmicdrift/kumiko-framewo
574
574
  readonly sensitive?: boolean;
575
575
  readonly writeOnly?: boolean;
576
576
  readonly readAsInstruction?: boolean;
577
- readonly format?: "email" | "url" | "phone" | "password" | "month";
577
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
578
578
  readonly default?: string;
579
579
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
580
580
  readonly multiline?: boolean | {
@@ -14,7 +14,7 @@ export declare const userContentEntryEntity: import("@cosmicdrift/kumiko-types/f
14
14
  readonly sensitive?: boolean;
15
15
  readonly writeOnly?: boolean;
16
16
  readonly readAsInstruction?: boolean;
17
- readonly format?: "email" | "url" | "phone" | "password" | "month";
17
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
18
18
  readonly default?: string;
19
19
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
20
20
  readonly multiline?: boolean | {
@@ -37,7 +37,7 @@ export declare const userContentEntryEntity: import("@cosmicdrift/kumiko-types/f
37
37
  readonly sensitive?: boolean;
38
38
  readonly writeOnly?: boolean;
39
39
  readonly readAsInstruction?: boolean;
40
- readonly format?: "email" | "url" | "phone" | "password" | "month";
40
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
41
41
  readonly default?: string;
42
42
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
43
43
  readonly multiline?: boolean | {
@@ -89,7 +89,7 @@ export declare const userContentEntryEntity: import("@cosmicdrift/kumiko-types/f
89
89
  readonly sensitive?: boolean;
90
90
  readonly writeOnly?: boolean;
91
91
  readonly readAsInstruction?: boolean;
92
- readonly format?: "email" | "url" | "phone" | "password" | "month";
92
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
93
93
  readonly default?: string;
94
94
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
95
95
  readonly multiline?: boolean | {
@@ -112,7 +112,7 @@ export declare const userContentEntryEntity: import("@cosmicdrift/kumiko-types/f
112
112
  readonly sensitive?: boolean;
113
113
  readonly writeOnly?: boolean;
114
114
  readonly readAsInstruction?: boolean;
115
- readonly format?: "email" | "url" | "phone" | "password" | "month";
115
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
116
116
  readonly default?: string;
117
117
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
118
118
  readonly multiline?: boolean | {
@@ -135,7 +135,7 @@ export declare const userContentEntryEntity: import("@cosmicdrift/kumiko-types/f
135
135
  readonly sensitive?: boolean;
136
136
  readonly writeOnly?: boolean;
137
137
  readonly readAsInstruction?: boolean;
138
- readonly format?: "email" | "url" | "phone" | "password" | "month";
138
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
139
139
  readonly default?: string;
140
140
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
141
141
  readonly multiline?: boolean | {
@@ -200,7 +200,7 @@ export declare const userContentEntriesTable: import("@cosmicdrift/kumiko-framew
200
200
  readonly sensitive?: boolean;
201
201
  readonly writeOnly?: boolean;
202
202
  readonly readAsInstruction?: boolean;
203
- readonly format?: "email" | "url" | "phone" | "password" | "month";
203
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
204
204
  readonly default?: string;
205
205
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
206
206
  readonly multiline?: boolean | {
@@ -223,7 +223,7 @@ export declare const userContentEntriesTable: import("@cosmicdrift/kumiko-framew
223
223
  readonly sensitive?: boolean;
224
224
  readonly writeOnly?: boolean;
225
225
  readonly readAsInstruction?: boolean;
226
- readonly format?: "email" | "url" | "phone" | "password" | "month";
226
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
227
227
  readonly default?: string;
228
228
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
229
229
  readonly multiline?: boolean | {
@@ -275,7 +275,7 @@ export declare const userContentEntriesTable: import("@cosmicdrift/kumiko-framew
275
275
  readonly sensitive?: boolean;
276
276
  readonly writeOnly?: boolean;
277
277
  readonly readAsInstruction?: boolean;
278
- readonly format?: "email" | "url" | "phone" | "password" | "month";
278
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
279
279
  readonly default?: string;
280
280
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
281
281
  readonly multiline?: boolean | {
@@ -298,7 +298,7 @@ export declare const userContentEntriesTable: import("@cosmicdrift/kumiko-framew
298
298
  readonly sensitive?: boolean;
299
299
  readonly writeOnly?: boolean;
300
300
  readonly readAsInstruction?: boolean;
301
- readonly format?: "email" | "url" | "phone" | "password" | "month";
301
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
302
302
  readonly default?: string;
303
303
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
304
304
  readonly multiline?: boolean | {
@@ -321,7 +321,7 @@ export declare const userContentEntriesTable: import("@cosmicdrift/kumiko-framew
321
321
  readonly sensitive?: boolean;
322
322
  readonly writeOnly?: boolean;
323
323
  readonly readAsInstruction?: boolean;
324
- readonly format?: "email" | "url" | "phone" | "password" | "month";
324
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
325
325
  readonly default?: string;
326
326
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
327
327
  readonly multiline?: boolean | {
@@ -1,3 +1,7 @@
1
1
  export declare function decryptTenantNames<T extends {
2
2
  readonly name?: unknown;
3
3
  }>(rows: readonly T[], fallbackRequestId: string): Promise<T[]>;
4
+ export declare function takeTenantsMatchingDecryptedName<T extends {
5
+ readonly id: unknown;
6
+ readonly name?: unknown;
7
+ }>(rows: readonly T[], labelMatches: (label: string) => boolean, limit: number, fallbackRequestId: string): Promise<T[]>;
@@ -8,3 +8,18 @@ export async function decryptTenantNames(rows, fallbackRequestId) {
8
8
  ? { ...row, name: await decryptStoredPii(row.name, "name", fallbackRequestId) }
9
9
  : row);
10
10
  }
11
+ // Tenant names are not in the search index (find: "none"), so a name search has to decrypt;
12
+ // stopping at `limit` matches keeps a typed prefix from decrypting the whole scan window.
13
+ export async function takeTenantsMatchingDecryptedName(rows, labelMatches, limit, fallbackRequestId) {
14
+ const batchSize = Math.max(limit, KMS_POOL_CONCURRENCY);
15
+ const matches = [];
16
+ for (let start = 0; start < rows.length && matches.length < limit; start += batchSize) {
17
+ const decrypted = await decryptTenantNames(rows.slice(start, start + batchSize), fallbackRequestId);
18
+ for (const row of decrypted) {
19
+ const label = typeof row.name === "string" ? row.name : String(row.id);
20
+ if (labelMatches(label))
21
+ matches.push(row);
22
+ }
23
+ }
24
+ return matches.slice(0, limit);
25
+ }
@@ -2,7 +2,7 @@ import { fetchOne, selectMany } from "@cosmicdrift/kumiko-framework/db";
2
2
  import { access, definePagedQueryHandler, MAX_LIST_LIMIT, } from "@cosmicdrift/kumiko-framework/engine";
3
3
  import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
4
4
  import * as z from "zod";
5
- import { decryptTenantNames } from "../decrypt-tenant-names.js";
5
+ import { decryptTenantNames, takeTenantsMatchingDecryptedName } from "../decrypt-tenant-names.js";
6
6
  import { tenantTable } from "../schema/tenant.js";
7
7
  import { isSystemAdmin } from "./is-system-admin.js";
8
8
  // Label source behind every `tenant:tenant` reference column (fw#3142). The
@@ -51,17 +51,16 @@ export const tenantDirectoryQuery = definePagedQueryHandler({
51
51
  }
52
52
  // name is ciphertext at rest, so the SQL page is keyed by the plaintext
53
53
  // `key`; label order and search happen in memory after decrypting.
54
- const decrypted = await decryptTenantNames(tenants, "tenant:tenant-directory");
55
- const labeled = decrypted
54
+ const needle = search?.toLowerCase();
55
+ const named = needle === undefined
56
+ ? await decryptTenantNames(tenants, "tenant:tenant-directory")
57
+ : await takeTenantsMatchingDecryptedName(tenants, (label) => label.toLowerCase().includes(needle), limit, "tenant:tenant-directory");
58
+ const rows = named
56
59
  .map((tenant) => {
57
60
  const id = String(tenant.id);
58
61
  return { id, label: typeof tenant.name === "string" ? tenant.name : id };
59
62
  })
60
63
  .sort((a, b) => a.label.localeCompare(b.label) || a.id.localeCompare(b.id));
61
- const needle = search?.toLowerCase();
62
- const rows = needle === undefined
63
- ? labeled
64
- : labeled.filter((row) => row.label.toLowerCase().includes(needle)).slice(0, limit);
65
64
  return { rows, nextCursor: null };
66
65
  },
67
66
  });
@@ -21,7 +21,7 @@ export declare const tenantInvitationEntity: import("@cosmicdrift/kumiko-types/f
21
21
  readonly sensitive?: boolean;
22
22
  readonly writeOnly?: boolean;
23
23
  readonly readAsInstruction?: boolean;
24
- readonly format?: "email" | "url" | "phone" | "password" | "month";
24
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
25
25
  readonly default?: string;
26
26
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
27
27
  readonly multiline?: boolean | {
@@ -44,7 +44,7 @@ export declare const tenantInvitationEntity: import("@cosmicdrift/kumiko-types/f
44
44
  readonly sensitive?: boolean;
45
45
  readonly writeOnly?: boolean;
46
46
  readonly readAsInstruction?: boolean;
47
- readonly format?: "email" | "url" | "phone" | "password" | "month";
47
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
48
48
  readonly default?: string;
49
49
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
50
50
  readonly multiline?: boolean | {
@@ -92,7 +92,7 @@ export declare const tenantInvitationEntity: import("@cosmicdrift/kumiko-types/f
92
92
  readonly sensitive?: boolean;
93
93
  readonly writeOnly?: boolean;
94
94
  readonly readAsInstruction?: boolean;
95
- readonly format?: "email" | "url" | "phone" | "password" | "month";
95
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
96
96
  readonly default?: string;
97
97
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
98
98
  readonly multiline?: boolean | {
@@ -151,7 +151,7 @@ export declare const tenantInvitationsTable: import("@cosmicdrift/kumiko-framewo
151
151
  readonly sensitive?: boolean;
152
152
  readonly writeOnly?: boolean;
153
153
  readonly readAsInstruction?: boolean;
154
- readonly format?: "email" | "url" | "phone" | "password" | "month";
154
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
155
155
  readonly default?: string;
156
156
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
157
157
  readonly multiline?: boolean | {
@@ -174,7 +174,7 @@ export declare const tenantInvitationsTable: import("@cosmicdrift/kumiko-framewo
174
174
  readonly sensitive?: boolean;
175
175
  readonly writeOnly?: boolean;
176
176
  readonly readAsInstruction?: boolean;
177
- readonly format?: "email" | "url" | "phone" | "password" | "month";
177
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
178
178
  readonly default?: string;
179
179
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
180
180
  readonly multiline?: boolean | {
@@ -222,7 +222,7 @@ export declare const tenantInvitationsTable: import("@cosmicdrift/kumiko-framewo
222
222
  readonly sensitive?: boolean;
223
223
  readonly writeOnly?: boolean;
224
224
  readonly readAsInstruction?: boolean;
225
- readonly format?: "email" | "url" | "phone" | "password" | "month";
225
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
226
226
  readonly default?: string;
227
227
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
228
228
  readonly multiline?: boolean | {
@@ -13,7 +13,7 @@ export declare const tenantMembershipEntity: import("@cosmicdrift/kumiko-types/f
13
13
  readonly sensitive?: boolean;
14
14
  readonly writeOnly?: boolean;
15
15
  readonly readAsInstruction?: boolean;
16
- readonly format?: "email" | "url" | "phone" | "password" | "month";
16
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
17
17
  readonly default?: string;
18
18
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
19
19
  readonly multiline?: boolean | {
@@ -36,7 +36,7 @@ export declare const tenantMembershipEntity: import("@cosmicdrift/kumiko-types/f
36
36
  readonly sensitive?: boolean;
37
37
  readonly writeOnly?: boolean;
38
38
  readonly readAsInstruction?: boolean;
39
- readonly format?: "email" | "url" | "phone" | "password" | "month";
39
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
40
40
  readonly default?: string;
41
41
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
42
42
  readonly multiline?: boolean | {
@@ -61,7 +61,7 @@ export declare const tenantMembershipsTable: import("@cosmicdrift/kumiko-framewo
61
61
  readonly sensitive?: boolean;
62
62
  readonly writeOnly?: boolean;
63
63
  readonly readAsInstruction?: boolean;
64
- readonly format?: "email" | "url" | "phone" | "password" | "month";
64
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
65
65
  readonly default?: string;
66
66
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
67
67
  readonly multiline?: boolean | {
@@ -84,7 +84,7 @@ export declare const tenantMembershipsTable: import("@cosmicdrift/kumiko-framewo
84
84
  readonly sensitive?: boolean;
85
85
  readonly writeOnly?: boolean;
86
86
  readonly readAsInstruction?: boolean;
87
- readonly format?: "email" | "url" | "phone" | "password" | "month";
87
+ readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
88
88
  readonly default?: string;
89
89
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
90
90
  readonly multiline?: boolean | {