@cosmicdrift/kumiko-bundled-features 0.333.0 → 0.335.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 (277) hide show
  1. package/dist/admin-shell/overview-panels.js +2 -2
  2. package/dist/agent-tools/agent-doc-lint.d.ts +1 -0
  3. package/dist/agent-tools/agent-doc-lint.js +33 -3
  4. package/dist/agent-tools/agent-manifest.js +47 -6
  5. package/dist/agent-tools/filter-ops.d.ts +2 -0
  6. package/dist/agent-tools/filter-ops.js +4 -0
  7. package/dist/agent-tools/tool-catalog.js +1 -4
  8. package/dist/agent-tools/tool-dispatch.js +1 -4
  9. package/dist/audit/constants.d.ts +1 -1
  10. package/dist/audit/constants.js +1 -1
  11. package/dist/audit/handlers/list.query.js +4 -3
  12. package/dist/auth-email-password/handlers/change-password.write.js +2 -0
  13. package/dist/auth-email-password/handlers/invite-create.write.js +12 -2
  14. package/dist/auth-email-password/handlers/login.write.js +2 -0
  15. package/dist/auth-email-password/handlers/self-registration-status.query.js +1 -1
  16. package/dist/auth-email-password/i18n.js +2 -0
  17. package/dist/auth-email-password/web/auth-client.d.ts +1 -0
  18. package/dist/auth-email-password/web/auth-client.js +1 -1
  19. package/dist/auth-email-password/web/auth-gate.js +6 -8
  20. package/dist/auth-email-password/web/index.d.ts +1 -1
  21. package/dist/auth-email-password/web/index.js +1 -1
  22. package/dist/auth-email-password/web/invite-accept-screen.js +16 -12
  23. package/dist/auth-email-password/web/login-screen.js +1 -1
  24. package/dist/auth-email-password/web/session-bootstrap-error.d.ts +5 -1
  25. package/dist/auth-email-password/web/session-bootstrap-error.js +2 -2
  26. package/dist/auth-email-password/web/session.d.ts +4 -0
  27. package/dist/auth-email-password/web/session.js +15 -0
  28. package/dist/auth-mfa/feature.js +3 -2
  29. package/dist/auth-mfa/handlers/enable-confirm-preauth.write.js +2 -2
  30. package/dist/auth-mfa/handlers/verify.write.js +2 -0
  31. package/dist/auth-mfa/hmac-token-codec.js +3 -0
  32. package/dist/auth-mfa/mfa-challenge-token.js +4 -0
  33. package/dist/auth-mfa/mfa-preauth-setup-token.js +3 -0
  34. package/dist/auth-mfa/schema/user-mfa.d.ts +6 -0
  35. package/dist/auth-mfa/web/mfa-client.d.ts +1 -1
  36. package/dist/auth-mfa/web/mfa-client.js +4 -10
  37. package/dist/billing-foundation/billing-info-query.js +1 -1
  38. package/dist/billing-foundation/checkout-core.d.ts +5 -5
  39. package/dist/billing-foundation/checkout-core.js +10 -6
  40. package/dist/billing-foundation/entities.d.ts +8 -0
  41. package/dist/billing-foundation/handlers/switch-plan.write.js +5 -8
  42. package/dist/billing-foundation/projection.d.ts +8 -0
  43. package/dist/cap-counter/book-cap-usage.js +15 -5
  44. package/dist/cap-counter/enforce-cap.js +9 -3
  45. package/dist/cap-counter/entity.d.ts +1 -0
  46. package/dist/cap-counter/feature.js +3 -2
  47. package/dist/cap-counter/i18n.d.ts +1 -0
  48. package/dist/cap-counter/i18n.js +6 -5
  49. package/dist/cap-overview/handlers/caps-usage.query.js +9 -16
  50. package/dist/cap-overview/handlers/tenant-caps-list.query.js +41 -15
  51. package/dist/cap-overview/i18n.js +5 -0
  52. package/dist/cap-overview/web/cap-cards-panel.js +4 -1
  53. package/dist/cap-overview/web/cap-usage-bar.js +1 -1
  54. package/dist/channel-email/email-channel.js +12 -1
  55. package/dist/channel-in-app/tables.js +2 -6
  56. package/dist/compliance-profiles/handlers/sub-processors.query.js +1 -1
  57. package/dist/compliance-profiles/schema/profile-selection.d.ts +2 -2
  58. package/dist/config/db/queries/resolver.js +3 -1
  59. package/dist/config/handlers/set.write.js +8 -4
  60. package/dist/config/table.d.ts +6 -0
  61. package/dist/config/web/i18n.js +13 -4
  62. package/dist/config/write-helpers.d.ts +1 -1
  63. package/dist/config/write-helpers.js +11 -5
  64. package/dist/crypto-shredding/handlers/forget-subject.write.js +15 -8
  65. package/dist/custom-fields/constants.js +1 -4
  66. package/dist/custom-fields/entity.d.ts +4 -0
  67. package/dist/data-retention/resolve-for-tenant.d.ts +2 -3
  68. package/dist/data-retention/run-retention-cleanup.js +100 -44
  69. package/dist/data-retention/schema/tenant-retention-override.d.ts +4 -0
  70. package/dist/delivery/tables.d.ts +6 -0
  71. package/dist/delivery/types.d.ts +2 -1
  72. package/dist/delivery/types.js +7 -0
  73. package/dist/delivery/unsubscribe.js +5 -11
  74. package/dist/derivatives-sharp/render.js +27 -9
  75. package/dist/document-ingest-foundation/entity.d.ts +4 -0
  76. package/dist/document-ingest-foundation/feature.js +5 -5
  77. package/dist/document-ingest-foundation/providers.d.ts +1 -0
  78. package/dist/document-ingest-foundation/providers.js +6 -0
  79. package/dist/document-ingest-foundation/write-document-extract.d.ts +2 -2
  80. package/dist/document-ingest-foundation/write-document-extract.js +10 -0
  81. package/dist/feature-toggles/global-feature-state-table.js +2 -7
  82. package/dist/feature-toggles/toggle-sync-signal.d.ts +2 -1
  83. package/dist/feature-toggles/toggle-sync-signal.js +3 -2
  84. package/dist/file-provider-s3/feature.js +1 -1
  85. package/dist/files-tenant-data/index.js +3 -2
  86. package/dist/folders/entity.d.ts +8 -0
  87. package/dist/folders/handlers/clear-folder.write.js +4 -11
  88. package/dist/folders/handlers/set-folder.write.js +4 -10
  89. package/dist/form-draft/entity.d.ts +2 -0
  90. package/dist/form-draft/feature.js +3 -2
  91. package/dist/form-draft/handlers/discard.write.js +7 -8
  92. package/dist/form-draft/handlers/get.query.js +7 -1
  93. package/dist/foundation-shared/host-allowlist.d.ts +4 -0
  94. package/dist/foundation-shared/host-allowlist.js +14 -0
  95. package/dist/foundation-shared/index.d.ts +1 -0
  96. package/dist/foundation-shared/index.js +1 -0
  97. package/dist/foundation-shared/mail-host-policy.d.ts +0 -2
  98. package/dist/foundation-shared/mail-host-policy.js +3 -13
  99. package/dist/inbound-mail-foundation/connect-routes.js +7 -5
  100. package/dist/inbound-mail-foundation/entities.d.ts +25 -0
  101. package/dist/inbound-mail-foundation/projection.d.ts +20 -0
  102. package/dist/inbound-mail-foundation/watch-supervisor.js +47 -43
  103. package/dist/jobs/handlers/list.query.d.ts +7 -2
  104. package/dist/jobs/handlers/list.query.js +9 -2
  105. package/dist/jobs/handlers/retry.write.js +3 -2
  106. package/dist/jobs/handlers/tenant-failures.query.js +3 -9
  107. package/dist/jobs/job-run-logger.js +18 -2
  108. package/dist/jobs/job-run-table.d.ts +6 -0
  109. package/dist/jobs/tenant-job-failure-table.js +2 -6
  110. package/dist/ledger/entity.d.ts +16 -3
  111. package/dist/ledger/handlers/confirm-schedule-period.write.js +2 -2
  112. package/dist/ledger/handlers/reverse-transaction.write.js +2 -2
  113. package/dist/ledger/subject-reference.d.ts +4 -0
  114. package/dist/ledger/subject-reference.js +7 -0
  115. package/dist/legal-pages/feature.js +8 -1
  116. package/dist/managed-pages/handlers/branding.query.js +1 -1
  117. package/dist/managed-pages/handlers/by-slug.query.js +1 -1
  118. package/dist/managed-pages/handlers/by-tenant-published.query.js +1 -1
  119. package/dist/managed-pages/table.d.ts +12 -0
  120. package/dist/metrics/compute.js +7 -1
  121. package/dist/metrics/window.js +5 -0
  122. package/dist/notes-history/entity.d.ts +12 -0
  123. package/dist/notes-history/handlers/add-note.write.js +4 -9
  124. package/dist/notes-history-user-data/hooks.js +8 -1
  125. package/dist/personal-access-tokens/constants.d.ts +1 -0
  126. package/dist/personal-access-tokens/constants.js +1 -0
  127. package/dist/personal-access-tokens/feature.js +2 -2
  128. package/dist/personal-access-tokens/handlers/create.write.js +2 -2
  129. package/dist/personal-access-tokens/handlers/revoke.write.js +2 -2
  130. package/dist/personal-access-tokens/i18n.js +12 -13
  131. package/dist/personal-access-tokens/schema/api-token.d.ts +6 -0
  132. package/dist/personal-access-tokens/schema/api-token.js +2 -1
  133. package/dist/personal-access-tokens/screens.js +5 -1
  134. package/dist/rate-limiting/feature.js +2 -0
  135. package/dist/rate-limiting/handlers/bucket-access.js +15 -7
  136. package/dist/rate-limiting/i18n.d.ts +10 -0
  137. package/dist/rate-limiting/i18n.js +10 -0
  138. package/dist/secrets/feature.js +3 -2
  139. package/dist/secrets/table.d.ts +1 -0
  140. package/dist/seo/handlers/seo-config.query.js +1 -1
  141. package/dist/sessions/constants.d.ts +1 -0
  142. package/dist/sessions/constants.js +1 -0
  143. package/dist/sessions/feature.js +5 -3
  144. package/dist/sessions/handlers/revoke-all-for-user.write.d.ts +1 -0
  145. package/dist/sessions/handlers/revoke-all-for-user.write.js +12 -2
  146. package/dist/sessions/handlers/revoke.write.js +2 -2
  147. package/dist/sessions/schema/user-session.d.ts +4 -0
  148. package/dist/sessions/schema/user-session.js +2 -1
  149. package/dist/sessions/screens.js +5 -1
  150. package/dist/sessions/session-callbacks.js +2 -2
  151. package/dist/shared/index.d.ts +1 -1
  152. package/dist/shared/index.js +1 -1
  153. package/dist/shared/lockout-counter.js +1 -1
  154. package/dist/shared/parent-visibility.d.ts +5 -0
  155. package/dist/shared/parent-visibility.js +17 -1
  156. package/dist/shared/row-bound-grant.js +20 -9
  157. package/dist/shared/single-use-token-store.js +5 -13
  158. package/dist/step-dispatcher/webhook-runner.d.ts +0 -2
  159. package/dist/step-dispatcher/webhook-runner.js +18 -17
  160. package/dist/subscription-mollie/plugin-methods.d.ts +3 -2
  161. package/dist/subscription-mollie/plugin-methods.js +7 -1
  162. package/dist/subscription-mollie/verify-webhook.js +6 -0
  163. package/dist/subscription-stripe/feature.js +3 -3
  164. package/dist/subscription-stripe/plugin-methods.js +25 -6
  165. package/dist/subscription-stripe/verify-webhook.js +13 -6
  166. package/dist/tags/entity.d.ts +9 -0
  167. package/dist/tags/handlers/assign-tag.write.js +4 -10
  168. package/dist/tags/handlers/remove-tag.write.js +4 -11
  169. package/dist/template-resolver/api.d.ts +2 -1
  170. package/dist/template-resolver/api.js +1 -1
  171. package/dist/template-resolver/handlers/by-slug.query.d.ts +1 -1
  172. package/dist/template-resolver/handlers/by-slug.query.js +2 -2
  173. package/dist/template-resolver/handlers/by-tenant.query.d.ts +3 -2
  174. package/dist/template-resolver/handlers/by-tenant.query.js +2 -2
  175. package/dist/template-resolver/handlers/collection-item.query.d.ts +1 -1
  176. package/dist/template-resolver/handlers/collection-list.query.d.ts +1 -1
  177. package/dist/template-resolver/handlers/collection-shared.d.ts +3 -2
  178. package/dist/template-resolver/handlers/collection-shared.js +1 -1
  179. package/dist/template-resolver/handlers/find-by-id.query.d.ts +1 -1
  180. package/dist/template-resolver/handlers/find-by-id.query.js +1 -1
  181. package/dist/template-resolver/handlers/list.query.d.ts +1 -1
  182. package/dist/template-resolver/handlers/list.query.js +1 -1
  183. package/dist/template-resolver/seeding.js +41 -39
  184. package/dist/template-resolver/table.d.ts +26 -13
  185. package/dist/template-resolver/testing.js +2 -2
  186. package/dist/template-resolver/user-content-table.d.ts +16 -9
  187. package/dist/template-resolver/web/client-plugin.d.ts +1 -1
  188. package/dist/template-resolver-user-data/hooks.js +1 -1
  189. package/dist/tenant/cancel-pending-invitation.d.ts +8 -0
  190. package/dist/tenant/cancel-pending-invitation.js +22 -0
  191. package/dist/tenant/feature.js +4 -2
  192. package/dist/tenant/handlers/cancel-invitation.write.js +6 -21
  193. package/dist/tenant/handlers/member-directory.query.d.ts +1 -0
  194. package/dist/tenant/handlers/member-directory.query.js +17 -4
  195. package/dist/tenant/handlers/remove-member.write.js +9 -14
  196. package/dist/tenant/handlers/team-list.query.js +2 -2
  197. package/dist/tenant/handlers/tenant-directory.query.d.ts +1 -0
  198. package/dist/tenant/handlers/tenant-directory.query.js +13 -3
  199. package/dist/tenant/handlers/update-member-roles.write.d.ts +1 -1
  200. package/dist/tenant/handlers/update-member-roles.write.js +28 -12
  201. package/dist/tenant/i18n.js +4 -0
  202. package/dist/tenant/index.d.ts +1 -1
  203. package/dist/tenant/index.js +1 -1
  204. package/dist/tenant/invitation-table.d.ts +8 -2
  205. package/dist/tenant/last-tenant-admin.d.ts +1 -1
  206. package/dist/tenant/last-tenant-admin.js +5 -3
  207. package/dist/tenant/membership-table.d.ts +4 -0
  208. package/dist/tenant/schema/tenant.d.ts +8 -2
  209. package/dist/tenant/seeding.d.ts +4 -0
  210. package/dist/tenant/seeding.js +9 -2
  211. package/dist/tenant-lifecycle/boot-checks.d.ts +1 -0
  212. package/dist/tenant-lifecycle/boot-checks.js +27 -1
  213. package/dist/tenant-lifecycle/feature.js +2 -1
  214. package/dist/tenant-lifecycle/stages.js +2 -2
  215. package/dist/tenant-settings/feature.js +9 -6
  216. package/dist/tier-engine/entity.d.ts +2 -0
  217. package/dist/tier-engine/feature.js +3 -2
  218. package/dist/tier-engine/tier-resolver.d.ts +2 -0
  219. package/dist/user/command-schemas.d.ts +2 -2
  220. package/dist/user/handlers/create.write.d.ts +1 -1
  221. package/dist/user/handlers/create.write.js +8 -2
  222. package/dist/user/handlers/update.write.d.ts +1 -1
  223. package/dist/user/handlers/update.write.js +8 -2
  224. package/dist/user/schema/user.d.ts +14 -2
  225. package/dist/user-data-rights/db/queries/export-jobs.d.ts +2 -0
  226. package/dist/user-data-rights/feature.js +22 -9
  227. package/dist/user-data-rights/handlers/confirm-deletion-by-token.write.js +4 -7
  228. package/dist/user-data-rights/handlers/deletion-grace-period.d.ts +3 -2
  229. package/dist/user-data-rights/handlers/deletion-grace-period.js +30 -8
  230. package/dist/user-data-rights/handlers/download-by-token.query.js +1 -1
  231. package/dist/user-data-rights/handlers/request-deletion-by-email.write.js +1 -1
  232. package/dist/user-data-rights/handlers/request-deletion.write.js +3 -6
  233. package/dist/user-data-rights/lib/update-user-lifecycle.js +7 -14
  234. package/dist/user-data-rights/run-export-jobs.d.ts +1 -0
  235. package/dist/user-data-rights/run-export-jobs.js +74 -41
  236. package/dist/user-data-rights/run-forget-cleanup.js +2 -2
  237. package/dist/user-data-rights/run-user-export.js +8 -4
  238. package/dist/user-data-rights/schema/download-attempt.d.ts +14 -0
  239. package/dist/user-data-rights/schema/download-token.d.ts +8 -0
  240. package/dist/user-data-rights/schema/export-job.d.ts +8 -2
  241. package/dist/user-data-rights-defaults/hooks/file-ref.userdata-hook.js +4 -1
  242. package/dist/user-profile/feature.js +6 -1
  243. package/dist/user-profile/handlers/change-email.write.js +2 -0
  244. package/dist/workflow-runner/db/queries/due-runs.js +6 -9
  245. package/dist/workflow-runner/event-subscriber.js +29 -9
  246. package/dist/workflow-runner/handlers/resume-run.write.js +8 -5
  247. package/dist/workflow-runner/pending-projection.js +8 -1
  248. package/package.json +11 -9
  249. package/src/agent-tools/changes.json +12 -0
  250. package/src/auth-email-password/changes.json +18 -0
  251. package/src/auth-mfa/changes.json +6 -0
  252. package/src/billing-foundation/changes.json +7 -0
  253. package/src/cap-counter/changes.json +7 -0
  254. package/src/cap-overview/changes.json +12 -0
  255. package/src/channel-email/changes.json +6 -0
  256. package/src/config/changes.json +6 -0
  257. package/src/crypto-shredding/changes.json +6 -0
  258. package/src/data-retention/changes.json +12 -0
  259. package/src/delivery/changes.json +7 -0
  260. package/src/derivatives-sharp/changes.json +11 -0
  261. package/src/document-ingest-foundation/changes.json +12 -0
  262. package/src/inbound-mail-foundation/changes.json +12 -0
  263. package/src/jobs/changes.json +13 -0
  264. package/src/legal-pages/changes.json +8 -1
  265. package/src/managed-pages/changes.json +5 -0
  266. package/src/metrics/changes.json +6 -0
  267. package/src/rate-limiting/changes.json +5 -0
  268. package/src/secrets/changes.json +7 -0
  269. package/src/sessions/changes.json +5 -0
  270. package/src/shared/changes.json +6 -0
  271. package/src/step-dispatcher/changes.json +5 -0
  272. package/src/subscription-mollie/changes.json +7 -0
  273. package/src/subscription-stripe/changes.json +11 -0
  274. package/src/template-resolver/changes.json +7 -0
  275. package/src/tenant/changes.json +29 -0
  276. package/src/user-data-rights/changes.json +22 -0
  277. package/src/workflow-runner/changes.json +6 -0
