@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
@@ -9,6 +9,7 @@ export declare const pageEntity: import("@cosmicdrift/kumiko-types/fields").Enti
9
9
  readonly filterable?: boolean;
10
10
  readonly encrypted?: boolean;
11
11
  readonly sensitive?: boolean;
12
+ readonly writeOnly?: boolean;
12
13
  readonly readAsInstruction?: boolean;
13
14
  readonly format?: "email" | "url" | "phone" | "password";
14
15
  readonly default?: string;
@@ -29,6 +30,7 @@ export declare const pageEntity: import("@cosmicdrift/kumiko-types/fields").Enti
29
30
  readonly filterable?: boolean;
30
31
  readonly encrypted?: boolean;
31
32
  readonly sensitive?: boolean;
33
+ readonly writeOnly?: boolean;
32
34
  readonly readAsInstruction?: boolean;
33
35
  readonly format?: "email" | "url" | "phone" | "password";
34
36
  readonly default?: string;
@@ -49,6 +51,7 @@ export declare const pageEntity: import("@cosmicdrift/kumiko-types/fields").Enti
49
51
  readonly filterable?: boolean;
50
52
  readonly encrypted?: boolean;
51
53
  readonly sensitive?: boolean;
54
+ readonly writeOnly?: boolean;
52
55
  readonly readAsInstruction?: boolean;
53
56
  readonly format?: "email" | "url" | "phone" | "password";
54
57
  readonly default?: string;
@@ -69,6 +72,7 @@ export declare const pageEntity: import("@cosmicdrift/kumiko-types/fields").Enti
69
72
  readonly filterable?: boolean;
70
73
  readonly encrypted?: boolean;
71
74
  readonly sensitive?: boolean;
75
+ readonly writeOnly?: boolean;
72
76
  readonly readAsInstruction?: boolean;
73
77
  readonly format?: "email" | "url" | "phone" | "password";
74
78
  readonly default?: string;
@@ -89,6 +93,7 @@ export declare const pageEntity: import("@cosmicdrift/kumiko-types/fields").Enti
89
93
  readonly filterable?: boolean;
90
94
  readonly encrypted?: boolean;
91
95
  readonly sensitive?: boolean;
96
+ readonly writeOnly?: boolean;
92
97
  readonly readAsInstruction?: boolean;
93
98
  readonly format?: "email" | "url" | "phone" | "password";
94
99
  readonly default?: string;
@@ -109,6 +114,7 @@ export declare const pageEntity: import("@cosmicdrift/kumiko-types/fields").Enti
109
114
  readonly filterable?: boolean;
110
115
  readonly encrypted?: boolean;
111
116
  readonly sensitive?: boolean;
117
+ readonly writeOnly?: boolean;
112
118
  readonly readAsInstruction?: boolean;
113
119
  readonly format?: "email" | "url" | "phone" | "password";
114
120
  readonly default?: string;
@@ -134,6 +140,7 @@ export declare const pagesTable: import("@cosmicdrift/kumiko-framework/db").Enti
134
140
  readonly filterable?: boolean;
135
141
  readonly encrypted?: boolean;
136
142
  readonly sensitive?: boolean;
143
+ readonly writeOnly?: boolean;
137
144
  readonly readAsInstruction?: boolean;
138
145
  readonly format?: "email" | "url" | "phone" | "password";
139
146
  readonly default?: string;
@@ -154,6 +161,7 @@ export declare const pagesTable: import("@cosmicdrift/kumiko-framework/db").Enti
154
161
  readonly filterable?: boolean;
155
162
  readonly encrypted?: boolean;
156
163
  readonly sensitive?: boolean;
164
+ readonly writeOnly?: boolean;
157
165
  readonly readAsInstruction?: boolean;
158
166
  readonly format?: "email" | "url" | "phone" | "password";
159
167
  readonly default?: string;
@@ -174,6 +182,7 @@ export declare const pagesTable: import("@cosmicdrift/kumiko-framework/db").Enti
174
182
  readonly filterable?: boolean;
175
183
  readonly encrypted?: boolean;
176
184
  readonly sensitive?: boolean;
185
+ readonly writeOnly?: boolean;
177
186
  readonly readAsInstruction?: boolean;
178
187
  readonly format?: "email" | "url" | "phone" | "password";
179
188
  readonly default?: string;
@@ -194,6 +203,7 @@ export declare const pagesTable: import("@cosmicdrift/kumiko-framework/db").Enti
194
203
  readonly filterable?: boolean;
195
204
  readonly encrypted?: boolean;
196
205
  readonly sensitive?: boolean;
206
+ readonly writeOnly?: boolean;
197
207
  readonly readAsInstruction?: boolean;
198
208
  readonly format?: "email" | "url" | "phone" | "password";
199
209
  readonly default?: string;
@@ -214,6 +224,7 @@ export declare const pagesTable: import("@cosmicdrift/kumiko-framework/db").Enti
214
224
  readonly filterable?: boolean;
215
225
  readonly encrypted?: boolean;
