@cosmicdrift/kumiko-bundled-features 0.351.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 (164) 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/book-cap-usage.d.ts +5 -1
  16. package/dist/cap-counter/book-cap-usage.js +65 -44
  17. package/dist/cap-counter/cap-reservation.d.ts +12 -0
  18. package/dist/cap-counter/cap-reservation.js +135 -0
  19. package/dist/cap-counter/constants.d.ts +3 -0
  20. package/dist/cap-counter/constants.js +6 -0
  21. package/dist/cap-counter/entity.d.ts +1 -0
  22. package/dist/cap-counter/feature.js +7 -2
  23. package/dist/cap-counter/handlers/increment-rolling.write.d.ts +5 -0
  24. package/dist/cap-counter/handlers/increment-rolling.write.js +4 -0
  25. package/dist/cap-counter/index.d.ts +4 -2
  26. package/dist/cap-counter/index.js +3 -1
  27. package/dist/cap-counter/payload-field.d.ts +1 -0
  28. package/dist/cap-counter/payload-field.js +11 -0
  29. package/dist/cap-counter/reservation-chain.d.ts +2 -0
  30. package/dist/cap-counter/reservation-chain.js +44 -0
  31. package/dist/cap-counter/stock-cap-guard.d.ts +3 -0
  32. package/dist/cap-counter/tables.d.ts +3 -0
  33. package/dist/cap-counter/tables.js +36 -0
  34. package/dist/cap-counter/value-cap-guard.d.ts +18 -0
  35. package/dist/cap-counter/value-cap-guard.js +33 -0
  36. package/dist/cap-counter/with-cap-enforcement.d.ts +5 -7
  37. package/dist/cap-counter/with-cap-enforcement.js +35 -51
  38. package/dist/cap-overview/access.js +2 -9
  39. package/dist/cap-overview/handlers/tenant-caps-list.query.js +2 -2
  40. package/dist/cap-overview/handlers/tenant-options.query.js +4 -5
  41. package/dist/channel-discord/feature.js +1 -0
  42. package/dist/channel-slack/feature.js +1 -0
  43. package/dist/channel-teams/feature.js +1 -0
  44. package/dist/compliance-profiles/schema/profile-selection.d.ts +10 -8
  45. package/dist/compliance-profiles-ops/handlers/tenants-missing-profile.query.js +2 -4
  46. package/dist/config/handlers/values.query.d.ts +3 -1
  47. package/dist/config/handlers/values.query.js +7 -2
  48. package/dist/config/table.d.ts +6 -0
  49. package/dist/custom-fields/entity.d.ts +4 -0
  50. package/dist/data-retention/schema/tenant-retention-override.d.ts +4 -0
  51. package/dist/delivery/attempt-log-retention.d.ts +9 -0
  52. package/dist/delivery/attempt-log-retention.js +23 -0
  53. package/dist/delivery/attempt-log.d.ts +1 -1
  54. package/dist/delivery/attempt-log.js +21 -2
  55. package/dist/delivery/constants.d.ts +1 -1
  56. package/dist/delivery/constants.js +1 -1
  57. package/dist/delivery/events.d.ts +1 -0
  58. package/dist/delivery/events.js +2 -0
  59. package/dist/delivery/feature.d.ts +1 -0
  60. package/dist/delivery/feature.js +39 -3
  61. package/dist/delivery/index.d.ts +2 -1
  62. package/dist/delivery/index.js +2 -1
  63. package/dist/delivery/mask-recipient-address.d.ts +1 -0
  64. package/dist/delivery/mask-recipient-address.js +41 -0
  65. package/dist/delivery/public-names.d.ts +2 -0
  66. package/dist/delivery/public-names.js +4 -1
  67. package/dist/delivery/tables.d.ts +6 -0
  68. package/dist/delivery/types.d.ts +1 -0
  69. package/dist/delivery/types.js +6 -2
  70. package/dist/document-ingest-foundation/entity.d.ts +4 -0
  71. package/dist/files-tenant-data/hooks.js +3 -2
  72. package/dist/folders/entity.d.ts +8 -0
  73. package/dist/folders/feature.js +2 -10
  74. package/dist/form-draft/db/queries/cleanup.d.ts +1 -1
  75. package/dist/form-draft/db/queries/cleanup.js +4 -3
  76. package/dist/form-draft/entity.d.ts +2 -0
  77. package/dist/form-draft/handlers/cleanup.job.d.ts +5 -1
  78. package/dist/form-draft/handlers/cleanup.job.js +11 -5
  79. package/dist/inbound-mail-foundation/entities.d.ts +25 -0
  80. package/dist/inbound-mail-foundation/projection.d.ts +20 -0
  81. package/dist/jobs/handlers/list.query.d.ts +2 -1
  82. package/dist/jobs/handlers/list.query.js +39 -14
  83. package/dist/jobs/i18n.js +1 -0
  84. package/dist/jobs/job-run-table.d.ts +6 -0
  85. package/dist/ledger/entity.d.ts +24 -8
  86. package/dist/ledger/entity.js +4 -0
  87. package/dist/ledger/schemas.js +8 -2
  88. package/dist/managed-pages/feature.js +28 -10
  89. package/dist/managed-pages/table.d.ts +12 -0
  90. package/dist/notes-history/entity.d.ts +12 -0
  91. package/dist/notes-history/feature.js +2 -10
  92. package/dist/personal-access-tokens/schema/api-token.d.ts +6 -0
  93. package/dist/personal-access-tokens/screens.js +14 -3
  94. package/dist/renderer-foundation/types.d.ts +1 -0
  95. package/dist/renderer-simple/feature.js +4 -6
  96. package/dist/renderer-simple/index.d.ts +1 -0
  97. package/dist/renderer-simple/mail-blocks.d.ts +21 -0
  98. package/dist/renderer-simple/mail-blocks.js +107 -0
  99. package/dist/renderer-simple/simple-renderer.js +21 -3
  100. package/dist/secrets/table.d.ts +1 -0
  101. package/dist/seo/feature.js +28 -6
  102. package/dist/sessions/schema/user-session.d.ts +4 -0
  103. package/dist/shared/assert-no-where-rule-in-ownership-write.d.ts +2 -0
  104. package/dist/shared/assert-no-where-rule-in-ownership-write.js +18 -0
  105. package/dist/shared/index.d.ts +2 -1
  106. package/dist/shared/index.js +2 -1
  107. package/dist/shared/rate-limited-text-response.d.ts +2 -0
  108. package/dist/shared/rate-limited-text-response.js +9 -0
  109. package/dist/tags/entity.d.ts +9 -0
  110. package/dist/tags/feature.js +2 -10
  111. package/dist/template-resolver/table.d.ts +48 -30
  112. package/dist/template-resolver/user-content-table.d.ts +34 -20
  113. package/dist/tenant/decrypt-tenant-names.d.ts +3 -0
  114. package/dist/tenant/decrypt-tenant-names.js +10 -0
  115. package/dist/tenant/handlers/me.query.js +5 -1
  116. package/dist/tenant/handlers/memberships.query.js +3 -2
  117. package/dist/tenant/handlers/tenant-directory.query.js +9 -3
  118. package/dist/tenant/index.d.ts +1 -0
  119. package/dist/tenant/index.js +1 -0
  120. package/dist/tenant/invitation-table.d.ts +16 -8
  121. package/dist/tenant/membership-table.d.ts +4 -0
  122. package/dist/tenant/schema/tenant.d.ts +18 -10
  123. package/dist/tenant/schema/tenant.js +4 -6
  124. package/dist/tenant/screens.js +1 -1
  125. package/dist/tenant-handover/move-entity-graph.js +11 -18
  126. package/dist/tenant-lifecycle/feature.js +1 -0
  127. package/dist/tenant-lifecycle/run-tenant-destroy.d.ts +3 -0
  128. package/dist/tenant-lifecycle/run-tenant-destroy.js +2 -0
  129. package/dist/tenant-lifecycle/stages.d.ts +2 -0
  130. package/dist/tenant-lifecycle/stages.js +7 -1
  131. package/dist/tier-engine/entity.d.ts +2 -0
  132. package/dist/tier-engine/index.d.ts +1 -0
  133. package/dist/tier-engine/index.js +1 -0
  134. package/dist/tier-engine/tier-option-gate.d.ts +32 -0
  135. package/dist/tier-engine/tier-option-gate.js +62 -0
  136. package/dist/tier-engine/tier-resolver.d.ts +2 -0
  137. package/dist/user/handlers/list.query.js +2 -2
  138. package/dist/user/schema/user.d.ts +22 -8
  139. package/dist/user-data-rights/boot-checks.js +5 -0
  140. package/dist/user-data-rights/handlers/confirm-deletion-by-token.write.js +2 -2
  141. package/dist/user-data-rights/schema/download-attempt.d.ts +14 -0
  142. package/dist/user-data-rights/schema/download-token.d.ts +8 -0
  143. package/dist/user-data-rights/schema/export-job.d.ts +16 -8
  144. package/dist/user-data-rights-defaults/hooks/delivery-attempt.userdata-hook.js +25 -6
  145. package/dist/waitlist/entity.d.ts +22 -6
  146. package/dist/workflow-runner/event-trigger.js +6 -3
  147. package/dist/workflow-runner/handlers/resume-run.write.js +3 -0
  148. package/package.json +10 -10
  149. package/src/agent-tools/changes.json +12 -0
  150. package/src/audit/changes.json +6 -0
  151. package/src/cap-counter/changes.json +28 -0
  152. package/src/delivery/changes.json +12 -0
  153. package/src/derivatives-sharp/changes.json +6 -0
  154. package/src/form-draft/changes.json +7 -1
  155. package/src/legal-pages/changes.json +6 -0
  156. package/src/managed-pages/changes.json +6 -0
  157. package/src/renderer-simple/changes.json +6 -0
  158. package/src/seo/changes.json +5 -0
  159. package/src/tenant/changes.json +7 -0
  160. package/src/tenant-handover/changes.json +6 -0
  161. package/src/tier-engine/changes.json +6 -0
  162. package/src/workflow-runner/changes.json +5 -0
  163. package/dist/shared/has-where-rule.d.ts +0 -2
  164. package/dist/shared/has-where-rule.js +0 -10
