@cosmicdrift/kumiko-bundled-features 0.365.0 → 0.366.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 (112) hide show
  1. package/dist/admin-shell/feature.js +9 -9
  2. package/dist/agent-tools/tool-dispatch.d.ts +9 -1
  3. package/dist/agent-tools/tool-dispatch.js +55 -5
  4. package/dist/audit/feature.js +1 -1
  5. package/dist/auth-email-password/constants.d.ts +0 -1
  6. package/dist/auth-email-password/constants.js +0 -2
  7. package/dist/auth-email-password/feature.js +3 -3
  8. package/dist/auth-email-password/handlers/invite-create.write.js +1 -0
  9. package/dist/auth-mfa/feature.js +4 -4
  10. package/dist/auth-mfa-user-data/index.js +1 -1
  11. package/dist/billing-foundation/consumer-protection/register-consent.js +1 -1
  12. package/dist/billing-foundation/feature.js +1 -1
  13. package/dist/cap-counter/cap-reservation.js +73 -25
  14. package/dist/cap-counter/constants.d.ts +1 -0
  15. package/dist/cap-counter/constants.js +2 -0
  16. package/dist/cap-counter/feature.js +2 -2
  17. package/dist/cap-counter/tables.js +5 -2
  18. package/dist/cap-overview/feature.js +3 -3
  19. package/dist/channel-discord/feature.js +2 -2
  20. package/dist/channel-email/feature.js +1 -1
  21. package/dist/channel-in-app/feature.js +1 -1
  22. package/dist/channel-push/feature.js +1 -1
  23. package/dist/channel-slack/feature.js +2 -2
  24. package/dist/channel-teams/feature.js +2 -2
  25. package/dist/channel-telegram/feature.js +2 -2
  26. package/dist/compliance-profiles/feature.js +1 -1
  27. package/dist/compliance-profiles-ops/index.js +3 -3
  28. package/dist/config/feature.js +1 -1
  29. package/dist/custom-fields/feature.js +2 -2
  30. package/dist/custom-fields/wire-for-entity.js +17 -13
  31. package/dist/data-retention/feature.js +1 -1
  32. package/dist/data-retention/presets.d.ts +4 -4
  33. package/dist/delivery/db/queries/address-opt-outs.d.ts +1 -1
  34. package/dist/delivery/db/queries/address-opt-outs.js +6 -2
  35. package/dist/delivery/db/queries/preferences.d.ts +1 -1
  36. package/dist/delivery/db/queries/preferences.js +4 -4
  37. package/dist/delivery/delivery-service.js +9 -2
  38. package/dist/delivery/feature.js +9 -5
  39. package/dist/delivery/handlers/resubscribe-address.write.js +13 -9
  40. package/dist/delivery/handlers/resubscribe-user.write.js +3 -1
  41. package/dist/delivery/handlers/set-preference.write.js +3 -1
  42. package/dist/delivery/handlers/unsubscribe-address.write.js +3 -1
  43. package/dist/delivery/handlers/unsubscribe-user.write.js +3 -1
  44. package/dist/delivery/notification-type-aliases.d.ts +3 -0
  45. package/dist/delivery/notification-type-aliases.js +11 -0
  46. package/dist/derivatives-sharp/feature.js +1 -1
  47. package/dist/document-ingest-foundation/feature.js +2 -2
  48. package/dist/file-derivatives/feature.js +3 -3
  49. package/dist/file-foundation/feature.js +1 -1
  50. package/dist/file-provider-inmemory/feature.js +2 -2
  51. package/dist/file-provider-s3/feature.js +3 -3
  52. package/dist/file-provider-s3-env/feature.js +2 -2
  53. package/dist/files-tenant-data/index.js +1 -1
  54. package/dist/folders/feature.js +2 -2
  55. package/dist/folders-user-data/index.js +1 -1
  56. package/dist/form-draft/feature.js +2 -2
  57. package/dist/form-draft-user-data/index.js +1 -1
  58. package/dist/inbound-mail-foundation/connect-routes.d.ts +3 -0
  59. package/dist/inbound-mail-foundation/connect-routes.js +6 -1
  60. package/dist/inbound-mail-foundation/feature.js +1 -1
  61. package/dist/inbound-mail-foundation/types.d.ts +17 -7
  62. package/dist/inbound-mail-foundation/watch-supervisor.js +16 -2
  63. package/dist/inbound-provider-imap/feature.js +2 -2
  64. package/dist/inbound-provider-inmemory/feature.js +1 -1
  65. package/dist/ledger/feature.js +5 -5
  66. package/dist/legal-pages/feature.js +1 -1
  67. package/dist/mail-foundation/feature.js +1 -1
  68. package/dist/mail-transport-inmemory/feature.js +2 -2
  69. package/dist/mail-transport-smtp/feature.js +3 -3
  70. package/dist/managed-pages/css-gate.js +1 -1
  71. package/dist/managed-pages/feature.js +2 -2
  72. package/dist/metrics/feature.js +2 -2
  73. package/dist/notes-history/feature.js +2 -2
  74. package/dist/notes-history-user-data/index.js +1 -1
  75. package/dist/personal-access-tokens/feature.js +17 -13
  76. package/dist/readiness/feature.js +2 -2
  77. package/dist/renderer-foundation/feature.js +1 -1
  78. package/dist/renderer-pdf/feature.js +1 -1
  79. package/dist/secrets/feature.js +1 -1
  80. package/dist/seo/feature.js +1 -1
  81. package/dist/sessions/feature.js +20 -16
  82. package/dist/step-dispatcher/constants.d.ts +10 -0
  83. package/dist/step-dispatcher/constants.js +17 -0
  84. package/dist/step-dispatcher/deliver-dispatch-request.d.ts +14 -0
  85. package/dist/step-dispatcher/deliver-dispatch-request.js +163 -0
  86. package/dist/step-dispatcher/feature.js +48 -101
  87. package/dist/step-dispatcher/webhook-runner.d.ts +7 -4
  88. package/dist/step-dispatcher/webhook-runner.js +26 -11
  89. package/dist/subscription-mollie/feature.js +1 -1
  90. package/dist/subscription-stripe/feature.js +3 -3
  91. package/dist/tags/feature.js +2 -2
  92. package/dist/template-resolver/feature.js +2 -2
  93. package/dist/template-resolver-user-data/index.js +1 -1
  94. package/dist/tenant/feature.js +4 -4
  95. package/dist/tenant/seeding.js +1 -1
  96. package/dist/tenant-lifecycle/feature.js +2 -3
  97. package/dist/tenant-lifecycle/stages.js +4 -1
  98. package/dist/tenant-settings/feature.js +1 -1
  99. package/dist/tier-engine/feature.js +86 -72
  100. package/dist/user/feature.js +1 -1
  101. package/dist/user-data-rights/feature.js +4 -4
  102. package/dist/user-data-rights-defaults/feature.js +2 -2
  103. package/dist/user-profile/feature.js +4 -4
  104. package/dist/waitlist/feature.js +2 -2
  105. package/package.json +10 -10
  106. package/src/agent-tools/changes.json +6 -0
  107. package/src/auth-email-password/changes.json +13 -0
  108. package/src/cap-counter/changes.json +7 -0
  109. package/src/crypto-shredding/changes.json +13 -0
  110. package/src/delivery/changes.json +6 -0
  111. package/src/inbound-mail-foundation/changes.json +13 -0
  112. package/src/step-dispatcher/changes.json +8 -1