@@ -23,6 +23,7 @@ export declare const exportJobEntity: import("@cosmicdrift/kumiko-types/fields")
23
23
  readonly filterable?: boolean;
24
24
  readonly encrypted?: boolean;
25
25
  readonly sensitive?: boolean;
26
+ readonly writeOnly?: boolean;
26
27
  readonly readAsInstruction?: boolean;
27
28
  readonly format?: "email" | "url" | "phone" | "password";
28
29
  readonly default?: string;
@@ -43,6 +44,7 @@ export declare const exportJobEntity: import("@cosmicdrift/kumiko-types/fields")
43
44
  readonly filterable?: boolean;
44
45
  readonly encrypted?: boolean;
45
46
  readonly sensitive?: boolean;
47
+ readonly writeOnly?: boolean;
46
48
  readonly readAsInstruction?: boolean;
47
49
  readonly format?: "email" | "url" | "phone" | "password";
48
50
  readonly default?: string;
@@ -71,7 +73,7 @@ export declare const exportJobEntity: import("@cosmicdrift/kumiko-types/fields")
71
73
  readonly running?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
72
74
  } | undefined;
73
75
  readonly optionsQuery?: string;
74
- readonly optionsQueryPayload?: Readonly<Record<string, string | number | boolean>>;
76
+ readonly optionsQueryPayload?: import("@cosmicdrift/kumiko-types/fields").OptionsQueryPayload;
75
77
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
76
78
  required: true;