216
226
  readonly sensitive?: boolean;
227
+ readonly writeOnly?: boolean;
217
228
  readonly readAsInstruction?: boolean;
218
229
  readonly format?: "email" | "url" | "phone" | "password";
219
230
  readonly default?: string;
@@ -234,6 +245,7 @@ export declare const pagesTable: import("@cosmicdrift/kumiko-framework/db").Enti
234
245
  readonly filterable?: boolean;
235
246
  readonly encrypted?: boolean;
236
247
  readonly sensitive?: boolean;
248
+ readonly writeOnly?: boolean;
237
249
  readonly readAsInstruction?: boolean;
238
250
  readonly format?: "email" | "url" | "phone" | "password";
239
251
  readonly default?: string;
@@ -111,7 +111,13 @@ export async function runMetric(db, metric, params) {
111
111
  ? bucketStartsMs(window, bucket, params.timeZone)
112
112
  : [];
113
113
  const fillBuckets = (byBucketMs) => bucketStarts.map((atMs) => ({ atMs, value: byBucketMs.get(atMs) ?? gapValue }));
114
- const points = fillBuckets(new Map(aggregates.points.map((row) => [Number(row.keys[0]), row.value])));
114
+ // Grouped metrics carry their time series in `series`; fetchAggregates skips the total series, so filling gaps here would fake a flat zero line.
115
+ const points = metric.groupBy === undefined
116
+ ? fillBuckets(new Map(aggregates.points.map((row) => [
117
+ Number(row.keys[0]),
118
+ row.value,
119
+ ])))
120
+ : [];
115
121
  const rows = buildRows(metric, aggregates, value, labelOf);
116
122
  return {
117
123
  value,
@@ -12,7 +12,12 @@ const RANGE_DAYS = {
12
12
  "14d": 14,
13
13
  "30d": 30,
14
14
  };
15
+ // Offset strings ("+01:00") pass Intl/Temporal as ISO offsets but Postgres
16
+ // date_trunc reads them as POSIX zones with the opposite sign, so only named
17
+ // zones are accepted.
15
18
  export function isValidTimeZone(timeZone) {
19
+ if (/^[+-]/.test(timeZone))
20
+ return false;
16
21
  try {
17
22
  new Intl.DateTimeFormat(undefined, { timeZone });
18
23
  return true;
@@ -10,6 +10,7 @@ export declare function createNoteEntryEntity(access?: EntityDefinition["access"
10
10
  readonly filterable?: boolean;
11
11
  readonly encrypted?: boolean;
12
12
  readonly sensitive?: boolean;
13
+ readonly writeOnly?: boolean;
13
14
  readonly readAsInstruction?: boolean;
14
15
  readonly format?: "email" | "url" | "phone" | "password";
15
16
  readonly default?: string;
@@ -30,6 +31,7 @@ export declare function createNoteEntryEntity(access?: EntityDefinition["access"
30
31
  readonly filterable?: boolean;
31
32
  readonly encrypted?: boolean;
32
33
  readonly sensitive?: boolean;
34
+ readonly writeOnly?: boolean;
33
35
  readonly readAsInstruction?: boolean;
34
36
  readonly format?: "email" | "url" | "phone" | "password";
35
37
  readonly default?: string;
@@ -50,6 +52,7 @@ export declare function createNoteEntryEntity(access?: EntityDefinition["access"
50
52
  readonly filterable?: boolean;
51
53
  readonly encrypted?: boolean;
52
54
  readonly sensitive?: boolean;
55
+ readonly writeOnly?: boolean;
53
56
  readonly readAsInstruction?: boolean;
54
57
  readonly format?: "email" | "url" | "phone" | "password";
55
58
  readonly default?: string;
@@ -70,6 +73,7 @@ export declare function createNoteEntryEntity(access?: EntityDefinition["access"
70
73
  readonly filterable?: boolean;
71
74
  readonly encrypted?: boolean;
72
75
  readonly sensitive?: boolean;
76
+ readonly writeOnly?: boolean;
73
77
  readonly readAsInstruction?: boolean;
74
78
  readonly format?: "email" | "url" | "phone" | "password";
75
79
  readonly default?: string;
@@ -108,6 +112,7 @@ export declare const noteEntryEntity: EntityDefinition<{
108
112
  readonly filterable?: boolean;
109
113
  readonly encrypted?: boolean;
110
114
  readonly sensitive?: boolean;
115
+ readonly writeOnly?: boolean;
111
116
  readonly readAsInstruction?: boolean;
112
117
  readonly format?: "email" | "url" | "phone" | "password";
113
118
  readonly default?: string;
@@ -128,6 +133,7 @@ export declare const noteEntryEntity: EntityDefinition<{
128
133
  readonly filterable?: boolean;
129
134
  readonly encrypted?: boolean;
130
135
  readonly sensitive?: boolean;
136
+ readonly writeOnly?: boolean;
131
137
  readonly readAsInstruction?: boolean;
132
138
  readonly format?: "email" | "url" | "phone" | "password";
133
139
  readonly default?: string;
@@ -148,6 +154,7 @@ export declare const noteEntryEntity: EntityDefinition<{
148
154
  readonly filterable?: boolean;
149
155
  readonly encrypted?: boolean;
150
156
  readonly sensitive?: boolean;
157
+ readonly writeOnly?: boolean;
151
158
  readonly readAsInstruction?: boolean;
152
159
  readonly format?: "email" | "url" | "phone" | "password";
153
160
  readonly default?: string;
@@ -168,6 +175,7 @@ export declare const noteEntryEntity: EntityDefinition<{
168
175
  readonly filterable?: boolean;
169
176
  readonly encrypted?: boolean;
170
177
  readonly sensitive?: boolean;
178
+ readonly writeOnly?: boolean;
171
179
  readonly readAsInstruction?: boolean;
172
180
  readonly format?: "email" | "url" | "phone" | "password";
173
181
  readonly default?: string;
@@ -206,6 +214,7 @@ export declare function createNoteMentionEntity(): EntityDefinition<{
206
214
  readonly filterable?: boolean;
207
215
  readonly encrypted?: boolean;
208
216
  readonly sensitive?: boolean;
217
+ readonly writeOnly?: boolean;
209
218
  readonly readAsInstruction?: boolean;
210
219
  readonly format?: "email" | "url" | "phone" | "password";
211
220
  readonly default?: string;
@@ -226,6 +235,7 @@ export declare function createNoteMentionEntity(): EntityDefinition<{
226
235
  readonly filterable?: boolean;
227
236
  readonly encrypted?: boolean;
228
237
  readonly sensitive?: boolean;
238
+ readonly writeOnly?: boolean;
229
239
  readonly readAsInstruction?: boolean;
230
240
  readonly format?: "email" | "url" | "phone" | "password";
231
241
  readonly default?: string;
@@ -248,6 +258,7 @@ export declare const noteMentionEntity: EntityDefinition<{
248
258
  readonly filterable?: boolean;
249
259
  readonly encrypted?: boolean;
250
260
  readonly sensitive?: boolean;
261
+ readonly writeOnly?: boolean;
251
262
  readonly readAsInstruction?: boolean;
252
263
  readonly format?: "email" | "url" | "phone" | "password";
253
264
  readonly default?: string;
@@ -268,6 +279,7 @@ export declare const noteMentionEntity: EntityDefinition<{
268
279
  readonly filterable?: boolean;
269
280
  readonly encrypted?: boolean;
270
281
  readonly sensitive?: boolean;
282
+ readonly writeOnly?: boolean;
271
283
  readonly readAsInstruction?: boolean;
272
284
  readonly format?: "email" | "url" | "phone" | "password";
273
285
  readonly default?: string;
@@ -1,6 +1,5 @@
1
1
  import { fetchOne, runInSavepointIfSupported } from "@cosmicdrift/kumiko-framework/bun-db";
2
- import { NotFoundError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
3
- import { decryptStoredPii, joinRowParentIsVisible } from "../../shared/index.js";
2
+ import { decryptStoredPii, denyUnlessJoinRowParentVisible } from "../../shared/index.js";
4
3
  import { userTable } from "../../user/index.js";
5
4
  import { DEFAULT_NOTES_HISTORY_ACCESS } from "../constants.js";
6
5
  import { noteEntryExecutor, noteMentionExecutor } from "../executor.js";
@@ -29,13 +28,9 @@ export function createAddNoteHandler(access = DEFAULT_NOTES_HISTORY_ACCESS) {
29
28
  },
30
29
  handler: async (event, ctx) => {
31
30
  const payload = event.payload; // @cast-boundary engine-payload
32
- // NotFoundError, not an access-denied error, so the response doesn't
33
- // double as an existence oracle — same policy as executor.detail,
34
- // which never distinguishes "no access" from "doesn't exist".
35
- const visible = await joinRowParentIsVisible(ctx.registry, "note-entry", payload, event.user, ctx.db);
36
- if (!visible) {
37
- return writeFailure(new NotFoundError(payload.entityType, payload.entityId));
38
- }
31
+ const denied = await denyUnlessJoinRowParentVisible(ctx.registry, "note-entry", payload, event.user, ctx.db);
32
+ if (denied)
33
+ return denied;
39
34
  let authorName = null;
40
35
  try {
41
36
  // read_users is tenant-agnostic → ctx.db.unsafeRaw, not the tenant-scoped ctx.db.
@@ -4,7 +4,7 @@
4
4
  // dependency. Mirrors job-run/delivery-attempt (user-data-rights-defaults):
5
5
  // export-only, erasure via crypto-shredding.
6
6
  import { configuredPiiSubjectKms } from "@cosmicdrift/kumiko-framework/crypto";
7
- import { resolveRetentionPolicyForTenant } from "../data-retention/index.js";
7
+ import { resolveRetentionPolicyForTenant, resolveTenantRetentionPreset, } from "../data-retention/index.js";
8
8
  import { noteEntryTable, noteMentionTable } from "../notes-history/index.js";
9
9
  import { policyToStrategy } from "../user-data-rights/index.js";
10
10
  // note-entry has no per-tenant scope quirk (unlike folders) — it's genuinely
@@ -58,6 +58,12 @@ export const noteEntryDeleteHook = async (ctx) => {
58
58
  if (mentions.length === 0)
59
59
  return;
60
60
  const noteIds = new Set(mentions.map((m) => m.noteId));
61
+ // Once per hook run: the preset depends on the tenant only, not the note.
62
+ const tenantPreset = await resolveTenantRetentionPreset({
63
+ db: ctx.db,
64
+ registry: ctx.registry,
65
+ tenantId: ctx.tenantId,
66
+ });
61
67
  for (const noteId of noteIds) {
62
68
  const note = await ctx.db.fetchOne(noteEntryTable, { id: noteId });
63
69
  // Defensive: append-only rows are never hard-deleted, so this shouldn't
@@ -69,6 +75,7 @@ export const noteEntryDeleteHook = async (ctx) => {
69
75
  registry: ctx.registry,
70
76
  tenantId: ctx.tenantId,
71
77
  entityName: note.entityType,
78
+ preloadedTenantPreset: tenantPreset,
72
79
  });
73
80
  if (policyToStrategy(hostPolicy.policy?.strategy ?? null) === "anonymize")
74
81
  continue;
@@ -1,4 +1,5 @@
1
1
  export declare const PAT_FEATURE = "personal-access-tokens";
2
+ export declare const API_TOKEN_ENTITY_NAME: "api-token";
2
3
  export declare const PatErrors: {
3
4
  readonly ownershipDenied: "ownership_denied";
4
5
  readonly reauthRequired: "reauth_required";
@@ -2,6 +2,7 @@
2
2
  // Pure constants — client-marked so the web screen (web/) may import screen-id
3
3
  // and QN names without pulling the feature's server runtime barrel.
4
4
  export const PAT_FEATURE = "personal-access-tokens";
5
+ export const API_TOKEN_ENTITY_NAME = "api-token";
5
6
  // Snake_case reason strings (Error-Reasons guard: no colons/dashes).
6
7
  export const PatErrors = {
7
8
  ownershipDenied: "ownership_denied",
@@ -2,7 +2,7 @@ import { EXT_TOKEN_VERIFIER, } from "@cosmicdrift/kumiko-bundled-features/auth-f
2
2
  import { PAT_TOKEN_PREFIX } from "@cosmicdrift/kumiko-framework/api";
3
3
  import { deriveEntityTableMeta } from "@cosmicdrift/kumiko-framework/db";
4
4
  import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
5
- import { PAT_DEFAULT_RATE_LIMIT, PAT_FEATURE } from "./constants.js";
5
+ import { API_TOKEN_ENTITY_NAME, PAT_DEFAULT_RATE_LIMIT, PAT_FEATURE, } from "./constants.js";
6
6
  import { buildAvailableScopesQuery } from "./handlers/available-scopes.query.js";
7
7
  import { createPatCreateHandler } from "./handlers/create.write.js";
8
8
  import { listPatQuery } from "./handlers/list.query.js";
@@ -62,7 +62,7 @@ export function createPersonalAccessTokensFeature(options) {
62
62
  // Direct-write store like store_user_sessions: create/revoke write it, the
63
63
  // resolver point-reads it. r.entity would make it a rebuildable projection
64
64
  // whose replay (no token events) would wipe every live token (#498/#494).
65
- r.storeTable(deriveEntityTableMeta("api-token", apiTokenEntity, { source: "unmanaged" }), {
65
+ r.storeTable(deriveEntityTableMeta(API_TOKEN_ENTITY_NAME, apiTokenEntity, { source: "unmanaged" }), {
66
66
  reason: "read_side.api_tokens_direct_write",
67
67
  // create.write encrypts `name` via encryptForDirectWrite (#820).
68
68
  piiEncryptedOnWrite: true,
@@ -6,7 +6,7 @@ import { Temporal } from "temporal-polyfill";
6
6
  import * as z from "zod";
7
7
  import { encryptForDirectWrite, verifyPassword } from "../../shared/index.js";
8
8
  import { UserQueries } from "../../user/index.js";
9
- import { PAT_DEFAULT_EXPIRES_IN_DAYS, PatErrors } from "../constants.js";
9
+ import { API_TOKEN_ENTITY_NAME, PAT_DEFAULT_EXPIRES_IN_DAYS, PatErrors } from "../constants.js";
10
10
  import { mintPatToken } from "../hash.js";
11
11
  import { apiTokenEntity, apiTokenTable } from "../schema/api-token.js";
12
12
  // @wrapper-known error-helper
@@ -67,7 +67,7 @@ export function createPatCreateHandler(opts = {}) {
67
67
  const { raw, hash, prefix } = mintPatToken();
68
68
  const now = Temporal.Now.instant();
69
69
  const id = generateId();
70
- const row = await encryptForDirectWrite(apiTokenEntity, "api-token", {
70
+ const row = await encryptForDirectWrite(apiTokenEntity, API_TOKEN_ENTITY_NAME, {
71
71
  id,
72
72
  userId: event.user.id,
73
73
  tenantId: event.user.tenantId,
@@ -4,7 +4,7 @@ import { UnprocessableError, writeFailure } from "@cosmicdrift/kumiko-framework/
4
4
  import { generateId } from "@cosmicdrift/kumiko-framework/utils";
5
5
  import { Temporal } from "temporal-polyfill";
6
6
  import * as z from "zod";
7
- import { PatErrors } from "../constants.js";
7
+ import { API_TOKEN_ENTITY_NAME, PatErrors } from "../constants.js";
8
8
  import { PAT_REVOKED_AGGREGATE_TYPE, PAT_REVOKED_EVENT_QN } from "../pat-revoked-event.js";
9
9
  import { apiTokenTable } from "../schema/api-token.js";
10
10
  // Revoke one of the caller's own tokens. Ownership is enforced in the WHERE
@@ -43,7 +43,7 @@ export const revokePatWrite = defineWriteHandler({
43
43
  i18nKey: "errors.ownershipDenied",
44
44
  details: {
45
45
  scope: "entity",
46
- entityName: "api-token",
46
+ entityName: API_TOKEN_ENTITY_NAME,
47
47
  action: "revoke",
48
48
  userId: event.user.id,
49
49
  },
@@ -1,3 +1,8 @@
1
+ import { ACTION_FORM_ENTITY } from "@cosmicdrift/kumiko-framework/ui-types";
2
+ import { fieldLabelKey, fieldOptionLabelKey } from "@cosmicdrift/kumiko-headless";
3
+ import { PAT_FEATURE } from "./constants.js";
4
+ const mintFieldKey = (field) => fieldLabelKey(PAT_FEATURE, ACTION_FORM_ENTITY, field);
5
+ const scopeOptionKey = (grant) => fieldOptionLabelKey(PAT_FEATURE, ACTION_FORM_ENTITY, "scopes", grant);
1
6
  export const PAT_FEATURE_I18N = {
2
7
  "screen:api-tokens.title": { en: "Personal Access Tokens" },
3
8
  "screen:api-token-create.title": { en: "Create a new token" },
@@ -23,13 +28,11 @@ export const PAT_FEATURE_I18N = {
23
28
  // (`__action-form__`, see action-form-shim.ts) — required by the i18n
24
29
  // boot-validator (requiredKeysFromScreen); the declarative renderer
25
30
  // resolves these through the schema payload, not a client component's t().
26
- "personal-access-tokens:entity:__action-form__:field:name": { en: "Name" },
27
- "personal-access-tokens:entity:__action-form__:field:scopes": { en: "Per-API access" },
28
- "personal-access-tokens:entity:__action-form__:field:expiresInDays": { en: "Expires in (days)" },
29
- "personal-access-tokens:entity:__action-form__:field:currentPassword": {
30
- en: "Password (to confirm)",
31
- },
32
- "personal-access-tokens:entity:__action-form__:field:mfaCode": { en: "2FA code (if enabled)" },
31
+ [mintFieldKey("name")]: { en: "Name" },
32
+ [mintFieldKey("scopes")]: { en: "Per-API access" },
33
+ [mintFieldKey("expiresInDays")]: { en: "Expires in (days)" },
34
+ [mintFieldKey("currentPassword")]: { en: "Password (to confirm)" },
35
+ [mintFieldKey("mfaCode")]: { en: "2FA code (if enabled)" },
33
36
  };
34
37
  // Per-grant-option labels for the mint form's `scopes` multiSelect —
35
38
  // "<domain> (read)" / "<domain> (read & write)", matching the wording the
@@ -38,13 +41,9 @@ export const PAT_FEATURE_I18N = {
38
41
  export function patScopeOptionTranslations(scopes) {
39
42
  const out = {};
40
43
  for (const [domain, def] of Object.entries(scopes)) {
41
- out[`personal-access-tokens:entity:__action-form__:field:scopes:option:${domain}:read`] = {
42
- en: `${def.label} (read)`,
43
- };
44
+ out[scopeOptionKey(`${domain}:read`)] = { en: `${def.label} (read)` };
44
45
  if (def.write && def.write.length > 0) {
45
- out[`personal-access-tokens:entity:__action-form__:field:scopes:option:${domain}:write`] = {
46
- en: `${def.label} (read & write)`,
47
- };
46
+ out[scopeOptionKey(`${domain}:write`)] = { en: `${def.label} (read & write)` };
48
47
  }
49
48
  }
50
49
  return out;
@@ -9,6 +9,7 @@ export declare const apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
9
9
  readonly filterable?: boolean;
10
10
  readonly encrypted?: boolean;
11
11
  readonly sensitive?: boolean;
12
+ readonly writeOnly?: boolean;
12
13
  readonly readAsInstruction?: boolean;
13
14
  readonly format?: "email" | "url" | "phone" | "password";
14
15
  readonly default?: string;
@@ -29,6 +30,7 @@ export declare const apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
29
30
  readonly filterable?: boolean;
30
31
  readonly encrypted?: boolean;
31
32
  readonly sensitive?: boolean;
33
+ readonly writeOnly?: boolean;
32
34
  readonly readAsInstruction?: boolean;
33
35
  readonly format?: "email" | "url" | "phone" | "password";
34
36
  readonly default?: string;
@@ -49,6 +51,7 @@ export declare const apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
49
51
  readonly filterable?: boolean;
50
52
  readonly encrypted?: boolean;
51
53
  readonly sensitive?: boolean;
54
+ readonly writeOnly?: boolean;
52
55
  readonly readAsInstruction?: boolean;
53
56
  readonly format?: "email" | "url" | "phone" | "password";
54
57
  readonly default?: string;
@@ -69,6 +72,7 @@ export declare const apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
69
72
  readonly filterable?: boolean;
70
73
  readonly encrypted?: boolean;
71
74
  readonly sensitive?: boolean;
75
+ readonly writeOnly?: boolean;
72
76
  readonly readAsInstruction?: boolean;
73
77
  readonly format?: "email" | "url" | "phone" | "password";
74
78
  readonly default?: string;
@@ -89,6 +93,7 @@ export declare const apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
89
93
  readonly filterable?: boolean;
90
94
  readonly encrypted?: boolean;
91
95
  readonly sensitive?: boolean;
96
+ readonly writeOnly?: boolean;
92
97
  readonly readAsInstruction?: boolean;
93
98
  readonly format?: "email" | "url" | "phone" | "password";
94
99
  readonly default?: string;
@@ -109,6 +114,7 @@ export declare const apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
109
114
  readonly filterable?: boolean;
110
115
  readonly encrypted?: boolean;
111
116
  readonly sensitive?: boolean;
117
+ readonly writeOnly?: boolean;
112
118
  readonly readAsInstruction?: boolean;
113
119
  readonly format?: "email" | "url" | "phone" | "password";
114
120
  readonly default?: string;
@@ -1,5 +1,6 @@
1
1
  import { deriveEntityTableMeta } from "@cosmicdrift/kumiko-framework/db";
2
2
  import { access, createEntity, createTextField, createTimestampField, } from "@cosmicdrift/kumiko-framework/engine";
3
+ import { API_TOKEN_ENTITY_NAME } from "../constants.js";
3
4
  // One row per Personal Access Token. Like store_user_sessions this is a
4
5
  // direct-write store (r.unmanagedTable): the create/revoke handlers write it and
5
6
  // the resolver point-reads it on the hot auth path. `tokenHash` is the SHA-256
@@ -80,6 +81,6 @@ export const apiTokenEntity = createEntity({
80
81
  // deriveEntityTableMeta (not buildEntityTable): this is a direct-write store, so
81
82
  // the table must be a WritableTable (post ES-write-brand #742) — same as
82
83
  // sessions' userSessionTable. buildEntityTable is branded executor-only.
83
- export const apiTokenTable = deriveEntityTableMeta("api-token", apiTokenEntity, {
84
+ export const apiTokenTable = deriveEntityTableMeta(API_TOKEN_ENTITY_NAME, apiTokenEntity, {
84
85
  source: "unmanaged",
85
86
  });
@@ -1,10 +1,14 @@
1
- import { i18nKey, } from "@cosmicdrift/kumiko-framework/engine";
1
+ import { i18nKey, MAX_LIST_LIMIT, } from "@cosmicdrift/kumiko-framework/engine";
2
2
  import { PAT_MINT_SCREEN_ID, PAT_SCREEN_ID, PatHandlers, PatQueries } from "./constants.js";
3
3
  const PAT_STATUS_OPTION_KEY_PREFIX = "pat.list.status.";
4
4
  export const patListScreen = {
5
5
  id: PAT_SCREEN_ID,
6
6
  type: "projectionList",
7
7
  query: PatQueries.mine,
8
+ // The `mine` handler honours `limit` only (no offset/total), so a pager would
9
+ // show page 1 forever: send one max-size request and render no pager.
10
+ pagination: false,
11
+ pageSize: MAX_LIST_LIMIT,
8
12
  defaultSort: { field: "createdAt", dir: "desc" },
9
13
  columns: [
10
14
  { field: "name", label: i18nKey("pat.list.col.name") },
@@ -1,5 +1,6 @@
1
1
  import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
2
2
  import { rateLimitStatus } from "./handlers/status.query.js";
3
+ import { RATE_LIMITING_I18N } from "./i18n.js";
3
4
  // Opt-in feature. Loading it does NOT install rate-limit middleware —
4
5
  // the framework auto-wires the L3 dispatcher hook and the resolver
5
6
  // when (a) at least one handler declared a rateLimit option, OR (b) the
@@ -17,5 +18,6 @@ export function createRateLimitingFeature() {
17
18
  recommended: false,
18
19
  });
19
20
  r.queryHandler(rateLimitStatus);
21
+ r.translations({ keys: RATE_LIMITING_I18N });
20
22
  }, { dedupeOptions: {} });
21
23
  }
@@ -1,23 +1,31 @@
1
+ import { ROLES } from "@cosmicdrift/kumiko-framework/auth";
1
2
  import { AccessDeniedError } from "@cosmicdrift/kumiko-framework/errors";
2
3
  import { RateLimitErrors } from "../constants.js";
4
+ // Typed against RateLimitPer so a renamed dimension in bucket.ts
5
+ // breaks the typecheck instead of silently denying access.
6
+ const BUCKET_OWNER_KIND = {
7
+ tenant: "tenant",
8
+ "tenant+handler": "tenant",
9
+ user: "user",
10
+ "user+handler": "user",
11
+ };
12
+ function isOwnableDimension(dimension) {
13
+ return Object.hasOwn(BUCKET_OWNER_KIND, dimension);
14
+ }
3
15
  function ownsBucket(bucket, caller) {
4
16
  const [dimension, subject, ...handler] = bucket.split(":");
5
17
  if (dimension === undefined)
6
18
  return false;
7
- const owner = dimension === "tenant" || dimension === "tenant+handler"
8
- ? caller.tenantId
9
- : dimension === "user" || dimension === "user+handler"
10
- ? caller.id
11
- : undefined;
12
- if (owner === undefined)
19
+ if (!isOwnableDimension(dimension))
13
20
  return false;
21
+ const owner = BUCKET_OWNER_KIND[dimension] === "tenant" ? caller.tenantId : caller.id;
14
22
  // Segment-exact: a startsWith check would pass `tenant:<own-id>-other`.
15
23
  if (subject !== owner)
16
24
  return false;
17
25
  return dimension.endsWith("+handler") ? handler.length > 0 : handler.length === 0;
18
26
  }
19
27
  export function bucketAccessDenied(bucket, caller) {
20
- if (caller.roles.includes("SystemAdmin"))
28
+ if (caller.roles.includes(ROLES.SystemAdmin))
21
29
  return undefined;
22
30
  if (ownsBucket(bucket, caller))
23
31
  return undefined;
@@ -0,0 +1,10 @@
1
+ export declare const RATE_LIMITING_I18N: {
2
+ readonly "rateLimiting.errors.bucketOutsideTenant": {
3
+ readonly en: "You can only inspect rate-limit buckets that belong to your own tenant or user.";
4
+ readonly de: "Du kannst nur Rate-Limit-Buckets deines eigenen Mandanten oder Benutzers einsehen.";
5
+ };
6
+ readonly "rateLimiting.errors.resolverUnavailable": {
7
+ readonly en: "Rate limiting is not available on this server.";
8
+ readonly de: "Rate-Limiting ist auf diesem Server nicht verfügbar.";
9
+ };
10
+ };
@@ -0,0 +1,10 @@
1
+ export const RATE_LIMITING_I18N = {
2
+ "rateLimiting.errors.bucketOutsideTenant": {
3
+ en: "You can only inspect rate-limit buckets that belong to your own tenant or user.",
4
+ de: "Du kannst nur Rate-Limit-Buckets deines eigenen Mandanten oder Benutzers einsehen.",
5
+ },
6
+ "rateLimiting.errors.resolverUnavailable": {
7
+ en: "Rate limiting is not available on this server.",
8
+ de: "Rate-Limiting ist auf diesem Server nicht verfügbar.",
9
+ },
10
+ };
@@ -9,6 +9,7 @@ import { rotateJob } from "./handlers/rotate.job.js";
9
9
  import { createSetHandler } from "./handlers/set.write.js";
10
10
  import { secretReadSchema } from "./secrets-context.js";
11
11
  import { tenantSecretEntity, tenantSecretsTable } from "./table.js";
12
+ const ROTATE_ESCAPE_HATCH_REASON = "re-encrypts every tenant's secrets with the new master key";
12
13
  /**
13
14
  * Env-vars contract for the `secrets` feature. Apps merge this via
14
15
  * `composeEnvSchema({ features: [secretsFeature, ...] })` so boot-time
@@ -130,9 +131,9 @@ export function createSecretsFeature(opts = {}) {
130
131
  name: "rotate",
131
132
  trigger: { manual: true },
132
133
  escapeHatch: {
133
- reason: "re-encrypts every tenant's secrets with the new master key",
134
+ reason: ROTATE_ESCAPE_HATCH_REASON,
134
135
  },
135
- handler: (payload, ctx) => rotateJob(payload, ctx, ctx.db.unsafeRaw("re-encrypts every tenant's secrets with the new master key")),
136
+ handler: (payload, ctx) => rotateJob(payload, ctx, ctx.db.unsafeRaw(ROTATE_ESCAPE_HATCH_REASON)),
136
137
  });
137
138
  // Pre-ES had a separate `retention-cleanup` job scrubbing the audit
138
139
  // table on a compliance-driven schedule (90d default). Post-ES the
@@ -14,6 +14,7 @@ export declare const tenantSecretEntity: import("@cosmicdrift/kumiko-types/field
14
14
  readonly filterable?: boolean;
15
15
  readonly encrypted?: boolean;
16
16
  readonly sensitive?: boolean;
17
+ readonly writeOnly?: boolean;
17
18
  readonly readAsInstruction?: boolean;
18
19
  readonly format?: "email" | "url" | "phone" | "password";
19
20
  readonly default?: string;
@@ -17,7 +17,7 @@ export const seoConfigQuery = defineQueryHandler({
17
17
  name: "config",
18
18
  schema: z.object({}),
19
19
  access: { roles: ["anonymous", "User", "TenantAdmin", "SystemAdmin"] },
20
- rateLimit: { per: "ip", limit: 60, windowSeconds: 60 },
20
+ rateLimit: { per: "ip+handler", limit: 60, windowSeconds: 60 },
21
21
  description: "Reads the tenant's SEO metadata settings — organization name and logo URL, Twitter site handle, LLMs summary and default OG image — as plain strings; use it to inspect what the sitemap, llms.txt and page meta tags will publish.",
22
22
  handler: async (_query, ctx) => {
23
23
  const read = async (qualifiedKey) => {
@@ -1,4 +1,5 @@
1
1
  export declare const SESSIONS_FEATURE: "sessions";
2
+ export declare const USER_SESSION_ENTITY_NAME: "user-session";
2
3
  export declare const SessionHandlers: {
3
4
  readonly revoke: "sessions:write:user-session:revoke";
4
5
  readonly revokeAllOthers: "sessions:write:user-session:revoke-all-others";
@@ -1,4 +1,5 @@
1
1
  export const SESSIONS_FEATURE = "sessions";
2
+ export const USER_SESSION_ENTITY_NAME = "user-session";
2
3
  // Qualified write handler names — entity prefix is "userSession", snake_case
3
4
  // "user_session" on the wire.
4
5
  export const SessionHandlers = {
@@ -1,6 +1,7 @@
1
1
  import { EXT_SESSION_STORE, } from "@cosmicdrift/kumiko-bundled-features/auth-foundation";
2
2
  import { deriveEntityTableMeta } from "@cosmicdrift/kumiko-framework/db";
3
3
  import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
4
+ import { USER_SESSION_ENTITY_NAME } from "./constants.js";
4
5
  import { cleanupJob } from "./handlers/cleanup.job.js";
5
6
  import { detailQuery } from "./handlers/detail.query.js";
6
7
  import { listQuery } from "./handlers/list.query.js";
@@ -13,6 +14,7 @@ import { userSessionEntity } from "./schema/user-session.js";
13
14
  import { sessionDetailScreen, sessionListScreen, sessionMineScreen } from "./screens.js";
14
15
  import { createSessionCallbacks, } from "./session-callbacks.js";
15
16
  import { SESSION_REVOKED_EVENT_SHORT, sessionRevokedSchema } from "./session-revoked-event.js";
17
+ const CLEANUP_ESCAPE_HATCH_REASON = "deletes expired session rows of every tenant";
16
18
  // Reads the late-bind setter off a mounted sessions feature's exports.
17
19
  // run{Prod,Dev}App call it once the DB connection is concrete — the feature
18
20
  // itself is constructed in app run-config long before a db exists, so the
@@ -66,7 +68,7 @@ export function createSessionsFeature(options) {
66
68
  // (#498/#494). r.storeTable keeps the migration DDL but opts the
67
69
  // table out of implicit rebuild, like jobs/channel-in-app/feature-toggles
68
70
  // which are direct-write stores too.
69
- r.storeTable(deriveEntityTableMeta("user-session", userSessionEntity, { source: "unmanaged" }), {
71
+ r.storeTable(deriveEntityTableMeta(USER_SESSION_ENTITY_NAME, userSessionEntity, { source: "unmanaged" }), {
70
72
  reason: "read_side.user_sessions_direct_write",
71
73
  // sessionCreator encrypts ip/userAgent via encryptForDirectWrite (#820).
72
74
  piiEncryptedOnWrite: true,
@@ -118,8 +120,8 @@ export function createSessionsFeature(options) {
118
120
  r.job({
119
121
  name: "cleanup",
120
122
  trigger: { manual: true },
121
- escapeHatch: { reason: "deletes expired session rows of every tenant" },
122
- handler: (payload, ctx) => cleanupJob(payload, ctx, ctx.db.unsafeRaw("deletes expired session rows of every tenant")),
123
+ escapeHatch: { reason: CLEANUP_ESCAPE_HATCH_REASON },
124
+ handler: (payload, ctx) => cleanupJob(payload, ctx, ctx.db.unsafeRaw(CLEANUP_ESCAPE_HATCH_REASON)),
123
125
  });
124
126
  // Cross-feature entity hook on "user". `r.entityHook` (NOT `r.hook`) is
125
127
  // the supported cross-feature path: entity-keyed, not prefixed by the