@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
@@ -0,0 +1,89 @@
1
+ import { runInSavepoint } from "../db/query.js";
2
+ import { writeFailure } from "../errors/index.js";
3
+ import { runBatch, unwrapSingle } from "./dispatch-batch.js";
4
+ import { executeQuery } from "./dispatch-query.js";
5
+ import { perCommandNestedDenied } from "./dispatch-shared.js";
6
+ import { executeWrite } from "./dispatch-write.js";
7
+ import { isFailedWriteResult, NestedWriteRollback, } from "./dispatcher-utils.js";
8
+ import { createGatedIdentitySwitch } from "./system-identity-switch.js";
9
+ export function createCommitSinks(afterCommitHooks, transactionScope, outerNotifySink) {
10
+ // Cross-feature bridge: ctx.query/write share the current tx + afterCommitHooks sink.
11
+ // queryAs/writeAs to anyone but the caller itself needs r.systemScope() or { escapeHatch } (system-identity-switch.ts).
12
+ const bridgeSink = afterCommitHooks ?? [];
13
+ // Query bridges and post-commit contexts get a throwaway sink: hooks pushed there never run.
14
+ const hasLiveCommitSink = afterCommitHooks !== undefined && transactionScope !== "postCommit";
15
+ return {
16
+ bridgeSink,
17
+ hasLiveCommitSink,
18
+ notifyCommitSink: hasLiveCommitSink ? bridgeSink : outerNotifySink,
19
+ };
20
+ }
21
+ // A failed inner write has already appended its event and projections when a later
22
+ // step (e.g. an inTransaction hook) fails, and the outer handler may swallow the
23
+ // failure result. The savepoint makes the failure undo exactly the inner write; the
24
+ // inner afterCommit hooks only reach the outer sink if it succeeded.
25
+ async function runNestedWriteInSavepoint(scope, sinks, asUser, targetType, payload) {
26
+ const { ctx, registry, origin, tx } = scope;
27
+ const { bridgeSink } = sinks;
28
+ if (registry.getWriteHandler(targetType)?.transaction === "perCommand") {
29
+ return writeFailure(perCommandNestedDenied(targetType));
30
+ }
31
+ if (!tx)
32
+ return executeWrite(ctx, targetType, payload, asUser, origin, tx, bridgeSink);
33
+ const innerAfterCommitHooks = [];
34
+ try {
35
+ const result = await runInSavepoint(tx, async (savepointTx) => {
36
+ const innerResult = await executeWrite(ctx, targetType, payload, asUser, origin, savepointTx, // @cast-boundary db-runner savepoint handle is a transaction handle
37
+ innerAfterCommitHooks);
38
+ if (isFailedWriteResult(innerResult))
39
+ throw new NestedWriteRollback(innerResult);
40
+ return innerResult;
41
+ });
42
+ bridgeSink.push(...innerAfterCommitHooks);
43
+ return result;
44
+ }
45
+ catch (e) {
46
+ if (e instanceof NestedWriteRollback)
47
+ return e.failure;
48
+ throw e;
49
+ }
50
+ }
51
+ // Savepoints on one connection are not serialized by the driver: two parallel nested writes would
52
+ // interleave their statements and a ROLLBACK TO of the first would also discard the second's rows.
53
+ // Writes nested one level deeper run on their own savepoint handle and therefore their own chain.
54
+ function createSerializedNestedWriter(scope, sinks) {
55
+ let nestedWriteChain = Promise.resolve();
56
+ return (asUser, targetType, payload) => {
57
+ const run = nestedWriteChain.then(() => runNestedWriteInSavepoint(scope, sinks, asUser, targetType, payload));
58
+ nestedWriteChain = run.catch(() => undefined);
59
+ return run;
60
+ };
61
+ }
62
+ export function buildDispatchFacet(scope, sinks) {
63
+ const { ctx, type, user, origin, tx, transactionScope, hasIdentitySwitchGrant, identitySwitchAudit, } = scope;
64
+ const scheduleAfterCommit = (hook) => {
65
+ sinks.bridgeSink.push(hook);
66
+ };
67
+ const executeNestedWriteInSavepoint = createSerializedNestedWriter(scope, sinks);
68
+ // Without an open transaction (post-commit hooks, stream/query handlers, perCommand handlers) a bare
69
+ // executeWrite would auto-commit event append, projections and inTransaction hooks separately and its
70
+ // afterCommit hooks would land in a sink nobody flushes. A batch-of-one gives the write its own
71
+ // transaction and its own flush.
72
+ const writeAsUser = async (asUser, targetType, payload) => transactionScope === "inTransaction"
73
+ ? executeNestedWriteInSavepoint(asUser, targetType, payload)
74
+ : unwrapSingle(await runBatch(ctx, [{ type: targetType, payload }], asUser, undefined, origin));
75
+ const identitySwitch = createGatedIdentitySwitch(`handler "${type}"`, user, hasIdentitySwitchGrant, {
76
+ // Inherits the caller's origin, so switching to SYSTEM cannot shed an anonymous root.
77
+ queryAs: (asUser, targetType, payload) => executeQuery(ctx, targetType, payload, asUser, origin, tx, sinks.notifyCommitSink), // @wrapper-known semantic-alias
78
+ writeAs: writeAsUser,
79
+ }, identitySwitchAudit);
80
+ return {
81
+ bridge: {
82
+ query: (targetType, payload) => executeQuery(ctx, targetType, payload, user, origin, tx, sinks.notifyCommitSink), // @wrapper-known semantic-alias
83
+ queryAs: identitySwitch.queryAs,
84
+ write: (targetType, payload) => writeAsUser(user, targetType, payload),
85
+ writeAs: identitySwitch.writeAs,
86
+ },
87
+ scheduleFragment: { ...(sinks.hasLiveCommitSink && { scheduleAfterCommit }) },
88
+ };
89
+ }
@@ -0,0 +1,43 @@
1
+ import type { AggregateStreamHandle, AppendEventArgs, AppendEventFn, FetchForWritingArgs } from "../engine/types/index.ts";
2
+ import { IdempotentAppendConflictError as EventStoreIdempotentAppendConflictError, VersionConflictError as EventStoreVersionConflictError } from "../event-store/errors.ts";
3
+ import { type StoredEvent } from "../event-store/event-store.ts";
4
+ import { type LoadAggregateWithSnapshotOptions, type LoadAggregateWithSnapshotResult, type SnapshotReducer } from "../event-store/snapshot.ts";
5
+ import type { HandlerContextScope } from "./handler-context-scope.ts";
6
+ export declare function buildEventStoreFacet(scope: HandlerContextScope): {
7
+ appendEvent: AppendEventFn;
8
+ unsafeAppendEvent: (args: AppendEventArgs) => Promise<void>;
9
+ tryAppendEvent: (args: AppendEventArgs) => Promise<{
10
+ ok: true;
11
+ event: StoredEvent;
12
+ conflict?: undefined;
13
+ } | {
14
+ event?: undefined;
15
+ ok: false;
16
+ conflict: EventStoreVersionConflictError;
17
+ } | {
18
+ event?: undefined;
19
+ ok: false;
20
+ conflict: EventStoreIdempotentAppendConflictError;
21
+ }>;
22
+ fetchForWriting: (args: FetchForWritingArgs) => Promise<AggregateStreamHandle>;
23
+ loadAggregate: (aggregateId: string, loadOptions?: {
24
+ readonly asOf?: Temporal.Instant;
25
+ }) => Promise<readonly StoredEvent[]>;
26
+ archiveStream: (aggregateId: string, archiveArgs: {
27
+ readonly aggregateType: string;
28
+ readonly reason?: string;
29
+ }) => Promise<void>;
30
+ restoreStream: (aggregateId: string) => Promise<void>;
31
+ isStreamArchived: (aggregateId: string) => Promise<boolean>;
32
+ snapshotAggregate: (snapshotArgs: {
33
+ readonly aggregateId: string;
34
+ readonly aggregateType: string;
35
+ readonly version: number;
36
+ readonly state: Record<string, unknown>;
37
+ readonly snapshotVersion?: number;
38
+ }) => Promise<void>;
39
+ loadAggregateWithSnapshot: <TState extends Record<string, unknown>>(aggregateId: string, reducer: SnapshotReducer<TState>, initial: TState, loadOptions?: Omit<LoadAggregateWithSnapshotOptions, "upcastEvent" | "entityEventVersionOf">) => Promise<LoadAggregateWithSnapshotResult<TState>>;
40
+ queryProjection: <T = Record<string, unknown>>(qualifiedName: string, options?: {
41
+ readonly unsafeAllTenants?: boolean;
42
+ }) => Promise<readonly T[]>;
43
+ };
@@ -0,0 +1,237 @@
1
+ import { runInSavepoint, selectMany } from "../db/query.js";
2
+ import { hasTenantColumn } from "../db/tenant-db.js";
3
+ import { InternalError, memberResolutionReadOnlyDenied, VersionConflictError, } from "../errors/index.js";
4
+ import { archiveStream as archiveStreamHelper, isStreamArchived, restoreStream as restoreStreamHelper, } from "../event-store/archive.js";
5
+ import { IdempotentAppendConflictError as EventStoreIdempotentAppendConflictError, VersionConflictError as EventStoreVersionConflictError, } from "../event-store/errors.js";
6
+ import { loadAggregate, loadAggregateAsOf, loadAggregateForWriting, } from "../event-store/event-store.js";
7
+ import { effectiveSnapshotGeneration, loadAggregateWithSnapshot, saveSnapshot, } from "../event-store/snapshot.js";
8
+ import { upcastStoredEvent, upcastStoredEvents } from "../event-store/upcaster.js";
9
+ import { appendDomainEventCore } from "./append-event-core.js";
10
+ import { isMemberResolutionPrincipal, resolveDbSource, } from "./dispatch-shared.js";
11
+ import { createGatedProjectionReader } from "./system-identity-switch.js";
12
+ function requireDbSource(ctx, tx, surfaceCall) {
13
+ const dbSource = resolveDbSource(ctx, tx);
14
+ if (!dbSource) {
15
+ throw new InternalError({
16
+ message: `${surfaceCall} requires a database connection — none is configured.`,
17
+ });
18
+ }
19
+ return dbSource;
20
+ }
21
+ // ctx.appendEvent — append a domain event onto a specific aggregate stream
22
+ // in the current tx, then fire matching inline projections. Core logic
23
+ // lives in appendDomainEventCore; this wrapper just locates dbSource +
24
+ // stringifies the SessionUser id for the shared helper.
25
+ async function appendDomainEvent(ctx, args, user, tx, callerFeature) {
26
+ // Sink behind every append surface, so a resolved member stays read-only even via fetchForWriting handles.
27
+ if (isMemberResolutionPrincipal(user))
28
+ throw memberResolutionReadOnlyDenied();
29
+ const { registry } = ctx;
30
+ const dbSource = requireDbSource(ctx, tx, `ctx.appendEvent("${args.type}")`);
31
+ await appendDomainEventCore({
32
+ registry,
33
+ db: dbSource,
34
+ tenantId: user.tenantId,
35
+ userId: String(user.id),
36
+ callSiteLabel: "ctx.appendEvent",
37
+ callerFeature,
38
+ }, args);
39
+ }
40
+ // Savepoint-scoped append: catches a losing writer's VersionConflictError
41
+ // without poisoning the rest of the handler's transaction. postgres.js
42
+ // aborts the WHOLE begin() on an uncaught statement error
43
+ // (SQLSTATE 25P02) even if the JS error is caught — runInSavepoint
44
+ // wraps the append in a real SAVEPOINT so only that nested scope rolls
45
+ // back on conflict, and subsequent statements in the outer tx (e.g. a
46
+ // dedup-anchor insert) still succeed. Use when a handler must react to
47
+ // losing a concurrent-append race instead of failing the whole write.
48
+ function buildTryAppendEvent(scope) {
49
+ const { type, user, tx, registry } = scope;
50
+ return async (args) => {
51
+ if (!tx) {
52
+ throw new InternalError({
53
+ message: `ctx.tryAppendEvent("${args.type}") requires an active transaction — no tx is threaded through this call.`,
54
+ });
55
+ }
56
+ try {
57
+ const event = await runInSavepoint(tx, (sp) => appendDomainEventCore({
58
+ registry,
59
+ db: sp,
60
+ tenantId: user.tenantId,
61
+ userId: String(user.id),
62
+ callSiteLabel: "ctx.tryAppendEvent",
63
+ callerFeature: registry.getHandlerFeature(type),
64
+ }, args));
65
+ return { ok: true, event };
66
+ }
67
+ catch (e) {
68
+ if (e instanceof EventStoreVersionConflictError) {
69
+ return { ok: false, conflict: e };
70
+ }
71
+ if (e instanceof EventStoreIdempotentAppendConflictError) {
72
+ return { ok: false, conflict: e };
73
+ }
74
+ throw e;
75
+ }
76
+ };
77
+ }
78
+ function buildFetchForWriting(scope) {
79
+ const { ctx, type, user, tx, registry } = scope;
80
+ return async (args) => {
81
+ const dbSource = requireDbSource(ctx, tx, `ctx.fetchForWriting("${args.aggregateId}")`);
82
+ // Stream-version authoritative (same policy as CRUD executor + Block 0), read together
83
+ // with the events so the pinned version covers exactly what the handler's rules saw.
84
+ const { events: storedEvents, streamVersion: fetchedVersion } = await loadAggregateForWriting(dbSource, args.aggregateId, user.tenantId, { aggregateType: args.aggregateType });
85
+ const events = await upcastStoredEvents(storedEvents, registry.getEventUpcasters(), {
86
+ db: dbSource,
87
+ tenantId: user.tenantId,
88
+ });
89
+ // Optimistic concurrency: if the caller knows the version they
90
+ // worked against (e.g. from a prior read-model row) and the stream
91
+ // has moved on, fail fast before any downstream work.
92
+ if (args.expectedVersion !== undefined && args.expectedVersion !== fetchedVersion) {
93
+ throw new VersionConflictError({
94
+ entityId: args.aggregateId,
95
+ expectedVersion: args.expectedVersion,
96
+ currentVersion: fetchedVersion,
97
+ });
98
+ }
99
+ // Pinned into every append: a write landing between fetch and append
100
+ // fails with VersionConflictError instead of stacking on a state the
101
+ // caller's business rules never saw. Bumps per appendOne so consecutive
102
+ // appends stay in order without re-reading the DB.
103
+ let handleVersion = fetchedVersion;
104
+ const appendOne = async (appendArgs) => {
105
+ await appendDomainEvent(ctx, {
106
+ aggregateId: args.aggregateId,
107
+ aggregateType: args.aggregateType,
108
+ type: appendArgs.type,
109
+ payload: appendArgs.payload,
110
+ expectedVersion: handleVersion,
111
+ }, user, tx, registry.getHandlerFeature(type));
112
+ handleVersion += 1;
113
+ };
114
+ return {
115
+ events,
116
+ get version() {
117
+ return handleVersion;
118
+ },
119
+ appendOne,
120
+ hasEvent: (type) => events.some((e) => e.type === type),
121
+ };
122
+ };
123
+ }
124
+ function buildLoadAggregate(scope) {
125
+ const { ctx, user, tx, registry } = scope;
126
+ return async (aggregateId, loadOptions) => {
127
+ const dbSource = requireDbSource(ctx, tx, `ctx.loadAggregate("${aggregateId}")`);
128
+ const events = loadOptions?.asOf
129
+ ? await loadAggregateAsOf(dbSource, aggregateId, user.tenantId, loadOptions.asOf)
130
+ : await loadAggregate(dbSource, aggregateId, user.tenantId);
131
+ return upcastStoredEvents(events, registry.getEventUpcasters(), {
132
+ db: dbSource,
133
+ tenantId: user.tenantId,
134
+ });
135
+ };
136
+ }
137
+ function buildSnapshotAggregate(scope) {
138
+ const { ctx, user, tx, registry } = scope;
139
+ return async (snapshotArgs) => {
140
+ const dbSource = requireDbSource(ctx, tx, `ctx.snapshotAggregate("${snapshotArgs.aggregateId}")`);
141
+ await saveSnapshot(dbSource, {
142
+ aggregateId: snapshotArgs.aggregateId,
143
+ tenantId: user.tenantId,
144
+ aggregateType: snapshotArgs.aggregateType,
145
+ version: snapshotArgs.version,
146
+ state: snapshotArgs.state,
147
+ snapshotVersion: effectiveSnapshotGeneration(snapshotArgs.snapshotVersion ?? 1, registry.getEntity(snapshotArgs.aggregateType)?.eventVersion),
148
+ });
149
+ };
150
+ }
151
+ function buildLoadAggregateWithSnapshot(scope) {
152
+ const { ctx, user, tx, registry } = scope;
153
+ return async (aggregateId, reducer, initial, loadOptions) => {
154
+ const dbSource = requireDbSource(ctx, tx, `ctx.loadAggregateWithSnapshot("${aggregateId}")`);
155
+ // Upcaster-aware: pass an upcastEvent callback so loadAggregateWithSnapshot
156
+ // walks every delta through the registered chain before invoking the
157
+ // user's (sync) reducer. Async upcasters (DB-enrichment) are awaited
158
+ // inside loadAggregateWithSnapshot — feature authors never see legacy
159
+ // payload shapes regardless of which load path they chose.
160
+ const upcasters = registry.getEventUpcasters();
161
+ const upcastCtx = { db: dbSource, tenantId: user.tenantId };
162
+ return loadAggregateWithSnapshot(dbSource, aggregateId, user.tenantId, reducer, initial, {
163
+ ...loadOptions,
164
+ entityEventVersionOf: (aggregateType) => registry.getEntity(aggregateType)?.eventVersion,
165
+ upcastEvent: (event) => upcastStoredEvent(event, upcasters, upcastCtx), // @wrapper-known semantic-alias
166
+ });
167
+ };
168
+ }
169
+ // queryProjection works against both single-stream and multi-stream
170
+ // projections. MSPs without a table cannot be queried — those are
171
+ // side-effect-only consumers (no state to read back).
172
+ function buildHandlerProjectionReader(scope) {
173
+ const { ctx, type, user, tx, registry, hasIdentitySwitchGrant, identitySwitchAudit } = scope;
174
+ const findProjectionTable = (qualifiedName) => registry.getAllProjections().get(qualifiedName)?.table ??
175
+ registry.getAllMultiStreamProjections().get(qualifiedName)?.table;
176
+ const ungatedQueryProjection = async (qualifiedName, queryOptions) => {
177
+ const projTable = findProjectionTable(qualifiedName);
178
+ if (!projTable) {
179
+ const singleNames = [...registry.getAllProjections().keys()];
180
+ const mspNames = [...registry.getAllMultiStreamProjections().keys()].filter((n) => registry.getAllMultiStreamProjections().get(n)?.table);
181
+ const all = [...singleNames, ...mspNames];
182
+ throw new InternalError({
183
+ message: `ctx.queryProjection("${qualifiedName}") — projection not registered, or it is a ` +
184
+ `table-less MSP (side-effect-only). Known queryable projections: ${all.join(", ") || "(none)"}`,
185
+ });
186
+ }
187
+ const dbSource = requireDbSource(ctx, tx, `ctx.queryProjection("${qualifiedName}")`);
188
+ const where = hasTenantColumn(projTable) && queryOptions?.unsafeAllTenants !== true
189
+ ? { tenantId: user.tenantId }
190
+ : undefined;
191
+ const rows = await selectMany(dbSource, projTable, where);
192
+ return rows; // @cast-boundary engine-payload
193
+ };
194
+ return createGatedProjectionReader(`handler "${type}"`, hasIdentitySwitchGrant, ungatedQueryProjection, identitySwitchAudit, (qualifiedName) => {
195
+ const projTable = findProjectionTable(qualifiedName);
196
+ // Unknown projection: keep the grant check first, the ungated reader reports the real error.
197
+ return projTable === undefined || hasTenantColumn(projTable);
198
+ });
199
+ }
200
+ export function buildEventStoreFacet(scope) {
201
+ const { ctx, type, user, tx, registry } = scope;
202
+ return {
203
+ // Strict + unsafe share the same runtime — only the type-surface
204
+ // differs. The strict signature is what's exposed to typed callers;
205
+ // unsafe is the explicit escape-hatch for runtime-pluggable events.
206
+ appendEvent: (async (args) => {
207
+ await appendDomainEvent(ctx, args, user, tx, registry.getHandlerFeature(type));
208
+ }), // @cast-boundary engine-bridge
209
+ unsafeAppendEvent: async (args) => {
210
+ await appendDomainEvent(ctx, args, user, tx, registry.getHandlerFeature(type));
211
+ },
212
+ tryAppendEvent: buildTryAppendEvent(scope),
213
+ fetchForWriting: buildFetchForWriting(scope),
214
+ loadAggregate: buildLoadAggregate(scope),
215
+ archiveStream: async (aggregateId, archiveArgs) => {
216
+ const dbSource = requireDbSource(ctx, tx, `ctx.archiveStream("${aggregateId}")`);
217
+ await archiveStreamHelper(dbSource, {
218
+ tenantId: user.tenantId,
219
+ aggregateId,
220
+ aggregateType: archiveArgs.aggregateType,
221
+ archivedBy: user.id,
222
+ reason: archiveArgs.reason,
223
+ });
224
+ },
225
+ restoreStream: async (aggregateId) => {
226
+ const dbSource = requireDbSource(ctx, tx, `ctx.restoreStream("${aggregateId}")`);
227
+ await restoreStreamHelper(dbSource, user.tenantId, aggregateId);
228
+ },
229
+ isStreamArchived: async (aggregateId) => {
230
+ const dbSource = requireDbSource(ctx, tx, `ctx.isStreamArchived("${aggregateId}")`);
231
+ return isStreamArchived(dbSource, user.tenantId, aggregateId);
232
+ },
233
+ snapshotAggregate: buildSnapshotAggregate(scope),
234
+ loadAggregateWithSnapshot: buildLoadAggregateWithSnapshot(scope),
235
+ queryProjection: buildHandlerProjectionReader(scope),
236
+ };
237
+ }
@@ -0,0 +1,12 @@
1
+ import type { DbConnection } from "../db/connection.ts";
2
+ import type { TenantId } from "../engine/types/identifiers.ts";
3
+ import type { NotifyFn, SessionUser } from "../engine/types/index.ts";
4
+ import type { CommitSinks } from "./handler-context-dispatch.ts";
5
+ import type { HandlerContextScope } from "./handler-context-scope.ts";
6
+ export declare function buildNotifyFacet(scope: HandlerContextScope, sinks: CommitSinks, outsideTxSource: DbConnection | undefined): {
7
+ notify: NotifyFn | undefined;
8
+ factoryFragment: {
9
+ _notifyInTransactionFactory?: ((notifyUser: SessionUser, notifyTenantId: TenantId) => NotifyFn) | undefined;
10
+ _notifyAfterCommitFactory?: ((notifyUser: SessionUser, notifyTenantId: TenantId) => NotifyFn) | undefined;
11
+ };
12
+ };
@@ -0,0 +1,55 @@
1
+ import { createDeferringNotify } from "./notify-scope.js";
2
+ // DispatchContext.jobRunner is typed as the handleEvent-only JobRunnerRef but
3
+ // holds the concrete runner at boot; narrow instead of casting.
4
+ function isNotifyJobDispatcher(runner) {
5
+ return (typeof runner === "object" &&
6
+ runner !== null &&
7
+ "dispatch" in runner &&
8
+ typeof runner.dispatch === "function");
9
+ }
10
+ function buildNotify(scope, sinks, notifyAfterCommitFactory, notifyInTransactionFactory) {
11
+ const { user, registry } = scope;
12
+ if (!notifyAfterCommitFactory)
13
+ return undefined;
14
+ const notifyAfterCommit = notifyAfterCommitFactory(user, user.tenantId);
15
+ if (!notifyInTransactionFactory)
16
+ return notifyAfterCommit;
17
+ const notifyInTransaction = notifyInTransactionFactory(user, user.tenantId);
18
+ // A rolled-back write must leave no notification behind. Without any commit to wait for (a
19
+ // post-commit context) delivery stays immediate.
20
+ const { notifyCommitSink } = sinks;
21
+ if (notifyCommitSink === undefined)
22
+ return notifyInTransaction;
23
+ return createDeferringNotify({
24
+ registry,
25
+ tenantId: user.tenantId,
26
+ notifyAfterCommit,
27
+ notifyNow: notifyInTransaction,
28
+ scheduleAfterCommit: (hook) => {
29
+ notifyCommitSink.push(hook);
30
+ },
31
+ });
32
+ }
33
+ export function buildNotifyFacet(scope, sinks, outsideTxSource) {
34
+ const { ctx, tx, context } = scope;
35
+ const notifyFactory = context._notifyFactory;
36
+ const notifyJobDispatcher = isNotifyJobDispatcher(ctx.jobRunner) ? ctx.jobRunner : undefined;
37
+ // Inside the write tx the app pool's slots can all be held by waiting write txs, so the
38
+ // delivery's own DB work (broadcast lookup, attempt log, channel sends) runs on the outside-tx pool.
39
+ const notifyInTransactionFactory = notifyFactory && tx !== undefined
40
+ ? (notifyUser, notifyTenantId) => notifyFactory(notifyUser, notifyTenantId, notifyJobDispatcher, outsideTxSource, context._fileProviderResolver)
41
+ : undefined;
42
+ const notifyAfterCommitFactory = notifyFactory
43
+ ? (notifyUser, notifyTenantId) => notifyFactory(notifyUser, notifyTenantId, notifyJobDispatcher, undefined, context._fileProviderResolver)
44
+ : undefined;
45
+ const notify = buildNotify(scope, sinks, notifyAfterCommitFactory, notifyInTransactionFactory);
46
+ return {
47
+ notify,
48
+ factoryFragment: {
49
+ ...(notifyInTransactionFactory && {
50
+ _notifyInTransactionFactory: notifyInTransactionFactory,
51
+ }),
52
+ ...(notifyAfterCommitFactory && { _notifyAfterCommitFactory: notifyAfterCommitFactory }),
53
+ },
54
+ };
55
+ }
@@ -0,0 +1,7 @@
1
+ import type { HandlerContextScope } from "./handler-context-scope.ts";
2
+ export declare function buildObservabilityFacet(scope: HandlerContextScope): {
3
+ log: import("@cosmicdrift/kumiko-types/logger").Logger | undefined;
4
+ tracer: import("@cosmicdrift/kumiko-types/observability/span").Tracer;
5
+ metrics: import("@cosmicdrift/kumiko-types/observability/metric").MetricsHandle;
6
+ metricsFor: (featureName: string) => import("@cosmicdrift/kumiko-types/observability/metric").MetricsHandle;
7
+ };
@@ -0,0 +1,21 @@
1
+ import { createMetricsForFactory, createMetricsHandle, createNoopMetricsHandle, getFallbackTracer, } from "../observability/index.js";
2
+ export function buildObservabilityFacet(scope) {
3
+ const { type, user, context, reqCtx, featureName } = scope;
4
+ const log = context.log?.child({
5
+ handler: type,
6
+ tenantId: user.tenantId,
7
+ userId: user.id,
8
+ ...(reqCtx && { requestId: reqCtx.requestId }),
9
+ });
10
+ // Observability — feature-bound metrics handle, so ctx.metrics.inc("foo")
11
+ // resolves to kumiko_<feature>_foo. Unknown feature falls back to noop
12
+ // so legacy internal handlers don't crash.
13
+ const tracer = context.tracer ?? getFallbackTracer();
14
+ const meter = context.meter;
15
+ const metrics = meter && featureName ? createMetricsHandle(meter, featureName) : createNoopMetricsHandle();
16
+ // ctx.metricsFor(featureName) — shared/library code binds to a feature
17
+ // name of its own choosing instead of the dispatching handler's.
18
+ // Unregistered names no-op rather than throw.
19
+ const metricsFor = createMetricsForFactory(meter);
20
+ return { log, tracer, metrics, metricsFor };
21
+ }
@@ -0,0 +1,48 @@
1
+ import { type EscapeHatchDeclaration, type EscapeHatchReporter } from "@cosmicdrift/kumiko-types/handlers";
2
+ import { type RequestContextData } from "../api/request-context.ts";
3
+ import type { DbConnection, DbTx } from "../db/connection.ts";
4
+ import type { AppContext, Registry, SessionUser } from "../engine/types/index.ts";
5
+ import { type DispatchContext, type HandlerTransactionScope } from "./dispatch-shared.ts";
6
+ import type { WriteOrigin } from "./write-origin.ts";
7
+ export type HandlerContextScope = {
8
+ readonly ctx: DispatchContext;
9
+ readonly type: string;
10
+ readonly user: SessionUser;
11
+ readonly origin: WriteOrigin;
12
+ readonly tx: DbTx | undefined;
13
+ readonly transactionScope: HandlerTransactionScope;
14
+ readonly registry: Registry;
15
+ readonly context: AppContext;
16
+ readonly isSystem: boolean;
17
+ readonly dbSource: DbConnection | DbTx | undefined;
18
+ readonly reqCtx: RequestContextData | undefined;
19
+ readonly writeEscapeHatch: EscapeHatchDeclaration | undefined;
20
+ readonly handlerEscapeHatch: EscapeHatchDeclaration | undefined;
21
+ readonly isWriteHandler: boolean;
22
+ readonly isPerCommand: boolean;
23
+ readonly denyDbWrites: boolean;
24
+ readonly featureName: string | undefined;
25
+ readonly hasIdentitySwitchGrant: boolean;
26
+ readonly reportEscapeHatch: EscapeHatchReporter;
27
+ readonly identitySwitchAudit: {
28
+ readonly reason: string | undefined;
29
+ readonly report: EscapeHatchReporter;
30
+ };
31
+ };
32
+ export declare function resolveHandlerContextScope(input: {
33
+ readonly ctx: DispatchContext;
34
+ readonly type: string;
35
+ readonly user: SessionUser;
36
+ readonly origin: WriteOrigin;
37
+ readonly tx: DbTx | undefined;
38
+ readonly transactionScope: HandlerTransactionScope;
39
+ }): HandlerContextScope;
40
+ export declare function buildPreSaveFragment(scope: HandlerContextScope): {
41
+ runPreSave?: ((handlerName: string, changes: Record<string, unknown>, previous: Readonly<Record<string, unknown>>, isNew: boolean) => Promise<Record<string, unknown>>) | undefined;
42
+ };
43
+ export declare function buildRuntimeFragment(scope: HandlerContextScope): {
44
+ signal?: AbortSignal | undefined;
45
+ effectiveFeatures?: import("@cosmicdrift/kumiko-framework/engine").EffectiveFeaturesResolver | undefined;
46
+ cacheSync?: import("@cosmicdrift/kumiko-types/cache-sync-types").CacheSyncBus | undefined;
47
+ jobRunner?: import("@cosmicdrift/kumiko-types/handlers").JobRunnerRef | undefined;
48
+ };
@@ -0,0 +1,88 @@
1
+ import { escapeHatchFor, } from "@cosmicdrift/kumiko-types/handlers";
2
+ import { requestContext } from "../api/request-context.js";
3
+ import { createFallbackLogger } from "../logging/utils.js";
4
+ import { createEscapeHatchReporter } from "../observability/index.js";
5
+ import { resolveDbSource, } from "./dispatch-shared.js";
6
+ export function resolveHandlerContextScope(input) {
7
+ const { ctx, type, user, origin, tx, transactionScope } = input;
8
+ const { registry, appContext: context } = ctx;
9
+ const isSystem = registry.isHandlerSystemScoped(type);
10
+ // The outer dispatcher receives a DbConnection from the server/stack;
11
+ // AppContext's `db` union also allows TenantDb (for downstream hook calls),
12
+ // but at this point we're the root of the pipeline — cast is safe.
13
+ const dbSource = resolveDbSource(ctx, tx);
14
+ const reqCtx = requestContext.get();
15
+ // global() writes are write-handler-only; SYSTEM identity switch and unsafeRaw accept write, query or stream escapeHatch.
16
+ const writeHandler = registry.getWriteHandler(type);
17
+ const writeEscapeHatch = writeHandler?.escapeHatch;
18
+ const isPerCommand = writeHandler?.transaction === "perCommand";
19
+ const denyDbWrites = transactionScope === "withoutTransaction";
20
+ const handlerEscapeHatch = writeEscapeHatch ??
21
+ registry.getQueryHandler(type)?.escapeHatch ??
22
+ registry.getStreamHandler(type)?.escapeHatch;
23
+ const identitySwitchEscapeHatch = escapeHatchFor(handlerEscapeHatch, "systemIdentity");
24
+ const hasIdentitySwitchGrant = isSystem || identitySwitchEscapeHatch !== undefined;
25
+ const featureName = registry.getHandlerFeature(type);
26
+ const reportEscapeHatch = createEscapeHatchReporter({
27
+ handler: type,
28
+ tenantId: user.tenantId,
29
+ actor: user.id,
30
+ sink: context._escapeHatchAuditSink,
31
+ log: context.log ?? createFallbackLogger("dispatcher"),
32
+ window: ctx.escapeHatchReportWindow,
33
+ meter: context.meter,
34
+ });
35
+ const identitySwitchGrantReason = identitySwitchEscapeHatch?.reason ??
36
+ (isSystem ? `r.systemScope() feature "${featureName ?? "unknown"}"` : undefined);
37
+ return {
38
+ ctx,
39
+ type,
40
+ user,
41
+ origin,
42
+ tx,
43
+ transactionScope,
44
+ registry,
45
+ context,
46
+ isSystem,
47
+ dbSource,
48
+ reqCtx,
49
+ writeEscapeHatch,
50
+ handlerEscapeHatch,
51
+ isWriteHandler: writeHandler !== undefined,
52
+ isPerCommand,
53
+ denyDbWrites,
54
+ featureName,
55
+ hasIdentitySwitchGrant,
56
+ reportEscapeHatch,
57
+ identitySwitchAudit: { reason: identitySwitchGrantReason, report: reportEscapeHatch },
58
+ };
59
+ }
60
+ // preSave hooks need `changes`/`previous`/`isNew`, which only exist once
61
+ // a handler actually starts building its write — bound here so entity
62
+ // CRUD handlers (entity-handlers.ts) can forward it to the executor.
63
+ export function buildPreSaveFragment(scope) {
64
+ const { lifecycle } = scope.ctx;
65
+ return {
66
+ ...(lifecycle && {
67
+ runPreSave: (handlerName, changes, previous, isNew) => lifecycle.runPreSave(handlerName, changes, previous, isNew, scope.context),
68
+ }),
69
+ };
70
+ }
71
+ export function buildRuntimeFragment(scope) {
72
+ const { ctx, reqCtx } = scope;
73
+ const { effectiveFeatures, jobRunner } = ctx;
74
+ return {
75
+ // Cancellation signal flows from the HTTP middleware via
76
+ // requestContext. Conditional spread so non-HTTP entry-points
77
+ // (jobs, dispatcher MSP-applies) don't get a phantom signal that
78
+ // would always read aborted=false but feel meaningful.
79
+ ...(reqCtx?.signal ? { signal: reqCtx.signal } : {}),
80
+ // Propagate the feature-toggle resolver so the lifecycle pipeline,
81
+ // MSP runner, and ctx.hasFeature all pull from the same source.
82
+ ...(effectiveFeatures && { effectiveFeatures }),
83
+ ...(ctx.cacheSync && { cacheSync: ctx.cacheSync }),
84
+ // Lets write handlers call ctx.jobRunner.dispatch(...) directly, same
85
+ // as a follow-up job would (test-stack.ts wires the matching runner).
86
+ ...(jobRunner && { jobRunner }),
87
+ };
88
+ }
@@ -46,7 +46,7 @@ export function assertIrreversibleOperationAllowed(operation) {
46
46
  // Fields flagged readAsInstruction: true have their write payload presence checked here — the
47
47
  // same reasoning as assertIrreversibleOperationAllowed, but the irreversible property belongs to
48
48
  // the target field, not the verb. Presence in the payload (not a value comparison against the
49
- // loaded row) is the deliberate fail-closed choice — see kumiko-framework#3358.
49
+ // loaded row) is the deliberate fail-closed choice.
50
50
  export function assertInstructionFieldWriteAllowed(entityName, verb, writtenFieldNames, instructionFieldNames) {
51
51
  const fields = writtenFieldNames.filter((name) => instructionFieldNames.includes(name));
52
52
  // skip: the write touches no readAsInstruction field.
@@ -5,17 +5,17 @@ function resolveTracer(context) {
5
5
  return context.tracer ?? getFallbackTracer();
6
6
  }
7
7
  /**
8
- * Resolve effective-feature set for the active context's tenant.
8
+ * Resolves the effective feature set for the active context's tenant.
9
9
  *
10
- * `_tenantId` wird vom dispatcher beim HandlerContext-Bau aus `user.tenantId`
11
- * gespiegelt. Bei legacy-callsites ohne user (system-jobs, boot-time
12
- * pipeline) ist es undefined — `effectiveFeatures(undefined)` wäre type-
13
- * unsafe; wir return undefined und behandeln das als "all features on"
14
- * (registry filterhooks akzeptieren undefined als "skip filter").
10
+ * `_tenantId` is copied from `user.tenantId` when the dispatcher builds the
11
+ * HandlerContext. Legacy call sites without a user (system jobs, boot-time
12
+ * pipeline) leave it undefined; `effectiveFeatures(undefined)` is type-unsafe,
13
+ * so this returns undefined and treats that as "all features on" (registry
14
+ * filter hooks accept undefined to skip filtering).
15
15
  *
16
- * Single source — die 4 lifecycle-hook-callsites (preSave, postSave,
17
- * preDelete, postDelete) rufen alle hier durch, damit der tenant-
18
- * scoping-Pfad nie inkonsistent ist.
16
+ * This is the single path used by all four lifecycle-hook call sites
17
+ * (preSave, postSave, preDelete, postDelete), keeping tenant scoping
18
+ * consistent.
19
19
  */
20
20
  function currentEffectiveFeatures(context) {
21
21
  if (context._tenantId === undefined)