77
79
  };
@@ -130,6 +132,7 @@ export declare const exportJobEntity: import("@cosmicdrift/kumiko-types/fields")
130
132
  readonly filterable?: boolean;
131
133
  readonly encrypted?: boolean;
132
134
  readonly sensitive?: boolean;
135
+ readonly writeOnly?: boolean;
133
136
  readonly readAsInstruction?: boolean;
134
137
  readonly format?: "email" | "url" | "phone" | "password";
135
138
  readonly default?: string;
@@ -195,6 +198,7 @@ export declare const exportJobsTable: import("@cosmicdrift/kumiko-framework/db")
195
198
  readonly filterable?: boolean;
196
199
  readonly encrypted?: boolean;
197
200
  readonly sensitive?: boolean;
201
+ readonly writeOnly?: boolean;
198
202
  readonly readAsInstruction?: boolean;
199
203
  readonly format?: "email" | "url" | "phone" | "password";
200
204
  readonly default?: string;
@@ -215,6 +219,7 @@ export declare const exportJobsTable: import("@cosmicdrift/kumiko-framework/db")
215
219
  readonly filterable?: boolean;
216
220
  readonly encrypted?: boolean;
217
221
  readonly sensitive?: boolean;
222
+ readonly writeOnly?: boolean;
218
223
  readonly readAsInstruction?: boolean;
