@cosmicdrift/kumiko-framework 0.387.0 → 0.389.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 (375) hide show
  1. package/AGENTS.md +9 -1
  2. package/agents/examples/anonymous-access.md +1 -1
  3. package/agents/examples/apex-landing.md +204 -0
  4. package/agents/examples/api-worker-split.md +64 -0
  5. package/agents/examples/app-config.md +14 -0
  6. package/agents/examples/auth-mfa.md +115 -0
  7. package/agents/examples/env-composition.md +28 -1
  8. package/agents/examples/golden-path.md +13 -1
  9. package/agents/examples/legal-pages.md +1 -1
  10. package/agents/examples/lifecycle-hooks.md +55 -2
  11. package/agents/examples/list-table-options.md +466 -0
  12. package/agents/examples/pdf-audit-export.md +4 -2
  13. package/agents/examples/prod-boot-options.md +16 -0
  14. package/agents/examples/rate-limiting.md +48 -0
  15. package/agents/examples/record-detail-layout.md +692 -0
  16. package/agents/examples/seed-migration.md +212 -0
  17. package/agents/examples/state-machine.md +3 -2
  18. package/agents/examples/tenant-isolation.md +73 -1
  19. package/agents/examples/wizard-form.md +941 -0
  20. package/agents/examples/writeform-section.md +548 -0
  21. package/agents/examples.md +1 -0
  22. package/agents/features.md +19 -2
  23. package/agents/widgets.md +2 -0
  24. package/dist/api/api-constants.d.ts +1 -0
  25. package/dist/api/api-constants.js +2 -0
  26. package/dist/api/auth-routes-shared.d.ts +2 -0
  27. package/dist/api/auth-routes-shared.js +5 -0
  28. package/dist/api/client-error-route.js +10 -5
  29. package/dist/api/entity-read-access.d.ts +2 -1
  30. package/dist/api/entity-read-access.js +14 -0
  31. package/dist/api/export/export-cells-reference.d.ts +9 -0
  32. package/dist/api/export/export-cells-reference.js +21 -0
  33. package/dist/api/export/export-cells.d.ts +11 -0
  34. package/dist/api/export/export-cells.js +136 -0
  35. package/dist/api/export/export-ciphertext-guard.d.ts +2 -0
  36. package/dist/api/export/export-ciphertext-guard.js +27 -0
  37. package/dist/api/export/export-columns.d.ts +29 -0
  38. package/dist/api/export/export-columns.js +85 -0
  39. package/dist/api/export/export-errors.d.ts +4 -0
  40. package/dist/api/export/export-errors.js +14 -0
  41. package/dist/api/export/export-route.d.ts +4 -0
  42. package/dist/api/export/export-route.js +74 -0
  43. package/dist/api/export/export-rows.d.ts +13 -0
  44. package/dist/api/export/export-rows.js +62 -0
  45. package/dist/api/export/export-screen.d.ts +12 -0
  46. package/dist/api/export/export-screen.js +47 -0
  47. package/dist/api/export/export-xlsx-writer.d.ts +4 -0
  48. package/dist/api/export/export-xlsx-writer.js +21 -0
  49. package/dist/api/extra-route.d.ts +4 -0
  50. package/dist/api/index.d.ts +3 -1
  51. package/dist/api/index.js +3 -1
  52. package/dist/api/pii-leak-guard.d.ts +3 -0
  53. package/dist/api/pii-leak-guard.js +3 -2
  54. package/dist/api/request-context.d.ts +1 -1
  55. package/dist/api/request-context.js +1 -1
  56. package/dist/api/routes.d.ts +16 -0
  57. package/dist/api/routes.js +2 -2
  58. package/dist/api/server-boot-checks.d.ts +10 -0
  59. package/dist/api/server-boot-checks.js +157 -0
  60. package/dist/api/server-event-dispatcher.d.ts +26 -0
  61. package/dist/api/server-event-dispatcher.js +208 -0
  62. package/dist/api/server-extra-routes.d.ts +11 -0
  63. package/dist/api/server-extra-routes.js +206 -0
  64. package/dist/api/server-middleware.d.ts +20 -0
  65. package/dist/api/server-middleware.js +211 -0
  66. package/dist/api/server-routes.d.ts +19 -0
  67. package/dist/api/server-routes.js +299 -0
  68. package/dist/api/server-websocket-routes.d.ts +4 -0
  69. package/dist/api/server-websocket-routes.js +108 -0
  70. package/dist/api/server-wiring.d.ts +32 -0
  71. package/dist/api/server-wiring.js +238 -0
  72. package/dist/api/server.d.ts +10 -15
  73. package/dist/api/server.js +48 -1251
  74. package/dist/api/session-cookies.js +4 -4
  75. package/dist/api/sse-route.d.ts +2 -1
  76. package/dist/api/sse-route.js +27 -13
  77. package/dist/config/index.d.ts +3 -1
  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.d.ts +1 -0
  81. package/dist/crypto/pg-kms-adapter.js +12 -18
  82. package/dist/crypto/pii-field-encryption.d.ts +5 -1
  83. package/dist/crypto/pii-field-encryption.js +3 -1
  84. package/dist/db/aggregate-sql.d.ts +23 -0
  85. package/dist/db/aggregate-sql.js +165 -0
  86. package/dist/db/closed-connection.d.ts +9 -0
  87. package/dist/db/closed-connection.js +88 -0
  88. package/dist/db/connection.d.ts +4 -5
  89. package/dist/db/connection.js +29 -20
  90. package/dist/db/dialect.js +1 -1
  91. package/dist/db/distinct-on.d.ts +1 -1
  92. package/dist/db/driver-row-coercion.d.ts +2 -0
  93. package/dist/db/driver-row-coercion.js +186 -0
  94. package/dist/db/driver-value-preparation.d.ts +16 -0
  95. package/dist/db/driver-value-preparation.js +101 -0
  96. package/dist/db/entity-field-encryption.d.ts +1 -0
  97. package/dist/db/entity-field-encryption.js +1 -1
  98. package/dist/db/entity-table-meta.js +103 -110
  99. package/dist/db/event-store-executor-context.d.ts +2 -1
  100. package/dist/db/event-store-executor-read-aggregate-currencies.d.ts +17 -0
  101. package/dist/db/event-store-executor-read-aggregate-currencies.js +40 -0
  102. package/dist/db/event-store-executor-read-aggregate.d.ts +8 -0
  103. package/dist/db/event-store-executor-read-aggregate.js +143 -0
  104. package/dist/db/event-store-executor-read-filter.d.ts +31 -0
  105. package/dist/db/event-store-executor-read-filter.js +206 -0
  106. package/dist/db/event-store-executor-read-paging.d.ts +14 -0
  107. package/dist/db/event-store-executor-read-paging.js +99 -0
  108. package/dist/db/event-store-executor-read-rows.d.ts +6 -0
  109. package/dist/db/event-store-executor-read-rows.js +32 -0
  110. package/dist/db/event-store-executor-read-search.d.ts +28 -0
  111. package/dist/db/event-store-executor-read-search.js +221 -0
  112. package/dist/db/event-store-executor-read-sort.d.ts +41 -0
  113. package/dist/db/event-store-executor-read-sort.js +130 -0
  114. package/dist/db/event-store-executor-read-where.d.ts +24 -0
  115. package/dist/db/event-store-executor-read-where.js +81 -0
  116. package/dist/db/event-store-executor-read.d.ts +2 -9
  117. package/dist/db/event-store-executor-read.js +48 -701
  118. package/dist/db/event-store-executor-write-atomic.d.ts +4 -0
  119. package/dist/db/event-store-executor-write-atomic.js +41 -0
  120. package/dist/db/event-store-executor-write-steps.d.ts +17 -0
  121. package/dist/db/event-store-executor-write-steps.js +95 -0
  122. package/dist/db/event-store-executor-write.js +405 -509
  123. package/dist/db/event-store-executor.js +2 -1
  124. package/dist/db/executor-managed-tables.js +1 -1
  125. package/dist/db/index.d.ts +7 -3
  126. package/dist/db/index.js +6 -2
  127. package/dist/db/list-aggregate-labels.d.ts +14 -0
  128. package/dist/db/list-aggregate-labels.js +26 -0
  129. package/dist/db/migrate-generator.js +71 -86
  130. package/dist/db/migrate-runner.js +7 -4
  131. package/dist/db/pg-error.js +26 -21
  132. package/dist/db/queries/backfill-pii.js +3 -6
  133. package/dist/db/queries/database-now.js +2 -1
  134. package/dist/db/queries/shadow-swap.js +2 -3
  135. package/dist/db/query.d.ts +16 -15
  136. package/dist/db/query.js +76 -804
  137. package/dist/db/reference-data.js +19 -2
  138. package/dist/db/statement-timeout.js +2 -1
  139. package/dist/db/table-builder.js +113 -166
  140. package/dist/db/table-info.d.ts +10 -0
  141. package/dist/db/table-info.js +45 -0
  142. package/dist/db/temporal-value-guards.d.ts +6 -0
  143. package/dist/db/temporal-value-guards.js +15 -0
  144. package/dist/db/tenant-db-runner.d.ts +1 -0
  145. package/dist/db/tenant-db-runner.js +3 -0
  146. package/dist/db/tenant-db-upsert.d.ts +5 -0
  147. package/dist/db/tenant-db-upsert.js +91 -0
  148. package/dist/db/tenant-db.js +50 -10
  149. package/dist/db/where-clause.d.ts +6 -0
  150. package/dist/db/where-clause.js +236 -0
  151. package/dist/derivatives/delete-stored-file.js +2 -1
  152. package/dist/engine/boot-validator/boot-check.js +1 -1
  153. package/dist/engine/boot-validator/bundled-lists-table.d.ts +2 -0
  154. package/dist/engine/boot-validator/bundled-lists-table.js +36 -0
  155. package/dist/engine/boot-validator/detail-screens.js +2 -2
  156. package/dist/engine/boot-validator/entity-handler.d.ts +1 -0
  157. package/dist/engine/boot-validator/entity-handler.js +17 -0
  158. package/dist/engine/boot-validator/entity-list-aggregate-options.d.ts +3 -0
  159. package/dist/engine/boot-validator/entity-list-aggregate-options.js +23 -0
  160. package/dist/engine/boot-validator/form-derivations.js +35 -1
  161. package/dist/engine/boot-validator/i18n-keys.js +5 -3
  162. package/dist/engine/boot-validator/index.js +2 -0
  163. package/dist/engine/boot-validator/list-bulk-actions.d.ts +3 -0
  164. package/dist/engine/boot-validator/list-bulk-actions.js +49 -0
  165. package/dist/engine/boot-validator/list-column-layout.d.ts +8 -0
  166. package/dist/engine/boot-validator/list-column-layout.js +41 -0
  167. package/dist/engine/boot-validator/list-table-aggregate-options.d.ts +24 -0
  168. package/dist/engine/boot-validator/list-table-aggregate-options.js +145 -0
  169. package/dist/engine/boot-validator/list-table-aggregate-query.d.ts +10 -0
  170. package/dist/engine/boot-validator/list-table-aggregate-query.js +31 -0
  171. package/dist/engine/boot-validator/list-table-export-options.d.ts +1 -0
  172. package/dist/engine/boot-validator/list-table-export-options.js +27 -0
  173. package/dist/engine/boot-validator/list-table-options.d.ts +3 -0
  174. package/dist/engine/boot-validator/list-table-options.js +77 -0
  175. package/dist/engine/boot-validator/list-table-reject-unknown-keys.d.ts +1 -0
  176. package/dist/engine/boot-validator/list-table-reject-unknown-keys.js +7 -0
  177. package/dist/engine/boot-validator/pii-retention.js +233 -155
  178. package/dist/engine/boot-validator/projection-list-screens.d.ts +1 -1
  179. package/dist/engine/boot-validator/projection-list-screens.js +3 -13
  180. package/dist/engine/boot-validator/related-list-table-rules.d.ts +4 -0
  181. package/dist/engine/boot-validator/related-list-table-rules.js +42 -0
  182. package/dist/engine/boot-validator/screens-actions.d.ts +1 -0
  183. package/dist/engine/boot-validator/screens-actions.js +8 -1
  184. package/dist/engine/boot-validator/screens-dashboard.js +2 -5
  185. package/dist/engine/boot-validator/screens-forms.js +2 -6
  186. package/dist/engine/boot-validator/screens-kind-config-edit.d.ts +3 -0
  187. package/dist/engine/boot-validator/screens-kind-config-edit.js +88 -0
  188. package/dist/engine/boot-validator/screens-kind-entity-edit.d.ts +3 -0
  189. package/dist/engine/boot-validator/screens-kind-entity-edit.js +55 -0
  190. package/dist/engine/boot-validator/screens-kind-entity-list.d.ts +3 -0
  191. package/dist/engine/boot-validator/screens-kind-entity-list.js +235 -0
  192. package/dist/engine/boot-validator/screens-kind-projection-detail.d.ts +3 -0
  193. package/dist/engine/boot-validator/screens-kind-projection-detail.js +235 -0
  194. package/dist/engine/boot-validator/screens-kind-projection-list.d.ts +3 -0
  195. package/dist/engine/boot-validator/screens-kind-projection-list.js +56 -0
  196. package/dist/engine/boot-validator/screens-kind-secret-mint.d.ts +3 -0
  197. package/dist/engine/boot-validator/screens-kind-secret-mint.js +28 -0
  198. package/dist/engine/boot-validator/screens-kind-shared.d.ts +37 -0
  199. package/dist/engine/boot-validator/screens-kind-shared.js +142 -0
  200. package/dist/engine/boot-validator/screens-lists.d.ts +2 -1
  201. package/dist/engine/boot-validator/screens-lists.js +24 -11
  202. package/dist/engine/boot-validator/screens.d.ts +0 -2
  203. package/dist/engine/boot-validator/screens.js +99 -860
  204. package/dist/engine/build-app-schema.d.ts +2 -1
  205. package/dist/engine/build-app-schema.js +65 -97
  206. package/dist/engine/build-config-feature-schema.js +6 -1
  207. package/dist/engine/define-feature.js +13 -2
  208. package/dist/engine/define-handler.js +1 -0
  209. package/dist/engine/entity-handlers.d.ts +13 -0
  210. package/dist/engine/entity-handlers.js +10 -21
  211. package/dist/engine/entity-list-runtime-options.d.ts +8 -0
  212. package/dist/engine/entity-list-runtime-options.js +18 -0
  213. package/dist/engine/feature-ast/extractors/handlers.js +1 -0
  214. package/dist/engine/feature-ast/extractors/index.d.ts +1 -1
  215. package/dist/engine/feature-ast/extractors/index.js +1 -1
  216. package/dist/engine/feature-ast/extractors/round1.d.ts +2 -1
  217. package/dist/engine/feature-ast/extractors/round1.js +6 -0
  218. package/dist/engine/feature-ast/extractors/round2.js +25 -6
  219. package/dist/engine/feature-ast/index.d.ts +1 -1
  220. package/dist/engine/feature-ast/parse.js +3 -1
  221. package/dist/engine/feature-ast/patch.d.ts +2 -0
  222. package/dist/engine/feature-ast/patch.js +5 -182
  223. package/dist/engine/feature-ast/pattern-change-schema.d.ts +10 -245
  224. package/dist/engine/feature-ast/pattern-change-schema.js +10 -163
  225. package/dist/engine/feature-ast/pattern-id-matchers.d.ts +7 -0
  226. package/dist/engine/feature-ast/pattern-id-matchers.js +211 -0
  227. package/dist/engine/feature-ast/pattern-id-schema.d.ts +251 -0
  228. package/dist/engine/feature-ast/pattern-id-schema.js +166 -0
  229. package/dist/engine/feature-ast/patterns.d.ts +14 -1
  230. package/dist/engine/feature-ast/patterns.js +44 -51
  231. package/dist/engine/feature-ast/render.js +52 -87
  232. package/dist/engine/feature-builder-state.d.ts +1 -0
  233. package/dist/engine/feature-builder-state.js +1 -0
  234. package/dist/engine/feature-entity-handlers.js +3 -0
  235. package/dist/engine/feature-manifest.d.ts +1 -0
  236. package/dist/engine/feature-manifest.js +1 -0
  237. package/dist/engine/field-access.d.ts +1 -0
  238. package/dist/engine/field-access.js +12 -0
  239. package/dist/engine/form-derivations.js +18 -7
  240. package/dist/engine/handler-field-references.js +1 -1
  241. package/dist/engine/index.d.ts +2 -2
  242. package/dist/engine/index.js +1 -1
  243. package/dist/engine/pattern-library/library.js +2 -1
  244. package/dist/engine/pattern-library/static-schemas.d.ts +1 -0
  245. package/dist/engine/pattern-library/static-schemas.js +9 -0
  246. package/dist/engine/project-app-schema-for-roles.d.ts +2 -0
  247. package/dist/engine/project-app-schema-for-roles.js +6 -4
  248. package/dist/engine/registry-ingest.js +1 -1
  249. package/dist/engine/schema-builder.js +228 -212
  250. package/dist/engine/screen-filter-ops.js +2 -0
  251. package/dist/engine/screen-helpers.js +1 -1
  252. package/dist/engine/screen-kind.d.ts +7 -0
  253. package/dist/engine/screen-kind.js +1 -0
  254. package/dist/engine/strip-disabled-features-from-app-schema.js +58 -17
  255. package/dist/engine/types/feature.d.ts +1 -0
  256. package/dist/engine/types/index.d.ts +4 -3
  257. package/dist/engine/types/screen.d.ts +1 -1
  258. package/dist/engine/types/screen.js +1 -1
  259. package/dist/engine/validate-projection-allowlist.js +2 -1
  260. package/dist/entrypoint/index.d.ts +6 -1
  261. package/dist/entrypoint/index.js +10 -1
  262. package/dist/errors/docs-url.d.ts +1 -1
  263. package/dist/errors/docs-url.js +22 -2
  264. package/dist/es-ops/context.js +1 -1
  265. package/dist/event-store/admin-api.js +1 -1
  266. package/dist/event-store/event-store.d.ts +1 -0
  267. package/dist/event-store/event-store.js +17 -8
  268. package/dist/event-store/provenance-append.js +1 -1
  269. package/dist/files/bind-pending-file-refs.js +3 -3
  270. package/dist/files/create-file-ref-from-bytes.js +7 -1
  271. package/dist/files/create-file-ref.d.ts +3 -0
  272. package/dist/files/create-file-ref.js +11 -2
  273. package/dist/files/file-handle.js +2 -3
  274. package/dist/files/file-routes.d.ts +2 -0
  275. package/dist/files/file-routes.js +40 -10
  276. package/dist/files/provider-resolver.d.ts +1 -0
  277. package/dist/files/provider-resolver.js +1 -1
  278. package/dist/files/record-write-check.d.ts +6 -0
  279. package/dist/files/record-write-check.js +13 -0
  280. package/dist/files/resolve-content-type.js +43 -0
  281. package/dist/files/upload-ledger.d.ts +2 -2
  282. package/dist/files/upload-ledger.js +11 -4
  283. package/dist/i18n/required-surface-keys.js +189 -208
  284. package/dist/internal/stack-support.d.ts +2 -1
  285. package/dist/internal/stack-support.js +1 -1
  286. package/dist/internal/testing.js +1 -1
  287. package/dist/jobs/job-runner-context.d.ts +32 -0
  288. package/dist/jobs/job-runner-context.js +305 -0
  289. package/dist/jobs/job-runner-execution.d.ts +7 -0
  290. package/dist/jobs/job-runner-execution.js +160 -0
  291. package/dist/jobs/job-runner-gates.d.ts +19 -0
  292. package/dist/jobs/job-runner-gates.js +198 -0
  293. package/dist/jobs/job-runner-helpers.d.ts +17 -3
  294. package/dist/jobs/job-runner-helpers.js +61 -2
  295. package/dist/jobs/job-runner-scope.d.ts +44 -0
  296. package/dist/jobs/job-runner-scope.js +102 -0
  297. package/dist/jobs/job-runner-types.d.ts +12 -0
  298. package/dist/jobs/job-runner.d.ts +0 -1
  299. package/dist/jobs/job-runner.js +50 -657
  300. package/dist/observability/escape-hatch-report.d.ts +3 -0
  301. package/dist/observability/escape-hatch-report.js +21 -3
  302. package/dist/pipeline/after-rollback-sink.d.ts +6 -0
  303. package/dist/pipeline/after-rollback-sink.js +32 -0
  304. package/dist/pipeline/cascade-handler.js +165 -138
  305. package/dist/pipeline/dispatch-batch.js +8 -1
  306. package/dist/pipeline/dispatch-query.js +10 -3
  307. package/dist/pipeline/dispatch-shared.d.ts +13 -3
  308. package/dist/pipeline/dispatch-shared.js +62 -710
  309. package/dist/pipeline/dispatch-write.js +296 -189
  310. package/dist/pipeline/event-dispatcher-admin.js +2 -1
  311. package/dist/pipeline/event-dispatcher-delivery.js +6 -6
  312. package/dist/pipeline/event-dispatcher.d.ts +1 -0
  313. package/dist/pipeline/event-dispatcher.js +6 -9
  314. package/dist/pipeline/handler-context-auth.d.ts +9 -0
  315. package/dist/pipeline/handler-context-auth.js +54 -0
  316. package/dist/pipeline/handler-context-config.d.ts +14 -0
  317. package/dist/pipeline/handler-context-config.js +113 -0
  318. package/dist/pipeline/handler-context-db.d.ts +24 -0
  319. package/dist/pipeline/handler-context-db.js +149 -0
  320. package/dist/pipeline/handler-context-dispatch.d.ts +23 -0
  321. package/dist/pipeline/handler-context-dispatch.js +101 -0
  322. package/dist/pipeline/handler-context-events.d.ts +41 -0
  323. package/dist/pipeline/handler-context-events.js +239 -0
  324. package/dist/pipeline/handler-context-notify.d.ts +12 -0
  325. package/dist/pipeline/handler-context-notify.js +55 -0
  326. package/dist/pipeline/handler-context-observability.d.ts +7 -0
  327. package/dist/pipeline/handler-context-observability.js +21 -0
  328. package/dist/pipeline/handler-context-scope.d.ts +48 -0
  329. package/dist/pipeline/handler-context-scope.js +88 -0
  330. package/dist/pipeline/notify-scope.js +12 -1
  331. package/dist/pipeline/pre-handler-gates.js +20 -4
  332. package/dist/pipeline/pre-transaction-reservations.d.ts +1 -0
  333. package/dist/pipeline/pre-transaction-reservations.js +9 -3
  334. package/dist/pipeline/projection-rebuild.js +2 -1
  335. package/dist/pipeline/system-hooks.d.ts +3 -1
  336. package/dist/pipeline/system-hooks.js +55 -6
  337. package/dist/pipeline/write-result-masking.js +13 -10
  338. package/dist/schema-cli/apply-command.d.ts +2 -0
  339. package/dist/schema-cli/apply-command.js +97 -0
  340. package/dist/schema-cli/baseline-command.d.ts +2 -0
  341. package/dist/schema-cli/baseline-command.js +33 -0
  342. package/dist/schema-cli/generate-command.d.ts +2 -0
  343. package/dist/schema-cli/generate-command.js +90 -0
  344. package/dist/schema-cli/shared.d.ts +39 -0
  345. package/dist/schema-cli/shared.js +95 -0
  346. package/dist/schema-cli/status-command.d.ts +2 -0
  347. package/dist/schema-cli/status-command.js +73 -0
  348. package/dist/schema-cli/validate-boot-input.d.ts +5 -0
  349. package/dist/schema-cli/validate-boot-input.js +130 -0
  350. package/dist/schema-cli/validate-command.d.ts +2 -0
  351. package/dist/schema-cli/validate-command.js +130 -0
  352. package/dist/schema-cli.d.ts +2 -18
  353. package/dist/schema-cli.js +47 -604
  354. package/dist/search/meilisearch-adapter.js +2 -1
  355. package/dist/search/reindex-entity.js +15 -5
  356. package/dist/ui-types/index.d.ts +2 -2
  357. package/dist/ui-types/index.js +1 -1
  358. package/dist/upgrade-changelog.d.ts +2 -0
  359. package/dist/upgrade-changelog.js +14 -0
  360. package/dist/upgrade-cli.d.ts +3 -12
  361. package/dist/upgrade-cli.js +171 -197
  362. package/dist/upgrade-marker-parse.d.ts +32 -0
  363. package/dist/upgrade-marker-parse.js +73 -0
  364. package/package.json +9 -6
  365. package/src/changes.json +555 -0
  366. package/src/scripts/codemod/codemod-support.ts +1 -1
  367. package/src/scripts/codemod/db-factory.ts +1 -1
  368. package/src/scripts/codemod/feature-layout.ts +10 -2
  369. package/src/scripts/codemod/object-form.ts +41 -4
  370. package/src/scripts/codemod/package-merge.ts +58 -8
  371. package/src/scripts/codemod/pii-personal-migration.ts +175 -145
  372. package/src/scripts/codemod/testing-injection.ts +122 -42
  373. package/src/scripts/codemod/testing-move.ts +29 -7
  374. package/dist/engine/provider-plugin-context.d.ts +0 -3
  375. package/dist/engine/provider-plugin-context.js +0 -28
