@cosmicdrift/kumiko-bundled-features 0.334.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 (242) 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/auth-email-password/handlers/change-password.write.js +2 -0
  12. package/dist/auth-email-password/handlers/invite-create.write.js +12 -2
  13. package/dist/auth-email-password/handlers/login.write.js +2 -0
  14. package/dist/auth-email-password/handlers/self-registration-status.query.js +1 -1
  15. package/dist/auth-email-password/i18n.js +2 -0
  16. package/dist/auth-email-password/web/auth-client.d.ts +1 -0
  17. package/dist/auth-email-password/web/auth-client.js +1 -1
  18. package/dist/auth-email-password/web/auth-gate.js +6 -8
  19. package/dist/auth-email-password/web/index.d.ts +1 -1
  20. package/dist/auth-email-password/web/index.js +1 -1
  21. package/dist/auth-email-password/web/invite-accept-screen.js +16 -12
  22. package/dist/auth-email-password/web/login-screen.js +1 -1
  23. package/dist/auth-email-password/web/session-bootstrap-error.d.ts +5 -1
  24. package/dist/auth-email-password/web/session-bootstrap-error.js +2 -2
  25. package/dist/auth-email-password/web/session.d.ts +4 -0
  26. package/dist/auth-email-password/web/session.js +15 -0
  27. package/dist/auth-mfa/feature.js +3 -2
  28. package/dist/auth-mfa/handlers/enable-confirm-preauth.write.js +2 -2
  29. package/dist/auth-mfa/handlers/verify.write.js +2 -0
  30. package/dist/auth-mfa/hmac-token-codec.js +3 -0
  31. package/dist/auth-mfa/mfa-challenge-token.js +4 -0
  32. package/dist/auth-mfa/mfa-preauth-setup-token.js +3 -0
  33. package/dist/auth-mfa/web/mfa-client.d.ts +1 -1
  34. package/dist/auth-mfa/web/mfa-client.js +4 -10
  35. package/dist/billing-foundation/billing-info-query.js +1 -1
  36. package/dist/billing-foundation/checkout-core.d.ts +5 -5
  37. package/dist/billing-foundation/checkout-core.js +10 -6
  38. package/dist/billing-foundation/handlers/switch-plan.write.js +5 -8
  39. package/dist/cap-counter/book-cap-usage.js +15 -5
  40. package/dist/cap-counter/enforce-cap.js +9 -3
  41. package/dist/cap-counter/feature.js +3 -2
  42. package/dist/cap-counter/i18n.d.ts +1 -0
  43. package/dist/cap-counter/i18n.js +6 -5
  44. package/dist/cap-overview/handlers/caps-usage.query.js +9 -16
  45. package/dist/cap-overview/handlers/tenant-caps-list.query.js +41 -15
  46. package/dist/cap-overview/i18n.js +5 -0
  47. package/dist/cap-overview/web/cap-cards-panel.js +4 -1
  48. package/dist/cap-overview/web/cap-usage-bar.js +1 -1
  49. package/dist/channel-email/email-channel.js +12 -1
  50. package/dist/channel-in-app/tables.js +2 -6
  51. package/dist/compliance-profiles/handlers/sub-processors.query.js +1 -1
  52. package/dist/config/db/queries/resolver.js +3 -1
  53. package/dist/config/handlers/set.write.js +8 -4
  54. package/dist/config/web/i18n.js +1 -0
  55. package/dist/config/write-helpers.d.ts +1 -1
  56. package/dist/config/write-helpers.js +11 -5
  57. package/dist/crypto-shredding/handlers/forget-subject.write.js +15 -8
  58. package/dist/custom-fields/constants.js +1 -4
  59. package/dist/data-retention/resolve-for-tenant.d.ts +2 -3
  60. package/dist/data-retention/run-retention-cleanup.js +100 -44
  61. package/dist/delivery/types.d.ts +2 -1
  62. package/dist/delivery/types.js +7 -0
  63. package/dist/delivery/unsubscribe.js +5 -11
  64. package/dist/derivatives-sharp/render.js +27 -9
  65. package/dist/document-ingest-foundation/feature.js +5 -5
  66. package/dist/document-ingest-foundation/providers.d.ts +1 -0
  67. package/dist/document-ingest-foundation/providers.js +6 -0
  68. package/dist/document-ingest-foundation/write-document-extract.d.ts +2 -2
  69. package/dist/document-ingest-foundation/write-document-extract.js +10 -0
  70. package/dist/feature-toggles/global-feature-state-table.js +2 -7
  71. package/dist/feature-toggles/toggle-sync-signal.d.ts +2 -1
  72. package/dist/feature-toggles/toggle-sync-signal.js +3 -2
  73. package/dist/files-tenant-data/index.js +3 -2
  74. package/dist/folders/handlers/clear-folder.write.js +4 -11
  75. package/dist/folders/handlers/set-folder.write.js +4 -10
  76. package/dist/form-draft/feature.js +3 -2
  77. package/dist/form-draft/handlers/discard.write.js +7 -8
  78. package/dist/form-draft/handlers/get.query.js +7 -1
  79. package/dist/foundation-shared/host-allowlist.d.ts +4 -0
  80. package/dist/foundation-shared/host-allowlist.js +14 -0
  81. package/dist/foundation-shared/index.d.ts +1 -0
  82. package/dist/foundation-shared/index.js +1 -0
  83. package/dist/foundation-shared/mail-host-policy.d.ts +0 -2
  84. package/dist/foundation-shared/mail-host-policy.js +3 -13
  85. package/dist/inbound-mail-foundation/connect-routes.js +7 -5
  86. package/dist/inbound-mail-foundation/watch-supervisor.js +47 -43
  87. package/dist/jobs/handlers/list.query.d.ts +7 -2
  88. package/dist/jobs/handlers/list.query.js +9 -2
  89. package/dist/jobs/handlers/retry.write.js +3 -2
  90. package/dist/jobs/handlers/tenant-failures.query.js +3 -9
  91. package/dist/jobs/job-run-logger.js +18 -2
  92. package/dist/jobs/tenant-job-failure-table.js +2 -6
  93. package/dist/ledger/handlers/confirm-schedule-period.write.js +2 -2
  94. package/dist/ledger/handlers/reverse-transaction.write.js +2 -2
  95. package/dist/ledger/subject-reference.d.ts +4 -0
  96. package/dist/ledger/subject-reference.js +7 -0
  97. package/dist/legal-pages/feature.js +8 -1
  98. package/dist/managed-pages/handlers/branding.query.js +1 -1
  99. package/dist/managed-pages/handlers/by-slug.query.js +1 -1
  100. package/dist/managed-pages/handlers/by-tenant-published.query.js +1 -1
  101. package/dist/metrics/compute.js +7 -1
  102. package/dist/metrics/window.js +5 -0
  103. package/dist/notes-history/handlers/add-note.write.js +4 -9
  104. package/dist/notes-history-user-data/hooks.js +8 -1
  105. package/dist/personal-access-tokens/constants.d.ts +1 -0
  106. package/dist/personal-access-tokens/constants.js +1 -0
  107. package/dist/personal-access-tokens/feature.js +2 -2
  108. package/dist/personal-access-tokens/handlers/create.write.js +2 -2
  109. package/dist/personal-access-tokens/handlers/revoke.write.js +2 -2
  110. package/dist/personal-access-tokens/i18n.js +12 -13
  111. package/dist/personal-access-tokens/schema/api-token.js +2 -1
  112. package/dist/personal-access-tokens/screens.js +5 -1
  113. package/dist/rate-limiting/feature.js +2 -0
  114. package/dist/rate-limiting/handlers/bucket-access.js +15 -7
  115. package/dist/rate-limiting/i18n.d.ts +10 -0
  116. package/dist/rate-limiting/i18n.js +10 -0
  117. package/dist/secrets/feature.js +3 -2
  118. package/dist/seo/handlers/seo-config.query.js +1 -1
  119. package/dist/sessions/constants.d.ts +1 -0
  120. package/dist/sessions/constants.js +1 -0
  121. package/dist/sessions/feature.js +5 -3
  122. package/dist/sessions/handlers/revoke-all-for-user.write.d.ts +1 -0
  123. package/dist/sessions/handlers/revoke-all-for-user.write.js +12 -2
  124. package/dist/sessions/handlers/revoke.write.js +2 -2
  125. package/dist/sessions/schema/user-session.js +2 -1
  126. package/dist/sessions/screens.js +5 -1
  127. package/dist/sessions/session-callbacks.js +2 -2
  128. package/dist/shared/index.d.ts +1 -1
  129. package/dist/shared/index.js +1 -1
  130. package/dist/shared/lockout-counter.js +1 -1
  131. package/dist/shared/parent-visibility.d.ts +5 -0
  132. package/dist/shared/parent-visibility.js +17 -1
  133. package/dist/shared/row-bound-grant.js +20 -9
  134. package/dist/shared/single-use-token-store.js +5 -13
  135. package/dist/step-dispatcher/webhook-runner.d.ts +0 -2
  136. package/dist/step-dispatcher/webhook-runner.js +18 -17
  137. package/dist/subscription-mollie/plugin-methods.d.ts +3 -2
  138. package/dist/subscription-mollie/plugin-methods.js +7 -1
  139. package/dist/subscription-mollie/verify-webhook.js +6 -0
  140. package/dist/subscription-stripe/plugin-methods.js +25 -6
  141. package/dist/subscription-stripe/verify-webhook.js +13 -6
  142. package/dist/tags/handlers/assign-tag.write.js +4 -10
  143. package/dist/tags/handlers/remove-tag.write.js +4 -11
  144. package/dist/template-resolver/api.d.ts +2 -1
  145. package/dist/template-resolver/api.js +1 -1
  146. package/dist/template-resolver/handlers/by-slug.query.d.ts +1 -1
  147. package/dist/template-resolver/handlers/by-slug.query.js +2 -2
  148. package/dist/template-resolver/handlers/by-tenant.query.d.ts +3 -2
  149. package/dist/template-resolver/handlers/by-tenant.query.js +2 -2
  150. package/dist/template-resolver/handlers/collection-item.query.d.ts +1 -1
  151. package/dist/template-resolver/handlers/collection-list.query.d.ts +1 -1
  152. package/dist/template-resolver/handlers/collection-shared.d.ts +3 -2
  153. package/dist/template-resolver/handlers/collection-shared.js +1 -1
  154. package/dist/template-resolver/handlers/find-by-id.query.d.ts +1 -1
  155. package/dist/template-resolver/handlers/find-by-id.query.js +1 -1
  156. package/dist/template-resolver/handlers/list.query.d.ts +1 -1
  157. package/dist/template-resolver/handlers/list.query.js +1 -1
  158. package/dist/template-resolver/seeding.js +41 -39
  159. package/dist/template-resolver/table.d.ts +8 -5
  160. package/dist/template-resolver/testing.js +2 -2
  161. package/dist/template-resolver/user-content-table.d.ts +2 -5
  162. package/dist/template-resolver/web/client-plugin.d.ts +1 -1
  163. package/dist/template-resolver-user-data/hooks.js +1 -1
  164. package/dist/tenant/cancel-pending-invitation.d.ts +8 -0
  165. package/dist/tenant/cancel-pending-invitation.js +22 -0
  166. package/dist/tenant/feature.js +4 -2
  167. package/dist/tenant/handlers/cancel-invitation.write.js +6 -21
  168. package/dist/tenant/handlers/member-directory.query.d.ts +1 -0
  169. package/dist/tenant/handlers/member-directory.query.js +17 -4
  170. package/dist/tenant/handlers/remove-member.write.js +9 -14
  171. package/dist/tenant/handlers/team-list.query.js +2 -2
  172. package/dist/tenant/handlers/tenant-directory.query.d.ts +1 -0
  173. package/dist/tenant/handlers/tenant-directory.query.js +13 -3
  174. package/dist/tenant/handlers/update-member-roles.write.d.ts +1 -1
  175. package/dist/tenant/handlers/update-member-roles.write.js +28 -12
  176. package/dist/tenant/i18n.js +4 -0
  177. package/dist/tenant/index.d.ts +1 -1
  178. package/dist/tenant/index.js +1 -1
  179. package/dist/tenant/last-tenant-admin.d.ts +1 -1
  180. package/dist/tenant/last-tenant-admin.js +5 -3
  181. package/dist/tenant/seeding.d.ts +4 -0
  182. package/dist/tenant/seeding.js +9 -2
  183. package/dist/tenant-lifecycle/boot-checks.d.ts +1 -0
  184. package/dist/tenant-lifecycle/boot-checks.js +27 -1
  185. package/dist/tenant-lifecycle/feature.js +2 -1
  186. package/dist/tenant-lifecycle/stages.js +2 -2
  187. package/dist/tier-engine/feature.js +3 -2
  188. package/dist/user/command-schemas.d.ts +2 -2
  189. package/dist/user/handlers/create.write.d.ts +1 -1
  190. package/dist/user/handlers/create.write.js +8 -2
  191. package/dist/user/handlers/update.write.d.ts +1 -1
  192. package/dist/user/handlers/update.write.js +8 -2
  193. package/dist/user-data-rights/db/queries/export-jobs.d.ts +2 -0
  194. package/dist/user-data-rights/feature.js +22 -9
  195. package/dist/user-data-rights/handlers/confirm-deletion-by-token.write.js +4 -7
  196. package/dist/user-data-rights/handlers/deletion-grace-period.d.ts +3 -2
  197. package/dist/user-data-rights/handlers/deletion-grace-period.js +30 -8
  198. package/dist/user-data-rights/handlers/download-by-token.query.js +1 -1
  199. package/dist/user-data-rights/handlers/request-deletion-by-email.write.js +1 -1
  200. package/dist/user-data-rights/handlers/request-deletion.write.js +3 -6
  201. package/dist/user-data-rights/lib/update-user-lifecycle.js +7 -14
  202. package/dist/user-data-rights/run-export-jobs.d.ts +1 -0
  203. package/dist/user-data-rights/run-export-jobs.js +74 -41
  204. package/dist/user-data-rights/run-forget-cleanup.js +2 -2
  205. package/dist/user-data-rights/run-user-export.js +2 -2
  206. package/dist/user-data-rights-defaults/hooks/file-ref.userdata-hook.js +4 -1
  207. package/dist/user-profile/feature.js +6 -1
  208. package/dist/user-profile/handlers/change-email.write.js +2 -0
  209. package/dist/workflow-runner/db/queries/due-runs.js +6 -9
  210. package/dist/workflow-runner/event-subscriber.js +29 -9
  211. package/dist/workflow-runner/handlers/resume-run.write.js +8 -5
  212. package/dist/workflow-runner/pending-projection.js +8 -1
  213. package/package.json +11 -9
  214. package/src/agent-tools/changes.json +12 -0
  215. package/src/auth-email-password/changes.json +18 -0
  216. package/src/auth-mfa/changes.json +6 -0
  217. package/src/billing-foundation/changes.json +7 -0
  218. package/src/cap-counter/changes.json +7 -0
  219. package/src/cap-overview/changes.json +12 -0
  220. package/src/channel-email/changes.json +6 -0
  221. package/src/config/changes.json +6 -0
  222. package/src/crypto-shredding/changes.json +6 -0
  223. package/src/data-retention/changes.json +12 -0
  224. package/src/delivery/changes.json +7 -0
  225. package/src/derivatives-sharp/changes.json +11 -0
  226. package/src/document-ingest-foundation/changes.json +12 -0
  227. package/src/inbound-mail-foundation/changes.json +12 -0
  228. package/src/jobs/changes.json +13 -0
  229. package/src/legal-pages/changes.json +8 -1
  230. package/src/managed-pages/changes.json +5 -0
  231. package/src/metrics/changes.json +6 -0
  232. package/src/rate-limiting/changes.json +5 -0
  233. package/src/secrets/changes.json +7 -0
  234. package/src/sessions/changes.json +5 -0
  235. package/src/shared/changes.json +6 -0
  236. package/src/step-dispatcher/changes.json +5 -0
  237. package/src/subscription-mollie/changes.json +7 -0
  238. package/src/subscription-stripe/changes.json +11 -0
  239. package/src/template-resolver/changes.json +7 -0
  240. package/src/tenant/changes.json +29 -0
  241. package/src/user-data-rights/changes.json +22 -0
  242. package/src/workflow-runner/changes.json +6 -0