219
224
  readonly format?: "email" | "url" | "phone" | "password";
220
225
  readonly default?: string;
@@ -243,7 +248,7 @@ export declare const exportJobsTable: import("@cosmicdrift/kumiko-framework/db")
243
248
  readonly running?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
244
249
  } | undefined;
245
250
  readonly optionsQuery?: string;
246
- readonly optionsQueryPayload?: Readonly<Record<string, string | number | boolean>>;
251
+ readonly optionsQueryPayload?: import("@cosmicdrift/kumiko-types/fields").OptionsQueryPayload;
247
252
  } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
248
253
  required: true;
249
254
  };
@@ -302,6 +307,7 @@ export declare const exportJobsTable: import("@cosmicdrift/kumiko-framework/db")
302
307
  readonly filterable?: boolean;
303
308
  readonly encrypted?: boolean;
304
309
  readonly sensitive?: boolean;
310
+ readonly writeOnly?: boolean;
305
311
  readonly readAsInstruction?: boolean;
306
312
  readonly format?: "email" | "url" | "phone" | "password";
307
313
  readonly default?: string;
@@ -1,6 +1,7 @@
1
1
  import { createEventStoreExecutor } from "@cosmicdrift/kumiko-framework/db";
2
2
  import { deleteStoredFileAndDerivatives } from "@cosmicdrift/kumiko-framework/derivatives";
3
3
  import { createSystemUser, } from "@cosmicdrift/kumiko-framework/engine";
4
+ import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
4
5
  import { fileRefEntity, fileRefsTable } from "@cosmicdrift/kumiko-framework/files";
5
6
  import { assertErased } from "../../shared/index.js";
6
7
  const ERROR_CONTEXT = "user-data-rights-defaults:fileRef";