@@ -126,9 +126,9 @@ export function createTierEngineFeature(opts = {}) {
126
126
  category: "billing",
127
127
  recommended: false,
128
128
  });
129
- r.requires("config");
130
- r.requires("tenant");
131
- r.entity("tier-assignment", tierAssignmentEntity);
129
+ r.requires({ features: ["config"] });
130
+ r.requires({ features: ["tenant"] });
131
+ r.entity({ name: "tier-assignment", ...tierAssignmentEntity });
132
132
  // Standard-CRUD via Helper.
133
133
  r.writeHandler(defineEntityCreateHandler("tier-assignment", tierAssignmentEntity, {
134
134
  ...writeAccess,
@@ -265,27 +265,35 @@ export function createTierEngineFeature(opts = {}) {
265
265
  };
266
266
  onTierAssigned.fn = applyAssignment;
267
267
  // Invalidation: tier-assignment CRUD events (e.g. the billing webhook) update the cache.
268
- r.hook("postSave", { allOf: "tier-assignment" }, async (result) => {
269
- // result.data has tenantId + tier (after entity-update merge)
270
- const data = result.data; // @cast-boundary engine-payload
271
- // skip: defensive type-guard auf payload-shape. Bei korrekt gerenderten
272
- // entity-events sind beide fields immer strings; ein malformed-payload
273
- // (custom-handler-bug) würde hier silent zum cache-skip führen statt
274
- // throwing — der lifecycle-pipeline darf nicht durch hook-fehler
275
- // blocken (afterCommit-pattern, side-effect-best-effort).
276
- if (typeof data.tenantId !== "string" || typeof data.tier !== "string")
277
- return;
278
- applyAssignment(data.tenantId, data.tier);
268
+ r.hook({
269
+ type: "postSave",
270
+ target: { allOf: "tier-assignment" },
271
+ handler: async (result) => {
272
+ // result.data has tenantId + tier (after entity-update merge)
273
+ const data = result.data; // @cast-boundary engine-payload
274
+ // skip: defensive guard on the payload shape. Correct entity events
275
+ // always carry both fields as strings; a malformed payload (a custom
276
+ // handler bug) skips the cache update instead of throwing, because a
277
+ // hook error must not block the lifecycle pipeline (afterCommit,
278
+ // best-effort side effect).
279
+ if (typeof data.tenantId !== "string" || typeof data.tier !== "string")
280
+ return;
281
+ applyAssignment(data.tenantId, data.tier);
282
+ },
279
283
  });
280
- r.hook("postDelete", { allOf: "tier-assignment" }, async (payload) => {
281
- const data = payload.data; // @cast-boundary engine-payload
282
- // skip: gleiche type-guard semantik wie postSave-hook oben.
283
- if (typeof data.tenantId !== "string")
284
- return;
285
- const tenantId = data.tenantId;
286
- takeTicket(tenantId);
287
- cache.delete(tenantId);
288
- publishAssignmentChange(tenantId);
284
+ r.hook({
285
+ type: "postDelete",
286
+ target: { allOf: "tier-assignment" },
287
+ handler: async (payload) => {
288
+ const data = payload.data; // @cast-boundary engine-payload
289
+ // skip: gleiche type-guard semantik wie postSave-hook oben.
290
+ if (typeof data.tenantId !== "string")
291
+ return;
292
+ const tenantId = data.tenantId;
293
+ takeTicket(tenantId);
294
+ cache.delete(tenantId);
295
+ publishAssignmentChange(tenantId);
296
+ },
289
297
  });
290
298
  // Auto-default-tier-on-tenant-signup: hook fires inTransaction (atomic
291
299
  // mit tenant-create rollback), schreibt tier-assignment via Direct-
@@ -302,55 +310,61 @@ export function createTierEngineFeature(opts = {}) {
302
310
  if (opts.defaultTier !== undefined) {
303
311
  const defaultTier = opts.defaultTier;
304
312
  const autoDefaultTierHookReason = "seeds the default tier-assignment stream of the tenant just created";
305
- r.hook("postSave", { allOf: "tenant" }, async (result, ctx) => {
306
- // result-shape: kumiko-framework's SaveContext mit isNew + data
307
- const saveResult = result; // @cast-boundary engine-payload
308
- // skip: nur bei tenant-create (initial) — tenant-updates feuern
309
- // auch postSave aber wir wollen kein neues tier-assignment bei
310
- // re-keying oder name-update.
311
- if (saveResult.isNew !== true)
312
- return;
313
- const data = saveResult.data; // @cast-boundary engine-payload
314
- // skip: defensive type-guard. Tenant-entity hat id zwingend, aber
315
- // CrudExecutor's payload-shape ist runtime-unknown.
316
- if (typeof data.id !== "string")
317
- return;
318
- const newTenantId = data.id; // @cast-boundary engine-payload
319
- const aggregateId = tierAssignmentAggregateId(newTenantId);
320
- // tenant is an r.systemScope() feature, so this cross-feature hook gets a
321
- // fail-closed ctx.db — reach for ctx.systemDb instead (see entity-handlers.ts).
322
- // Event-store paths need the raw DbConnection — TenantDb only exposes
323
- // select/insert/update/delete, not execute (db.execute(sql`SELECT pg_notify(...)`) would TypeError).
324
- const rawDb = ctx.systemDb
325
- ? ctx.systemDb.unsafeRaw(autoDefaultTierHookReason)
326
- : ctx.db && "unsafeRaw" in ctx.db
327
- ? ctx.db.unsafeRaw()
328
- : undefined;
329
- // skip: defensive — inTransaction phase always sets db, but AppContext's
330
- // type makes it optional. Throwing would be overreach (lifecycle
331
- // blocking), silent-skip is defensive-soft.
332
- if (!rawDb)
333
- return;
334
- // Idempotency: stream-existence-check vor create. Pattern aus
335
- // seedTenant.ts. Bei re-replay (rebuild) nicht versionsbumpen.
336
- const streamVersion = await getUnscopedAggregateStreamMaxVersion(rawDb, aggregateId);
337
- // skip: idempotent — tier-assignment stream already seeded (rebuild/replay).
338
- if (streamVersion > 0)
339
- return;
340
- // SystemUser für den NEUEN tenant — der Hook wird vom signup-
341
- // user (anderer tenant, oder SystemAdmin) ausgelöst, aber das
342
- // tier-assignment muss im stream des neu-erzeugten tenants
343
- // landen (= aggregate-id deterministic auf newTenantId). Memory
344
- // `feedback_event_store_tenant_consistency`: by.tenantId muss =
345
- // ziel-tenant.
346
- const systemUser = {
347
- id: "00000000-0000-4000-8000-000000000001",
348
- tenantId: newTenantId,
349
- roles: ["SystemAdmin"],
350
- };
351
- const tdb = createTenantDb(rawDb, newTenantId, "system");
352
- await tierAssignmentExecutor.create({ id: aggregateId, tier: defaultTier, source: TierAssignmentSources.default }, systemUser, tdb);
353
- }, { phase: HookPhases.inTransaction, escapeHatch: { reason: autoDefaultTierHookReason } });
313
+ r.hook({
314
+ type: "postSave",
315
+ target: { allOf: "tenant" },
316
+ handler: async (result, ctx) => {
317
+ // result-shape: kumiko-framework's SaveContext mit isNew + data
318
+ const saveResult = result; // @cast-boundary engine-payload
319
+ // skip: nur bei tenant-create (initial) — tenant-updates feuern
320
+ // auch postSave aber wir wollen kein neues tier-assignment bei
321
+ // re-keying oder name-update.
322
+ if (saveResult.isNew !== true)
323
+ return;
324
+ const data = saveResult.data; // @cast-boundary engine-payload
325
+ // skip: defensive type-guard. Tenant-entity hat id zwingend, aber
326
+ // CrudExecutor's payload-shape ist runtime-unknown.
327
+ if (typeof data.id !== "string")
328
+ return;
329
+ const newTenantId = data.id; // @cast-boundary engine-payload
330
+ const aggregateId = tierAssignmentAggregateId(newTenantId);
331
+ // tenant is an r.systemScope() feature, so this cross-feature hook gets a
332
+ // fail-closed ctx.db — reach for ctx.systemDb instead (see entity-handlers.ts).
333
+ // Event-store paths need the raw DbConnection — TenantDb only exposes
334
+ // select/insert/update/delete, not execute (db.execute(sql`SELECT pg_notify(...)`) would TypeError).
335
+ const rawDb = ctx.systemDb
336
+ ? ctx.systemDb.unsafeRaw(autoDefaultTierHookReason)
337
+ : ctx.db && "unsafeRaw" in ctx.db
338
+ ? ctx.db.unsafeRaw()
339
+ : undefined;
340
+ // skip: defensive — inTransaction phase always sets db, but AppContext's
341
+ // type makes it optional. Throwing would be overreach (lifecycle
342
+ // blocking), silent-skip is defensive-soft.
343
+ if (!rawDb)
344
+ return;
345
+ // Idempotency: stream-existence-check vor create. Pattern aus
346
+ // seedTenant.ts. Bei re-replay (rebuild) nicht versionsbumpen.
347
+ const streamVersion = await getUnscopedAggregateStreamMaxVersion(rawDb, aggregateId);
348
+ // skip: idempotent — tier-assignment stream already seeded (rebuild/replay).
349
+ if (streamVersion > 0)
350
+ return;
351
+ // SystemUser für den NEUEN tenant — der Hook wird vom signup-
352
+ // user (anderer tenant, oder SystemAdmin) ausgelöst, aber das
353
+ // tier-assignment muss im stream des neu-erzeugten tenants
354
+ // landen (= aggregate-id deterministic auf newTenantId). Memory
355
+ // `feedback_event_store_tenant_consistency`: by.tenantId muss =
356
+ // ziel-tenant.
357
+ const systemUser = {
358
+ id: "00000000-0000-4000-8000-000000000001",
359
+ tenantId: newTenantId,
360
+ roles: ["SystemAdmin"],
361
+ };
362
+ const tdb = createTenantDb(rawDb, newTenantId, "system");
363
+ await tierAssignmentExecutor.create({ id: aggregateId, tier: defaultTier, source: TierAssignmentSources.default }, systemUser, tdb);
364
+ },
365
+ phase: HookPhases.inTransaction,
366
+ escapeHatch: { reason: autoDefaultTierHookReason },
367
+ });
354
368
  }
355
369
  // Extension-point declaration + self-registration. Pattern analog
356
370
  // mail-foundation/file-foundation: das feature deklariert den
@@ -21,7 +21,7 @@ export function createUserFeature() {
21
21
  recommended: true,
22
22
  });
23
23
  r.systemScope();
24
- r.entity("user", userEntity);
24
+ r.entity({ name: "user", ...userEntity });
25
25
  // Self-extension: `user` declares AND fulfils principalStatus (precedent:
26
26
  // tier-engine/feature.ts) — wires the blocked-principal check into every stack that mounts `user`.
27
27
  r.extendsRegistrar(EXT_PRINCIPAL_STATUS, {});
@@ -56,7 +56,7 @@ export function createUserDataRightsFeature(opts = {}) {
56
56
  category: "compliance",
57
57
  recommended: false,
58
58
  });
59
- r.requires("user", "data-retention", "compliance-profiles", "sessions");
59
+ r.requires({ features: ["user", "data-retention", "compliance-profiles", "sessions"] });
60
60
  r.usesApi("compliance.forTenant");
61
61
  r.usesApi("retention.policyFor");
62
62
  // S2.U6 — restrict-account ruft sessions.revokeAllForUser cross-feature.
@@ -92,13 +92,13 @@ export function createUserDataRightsFeature(opts = {}) {
92
92
  env: "TENANT_MODEL",
93
93
  }));
94
94
  // S2.U3 Atom 1b — ExportJob-Lifecycle-Entity.
95
- r.entity("export-job", exportJobEntity);
95
+ r.entity({ name: "export-job", ...exportJobEntity });
96
96
  // S2.U3 Atom 4a — Download-Token-Entity. Worker generiert Token beim
97
97
  // Flip auf done (siehe run-export-jobs.ts). Hash in DB, plain im
98
98
  // RunExportJobsResult fuer Atom 5 (Notification per Email).
99
- r.entity("export-download-token", exportDownloadTokenEntity);
99
+ r.entity({ name: "export-download-token", ...exportDownloadTokenEntity });
100
100
  // S2.U7 — Audit-Trail invalid Download-Attempts (DPO Brute-Force-Detection).
101
- r.entity("download-attempt", downloadAttemptEntity);
101
+ r.entity({ name: "download-attempt", ...downloadAttemptEntity });
102
102
  // S2.U6 — DSGVO Art. 18 Account-Freeze (Verarbeitungs-Pause).
103
103
  // Endpoints fuer Restrict + Lift. Login-Block fuer Restricted Users
104
104
  // lebt in auth-email-password/login.write.ts.
@@ -14,7 +14,7 @@ import { userSessionDeleteHook, userSessionExportHook, } from "./hooks/user-sess
14
14
  // `user` (S2.H1) und `fileRef` (S2.H2).
15
15
  //
16
16
  // Architektur-Entscheidung (S2.H1+H2): user-data-rights selbst kann
17
- // nicht r.requires("user", "files") + r.useExtension(EXT_USER_DATA, ...)
17
+ // nicht r.requires({ features: ["user", "files"] }) + r.useExtension(EXT_USER_DATA, ...)
18
18
  // machen weil es selbst Provider von EXT_USER_DATA ist (Boot-Validator
19
19
  // lehnt self-extension ab). Lösung: drittes optional-mountbares Feature
20
20
  // das requires beide Sources + die useExtension-Calls macht.
@@ -40,7 +40,7 @@ export function createUserDataRightsDefaultsFeature() {
40
40
  category: "compliance",
41
41
  recommended: false,
42
42
  });
43
- r.requires("user", "files", "user-data-rights");
43
+ r.requires({ features: ["user", "files", "user-data-rights"] });
44
44
  // Optional sources: hooks are registered unconditionally but each one
45
45
  // no-ops at runtime when its source feature isn't mounted (see
46
46
  // hooks/feature-mounted.ts) — the export runner has no per-hook
@@ -22,10 +22,10 @@ export function createUserProfileFeature(options) {
22
22
  category: "identity",
23
23
  recommended: true,
24
24
  });
25
- r.requires("user");
26
- r.requires("auth-email-password");
27
- r.requires("user-data-rights");
28
- r.requires("user-data-rights-defaults");
25
+ r.requires({ features: ["user"] });
26
+ r.requires({ features: ["auth-email-password"] });
27
+ r.requires({ features: ["user-data-rights"] });
28
+ r.requires({ features: ["user-data-rights-defaults"] });
29
29
  const handlers = allowEmailChange ? { changeEmail: r.writeHandler(changeEmailWrite) } : {};
30
30
  // Boot-validator requires a "screen:<id>.title" key per r.screen(), plus
31
31
  // one entry per i18nKey()-marked dot-form label below — kept inline (not
@@ -15,7 +15,7 @@ export function createWaitlistFeature(opts = {}) {
15
15
  r.describe("Public waitlist: an anonymous, rate-limited `submit` handler collects name, email and optional company, portfolio and message (a honeypot field and per-email dedupe keep bots and repeat submits out, and the response body is identical whether or not an address was already listed), and mails the submitter a confirmation. SystemAdmins review entries in a list screen and invite or reject them; an invite creates the entrant's own tenant (or joins one shared tenant) and mails an accept link with the configured membership role, never a global role. Entries are platform-wide, their personal data is crypto-shredding-ready, and the EXT_USER_DATA hooks export and erase entries matching a user's email.");
16
16
  r.uiHints({ displayLabel: "Waitlist", category: "data", recommended: false });
17
17
  r.systemScope();
18
- r.requires("tenant", "auth-email-password", "user-data-rights");
18
+ r.requires({ features: ["tenant", "auth-email-password", "user-data-rights"] });
19
19
  r.translations({ keys: WAITLIST_I18N });
20
20
  r.notification("confirmation", {
21
21
  variables: WAITLIST_CONFIRMATION_VARIABLES,
@@ -26,7 +26,7 @@ export function createWaitlistFeature(opts = {}) {
26
26
  record: WAITLIST_ADMIN_NOTICE_RECORD,
27
27
  localizedTemplate: waitlistAdminNoticeTemplate,
28
28
  });
29
- r.entity("waitlistEntry", waitlistEntryEntity);
29
+ r.entity({ name: "waitlistEntry", ...waitlistEntryEntity });
30
30
  r.writeHandler(createSubmitHandler(opts));
31
31
  r.writeHandler(createInviteHandler(opts.invite));
32
32
  r.writeHandler(rejectHandler);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-bundled-features",
3
- "version": "0.365.0",
3
+ "version": "0.366.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>",
@@ -524,12 +524,12 @@
524
524
  }
525
525
  },
526
526
  "dependencies": {
527
- "@cosmicdrift/kumiko-dispatcher-live": "0.365.0",
528
- "@cosmicdrift/kumiko-framework": "0.365.0",
529
- "@cosmicdrift/kumiko-headless": "0.365.0",
530
- "@cosmicdrift/kumiko-renderer": "0.365.0",
531
- "@cosmicdrift/kumiko-renderer-web": "0.365.0",
532
- "@cosmicdrift/kumiko-types": "0.365.0",
527
+ "@cosmicdrift/kumiko-dispatcher-live": "0.366.0",
528
+ "@cosmicdrift/kumiko-framework": "0.366.0",
529
+ "@cosmicdrift/kumiko-headless": "0.366.0",
530
+ "@cosmicdrift/kumiko-renderer": "0.366.0",
531
+ "@cosmicdrift/kumiko-renderer-web": "0.366.0",
532
+ "@cosmicdrift/kumiko-types": "0.366.0",
533
533
  "@mollie/api-client": "^4.5.0",
534
534
  "@node-rs/argon2": "^2.0.2",
535
535
  "@tiptap/core": "3.31.3",
@@ -1078,9 +1078,9 @@
1078
1078
  ],
1079
1079
  "devDependencies": {
1080
1080
  "@testing-library/user-event": "^14.6.1",
1081
- "@cosmicdrift/kumiko-locale-de": "0.365.0",
1082
- "@cosmicdrift/kumiko-locale-es": "0.365.0",
1083
- "@cosmicdrift/kumiko-renderer-mail-html": "0.365.0",
1081
+ "@cosmicdrift/kumiko-locale-de": "0.366.0",
1082
+ "@cosmicdrift/kumiko-locale-es": "0.366.0",
1083
+ "@cosmicdrift/kumiko-renderer-mail-html": "0.366.0",
1084
1084
  "jsqr": "^1.4.0",
1085
1085
  "unpdf": "^1.8.1"
1086
1086
  }
@@ -1,4 +1,10 @@
1
1
  [
2
+ {
3
+ "version": "0.366.0",
4
+ "type": "improvement",
5
+ "title": "Agent tool failures are classified instead of passing raw messages through",
6
+ "detail": "dispatchToolCall now adds a code to failed tool results. Client errors keep their message, rate limits also set retryable and retryAfterSeconds. Server errors and unknown throws are logged with their cause and returned to the model as \"Tool failed unexpectedly\", so internal details such as database messages no longer reach the model. ToolCallRequest accepts an optional log."
7
+ },
2
8
  {
3
9
  "version": "0.353.0",
4
10
  "type": "fix",
@@ -1,4 +1,17 @@
1
1
  [
2
+ {
3
+ "version": "0.366.0",
4
+ "type": "fix",
5
+ "title": "Removed unused AuthHandlers.inviteCancel constant",
6
+ "detail": "AuthHandlers.inviteCancel pointed at auth-email-password:write:invite-cancel, a handler that never existed. The constant is removed.",
7
+ "migration": "If you referenced AuthHandlers.inviteCancel, use tenant:write:cancel-invitation."
8
+ },
9
+ {
10
+ "version": "0.366.0",
11
+ "type": "fix",
12
+ "title": "invite-create is no longer exposed to agents",
13
+ "detail": "The admin invite-create handler is now opted out of the agent tool catalog with agent: { expose: false }, like the other invite handlers, because it mails an accept link to an arbitrary address."
14
+ },
2
15
  {
3
16
  "version": "0.365.0",
4
17
  "type": "fix",
@@ -1,4 +1,11 @@
1
1
  [
2
+ {
3
+ "version": "0.366.0",
4
+ "type": "fix",
5
+ "title": "cap-counter calendar settle is idempotent",
6
+ "detail": "Settling a calendar cap reservation now keeps the row as a tombstone (new column store_cap_reservations.settled_at, retained for 24 hours) instead of deleting it. A repeated settle with the same reservation id is a no-op, and confirm, give-back and expiry sweep ignore settled rows. The sweep deletes expired tombstones.",
7
+ "migration": "Apps with cap-counter generate a migration for the new column store_cap_reservations.settled_at (kumiko migrate generate)."
8
+ },
2
9
  {
3
10
  "version": "0.365.0",
4
11
  "type": "breaking",
@@ -1,4 +1,17 @@
1
1
  [
2
+ {
3
+ "version": "0.366.0",
4
+ "type": "fix",
5
+ "title": "forget-subject is covered for users stored before the KMS and users without an event stream",
6
+ "detail": "Tests now cover forget-subject for users stored as plaintext before the KMS was configured and for users without an event stream. Both succeed since the KMS tombstone fix, close the login door and write the audit event."
7
+ },
8
+ {
9
+ "version": "0.365.1",
10
+ "type": "fix",
11
+ "title": "eraseKey leaves a tombstone for subjects that never had a key",
12
+ "detail": "eraseKey now records an erased entry even when the subject has no key (pre-KMS plaintext). A later createKey or write for that subject fails with subject_erased instead of minting a fresh key and re-encrypting the plaintext. Erase flows must write with writePurpose erasure after eraseKey. kumiko_subject_keys now gets one tombstone row per forgotten record subject and per step dispatch. The tenant destruction stage tenant-row now fails loudly when marking the tenant destroyed does not succeed.",
13
+ "migration": "Run backfill-pii before erasing Pre-KMS data, otherwise untouched plaintext stays readable after eraseKey."
14
+ },
2
15
  {
3
16
  "version": "0.364.0",
4
17
  "type": "fix",
@@ -1,4 +1,10 @@
1
1
  [
2
+ {
3
+ "version": "0.366.0",
4
+ "type": "improvement",
5
+ "title": "Renaming a notification type keeps user preferences and address opt-outs",
6
+ "detail": "r.notification accepts renamedFrom, a list of former qualified names. Preferences and address opt-outs stored under a former name keep applying to the renamed notification, and a stored choice under the current name wins over one under a former name. New writes via setPreference and the unsubscribe and resubscribe handlers are stored under the current name, resubscribeAddress also lifts opt-outs under former names. Boot fails if an alias is a current notification name, is the wildcard, or is claimed by two notifications."
7
+ },
2
8
  {
3
9
  "version": "0.365.0",
4
10
  "type": "fix",
@@ -1,4 +1,17 @@
1
1
  [
2
+ {
3
+ "version": "0.366.0",
4
+ "type": "improvement",
5
+ "title": "Inbound mail fetch can skip already-seen message ids",
6
+ "detail": "InboundMailProviderPlugin.fetch receives an optional filterUnknownMessageIds(providerMessageIds) hook. The watch supervisor implements it with a tenant- and account-scoped lookup of already-seen provider message ids and returns the ids not seen yet, so a plugin can avoid downloading messages it has already processed."
7
+ },
8
+ {
9
+ "version": "0.366.0",
10
+ "type": "breaking",
11
+ "title": "InboundMailContext exposes metricsFor to provider plugins",
12
+ "detail": "InboundMailContext gains an optional metricsFor(featureName) returning a MetricsHandle. runProdApp, the worker app and the dev server wire it from the observability meter, so provider plugins can emit their own metrics through the same pipeline.",
13
+ "migration": "Code that builds SystemWireDeps itself adds metricsFor (createMetricsForFactory(meter))."
14
+ },
2
15
  {
3
16
  "version": "0.364.0",
4
17
  "type": "fix",
@@ -1,4 +1,11 @@
1
1
  [
2
+ {
3
+ "version": "0.365.1",
4
+ "type": "fix",
5
+ "title": "step-dispatcher delivers each dispatch as a retried job, so a slow receiver no longer delays other tenants",
6
+ "detail": "The step-dispatcher projection now only enqueues one `step-dispatcher:job:deliver` job per dispatch request, and the job performs the webhook or mail delivery. Redis holds only the dispatch stream id and the tenant, never the request payload. Transient failures (network error, timeout, 5xx, 408, 429, any mail adapter failure) are retried 3 times with exponential backoff; the outcome event and the payload key erase follow after the final attempt. Permanent failures (invalid url, blocked host, other 4xx, unavailable auth secret) are recorded at once. At most 2 deliveries per tenant run at the same time, so one tenant's slow receivers cannot hold up the others. This corrects the 0.346.0 note that a hanging receiver no longer stalls the dispatcher for every tenant: the shared consumer still delivered one request at a time.",
7
+ "migration": "step-dispatcher now needs a job runner (Redis) and a worker consuming the default worker lane. Without a job runner the projection throws instead of delivering. Test stacks must pass `jobs` to setupTestStack and call `drainJobs()` after the event dispatcher ran. `r.job` accepts `maxPerTenant: { limit, onLimit: \"defer\", deferMs? }`; a plain number still drops excess enqueues. A `webhook.send` request that gets a retryable status (5xx, 408, 429) is now sent up to 4 times under the same Idempotency-Key. Worker pods must know the deliver job before the projection enqueues it, so deploy worker and API together or the worker first. A dev server only consumes jobs when the app mounts `createJobsFeature()`."
8
+ },
2
9
  {
3
10
  "version": "0.347.0",
4
11
  "type": "fix",
@@ -21,7 +28,7 @@
21
28
  "version": "0.346.0",
22
29
  "type": "fix",
23
30
  "title": "Webhook requests time out after 10 seconds instead of blocking the dispatcher",
24
- "detail": "Webhook requests time out after 10 seconds\nA hanging receiver now ends as `step.dispatch-failed` and no longer stalls the step-dispatcher for every tenant."
31
+ "detail": "Webhook requests time out after 10 seconds\nA hanging receiver now ends as `step.dispatch-failed` after the timeout instead of waiting indefinitely. Until the step-dispatcher delivered through jobs, the shared consumer still processed requests one at a time, so a slow receiver of one tenant delayed the others."
25
32
  },
26
33
  {
27
34
  "version": "0.346.0",