@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
@@ -3,6 +3,7 @@ export declare const mailAccountsProjectionTable: import("@cosmicdrift/kumiko-fr
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 mailAccountsProjectionTable: import("@cosmicdrift/kumiko-fr
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 mailAccountsProjectionTable: import("@cosmicdrift/kumiko-fr
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 mailAccountsProjectionTable: import("@cosmicdrift/kumiko-fr
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 mailAccountsProjectionTable: import("@cosmicdrift/kumiko-fr
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 mailAccountsProjectionTable: import("@cosmicdrift/kumiko-fr
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 mailAccountsProjectionTable: import("@cosmicdrift/kumiko-fr
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 inboundMessagesProjectionTable: import("@cosmicdrift/kumiko
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 inboundMessagesProjectionTable: import("@cosmicdrift/kumiko
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 inboundMessagesProjectionTable: import("@cosmicdrift/kumiko
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 inboundMessagesProjectionTable: import("@cosmicdrift/kumiko
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 inboundMessagesProjectionTable: import("@cosmicdrift/kumiko
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 inboundMessagesProjectionTable: import("@cosmicdrift/kumiko
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 inboundMessagesProjectionTable: import("@cosmicdrift/kumiko
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;
@@ -314,6 +328,7 @@ export declare const inboundMessagesProjectionTable: import("@cosmicdrift/kumiko
314
328
  readonly type: "text";
315
329
  readonly description?: string;
316
330
  readonly maxLength?: number;
331
+ readonly minLength?: number;
317
332
  readonly required?: boolean;
318
333
  readonly searchable?: boolean;
319
334
  readonly sortable?: boolean;
@@ -335,6 +350,7 @@ export declare const inboundMessagesProjectionTable: import("@cosmicdrift/kumiko
335
350
  readonly type: "text";
336
351
  readonly description?: string;
337
352
  readonly maxLength?: number;
353
+ readonly minLength?: number;
338
354
  readonly required?: boolean;
339
355
  readonly searchable?: boolean;
340
356
  readonly sortable?: boolean;
@@ -371,6 +387,7 @@ export declare const inboundMessagesProjectionTable: import("@cosmicdrift/kumiko
371
387
  readonly type: "text";
372
388
  readonly description?: string;
373
389
  readonly maxLength?: number;
390
+ readonly minLength?: number;
374
391
  readonly required?: boolean;
375
392
  readonly searchable?: boolean;
376
393
  readonly sortable?: boolean;
@@ -392,6 +409,7 @@ export declare const inboundMessagesProjectionTable: import("@cosmicdrift/kumiko
392
409
  readonly type: "text";
393
410
  readonly description?: string;
394
411
  readonly maxLength?: number;
412
+ readonly minLength?: number;
395
413
  readonly required?: boolean;
396
414
  readonly searchable?: boolean;
397
415
  readonly sortable?: boolean;
@@ -415,6 +433,7 @@ export declare const mailThreadsProjectionTable: import("@cosmicdrift/kumiko-fra
415
433
  readonly type: "text";
416
434
  readonly description?: string;
417
435
  readonly maxLength?: number;
436
+ readonly minLength?: number;
418
437
  readonly required?: boolean;
419
438
  readonly searchable?: boolean;
420
439
  readonly sortable?: boolean;
@@ -436,6 +455,7 @@ export declare const mailThreadsProjectionTable: import("@cosmicdrift/kumiko-fra
436
455
  readonly type: "text";
437
456
  readonly description?: string;
438
457
  readonly maxLength?: number;
458
+ readonly minLength?: number;
439
459
  readonly required?: boolean;
440
460
  readonly searchable?: boolean;
441
461
  readonly sortable?: boolean;
@@ -28,9 +28,10 @@ export declare const listQuery: import("@cosmicdrift/kumiko-types/define-handler
28
28
  desc: "desc";
29
29
  }>>;
30
30
  limit: z.ZodOptional<z.ZodNumber>;
31
+ cursor: z.ZodOptional<z.ZodString>;
31
32
  totalCount: z.ZodOptional<z.ZodBoolean>;
32
33
  }, z.core.$strip>, {
33
34
  rows: any[];
34
- nextCursor: null;
35
+ nextCursor: string | null;
35
36
  total?: number | undefined;
36
37
  }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
@@ -1,10 +1,12 @@
1
1
  import { countWhere, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
+ import { decodeCursor, encodeCursor } from "@cosmicdrift/kumiko-framework/db";
2
3
  import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
3
- import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
4
+ import { InternalError, ValidationError } from "@cosmicdrift/kumiko-framework/errors";
4
5
  import * as z from "zod";
5
6
  import { decryptStoredPii, mapWithConcurrency } from "../../shared/index.js";
6
7
  import { jobRunsTable } from "../job-run-table.js";
7
8
  const KMS_POOL_CONCURRENCY = 4;
9
+ const DEFAULT_PAGE_SIZE = 50;
8
10
  const jobRunStatusSchema = z.enum(["queued", "running", "completed", "failed"]);
9
11
  async function decryptRunRow(row) {
10
12
  let result = row;
@@ -19,6 +21,26 @@ async function decryptRunRow(row) {
19
21
  }
20
22
  return result;
21
23
  }
24
+ function buildListWhere(payload) {
25
+ const where = {};
26
+ if (payload.jobName)
27
+ where["jobName"] = payload.jobName;
28
+ const statuses = payload.filters?.[0]?.value ?? (payload.status ? [payload.status] : []);
29
+ if (statuses.length === 1)
30
+ where["status"] = statuses[0];
31
+ else if (statuses.length > 1)
32
+ where["status"] = { in: statuses };
33
+ return where;
34
+ }
35
+ function parseOffsetCursor(cursor) {
36
+ const offset = cursor ? Number(decodeCursor(cursor)) : 0;
37
+ if (!Number.isInteger(offset) || offset < 0) {
38
+ throw new ValidationError({
39
+ fields: [{ path: "cursor", code: "invalid_cursor", i18nKey: "jobs.errors.invalidCursor" }],
40
+ });
41
+ }
42
+ return offset;
43
+ }
22
44
  export const listQuery = defineQueryHandler({
23
45
  name: "list",
24
46
  description: "Lists job runs across all tenants newest-first, optionally filtered by job name and status; use it to check whether a job ran and whether it succeeded.",
@@ -35,6 +57,7 @@ export const listQuery = defineQueryHandler({
35
57
  sort: z.enum(["jobName", "status", "startedAt", "duration"]).optional(),
36
58
  sortDirection: z.enum(["asc", "desc"]).optional(),
37
59
  limit: z.number().optional(),
60
+ cursor: z.string().optional(),
38
61
  totalCount: z.boolean().optional(),
39
62
  }),
40
63
  access: { roles: ["SystemAdmin"] },
@@ -45,27 +68,29 @@ export const listQuery = defineQueryHandler({
45
68
  });
46
69
  }
47
70
  const db = ctx.systemDb.unsafeRaw("cross-tenant job monitoring: SystemAdmin lists job runs of every tenant");
48
- const where = {};
49
- if (query.payload.jobName)
50
- where["jobName"] = query.payload.jobName;
51
- const statusFilter = query.payload.filters?.find((filter) => filter.field === "status");
52
- const statuses = statusFilter?.value ?? (query.payload.status ? [query.payload.status] : []);
53
- if (statuses.length === 1)
54
- where["status"] = statuses[0];
55
- else if (statuses.length > 1)
56
- where["status"] = { in: statuses };
71
+ const where = buildListWhere(query.payload);
57
72
  const sortColumn = query.payload.sort ?? "startedAt";
58
- const rows = await selectMany(db, jobRunsTable, where, {
59
- orderBy: { col: sortColumn, direction: query.payload.sortDirection ?? "desc" },
60
- limit: query.payload.limit ?? 50,
73
+ const limit = query.payload.limit ?? DEFAULT_PAGE_SIZE;
74
+ const offset = parseOffsetCursor(query.payload.cursor);
75
+ const direction = query.payload.sortDirection ?? "desc";
76
+ // selectMany has no OFFSET: read through the end of the page plus one probe
77
+ // row, then slice. `id` as tiebreaker keeps pages stable for non-unique sorts.
78
+ const fetched = await selectMany(db, jobRunsTable, where, {
79
+ orderBy: [
80
+ { col: sortColumn, direction },
81
+ { col: "id", direction },
82
+ ],
83
+ limit: offset + limit + 1,
61
84
  });
85
+ const hasMore = fetched.length > offset + limit;
86
+ const rows = fetched.slice(offset, offset + limit);
62
87
  // countWhere reruns the SAME `where` without the limit — `rows.length` is
63
88
  // capped at the page size and would silently undercount the total.
64
89
  const total = query.payload.totalCount === true ? await countWhere(db, jobRunsTable, where) : undefined;
65
90
  // payload/error are stored encrypted under the triggering user's DEK (#799, #2307).
66
91
  return {
67
92
  rows: await mapWithConcurrency(rows, KMS_POOL_CONCURRENCY, decryptRunRow),
68
- nextCursor: null,
93
+ nextCursor: hasMore ? encodeCursor(String(offset + limit)) : null,
69
94
  ...(total !== undefined && { total }),
70
95
  };
71
96
  },
package/dist/jobs/i18n.js CHANGED
@@ -33,6 +33,7 @@ export const JOBS_I18N = {
33
33
  "jobs.detail.logs": { en: "Logs" },
34
34
  "jobs.detail.retry": { en: "Retry" },
35
35
  "jobs.trigger.title": { en: "Run a job" },
36
+ "jobs.errors.invalidCursor": { en: "The pagination cursor is invalid." },
36
37
  "jobs.trigger.submit": { en: "Run" },
37
38
  "jobs.errors.unknownJob": { en: "Unknown job." },
38
39
  "jobs.errors.notManual": {
@@ -6,6 +6,7 @@ export declare const jobRunEntity: import("@cosmicdrift/kumiko-types/fields").En
6
6
  readonly type: "text";
7
7
  readonly description?: string;
8
8
  readonly maxLength?: number;
9
+ readonly minLength?: number;
9
10
  readonly required?: boolean;
10
11
  readonly searchable?: boolean;
11
12
  readonly sortable?: boolean;
@@ -27,6 +28,7 @@ export declare const jobRunEntity: import("@cosmicdrift/kumiko-types/fields").En
27
28
  readonly type: "text";
28
29
  readonly description?: string;
29
30
  readonly maxLength?: number;
31
+ readonly minLength?: number;
30
32
  readonly required?: boolean;
31
33
  readonly searchable?: boolean;
32
34
  readonly sortable?: boolean;
@@ -48,6 +50,7 @@ export declare const jobRunEntity: import("@cosmicdrift/kumiko-types/fields").En
48
50
  readonly type: "text";
49
51
  readonly description?: string;
50
52
  readonly maxLength?: number;
53
+ readonly minLength?: number;
51
54
  readonly required?: boolean;
52
55
  readonly searchable?: boolean;
53
56
  readonly sortable?: boolean;
@@ -69,6 +72,7 @@ export declare const jobRunEntity: import("@cosmicdrift/kumiko-types/fields").En
69
72
  readonly type: "text";
70
73
  readonly description?: string;
71
74
  readonly maxLength?: number;
75
+ readonly minLength?: number;
72
76
  readonly required?: boolean;
73
77
  readonly searchable?: boolean;
74
78
  readonly sortable?: boolean;
@@ -90,6 +94,7 @@ export declare const jobRunEntity: import("@cosmicdrift/kumiko-types/fields").En
90
94
  readonly type: "text";
91
95
  readonly description?: string;
92
96
  readonly maxLength?: number;
97
+ readonly minLength?: number;
93
98
  readonly required?: boolean;
94
99
  readonly searchable?: boolean;
95
100
  readonly sortable?: boolean;
@@ -179,6 +184,7 @@ export declare const jobRunEntity: import("@cosmicdrift/kumiko-types/fields").En
179
184
  readonly type: "text";
180
185
  readonly description?: string;
181
186
  readonly maxLength?: number;
187
+ readonly minLength?: number;
182
188
  readonly required?: boolean;
183
189
  readonly searchable?: boolean;
184
190
  readonly sortable?: boolean;
@@ -3,6 +3,7 @@ export declare const accountEntity: import("@cosmicdrift/kumiko-types/fields").E
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;
@@ -32,11 +33,11 @@ export declare const accountEntity: import("@cosmicdrift/kumiko-types/fields").E
32
33
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
33
34
  readonly display?: "radio" | "dropdown";
34
35
  readonly optionTones?: {
35
- readonly asset?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
36
- readonly equity?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
37
- readonly expense?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
38
- readonly income?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
39
- readonly liability?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
36
+ readonly asset?: "bad" | "neutral" | "ok" | "warn" | undefined;
37
+ readonly equity?: "bad" | "neutral" | "ok" | "warn" | undefined;
38
+ readonly expense?: "bad" | "neutral" | "ok" | "warn" | undefined;
39
+ readonly income?: "bad" | "neutral" | "ok" | "warn" | undefined;
40
+ readonly liability?: "bad" | "neutral" | "ok" | "warn" | undefined;
40
41
  } | undefined;
41
42
  readonly optionsQuery?: string;
42
43
  readonly optionsQueryPayload?: import("@cosmicdrift/kumiko-types/fields").OptionsQueryPayload;
@@ -48,6 +49,7 @@ export declare const accountEntity: import("@cosmicdrift/kumiko-types/fields").E
48
49
  readonly type: "text";
49
50
  readonly description?: string;
50
51
  readonly maxLength?: number;
52
+ readonly minLength?: number;
51
53
  readonly required?: boolean;
52
54
  readonly searchable?: boolean;
53
55
  readonly sortable?: boolean;
@@ -69,6 +71,7 @@ export declare const accountEntity: import("@cosmicdrift/kumiko-types/fields").E
69
71
  readonly type: "text";
70
72
  readonly description?: string;
71
73
  readonly maxLength?: number;
74
+ readonly minLength?: number;
72
75
  readonly required?: boolean;
73
76
  readonly searchable?: boolean;
74
77
  readonly sortable?: boolean;
@@ -106,6 +109,7 @@ export declare const transactionEntity: import("@cosmicdrift/kumiko-types/fields
106
109
  readonly type: "text";
107
110
  readonly description?: string;
108
111
  readonly maxLength?: number;
112
+ readonly minLength?: number;
109
113
  readonly required?: boolean;
110
114
  readonly searchable?: boolean;
111
115
  readonly sortable?: boolean;
@@ -127,6 +131,7 @@ export declare const transactionEntity: import("@cosmicdrift/kumiko-types/fields
127
131
  readonly type: "text";
128
132
  readonly description?: string;
129
133
  readonly maxLength?: number;
134
+ readonly minLength?: number;
130
135
  readonly required?: boolean;
131
136
  readonly searchable?: boolean;
132
137
  readonly sortable?: boolean;
@@ -156,8 +161,8 @@ export declare const transactionEntity: import("@cosmicdrift/kumiko-types/fields
156
161
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
157
162
  readonly display?: "radio" | "dropdown";
158
163
  readonly optionTones?: {
159
- readonly draft?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
160
- readonly posted?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
164
+ readonly draft?: "bad" | "neutral" | "ok" | "warn" | undefined;
165
+ readonly posted?: "bad" | "neutral" | "ok" | "warn" | undefined;
161
166
  } | undefined;
162
167
  readonly optionsQuery?: string;
163
168
  readonly optionsQueryPayload?: import("@cosmicdrift/kumiko-types/fields").OptionsQueryPayload;
@@ -185,6 +190,7 @@ export declare const transactionEntity: import("@cosmicdrift/kumiko-types/fields
185
190
  readonly type: "text";
186
191
  readonly description?: string;
187
192
  readonly maxLength?: number;
193
+ readonly minLength?: number;
188
194
  readonly required?: boolean;
189
195
  readonly searchable?: boolean;
190
196
  readonly sortable?: boolean;
@@ -206,6 +212,7 @@ export declare const transactionEntity: import("@cosmicdrift/kumiko-types/fields
206
212
  readonly type: "text";
207
213
  readonly description?: string;
208
214
  readonly maxLength?: number;
215
+ readonly minLength?: number;
209
216
  readonly required?: boolean;
210
217
  readonly searchable?: boolean;
211
218
  readonly sortable?: boolean;
@@ -229,6 +236,7 @@ export declare const scheduleEntity: import("@cosmicdrift/kumiko-types/fields").
229
236
  readonly type: "text";
230
237
  readonly description?: string;
231
238
  readonly maxLength?: number;
239
+ readonly minLength?: number;
232
240
  readonly required?: boolean;
233
241
  readonly searchable?: boolean;
234
242
  readonly sortable?: boolean;
@@ -286,7 +294,7 @@ export declare const scheduleEntity: import("@cosmicdrift/kumiko-types/fields").
286
294
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
287
295
  readonly display?: "radio" | "dropdown";
288
296
  readonly optionTones?: {
289
- readonly monthly?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
297
+ readonly monthly?: "bad" | "neutral" | "ok" | "warn" | undefined;
290
298
  } | undefined;
291
299
  readonly optionsQuery?: string;
292
300
  readonly optionsQueryPayload?: import("@cosmicdrift/kumiko-types/fields").OptionsQueryPayload;
@@ -317,6 +325,7 @@ export declare const scheduleEntity: import("@cosmicdrift/kumiko-types/fields").
317
325
  readonly type: "text";
318
326
  readonly description?: string;
319
327
  readonly maxLength?: number;
328
+ readonly minLength?: number;
320
329
  readonly required?: boolean;
321
330
  readonly searchable?: boolean;
322
331
  readonly sortable?: boolean;
@@ -338,6 +347,7 @@ export declare const scheduleEntity: import("@cosmicdrift/kumiko-types/fields").
338
347
  readonly type: "text";
339
348
  readonly description?: string;
340
349
  readonly maxLength?: number;
350
+ readonly minLength?: number;
341
351
  readonly required?: boolean;
342
352
  readonly searchable?: boolean;
343
353
  readonly sortable?: boolean;
@@ -359,6 +369,7 @@ export declare const scheduleEntity: import("@cosmicdrift/kumiko-types/fields").
359
369
  readonly type: "text";
360
370
  readonly description?: string;
361
371
  readonly maxLength?: number;
372
+ readonly minLength?: number;
362
373
  readonly required?: boolean;
363
374
  readonly searchable?: boolean;
364
375
  readonly sortable?: boolean;
@@ -380,6 +391,7 @@ export declare const scheduleEntity: import("@cosmicdrift/kumiko-types/fields").
380
391
  readonly type: "text";
381
392
  readonly description?: string;
382
393
  readonly maxLength?: number;
394
+ readonly minLength?: number;
383
395
  readonly required?: boolean;
384
396
  readonly searchable?: boolean;
385
397
  readonly sortable?: boolean;
@@ -401,6 +413,7 @@ export declare const scheduleEntity: import("@cosmicdrift/kumiko-types/fields").
401
413
  readonly type: "text";
402
414
  readonly description?: string;
403
415
  readonly maxLength?: number;
416
+ readonly minLength?: number;
404
417
  readonly required?: boolean;
405
418
  readonly searchable?: boolean;
406
419
  readonly sortable?: boolean;
@@ -80,12 +80,14 @@ export const transactionEntity = createEntity({
80
80
  // Optional business-object reference (e.g. a lease contract) so entries can
81
81
  // be filtered by what they're about, not just grepped out of `reference`.
82
82
  subjectType: createTextField({
83
+ minLength: 1,
83
84
  maxLength: 64,
84
85
  personal: false,
85
86
  reason: "technical_reference",
86
87
  filterable: true,
87
88
  }),
88
89
  subjectId: createTextField({
90
+ minLength: 1,
89
91
  maxLength: 128,
90
92
  personal: false,
91
93
  reason: "technical_reference",
@@ -131,12 +133,14 @@ export const scheduleEntity = createEntity({
131
133
  // Confirmed periods inherit this from the schedule (see
132
134
  // confirm-schedule-period.write.ts) rather than repeating it per period.
133
135
  subjectType: createTextField({
136
+ minLength: 1,
134
137
  maxLength: 64,
135
138
  personal: false,
136
139
  reason: "technical_reference",
137
140
  filterable: true,
138
141
  }),
139
142
  subjectId: createTextField({
143
+ minLength: 1,
140
144
  maxLength: 128,
141
145
  personal: false,
142
146
  reason: "technical_reference",
@@ -21,8 +21,14 @@ export const createTransactionPayloadSchema = z
21
21
  status: z.enum(TRANSACTION_STATUS).optional(),
22
22
  lines: z.array(postingSchema).min(2),
23
23
  // Business-object reference (e.g. a lease contract) this entry is about.
24
- subjectType: z.string().max(64).optional(),
25
- subjectId: z.string().max(128).optional(),
24
+ subjectType: z.string().min(1).max(64).optional(),
25
+ subjectId: z.string().min(1).max(128).optional(),
26
+ })
27
+ // Ids are only unique within their type, so a lone subjectId would match
28
+ // bookings of a different object type.
29
+ .refine((p) => (p.subjectType === undefined) === (p.subjectId === undefined), {
30
+ message: "subjectType and subjectId must be set together",
31
+ path: ["subjectId"],
26
32
  })
27
33
  .refine((p) => sumIsZero(p.lines), {
28
34
  message: "Transaction must balance: Σ of posting amounts must equal 0",
@@ -1,6 +1,8 @@
1
1
  import { computeRevisionEtag, etagMatches } from "@cosmicdrift/kumiko-framework/api";
2
2
  import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
3
+ import { RateLimitError } from "@cosmicdrift/kumiko-framework/errors";
3
4
  import { cachedSecurePageResponse, EMPTY_BRANDING, renderSafeMarkdown, wrapInLayout, } from "../page-render/index.js";
5
+ import { rateLimitedTextResponse } from "../shared/index.js";
4
6
  import { BRANDING_KEYS, BRANDING_QUERY_QN, CUSTOM_CSS_KEY, coerceBranding } from "./branding.js";
5
7
  import { createBrandingQuery } from "./handlers/branding.query.js";
6
8
  import { bySlugQuery } from "./handlers/by-slug.query.js";
@@ -28,14 +30,17 @@ function brandingRevisionSeed(branding) {
28
30
  branding.customCss,
29
31
  ]);
30
32
  }
31
- // Never throws: a query failure or malformed result degrades to the
32
- // unbranded default (branding is decoration, not a hard dependency of the
33
- // page render).
33
+ // A query failure or malformed result degrades to the unbranded default
34
+ // (branding is decoration, not a hard dependency of the page render). A rate
35
+ // limit is rethrown so the route answers 429 instead of serving (and caching)
36
+ // an unbranded page.
34
37
  async function readBrandingViaSystemQuery(systemQuery, tenantId) {
35
38
  try {
36
39
  return coerceBranding(await systemQuery(BRANDING_QUERY_QN, {}, tenantId));
37
40
  }
38
- catch {
41
+ catch (error) {
42
+ if (error instanceof RateLimitError)
43
+ throw error;
39
44
  return EMPTY_BRANDING;
40
45
  }
41
46
  }
@@ -125,12 +130,25 @@ export function createManagedPagesFeature(opts) {
125
130
  // von resolverTrust: "authoritative" zurecht abgelehnt; siehe
126
131
  // systemQuery's Doc in http-route.ts). Branding ist Deko → ein
127
132
  // Fehlschlag degradiert zum unbranded Default, blockt nie die Page.
128
- const [pageSettled, brandingResult] = await Promise.all([
129
- systemQuery(BY_SLUG_QN, { slug, lang }, tenantId)
130
- .then((data) => ({ ok: true, data: data }))
131
- .catch(() => ({ ok: false, data: null })),
132
- readBrandingViaSystemQuery(systemQuery, tenantId),
133
- ]);
133
+ let pageSettled;
134
+ let brandingResult;
135
+ try {
136
+ [pageSettled, brandingResult] = await Promise.all([
137
+ systemQuery(BY_SLUG_QN, { slug, lang }, tenantId)
138
+ .then((data) => ({ ok: true, data: data }))
139
+ .catch((error) => {
140
+ if (error instanceof RateLimitError)
141
+ throw error;
142
+ return { ok: false };
143
+ }),
144
+ readBrandingViaSystemQuery(systemQuery, tenantId),
145
+ ]);
146
+ }
147
+ catch (error) {
148
+ if (error instanceof RateLimitError)
149
+ return rateLimitedTextResponse(error);
150
+ throw error;
151
+ }
134
152
  if (!pageSettled.ok)
135
153
  return c.text("page unavailable", 503);
136
154
  const data = pageSettled.data;
@@ -3,6 +3,7 @@ export declare const pageEntity: import("@cosmicdrift/kumiko-types/fields").Enti
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 pageEntity: import("@cosmicdrift/kumiko-types/fields").Enti
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 pageEntity: import("@cosmicdrift/kumiko-types/fields").Enti
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 pageEntity: import("@cosmicdrift/kumiko-types/fields").Enti
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 pageEntity: import("@cosmicdrift/kumiko-types/fields").Enti
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 pageEntity: import("@cosmicdrift/kumiko-types/fields").Enti
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;
@@ -134,6 +140,7 @@ export declare const pagesTable: import("@cosmicdrift/kumiko-framework/db").Enti
134
140
  readonly type: "text";
135
141
  readonly description?: string;
136
142
  readonly maxLength?: number;
143
+ readonly minLength?: number;
137
144
  readonly required?: boolean;
138
145
  readonly searchable?: boolean;
139
146
  readonly sortable?: boolean;
@@ -155,6 +162,7 @@ export declare const pagesTable: import("@cosmicdrift/kumiko-framework/db").Enti
155
162
  readonly type: "text";
156
163
  readonly description?: string;
157
164
  readonly maxLength?: number;
165
+ readonly minLength?: number;
158
166
  readonly required?: boolean;
159
167
  readonly searchable?: boolean;
160
168
  readonly sortable?: boolean;
@@ -176,6 +184,7 @@ export declare const pagesTable: import("@cosmicdrift/kumiko-framework/db").Enti
176
184
  readonly type: "text";
177
185
  readonly description?: string;
178
186
  readonly maxLength?: number;
187
+ readonly minLength?: number;
179
188
  readonly required?: boolean;
180
189
  readonly searchable?: boolean;
181
190
  readonly sortable?: boolean;
@@ -197,6 +206,7 @@ export declare const pagesTable: import("@cosmicdrift/kumiko-framework/db").Enti
197
206
  readonly type: "text";
198
207
  readonly description?: string;
199
208
  readonly maxLength?: number;
209
+ readonly minLength?: number;
200
210
  readonly required?: boolean;
201
211
  readonly searchable?: boolean;
202
212
  readonly sortable?: boolean;
@@ -218,6 +228,7 @@ export declare const pagesTable: import("@cosmicdrift/kumiko-framework/db").Enti
218
228
  readonly type: "text";
219
229
  readonly description?: string;
220
230
  readonly maxLength?: number;
231
+ readonly minLength?: number;
221
232
  readonly required?: boolean;
222
233
  readonly searchable?: boolean;
223
234
  readonly sortable?: boolean;
@@ -239,6 +250,7 @@ export declare const pagesTable: import("@cosmicdrift/kumiko-framework/db").Enti
239
250
  readonly type: "text";
240
251
  readonly description?: string;
241
252
  readonly maxLength?: number;
253
+ readonly minLength?: number;
242
254
  readonly required?: boolean;
243
255
  readonly searchable?: boolean;
244
256
  readonly sortable?: boolean;