@@ -222,7 +223,9 @@ export const fileRefDeleteHook = async (ctx, strategy) => {
222
223
  if (storageProvider) {
223
224
  const failedKeys = await deleteBinaries(personalRows, storageProvider);
224
225
  if (failedKeys.length > 0) {
225
- throw new Error(`[user-data-rights-defaults:fileRef] ${failedKeys.length} binary delete(s) failed — aborting forget so the rows are retried next run (keys: ${failedKeys.join(", ")})`);
226
+ throw new InternalError({
227
+ message: `[user-data-rights-defaults:fileRef] ${failedKeys.length} binary delete(s) failed — aborting forget so the rows are retried next run (keys: ${failedKeys.join(", ")})`,
228
+ });
226
229
  }
227
230
  }
228
231
  else {
@@ -42,6 +42,9 @@ export function createUserProfileFeature() {
42
42
  "profile.email.title": { en: "Email address" },
43
43
  "profile.password.title": { en: "Password" },
44
44
  "profile.danger.title": { en: "Delete account" },
45
+ "profile.danger.explainer": {
46
+ en: "Your account will be permanently deleted after a grace period. Until then you can cancel the deletion at any time.",
47
+ },
45
48
  "profile.danger.gracePeriodEnd": { en: "Deletion date" },
46
49
  "profile.danger.delete": { en: "Delete account" },
47
50
  "profile.danger.dialogDescription": {
@@ -94,7 +97,7 @@ export function createUserProfileFeature() {
94
97
  },
95
98
  {
96
99
  title: i18nKey("profile.danger.title"),
97
- description: "profile.danger.explainer",
100
+ description: i18nKey("profile.danger.explainer"),
98
101
  // gracePeriodEnd is only meaningful once a deletion is actually
99
102
  // pending — hide it instead of showing an empty date when status
100
103
  // isn't deletionRequested (same rule as privacy-center).
@@ -123,6 +126,8 @@ export function createUserProfileFeature() {
123
126
  handler: UserDataRightsHandlers.cancelDeletion,
124
127
  visible: { field: "status", eq: "deletionRequested" },
125
128
  style: "secondary",
129
+ // Declared: the id's last segment ("deletion") would otherwise resolve to the destructive trash icon.
130
+ icon: "x",
126
131
  },
127
132
  ],
128
133
  });
@@ -33,6 +33,8 @@ export const changeEmailWrite = defineWriteHandler({
33
33
  "email uniqueness the same way, and writes the new email via ctx.writeAs(SYSTEM, " +
34
34
  "user:update) — field-access on passwordHash/email is privileged-only.",
35
35
  },
36
+ // Payload carries the current password.
37
+ agent: { expose: false },
36
38
  description: "Replaces the signed-in caller's own email address after re-checking their current password, refusing an unchanged or already-taken address and clearing the verified flag so the app can re-run email verification.",
37
39
  handler: async (event, ctx) => {
38
40
  const systemUser = createSystemUser(event.user.tenantId);
@@ -1,12 +1,9 @@
1
1
  import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
2
- // Standalone statement (no db.begin) — FOR UPDATE SKIP LOCKED only
3
- // needs to survive this one SELECT to reduce redundant dispatches
4
- // under true concurrency; it auto-commits and releases the row locks
5
- // immediately after. The real correctness guarantee is resume-run's
6
- // VersionConflictError-checked claim (ctx.tryAppendEvent on
7
- // WORKFLOW_RESUMED) — a second dispatch for the same row just loses
8
- // that race and no-ops, same as the sample this was adapted from.
2
+ // Dedup happens solely via resume-run's VersionConflictError-checked claim
3
+ // (ctx.tryAppendEvent on WORKFLOW_RESUMED) — a second dispatch for the same
4
+ // row loses that race and no-ops. No row locks: in an autocommit statement
5
+ // they would be released right after the SELECT and guard nothing.
9
6
  export async function selectDueWorkflowRunPending(db, tenantId) {
10
- // kumiko-lint-ignore raw-sql tenant-scoped due-row pickup with FOR UPDATE SKIP LOCKED
11
- return (await asRawClient(db).unsafe(`SELECT run_id, step_index FROM workflow_run_pending WHERE tenant_id = $1 AND wake_at < now() FOR UPDATE SKIP LOCKED`, [tenantId]));
7
+ // kumiko-lint-ignore raw-sql tenant-scoped due-row pickup
8
+ return (await asRawClient(db).unsafe(`SELECT run_id, step_index FROM workflow_run_pending WHERE tenant_id = $1 AND wake_at < now()`, [tenantId]));
12
9
  }
@@ -25,21 +25,41 @@
25
25
  // cron tick — the one resume path stays exactly the one Phase 2 built.
26
26
  import { selectMany, updateMany } from "@cosmicdrift/kumiko-framework/bun-db";
27
27
  import { evaluateEventMatch } from "@cosmicdrift/kumiko-framework/engine";
28
+ import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
29
+ import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
28
30
  import { workflowRunPendingTable } from "./tables.js";
31
+ const log = createFallbackLogger("workflow-runner");
32
+ // A row whose persisted matchExpr cannot be evaluated (unknown version from a
33
+ // rolling deploy, corrupt AST) must not abort the whole projection: that would
34
+ // block every other waiting run of this event type on the same event forever.
35
+ function rowMatchesEvent(row, eventType, payload) {
36
+ if (!row.matchExpr)
37
+ return true;
38
+ try {
39
+ return evaluateEventMatch(
40
+ // @cast-boundary engine-payload — matchExpr round-trips through
41
+ // jsonb as the EventMatch AST waitForEvent's `match` resolver
42
+ // persisted (see steps/wait-for-event.ts); evaluateEventMatch
43
+ // rejects a malformed shape itself.
44
+ row.matchExpr, payload);
45
+ }
46
+ catch (error) {
47
+ log.error("workflow wakeup skipped a pending row with an unevaluable matchExpr", {
48
+ runId: row.runId,
49
+ stepIndex: row.stepIndex,
50
+ eventType,
51
+ error: error instanceof Error ? error.message : String(error),
52
+ });
53
+ return false;
54
+ }
55
+ }
29
56
  const wakeupApply = async (event, tx) => {
30
57
  const rows = await selectMany(tx, workflowRunPendingTable, {
31
58
  tenantId: event.tenantId,
32
59
  waitEventType: event.type,
33
60
  });
34
61
  for (const row of rows) {
35
- const matches = row.matchExpr
36
- ? evaluateEventMatch(
37
- // @cast-boundary engine-payload — matchExpr round-trips through
38
- // jsonb as the EventMatch AST waitForEvent's `match` resolver
39
- // persisted (see steps/wait-for-event.ts); never an unchecked
40
- // external value.
41
- row.matchExpr, event.payload)
42
- : true;
62
+ const matches = rowMatchesEvent(row, event.type, event.payload);
43
63
  // skip: this row's matchExpr rejected the event — not this row's
44
64
  // resume, leave wakeAt/triggerEventType untouched.
45
65
  if (!matches)
@@ -47,7 +67,7 @@ const wakeupApply = async (event, tx) => {
47
67
  await updateMany(tx, workflowRunPendingTable, {
48
68
  triggerEventType: event.type,
49
69
  triggerPayload: event.payload,
50
- wakeAt: Temporal.Now.instant().toString(),
70
+ wakeAt: getTemporal().Now.instant().toString(),
51
71
  }, { tenantId: row.tenantId, runId: row.runId, stepIndex: row.stepIndex });
52
72
  }
53
73
  };
@@ -36,7 +36,7 @@ import { workflowRunPendingTable } from "../tables.js";
36
36
  import { getWorkflow } from "../workflow-registry.js";
37
37
  const log = createFallbackLogger("workflow-runner");
38
38
  const resumeRunSchema = z.object({
39
- runId: z.string().min(1),
39
+ runId: z.string().uuid(),
40
40
  stepIndex: z.number().int().nonnegative(),
41
41
  });
42
42
  async function appendRunFailed(ctx, runId, failedPayload) {
@@ -170,16 +170,19 @@ export const resumeRunHandler = {
170
170
  return { isSuccess: true, data: { outcome: "already-resumed" } };
171
171
  }
172
172
  const { workflowName } = pending;
173
+ const runEvents = await ctx.loadAggregate(runId);
174
+ // Before the workflow resolution: a racing tick on an already-settled run
175
+ // must not append run-failed after run-completed when the definition has
176
+ // since changed or been removed.
177
+ if (isRunAlreadySettled(runEvents, stepIndex, pending.retryAttempt ?? undefined)) {
178
+ return { isSuccess: true, data: { outcome: "already-resumed" } };
179
+ }
173
180
  const resolved = resolveRunnableWorkflow(workflowName, runId, stepIndex, pending.definitionFingerprint);
174
181
  if (resolved.failure !== null) {
175
182
  await appendRunFailed(ctx, runId, resolved.failure);
176
183
  return { isSuccess: true, data: { outcome: "failed" } };
177
184
  }
178
185
  const workflow = resolved.workflow;
179
- const runEvents = await ctx.loadAggregate(runId);
180
- if (isRunAlreadySettled(runEvents, stepIndex, pending.retryAttempt ?? undefined)) {
181
- return { isSuccess: true, data: { outcome: "already-resumed" } };
182
- }
183
186
  const claim = await ctx.tryAppendEvent({
184
187
  aggregateId: runId,
185
188
  aggregateType: WORKFLOW_AGGREGATE_TYPE,
@@ -11,10 +11,17 @@
11
11
  // No resume logic here — framework#2513 Phase 2 owns the job that reads
12
12
  // this table and dispatches the actual resume. This projection only writes
13
13
  // the rows.
14
- import { deleteMany, upsertOnConflict } from "@cosmicdrift/kumiko-framework/bun-db";
14
+ import { deleteMany, fetchOne, upsertOnConflict } from "@cosmicdrift/kumiko-framework/bun-db";
15
15
  import { WORKFLOW_RESUMED_TYPE, WORKFLOW_RETRY_SCHEDULED_TYPE, WORKFLOW_RUN_COMPLETED_TYPE, WORKFLOW_RUN_FAILED_TYPE, WORKFLOW_WAITING_FOR_EVENT_TYPE, WORKFLOW_WAITING_TYPE, } from "@cosmicdrift/kumiko-framework/engine";
16
16
  import { workflowRunPendingTable } from "./tables.js";
17
17
  async function upsertPending(tx, row) {
18
+ // At-least-once redelivery of a suspension event after the event-subscriber
19
+ // already matched a trigger onto this row must not reset wakeAt/trigger —
20
+ // the run would sleep until timeout and lose the matched payload.
21
+ const existing = await fetchOne(tx, workflowRunPendingTable, { tenantId: row.tenantId, runId: row.runId, stepIndex: row.stepIndex });
22
+ // skip: row already carries a trigger event type, keep it on redelivery
23
+ if (existing?.triggerEventType != null)
24
+ return;
18
25
  await upsertOnConflict(tx, workflowRunPendingTable, {
19
26
  ...row,
20
27
  // Phase 3's event-subscriber writes these when the awaited event
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-bundled-features",
3
- "version": "0.333.0",
3
+ "version": "0.335.0",
4
4
  "description": "Built-in features — tenant, user, auth, delivery. The stuff you'd rewrite anyway, already typed.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -483,18 +483,20 @@
483
483
  }
484
484
  },
485
485
  "dependencies": {
486
- "@cosmicdrift/kumiko-dispatcher-live": "0.333.0",
487
- "@cosmicdrift/kumiko-framework": "0.333.0",
488
- "@cosmicdrift/kumiko-headless": "0.333.0",
489
- "@cosmicdrift/kumiko-renderer": "0.333.0",
490
- "@cosmicdrift/kumiko-renderer-web": "0.333.0",
491
- "@cosmicdrift/kumiko-types": "0.333.0",
486
+ "@cosmicdrift/kumiko-dispatcher-live": "0.335.0",
487
+ "@cosmicdrift/kumiko-framework": "0.335.0",
488
+ "@cosmicdrift/kumiko-headless": "0.335.0",
489
+ "@cosmicdrift/kumiko-renderer": "0.335.0",
490
+ "@cosmicdrift/kumiko-renderer-web": "0.335.0",
491
+ "@cosmicdrift/kumiko-types": "0.335.0",
492
492
  "@mollie/api-client": "^4.5.0",
493
493
  "@node-rs/argon2": "^2.0.2",
494
494
  "@types/mailparser": "^3.4.6",
495
495
  "@types/qrcode": "^1.5.5",
496
496
  "clsx": "^2.1.1",
497
+ "hono": "^4.13.1",
497
498
  "imapflow": "^1.7.8",
499
+ "ioredis": "^5.10.1",
498
500
  "jose": "^6.2.3",
499
501
  "lucide-react": "^1.14.0",
500
502
  "mailparser": "^3.9.32",
@@ -983,8 +985,8 @@
983
985
  ],
984
986
  "devDependencies": {
985
987
  "@testing-library/user-event": "^14.6.1",
986
- "@cosmicdrift/kumiko-locale-de": "0.333.0",
987
- "@cosmicdrift/kumiko-locale-es": "0.333.0",
988
+ "@cosmicdrift/kumiko-locale-de": "0.335.0",
989
+ "@cosmicdrift/kumiko-locale-es": "0.335.0",
988
990
  "jsqr": "^1.4.0"
989
991
  }
990
992
  }
@@ -1,4 +1,16 @@
1
1
  [
2
+ {
3
+ "version": "0.335.0",
4
+ "type": "breaking",
5
+ "title": "Manifest rejects unknown denyQns, drops navs under hidden parents; doc lint flags blank and exposed-undescribed handlers",
6
+ "detail": "Agent tools: `buildAgentManifest` now throws when `denyQns` names a handler that is not registered, so a typo can no longer leave a handler exposed. Navs whose parent is hidden from the role are dropped from the manifest, and screens reachable only through them are no longer listed. The agent doc lint reports handlers that are exposed without a description and treats a blank description as a gap.",
7
+ "migration": "`buildAgentManifest` now throws when `denyQns` names a handler that is not registered; remove stale or misspelled entries from your `denyQns` lists."
8
+ },
9
+ {
10
+ "version": "0.335.0",
11
+ "type": "fix",
12
+ "title": "Agent doc lint reports exposed handlers with a non-convertible input schema"
13
+ },
2
14
  {
3
15
  "version": "0.313.0",
4
16
  "type": "fix",
@@ -1,4 +1,22 @@
1
1
  [
2
+ {
3
+ "version": "0.335.0",
4
+ "type": "fix",
5
+ "title": "Credential-carrying auth handlers are hidden from the agent catalog; additionalAssignableRoles rejects framework-ranked roles",
6
+ "detail": "The credential-carrying handlers `auth-email-password:login`, `change-password`, `user-profile:change-email`, `auth-mfa:verify` and `auth-mfa:enable-confirm-preauth` are now opted out of the agent tool catalog with `agent: { expose: false }`, so passwords, TOTP codes and setup tokens no longer reach an LLM transcript. `invite.additionalAssignableRoles` now throws at construction when it lists a framework-ranked role such as `TenantAdmin`, instead of silently disabling the elevation guard for it."
7
+ },
8
+ {
9
+ "version": "0.335.0",
10
+ "type": "fix",
11
+ "title": "Invite accept handles MFA responses, bootstrap error screen offers Sign out",
12
+ "detail": "`InviteAcceptScreen` no longer redirects to a tenant URL when the accept route answers with an MFA challenge or setup requirement (no session cookie was minted); it sends the user to `loginHref` instead. `SessionBootstrapErrorScreen` accepts an optional `onSignOut` and the auth gate wires it, so a permanent bootstrap failure no longer traps the user behind \"Try again\" (new i18n key `auth.sessionBootstrap.signOut`). Cap bookings back off with a small random delay between version-conflict retries."
13
+ },
14
+ {
15
+ "version": "0.335.0",
16
+ "type": "fix",
17
+ "title": "Ended session leads back to the login screen",
18
+ "detail": "When the server ends a session (revoked, expired), the app now shows the login screen with a short hint instead of a raw error banner. After signing in again the user lands on the same screen as before."
19
+ },
2
20
  {
3
21
  "version": "0.328.1",
4
22
  "type": "fix",
@@ -1,4 +1,10 @@
1
1
  [
2
+ {
3
+ "version": "0.335.0",
4
+ "type": "fix",
5
+ "title": "MFA web client shares the login response mapper",
6
+ "detail": "The MFA web client reuses the shared `toLoginResponse` helper (now exported from the auth-email-password web barrel) for `landingPath` handling, so login, MFA verify and MFA setup confirm map the response identically."
7
+ },
2
8
  {
3
9
  "version": "0.329.0",
4
10
  "type": "improvement",
@@ -1,4 +1,11 @@
1
1
  [
2
+ {
3
+ "version": "0.335.0",
4
+ "type": "breaking",
5
+ "title": "Unregistered catalog.providerName fails unconfigured; switch-plan checks provider mismatch before lookup",
6
+ "detail": "`billing-plans` now fails with an `unconfigured` error when `catalog.providerName` names a provider that is not registered, instead of rendering billing as disabled. Billing renders as disabled only when no `providerName` is set and no provider is mounted. `switch-plan` answers a subscription on an unmounted provider with the `providerMismatch` conflict instead of a 500.",
7
+ "migration": "If `catalog.providerName` names a provider that is not mounted, billing-plans now fails with `unconfigured` instead of rendering billing as disabled; mount the provider or remove `providerName`."
8
+ },
2
9
  {
3
10
  "version": "0.330.0",
4
11
  "type": "fix",
@@ -1,4 +1,11 @@
1
1
  [
2
+ {
3
+ "version": "0.335.0",
4
+ "type": "breaking",
5
+ "title": "Cap helpers validate periodStartIso and amount, surface failed soft-warn writes; German column labels",
6
+ "detail": "`bookCapUsage` and `markCapSoftWarned` now reject a `periodStartIso` that is not an ISO instant with a validation error instead of silently forking a counter row or throwing a `RangeError`. `enforceCap` rejects a non-integer or non-positive `amount` instead of letting it bypass the cap. `enforceCapAndMaybeNotify` surfaces a failed soft-warn write instead of re-sending the notification on the next call. The cap-counter list now has German column labels.",
7
+ "migration": "`bookCapUsage` and `markCapSoftWarned` now reject a `periodStartIso` that is not a full ISO instant, including date-only strings like `2026-10-01`; pass e.g. `2026-10-01T00:00:00.000Z`."
8
+ },
2
9
  {
3
10
  "version": "0.317.0",
4
11
  "type": "fix",
@@ -1,4 +1,16 @@
1
1
  [
2
+ {
3
+ "version": "0.335.0",
4
+ "type": "fix",
5
+ "title": "tenant-caps:list validates cursor and filters, parallel usage reads; cap-counter list not searchable",
6
+ "detail": "`tenant-caps:list` now rejects a malformed pagination cursor and unsupported or repeated filters with a validation error instead of returning a wrong page or silently ignoring the filter, and loads per-tenant usage in parallel. The `cap-counter` operator list is no longer searchable, because search resolved against the caller's own tenant instead of all tenants."
7
+ },
8
+ {
9
+ "version": "0.335.0",
10
+ "type": "fix",
11
+ "title": "Unlimited caps show a labelled usage count",
12
+ "detail": "The cap usage bar labels an unlimited cap as \"<used> · unlimited\" through the new `cap-overview.unlimited` key instead of a bare number. The German and Spanish catalogs ship the translation."
13
+ },
2
14
  {
3
15
  "version": "0.332.0",
4
16
  "type": "fix",
@@ -1,4 +1,10 @@
1
1
  [
2
+ {
3
+ "version": "0.335.0",
4
+ "type": "fix",
5
+ "title": "Explicit List-Unsubscribe headers replace the automatic pair case-insensitively",
6
+ "detail": "The email channel now matches explicit `data.headers` case-insensitively against the automatic unsubscribe headers. Overriding `List-Unsubscribe` or `List-Unsubscribe-Post` in any spelling drops the whole automatic pair, so a mail client no longer sends a one-click POST to a URL that cannot handle it."
7
+ },
2
8
  {
3
9
  "version": "0.321.0",
4
10
  "type": "improvement",
@@ -1,4 +1,10 @@
1
1
  [
2
+ {
3
+ "version": "0.335.0",
4
+ "type": "breaking",
5
+ "title": "config:write:set masks the echoed value for encrypted-at-rest keys; validation errors no longer include the value",
6
+ "migration": "A caller that reads `data.value` from a `config:write:set` result for a key with `encrypted: true` or `backing: \"secrets\"` now gets the mask. Use the value it sent instead. Tests that match the `value` param of a validation error on such a key need to drop that expectation."
7
+ },
2
8
  {
3
9
  "version": "0.326.0",
4
10
  "type": "breaking",
@@ -1,4 +1,10 @@
1
1
  [
2
+ {
3
+ "version": "0.335.0",
4
+ "type": "fix",
5
+ "title": "forget-denied audit events record the specific tenant-gate denial reason",
6
+ "detail": "The `forget-denied` audit event now records the specific tenant-gate reason (for example `target_tenant_not_admin_tenant`) instead of the generic `access_denied`. The framework exports `requireEntityTableMeta` from `bun-db`."
7
+ },
2
8
  {
3
9
  "version": "0.332.0",
4
10
  "type": "fix",
@@ -1,4 +1,16 @@
1
1
  [
2
+ {
3
+ "version": "0.335.0",
4
+ "type": "fix",
5
+ "title": "Retention hardDelete keeps cross-entity shared files and isolates storage errors per row",
6
+ "detail": "Retention hardDelete no longer deletes a fileRef that a live row of another entity type still references in a file/image field (relevant for unbound uploads). A storage error while deleting a row's files (for example a missing list permission) now skips only that row with `file_delete_failed` instead of aborting the whole tenant's cleanup run."
7
+ },
8
+ {
9
+ "version": "0.335.0",
10
+ "type": "fix",
11
+ "title": "Retention hardDelete pages past stuck file rows and blockDelete reports personal fields without anonymize",
12
+ "detail": "Retention hardDelete on entities with file fields now pages by id, so rows stuck on a storage failure or a missing file storage no longer starve the rows behind them. A `blockDelete` entity with a personal field but no `anonymize` function is reported as `missing_anonymize_fields` again after the hold expires; only entities without any anonymizable subject field stay silent. The framework exports `entityHasAnonymizableSubjectField` for this check."
13
+ },
2
14
  {
3
15
  "version": "0.330.1",
4
16
  "type": "fix",
@@ -1,4 +1,11 @@
1
1
  [
2
+ {
3
+ "version": "0.335.0",
4
+ "type": "breaking",
5
+ "title": "Channel plugin guard validates mode and render",
6
+ "detail": "Stricter input guards. A delivery channel plugin must declare `mode` as `inline` or `queued` and a function `render`, otherwise boot fails instead of silently dispatching inline. The derivatives-sharp overlay rejects a non-finite `marginPct` before it reaches sharp. A document-ingest provider can no longer register under the reserved name `unknown`, which marks upcast legacy ingest events. The unsubscribe POST route also accepts a JSON body.",
7
+ "migration": "A delivery channel plugin must now declare `mode` as `\"inline\"` or `\"queued\"` and a function `render`; boot fails otherwise instead of dispatching inline."
8
+ },
2
9
  {
3
10
  "version": "0.330.0",
4
11
  "type": "improvement",
@@ -1,4 +1,15 @@
1
1
  [
2
+ {
3
+ "version": "0.335.0",
4
+ "type": "fix",
5
+ "title": "Overlay and blur intermediates are lossless and QR overlays render crisp at their final size",
6
+ "detail": "The sharp renderer buffers intermediates losslessly before overlays and blur regions, so a JPEG source no longer gains an extra compression generation. QR overlays are generated at their final size and checked against the smaller of output width and height before generation, so a flat output can no longer cache an unscannable QR."
7
+ },
8
+ {
9
+ "version": "0.335.0",
10
+ "type": "fix",
11
+ "title": "Overlay marginPct must be finite"
12
+ },
2
13
  {
3
14
  "version": "0.287.0",
4
15
  "type": "improvement",
@@ -1,4 +1,16 @@
1
1
  [
2
+ {
3
+ "version": "0.335.0",
4
+ "type": "fix",
5
+ "title": "Writing a document extract is idempotent per fileRef",
6
+ "detail": "`writeDocumentExtractForLiveFileRef` now returns `{ kind: \"skipped\", reason: \"already_extracted\" }` when the fileRef already has a documentExtract, so a redelivered `documentIngest.requested` or a delete-restore race no longer creates a second extract. Providers need no existence check of their own; code that switches exhaustively on `reason` must handle the new value."
7
+ },
8
+ {
9
+ "version": "0.335.0",
10
+ "type": "breaking",
11
+ "title": "Provider name \"unknown\" is reserved for upcast legacy events",
12
+ "migration": "Registering a document-ingest provider under the name `unknown` now throws; rename any provider that uses it."
13
+ },
2
14
  {
3
15
  "version": "0.312.0",
4
16
  "type": "breaking",
@@ -1,4 +1,16 @@
1
1
  [
2
+ {
3
+ "version": "0.335.0",
4
+ "type": "fix",
5
+ "title": "Poll cursor is persisted with the account's tenantId",
6
+ "detail": "Several bundled-feature fixes. The inbound-mail watch supervisor now stamps `tenantId` when it first saves a poll cursor, so cursors persist instead of failing on the NOT NULL column. The anonymous queries of managed-pages, template-resolver, seo, compliance-profiles and auth-email-password rate-limit per IP and handler, so one handler no longer drains the bucket of the others. The personal-access-token list loads up to the maximum page size without a pager that could not page. Serial host ids beyond int4 are denied as not found, and the notes forget hook resolves the tenant retention preset once."
7
+ },
8
+ {
9
+ "version": "0.335.0",
10
+ "type": "fix",
11
+ "title": "OAuth callback fails before creating an account when no secrets context is wired",
12
+ "detail": "The inbound-mail OAuth callback now checks for a configured secrets context before exchanging the authorization code and creating the mail account. Without secrets the callback answers 500 `secrets_context_missing` and no orphan account is left behind. `form-draft:get` now logs a warning when a stored draft blob fails its schema instead of dropping it silently."
13
+ },
2
14
  {
3
15
  "version": "0.306.0",
4
16
  "type": "fix",
@@ -1,4 +1,17 @@
1
1
  [
2
+ {
3
+ "version": "0.335.0",
4
+ "type": "fix",
5
+ "title": "Tenant failure subject filter shares one serializer with the job runner",
6
+ "detail": "The framework `jobs` entry now exports `serializeJobSubject`, the single source of the tenant-visible failure subject string. The job runner and `jobs:query:failures` both use it, so the subject filter cannot drift from what the runner stored."
7
+ },
8
+ {
9
+ "version": "0.335.0",
10
+ "type": "breaking",
11
+ "title": "Job runner keeps retry budget on sequential lock conflicts, names the error without a db, fails loudly on bad tenantVisibleFailure subjects, stamps the raw feature name; jobs list filters multi-status",
12
+ "detail": "Job runner hardening. A job run now stamps its event `metadata.feature` with the feature's raw name, the same value write-handlers stamp (previously the kebab-case form for camelCase features). On a runner built without a db, touching `ctx.db` in a job throws a named error instead of a `TypeError`. A non-primitive `tenantVisibleFailure.subjectFields` value now fails the run visibly (run row, `onJobFailed`) and is not retried. A failing last-success metric no longer fails a successful run, and the runner registers the standard metrics on its meter itself. A sequential job whose retry attempt hits a held lock keeps its remaining retry budget instead of getting a fresh one. `stop()` waits at most 1 second for queue readiness. A custom `tenant:query:active-tenant-ids` handler returning anything but `string[]` is rejected. `Registry` gains `getJobFeature`.\nIn the jobs feature, a failing tenant-failure record write no longer blocks the run-row update or fails a completed run, `jobs:query:list` honours multi-value status filters and rejects filters on unknown fields, and `jobs:write:retry` answers 422 for a payload that is still ciphertext because no KMS is configured.",
13
+ "migration": "Events written by a job of a camelCase feature now carry metadata.feature with the raw feature name (for example \"pubSubOrders\") instead of the kebab-case form; update any audit or metrics filter that matched the kebab-case value. Custom Registry implementations must add getJobFeature(qualifiedJobName). A jobs:query:list call with filters on a field other than status is now rejected."
14
+ },
2
15
  {
3
16
  "version": "0.330.0",
4
17
  "type": "improvement",
@@ -1 +1,8 @@
1
- []
1
+ [
2
+ {
3
+ "version": "0.335.0",
4
+ "type": "fix",
5
+ "title": "Legal pages fail fast on custom routes without required blocks; seed race honours skip",
6
+ "detail": "`createLegalPagesFeature` now throws when custom `routes` cover no default required block and `requiredBlocks` is omitted, instead of failing the production boot check later. The text-block and system-template seed race loser now honours `ifExists: \"skip\"` and the no-op comparison. `SegmentedSelect` keeps the first segment tabbable when the stored value matches no option. The `subscribePathname` option documents that the `popstate` default misses `pushState` navigation."
7
+ }
8
+ ]
@@ -1,4 +1,9 @@
1
1
  [
2
+ {
3
+ "version": "0.335.0",
4
+ "type": "fix",
5
+ "title": "Anonymous queries rate-limit per IP and handler instead of sharing one bucket"
6
+ },
2
7
  {
3
8
  "version": "0.147.3",
4
9
  "type": "fix",
@@ -1,4 +1,10 @@
1
1
  [
2
+ {
3
+ "version": "0.335.0",
4
+ "type": "fix",
5
+ "title": "Metric queries reject offset time zones",
6
+ "detail": "Metric queries now reject offset time zones such as `+01:00` with a validation error. Postgres reads them with the opposite sign, so day buckets came back empty. Use IANA names like `Europe/Berlin`."
7
+ },
2
8
  {
3
9
  "version": "0.330.0",
4
10
  "type": "fix",
@@ -1,4 +1,9 @@
1
1
  [
2
+ {
3
+ "version": "0.335.0",
4
+ "type": "fix",
5
+ "title": "Rate-limiting error keys have de/en translations"
6
+ },
2
7
  {
3
8
  "version": "0.291.0",
4
9
  "type": "breaking",
@@ -1,4 +1,11 @@
1
1
  [
2
+ {
3
+ "version": "0.335.0",
4
+ "type": "breaking",
5
+ "title": "createSecretsFeature rejects access and roles together",
6
+ "detail": "`createSecretsFeature` throws at mount when both `access` and `roles` are passed; `access` used to silently discard `roles`. The personal-access-token mint form builds its i18n keys from the shared key helpers, and `ACTION_FORM_ENTITY` is now exported from `@cosmicdrift/kumiko-framework/ui-types`. The rate-limiting feature registers de/en copy for its error keys.",
7
+ "migration": "Remove `roles` from the `createSecretsFeature` options when `access` is set (or drop `access` and keep `roles`). Before, `roles` was silently ignored next to `access`."
8
+ },
2
9
  {
3
10
  "version": "0.275.0",
4
11
  "type": "breaking",
@@ -1,4 +1,9 @@
1
1
  [
2
+ {
3
+ "version": "0.335.0",
4
+ "type": "fix",
5
+ "title": "Session list loads one max-size page and renders no pager"
6
+ },
2
7
  {
3
8
  "version": "0.330.0",
4
9
  "type": "improvement",
@@ -1,4 +1,10 @@
1
1
  [
2
+ {
3
+ "version": "0.335.0",
4
+ "type": "fix",
5
+ "title": "Row-bound grant redemption logs a failing anchor lookup instead of swallowing it silently",
6
+ "detail": "`redeemRowBoundGrant` now logs a warning (error name and code only, never the token or subject) when the anchor lookup throws, so a database outage no longer looks like an expired link. The result is still the same bare rejection."
7
+ },
2
8
  {
3
9
  "version": "0.287.0",
4
10
  "type": "improvement",