@@ -3,11 +3,13 @@
3
3
  // user-data-rights' confirm-deletion-by-token and any "you just created this
4
4
  // row, now enrich it" flow.
5
5
  //
6
- // The row's own anchor (a value that moves on when the row is consumed: a
7
- // request id, a status, a version) is mixed INTO the HMAC purpose rather
8
- // than carried in the token. Verification recomputes it from the row's
9
- // CURRENT anchor, so a replayed token dies the moment the row moves on —
10
- // single-use semantics without a burn key or Redis. Minting and redeeming
6
+ // The row's own anchor (a value that moves on when the row is consumed) is
7
+ // mixed INTO the HMAC purpose rather than carried in the token. It must be
8
+ // unique per cycle and never recur (request id, UUID, monotonic version): a
9
+ // status can return to an earlier value and revive an old grant (ABA replay).
10
+ // Verification recomputes it from the row's CURRENT anchor, so a replayed
11
+ // token dies the moment the row moves on — single-use semantics without a
12
+ // burn key or Redis. Minting and redeeming
11
13
  // share one purpose-building function so the two can't drift apart; that
12
14
  // coupling is the whole point of this module, since an unanchored purpose
13
15
  // silently degrades to a bearer token valid for the full TTL.
@@ -27,7 +29,9 @@
27
29
  // clear. A grant on a row therefore exposes that row's id to whoever holds