@@ -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,14 +33,15 @@ 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;
44
+ readonly optionsAvailabilityQuery?: string;
43
45
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
44
46
  required: true;
45
47
  };
@@ -47,6 +49,7 @@ export declare const accountEntity: import("@cosmicdrift/kumiko-types/fields").E
47
49
  readonly type: "text";
48
50
  readonly description?: string;
49
51
  readonly maxLength?: number;
52
+ readonly minLength?: number;
50
53
  readonly required?: boolean;
51
54
  readonly searchable?: boolean;
52
55
  readonly sortable?: boolean;
@@ -68,6 +71,7 @@ export declare const accountEntity: import("@cosmicdrift/kumiko-types/fields").E
68
71
  readonly type: "text";
69
72
  readonly description?: string;
70
73
  readonly maxLength?: number;
74
+ readonly minLength?: number;
71
75
  readonly required?: boolean;
72
76
  readonly searchable?: boolean;
73
77
  readonly sortable?: boolean;
@@ -105,6 +109,7 @@ export declare const transactionEntity: import("@cosmicdrift/kumiko-types/fields
105
109
  readonly type: "text";
106
110
  readonly description?: string;
107
111
  readonly maxLength?: number;
112
+ readonly minLength?: number;
108
113
  readonly required?: boolean;
109
114
  readonly searchable?: boolean;
110
115
  readonly sortable?: boolean;
@@ -126,6 +131,7 @@ export declare const transactionEntity: import("@cosmicdrift/kumiko-types/fields
126
131
  readonly type: "text";
127
132
  readonly description?: string;
128
133
  readonly maxLength?: number;
134
+ readonly minLength?: number;
129
135
  readonly required?: boolean;
130
136
  readonly searchable?: boolean;
131
137
  readonly sortable?: boolean;
@@ -155,11 +161,12 @@ export declare const transactionEntity: import("@cosmicdrift/kumiko-types/fields
155
161
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
156
162
  readonly display?: "radio" | "dropdown";
157
163
  readonly optionTones?: {
158
- readonly draft?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
159
- 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;
160
166
  } | undefined;
161
167
  readonly optionsQuery?: string;
162
168
  readonly optionsQueryPayload?: import("@cosmicdrift/kumiko-types/fields").OptionsQueryPayload;
169
+ readonly optionsAvailabilityQuery?: string;
163
170
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
164
171
  required: true;
165
172
  };
@@ -183,6 +190,7 @@ export declare const transactionEntity: import("@cosmicdrift/kumiko-types/fields
183
190
  readonly type: "text";
184
191
  readonly description?: string;
185
192
  readonly maxLength?: number;
193
+ readonly minLength?: number;
186
194
  readonly required?: boolean;
187
195
  readonly searchable?: boolean;
188
196
  readonly sortable?: boolean;
@@ -204,6 +212,7 @@ export declare const transactionEntity: import("@cosmicdrift/kumiko-types/fields
204
212
  readonly type: "text";
205
213
  readonly description?: string;
206
214
  readonly maxLength?: number;
215
+ readonly minLength?: number;
207
216
  readonly required?: boolean;
208
217
  readonly searchable?: boolean;
209
218
  readonly sortable?: boolean;
@@ -227,6 +236,7 @@ export declare const scheduleEntity: import("@cosmicdrift/kumiko-types/fields").
227
236
  readonly type: "text";
228
237
  readonly description?: string;
229
238
  readonly maxLength?: number;
239
+ readonly minLength?: number;
230
240
  readonly required?: boolean;
231
241
  readonly searchable?: boolean;
232
242
  readonly sortable?: boolean;
@@ -284,10 +294,11 @@ export declare const scheduleEntity: import("@cosmicdrift/kumiko-types/fields").
284
294
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
285
295
  readonly display?: "radio" | "dropdown";
286
296
  readonly optionTones?: {
287
- readonly monthly?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
297
+ readonly monthly?: "bad" | "neutral" | "ok" | "warn" | undefined;
288
298
  } | undefined;
289
299
  readonly optionsQuery?: string;
290
300
  readonly optionsQueryPayload?: import("@cosmicdrift/kumiko-types/fields").OptionsQueryPayload;
301
+ readonly optionsAvailabilityQuery?: string;
291
302
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
292
303
  required: true;
293
304
  };
@@ -314,6 +325,7 @@ export declare const scheduleEntity: import("@cosmicdrift/kumiko-types/fields").
314
325
  readonly type: "text";
315
326
  readonly description?: string;
316
327
  readonly maxLength?: number;
328
+ readonly minLength?: number;
317
329
  readonly required?: boolean;
318
330
  readonly searchable?: boolean;
319
331
  readonly sortable?: boolean;
@@ -335,6 +347,7 @@ export declare const scheduleEntity: import("@cosmicdrift/kumiko-types/fields").
335
347
  readonly type: "text";
336
348
  readonly description?: string;
337
349
  readonly maxLength?: number;
350
+ readonly minLength?: number;
338
351
  readonly required?: boolean;
339
352
  readonly searchable?: boolean;
340
353
  readonly sortable?: boolean;
@@ -356,6 +369,7 @@ export declare const scheduleEntity: import("@cosmicdrift/kumiko-types/fields").
356
369
  readonly type: "text";
357
370
  readonly description?: string;
358
371
  readonly maxLength?: number;
372
+ readonly minLength?: number;
359
373
  readonly required?: boolean;
360
374
  readonly searchable?: boolean;
361
375
  readonly sortable?: boolean;
@@ -377,6 +391,7 @@ export declare const scheduleEntity: import("@cosmicdrift/kumiko-types/fields").
377
391
  readonly type: "text";
378
392
  readonly description?: string;
379
393
  readonly maxLength?: number;
394
+ readonly minLength?: number;
380
395
  readonly required?: boolean;
381
396
  readonly searchable?: boolean;
382
397
  readonly sortable?: boolean;
@@ -398,6 +413,7 @@ export declare const scheduleEntity: import("@cosmicdrift/kumiko-types/fields").
398
413
  readonly type: "text";
399
414
  readonly description?: string;
400
415
  readonly maxLength?: number;
416
+ readonly minLength?: number;
401
417
  readonly required?: boolean;
402
418
  readonly searchable?: boolean;
403
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;
@@ -4,6 +4,7 @@ export declare function createNoteEntryEntity(access?: EntityDefinition["access"
4
4
  readonly type: "text";
5
5
  readonly description?: string;
6
6
  readonly maxLength?: number;
7
+ readonly minLength?: number;
7
8
  readonly required?: boolean;
8
9
  readonly searchable?: boolean;
9
10
  readonly sortable?: boolean;
@@ -25,6 +26,7 @@ export declare function createNoteEntryEntity(access?: EntityDefinition["access"
25
26
  readonly type: "text";
26
27
  readonly description?: string;
27
28
  readonly maxLength?: number;
29
+ readonly minLength?: number;
28
30
  readonly required?: boolean;
29
31
  readonly searchable?: boolean;
30
32
  readonly sortable?: boolean;
@@ -46,6 +48,7 @@ export declare function createNoteEntryEntity(access?: EntityDefinition["access"
46
48
  readonly type: "text";
47
49
  readonly description?: string;
48
50
  readonly maxLength?: number;
51
+ readonly minLength?: number;
49
52
  readonly required?: boolean;
50
53
  readonly searchable?: boolean;
51
54
  readonly sortable?: boolean;
@@ -67,6 +70,7 @@ export declare function createNoteEntryEntity(access?: EntityDefinition["access"
67
70
  readonly type: "text";
68
71
  readonly description?: string;
69
72
  readonly maxLength?: number;
73
+ readonly minLength?: number;
70
74
  readonly required?: boolean;
71
75
  readonly searchable?: boolean;
72
76
  readonly sortable?: boolean;
@@ -106,6 +110,7 @@ export declare const noteEntryEntity: EntityDefinition<{
106
110
  readonly type: "text";
107
111
  readonly description?: string;
108
112
  readonly maxLength?: number;
113
+ readonly minLength?: number;
109
114
  readonly required?: boolean;
110
115
  readonly searchable?: boolean;
111
116
  readonly sortable?: boolean;
@@ -127,6 +132,7 @@ export declare const noteEntryEntity: EntityDefinition<{
127
132
  readonly type: "text";
128
133
  readonly description?: string;
129
134
  readonly maxLength?: number;
135
+ readonly minLength?: number;
130
136
  readonly required?: boolean;
131
137
  readonly searchable?: boolean;
132
138
  readonly sortable?: boolean;
@@ -148,6 +154,7 @@ export declare const noteEntryEntity: EntityDefinition<{
148
154
  readonly type: "text";
149
155
  readonly description?: string;
150
156
  readonly maxLength?: number;
157
+ readonly minLength?: number;
151
158
  readonly required?: boolean;
152
159
  readonly searchable?: boolean;
153
160
  readonly sortable?: boolean;
@@ -169,6 +176,7 @@ export declare const noteEntryEntity: EntityDefinition<{
169
176
  readonly type: "text";
170
177
  readonly description?: string;
171
178
  readonly maxLength?: number;
179
+ readonly minLength?: number;
172
180
  readonly required?: boolean;
173
181
  readonly searchable?: boolean;
174
182
  readonly sortable?: boolean;
@@ -208,6 +216,7 @@ export declare function createNoteMentionEntity(): EntityDefinition<{
208
216
  readonly type: "text";
209
217
  readonly description?: string;
210
218
  readonly maxLength?: number;
219
+ readonly minLength?: number;
211
220
  readonly required?: boolean;
212
221
  readonly searchable?: boolean;
213
222
  readonly sortable?: boolean;
@@ -229,6 +238,7 @@ export declare function createNoteMentionEntity(): EntityDefinition<{
229
238
  readonly type: "text";
230
239
  readonly description?: string;
231
240
  readonly maxLength?: number;
241
+ readonly minLength?: number;
232
242
  readonly required?: boolean;
233
243
  readonly searchable?: boolean;
234
244
  readonly sortable?: boolean;
@@ -252,6 +262,7 @@ export declare const noteMentionEntity: EntityDefinition<{
252
262
  readonly type: "text";
253
263
  readonly description?: string;
254
264
  readonly maxLength?: number;
265
+ readonly minLength?: number;
255
266
  readonly required?: boolean;
256
267
  readonly searchable?: boolean;
257
268
  readonly sortable?: boolean;
@@ -273,6 +284,7 @@ export declare const noteMentionEntity: EntityDefinition<{
273
284
  readonly type: "text";
274
285
  readonly description?: string;
275
286
  readonly maxLength?: number;
287
+ readonly minLength?: number;
276
288
  readonly required?: boolean;
277
289
  readonly searchable?: boolean;
278
290
  readonly sortable?: boolean;
@@ -13,7 +13,7 @@
13
13
  // a new entry, not an edit; see entity.ts for why that still satisfies GDPR
14
14
  // erasure without a delete path.
15
15
  import { defineEntityListHandler, defineFeature, } from "@cosmicdrift/kumiko-framework/engine";
16
- import { hasWhereRule } from "../shared/index.js";
16
+ import { assertNoWhereRuleInOwnershipWrite } from "../shared/index.js";
17
17
  import { DEFAULT_NOTES_HISTORY_ACCESS, NOTES_HISTORY_FEATURE_NAME } from "./constants.js";
18
18
  import { createNoteEntryEntity, noteMentionEntity } from "./entity.js";
19
19
  import { createNoteEntryExecutor } from "./executor.js";
@@ -58,15 +58,7 @@ export function createNotesHistoryFeature(opts = {}) {
58
58
  opts.parents === undefined) {
59
59
  return notesHistoryFeature;
60
60
  }
61
- if (hasWhereRule(opts.ownership?.write)) {
62
- throw new Error("createNotesHistoryFeature({ ownership }): ownership.write must not contain a " +
63
- '`{ kind: "where" }` rule — where-rules are evaluated only at the SQL ' +
64
- "layer (the read path, via buildOwnershipClause). Write paths that " +
65
- "consult access.write (userCanCreateFieldRow/userCanWriteFieldRow) can't " +
66
- "evaluate them, so such a rule can only ever deny — boot validation " +
67
- "rejects it too (fw#2626). Use a `from()` rule for ownership.write, or " +
68
- "leave it unset.");
69
- }
61
+ assertNoWhereRuleInOwnershipWrite("createNotesHistoryFeature", opts.ownership?.write);
70
62
  // A mount that accepts no parents at all can never take a note write —
71
63
  // that's a config mistake, not a valid allowlist. Omit `parents` instead
72
64
  // of passing an empty array.
@@ -3,6 +3,7 @@ export declare const apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
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 apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
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 apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
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 apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
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 apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
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 apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
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;