@@ -7,6 +7,7 @@ export type EventConsumerBatchHandler = (events: readonly StoredEvent[], ctx: Ap
7
7
  export type EventConsumerErrorPolicy = {
8
8
  readonly skipApplyErrors?: boolean;
9
9
  readonly maxAttempts?: number;
10
+ readonly isTransientError?: (error: unknown) => boolean;
10
11
  };
11
12
  export type EventConsumer = {
12
13
  readonly name: string;
@@ -16,12 +16,10 @@ const PENDING_GAPS_WARN_THRESHOLD = 1000;
16
16
  // Only these say the database socket itself is gone; timeouts, schema or pool errors do not
17
17
  // imply the dedicated LISTEN connection dropped.
18
18
  const CONNECTION_LOSS_CODES = new Set([
19
- "CONNECTION_CLOSED",
20
- "CONNECTION_DESTROYED",
21
- "CONNECTION_ENDED",
22
- "CONNECT_TIMEOUT",
23
- "ECONNREFUSED",
24
- "ECONNRESET",
19
+ "ERR_POSTGRES_CONNECTION_CLOSED",
20
+ "ERR_POSTGRES_CONNECTION_REFUSED",
21
+ "ERR_POSTGRES_CONNECTION_TIMEOUT",
22
+ "ERR_POSTGRES_CONNECTION_FAILED",
25
23
  "57P01",
26
24
  ]);
27
25
  function isConnectionLossError(e) {
@@ -111,7 +109,7 @@ export function createEventDispatcher(options) {
111
109
  let pgSubscription = null;
112
110
  // Bounds how many consumer turns hold a DB transaction at once. Each turn's
113
111
  // db.begin() checks out one pool connection, plus any writes the handler
114
- // itself makes via context.db — the app db pool defaults to postgres-js's
112
+ // itself makes via context.db — the app db pool defaults to Bun.SQL's
115
113
  // max=10 (db/connection.ts, no DATABASE_POOL_MAX override) and is shared
116
114
  // with HTTP request handlers, so turns can't be allowed to claim it all.
117
115
  // A pool no larger than this limit can deadlock: every connection held by
@@ -493,8 +491,7 @@ export function createEventDispatcher(options) {
493
491
  const sub = await options.pgClient.listen(EVENTS_PUBSUB_CHANNEL, runBackgroundPass, () => {
494
492
  // Fires on initial connect and again when the driver's own
495
493
  // re-LISTEN lands after a drop; during an outage that re-LISTEN
496
- // waits in its connection backoff (postgres-js: measured 10-30s
497
- // after the DB returns), polling covers
494
+ // waits in the driver's reconnect backoff, polling covers
498
495
  // delivery meanwhile. Never unlisten()
499
496
  // + listen() by hand: that leaves duplicate listeners.
500
497
  // ponytail: no own LISTEN reconnect, the backoff gap is polled;
@@ -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,23 @@
1
+ import type { WriteResult } from "../engine/types/index.ts";
2
+ import { type AfterRollbackHook } from "./after-rollback-sink.ts";
3
+ import { type HandlerTransactionScope } from "./dispatch-shared.ts";
4
+ import { type AfterCommitHook } from "./dispatcher-utils.ts";
5
+ import type { HandlerContextScope } from "./handler-context-scope.ts";
6
+ export type CommitSinks = {
7
+ readonly bridgeSink: AfterCommitHook[];
8
+ readonly hasLiveCommitSink: boolean;
9
+ readonly notifyCommitSink: AfterCommitHook[] | undefined;
10
+ };
11
+ export declare function createCommitSinks(afterCommitHooks: AfterCommitHook[] | undefined, transactionScope: HandlerTransactionScope, outerNotifySink: AfterCommitHook[] | undefined): CommitSinks;
12
+ export declare function buildDispatchFacet(scope: HandlerContextScope, sinks: CommitSinks): {
13
+ bridge: {
14
+ query: (targetType: string, payload: unknown) => Promise<unknown>;
15
+ queryAs: import("./system-identity-switch.ts").QueryAsFn;
16
+ write: (targetType: string, payload: unknown) => Promise<WriteResult>;
17
+ writeAs: import("./system-identity-switch.ts").WriteAsFn;
18
+ };
19
+ scheduleFragment: {
20
+ scheduleAfterCommit?: ((hook: AfterCommitHook) => void) | undefined;
21
+ scheduleAfterRollback?: ((hook: AfterRollbackHook) => void) | undefined;
22
+ };
23
+ };
@@ -0,0 +1,101 @@
1
+ import { runWithWriteOrigin } from "../api/request-context.js";
2
+ import { runInSavepoint } from "../db/query.js";
3
+ import { writeFailure } from "../errors/index.js";
4
+ import { afterRollbackHooksOf, carrySavepointRollbackHooks, } from "./after-rollback-sink.js";
5
+ import { runBatch, unwrapSingle } from "./dispatch-batch.js";
6
+ import { executeQuery } from "./dispatch-query.js";
7
+ import { perCommandNestedDenied } from "./dispatch-shared.js";
8
+ import { executeWrite } from "./dispatch-write.js";
9
+ import { isFailedWriteResult, NestedWriteRollback, } from "./dispatcher-utils.js";
10
+ import { createGatedIdentitySwitch } from "./system-identity-switch.js";
11
+ export function createCommitSinks(afterCommitHooks, transactionScope, outerNotifySink) {
12
+ // Cross-feature bridge: ctx.query/write share the current tx + afterCommitHooks sink.
13
+ // queryAs/writeAs to anyone but the caller itself needs r.systemScope() or { escapeHatch } (system-identity-switch.ts).
14
+ const bridgeSink = afterCommitHooks ?? [];
15
+ // Query bridges and post-commit contexts get a throwaway sink: hooks pushed there never run.
16
+ const hasLiveCommitSink = afterCommitHooks !== undefined && transactionScope !== "postCommit";
17
+ return {
18
+ bridgeSink,
19
+ hasLiveCommitSink,
20
+ notifyCommitSink: hasLiveCommitSink ? bridgeSink : outerNotifySink,
21
+ };
22
+ }
23
+ // A failed inner write has already appended its event and projections when a later
24
+ // step (e.g. an inTransaction hook) fails, and the outer handler may swallow the
25
+ // failure result. The savepoint makes the failure undo exactly the inner write; the
26
+ // inner afterCommit hooks only reach the outer sink if it succeeded.
27
+ async function runNestedWriteInSavepoint(scope, sinks, asUser, targetType, payload) {
28
+ const { ctx, registry, origin, tx } = scope;
29
+ const { bridgeSink } = sinks;
30
+ if (registry.getWriteHandler(targetType)?.transaction === "perCommand") {
31
+ return writeFailure(perCommandNestedDenied(targetType));
32
+ }
33
+ if (!tx)
34
+ return executeWrite(ctx, targetType, payload, asUser, origin, tx, bridgeSink);
35
+ const innerAfterCommitHooks = [];
36
+ try {
37
+ const result = await runInSavepoint(tx, async (savepointTx) => {
38
+ const innerResult = await executeWrite(ctx, targetType, payload, asUser, origin, savepointTx, // @cast-boundary db-runner savepoint handle is a transaction handle
39
+ innerAfterCommitHooks);
40
+ if (isFailedWriteResult(innerResult))
41
+ throw new NestedWriteRollback(innerResult);
42
+ return innerResult;
43
+ });
44
+ bridgeSink.push(...innerAfterCommitHooks);
45
+ carrySavepointRollbackHooks(innerAfterCommitHooks, bridgeSink, false);
46
+ return result;
47
+ }
48
+ catch (e) {
49
+ carrySavepointRollbackHooks(innerAfterCommitHooks, bridgeSink, true);
50
+ if (e instanceof NestedWriteRollback)
51
+ return e.failure;
52
+ throw e;
53
+ }
54
+ }
55
+ // Savepoints on one connection are not serialized by the driver: two parallel nested writes would
56
+ // interleave their statements and a ROLLBACK TO of the first would also discard the second's rows.
57
+ // Writes nested one level deeper run on their own savepoint handle and therefore their own chain.
58
+ function createSerializedNestedWriter(scope, sinks) {
59
+ let nestedWriteChain = Promise.resolve();
60
+ return (asUser, targetType, payload) => {
61
+ const run = nestedWriteChain.then(() => runNestedWriteInSavepoint(scope, sinks, asUser, targetType, payload));
62
+ nestedWriteChain = run.catch(() => undefined);
63
+ return run;
64
+ };
65
+ }
66
+ export function buildDispatchFacet(scope, sinks) {
67
+ const { ctx, type, user, origin, tx, transactionScope, hasIdentitySwitchGrant, identitySwitchAudit, } = scope;
68
+ const scheduleAfterCommit = (hook) => {
69
+ sinks.bridgeSink.push(hook);
70
+ };
71
+ const scheduleAfterRollback = (hook) => {
72
+ afterRollbackHooksOf(sinks.bridgeSink).push(() => runWithWriteOrigin(origin, hook));
73
+ };
74
+ const executeNestedWriteInSavepoint = createSerializedNestedWriter(scope, sinks);
75
+ // Without an open transaction (post-commit hooks, stream/query handlers, perCommand handlers) a bare
76
+ // executeWrite would auto-commit event append, projections and inTransaction hooks separately and its
77
+ // afterCommit hooks would land in a sink nobody flushes. A batch-of-one gives the write its own
78
+ // transaction and its own flush.
79
+ const writeAsUser = async (asUser, targetType, payload) => transactionScope === "inTransaction"
80
+ ? executeNestedWriteInSavepoint(asUser, targetType, payload)
81
+ : unwrapSingle(await runBatch(ctx, [{ type: targetType, payload }], asUser, undefined, origin));
82
+ const identitySwitch = createGatedIdentitySwitch(`handler "${type}"`, user, hasIdentitySwitchGrant, {
83
+ // Inherits the caller's origin, so switching to SYSTEM cannot shed an anonymous root.
84
+ queryAs: (asUser, targetType, payload) => executeQuery(ctx, targetType, payload, asUser, origin, tx, sinks.notifyCommitSink), // @wrapper-known semantic-alias
85
+ writeAs: writeAsUser,
86
+ }, identitySwitchAudit);
87
+ return {
88
+ bridge: {
89
+ query: (targetType, payload) => executeQuery(ctx, targetType, payload, user, origin, tx, sinks.notifyCommitSink), // @wrapper-known semantic-alias
90
+ queryAs: identitySwitch.queryAs,
91
+ write: (targetType, payload) => writeAsUser(user, targetType, payload),
92
+ writeAs: identitySwitch.writeAs,
93
+ },
94
+ scheduleFragment: {
95
+ ...(sinks.hasLiveCommitSink && { scheduleAfterCommit }),
96
+ // perCommand and no-tx paths have earlier commands already committed: nothing to compensate on rollback.
97
+ ...(sinks.hasLiveCommitSink &&
98
+ transactionScope === "inTransaction" && { scheduleAfterRollback }),
99
+ },
100
+ };
101
+ }
@@ -0,0 +1,41 @@
1
+ import type { AggregateStreamHandle, AppendEventArgs, AppendEventFn, FetchForWritingArgs, HandlerContext } 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?: Parameters<HandlerContext["loadAggregate"]>[1]) => Promise<readonly StoredEvent[]>;
24
+ archiveStream: (aggregateId: string, archiveArgs: {
25
+ readonly aggregateType: string;
26
+ readonly reason?: string;
27
+ }) => Promise<void>;
28
+ restoreStream: (aggregateId: string) => Promise<void>;
29
+ isStreamArchived: (aggregateId: string) => Promise<boolean>;
30
+ snapshotAggregate: (snapshotArgs: {
31
+ readonly aggregateId: string;
32
+ readonly aggregateType: string;
33
+ readonly version: number;
34
+ readonly state: Record<string, unknown>;
35
+ readonly snapshotVersion?: number;
36
+ }) => Promise<void>;
37
+ loadAggregateWithSnapshot: <TState extends Record<string, unknown>>(aggregateId: string, reducer: SnapshotReducer<TState>, initial: TState, loadOptions?: Omit<LoadAggregateWithSnapshotOptions, "upcastEvent" | "entityEventVersionOf">) => Promise<LoadAggregateWithSnapshotResult<TState>>;
38
+ queryProjection: <T = Record<string, unknown>>(qualifiedName: string, options?: {
39
+ readonly unsafeAllTenants?: boolean;
40
+ }) => Promise<readonly T[]>;
41
+ };