28
30
  // the link. Where the id itself must stay hidden, use an opaque handle
29
31
  // (single-use-token-store) instead.
32
+ import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
30
33
  import { peekTokenSubject, signToken, verifyToken } from "./signed-token.js";
34
+ const log = createFallbackLogger("row-bound-grant");
31
35
  const FAILED = { ok: false };
32
36
  function anchoredPurpose(purpose, anchor) {
33
37
  return `${purpose}:${anchor}`;
@@ -40,6 +44,10 @@ export function signRowBoundGrant(args) {
40
44
  // hand an attacker a row-existence oracle on an endpoint that is open to
41
45
  // anonymous callers.
42
46
  export async function redeemRowBoundGrant(args) {
47
+ // Misconfiguration must throw on every call, not only on the first valid redemption.
48
+ if (typeof args.commitAnchor !== "function" && !args.commitAnchor.unsafeSkip.reason.trim()) {
49
+ throw new Error("row-bound grant: unsafeSkip needs a non-empty reason");
50
+ }
43
51
  if (!args.secret)
44
52
  return FAILED;
45
53
  const subject = peekTokenSubject(args.token);
@@ -52,7 +60,13 @@ export async function redeemRowBoundGrant(args) {
52
60
  try {
53
61
  anchor = await args.loadAnchor(subject);
54
62
  }
55
- catch {
63
+ catch (error) {
64
+ // Name/code only: driver messages can echo the attacker-supplied subject.
65
+ // Logged so an infrastructure outage doesn't look like an expired link.
66
+ log.warn("row-bound grant anchor lookup failed", {
67
+ errorName: error instanceof Error ? error.name : typeof error,
68
+ code: error instanceof Error && "code" in error ? String(error.code) : undefined,
69
+ });
56
70
  return FAILED;
57
71
  }
58
72
  if (!anchor)
@@ -67,8 +81,5 @@ export async function redeemRowBoundGrant(args) {
67
81
  if (!spent)
68
82
  return FAILED;
69
83
  }
70
- else if (!args.commitAnchor.unsafeSkip.reason.trim()) {
71
- throw new Error("row-bound grant: unsafeSkip needs a non-empty reason");
72
- }
73
84
  return { ok: true, subject, expiresAtMs: verified.expiresAtMs };
74
85
  }
@@ -1,21 +1,13 @@
1
1
  // Generic Redis-backed pre-activation token store: bidirectional
2
2
  // token↔subject mapping plus single-use burn/unburn semantics. Extracted
3
3
  // from auth-email-password/signup-token-store.ts and
4
- // auth-email-password/invite-token-store.ts (infra#446) — both were the
5
- // same Redis layout, differing only in their key prefixes and which field
6
- // (email vs. invitationId) plays the "subject" role.
4
+ // auth-email-password/invite-token-store.ts — both were the same Redis
5
+ // layout, differing only in their key prefixes and which field (email vs.
6
+ // invitationId) plays the "subject" role.
7
7
  //
8
8
  // Public subpath export (./shared/single-use-token-store in package.json):
9
- // offlot-app (a separate repo, external consumer) carries its own copy of
10
- // this exact logic under `src/features/waitlist/signup-token-store.ts`,
11
- // with a comment noting it "must stay byte-compatible with
12
- // auth-email-password/signup-token-store" because signup-confirm resolves
13
- // tokens via the same Redis key layout. offlot-app#418 (separate issue,
14
- // after this ships) will replace that copy with
15
- // `createSingleUseTokenStore({ tokenPrefix: "signup:by-token:", subjectPrefix:
16
- // "signup:by-email:", burnPrefix: "signup:burn:" })` — i.e. the exact same
17
- // prefix strings the framework's own signup store below uses, so both stay
18
- // byte-compatible by construction instead of by hand-copied logic.
9
+ // external consumers share the signup key layout, so the prefixes must stay
10
+ // byte-identical.
19
11
  //
20
12
  // Token material: opaque random 256-bit (e.g. crypto.randomBytes,
21
13
  // base64url-encoded). Not designed for human typing — the subject clicks a
@@ -3,8 +3,6 @@ import type { SecretsContext } from "@cosmicdrift/kumiko-framework/secrets";
3
3
  import type { TenantId } from "@cosmicdrift/kumiko-types/identifiers";
4
4
  import * as z from "zod";
5
5
  export declare const WEBHOOK_ALLOWED_PRIVATE_HOSTS_ENV_VAR = "KUMIKO_WEBHOOK_ALLOWED_PRIVATE_HOSTS";
6
- /** Parses the comma-separated operator allowlist env var. Never throws —
7
- * an unset or empty value just means no bypass. */
8
6
  export declare function readAllowedPrivateWebhookHostsFromEnv(env?: Readonly<Record<string, string | undefined>>): readonly string[];
9
7
  export declare function setWebhookHostLookup(fn: typeof lookup | undefined): void;
10
8
  export declare const WEBHOOK_AUTH_SECRET_KEY_PREFIX = "step-dispatcher:webhook-auth.";
@@ -22,18 +22,11 @@
22
22
  import { BlockedHostError, buildPinnedRequest, HostResolutionError, resolvePublicHostname, } from "@cosmicdrift/kumiko-framework/http";
23
23
  import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
24
24
  import * as z from "zod";
25
+ import { isHostAllowlisted, readHostAllowlistFromEnv } from "../foundation-shared/index.js";
25
26
  const log = createFallbackLogger("step-dispatcher");
26
27
  export const WEBHOOK_ALLOWED_PRIVATE_HOSTS_ENV_VAR = "KUMIKO_WEBHOOK_ALLOWED_PRIVATE_HOSTS";
27
- /** Parses the comma-separated operator allowlist env var. Never throws —
28
- * an unset or empty value just means no bypass. */
29
28
  export function readAllowedPrivateWebhookHostsFromEnv(env = process.env) {
30
- const raw = env[WEBHOOK_ALLOWED_PRIVATE_HOSTS_ENV_VAR];
31
- if (!raw)
32
- return [];
33
- return raw
34
- .split(",")
35
- .map((host) => host.trim())
36
- .filter((host) => host.length > 0);
29
+ return readHostAllowlistFromEnv(WEBHOOK_ALLOWED_PRIVATE_HOSTS_ENV_VAR, env);
37
30
  }
38
31
  // Test-only DNS seam — production never calls this, resolvePublicHostname
39
32
  // defaults to the real resolver. Reset it in afterEach/afterAll — this is
@@ -87,14 +80,23 @@ async function buildWebhookHeaders(spec, deps) {
87
80
  return { ok: true, headers };
88
81
  if (!deps.secrets)
89
82
  return { ok: false, error: WEBHOOK_AUTH_SECRET_UNAVAILABLE_ERROR };
90
- const revealed = await deps.secrets.get(deps.tenantId, webhookAuthSecretKey(spec.auth.secret), {
91
- userId: deps.userId,
92
- handlerName: "step-dispatcher:webhook.send",
93
- });
94
- if (!revealed) {
83
+ // A throwing get (corrupt envelope, rotated key, KMS error) must become a
84
+ // dispatch-failed event; rethrowing would retry and dead-letter the whole
85
+ // step-dispatcher consumer for every tenant.
86
+ let secret;
87
+ try {
88
+ const revealed = await deps.secrets.get(deps.tenantId, webhookAuthSecretKey(spec.auth.secret), {
89
+ userId: deps.userId,
90
+ handlerName: "step-dispatcher:webhook.send",
91
+ });
92
+ if (!revealed)
93
+ return { ok: false, error: WEBHOOK_AUTH_SECRET_UNAVAILABLE_ERROR };
94
+ secret = revealed.reveal();
95
+ }
96
+ catch {
97
+ log.warn("webhook auth secret lookup failed", { tenantId: deps.tenantId });
95
98
  return { ok: false, error: WEBHOOK_AUTH_SECRET_UNAVAILABLE_ERROR };
96
99
  }
97
- const secret = revealed.reveal();
98
100
  if (spec.auth.kind === "bearer") {
99
101
  headers["authorization"] = `Bearer ${secret}`;
100
102
  }
@@ -104,8 +106,7 @@ async function buildWebhookHeaders(spec, deps) {
104
106
  return { ok: true, headers };
105
107
  }
106
108
  async function resolveWebhookFetchTarget(rawUrl, url, headers) {
107
- const isAllowedPrivateHost = readAllowedPrivateWebhookHostsFromEnv().some((candidate) => candidate.toLowerCase() === url.hostname.toLowerCase());
108
- if (isAllowedPrivateHost)
109
+ if (isHostAllowlisted(url.hostname, readAllowedPrivateWebhookHostsFromEnv()))
109
110
  return { ok: true, fetchUrl: rawUrl, requestInit: { headers } };
110
111
  try {
111
112
  const resolved = await resolvePublicHostname(url.hostname, webhookHostLookup);
@@ -8,8 +8,9 @@ export type MolliePriceConfig = {
8
8
  /** Mollie-format: 2-decimal string, z.B. `"9.99"`. */
9
9
  readonly amountValue: string;
10
10
  readonly amountCurrency: string;
11
- /** Mollie-format: `1 month` / `1 year` / `14 days`. */
12
- readonly interval: string;
11
+ /** Mollie-format: `1 month` / `1 year` / `14 days`. Required for
12
+ * subscription prices; omit for one-off prices (`mode: "payment"`). */
13
+ readonly interval?: string;
13
14
  readonly description: string;
14
15
  };
15
16
  export declare function createMollieCheckoutSession(client: MollieClient, priceToConfig: Readonly<Record<string, MolliePriceConfig>>, appWebhookUrl: string): (_ctx: HandlerContext, options: MollieCheckoutOptions) => Promise<{
@@ -19,6 +19,11 @@ export function createMollieCheckoutSession(client, priceToConfig, appWebhookUrl
19
19
  if (!priceCfg) {
20
20
  throw new Error(`subscription-mollie: priceId "${options.priceId}" not in priceToConfig-Map`);
21
21
  }
22
+ // A first payment without an interval would set up a mandate that
23
+ // verify-webhook can't turn into a subscription — fail before charging.
24
+ if (options.mode !== "payment" && !priceCfg.interval) {
25
+ throw new Error(`subscription-mollie: priceId "${options.priceId}" has no interval but is booked as a subscription (mode is not "payment")`);
26
+ }
22
27
  let customerId = options.providerCustomerId;
23
28
  if (!customerId) {
24
29
  const customer = await client.customers.create({
@@ -50,7 +55,8 @@ export function createMollieCheckoutSession(client, priceToConfig, appWebhookUrl
50
55
  })); // @cast-boundary engine-bridge
51
56
  const checkoutHref = payment.getCheckoutUrl();
52
57
  if (!checkoutHref) {
53
- throw new Error("subscription-mollie: payment.getCheckoutUrl() returned null — first-payment-mandates ggf. nicht aktiviert");
58
+ const mandateHint = sequenceType === SequenceType.first ? " — first-payment-mandates ggf. nicht aktiviert" : "";
59
+ throw new Error(`subscription-mollie: payment.getCheckoutUrl() returned null (sequenceType=${sequenceType})${mandateHint}`);
54
60
  }
55
61
  return { url: checkoutHref };
56
62
  };
@@ -14,8 +14,10 @@
14
14
  // das im Plugin (idempotent via list-check), damit Foundation einen
15
15
  // Created-Event mit subscription-id bekommt.
16
16
  import { SubscriptionEventTypes, SubscriptionStatuses, } from "@cosmicdrift/kumiko-bundled-features/billing-foundation";
17
+ import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
17
18
  import { Temporal } from "temporal-polyfill";
18
19
  import { MOLLIE_PROVIDER_NAME } from "./constants.js";
20
+ const log = createFallbackLogger("subscription-mollie");
19
21
  export function verifyAndParseMollieWebhook(client, options) {
20
22
  return async (rawBody, headers) => {
21
23
  const id = extractMollieId(rawBody, headers);
@@ -115,6 +117,10 @@ async function ensureSubscriptionForMandate(client, options, payment) {
115
117
  const priceCfg = options.priceToConfig[priceId];
116
118
  if (!priceCfg)
117
119
  return null;
120
+ if (!priceCfg.interval) {
121
+ log.warn("price has no interval, no subscription created", { priceId });
122
+ return null;
123
+ }
118
124
  const existing = await client.customerSubscriptions.list(customerId);
119
125
  const matchingExisting = existing.find((sub) => sub.metadata?.["priceId"] === priceId && // @cast-boundary engine-bridge
120
126
  (sub.status === "active" || sub.status === "pending"));
@@ -45,6 +45,10 @@ export function createStripeCheckoutSession(runtime, { paymentInvoiceCreation =
45
45
  : {
46
46
  payment_intent_data: { metadata: { tenantId: options.tenantId } },
47
47
  invoice_creation: { enabled: paymentInvoiceCreation },
48
+ // Without a customer, Stripe's default "if_required" makes a guest
49
+ // checkout when invoice_creation is off; the webhook then has no
50
+ // customer id and drops the paid payment as ignored.
51
+ ...(!options.providerCustomerId && { customer_creation: "always" }),
48
52
  }),
49
53
  ...(options.providerCustomerId && { customer: options.providerCustomerId }),
50
54
  });
@@ -113,12 +117,14 @@ function isKnownRecurringInterval(interval) {
113
117
  }
114
118
  function mapStripePrice(price) {
115
119
  const interval = price.recurring?.interval;
120
+ const knownInterval = isKnownRecurringInterval(interval) ? interval : null;
116
121
  return {
117
122
  priceId: price.id,
118
123
  unitAmount: price.unit_amount,
119
124
  currency: price.currency,
120
- interval: isKnownRecurringInterval(interval) ? interval : null,
121
- intervalCount: price.recurring?.interval_count ?? null,
125
+ interval: knownInterval,
126
+ // Kept consistent with `interval`: a count without an interval would read as a half-recurring price.
127
+ intervalCount: knownInterval ? (price.recurring?.interval_count ?? null) : null,
122
128
  active: price.active,
123
129
  metadata: price.metadata ?? {},
124
130
  };
@@ -162,11 +168,23 @@ async function resolvePricesViaCache(runtime, cache, ctx, priceIds) {
162
168
  }
163
169
  }
164
170
  export function createStripeRetrievePrices(runtime, cache) {
171
+ const warnedUnknownIntervalPriceIds = new Set();
165
172
  return async (ctx, priceIds) => {
166
173
  await resolvePricesViaCache(runtime, cache, ctx, priceIds);
167
174
  return priceIds.flatMap((priceId) => {
168
175
  const price = cache.get(priceId);
169
- return price ? [mapStripePrice(price)] : [];
176
+ if (!price)
177
+ return [];
178
+ const mapped = mapStripePrice(price);
179
+ const rawInterval = price.recurring?.interval;
180
+ if (rawInterval && mapped.interval === null && !warnedUnknownIntervalPriceIds.has(priceId)) {
181
+ warnedUnknownIntervalPriceIds.add(priceId);
182
+ ctx.log?.warn("subscription-stripe: price has an unknown recurring interval", {
183
+ priceId,
184
+ interval: rawInterval,
185
+ });
186
+ }
187
+ return [mapped];
170
188
  });
171
189
  };
172
190
  }
@@ -175,7 +193,7 @@ function resolveProductId(product) {
175
193
  return typeof product === "string" ? product : product.id;
176
194
  }
177
195
  /** Both our own pre-Stripe-call product+interval collision check and a live
178
- * `StripeInvalidRequestError` from `configurations.create()`/`sessions.create()`
196
+ * `StripeInvalidRequestError` from `configurations.create()`
179
197
  * surface the same underlying misconfiguration (two plan tiers sharing one
180
198
  * Stripe product) — mapped to the same `UnprocessableError` so the panel
181
199
  * only needs one i18nKey to translate. */
@@ -336,8 +354,9 @@ export function createStripePlanSwitchSession(runtime, cache, portalConfigCache)
336
354
  // out-of-band) must not poison every subsequent switch for the same
337
355
  // price-set — evict it so the next call re-searches/re-creates.
338
356
  portalConfigCache.delete(hash);
339
- if (isPlanTiersShareProductStripeError(error))
340
- throw planTiersShareProductError(error);
357
+ // Not mapped to plan_tiers_share_product: a sessions.create error under
358
+ // flow_data.subscription_update_confirm.* is a different problem (inactive
359
+ // target price, wrong item) and mapping it would hide the real cause.
341
360
  throw error;
342
361
  }
343
362
  };
@@ -33,9 +33,11 @@
33
33
  // redelivers.
34
34
  import { BillingEventKinds, SubscriptionEventTypes, SubscriptionStatuses, } from "@cosmicdrift/kumiko-bundled-features/billing-foundation";
35
35
  import { ExtraRouteRejection } from "@cosmicdrift/kumiko-framework/api";
36
+ import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
36
37
  import Stripe from "stripe";
37
38
  import { Temporal } from "temporal-polyfill";
38
39
  import { STRIPE_PROVIDER_NAME, StripeEventTypes } from "./constants.js";
40
+ const log = createFallbackLogger("subscription-stripe");
39
41
  export function isResourceMissingStripeError(error) {
40
42
  return (error instanceof Stripe.errors.StripeInvalidRequestError && error.code === "resource_missing");
41
43
  }
@@ -259,6 +261,12 @@ function isCheckoutSessionEventType(stripeType) {
259
261
  return (stripeType === StripeEventTypes.checkoutSessionCompleted ||
260
262
  stripeType === StripeEventTypes.checkoutSessionAsyncPaymentSucceeded);
261
263
  }
264
+ // The session is already paid, so a silent drop means money collected with no
265
+ // credit — warn (ids and reason only, no PII) so operators can spot it.
266
+ function dropPaidSession(event, session, reason) {
267
+ log.warn("paid checkout session dropped", { eventId: event.id, sessionId: session.id, reason });
268
+ return null;
269
+ }
262
270
  /** Parses a checkout.session.completed / .async_payment_succeeded event into
263
271
  * a PaymentEvent, or null if it isn't a paid one-off-payment session. */
264
272
  async function parsePaymentEvent(event, stripe) {
@@ -296,17 +304,16 @@ async function parsePaymentEvent(event, stripe) {
296
304
  const paymentIntent = expanded.payment_intent;
297
305
  if (!paymentIntent || typeof paymentIntent === "string") {
298
306
  // Not expanded (Stripe-API-drift) or absent — no verified tenant-claim
299
- // to trust. Drop silent, same as the subscription-path's missing-
300
- // metadata case.
301
- return null;
307
+ // to trust.
308
+ return dropPaidSession(event, session, "payment_intent_not_expanded");
302
309
  }
303
310
  const tenantId = paymentIntent.metadata?.["tenantId"];
304
311
  if (!tenantId || tenantId.length === 0) {
305
- return null;
312
+ return dropPaidSession(event, session, "missing_tenant_id");
306
313
  }
307
314
  const priceId = expanded.line_items?.data[0]?.price?.id;
308
315
  if (!priceId) {
309
- return null;
316
+ return dropPaidSession(event, session, "missing_price_id");
310
317
  }
311
318
  // providerCustomerId: session.customer is null for guest checkouts (no
312
319
  // customer_creation configured) — fall back to the PaymentIntent's own
@@ -317,7 +324,7 @@ async function parsePaymentEvent(event, stripe) {
317
324
  ? paymentIntent.customer
318
325
  : paymentIntent.customer?.id);
319
326
  if (!providerCustomerId) {
320
- return null;
327
+ return dropPaidSession(event, session, "missing_customer");
321
328
  }
322
329
  return {
323
330
  kind: BillingEventKinds.payment,
@@ -1,5 +1,5 @@
1
1
  import { NotFoundError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
2
- import { joinRowParentIsVisible } from "../../shared/index.js";
2
+ import { denyUnlessJoinRowParentVisible } from "../../shared/index.js";
3
3
  import { tagAssignmentAggregateId } from "../aggregate-id.js";
4
4
  import { DEFAULT_TAG_ACCESS } from "../constants.js";
5
5
  import { tagAssignmentExecutor, tagExecutor } from "../executor.js";
@@ -33,15 +33,9 @@ export function createAssignTagHandler(access = DEFAULT_TAG_ACCESS) {
33
33
  description: "Attaches an existing catalog tag to one host entity addressed by its type and id, reporting success when the tag was already attached; use it to tag a record, not to create the tag.",
34
34
  handler: async (event, ctx) => {
35
35
  const payload = event.payload; // @cast-boundary engine-payload
36
- // entityType/entityId are client input: the caller must be able to see
37
- // the host row through that entity's own read path (tenant scope plus its
38
- // `access.read` ownership) before its assignment row may be written.
39
- // Checked FIRST, ahead of every lookup below, so a denied caller can't tell
40
- // an invisible parent apart from a missing assignment or an unknown tag —
41
- // every path answers with the same NotFoundError.
42
- if (!(await joinRowParentIsVisible(ctx.registry, "tag-assignment", payload, event.user, ctx.db))) {
43
- return writeFailure(new NotFoundError(payload.entityType, payload.entityId));
44
- }
36
+ const denied = await denyUnlessJoinRowParentVisible(ctx.registry, "tag-assignment", payload, event.user, ctx.db);
37
+ if (denied)
38
+ return denied;
45
39
  const id = tagAssignmentAggregateId(event.user.tenantId, payload.tagId, payload.entityType, payload.entityId);
46
40
  const existing = await tagAssignmentExecutor.detail({ id }, event.user, ctx.db);
47
41
  if (existing) {
@@ -1,5 +1,4 @@
1
- import { NotFoundError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
2
- import { joinRowParentIsVisible } from "../../shared/index.js";
1
+ import { denyUnlessJoinRowParentVisible } from "../../shared/index.js";
3
2
  import { tagAssignmentAggregateId } from "../aggregate-id.js";
4
3
  import { DEFAULT_TAG_ACCESS } from "../constants.js";
5
4
  import { tagAssignmentExecutor } from "../executor.js";
@@ -19,15 +18,9 @@ export function createRemoveTagHandler(access = DEFAULT_TAG_ACCESS) {
19
18
  description: "Detaches one catalog tag from one host entity, reporting success when it was not attached; use it to untag a single record while the tag itself stays in the catalog.",
20
19
  handler: async (event, ctx) => {
21
20
  const payload = event.payload; // @cast-boundary engine-payload
22
- // entityType/entityId are client input: the caller must be able to see
23
- // the host row through that entity's own read path (tenant scope plus its
24
- // `access.read` ownership) before its assignment row may be written.
25
- // Checked FIRST, ahead of every lookup below, so a denied caller can't tell
26
- // an invisible parent apart from a missing assignment or an unknown tag —
27
- // every path answers with the same NotFoundError.
28
- if (!(await joinRowParentIsVisible(ctx.registry, "tag-assignment", payload, event.user, ctx.db))) {
29
- return writeFailure(new NotFoundError(payload.entityType, payload.entityId));
30
- }
21
+ const denied = await denyUnlessJoinRowParentVisible(ctx.registry, "tag-assignment", payload, event.user, ctx.db);
22
+ if (denied)
23
+ return denied;
31
24
  const id = tagAssignmentAggregateId(event.user.tenantId, payload.tagId, payload.entityType, payload.entityId);
32
25
  const existing = await tagAssignmentExecutor.detail({ id }, event.user, ctx.db);
33
26
  if (!existing) {
@@ -1,5 +1,6 @@
1
1
  import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
2
2
  import type { SessionUser, TenantId } from "@cosmicdrift/kumiko-framework/engine";
3
+ import type { Temporal } from "temporal-polyfill";
3
4
  import type { ContentFormat, TemplateKind } from "./constants.js";
4
5
  export type TemplateResource = {
5
6
  readonly id: string;
@@ -17,7 +18,7 @@ export type TemplateResource = {
17
18
  readonly scope: "system" | "tenant";
18
19
  readonly parentTemplateId: string | null;
19
20
  readonly status: "draft" | "active" | "archived";
20
- readonly updatedAt: Date;
21
+ readonly modifiedAt: Temporal.Instant | null;
21
22
  };
22
23
  export type ResolveRequest = {
23
24
  readonly tenantId: TenantId;
@@ -70,7 +70,7 @@ function toPublic(row) {
70
70
  scope,
71
71
  parentTemplateId: row.parentTemplateId,
72
72
  status,
73
- updatedAt: row.updatedAt,
73
+ modifiedAt: row.modifiedAt,
74
74
  };
75
75
  }
76
76
  function parseJson(raw) {
@@ -10,5 +10,5 @@ export declare const bySlugQuery: import("@cosmicdrift/kumiko-types/define-handl
10
10
  content: string | null;
11
11
  contentFormat: string;
12
12
  folder: string | null;
13
- updatedAt: Date;
13
+ modifiedAt: import("temporal-spec").Temporal.Instant | null;
14
14
  } | null, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
@@ -24,7 +24,7 @@ export const bySlugQuery = defineQueryHandler({
24
24
  tenantIdOverride: z.string().min(1).optional(),
25
25
  }),
26
26
  access: { roles: ["anonymous", "User", "TenantAdmin", "SystemAdmin"] },
27
- rateLimit: { per: "ip", limit: 60, windowSeconds: 60 },
27
+ rateLimit: { per: "ip+handler", limit: 60, windowSeconds: 60 },
28
28
  description: "Reads one text-block of a tenant by slug and locale together with its body and format; it is pinned to the text-block kind so mail templates and AI prompts in the same table stay unreachable through this anonymous-capable path.",
29
29
  handler: async (query, ctx) => {
30
30
  const override = query.payload.tenantIdOverride;
@@ -47,7 +47,7 @@ export const bySlugQuery = defineQueryHandler({
47
47
  content: row.content,
48
48
  contentFormat: row.contentFormat,
49
49
  folder: row.folder,
50
- updatedAt: row.updatedAt,
50
+ modifiedAt: row.modifiedAt,
51
51
  };
52
52
  },
53
53
  });
@@ -1,3 +1,4 @@
1
+ import type { Temporal } from "temporal-polyfill";
1
2
  import * as z from "zod";
2
3
  export type TextBlockSummary = {
3
4
  readonly slug: string;
@@ -5,7 +6,7 @@ export type TextBlockSummary = {
5
6
  readonly title: string | null;
6
7
  readonly content: string | null;
7
8
  readonly folder: string | null;
8
- readonly updatedAt: Date;
9
+ readonly modifiedAt: Temporal.Instant | null;
9
10
  };
10
11
  export declare const byTenantQuery: import("@cosmicdrift/kumiko-types/define-handler").QueryHandlerDefinition<"by-tenant", z.ZodObject<{
11
12
  tenantIdOverride: z.ZodOptional<z.ZodString>;
@@ -16,6 +17,6 @@ export declare const byTenantQuery: import("@cosmicdrift/kumiko-types/define-han
16
17
  title: string | null;
17
18
  content: string | null;
18
19
  folder: string | null;
19
- updatedAt: Date;
20
+ modifiedAt: Temporal.Instant | null;
20
21
  }[];
21
22
  }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
@@ -11,7 +11,7 @@ export const byTenantQuery = defineQueryHandler({
11
11
  tenantIdOverride: z.string().min(1).optional(),
12
12
  }),
13
13
  access: { roles: ["anonymous", "User", "TenantAdmin", "SystemAdmin"] },
14
- rateLimit: { per: "ip", limit: 60, windowSeconds: 60 },
14
+ rateLimit: { per: "ip+handler", limit: 60, windowSeconds: 60 },
15
15
  description: "Lists every text-block of a tenant with slug, locale, title and body so a public content tree can be rendered in one call; use it for the whole sidebar, and by-slug when only one block is needed.",
16
16
  handler: async (query, ctx) => {
17
17
  const override = query.payload.tenantIdOverride;
@@ -27,7 +27,7 @@ export const byTenantQuery = defineQueryHandler({
27
27
  title: row.title,
28
28
  content: row.content,
29
29
  folder: row.folder,
30
- updatedAt: row.updatedAt,
30
+ modifiedAt: row.modifiedAt,
31
31
  })),
32
32
  };
33
33
  },
@@ -11,5 +11,5 @@ export declare function makeCollectionItemQuery(collection: ContentCollectionDef
11
11
  content: string | null;
12
12
  contentFormat: string;
13
13
  folder: string | null;
14
- updatedAt: Date;
14
+ modifiedAt: import("temporal-spec").Temporal.Instant | null;
15
15
  } | null, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
@@ -10,6 +10,6 @@ export declare function makeCollectionListQuery(collection: ContentCollectionDef
10
10
  content: string | null;
11
11
  contentFormat: string;
12
12
  folder: string | null;
13
- updatedAt: Date;
13
+ modifiedAt: import("temporal-spec").Temporal.Instant | null;
14
14
  }[];
15
15
  }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
@@ -1,5 +1,6 @@
1
1
  import type { EventStoreExecutor } from "@cosmicdrift/kumiko-framework/db";
2
2
  import type { AccessRule, ContentCollectionDefinition, SessionUser, TenantId } from "@cosmicdrift/kumiko-framework/engine";
3
+ import type { Temporal } from "temporal-polyfill";
3
4
  import { templateResourcesTable } from "../table.js";
4
5
  import { userContentEntriesTable } from "../user-content-table.js";
5
6
  export declare const DEFAULT_COLLECTION_ACCESS: AccessRule;
@@ -12,7 +13,7 @@ export type CollectionEntryRow = {
12
13
  readonly content: string | null;
13
14
  readonly contentFormat: string;
14
15
  readonly folder: string | null;
15
- readonly updatedAt: Date;
16
+ readonly modifiedAt: Temporal.Instant | null;
16
17
  };
17
18
  export type CollectionStore = {
18
19
  readonly table: typeof templateResourcesTable | typeof userContentEntriesTable;
@@ -30,5 +31,5 @@ export declare function toCollectionEntry(row: CollectionEntryRow): {
30
31
  content: string | null;
31
32
  contentFormat: string;
32
33
  folder: string | null;
33
- updatedAt: Date;
34
+ modifiedAt: Temporal.Instant | null;
34
35
  };
@@ -45,6 +45,6 @@ export function toCollectionEntry(row) {
45
45
  content: row.content,
46
46
  contentFormat: row.contentFormat,
47
47
  folder: row.folder,
48
- updatedAt: row.updatedAt,
48
+ modifiedAt: row.modifiedAt,
49
49
  };
50
50
  }
@@ -15,5 +15,5 @@ export declare const findByIdQuery: import("@cosmicdrift/kumiko-types/define-han
15
15
  scope: string;
16
16
  parentTemplateId: string | null;
17
17
  status: string;
18
- updatedAt: Date;
18
+ modifiedAt: import("temporal-spec").Temporal.Instant | null;
19
19
  } | null, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
@@ -37,7 +37,7 @@ export const findByIdQuery = defineQueryHandler({
37
37
  scope: row.scope,
38
38
  parentTemplateId: row.parentTemplateId,
39
39
  status: row.status,
40
- updatedAt: row.updatedAt,
40
+ modifiedAt: row.modifiedAt,
41
41
  };
42
42
  },
43
43
  });
@@ -22,5 +22,5 @@ export declare const listQuery: import("@cosmicdrift/kumiko-types/define-handler
22
22
  scope: string;
23
23
  status: string;
24
24
  contentFormat: string;
25
- updatedAt: Date;
25
+ modifiedAt: import("temporal-spec").Temporal.Instant | null;
26
26
  }[], import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
@@ -48,7 +48,7 @@ export const listQuery = defineQueryHandler({
48
48
  scope: row.scope,
49
49
  status: row.status,
50
50
  contentFormat: row.contentFormat,
51
- updatedAt: row.updatedAt,
51
+ modifiedAt: row.modifiedAt,
52
52
  }));
53
53
  },
54
54
  });