@cosmicdrift/kumiko-framework 0.386.0 → 0.388.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 (468) hide show
  1. package/AGENTS.md +6 -1
  2. package/LICENSE +21 -57
  3. package/README.md +2 -2
  4. package/agents/examples/anonymous-access.md +1 -1
  5. package/agents/examples/apex-landing.md +204 -0
  6. package/agents/examples/api-worker-split.md +64 -0
  7. package/agents/examples/app-config.md +14 -0
  8. package/agents/examples/auth-mfa.md +115 -0
  9. package/agents/examples/env-composition.md +28 -1
  10. package/agents/examples/legal-pages.md +1 -1
  11. package/agents/examples/prod-boot-options.md +16 -0
  12. package/agents/examples/rate-limiting.md +48 -0
  13. package/agents/examples/record-detail-layout.md +692 -0
  14. package/agents/examples/seed-migration.md +212 -0
  15. package/agents/examples/wizard-form.md +941 -0
  16. package/agents/examples/writeform-section.md +548 -0
  17. package/agents/features.md +19 -2
  18. package/agents/widgets.md +40 -39
  19. package/dist/api/api-constants.js +2 -2
  20. package/dist/api/auth-middleware.js +7 -7
  21. package/dist/api/auth-rate-limiters.js +7 -3
  22. package/dist/api/auth-routes-mfa.js +1 -1
  23. package/dist/api/auth-routes-session.js +1 -1
  24. package/dist/api/auth-routes-shared.js +7 -3
  25. package/dist/api/auth-routes-signup.js +1 -1
  26. package/dist/api/client-error-route.js +8 -2
  27. package/dist/api/client-ip.js +3 -1
  28. package/dist/api/extra-route.js +1 -1
  29. package/dist/api/index.d.ts +3 -1
  30. package/dist/api/index.js +3 -1
  31. package/dist/api/pii-leak-guard.js +4 -2
  32. package/dist/api/readiness.js +5 -5
  33. package/dist/api/redis-sse-broker.js +8 -6
  34. package/dist/api/request-context.d.ts +1 -1
  35. package/dist/api/request-context.js +1 -1
  36. package/dist/api/routes.js +2 -2
  37. package/dist/api/server-boot-checks.d.ts +10 -0
  38. package/dist/api/server-boot-checks.js +157 -0
  39. package/dist/api/server-event-dispatcher.d.ts +26 -0
  40. package/dist/api/server-event-dispatcher.js +208 -0
  41. package/dist/api/server-extra-routes.d.ts +11 -0
  42. package/dist/api/server-extra-routes.js +203 -0
  43. package/dist/api/server-middleware.d.ts +20 -0
  44. package/dist/api/server-middleware.js +211 -0
  45. package/dist/api/server-routes.d.ts +19 -0
  46. package/dist/api/server-routes.js +293 -0
  47. package/dist/api/server-websocket-routes.d.ts +4 -0
  48. package/dist/api/server-websocket-routes.js +108 -0
  49. package/dist/api/server-wiring.d.ts +32 -0
  50. package/dist/api/server-wiring.js +236 -0
  51. package/dist/api/server.d.ts +10 -15
  52. package/dist/api/server.js +48 -1250
  53. package/dist/api/session-cookies.js +4 -4
  54. package/dist/api/sse-broker.js +1 -1
  55. package/dist/api/sse-route.d.ts +13 -13
  56. package/dist/api/sse-route.js +31 -27
  57. package/dist/auth/index.js +4 -4
  58. package/dist/auth/roles.d.ts +4 -4
  59. package/dist/auth/roles.js +24 -24
  60. package/dist/compliance/duration-spec.js +18 -20
  61. package/dist/compliance/index.js +2 -3
  62. package/dist/compliance/override-schema.d.ts +4 -4
  63. package/dist/compliance/override-schema.js +19 -22
  64. package/dist/compliance/profiles.d.ts +26 -29
  65. package/dist/compliance/profiles.js +62 -69
  66. package/dist/compliance/sub-processors.d.ts +28 -30
  67. package/dist/compliance/sub-processors.js +17 -21
  68. package/dist/consumer-cli.js +4 -4
  69. package/dist/crypto/blind-index.js +18 -17
  70. package/dist/crypto/ciphertext-pattern.js +1 -1
  71. package/dist/crypto/event-pii.js +3 -3
  72. package/dist/crypto/kek-source.d.ts +1 -1
  73. package/dist/crypto/kek-source.js +3 -2
  74. package/dist/crypto/kms-adapter.d.ts +1 -1
  75. package/dist/crypto/kms-adapter.js +4 -6
  76. package/dist/crypto/kms-wiring.d.ts +1 -2
  77. package/dist/crypto/kms-wiring.js +2 -2
  78. package/dist/crypto/owner-transition-rekey.d.ts +5 -1
  79. package/dist/crypto/owner-transition-rekey.js +1 -1
  80. package/dist/crypto/pg-kms-adapter.js +2 -6
  81. package/dist/crypto/pii-field-encryption.d.ts +5 -1
  82. package/dist/crypto/pii-field-encryption.js +7 -5
  83. package/dist/crypto/subject-resolver.d.ts +1 -1
  84. package/dist/crypto/subject-resolver.js +2 -2
  85. package/dist/db/aggregate-sql.d.ts +21 -0
  86. package/dist/db/aggregate-sql.js +155 -0
  87. package/dist/db/apply-entity-event.d.ts +6 -6
  88. package/dist/db/apply-entity-event.js +55 -55
  89. package/dist/db/blind-index-cleanup.js +7 -7
  90. package/dist/db/closed-connection.d.ts +9 -0
  91. package/dist/db/closed-connection.js +95 -0
  92. package/dist/db/collect-table-metas.js +10 -11
  93. package/dist/db/compound-types.d.ts +1 -1
  94. package/dist/db/compound-types.js +12 -12
  95. package/dist/db/cursor.js +3 -4
  96. package/dist/db/dialect.d.ts +2 -2
  97. package/dist/db/dialect.js +3 -3
  98. package/dist/db/distinct-on.d.ts +1 -1
  99. package/dist/db/driver-row-coercion.d.ts +2 -0
  100. package/dist/db/driver-row-coercion.js +186 -0
  101. package/dist/db/driver-value-preparation.d.ts +16 -0
  102. package/dist/db/driver-value-preparation.js +99 -0
  103. package/dist/db/eagerload.d.ts +3 -3
  104. package/dist/db/eagerload.js +21 -12
  105. package/dist/db/entity-field-encryption.d.ts +1 -0
  106. package/dist/db/entity-field-encryption.js +1 -1
  107. package/dist/db/entity-table-meta.d.ts +2 -2
  108. package/dist/db/entity-table-meta.js +105 -112
  109. package/dist/db/event-store-executor-context.d.ts +3 -2
  110. package/dist/db/event-store-executor-context.js +13 -13
  111. package/dist/db/event-store-executor-read-aggregate.d.ts +8 -0
  112. package/dist/db/event-store-executor-read-aggregate.js +112 -0
  113. package/dist/db/event-store-executor-read-filter.d.ts +31 -0
  114. package/dist/db/event-store-executor-read-filter.js +203 -0
  115. package/dist/db/event-store-executor-read-paging.d.ts +14 -0
  116. package/dist/db/event-store-executor-read-paging.js +99 -0
  117. package/dist/db/event-store-executor-read-rows.d.ts +6 -0
  118. package/dist/db/event-store-executor-read-rows.js +32 -0
  119. package/dist/db/event-store-executor-read-search.d.ts +28 -0
  120. package/dist/db/event-store-executor-read-search.js +221 -0
  121. package/dist/db/event-store-executor-read-sort.d.ts +41 -0
  122. package/dist/db/event-store-executor-read-sort.js +130 -0
  123. package/dist/db/event-store-executor-read-where.d.ts +24 -0
  124. package/dist/db/event-store-executor-read-where.js +81 -0
  125. package/dist/db/event-store-executor-read.d.ts +2 -9
  126. package/dist/db/event-store-executor-read.js +50 -704
  127. package/dist/db/event-store-executor-write-steps.d.ts +20 -0
  128. package/dist/db/event-store-executor-write-steps.js +100 -0
  129. package/dist/db/event-store-executor-write.js +401 -510
  130. package/dist/db/index.d.ts +6 -2
  131. package/dist/db/index.js +5 -1
  132. package/dist/db/list-aggregate-labels.d.ts +14 -0
  133. package/dist/db/list-aggregate-labels.js +26 -0
  134. package/dist/db/located-timestamp.d.ts +11 -11
  135. package/dist/db/located-timestamp.js +29 -30
  136. package/dist/db/migrate-generator.d.ts +1 -1
  137. package/dist/db/migrate-generator.js +97 -105
  138. package/dist/db/migrate-runner.js +29 -26
  139. package/dist/db/money.d.ts +6 -6
  140. package/dist/db/money.js +29 -27
  141. package/dist/db/queries/backfill-pii.js +5 -5
  142. package/dist/db/queries/ddl.js +1 -1
  143. package/dist/db/queries/es-ops.d.ts +1 -1
  144. package/dist/db/queries/es-ops.js +1 -1
  145. package/dist/db/queries/event-consumer.js +3 -3
  146. package/dist/db/queries/event-store.js +4 -2
  147. package/dist/db/queries/projection-rebuild.js +3 -3
  148. package/dist/db/queries/raw-sql.d.ts +2 -2
  149. package/dist/db/queries/raw-sql.js +2 -2
  150. package/dist/db/queries/shadow-swap.js +19 -19
  151. package/dist/db/query.d.ts +3 -16
  152. package/dist/db/query.js +8 -761
  153. package/dist/db/rebuild-marker.js +20 -21
  154. package/dist/db/reference-data.js +6 -1
  155. package/dist/db/replay-migration-sql.js +2 -2
  156. package/dist/db/schema-inspection.js +4 -5
  157. package/dist/db/table-builder.d.ts +1 -2
  158. package/dist/db/table-builder.js +137 -190
  159. package/dist/db/table-info.d.ts +10 -0
  160. package/dist/db/table-info.js +45 -0
  161. package/dist/db/temporal-value-guards.d.ts +6 -0
  162. package/dist/db/temporal-value-guards.js +15 -0
  163. package/dist/db/tenant-db-runner.d.ts +1 -0
  164. package/dist/db/tenant-db-runner.js +3 -0
  165. package/dist/db/tenant-db.d.ts +2 -2
  166. package/dist/db/tenant-db.js +48 -11
  167. package/dist/db/where-clause.d.ts +6 -0
  168. package/dist/db/where-clause.js +236 -0
  169. package/dist/derivatives/delete-stored-file.js +6 -2
  170. package/dist/derivatives/derivatives-context.js +1 -1
  171. package/dist/engine/boot-validator/access-roles.js +4 -3
  172. package/dist/engine/boot-validator/api-ext.js +14 -14
  173. package/dist/engine/boot-validator/boot-check.js +1 -1
  174. package/dist/engine/boot-validator/config-deps.js +7 -6
  175. package/dist/engine/boot-validator/detail-screens.js +4 -4
  176. package/dist/engine/boot-validator/entity-handler.d.ts +1 -0
  177. package/dist/engine/boot-validator/entity-handler.js +77 -61
  178. package/dist/engine/boot-validator/form-derivations.js +35 -1
  179. package/dist/engine/boot-validator/gdpr-storage.js +4 -4
  180. package/dist/engine/boot-validator/i18n-keys.js +6 -4
  181. package/dist/engine/boot-validator/index.d.ts +2 -2
  182. package/dist/engine/boot-validator/index.js +22 -20
  183. package/dist/engine/boot-validator/nav.js +5 -5
  184. package/dist/engine/boot-validator/ownership.js +12 -9
  185. package/dist/engine/boot-validator/parent-ref.js +1 -1
  186. package/dist/engine/boot-validator/pii-retention.js +258 -187
  187. package/dist/engine/boot-validator/projection-list-screens.js +7 -7
  188. package/dist/engine/boot-validator/query-refs.js +1 -1
  189. package/dist/engine/boot-validator/screens-actions.d.ts +1 -0
  190. package/dist/engine/boot-validator/screens-actions.js +23 -17
  191. package/dist/engine/boot-validator/screens-dashboard.js +2 -5
  192. package/dist/engine/boot-validator/screens-forms.js +12 -17
  193. package/dist/engine/boot-validator/screens-kind-config-edit.d.ts +3 -0
  194. package/dist/engine/boot-validator/screens-kind-config-edit.js +88 -0
  195. package/dist/engine/boot-validator/screens-kind-entity-edit.d.ts +3 -0
  196. package/dist/engine/boot-validator/screens-kind-entity-edit.js +55 -0
  197. package/dist/engine/boot-validator/screens-kind-entity-list.d.ts +3 -0
  198. package/dist/engine/boot-validator/screens-kind-entity-list.js +228 -0
  199. package/dist/engine/boot-validator/screens-kind-projection-detail.d.ts +3 -0
  200. package/dist/engine/boot-validator/screens-kind-projection-detail.js +235 -0
  201. package/dist/engine/boot-validator/screens-kind-projection-list.d.ts +3 -0
  202. package/dist/engine/boot-validator/screens-kind-projection-list.js +52 -0
  203. package/dist/engine/boot-validator/screens-kind-secret-mint.d.ts +3 -0
  204. package/dist/engine/boot-validator/screens-kind-secret-mint.js +28 -0
  205. package/dist/engine/boot-validator/screens-kind-shared.d.ts +37 -0
  206. package/dist/engine/boot-validator/screens-kind-shared.js +142 -0
  207. package/dist/engine/boot-validator/screens-lists.d.ts +2 -1
  208. package/dist/engine/boot-validator/screens-lists.js +26 -24
  209. package/dist/engine/boot-validator/screens.d.ts +0 -2
  210. package/dist/engine/boot-validator/screens.js +102 -869
  211. package/dist/engine/boot-validator/security-baseline.js +3 -2
  212. package/dist/engine/boot-validator/transfer-graph.js +2 -2
  213. package/dist/engine/boot-validator/workspaces.js +3 -3
  214. package/dist/engine/bridge-stub.js +3 -4
  215. package/dist/engine/build-app-schema.js +130 -170
  216. package/dist/engine/build-config-feature-schema.js +50 -50
  217. package/dist/engine/build-target.js +20 -23
  218. package/dist/engine/codemod/pipeline-codemod.js +20 -13
  219. package/dist/engine/config-helpers.js +4 -4
  220. package/dist/engine/constants.js +2 -2
  221. package/dist/engine/create-app.js +2 -2
  222. package/dist/engine/define-feature.js +21 -13
  223. package/dist/engine/define-handler.js +13 -11
  224. package/dist/engine/define-step.js +1 -1
  225. package/dist/engine/define-workflow.d.ts +5 -5
  226. package/dist/engine/define-workflow.js +6 -6
  227. package/dist/engine/entity-handlers.d.ts +13 -0
  228. package/dist/engine/entity-handlers.js +22 -34
  229. package/dist/engine/entity-list-runtime-options.d.ts +8 -0
  230. package/dist/engine/entity-list-runtime-options.js +18 -0
  231. package/dist/engine/event-def.js +2 -2
  232. package/dist/engine/extension-names.d.ts +16 -17
  233. package/dist/engine/extension-names.js +37 -38
  234. package/dist/engine/extensions/escape-hatch-usage.js +1 -1
  235. package/dist/engine/extensions/tenant-data.js +2 -2
  236. package/dist/engine/extensions/user-data.d.ts +23 -24
  237. package/dist/engine/factories.d.ts +14 -13
  238. package/dist/engine/factories.js +38 -37
  239. package/dist/engine/feature-ast/entity-field-types.js +1 -1
  240. package/dist/engine/feature-ast/extractors/handlers.js +2 -1
  241. package/dist/engine/feature-ast/extractors/hooks.js +1 -1
  242. package/dist/engine/feature-ast/extractors/round2.js +25 -6
  243. package/dist/engine/feature-ast/extractors/shared.d.ts +2 -3
  244. package/dist/engine/feature-ast/extractors/shared.js +2 -3
  245. package/dist/engine/feature-ast/parse.js +13 -13
  246. package/dist/engine/feature-ast/patch.js +4 -182
  247. package/dist/engine/feature-ast/pattern-change-schema.d.ts +3 -0
  248. package/dist/engine/feature-ast/pattern-change-schema.js +2 -1
  249. package/dist/engine/feature-ast/pattern-id-matchers.d.ts +7 -0
  250. package/dist/engine/feature-ast/pattern-id-matchers.js +210 -0
  251. package/dist/engine/feature-ast/patterns.d.ts +11 -2
  252. package/dist/engine/feature-ast/patterns.js +43 -51
  253. package/dist/engine/feature-ast/render.js +50 -89
  254. package/dist/engine/feature-config-events-jobs.js +15 -12
  255. package/dist/engine/feature-entity-handlers.js +12 -5
  256. package/dist/engine/feature-manifest.d.ts +9 -9
  257. package/dist/engine/feature-manifest.js +6 -7
  258. package/dist/engine/feature-ui-extensions.d.ts +3 -3
  259. package/dist/engine/feature-ui-extensions.js +30 -22
  260. package/dist/engine/field-access.js +5 -6
  261. package/dist/engine/form-derivations.js +18 -7
  262. package/dist/engine/handler-field-references.js +1 -1
  263. package/dist/engine/index.d.ts +1 -0
  264. package/dist/engine/index.js +4 -4
  265. package/dist/engine/object-form.d.ts +1 -0
  266. package/dist/engine/object-form.js +14 -4
  267. package/dist/engine/ownership.js +3 -3
  268. package/dist/engine/parse-ref-target.js +5 -7
  269. package/dist/engine/pattern-library/index.js +2 -2
  270. package/dist/engine/pattern-library/library.js +2 -2
  271. package/dist/engine/pattern-library/types.js +3 -3
  272. package/dist/engine/pipeline.js +2 -2
  273. package/dist/engine/project-app-schema-for-roles.d.ts +2 -0
  274. package/dist/engine/project-app-schema-for-roles.js +7 -5
  275. package/dist/engine/projection-helpers.js +9 -9
  276. package/dist/engine/qualified-name.js +2 -2
  277. package/dist/engine/registry-facade.js +1 -1
  278. package/dist/engine/registry-ingest.js +13 -13
  279. package/dist/engine/registry-state.js +17 -19
  280. package/dist/engine/registry-validate.js +12 -12
  281. package/dist/engine/run-in.js +2 -2
  282. package/dist/engine/run-pipeline.js +7 -0
  283. package/dist/engine/schema-builder.js +251 -233
  284. package/dist/engine/screen-filter-ops.js +17 -20
  285. package/dist/engine/screen-helpers.js +4 -3
  286. package/dist/engine/screen-kind.d.ts +7 -0
  287. package/dist/engine/screen-kind.js +1 -0
  288. package/dist/engine/state-machine.d.ts +12 -12
  289. package/dist/engine/state-machine.js +7 -7
  290. package/dist/engine/steps/_resolver-utils.js +4 -3
  291. package/dist/engine/steps/for-each.js +4 -4
  292. package/dist/engine/steps/unsafe-projection-delete.js +1 -1
  293. package/dist/engine/steps/unsafe-projection-upsert.js +4 -4
  294. package/dist/engine/steps/wait-for-event.js +4 -4
  295. package/dist/engine/steps/workflow-system-events.js +3 -3
  296. package/dist/engine/strip-disabled-features-from-app-schema.js +51 -14
  297. package/dist/engine/tier-resolver-extension.d.ts +29 -27
  298. package/dist/engine/tier-resolver-extension.js +26 -26
  299. package/dist/engine/transfer-adjacency.js +2 -2
  300. package/dist/engine/validate-projection-allowlist.js +9 -28
  301. package/dist/engine/walk-step-instances.d.ts +2 -0
  302. package/dist/engine/walk-step-instances.js +38 -0
  303. package/dist/engine/where-rule-lint.js +1 -1
  304. package/dist/engine/write-handler-qn-extract.js +1 -1
  305. package/dist/entrypoint/index.d.ts +4 -1
  306. package/dist/entrypoint/index.js +10 -4
  307. package/dist/env/index.d.ts +7 -6
  308. package/dist/env/index.js +10 -9
  309. package/dist/errors/docs-url.d.ts +1 -1
  310. package/dist/errors/docs-url.js +22 -2
  311. package/dist/errors/transition-details.js +7 -7
  312. package/dist/errors/write-error-info.d.ts +6 -6
  313. package/dist/errors/write-error-info.js +6 -6
  314. package/dist/es-ops/context.d.ts +9 -9
  315. package/dist/es-ops/context.js +29 -29
  316. package/dist/es-ops/index.js +7 -9
  317. package/dist/es-ops/operations-schema.js +4 -10
  318. package/dist/es-ops/runner.d.ts +11 -11
  319. package/dist/es-ops/runner.js +49 -53
  320. package/dist/es-ops/types.d.ts +55 -55
  321. package/dist/es-ops/types.js +11 -11
  322. package/dist/event-store/admin-api.js +7 -8
  323. package/dist/event-store/errors.js +1 -1
  324. package/dist/event-store/event-store.js +12 -6
  325. package/dist/event-store/rebuild-dead-letter.js +1 -1
  326. package/dist/files/bind-pending-file-refs.js +3 -3
  327. package/dist/files/create-file-ref-from-bytes.js +4 -1
  328. package/dist/files/feature.js +9 -10
  329. package/dist/files/file-handle.js +7 -9
  330. package/dist/files/file-ref-entity.js +15 -15
  331. package/dist/files/file-ref-table.js +7 -8
  332. package/dist/files/file-routes.d.ts +7 -0
  333. package/dist/files/file-routes.js +22 -13
  334. package/dist/files/in-memory-provider.js +8 -9
  335. package/dist/files/local-provider.js +18 -25
  336. package/dist/files/provider-resolver.d.ts +7 -7
  337. package/dist/files/resolve-content-type.js +43 -0
  338. package/dist/files/storage-tracking.js +2 -2
  339. package/dist/files/upload-ledger.js +7 -0
  340. package/dist/files/zip-stream.d.ts +12 -19
  341. package/dist/files/zip-stream.js +44 -63
  342. package/dist/i18n/required-surface-keys.d.ts +1 -1
  343. package/dist/i18n/required-surface-keys.js +188 -212
  344. package/dist/internal/stack-support.d.ts +2 -1
  345. package/dist/internal/stack-support.js +1 -1
  346. package/dist/jobs/job-runner-context.d.ts +32 -0
  347. package/dist/jobs/job-runner-context.js +305 -0
  348. package/dist/jobs/job-runner-execution.d.ts +7 -0
  349. package/dist/jobs/job-runner-execution.js +160 -0
  350. package/dist/jobs/job-runner-gates.d.ts +19 -0
  351. package/dist/jobs/job-runner-gates.js +198 -0
  352. package/dist/jobs/job-runner-helpers.d.ts +17 -3
  353. package/dist/jobs/job-runner-helpers.js +69 -4
  354. package/dist/jobs/job-runner-schedulers.d.ts +1 -1
  355. package/dist/jobs/job-runner-schedulers.js +3 -4
  356. package/dist/jobs/job-runner-scope.d.ts +44 -0
  357. package/dist/jobs/job-runner-scope.js +102 -0
  358. package/dist/jobs/job-runner-types.d.ts +12 -0
  359. package/dist/jobs/job-runner.d.ts +0 -1
  360. package/dist/jobs/job-runner.js +57 -669
  361. package/dist/lifecycle/lifecycle.js +3 -5
  362. package/dist/lifecycle/signal-handlers.js +5 -4
  363. package/dist/logging/utils.d.ts +2 -1
  364. package/dist/logging/utils.js +38 -10
  365. package/dist/migrations/index.js +1 -1
  366. package/dist/migrations/kumiko-drift.js +14 -13
  367. package/dist/migrations/pending-rebuilds.d.ts +21 -20
  368. package/dist/migrations/pending-rebuilds.js +27 -27
  369. package/dist/migrations/projection-table-index.d.ts +3 -3
  370. package/dist/migrations/projection-table-index.js +8 -8
  371. package/dist/observability/escape-hatch-report.d.ts +3 -0
  372. package/dist/observability/escape-hatch-report.js +12 -1
  373. package/dist/observability/metric-validator.js +1 -1
  374. package/dist/observability/metrics-handle.js +3 -3
  375. package/dist/pipeline/append-event-core.js +11 -2
  376. package/dist/pipeline/cascade-handler.d.ts +1 -1
  377. package/dist/pipeline/cascade-handler.js +165 -138
  378. package/dist/pipeline/dispatch-batch.js +2 -2
  379. package/dist/pipeline/dispatch-query.js +20 -13
  380. package/dist/pipeline/dispatch-shared.d.ts +14 -3
  381. package/dist/pipeline/dispatch-shared.js +67 -721
  382. package/dist/pipeline/dispatch-stream.js +1 -1
  383. package/dist/pipeline/dispatch-write.js +297 -190
  384. package/dist/pipeline/dispatcher-utils.d.ts +1 -1
  385. package/dist/pipeline/dispatcher-utils.js +1 -1
  386. package/dist/pipeline/event-consumer-state.js +2 -2
  387. package/dist/pipeline/event-dispatcher-admin.js +2 -1
  388. package/dist/pipeline/event-dispatcher-delivery.js +4 -6
  389. package/dist/pipeline/event-dispatcher.js +17 -28
  390. package/dist/pipeline/handler-context-auth.d.ts +9 -0
  391. package/dist/pipeline/handler-context-auth.js +54 -0
  392. package/dist/pipeline/handler-context-config.d.ts +14 -0
  393. package/dist/pipeline/handler-context-config.js +113 -0
  394. package/dist/pipeline/handler-context-db.d.ts +24 -0
  395. package/dist/pipeline/handler-context-db.js +149 -0
  396. package/dist/pipeline/handler-context-dispatch.d.ts +21 -0
  397. package/dist/pipeline/handler-context-dispatch.js +89 -0
  398. package/dist/pipeline/handler-context-events.d.ts +43 -0
  399. package/dist/pipeline/handler-context-events.js +237 -0
  400. package/dist/pipeline/handler-context-notify.d.ts +12 -0
  401. package/dist/pipeline/handler-context-notify.js +55 -0
  402. package/dist/pipeline/handler-context-observability.d.ts +7 -0
  403. package/dist/pipeline/handler-context-observability.js +21 -0
  404. package/dist/pipeline/handler-context-scope.d.ts +48 -0
  405. package/dist/pipeline/handler-context-scope.js +88 -0
  406. package/dist/pipeline/irreversible-operation-gate.js +1 -1
  407. package/dist/pipeline/lifecycle-pipeline.js +9 -9
  408. package/dist/pipeline/member-read-only-transaction.js +1 -3
  409. package/dist/pipeline/msp-rebuild.js +1 -1
  410. package/dist/pipeline/notify-scope.js +12 -1
  411. package/dist/pipeline/pre-handler-gates.js +20 -4
  412. package/dist/pipeline/pre-transaction-reservations.d.ts +1 -0
  413. package/dist/pipeline/pre-transaction-reservations.js +9 -3
  414. package/dist/pipeline/projection-rebuild.js +15 -13
  415. package/dist/pipeline/system-hooks.js +39 -41
  416. package/dist/pipeline/system-identity-switch.js +1 -3
  417. package/dist/pipeline/tenant-timezone-cache.js +1 -1
  418. package/dist/random/generate.d.ts +29 -29
  419. package/dist/random/generate.js +30 -30
  420. package/dist/random/words.js +20 -20
  421. package/dist/rate-limit/middleware.js +9 -5
  422. package/dist/redis/client.js +2 -2
  423. package/dist/redis/pubsub-signal.js +14 -17
  424. package/dist/schema-cli/apply-command.d.ts +2 -0
  425. package/dist/schema-cli/apply-command.js +97 -0
  426. package/dist/schema-cli/baseline-command.d.ts +2 -0
  427. package/dist/schema-cli/baseline-command.js +33 -0
  428. package/dist/schema-cli/generate-command.d.ts +2 -0
  429. package/dist/schema-cli/generate-command.js +90 -0
  430. package/dist/schema-cli/shared.d.ts +39 -0
  431. package/dist/schema-cli/shared.js +95 -0
  432. package/dist/schema-cli/status-command.d.ts +2 -0
  433. package/dist/schema-cli/status-command.js +73 -0
  434. package/dist/schema-cli/validate-boot-input.d.ts +5 -0
  435. package/dist/schema-cli/validate-boot-input.js +130 -0
  436. package/dist/schema-cli/validate-command.d.ts +2 -0
  437. package/dist/schema-cli/validate-command.js +130 -0
  438. package/dist/schema-cli.d.ts +2 -18
  439. package/dist/schema-cli.js +47 -510
  440. package/dist/search/index.js +4 -4
  441. package/dist/search/meilisearch-adapter.js +2 -1
  442. package/dist/search/purge-subject.js +4 -5
  443. package/dist/search/reindex-entity.js +6 -9
  444. package/dist/time/boot-tz-warning.d.ts +2 -2
  445. package/dist/time/boot-tz-warning.js +8 -8
  446. package/dist/time/iana.js +10 -10
  447. package/dist/time/index.js +9 -9
  448. package/dist/time/legacy-date.d.ts +2 -2
  449. package/dist/time/legacy-date.js +6 -8
  450. package/dist/time/polyfill.js +1 -1
  451. package/dist/time/tz-context.d.ts +1 -1
  452. package/dist/time/tz-context.js +1 -1
  453. package/dist/ui-types/app-schema.js +7 -7
  454. package/dist/ui-types/list-row-meta.js +4 -4
  455. package/dist/upgrade-cli.js +159 -130
  456. package/dist/utils/compare.js +1 -1
  457. package/package.json +5 -5
  458. package/src/changes.json +358 -0
  459. package/src/scripts/codemod/crypto-shredding-testing-move.ts +2 -2
  460. package/src/scripts/codemod/feature-layout.ts +10 -2
  461. package/src/scripts/codemod/migrate-cross-tenant.ts +1 -1
  462. package/src/scripts/codemod/migrate-open-to-all.ts +1 -1
  463. package/src/scripts/codemod/object-form.ts +41 -4
  464. package/src/scripts/codemod/package-merge.ts +58 -8
  465. package/src/scripts/codemod/pii-personal-migration.ts +192 -153
  466. package/src/scripts/codemod/testing-injection.ts +122 -42
  467. package/src/scripts/codemod/testing-move.ts +29 -7
  468. package/src/scripts/codemod/unprocessable-error-details-reason.ts +2 -2
@@ -4,7 +4,7 @@ import { tableExists } from "../db/schema-inspection.js";
4
4
  import { unsafePushTables } from "../db/table-push.js";
5
5
  // Reserved sentinel used in the instance_id column for consumers whose
6
6
  // delivery is "shared" — i.e. one cursor across all dispatcher instances
7
- // (the default, pre-Welle-2.7 behaviour). Per-instance consumers store
7
+ // (the default behaviour). Per-instance consumers store
8
8
  // the concrete instanceId. Postgres PK columns can't be NULL and
9
9
  // `NULL = NULL` is UNKNOWN in SQL — a nullable instance_id would break
10
10
  // both uniqueness and PK constraints. The boot-validator refuses to
@@ -62,7 +62,7 @@ export async function createEventConsumerStateTable(db) {
62
62
  // no need to pre-check columnNamesOf first. Two instances booting
63
63
  // concurrently against the same DB (this table is explicitly
64
64
  // multi-instance) both ALTER, neither crashes on "column already
65
- // exists" (#1362).
65
+ // exists".
66
66
  await alterTableAddColumn(db, "kumiko_event_consumers", "rearm_count", "integer", " DEFAULT 0", " NOT NULL",
67
67
  /* ifNotExists */ true);
68
68
  await alterTableAddColumn(db, "kumiko_event_consumers", "pending_gaps", "jsonb", " DEFAULT '[]'::jsonb", " NOT NULL",
@@ -1,6 +1,7 @@
1
1
  import { advanceConsumerPastEventReturning, removePendingGapReturning, selectConsumerForUpdate, selectSnapshotXmax, updateConsumerStatusReturning, } from "../db/queries/event-consumer.js";
2
2
  import { selectNextEventIdAfter, selectSmallestVisibleIdInRanges, } from "../db/queries/event-store.js";
3
- import { coerceRow, extractTableInfo, runInNewTransaction, runInSavepointIfSupported, runnerCanBeginTransaction, selectMany, } from "../db/query.js";
3
+ import { coerceRow, runInNewTransaction, runInSavepointIfSupported, runnerCanBeginTransaction, selectMany, } from "../db/query.js";
4
+ import { extractTableInfo } from "../db/table-info.js";
4
5
  import { getEventsHighWaterMark } from "../event-store/index.js";
5
6
  import { eventConsumerStateTable, SHARED_INSTANCE_SENTINEL, } from "./event-consumer-state.js";
6
7
  import { mergeContiguousGapRanges, rangeContainsId, splitRangeExcludingIds, toIdRanges, } from "./pending-gap-ranges.js";
@@ -3,7 +3,8 @@ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
3
3
  import { requestContext } from "../api/request-context.js";
4
4
  import { insertConsumerIfAbsent, markConsumerProcessing, rearmDeadConsumer, selectConsumerForUpdate, selectConsumerForUpdateSkipLocked, selectProvablyIdleConsumerPairs, updateConsumerDeliveryOutcome, } from "../db/queries/event-consumer.js";
5
5
  import { selectEventsHeadId, selectPendingAndNewEventRows, } from "../db/queries/event-store.js";
6
- import { coerceRow, extractTableInfo } from "../db/query.js";
6
+ import { coerceRow } from "../db/query.js";
7
+ import { extractTableInfo } from "../db/table-info.js";
7
8
  import { qnScope } from "../engine/qualified-name.js";
8
9
  import { eventsTable, toStoredEvent as rowToStoredEvent, } from "../event-store/index.js";
9
10
  import { emitDispatcherError, emitEventConsumerLag, emitEventConsumerPendingGaps, getFallbackMeter, } from "../observability/index.js";
@@ -30,10 +31,6 @@ const UNPARSEABLE_STORED_WRITE_ORIGIN = {
30
31
  export async function acquireConsumerState(tx, name, instanceId, rearmCooldownMs, maxRearmCount) {
31
32
  const rawState = await selectConsumerForUpdateSkipLocked(tx, name, instanceId);
32
33
  if (!rawState) {
33
- return { state: null, skip: "not_registered" };
34
- }
35
- const state = coerceRow(rawState, extractTableInfo(eventConsumerStateTable));
36
- if (!state) {
37
34
  // Either the row never existed (no pre-reg, no ensureRegistered) or
38
35
  // another instance currently holds the lock with SKIP LOCKED filtering
39
36
  // us out. We can't distinguish here in a single query, so return
@@ -42,6 +39,7 @@ export async function acquireConsumerState(tx, name, instanceId, rearmCooldownMs
42
39
  // this path is never taken.
43
40
  return { state: null, skip: "not_registered" };
44
41
  }
42
+ const state = coerceRow(rawState, extractTableInfo(eventConsumerStateTable));
45
43
  if (state.status === ConsumerStatuses.disabled)
46
44
  return { state: null, skip: "disabled" };
47
45
  if (state.status === ConsumerStatuses.dead) {
@@ -239,7 +237,7 @@ export async function deliverEvents(consumer, events, context, maxAttempts, stat
239
237
  // turn (see applyBatch for why). requestId falls back to a fresh id
240
238
  // because the dispatcher runs outside any HTTP request (background poll),
241
239
  // and a stable log-correlation handle is still useful for debugging.
242
- // #3043 — an event this apply writes is attributed to the consumer, not
240
+ // An event this apply writes is attributed to the consumer, not
243
241
  // to whatever wrote the triggering event; causationId already links back.
244
242
  function buildConsumerRequestScope(consumer, causationSource) {
245
243
  const correlationId = causationSource.metadata.correlationId ?? requestContext.generateId();
@@ -2,6 +2,7 @@ import { extractPgError } from "../db/pg-error.js";
2
2
  import { recordConsumerPassFailure, selectSnapshotXmax, selectSnapshotXmin, } from "../db/queries/event-consumer.js";
3
3
  import { SYSTEM_TENANT_ID } from "../engine/types/identifiers.js";
4
4
  import { EVENTS_PUBSUB_CHANNEL } from "../event-store/index.js";
5
+ import { createFallbackLogger } from "../logging/utils.js";
5
6
  import { emitEventConsumerPassOutcome, emitEventConsumerPassSkipped, emitEventConsumerRearmExhausted, emitEventDispatcherListenConnected, getFallbackMeter, getFallbackTracer, } from "../observability/index.js";
6
7
  import { SHARED_INSTANCE_SENTINEL } from "./event-consumer-state.js";
7
8
  import { acquireConsumerState, consumerInstanceId, deliverEvents, emitLagFromTx, fetchPendingEvents, markProcessing, persistConsumerOutcome, preRegisterConsumers, selectIdleConsumerKeys, } from "./event-dispatcher-delivery.js";
@@ -52,15 +53,9 @@ export function createEventDispatcher(options) {
52
53
  }
53
54
  const tracer = options.tracer ?? getFallbackTracer();
54
55
  const meter = options.meter ?? getFallbackMeter();
55
- // A standalone dispatcher has no logger on its context; console.error keeps the failure in pod logs.
56
- const logDispatcherError = (message) => {
57
- if (context.log) {
58
- context.log.error(message);
59
- }
60
- else {
61
- console.error(message);
62
- }
63
- };
56
+ // A standalone dispatcher has no logger on its context; the fallback keeps the failure in pod logs.
57
+ const dispatcherLog = createFallbackLogger("event-dispatcher", context.log);
58
+ const logDispatcherError = (message) => dispatcherLog.error(message);
64
59
  // Tracks which (consumer, instanceId) pairs already fired
65
60
  // kumiko_event_consumer_rearm_exhausted_total, so a consumer stuck dead
66
61
  // across many poll passes emits the ops-signal once, not every pass
@@ -79,7 +74,7 @@ export function createEventDispatcher(options) {
79
74
  if (count <= PENDING_GAPS_WARN_THRESHOLD || reportedLargeGapSets.has(key))
80
75
  return;
81
76
  reportedLargeGapSets.add(key);
82
- logDispatcherError(`[event-dispatcher] ${consumerName} tracks ${count} pending gap ranges (> ${PENDING_GAPS_WARN_THRESHOLD}); ` +
77
+ logDispatcherError(`${consumerName} tracks ${count} pending gap ranges (> ${PENDING_GAPS_WARN_THRESHOLD}); ` +
83
78
  `a long-running write transaction is probably holding back the snapshot xmin`);
84
79
  };
85
80
  // Per-(consumer, instanceId) backoff after a thrown pass (see the catch in
@@ -245,13 +240,13 @@ export function createEventDispatcher(options) {
245
240
  // consumers see the same disabled-set even if an operator flips a
246
241
  // toggle mid-pass, so "this event batch" decisions stay consistent.
247
242
  //
248
- // Sprint-8a tier-composition: per-tenant resolver per-pass-konsultiert
249
- // mit SYSTEM_TENANT_ID. Async-events sind tier-agnostic — wenn ein
250
- // Tenant downgrade'd, sollen seine queued events trotzdem verarbeitet
251
- // werden (events sind immutable, projection ist eventually-consistent).
252
- // Tier-cuts wirken request-time im sync-dispatcher + lifecycle-pipeline,
253
- // nicht im async-replay. App-level resolver entscheidet was er bei
254
- // SYSTEM_TENANT_ID returnt (typisch: union-of-all-tier-features).
243
+ // Tier composition: the per-tenant resolver is consulted once per pass
244
+ // with SYSTEM_TENANT_ID. Async events are tier-agnostic — if a tenant is
245
+ // downgraded, its queued events are still processed (events are immutable,
246
+ // and the projection is eventually consistent).
247
+ // Tier cuts apply at request time in the sync dispatcher and lifecycle
248
+ // pipeline, not during async replay. The app-level resolver decides what
249
+ // it returns for SYSTEM_TENANT_ID (typically: the union of all tier features).
255
250
  const effective = context.effectiveFeatures?.(SYSTEM_TENANT_ID);
256
251
  // Lock-free pre-check, once per pass: which consumers are provably idle
257
252
  // (no locking, no xid, no WAL — see selectIdleConsumerKeys). Read
@@ -275,13 +270,7 @@ export function createEventDispatcher(options) {
275
270
  if (idlePreCheckErrorLogged) {
276
271
  idlePreCheckErrorLogged = false;
277
272
  emitEventDispatcherListenConnected(meter, listenSubscribed);
278
- const recoveredMsg = "[event-dispatcher] idle pre-check recovered, database reachable again";
279
- if (context.log) {
280
- context.log.info(recoveredMsg);
281
- }
282
- else {
283
- console.log(recoveredMsg);
284
- }
273
+ dispatcherLog.info("idle pre-check recovered, database reachable again");
285
274
  }
286
275
  }
287
276
  catch (e) {
@@ -297,7 +286,7 @@ export function createEventDispatcher(options) {
297
286
  if (isConnectionLossError(e))
298
287
  emitEventDispatcherListenConnected(meter, false);
299
288
  const msg = e instanceof Error ? e.message : String(e);
300
- const logMsg = `[event-dispatcher] idle pre-check failed, falling back to per-consumer locking every tick until it recovers: ${msg}`;
289
+ const logMsg = `idle pre-check failed, falling back to per-consumer locking every tick until it recovers: ${msg}`;
301
290
  logDispatcherError(logMsg);
302
291
  }
303
292
  }
@@ -438,7 +427,7 @@ export function createEventDispatcher(options) {
438
427
  // callers and at-least-once-with-duplicate-delivery on the next pass;
439
428
  // neither is what we want, so ops needs to see it.
440
429
  const msg = e instanceof Error ? e.message : String(e);
441
- logDispatcherError(`[event-dispatcher] ${consumer.name} pass failed: ${msg}`);
430
+ logDispatcherError(`${consumer.name} pass failed: ${msg}`);
442
431
  span.setStatus("error", msg);
443
432
  // The whole pass rolled back, so markProcessing/persistConsumerOutcome
444
433
  // never committed — attempts/last_error are stuck at their pre-pass
@@ -452,7 +441,7 @@ export function createEventDispatcher(options) {
452
441
  }
453
442
  catch (persistErr) {
454
443
  const persistMsg = persistErr instanceof Error ? persistErr.message : String(persistErr);
455
- const failureMsg = `[event-dispatcher] ${consumer.name} failed to persist pass-failure state: ${persistMsg}`;
444
+ const failureMsg = `${consumer.name} failed to persist pass-failure state: ${persistMsg}`;
456
445
  logDispatcherError(failureMsg);
457
446
  }
458
447
  // Escalate the backoff so this consumer isn't retried on the very
@@ -519,7 +508,7 @@ export function createEventDispatcher(options) {
519
508
  catch (e) {
520
509
  emitEventDispatcherListenConnected(meter, false);
521
510
  const msg = e instanceof Error ? e.message : String(e);
522
- context.log?.error(`[event-dispatcher] pg LISTEN failed: ${msg}`);
511
+ dispatcherLog.error("pg LISTEN failed", { err: msg });
523
512
  }
524
513
  }
525
514
  },
@@ -0,0 +1,9 @@
1
+ import type { TenantId } from "../engine/types/identifiers.ts";
2
+ import type { MemberReader, SessionUser } from "../engine/types/index.ts";
3
+ import type { HandlerContextScope } from "./handler-context-scope.ts";
4
+ export declare function buildAuthFacet(scope: HandlerContextScope): {
5
+ resolveAuthClaims: (claimsUser: SessionUser) => Promise<Record<string, unknown>>;
6
+ resolveActiveMembership: (userId: string, tenantId: TenantId) => Promise<import("@cosmicdrift/kumiko-types/handlers").ActiveMembershipResult>;
7
+ queryAsMember: MemberReader;
8
+ hasFeature: (featureName: string) => Promise<boolean>;
9
+ };
@@ -0,0 +1,54 @@
1
+ import { INTERACTIVE_SIGN_IN_POLICY, resolveActiveMembershipFn } from "./active-membership.js";
2
+ import { resolveAuthClaimsFn } from "./dispatch-shared.js";
3
+ import { createMemberReaderFn } from "./member-reader.js";
4
+ import { createGatedMemberReader, systemIdentitySwitchDenied } from "./system-identity-switch.js";
5
+ export function buildAuthFacet(scope) {
6
+ const { ctx, type, user, tx, hasIdentitySwitchGrant, identitySwitchAudit } = scope;
7
+ const { effectiveFeatures } = ctx;
8
+ // Lazy — creates the reader (which fails closed on SYSTEM_TENANT_ID) only
9
+ // on first actual use, not on every HandlerContext build.
10
+ let ungatedMemberReader;
11
+ const queryAsMember = createGatedMemberReader(`handler "${type}"`, hasIdentitySwitchGrant, (userId, qn, payload) => {
12
+ ungatedMemberReader ??= createMemberReaderFn(ctx, user.tenantId, tx);
13
+ return ungatedMemberReader(userId, qn, payload);
14
+ }, { ...identitySwitchAudit, tenantId: user.tenantId });
15
+ return {
16
+ // Thin pass-through: one resolve impl lives on the dispatcher, the
17
+ // handler surface just forwards the call so both entry points (login
18
+ // handler via ctx.resolveAuthClaims, switch-tenant route via
19
+ // dispatcher.resolveAuthClaims) cannot drift.
20
+ resolveAuthClaims: (claimsUser) => resolveAuthClaimsFn(ctx, claimsUser), // @wrapper-known semantic-alias
21
+ // Thin pass-through, same reasoning as resolveAuthClaims above — one
22
+ // resolve impl lives on the dispatcher so callers can't drift apart.
23
+ // Internally queries memberships as SYSTEM, so it needs the same grant
24
+ // as a SYSTEM queryAs/writeAs (r.systemScope() or escapeHatch).
25
+ resolveActiveMembership: (userId, tenantId) => {
26
+ if (!hasIdentitySwitchGrant)
27
+ throw systemIdentitySwitchDenied(`handler "${type}"`);
28
+ return resolveActiveMembershipFn(ctx, userId, tenantId, INTERACTIVE_SIGN_IN_POLICY); // @wrapper-known semantic-alias
29
+ },
30
+ // Needs the same grant as a SYSTEM queryAs.
31
+ queryAsMember,
32
+ // Feature-effective check for in-handler opt-in logic. Scope:
33
+ // **current user's tenant** — for cross-tenant lookups (rare,
34
+ // SysAdmin operations) read effectiveFeatures(otherTenantId) directly.
35
+ // When the feature-toggles or tier-engine feature isn't wired (no
36
+ // effectiveFeatures callback), always returns true — apps without
37
+ // tier-cuts treat all features on.
38
+ //
39
+ // Falls back to the live trialGate when the sync set says the feature
40
+ // is off — the sync set never contains trial-tier features (time-
41
+ // derived, can't boot-cache), so without this a trial tenant checking
42
+ // a companion feature's toggle would silently read `false` even though
43
+ // the dispatch gate already lets trial-tier handlers run.
44
+ hasFeature: async (featureName) => {
45
+ if (!effectiveFeatures)
46
+ return true;
47
+ if (effectiveFeatures(user.tenantId).has(featureName))
48
+ return true;
49
+ if (!effectiveFeatures.trialGate)
50
+ return false;
51
+ return effectiveFeatures.trialGate(user.tenantId, featureName);
52
+ },
53
+ };
54
+ }
@@ -0,0 +1,14 @@
1
+ import { type TenantDb } from "../db/tenant-db.ts";
2
+ import type { TenantId } from "../engine/types/identifiers.ts";
3
+ import type { ConfigAccessor } from "../engine/types/index.ts";
4
+ import { createTzContext } from "../time/index.ts";
5
+ import type { HandlerContextScope } from "./handler-context-scope.ts";
6
+ export declare function buildConfigFacet(scope: HandlerContextScope, db: TenantDb | undefined): {
7
+ config: ConfigAccessor | undefined;
8
+ fragment: {
9
+ config?: ConfigAccessor | undefined;
10
+ configFor?: ((targetTenantId: TenantId) => ConfigAccessor) | undefined;
11
+ };
12
+ };
13
+ export declare function resolveHandlerTz(scope: HandlerContextScope, config: ConfigAccessor | undefined): Promise<ReturnType<typeof createTzContext>>;
14
+ export declare function resolveHandlerLocale(scope: HandlerContextScope): string;
@@ -0,0 +1,113 @@
1
+ import { createTenantDb } from "../db/tenant-db.js";
2
+ import { createSystemUser } from "../engine/system-user.js";
3
+ import { AccessDeniedError } from "../errors/index.js";
4
+ import { DEFAULT_LOCALE, isValidLocaleTag } from "../i18n/request-locale.js";
5
+ import { createTzContext, isValidIanaTimeZone } from "../time/index.js";
6
+ import { configAccessorFactoryOf } from "./config-accessor-factory.js";
7
+ import { isSystemIdentity } from "./system-identity-switch.js";
8
+ import { TENANT_TIMEZONE_CONFIG_KEY } from "./tenant-timezone-config-key.js";
9
+ // Cross-tenant accessor for SystemAdmin/system callers. The
10
+ // identity is the target tenant's system user so the caller's own user-scope
11
+ // values never resolve into another tenant's config; the db is tenant-mode
12
+ // scoped to the target.
13
+ function buildConfigFor(scope, configFactory, config) {
14
+ const { user, context, registry, dbSource } = scope;
15
+ return configFactory && dbSource
16
+ ? (targetTenantId) => {
17
+ if (targetTenantId === user.tenantId && config)
18
+ return config;
19
+ if (!isSystemIdentity(user) && !user.roles.includes("SystemAdmin")) {
20
+ throw new AccessDeniedError({
21
+ message: "ctx.configFor(otherTenant) requires the system role or SystemAdmin",
22
+ details: { reason: "config_for_requires_system_admin" },
23
+ });
24
+ }
25
+ const targetSystemUser = createSystemUser(targetTenantId);
26
+ return configFactory({
27
+ user: { id: targetSystemUser.id, tenantId: targetTenantId },
28
+ db: createTenantDb({
29
+ db: dbSource,
30
+ tenantId: targetTenantId,
31
+ mode: "tenant",
32
+ tracer: context.tracer,
33
+ meter: context.meter,
34
+ grants: { projectionRegistry: registry },
35
+ }),
36
+ secrets: context.secrets,
37
+ });
38
+ }
39
+ : undefined;
40
+ }
41
+ export function buildConfigFacet(scope, db) {
42
+ const { user, context } = scope;
43
+ // Mirror notify: only built when the config feature wired its factory.
44
+ const configFactory = configAccessorFactoryOf(context);
45
+ const config = configFactory && db
46
+ ? configFactory({
47
+ user: { id: user.id, tenantId: user.tenantId },
48
+ db,
49
+ secrets: context.secrets,
50
+ })
51
+ : undefined;
52
+ const configFor = buildConfigFor(scope, configFactory, config);
53
+ return { config, fragment: { ...(config && { config }), ...(configFor && { configFor }) } };
54
+ }
55
+ async function readTenantTimezone(scope, config) {
56
+ const { ctx, user, tx } = scope;
57
+ if (config === undefined)
58
+ return undefined;
59
+ const cachedTz = ctx.tenantTimezoneCache.get(user.tenantId);
60
+ if (cachedTz)
61
+ return cachedTz.value;
62
+ const generationAtRead = ctx.tenantTimezoneCache.generation();
63
+ const tenantTz = await config(TENANT_TIMEZONE_CONFIG_KEY);
64
+ if (tx === undefined) {
65
+ ctx.tenantTimezoneCache.setIfFresh(user.tenantId, typeof tenantTz === "string" ? tenantTz : undefined, generationAtRead);
66
+ }
67
+ return tenantTz;
68
+ }
69
+ // ctx.tz is always present. tenant reads tenant:config:timezone through
70
+ // the already-built `config` accessor (raw qualified-key — no import of
71
+ // the tenant feature, framework/pipeline stays bundled-features-free);
72
+ // falls back to "UTC" when config is missing or the key is unset. user
73
+ // comes from SessionUser.timezone (set at login), else falls back to
74
+ // tenant (createTzContext's own default). An app-injected GeoTzProvider
75
+ // (context.geoTzProvider) feeds ctx.tz.fromCoordinates / fromAddress.
76
+ //
77
+ // Resolving this via `config()` is a config-table SELECT on every single
78
+ // dispatch — ctx.tenantTimezoneCache memoizes it per tenant.
79
+ // dispatch-write.ts invalidates on config:write:set/reset for this key.
80
+ // Only populate from a tx-free read (tx === undefined): a write-path
81
+ // lookup runs inside the caller's own open transaction, and caching a
82
+ // value read there could memoize state from a write earlier in that same
83
+ // transaction before it's known to commit. Reads always consult the
84
+ // cache regardless of tx, since serving a slightly stale cached value on
85
+ // the write path is harmless (bounded by ttlMs) and avoids reintroducing
86
+ // the SELECT on the hot path this fix targets.
87
+ export async function resolveHandlerTz(scope, config) {
88
+ const { user, context } = scope;
89
+ const tenantTz = await readTenantTimezone(scope, config);
90
+ // Guarded against garbage: an unvalidated string here (free-form config
91
+ // key, legacy JWT claim predating validation) blows up every ctx.tz call
92
+ // for the whole tenant with a RangeError. Fall back to UTC/tenant instead
93
+ // of trusting the raw value.
94
+ const safeTenantTz = typeof tenantTz === "string" && isValidIanaTimeZone(tenantTz) ? tenantTz : "UTC";
95
+ const safeUserTz = user.timezone !== undefined && isValidIanaTimeZone(user.timezone) ? user.timezone : undefined;
96
+ return createTzContext({
97
+ ...(context.geoTzProvider !== undefined ? { geoTz: context.geoTzProvider } : {}),
98
+ ...(context.clock !== undefined ? { clock: context.clock } : {}),
99
+ tenant: safeTenantTz,
100
+ ...(safeUserTz !== undefined && { user: safeUserTz }),
101
+ });
102
+ }
103
+ // ctx.locale — request-layer signal (X-Locale header → Accept-Language,
104
+ // resolved once at the HTTP boundary by request-id-middleware.ts) wins;
105
+ // then SessionUser.locale (set at login — stale until re-login, same
106
+ // tradeoff as ctx.tz.user); falls back to the app's
107
+ // boot-configured defaultLocale, then DEFAULT_LOCALE. Mirrors ctx.tz's
108
+ // Request → Session → Boot-Default chain.
109
+ export function resolveHandlerLocale(scope) {
110
+ const { user, reqCtx, context } = scope;
111
+ const safeUserLocale = user.locale !== undefined && isValidLocaleTag(user.locale) ? user.locale : undefined;
112
+ return reqCtx?.locale ?? safeUserLocale ?? context.defaultLocale ?? DEFAULT_LOCALE;
113
+ }
@@ -0,0 +1,24 @@
1
+ import type { DbConnection, DbTx } from "../db/connection.ts";
2
+ import { type TenantDb } from "../db/tenant-db.ts";
3
+ import type { TenantId } from "../engine/types/identifiers.ts";
4
+ import type { TenantWriteTarget } from "../engine/types/index.ts";
5
+ import type { HandlerContextScope } from "./handler-context-scope.ts";
6
+ export declare function buildTenantScopedDb(scope: HandlerContextScope, source: DbConnection | DbTx, signal: AbortSignal | undefined, foreignTenantId?: TenantId, denyWrites?: boolean): TenantDb;
7
+ export declare function buildHandlerDbFacet(scope: HandlerContextScope): {
8
+ db: TenantDb | undefined;
9
+ exposedDb: TenantDb | undefined;
10
+ outsideTxSource: DbConnection | undefined;
11
+ fragment: {
12
+ db: TenantDb | undefined;
13
+ dbOutsideTransaction: TenantDb | undefined;
14
+ systemDb?: import("@cosmicdrift/kumiko-types/tenant-db-types").UncheckedSystemDb | undefined;
15
+ crossTenantReads?: import("@cosmicdrift/kumiko-types/tenant-db-types").CrossTenantReads | undefined;
16
+ };
17
+ };
18
+ export declare function buildForTenantFragment(scope: HandlerContextScope, exposedDb: TenantDb | undefined, readOnlyForTenant: boolean): {
19
+ forTenant?: ((targetTenantId: TenantId) => TenantWriteTarget) | undefined;
20
+ };
21
+ export declare function buildFileFragment(scope: HandlerContextScope, db: TenantDb | undefined): {
22
+ files?: import("@cosmicdrift/kumiko-types/file-handle-types").FileContext | undefined;
23
+ derivatives?: import("@cosmicdrift/kumiko-types/derivatives-types").DerivativesContext | undefined;
24
+ };
@@ -0,0 +1,149 @@
1
+ import { crossTenantReadsGrantFor, escapeHatchFor } from "@cosmicdrift/kumiko-types/handlers";
2
+ import { isAgentToolCallScope } from "../api/request-context.js";
3
+ import { createHandlerCrossTenantReads } from "../db/cross-tenant-reads.js";
4
+ import { createTenantDb, createUncheckedSystemDb } from "../db/tenant-db.js";
5
+ import { createDerivativesContext } from "../derivatives/derivatives-context.js";
6
+ import { mayOverrideTenant } from "../engine/cross-tenant.js";
7
+ import { AccessDeniedError, FrameworkReasons } from "../errors/index.js";
8
+ import { createTenantFileContext } from "../files/file-handle.js";
9
+ import { createSystemScopedDbGuard, isMemberResolutionPrincipal, resolveAppPoolDb, } from "./dispatch-shared.js";
10
+ import { buildPersonalDataGate } from "./write-origin.js";
11
+ export function buildTenantScopedDb(scope, source, signal, foreignTenantId, denyWrites = false) {
12
+ const { user, context, registry, origin, isSystem, writeEscapeHatch, handlerEscapeHatch } = scope;
13
+ return createTenantDb({
14
+ db: source,
15
+ tenantId: foreignTenantId ?? user.tenantId,
16
+ mode: isSystem && foreignTenantId === undefined ? "system" : "tenant",
17
+ tracer: context.tracer,
18
+ meter: context.meter,
19
+ signal,
20
+ grants: {
21
+ ...(foreignTenantId === undefined && {
22
+ globalWrites: escapeHatchFor(writeEscapeHatch, "globalWrites"),
23
+ unsafeRaw: escapeHatchFor(handlerEscapeHatch, "unsafeRaw"),
24
+ }),
25
+ report: scope.reportEscapeHatch,
26
+ memberReadOnly: isMemberResolutionPrincipal(user),
27
+ personalDataGate: buildPersonalDataGate(registry, origin),
28
+ projectionRegistry: registry,
29
+ ...(denyWrites && { writesDeniedReason: "without an open transaction" }),
30
+ },
31
+ });
32
+ }
33
+ function buildHandlerCrossTenantReads(scope) {
34
+ const { user, context, registry, origin, dbSource, reqCtx, handlerEscapeHatch } = scope;
35
+ // An agent tool call runs with the user's session but feeds the LLM context, so
36
+ // cross-tenant rows must never reach it, even when unsafeRaw implies the grant.
37
+ const crossTenantReadsReason = isAgentToolCallScope()
38
+ ? undefined
39
+ : crossTenantReadsGrantFor(handlerEscapeHatch)?.reason;
40
+ return dbSource && crossTenantReadsReason !== undefined
41
+ ? createHandlerCrossTenantReads({
42
+ db: dbSource,
43
+ tenantId: user.tenantId,
44
+ tracer: context.tracer,
45
+ meter: context.meter,
46
+ signal: reqCtx?.signal,
47
+ reason: crossTenantReadsReason,
48
+ report: scope.reportEscapeHatch,
49
+ grants: {
50
+ report: scope.reportEscapeHatch,
51
+ memberReadOnly: isMemberResolutionPrincipal(user),
52
+ personalDataGate: buildPersonalDataGate(registry, origin),
53
+ projectionRegistry: registry,
54
+ },
55
+ })
56
+ : undefined;
57
+ }
58
+ export function buildHandlerDbFacet(scope) {
59
+ const { ctx, type, isSystem, dbSource, reqCtx, denyDbWrites } = scope;
60
+ // Propagate the request's AbortSignal so every TenantDb query throws when
61
+ // the client has disconnected — handlers with many sequential queries skip
62
+ // the rest of the chain instead of burning DB-CPU for results no one reads.
63
+ const db = dbSource
64
+ ? buildTenantScopedDb(scope, dbSource, reqCtx?.signal, undefined, denyDbWrites)
65
+ : undefined;
66
+ // Unbound pool, tenant-scoped like `db` but never tx-bound — writes
67
+ // through it survive a rollback of the handler's own transaction. No
68
+ // AbortSignal here: a client disconnect must not abort a durability write
69
+ // that is meant to outlive the request. Computed before `systemDb` below
70
+ // so its guarded escape hatch (ctx.systemDb.outsideTransaction) can wrap
71
+ // this same TenantDb instead of a second, independently-built one.
72
+ const outsideTxSource = ctx.outsideTransactionDb ?? resolveAppPoolDb(ctx);
73
+ const rawDbOutsideTransaction = outsideTxSource
74
+ ? buildTenantScopedDb(scope, outsideTxSource, undefined)
75
+ : undefined;
76
+ const systemDb = isSystem && db
77
+ ? createUncheckedSystemDb(db, rawDbOutsideTransaction, scope.reportEscapeHatch)
78
+ : undefined;
79
+ const crossTenantReads = buildHandlerCrossTenantReads(scope);
80
+ // Exposed as ctx.db below — the internal `db` above stays the real,
81
+ // working TenantDb for config/derivatives accessors and systemDb
82
+ // construction.
83
+ const exposedDb = isSystem && db
84
+ ? createSystemScopedDbGuard(type, "db", "ctx.systemDb.assertTenantMatch(...) or ctx.systemDb.acknowledgeCrossTenant(...)")
85
+ : db;
86
+ // Same fail-closed treatment as `exposedDb` — a system handler reaching
87
+ // ctx.dbOutsideTransaction directly would be a second, unguarded door to
88
+ // the same cross-tenant access ctx.db closes off above.
89
+ const dbOutsideTransaction = isSystem && rawDbOutsideTransaction
90
+ ? createSystemScopedDbGuard(type, "dbOutsideTransaction", "ctx.systemDb.outsideTransaction.assertTenantMatch(...) or " +
91
+ "ctx.systemDb.outsideTransaction.acknowledgeCrossTenant(...)")
92
+ : rawDbOutsideTransaction;
93
+ return {
94
+ db,
95
+ exposedDb,
96
+ outsideTxSource,
97
+ fragment: {
98
+ db: exposedDb,
99
+ dbOutsideTransaction,
100
+ ...(systemDb && { systemDb }),
101
+ ...(crossTenantReads && { crossTenantReads }),
102
+ },
103
+ };
104
+ }
105
+ // Write handlers only, and not r.systemScope() ones (they have ctx.systemDb). The gate is the
106
+ // dispatcher's tenantIdOverride predicate, so no caller reaches more than it could via
107
+ // tenantIdOverride. The foreign db carries no unsafeRaw/globalWrites grant and is tenant mode
108
+ // bound to the target; the executor accepts streamTenantId only on a db bound to that tenant.
109
+ export function buildForTenantFragment(scope, exposedDb, readOnlyForTenant) {
110
+ const { user, isSystem, dbSource, isWriteHandler, reqCtx, denyDbWrites, isPerCommand } = scope;
111
+ const forTenant = !isSystem && dbSource && isWriteHandler
112
+ ? (targetTenantId) => {
113
+ if (targetTenantId === user.tenantId && exposedDb) {
114
+ // Only perCommand handlers lose forTenant writes; reservations keep their pre-transaction contract.
115
+ const ownTenantDb = denyDbWrites && !isPerCommand
116
+ ? buildTenantScopedDb(scope, dbSource, reqCtx?.signal, undefined, readOnlyForTenant)
117
+ : exposedDb;
118
+ return { db: ownTenantDb, streamTenantId: undefined };
119
+ }
120
+ if (!mayOverrideTenant(user)) {
121
+ throw new AccessDeniedError({
122
+ details: { reason: FrameworkReasons.tenantOverrideRequiresSystemAdmin },
123
+ });
124
+ }
125
+ return {
126
+ db: buildTenantScopedDb(scope, dbSource, reqCtx?.signal, targetTenantId, denyDbWrites && (isPerCommand || readOnlyForTenant)),
127
+ streamTenantId: targetTenantId,
128
+ };
129
+ }
130
+ : undefined;
131
+ return { ...(forTenant && { forTenant }) };
132
+ }
133
+ // ctx.files resolved per-tenant through file-foundation (lazy — the
134
+ // provider is only resolved when a handle actually does I/O). Boot wires
135
+ // _fileProviderResolver when a file-provider plugin is mounted; falls back
136
+ // to a statically-injected context.files (tests).
137
+ export function buildFileFragment(scope, db) {
138
+ const { context, registry, user } = scope;
139
+ const fileResolver = context._fileProviderResolver;
140
+ const files = fileResolver ? createTenantFileContext(fileResolver, user.tenantId) : context.files;
141
+ // ctx.derivatives builds on ctx.files (needs a FileContext to read the
142
+ // original + write the variant) plus db (to look up the FileRef row) — so
143
+ // it can only be constructed exactly when files+db both resolved; falls
144
+ // back to a statically-injected context.derivatives otherwise (tests).
145
+ const derivatives = files && db
146
+ ? createDerivativesContext({ files, registry, db, tenantId: user.tenantId })
147
+ : context.derivatives;
148
+ return { ...(files && { files }), ...(derivatives && { derivatives }) };
149
+ }
@@ -0,0 +1,21 @@
1
+ import type { WriteResult } from "../engine/types/index.ts";
2
+ import { type HandlerTransactionScope } from "./dispatch-shared.ts";
3
+ import { type AfterCommitHook } from "./dispatcher-utils.ts";
4
+ import type { HandlerContextScope } from "./handler-context-scope.ts";
5
+ export type CommitSinks = {
6
+ readonly bridgeSink: AfterCommitHook[];
7
+ readonly hasLiveCommitSink: boolean;
8
+ readonly notifyCommitSink: AfterCommitHook[] | undefined;
9
+ };
10
+ export declare function createCommitSinks(afterCommitHooks: AfterCommitHook[] | undefined, transactionScope: HandlerTransactionScope, outerNotifySink: AfterCommitHook[] | undefined): CommitSinks;
11
+ export declare function buildDispatchFacet(scope: HandlerContextScope, sinks: CommitSinks): {
12
+ bridge: {
13
+ query: (targetType: string, payload: unknown) => Promise<unknown>;
14
+ queryAs: import("./system-identity-switch.ts").QueryAsFn;
15
+ write: (targetType: string, payload: unknown) => Promise<WriteResult>;
16
+ writeAs: import("./system-identity-switch.ts").WriteAsFn;
17
+ };
18
+ scheduleFragment: {
19
+ scheduleAfterCommit?: ((hook: AfterCommitHook) => void) | undefined;
20
+ };
21
+ };