@cosmicdrift/kumiko-framework 0.388.0 → 0.390.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 (711) hide show
  1. package/AGENTS.md +12 -4
  2. package/README.md +1 -1
  3. package/agents/examples/apex-surface-auth.md +2 -3
  4. package/agents/examples/api-worker-split.md +9 -2
  5. package/agents/examples/auth-claims.md +1 -1
  6. package/agents/examples/cross-feature-events.md +12 -12
  7. package/agents/examples/crypto-shredding-hr.md +2 -2
  8. package/agents/examples/encrypted-tenant-config.md +1 -1
  9. package/agents/examples/error-contract.md +12 -7
  10. package/agents/examples/event-sourcing.md +29 -29
  11. package/agents/examples/files-post-processing.md +10 -8
  12. package/agents/examples/golden-path.md +13 -1
  13. package/agents/examples/inbound-mail-triage.md +1 -0
  14. package/agents/examples/lane-routing.md +2 -1
  15. package/agents/examples/legal-pages.md +2 -2
  16. package/agents/examples/lifecycle-hooks.md +55 -2
  17. package/agents/examples/list-table-options.md +466 -0
  18. package/agents/examples/managed-config.md +4 -4
  19. package/agents/examples/managed-pages.md +1 -1
  20. package/agents/examples/pdf-audit-export.md +4 -2
  21. package/agents/examples/relations.md +19 -4
  22. package/agents/examples/search.md +4 -4
  23. package/agents/examples/secrets-demo.md +9 -6
  24. package/agents/examples/state-machine.md +5 -4
  25. package/agents/examples/tenant-isolation.md +73 -1
  26. package/agents/examples/tenant-settings.md +1 -1
  27. package/agents/examples/user-data-rights.md +3 -6
  28. package/agents/examples/workflow-engine.md +53 -47
  29. package/agents/examples.md +5 -4
  30. package/agents/features.md +1 -1
  31. package/agents/widgets.md +1 -0
  32. package/dist/api/api-constants.d.ts +1 -0
  33. package/dist/api/api-constants.js +2 -0
  34. package/dist/api/auth-middleware.js +1 -1
  35. package/dist/api/auth-routes-invite.js +2 -1
  36. package/dist/api/auth-routes-shared.d.ts +2 -0
  37. package/dist/api/auth-routes-shared.js +5 -0
  38. package/dist/api/client-error-route.js +2 -3
  39. package/dist/api/entity-read-access.d.ts +4 -1
  40. package/dist/api/entity-read-access.js +14 -0
  41. package/dist/api/export/export-cells-reference.d.ts +9 -0
  42. package/dist/api/export/export-cells-reference.js +21 -0
  43. package/dist/api/export/export-cells.d.ts +11 -0
  44. package/dist/api/export/export-cells.js +136 -0
  45. package/dist/api/export/export-ciphertext-guard.d.ts +2 -0
  46. package/dist/api/export/export-ciphertext-guard.js +27 -0
  47. package/dist/api/export/export-columns.d.ts +31 -0
  48. package/dist/api/export/export-columns.js +85 -0
  49. package/dist/api/export/export-errors.d.ts +4 -0
  50. package/dist/api/export/export-errors.js +14 -0
  51. package/dist/api/export/export-route.d.ts +4 -0
  52. package/dist/api/export/export-route.js +75 -0
  53. package/dist/api/export/export-rows.d.ts +13 -0
  54. package/dist/api/export/export-rows.js +62 -0
  55. package/dist/api/export/export-screen.d.ts +13 -0
  56. package/dist/api/export/export-screen.js +47 -0
  57. package/dist/api/export/export-xlsx-writer.d.ts +4 -0
  58. package/dist/api/export/export-xlsx-writer.js +21 -0
  59. package/dist/api/extra-route.d.ts +7 -1
  60. package/dist/api/index.d.ts +23 -27
  61. package/dist/api/index.js +13 -15
  62. package/dist/api/jwt.js +1 -1
  63. package/dist/api/observability-middleware.d.ts +1 -1
  64. package/dist/api/observability-middleware.js +2 -1
  65. package/dist/api/pii-leak-guard.d.ts +3 -0
  66. package/dist/api/pii-leak-guard.js +3 -2
  67. package/dist/api/request-context-internals.d.ts +31 -0
  68. package/dist/api/request-context-internals.js +11 -0
  69. package/dist/api/request-context-scopes.d.ts +14 -0
  70. package/dist/api/request-context-scopes.js +69 -0
  71. package/dist/api/request-context.d.ts +1 -40
  72. package/dist/api/request-context.js +22 -51
  73. package/dist/api/request-id-middleware.d.ts +1 -1
  74. package/dist/api/route-registrars.d.ts +2 -1
  75. package/dist/api/route-registrars.js +1 -1
  76. package/dist/api/routes.d.ts +16 -0
  77. package/dist/api/routes.js +9 -5
  78. package/dist/api/server-boot-checks.d.ts +1 -1
  79. package/dist/api/server-boot-checks.js +2 -2
  80. package/dist/api/server-event-dispatcher.d.ts +6 -1
  81. package/dist/api/server-event-dispatcher.js +3 -4
  82. package/dist/api/server-extra-routes.d.ts +1 -1
  83. package/dist/api/server-extra-routes.js +5 -1
  84. package/dist/api/server-middleware.js +3 -1
  85. package/dist/api/server-routes.d.ts +3 -2
  86. package/dist/api/server-routes.js +8 -2
  87. package/dist/api/server-wiring.js +8 -4
  88. package/dist/api/server.d.ts +2 -1
  89. package/dist/api/sse-route.d.ts +3 -1
  90. package/dist/api/sse-route.js +11 -1
  91. package/dist/api/tenant-scoped-secrets.d.ts +2 -0
  92. package/dist/api/tenant-scoped-secrets.js +13 -0
  93. package/dist/api/websocket-route.d.ts +1 -1
  94. package/dist/auth/index.d.ts +0 -1
  95. package/dist/auth/roles.d.ts +0 -5
  96. package/dist/compliance/duration-spec.d.ts +0 -1
  97. package/dist/compliance/duration-spec.js +0 -5
  98. package/dist/compliance/index.d.ts +4 -4
  99. package/dist/compliance/index.js +2 -2
  100. package/dist/compliance/profiles.d.ts +0 -15
  101. package/dist/compliance/profiles.js +0 -23
  102. package/dist/config/index.d.ts +11 -9
  103. package/dist/config/index.js +13 -1
  104. package/dist/config/internals.d.ts +9 -0
  105. package/dist/config/internals.js +1 -0
  106. package/dist/consumer-cli-internals.d.ts +4 -0
  107. package/dist/consumer-cli-internals.js +1 -0
  108. package/dist/consumer-cli.d.ts +1 -4
  109. package/dist/consumer-cli.js +5 -1
  110. package/dist/crypto/event-pii.js +1 -1
  111. package/dist/crypto/index.d.ts +8 -9
  112. package/dist/crypto/index.js +7 -8
  113. package/dist/crypto/pg-kms-adapter.d.ts +1 -0
  114. package/dist/crypto/pg-kms-adapter.js +17 -18
  115. package/dist/db/aggregate-sql.d.ts +2 -0
  116. package/dist/db/aggregate-sql.js +10 -0
  117. package/dist/db/apply-entity-event.d.ts +1 -1
  118. package/dist/db/apply-entity-event.js +15 -47
  119. package/dist/db/blind-index-cleanup.js +91 -77
  120. package/dist/db/closed-connection.js +12 -19
  121. package/dist/db/collect-table-metas.js +1 -1
  122. package/dist/db/compound-types.d.ts +1 -1
  123. package/dist/db/compound-types.js +0 -7
  124. package/dist/db/config-seed.d.ts +2 -1
  125. package/dist/db/config-seed.js +6 -1
  126. package/dist/db/connection.d.ts +7 -6
  127. package/dist/db/connection.js +43 -24
  128. package/dist/db/cross-tenant-reads.d.ts +1 -1
  129. package/dist/db/cross-tenant-reads.js +1 -1
  130. package/dist/db/dialect.d.ts +0 -5
  131. package/dist/db/dialect.js +0 -3
  132. package/dist/db/driver-row-coercion.js +4 -4
  133. package/dist/db/driver-value-preparation.js +6 -4
  134. package/dist/db/eagerload.d.ts +2 -1
  135. package/dist/db/entity-field-encryption.d.ts +1 -1
  136. package/dist/db/entity-table-meta.d.ts +1 -1
  137. package/dist/db/event-store-executor-context.d.ts +3 -1
  138. package/dist/db/event-store-executor-context.js +4 -3
  139. package/dist/db/event-store-executor-read-aggregate-currencies.d.ts +17 -0
  140. package/dist/db/event-store-executor-read-aggregate-currencies.js +40 -0
  141. package/dist/db/event-store-executor-read-aggregate.js +36 -5
  142. package/dist/db/event-store-executor-read-filter.d.ts +1 -1
  143. package/dist/db/event-store-executor-read-filter.js +6 -3
  144. package/dist/db/event-store-executor-read-rows.d.ts +2 -1
  145. package/dist/db/event-store-executor-read-search.d.ts +2 -1
  146. package/dist/db/event-store-executor-read-sort.d.ts +2 -1
  147. package/dist/db/event-store-executor-read.js +2 -2
  148. package/dist/db/event-store-executor-write-atomic.d.ts +4 -0
  149. package/dist/db/event-store-executor-write-atomic.js +42 -0
  150. package/dist/db/event-store-executor-write-steps.d.ts +3 -5
  151. package/dist/db/event-store-executor-write-steps.js +0 -5
  152. package/dist/db/event-store-executor-write.js +22 -19
  153. package/dist/db/event-store-executor.d.ts +1 -1
  154. package/dist/db/event-store-executor.js +2 -1
  155. package/dist/db/executor-managed-tables.d.ts +7 -1
  156. package/dist/db/executor-managed-tables.js +12 -3
  157. package/dist/db/index.d.ts +29 -28
  158. package/dist/db/index.js +22 -20
  159. package/dist/db/known-table-names.d.ts +6 -0
  160. package/dist/db/known-table-names.js +21 -0
  161. package/dist/db/list-aggregate-labels.d.ts +1 -1
  162. package/dist/db/located-timestamp.d.ts +1 -1
  163. package/dist/db/located-timestamp.js +0 -5
  164. package/dist/db/migrate-generator.d.ts +17 -1
  165. package/dist/db/migrate-generator.js +50 -20
  166. package/dist/db/migrate-primary-key.d.ts +12 -0
  167. package/dist/db/migrate-primary-key.js +45 -0
  168. package/dist/db/migrate-rls-diff.d.ts +20 -0
  169. package/dist/db/migrate-rls-diff.js +61 -0
  170. package/dist/db/migrate-runner.js +7 -3
  171. package/dist/db/money.d.ts +1 -1
  172. package/dist/db/money.js +1 -1
  173. package/dist/db/parent-ref-clause.d.ts +2 -1
  174. package/dist/db/pg-error.d.ts +2 -0
  175. package/dist/db/pg-error.js +37 -21
  176. package/dist/db/queries/backfill-pii.js +3 -6
  177. package/dist/db/queries/database-now.js +2 -1
  178. package/dist/db/queries/ddl.d.ts +3 -0
  179. package/dist/db/queries/ddl.js +34 -1
  180. package/dist/db/queries/event-store.js +4 -2
  181. package/dist/db/queries/event-tenants.d.ts +2 -0
  182. package/dist/db/queries/event-tenants.js +17 -0
  183. package/dist/db/queries/rebuild-rls-guard.d.ts +2 -0
  184. package/dist/db/queries/rebuild-rls-guard.js +29 -0
  185. package/dist/db/queries/rls-boot-catalog.d.ts +16 -0
  186. package/dist/db/queries/rls-boot-catalog.js +39 -0
  187. package/dist/db/queries/rls-catalog.d.ts +6 -0
  188. package/dist/db/queries/rls-catalog.js +82 -0
  189. package/dist/db/queries/rls-scope.d.ts +3 -0
  190. package/dist/db/queries/rls-scope.js +17 -0
  191. package/dist/db/queries/rls-shape.d.ts +10 -0
  192. package/dist/db/queries/rls-shape.js +42 -0
  193. package/dist/db/queries/session-advisory-lock.d.ts +2 -0
  194. package/dist/db/queries/session-advisory-lock.js +23 -0
  195. package/dist/db/queries/shadow-swap.d.ts +1 -2
  196. package/dist/db/queries/shadow-swap.js +11 -28
  197. package/dist/db/queries/test-stack.d.ts +4 -1
  198. package/dist/db/queries/test-stack.js +83 -2
  199. package/dist/db/query.d.ts +14 -0
  200. package/dist/db/query.js +77 -48
  201. package/dist/db/reference-data.d.ts +1 -1
  202. package/dist/db/reference-data.js +20 -3
  203. package/dist/db/render-ddl.d.ts +6 -2
  204. package/dist/db/render-ddl.js +7 -7
  205. package/dist/db/render-rls-ddl.d.ts +35 -0
  206. package/dist/db/render-rls-ddl.js +127 -0
  207. package/dist/db/replay-migration-sql.d.ts +3 -1
  208. package/dist/db/replay-migration-sql.js +22 -5
  209. package/dist/db/replay-rls.d.ts +25 -0
  210. package/dist/db/replay-rls.js +156 -0
  211. package/dist/db/row-level-security-boot-check.d.ts +5 -0
  212. package/dist/db/row-level-security-boot-check.js +85 -0
  213. package/dist/db/row-level-security-connection.d.ts +3 -0
  214. package/dist/db/row-level-security-connection.js +213 -0
  215. package/dist/db/row-level-security-scope.d.ts +33 -0
  216. package/dist/db/row-level-security-scope.js +127 -0
  217. package/dist/db/scope-tenant-db-to-tenant.d.ts +3 -0
  218. package/dist/db/scope-tenant-db-to-tenant.js +14 -0
  219. package/dist/db/sql-identifier.d.ts +1 -0
  220. package/dist/db/sql-identifier.js +3 -0
  221. package/dist/db/statement-timeout.js +2 -1
  222. package/dist/db/table-builder.d.ts +2 -1
  223. package/dist/db/table-builder.js +1 -1
  224. package/dist/db/table-info.js +1 -1
  225. package/dist/db/table-push.d.ts +11 -2
  226. package/dist/db/table-push.js +15 -2
  227. package/dist/db/tenant-db-runner.d.ts +3 -1
  228. package/dist/db/tenant-db-runner.js +4 -3
  229. package/dist/db/tenant-db-scoped-rebind.d.ts +10 -0
  230. package/dist/db/tenant-db-scoped-rebind.js +33 -0
  231. package/dist/db/tenant-db-statement-runner.d.ts +13 -0
  232. package/dist/db/tenant-db-statement-runner.js +48 -0
  233. package/dist/db/tenant-db-upsert.d.ts +5 -0
  234. package/dist/db/tenant-db-upsert.js +91 -0
  235. package/dist/db/tenant-db.d.ts +17 -12
  236. package/dist/db/tenant-db.js +60 -121
  237. package/dist/db/tenant-id-filter.d.ts +3 -0
  238. package/dist/db/tenant-id-filter.js +30 -0
  239. package/dist/db/transaction-serialization.d.ts +6 -0
  240. package/dist/db/transaction-serialization.js +60 -0
  241. package/dist/derivatives/delete-stored-file.js +1 -1
  242. package/dist/derivatives/index.d.ts +2 -5
  243. package/dist/derivatives/index.js +2 -3
  244. package/dist/engine/access.d.ts +2 -1
  245. package/dist/engine/access.js +1 -1
  246. package/dist/engine/boot-validator/bundled-lists-table.d.ts +2 -0
  247. package/dist/engine/boot-validator/bundled-lists-table.js +36 -0
  248. package/dist/engine/boot-validator/entity-handler.d.ts +2 -1
  249. package/dist/engine/boot-validator/entity-handler.js +1 -1
  250. package/dist/engine/boot-validator/entity-list-aggregate-options.d.ts +3 -0
  251. package/dist/engine/boot-validator/entity-list-aggregate-options.js +23 -0
  252. package/dist/engine/boot-validator/list-bulk-actions.d.ts +3 -0
  253. package/dist/engine/boot-validator/list-bulk-actions.js +49 -0
  254. package/dist/engine/boot-validator/list-column-layout.d.ts +8 -0
  255. package/dist/engine/boot-validator/list-column-layout.js +41 -0
  256. package/dist/engine/boot-validator/list-table-aggregate-options.d.ts +24 -0
  257. package/dist/engine/boot-validator/list-table-aggregate-options.js +145 -0
  258. package/dist/engine/boot-validator/list-table-aggregate-query.d.ts +11 -0
  259. package/dist/engine/boot-validator/list-table-aggregate-query.js +31 -0
  260. package/dist/engine/boot-validator/list-table-export-options.d.ts +1 -0
  261. package/dist/engine/boot-validator/list-table-export-options.js +27 -0
  262. package/dist/engine/boot-validator/list-table-options.d.ts +3 -0
  263. package/dist/engine/boot-validator/list-table-options.js +77 -0
  264. package/dist/engine/boot-validator/list-table-reject-unknown-keys.d.ts +1 -0
  265. package/dist/engine/boot-validator/list-table-reject-unknown-keys.js +7 -0
  266. package/dist/engine/boot-validator/nav.d.ts +3 -1
  267. package/dist/engine/boot-validator/nav.js +1 -1
  268. package/dist/engine/boot-validator/ownership.d.ts +2 -1
  269. package/dist/engine/boot-validator/parent-ref.d.ts +2 -1
  270. package/dist/engine/boot-validator/projection-list-screens.d.ts +2 -1
  271. package/dist/engine/boot-validator/projection-list-screens.js +3 -13
  272. package/dist/engine/boot-validator/query-refs.d.ts +2 -1
  273. package/dist/engine/boot-validator/related-list-table-rules.d.ts +4 -0
  274. package/dist/engine/boot-validator/related-list-table-rules.js +42 -0
  275. package/dist/engine/boot-validator/screens-forms.d.ts +2 -1
  276. package/dist/engine/boot-validator/screens-kind-entity-list.js +7 -0
  277. package/dist/engine/boot-validator/screens-kind-projection-list.js +4 -0
  278. package/dist/engine/boot-validator/screens-kind-shared.d.ts +3 -1
  279. package/dist/engine/boot-validator/screens-lists.js +12 -2
  280. package/dist/engine/boot-validator/screens.d.ts +2 -1
  281. package/dist/engine/boot-validator/workspaces.d.ts +3 -1
  282. package/dist/engine/bridge-stub.d.ts +5 -2
  283. package/dist/engine/bridge-stub.js +39 -24
  284. package/dist/engine/build-app-schema.d.ts +2 -1
  285. package/dist/engine/build-app-schema.js +9 -1
  286. package/dist/engine/build-config-feature-schema.js +6 -1
  287. package/dist/engine/config-helpers.d.ts +2 -2
  288. package/dist/engine/config-helpers.js +0 -9
  289. package/dist/engine/constants.d.ts +0 -10
  290. package/dist/engine/constants.js +0 -8
  291. package/dist/engine/create-app.js +1 -1
  292. package/dist/engine/define-feature.d.ts +2 -1
  293. package/dist/engine/define-feature.js +5 -2
  294. package/dist/engine/define-handler.d.ts +1 -1
  295. package/dist/engine/define-step.d.ts +0 -1
  296. package/dist/engine/define-step.js +0 -3
  297. package/dist/engine/embedded-derived.d.ts +1 -1
  298. package/dist/engine/entity-handlers.d.ts +2 -1
  299. package/dist/engine/entity-handlers.js +7 -7
  300. package/dist/engine/entity-list-runtime-options.js +3 -1
  301. package/dist/engine/entity-list-searchability.d.ts +2 -1
  302. package/dist/engine/event-def.d.ts +2 -1
  303. package/dist/engine/event-helpers.d.ts +3 -1
  304. package/dist/engine/event-helpers.js +3 -3
  305. package/dist/engine/extension-names.d.ts +2 -8
  306. package/dist/engine/extension-names.js +2 -2
  307. package/dist/engine/extension-selector-plugins.d.ts +1 -1
  308. package/dist/engine/extensions/storage-provider.d.ts +1 -2
  309. package/dist/engine/extensions/user-data.d.ts +0 -8
  310. package/dist/engine/factories.d.ts +2 -1
  311. package/dist/engine/feature-builder-state.d.ts +9 -1
  312. package/dist/engine/feature-builder-state.js +1 -0
  313. package/dist/engine/feature-changelog.d.ts +1 -1
  314. package/dist/engine/feature-config-events-jobs.d.ts +4 -1
  315. package/dist/engine/feature-entity-handlers.d.ts +5 -1
  316. package/dist/engine/feature-manifest.d.ts +1 -0
  317. package/dist/engine/feature-manifest.js +2 -1
  318. package/dist/engine/feature-ui-extensions.d.ts +7 -1
  319. package/dist/engine/feature-ui-extensions.js +6 -4
  320. package/dist/engine/field-access.d.ts +3 -1
  321. package/dist/engine/field-access.js +12 -0
  322. package/dist/engine/handler-field-references.d.ts +0 -4
  323. package/dist/engine/index.d.ts +58 -55
  324. package/dist/engine/index.js +23 -28
  325. package/dist/engine/instruction-fields.d.ts +1 -1
  326. package/dist/engine/personal-data-fields.d.ts +1 -1
  327. package/dist/engine/qualified-name.d.ts +0 -1
  328. package/dist/engine/read-claim.d.ts +2 -1
  329. package/dist/engine/registry-facade.js +0 -3
  330. package/dist/engine/registry-ingest.d.ts +2 -1
  331. package/dist/engine/registry-ingest.js +1 -2
  332. package/dist/engine/registry-state.d.ts +12 -2
  333. package/dist/engine/registry-state.js +2 -3
  334. package/dist/engine/registry-validate.d.ts +3 -1
  335. package/dist/engine/registry-validate.js +7 -7
  336. package/dist/engine/run-in.d.ts +1 -1
  337. package/dist/engine/run-in.js +0 -1
  338. package/dist/engine/schema-builder.d.ts +1 -1
  339. package/dist/engine/schema-builder.js +1 -1
  340. package/dist/engine/screen-filter-ops.d.ts +2 -1
  341. package/dist/engine/screen-filter-ops.js +2 -5
  342. package/dist/engine/soft-delete-cleanup.d.ts +1 -1
  343. package/dist/engine/soft-delete-cleanup.js +4 -4
  344. package/dist/engine/state-machine.js +2 -1
  345. package/dist/engine/steps/_read-source.js +1 -1
  346. package/dist/engine/steps/aggregate-append-event.js +2 -2
  347. package/dist/engine/steps/call-feature.js +2 -2
  348. package/dist/engine/steps/mail-send.js +1 -1
  349. package/dist/engine/steps/retry.js +1 -1
  350. package/dist/engine/steps/wait-for-event.js +1 -1
  351. package/dist/engine/steps/wait.js +1 -1
  352. package/dist/engine/steps/webhook-send.js +1 -1
  353. package/dist/engine/strip-disabled-features-from-app-schema.js +7 -3
  354. package/dist/engine/template-variables.d.ts +2 -1
  355. package/dist/engine/transfer-adjacency.d.ts +1 -1
  356. package/dist/engine/types/feature.d.ts +1 -0
  357. package/dist/engine/types/index.d.ts +8 -30
  358. package/dist/engine/types/index.js +2 -10
  359. package/dist/engine/types/screen.d.ts +1 -1
  360. package/dist/engine/types/screen.js +1 -1
  361. package/dist/engine/validation.d.ts +2 -1
  362. package/dist/entrypoint/index.d.ts +9 -35
  363. package/dist/entrypoint/index.js +10 -1
  364. package/dist/entrypoint/internals.d.ts +40 -0
  365. package/dist/entrypoint/internals.js +1 -0
  366. package/dist/entrypoint/row-level-security-mode-check.d.ts +3 -0
  367. package/dist/entrypoint/row-level-security-mode-check.js +19 -0
  368. package/dist/env/dry-run-internals.d.ts +10 -0
  369. package/dist/env/dry-run-internals.js +1 -0
  370. package/dist/env/dry-run.d.ts +1 -10
  371. package/dist/env/dry-run.js +2 -1
  372. package/dist/env/index.d.ts +1 -85
  373. package/dist/env/index.js +6 -50
  374. package/dist/env/internals.d.ts +87 -0
  375. package/dist/env/internals.js +53 -0
  376. package/dist/errors/classes.js +2 -2
  377. package/dist/errors/index.d.ts +5 -14
  378. package/dist/errors/index.js +4 -9
  379. package/dist/errors/member-resolution.js +1 -1
  380. package/dist/errors/reasons.d.ts +0 -1
  381. package/dist/errors/reasons.js +7 -7
  382. package/dist/errors/serialize.d.ts +0 -18
  383. package/dist/errors/serialize.js +0 -27
  384. package/dist/es-ops/index.d.ts +4 -4
  385. package/dist/es-ops/index.js +2 -2
  386. package/dist/es-ops/operations-schema.d.ts +1 -1
  387. package/dist/es-ops/types.d.ts +4 -5
  388. package/dist/event-store/admin-api-internals.d.ts +14 -0
  389. package/dist/event-store/admin-api-internals.js +1 -0
  390. package/dist/event-store/admin-api.d.ts +1 -14
  391. package/dist/event-store/admin-api.js +11 -6
  392. package/dist/event-store/archive.js +4 -2
  393. package/dist/event-store/errors.js +2 -2
  394. package/dist/event-store/event-store.d.ts +1 -0
  395. package/dist/event-store/event-store.js +42 -32
  396. package/dist/event-store/events-schema.js +4 -1
  397. package/dist/event-store/index.d.ts +11 -13
  398. package/dist/event-store/index.js +7 -9
  399. package/dist/event-store/provenance-append.js +1 -1
  400. package/dist/event-store/rebuild-dead-letter.js +4 -2
  401. package/dist/event-store/snapshot.js +4 -3
  402. package/dist/event-store/stream-scope.d.ts +2 -0
  403. package/dist/event-store/stream-scope.js +6 -0
  404. package/dist/event-store/upcaster-dead-letter.js +4 -2
  405. package/dist/event-store/upcaster.d.ts +2 -1
  406. package/dist/files/action-form-upload.d.ts +2 -1
  407. package/dist/files/action-form-upload.js +1 -1
  408. package/dist/files/assert-file-refs-belong-to-tenant.d.ts +2 -1
  409. package/dist/files/assert-file-refs-belong-to-tenant.js +1 -1
  410. package/dist/files/bind-pending-file-refs.d.ts +2 -1
  411. package/dist/files/bind-pending-file-refs.js +2 -1
  412. package/dist/files/bind-referenced-files.write.js +1 -1
  413. package/dist/files/create-file-ref-from-bytes.d.ts +6 -3
  414. package/dist/files/create-file-ref-from-bytes.js +6 -3
  415. package/dist/files/create-file-ref.d.ts +6 -2
  416. package/dist/files/create-file-ref.js +13 -4
  417. package/dist/files/dispose-removed-file-refs.d.ts +2 -1
  418. package/dist/files/dispose-removed-file-refs.js +1 -1
  419. package/dist/files/feature.js +2 -2
  420. package/dist/files/file-handle.js +1 -1
  421. package/dist/files/file-routes.d.ts +3 -1
  422. package/dist/files/file-routes.js +41 -4
  423. package/dist/files/index.d.ts +11 -13
  424. package/dist/files/index.js +5 -7
  425. package/dist/files/load-save-file-refs.d.ts +2 -1
  426. package/dist/files/load-save-file-refs.js +1 -1
  427. package/dist/files/provider-resolver.d.ts +6 -4
  428. package/dist/files/provider-resolver.js +5 -5
  429. package/dist/files/record-write-check.d.ts +6 -0
  430. package/dist/files/record-write-check.js +13 -0
  431. package/dist/files/storage-tracking.js +4 -2
  432. package/dist/files/sweep-orphaned-uploads.d.ts +1 -1
  433. package/dist/files/sweep-orphaned-uploads.js +1 -1
  434. package/dist/files/upload-ledger.d.ts +2 -2
  435. package/dist/files/upload-ledger.js +4 -4
  436. package/dist/i18n/index.d.ts +2 -8
  437. package/dist/i18n/index.js +1 -1
  438. package/dist/i18n/internals.d.ts +7 -0
  439. package/dist/i18n/internals.js +1 -0
  440. package/dist/i18n/required-surface-keys.d.ts +4 -1
  441. package/dist/i18n/required-surface-keys.js +7 -2
  442. package/dist/internal/row-level-security.d.ts +3 -0
  443. package/dist/internal/row-level-security.js +3 -0
  444. package/dist/internal/stack-support.d.ts +1 -1
  445. package/dist/internal/stack-support.js +1 -1
  446. package/dist/internal/testing.d.ts +1 -0
  447. package/dist/internal/testing.js +10 -6
  448. package/dist/jobs/index.d.ts +1 -1
  449. package/dist/jobs/job-append-event.d.ts +12 -0
  450. package/dist/jobs/job-append-event.js +30 -0
  451. package/dist/jobs/job-runner-context.d.ts +2 -2
  452. package/dist/jobs/job-runner-context.js +46 -16
  453. package/dist/jobs/job-runner-execution.d.ts +1 -1
  454. package/dist/jobs/job-runner-execution.js +6 -4
  455. package/dist/jobs/job-runner-gates.d.ts +1 -1
  456. package/dist/jobs/job-runner-gates.js +0 -1
  457. package/dist/jobs/job-runner-helpers.d.ts +11 -4
  458. package/dist/jobs/job-runner-helpers.js +10 -1
  459. package/dist/jobs/job-runner-scope.d.ts +5 -2
  460. package/dist/jobs/job-runner-scope.js +6 -6
  461. package/dist/jobs/job-runner-types.d.ts +2 -1
  462. package/dist/jobs/job-runner.js +22 -6
  463. package/dist/jobs/max-per-tenant.d.ts +1 -1
  464. package/dist/logging/index.d.ts +1 -2
  465. package/dist/logging/index.js +1 -1
  466. package/dist/logging/pino-logger.js +1 -1
  467. package/dist/logging/utils.d.ts +1 -0
  468. package/dist/logging/utils.js +6 -0
  469. package/dist/migrations/index.d.ts +3 -3
  470. package/dist/migrations/index.js +3 -3
  471. package/dist/migrations/pending-rebuilds.d.ts +1 -1
  472. package/dist/migrations/pending-rebuilds.js +1 -1
  473. package/dist/migrations/pii-event-backfill.js +4 -1
  474. package/dist/observability/escape-hatch-report.d.ts +2 -1
  475. package/dist/observability/escape-hatch-report.js +9 -2
  476. package/dist/observability/index.d.ts +7 -15
  477. package/dist/observability/index.js +4 -12
  478. package/dist/observability/metric-validator.js +1 -1
  479. package/dist/observability/metrics-handle.d.ts +0 -1
  480. package/dist/observability/metrics-handle.js +2 -15
  481. package/dist/observability/recording-tracer.d.ts +1 -2
  482. package/dist/observability/recording-tracer.js +0 -6
  483. package/dist/observability/sensitive-filter.d.ts +0 -2
  484. package/dist/observability/sensitive-filter.js +0 -29
  485. package/dist/pipeline/active-membership.d.ts +2 -1
  486. package/dist/pipeline/active-membership.js +6 -2
  487. package/dist/pipeline/after-rollback-sink.d.ts +6 -0
  488. package/dist/pipeline/after-rollback-sink.js +32 -0
  489. package/dist/pipeline/append-event-core.d.ts +2 -1
  490. package/dist/pipeline/append-event-core.js +2 -2
  491. package/dist/pipeline/attach-outside-transaction.d.ts +3 -0
  492. package/dist/pipeline/attach-outside-transaction.js +13 -0
  493. package/dist/pipeline/auth-claims-resolver.d.ts +2 -1
  494. package/dist/pipeline/cascade-handler.d.ts +2 -1
  495. package/dist/pipeline/cascade-handler.js +3 -2
  496. package/dist/pipeline/config-accessor-factory.d.ts +1 -1
  497. package/dist/pipeline/config-accessor-factory.js +1 -1
  498. package/dist/pipeline/context-services.d.ts +56 -0
  499. package/dist/pipeline/context-services.js +73 -0
  500. package/dist/pipeline/dispatch-batch.d.ts +2 -1
  501. package/dist/pipeline/dispatch-batch.js +39 -19
  502. package/dist/pipeline/dispatch-query.js +5 -4
  503. package/dist/pipeline/dispatch-scope.d.ts +6 -0
  504. package/dist/pipeline/dispatch-scope.js +27 -0
  505. package/dist/pipeline/dispatch-shared.d.ts +13 -9
  506. package/dist/pipeline/dispatch-shared.js +75 -194
  507. package/dist/pipeline/dispatch-stream.js +8 -4
  508. package/dist/pipeline/dispatch-write.d.ts +2 -1
  509. package/dist/pipeline/dispatch-write.js +10 -8
  510. package/dist/pipeline/dispatcher-utils.d.ts +3 -1
  511. package/dist/pipeline/dispatcher-utils.js +8 -2
  512. package/dist/pipeline/dispatcher.d.ts +3 -2
  513. package/dist/pipeline/dispatcher.js +4 -3
  514. package/dist/pipeline/event-consumer-state.d.ts +0 -1
  515. package/dist/pipeline/event-dispatcher-delivery.d.ts +1 -1
  516. package/dist/pipeline/event-dispatcher-delivery.js +7 -3
  517. package/dist/pipeline/event-dispatcher.d.ts +3 -2
  518. package/dist/pipeline/event-dispatcher.js +13 -12
  519. package/dist/pipeline/event-retention.js +59 -56
  520. package/dist/pipeline/handler-access-error.js +2 -1
  521. package/dist/pipeline/handler-context-auth.d.ts +5 -6
  522. package/dist/pipeline/handler-context-auth.js +7 -4
  523. package/dist/pipeline/handler-context-config.d.ts +2 -5
  524. package/dist/pipeline/handler-context-config.js +2 -2
  525. package/dist/pipeline/handler-context-db.d.ts +32 -7
  526. package/dist/pipeline/handler-context-db.js +26 -17
  527. package/dist/pipeline/handler-context-dispatch.d.ts +14 -6
  528. package/dist/pipeline/handler-context-dispatch.js +18 -5
  529. package/dist/pipeline/handler-context-events.d.ts +22 -38
  530. package/dist/pipeline/handler-context-events.js +42 -35
  531. package/dist/pipeline/handler-context-member-resolution.d.ts +2 -0
  532. package/dist/pipeline/handler-context-member-resolution.js +110 -0
  533. package/dist/pipeline/handler-context-notify.d.ts +3 -2
  534. package/dist/pipeline/handler-context-notify.js +1 -1
  535. package/dist/pipeline/handler-context-observability.d.ts +2 -4
  536. package/dist/pipeline/handler-context-observability.js +7 -5
  537. package/dist/pipeline/handler-context-phase-denials.d.ts +11 -0
  538. package/dist/pipeline/handler-context-phase-denials.js +98 -0
  539. package/dist/pipeline/handler-context-scope.d.ts +7 -10
  540. package/dist/pipeline/handler-context-scope.js +18 -20
  541. package/dist/pipeline/index.d.ts +9 -21
  542. package/dist/pipeline/index.js +5 -7
  543. package/dist/pipeline/irreversible-operation-gate.d.ts +1 -1
  544. package/dist/pipeline/irreversible-operation-gate.js +2 -2
  545. package/dist/pipeline/lifecycle-pipeline.d.ts +8 -7
  546. package/dist/pipeline/lifecycle-pipeline.js +9 -15
  547. package/dist/pipeline/member-read-only-transaction.js +11 -3
  548. package/dist/pipeline/member-reader.d.ts +1 -1
  549. package/dist/pipeline/member-reader.js +2 -2
  550. package/dist/pipeline/msp-rebuild.js +5 -3
  551. package/dist/pipeline/multi-stream-apply-context.js +3 -2
  552. package/dist/pipeline/notify-scope.d.ts +2 -1
  553. package/dist/pipeline/pre-handler-gates.d.ts +2 -1
  554. package/dist/pipeline/pre-transaction-reservations.d.ts +3 -3
  555. package/dist/pipeline/pre-transaction-reservations.js +9 -7
  556. package/dist/pipeline/projection-rebuild.js +12 -5
  557. package/dist/pipeline/projection-state.d.ts +0 -6
  558. package/dist/pipeline/projection-state.js +0 -5
  559. package/dist/pipeline/projections-runner.d.ts +2 -1
  560. package/dist/pipeline/projections-runner.js +10 -6
  561. package/dist/pipeline/system-hooks.d.ts +6 -2
  562. package/dist/pipeline/system-hooks.js +61 -10
  563. package/dist/pipeline/system-identity-switch.d.ts +4 -2
  564. package/dist/pipeline/system-identity-switch.js +51 -36
  565. package/dist/pipeline/typed-append-event.d.ts +2 -0
  566. package/dist/pipeline/typed-append-event.js +4 -0
  567. package/dist/pipeline/write-origin.js +2 -2
  568. package/dist/pipeline/write-result-masking.d.ts +1 -1
  569. package/dist/pipeline/write-result-masking.js +13 -10
  570. package/dist/random/index.d.ts +1 -2
  571. package/dist/random/index.js +1 -2
  572. package/dist/rate-limit/bucket.d.ts +2 -1
  573. package/dist/rate-limit/middleware.js +2 -1
  574. package/dist/redis/index.d.ts +3 -4
  575. package/dist/redis/index.js +2 -2
  576. package/dist/schema-cli/apply-command.js +5 -2
  577. package/dist/schema-cli/baseline-command.js +7 -2
  578. package/dist/schema-cli/generate-command.js +7 -9
  579. package/dist/schema-cli/shared.d.ts +4 -0
  580. package/dist/schema-cli/shared.js +21 -2
  581. package/dist/schema-cli/status-command.js +8 -2
  582. package/dist/schema-cli/validate-command.js +52 -4
  583. package/dist/schema-cli.d.ts +0 -1
  584. package/dist/search/configure-tenant-index.d.ts +1 -3
  585. package/dist/search/configure-tenant-index.js +0 -4
  586. package/dist/search/index.d.ts +1 -4
  587. package/dist/search/index.js +0 -2
  588. package/dist/search/meilisearch-adapter-internals.d.ts +8 -0
  589. package/dist/search/meilisearch-adapter-internals.js +9 -0
  590. package/dist/search/meilisearch-adapter.d.ts +1 -8
  591. package/dist/search/meilisearch-adapter.js +6 -7
  592. package/dist/search/meilisearch-peer.d.ts +2 -0
  593. package/dist/search/meilisearch-peer.js +5 -0
  594. package/dist/search/reindex-entity.js +107 -91
  595. package/dist/secrets/index.d.ts +3 -4
  596. package/dist/secrets/index.js +2 -3
  597. package/dist/seeding/index.d.ts +2 -2
  598. package/dist/seeding/index.js +1 -2
  599. package/dist/time/index.d.ts +2 -2
  600. package/dist/time/index.js +1 -1
  601. package/dist/time/tz-context.d.ts +0 -5
  602. package/dist/time/tz-context.js +0 -9
  603. package/dist/ui-types/index.d.ts +8 -11
  604. package/dist/ui-types/index.js +3 -3
  605. package/dist/upgrade-changelog.d.ts +3 -0
  606. package/dist/upgrade-changelog.js +18 -0
  607. package/dist/upgrade-cli-internals.d.ts +8 -0
  608. package/dist/upgrade-cli-internals.js +63 -0
  609. package/dist/upgrade-cli.d.ts +1 -18
  610. package/dist/upgrade-cli.js +29 -137
  611. package/dist/upgrade-marker-parse.d.ts +32 -0
  612. package/dist/upgrade-marker-parse.js +73 -0
  613. package/dist/utils/import-optional-peer.d.ts +6 -0
  614. package/dist/utils/import-optional-peer.js +19 -0
  615. package/dist/utils/index.d.ts +1 -4
  616. package/dist/utils/index.js +1 -4
  617. package/package.json +27 -20
  618. package/src/changes.json +451 -0
  619. package/dist/crypto/request-kms-cache.d.ts +0 -8
  620. package/dist/crypto/request-kms-cache.js +0 -21
  621. package/dist/engine/codemod/index.d.ts +0 -2
  622. package/dist/engine/codemod/index.js +0 -1
  623. package/dist/engine/codemod/pipeline-codemod.d.ts +0 -50
  624. package/dist/engine/codemod/pipeline-codemod.js +0 -536
  625. package/dist/engine/feature-ast/entity-field-types.d.ts +0 -13
  626. package/dist/engine/feature-ast/entity-field-types.js +0 -35
  627. package/dist/engine/feature-ast/extractors/ai-steps.d.ts +0 -6
  628. package/dist/engine/feature-ast/extractors/ai-steps.js +0 -254
  629. package/dist/engine/feature-ast/extractors/events.d.ts +0 -5
  630. package/dist/engine/feature-ast/extractors/events.js +0 -275
  631. package/dist/engine/feature-ast/extractors/handlers.d.ts +0 -24
  632. package/dist/engine/feature-ast/extractors/handlers.js +0 -358
  633. package/dist/engine/feature-ast/extractors/hooks.d.ts +0 -24
  634. package/dist/engine/feature-ast/extractors/hooks.js +0 -248
  635. package/dist/engine/feature-ast/extractors/index.d.ts +0 -14
  636. package/dist/engine/feature-ast/extractors/index.js +0 -13
  637. package/dist/engine/feature-ast/extractors/jobs-routes.d.ts +0 -7
  638. package/dist/engine/feature-ast/extractors/jobs-routes.js +0 -145
  639. package/dist/engine/feature-ast/extractors/projections-screens.d.ts +0 -10
  640. package/dist/engine/feature-ast/extractors/projections-screens.js +0 -195
  641. package/dist/engine/feature-ast/extractors/resolve-import.d.ts +0 -21
  642. package/dist/engine/feature-ast/extractors/resolve-import.js +0 -180
  643. package/dist/engine/feature-ast/extractors/round1.d.ts +0 -10
  644. package/dist/engine/feature-ast/extractors/round1.js +0 -81
  645. package/dist/engine/feature-ast/extractors/round2.d.ts +0 -7
  646. package/dist/engine/feature-ast/extractors/round2.js +0 -191
  647. package/dist/engine/feature-ast/extractors/round3.d.ts +0 -23
  648. package/dist/engine/feature-ast/extractors/round3.js +0 -292
  649. package/dist/engine/feature-ast/extractors/round5.d.ts +0 -9
  650. package/dist/engine/feature-ast/extractors/round5.js +0 -68
  651. package/dist/engine/feature-ast/extractors/round6.d.ts +0 -4
  652. package/dist/engine/feature-ast/extractors/round6.js +0 -17
  653. package/dist/engine/feature-ast/extractors/shared.d.ts +0 -94
  654. package/dist/engine/feature-ast/extractors/shared.js +0 -343
  655. package/dist/engine/feature-ast/index.d.ts +0 -16
  656. package/dist/engine/feature-ast/index.js +0 -10
  657. package/dist/engine/feature-ast/parse.d.ts +0 -31
  658. package/dist/engine/feature-ast/parse.js +0 -483
  659. package/dist/engine/feature-ast/patch.d.ts +0 -210
  660. package/dist/engine/feature-ast/patch.js +0 -610
  661. package/dist/engine/feature-ast/patcher.d.ts +0 -217
  662. package/dist/engine/feature-ast/patcher.js +0 -281
  663. package/dist/engine/feature-ast/pattern-change-schema.d.ts +0 -1476
  664. package/dist/engine/feature-ast/pattern-change-schema.js +0 -1226
  665. package/dist/engine/feature-ast/pattern-id-matchers.d.ts +0 -7
  666. package/dist/engine/feature-ast/pattern-id-matchers.js +0 -210
  667. package/dist/engine/feature-ast/patterns.d.ts +0 -318
  668. package/dist/engine/feature-ast/patterns.js +0 -106
  669. package/dist/engine/feature-ast/render.d.ts +0 -47
  670. package/dist/engine/feature-ast/render.js +0 -662
  671. package/dist/engine/feature-ast/source-location.d.ts +0 -17
  672. package/dist/engine/feature-ast/source-location.js +0 -27
  673. package/dist/engine/pattern-library/index.d.ts +0 -2
  674. package/dist/engine/pattern-library/index.js +0 -3
  675. package/dist/engine/pattern-library/library.d.ts +0 -14
  676. package/dist/engine/pattern-library/library.js +0 -93
  677. package/dist/engine/pattern-library/mixed-schemas.d.ts +0 -16
  678. package/dist/engine/pattern-library/mixed-schemas.js +0 -559
  679. package/dist/engine/pattern-library/opaque-schemas.d.ts +0 -7
  680. package/dist/engine/pattern-library/opaque-schemas.js +0 -115
  681. package/dist/engine/pattern-library/shared-fields.d.ts +0 -122
  682. package/dist/engine/pattern-library/shared-fields.js +0 -88
  683. package/dist/engine/pattern-library/static-schemas.d.ts +0 -20
  684. package/dist/engine/pattern-library/static-schemas.js +0 -478
  685. package/dist/engine/pattern-library/types.d.ts +0 -158
  686. package/dist/engine/pattern-library/types.js +0 -36
  687. package/dist/engine/provider-plugin-context.d.ts +0 -3
  688. package/dist/engine/provider-plugin-context.js +0 -28
  689. package/dist/engine/resolve-config-or-param.d.ts +0 -18
  690. package/dist/engine/resolve-config-or-param.js +0 -76
  691. package/src/scripts/codemod/README.md +0 -187
  692. package/src/scripts/codemod/codemod-support.ts +0 -334
  693. package/src/scripts/codemod/collect-source-files.ts +0 -48
  694. package/src/scripts/codemod/crypto-shredding-testing-move.ts +0 -119
  695. package/src/scripts/codemod/db-factory.ts +0 -810
  696. package/src/scripts/codemod/feature-factory.ts +0 -429
  697. package/src/scripts/codemod/feature-layout.ts +0 -424
  698. package/src/scripts/codemod/import-extensions.ts +0 -118
  699. package/src/scripts/codemod/migrate-cross-tenant.ts +0 -328
  700. package/src/scripts/codemod/migrate-db-raw.ts +0 -401
  701. package/src/scripts/codemod/migrate-open-to-all.ts +0 -235
  702. package/src/scripts/codemod/object-form.ts +0 -746
  703. package/src/scripts/codemod/package-merge.ts +0 -299
  704. package/src/scripts/codemod/pii-personal-migration.ts +0 -816
  705. package/src/scripts/codemod/remove-engine-payload-casts.ts +0 -318
  706. package/src/scripts/codemod/s3-imports.ts +0 -236
  707. package/src/scripts/codemod/tenant-db-options.ts +0 -454
  708. package/src/scripts/codemod/testing-injection.ts +0 -215
  709. package/src/scripts/codemod/testing-move-targets.ts +0 -161
  710. package/src/scripts/codemod/testing-move.ts +0 -542
  711. package/src/scripts/codemod/unprocessable-error-details-reason.ts +0 -411
@@ -17,7 +17,9 @@ import { createHash } from "node:crypto";
17
17
  import { existsSync, readdirSync, readFileSync } from "node:fs";
18
18
  import { join } from "node:path";
19
19
  import { propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
20
+ import { applyRowLevelSecurityScope } from "./queries/rls-scope.js";
20
21
  import { asRawClient, hasBegin, } from "./query.js";
22
+ import { SYSTEM_ROW_LEVEL_SECURITY_SCOPE } from "./row-level-security-scope.js";
21
23
  // Advisory-lock key (random 32-bit integer, stable). Prevents two Pods
22
24
  // booting at the same time from both running migrate apply (the second
23
25
  // blocks until the first finishes).
@@ -188,16 +190,16 @@ function migrationClient(db) {
188
190
  if (hasBegin(client))
189
191
  return client;
190
192
  }
191
- throw new Error("migrate-runner: db argument has no .unsafe() and .begin() (need a postgres-js Sql or drizzle DbConnection)");
193
+ throw new Error("migrate-runner: db argument has no .unsafe() and .begin() (need a Bun.SQL pool or a DbConnection)");
192
194
  }
193
- // Raw-SQL via postgres-js .unsafe().
195
+ // Raw-SQL via Bun.SQL .unsafe().
194
196
  async function executeRaw(db, sqlText) {
195
197
  await migrationClient(db).unsafe(sqlText);
196
198
  }
197
199
  async function reserveConnection(db) {
198
200
  const client = migrationClient(db);
199
201
  if (typeof client.reserve !== "function") {
200
- throw new Error("migrate-runner: db argument has no .reserve() (need a postgres-js Sql)");
202
+ throw new Error("migrate-runner: db argument has no .reserve() (need a Bun.SQL pool)");
201
203
  }
202
204
  return client.reserve();
203
205
  }
@@ -261,6 +263,8 @@ export async function runMigrations(db, migrations) {
261
263
  // apply remains stuck in the books.
262
264
  await conn.unsafe("BEGIN");
263
265
  try {
266
+ // Transaction-local, so the next migration's BEGIN starts clean; migrations rewrite every tenant's rows.
267
+ await applyRowLevelSecurityScope(conn, SYSTEM_ROW_LEVEL_SECURITY_SCOPE);
264
268
  for (const stmt of m.statements) {
265
269
  await conn.unsafe(stmt);
266
270
  }
@@ -1,5 +1,5 @@
1
+ import type { EntityDefinition } from "@cosmicdrift/kumiko-types/fields";
1
2
  import { toMinorUnits } from "../engine/minor-units.ts";
2
- import type { EntityDefinition } from "../engine/types/index.ts";
3
3
  export { toMinorUnits };
4
4
  export declare function moneyPayloadToMinorUnits(raw: unknown): number | undefined;
5
5
  export declare function flattenMoney(payload: Record<string, unknown>, entity: EntityDefinition): Record<string, unknown>;
package/dist/db/money.js CHANGED
@@ -8,6 +8,7 @@
8
8
  // MINOR_UNIT_SCALE units for callers that need scale-exact
9
9
  // comparisons (e.g. invoice sums) instead of round-tripping the
10
10
  // float amount through /100·*100.
11
+ import { DEFAULT_CURRENCIES } from "../engine/field-helpers.js";
11
12
  // `amountMinor` stays as a @deprecated alias of the same
12
13
  // value until the remaining consumers
13
14
  // (renderer/web/primitives/index.tsx,
@@ -31,7 +32,6 @@
31
32
  // amount column (legacy DB convention for money — `SUM(buying_price)` remains
32
33
  // idiomatic). `<name>Currency` is the additional column.
33
34
  import { MINOR_UNIT_SCALE, toMinorUnits } from "../engine/minor-units.js";
34
- import { DEFAULT_CURRENCIES } from "../engine/types/index.js";
35
35
  const FRAMEWORK_DEFAULT_CURRENCY = DEFAULT_CURRENCIES[0]; // "EUR"
36
36
  export { toMinorUnits };
37
37
  function toMajorUnits(amountScaled) {
@@ -1,6 +1,7 @@
1
1
  import type { ParentVisibilityOption } from "@cosmicdrift/kumiko-types/event-store-executor-types";
2
+ import type { EntityDefinition } from "@cosmicdrift/kumiko-types/fields";
2
3
  import { type OwnershipClause } from "../engine/ownership.ts";
3
- import type { EntityDefinition, SessionUser } from "../engine/types/index.ts";
4
+ import type { SessionUser } from "../engine/types/index.ts";
4
5
  import type { Table } from "./event-store-executor-context.ts";
5
6
  import type { TenantDb } from "./tenant-db.ts";
6
7
  export type { ParentVisibilityOption } from "@cosmicdrift/kumiko-types/event-store-executor-types";
@@ -3,8 +3,10 @@ export type PgErrorInfo = {
3
3
  readonly constraint_name: string | undefined;
4
4
  };
5
5
  export declare function extractPgError(e: unknown): PgErrorInfo | null;
6
+ export declare function isInvalidDateTimeValue(e: unknown): boolean;
6
7
  export declare function isUniqueViolation(e: unknown): boolean;
7
8
  export declare function isTableAlreadyExists(e: unknown): boolean;
8
9
  export declare function isLockNotAvailable(e: unknown): boolean;
9
10
  export declare function isUndefinedTable(e: unknown): boolean;
11
+ export declare function isRowLevelSecurityViolation(e: unknown): boolean;
10
12
  export declare function constraintOf(e: unknown): string | undefined;
@@ -1,31 +1,43 @@
1
- // Drizzle wraps postgres-js errors in `DrizzleQueryError`; the original PG
2
- // error (with SQLSTATE `code` and `constraint_name`) lives in `.cause`. We
3
- // unwrap both layers so callers don't have to know which layer produced the
4
- // error. Used by the event-store to distinguish a unique-violation on the
5
- // aggregate-version index (optimistic-concurrency conflict) from the one on
6
- // the idempotency-key index (caller-side replay signal).
1
+ // Bun.SQL server errors carry the SQLSTATE in `errno` and the constraint in
2
+ // `constraint` (`code` is the generic ERR_POSTGRES_SERVER_ERROR). Client-side
3
+ // failures have no `errno`, so their `code` (e.g. ERR_POSTGRES_CONNECTION_CLOSED)
4
+ // is returned instead. The `.cause` chain is walked so wrapped errors resolve too.
5
+ import { propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
7
6
  const MAX_CAUSE_DEPTH = 8;
7
+ function stringOrUndefined(value) {
8
+ return typeof value === "string" ? value : undefined;
9
+ }
10
+ function infoOfLayer(layer) {
11
+ const sqlState = stringOrUndefined(propertyOf(layer, "errno"));
12
+ if (sqlState !== undefined) {
13
+ return {
14
+ code: sqlState,
15
+ constraint_name: stringOrUndefined(propertyOf(layer, "constraint")),
16
+ };
17
+ }
18
+ const code = stringOrUndefined(propertyOf(layer, "code"));
19
+ return code === undefined ? null : { code, constraint_name: undefined };
20
+ }
8
21
  export function extractPgError(e) {
9
- if (typeof e !== "object" || e === null)
10
- return null;
11
- const layers = [];
12
22
  let current = e;
13
23
  // Depth cap doubles as cycle protection for self-referential causes.
14
- while (typeof current === "object" && current !== null && layers.length < MAX_CAUSE_DEPTH) {
15
- layers.push(current);
16
- // @cast-boundary error-details — DrizzleQueryError wraps PG-error in .cause
17
- current = current.cause;
18
- }
19
- for (const layer of layers) {
20
- // @cast-boundary error-details — postgres-js error shape (code, constraint_name)
21
- const code = layer.code;
22
- const constraintName = layer.constraint_name; // @cast-boundary error-details
23
- if (code !== undefined || constraintName !== undefined) {
24
- return { code, constraint_name: constraintName };
25
- }
24
+ for (let depth = 0; depth < MAX_CAUSE_DEPTH; depth++) {
25
+ if (typeof current !== "object" || current === null)
26
+ return null;
27
+ const info = infoOfLayer(current);
28
+ if (info !== null)
29
+ return info;
30
+ current = propertyOf(current, "cause");
26
31
  }
27
32
  return null;
28
33
  }
34
+ // PG SQLSTATE 22007 invalid_datetime_format and 22008 datetime_field_overflow: the
35
+ // column type rejects the value ("not-a-date", or "2026-02-30" for a date column).
36
+ const INVALID_DATETIME_VALUE_CODES = new Set(["22007", "22008"]);
37
+ export function isInvalidDateTimeValue(e) {
38
+ const code = extractPgError(e)?.code;
39
+ return code !== undefined && INVALID_DATETIME_VALUE_CODES.has(code);
40
+ }
29
41
  export function isUniqueViolation(e) {
30
42
  return extractPgError(e)?.code === "23505";
31
43
  }
@@ -50,6 +62,10 @@ export function isLockNotAvailable(e) {
50
62
  export function isUndefinedTable(e) {
51
63
  return extractPgError(e)?.code === "42P01";
52
64
  }
65
+ // PG SQLSTATE 42501 insufficient_privilege: a row failing a row-level-security WITH CHECK policy.
66
+ export function isRowLevelSecurityViolation(e) {
67
+ return extractPgError(e)?.code === "42501";
68
+ }
53
69
  export function constraintOf(e) {
54
70
  return extractPgError(e)?.constraint_name;
55
71
  }
@@ -499,7 +499,7 @@ async function rekeyTenantKeyedValue(deps, target) {
499
499
  const userSubject = keyedUnder?.kind === "tenant" && keyedUnder.tenantId === target.tenantId
500
500
  ? rekeyTargetOfTenantKeyedValue(target)
501
501
  : keyedUnder?.kind === "user"
502
- ? currentProjectionOwnerIfOther(target, keyedUnder.userId)
502
+ ? currentProjectionOwnerIfOther(target, fieldDef.userOwned.ownerField, keyedUnder.userId)
503
503
  : null;
504
504
  if (userSubject === null)
505
505
  return { kind: "unchanged" };
@@ -536,11 +536,8 @@ function rekeyTargetOfTenantKeyedValue(target) {
536
536
  // A value keyed under a previous owner moves to the owner the row has now. The
537
537
  // projection of the event's own tenant is authoritative here: the payload of
538
538
  // an old created event still names the owner the row had back then.
539
- function currentProjectionOwnerIfOther(target, keyedUserId) {
540
- const fieldDef = target.entity.fields[target.field];
541
- if (fieldDef === undefined || !("userOwned" in fieldDef) || !fieldDef.userOwned)
542
- return null;
543
- const owner = nonEmptyString(target.projectionOwnerRow?.[fieldDef.userOwned.ownerField]);
539
+ function currentProjectionOwnerIfOther(target, ownerField, keyedUserId) {
540
+ const owner = nonEmptyString(target.projectionOwnerRow?.[ownerField]);
544
541
  return owner === null || owner === keyedUserId ? null : { kind: "user", userId: owner };
545
542
  }
546
543
  // Only the row's own tenant key or a user key counts: another tenant's key is
@@ -1,7 +1,8 @@
1
1
  import { executeRawQueryRead } from "./raw-sql.js";
2
2
  // Called by createTenantDb with its inner runner; keeps raw SQL out of consumers.
3
+ // timestamptz(3): events.created_at has millisecond precision, a microsecond now() would compare later.
3
4
  export async function queryDatabaseNow(runner) {
4
- const [row] = await executeRawQueryRead(runner, "SELECT now() AS now");
5
+ const [row] = await executeRawQueryRead(runner, "SELECT now()::timestamptz(3) AS now");
5
6
  if (!row)
6
7
  throw new Error("SELECT now() returned no row");
7
8
  return row.now;
@@ -1,4 +1,7 @@
1
+ import type { DbConnection } from "../connection.ts";
2
+ import type { EntityTableMeta } from "../entity-table-meta.ts";
1
3
  import type { AnyDb } from "../query.ts";
2
4
  export declare function executeDdlStatement(db: AnyDb, sqlText: string): Promise<void>;
3
5
  export declare function alterTableAddColumn(db: AnyDb, tableName: string, columnName: string, columnType: string, defaultClause: string, notNull: string, ifNotExists?: boolean): Promise<void>;
4
6
  export declare function createIndexIfNotExists(db: AnyDb, indexKind: "UNIQUE INDEX" | "INDEX", indexName: string, tableName: string, columnList: string, whereSql?: string): Promise<void>;
7
+ export declare function applyTableRowLevelSecurity(db: DbConnection, meta: EntityTableMeta): Promise<void>;
@@ -1,4 +1,7 @@
1
- import { asRawClient } from "../query.js";
1
+ import { asRawClient, transaction } from "../query.js";
2
+ import { renderTableRowLevelSecurityDdl, tenantRowLevelSecurityFor } from "../render-rls-ddl.js";
3
+ import { quoteIdent } from "../sql-identifier.js";
4
+ import { readRowLevelSecurityShape } from "./rls-shape.js";
2
5
  import { quoteTableIdent } from "./table-ops.js";
3
6
  // Generic DDL helpers used on the prod-boot path (db/table-push.ts's
4
7
  // unsafePushTables, event-consumer-state.ts's multi-instance backfill) —
@@ -20,3 +23,33 @@ export async function createIndexIfNotExists(db, indexKind, indexName, tableName
20
23
  const where = whereSql !== undefined ? ` WHERE ${whereSql}` : "";
21
24
  await asRawClient(db).unsafe(`CREATE ${indexKind} IF NOT EXISTS ${quoteTableIdent(indexName)} ON ${quoteTableIdent(tableName)} (${columnList})${where}`);
22
25
  }
26
+ // Policy texts are not compared: a hand-edited kumiko policy is the operator's to keep, and the
27
+ // catalog check of the boot path reports it. Foreign policies are ignored.
28
+ function hasKumikoRowLevelSecurity(state, meta) {
29
+ const spec = tenantRowLevelSecurityFor(meta);
30
+ if (state === undefined || spec === undefined || !state.enabled || !state.forced)
31
+ return false;
32
+ return spec.policies.every((expected) => state.policies.some((actual) => actual.name === expected.name &&
33
+ actual.kind === expected.kind &&
34
+ actual.command === expected.command));
35
+ }
36
+ export async function applyTableRowLevelSecurity(db, meta) {
37
+ const statements = renderTableRowLevelSecurityDdl(meta);
38
+ // skip: no tenant_id column, nothing to enforce
39
+ if (statements.length === 0)
40
+ return;
41
+ // skip: already applied, so a repeated push takes no lock and runs no DDL
42
+ if (hasKumikoRowLevelSecurity(await readRowLevelSecurityShape(db, meta.tableName), meta)) {
43
+ return;
44
+ }
45
+ await transaction(db, async (tx) => {
46
+ // Self-conflicting lock: parallel pushes queue up instead of failing CREATE POLICY with duplicate_object.
47
+ await tx.unsafe(`LOCK TABLE ${quoteIdent(meta.tableName)} IN SHARE ROW EXCLUSIVE MODE`);
48
+ // skip: a parallel push applied it while this one waited for the lock
49
+ if (hasKumikoRowLevelSecurity(await readRowLevelSecurityShape(tx, meta.tableName), meta)) {
50
+ return;
51
+ }
52
+ for (const statement of statements)
53
+ await tx.unsafe(statement);
54
+ });
55
+ }
@@ -1,6 +1,7 @@
1
1
  import { createFallbackLogger } from "../../logging/utils.js";
2
2
  import { constraintOf, isLockNotAvailable, isTableAlreadyExists, isUniqueViolation, } from "../pg-error.js";
3
3
  import { asRawClient, runInNewTransaction, runnerCanBeginTransaction, unsafeReadRetrying, } from "../query.js";
4
+ import { innerOfScopedRunner, outsideRowLevelSecurityScope } from "../row-level-security-scope.js";
4
5
  import { withoutStatementTimeout } from "../statement-timeout.js";
5
6
  import { toPostgresArrayLiteral } from "./postgres-array-literal.js";
6
7
  const log = createFallbackLogger("event-store");
@@ -51,8 +52,9 @@ async function indexValidity(client, indexName = IDEMPOTENCY_INDEX_NAME) {
51
52
  `WHERE c.relname = $1 AND i.indrelid = '"kumiko_events"'::regclass`, [indexName]);
52
53
  return rows[0]?.indisvalid;
53
54
  }
55
+ // Index DDL carries no tenant data and CONCURRENTLY cannot run in a transaction, so both the pinned runner and the ambient scope are left.
54
56
  export function ensureIdempotencyKeyIndex(db) {
55
- return withoutStatementTimeout(db, buildIdempotencyKeyIndex);
57
+ return outsideRowLevelSecurityScope(() => withoutStatementTimeout(innerOfScopedRunner(db) ?? db, buildIdempotencyKeyIndex));
56
58
  }
57
59
  async function buildIdempotencyKeyIndex(db) {
58
60
  const client = asRawClient(db);
@@ -96,7 +98,7 @@ async function buildIdempotencyKeyIndex(db) {
96
98
  // tenant), which no other index covers: without this it scans every event.
97
99
  // Same CONCURRENTLY + INVALID-rebuild handling as the idempotency index.
98
100
  export function ensureMembershipPayloadTenantIndex(db) {
99
- return withoutStatementTimeout(db, buildMembershipPayloadTenantIndex);
101
+ return outsideRowLevelSecurityScope(() => withoutStatementTimeout(innerOfScopedRunner(db) ?? db, buildMembershipPayloadTenantIndex));
100
102
  }
101
103
  async function buildMembershipPayloadTenantIndex(db) {
102
104
  const client = asRawClient(db);
@@ -0,0 +1,2 @@
1
+ import type { AnyDb } from "../query.ts";
2
+ export declare function listEventTenantIds(db: AnyDb): Promise<readonly string[]>;
@@ -0,0 +1,17 @@
1
+ import { unsafeReadRetrying } from "../query.js";
2
+ // Every tenant_id that owns at least one event. Loose index scan: one probe on
3
+ // events_tenant_type_idx (leading column tenant_id) per distinct value, so the cost follows the
4
+ // number of tenants, not the number of events.
5
+ export async function listEventTenantIds(db) {
6
+ const rows = await unsafeReadRetrying(db, `WITH RECURSIVE tenants AS (
7
+ (SELECT "tenant_id" FROM "kumiko_events" ORDER BY "tenant_id" LIMIT 1)
8
+ UNION ALL
9
+ SELECT (SELECT e."tenant_id" FROM "kumiko_events" e
10
+ WHERE e."tenant_id" > tenants."tenant_id"
11
+ ORDER BY e."tenant_id" LIMIT 1)
12
+ FROM tenants
13
+ WHERE tenants."tenant_id" IS NOT NULL
14
+ )
15
+ SELECT "tenant_id"::text AS tenant_id FROM tenants WHERE "tenant_id" IS NOT NULL`, []);
16
+ return rows.map((row) => row.tenant_id);
17
+ }
@@ -0,0 +1,2 @@
1
+ import { type AnyDb } from "../query.ts";
2
+ export declare function assertRebuildableRowLevelSecurity(tx: AnyDb, tableName: string): Promise<void>;
@@ -0,0 +1,29 @@
1
+ // First statement of a rebuild tx. The rebuild runs in system scope, which the tenant isolation
2
+ // policy admits for every row; a table whose policies differ from the framework's shape could hide
3
+ // rows from the guards and let the swap drop them, so it is refused before anything is read.
4
+ import { asRawClient } from "../query.js";
5
+ import { TENANT_ISOLATION_POLICY } from "../render-rls-ddl.js";
6
+ import { readRowLevelSecurityShape } from "./rls-shape.js";
7
+ import { quoteTableIdent } from "./table-ops.js";
8
+ export async function assertRebuildableRowLevelSecurity(tx, tableName) {
9
+ // SHARE UPDATE EXCLUSIVE conflicts with CREATE/ALTER/DROP POLICY and ENABLE/FORCE (ACCESS EXCLUSIVE) but not with readers, and with itself: a second rebuild waits instead of both holding a shared lock and deadlocking on the swap's ACCESS EXCLUSIVE.
10
+ await asRawClient(tx).unsafe(`LOCK TABLE public.${quoteTableIdent(tableName)} IN SHARE UPDATE EXCLUSIVE MODE`);
11
+ const state = await readRowLevelSecurityShape(tx, tableName);
12
+ // skip: table has no row-level security, nothing to verify
13
+ if (state === undefined || !state.enabled)
14
+ return;
15
+ const isolation = state.policies.find((policy) => policy.name === TENANT_ISOLATION_POLICY);
16
+ const hasRestrictive = state.policies.some((policy) => policy.kind === "RESTRICTIVE");
17
+ const isolationIntact = isolation !== undefined &&
18
+ isolation.kind === "PERMISSIVE" &&
19
+ isolation.command === "ALL" &&
20
+ isolation.appliesToPublic;
21
+ // skip: framework policy intact, rebuild reads are safe
22
+ if (isolationIntact && !hasRestrictive)
23
+ return;
24
+ const listed = state.policies
25
+ .map((policy) => `${policy.name} (${policy.kind} ${policy.command})`)
26
+ .join(", ");
27
+ throw new Error(`rebuild: public.${tableName} has row level security that a system scope rebuild cannot rely on ` +
28
+ `(needs a PERMISSIVE ALL ${TENANT_ISOLATION_POLICY} policy for PUBLIC and no RESTRICTIVE policy); policies: ${listed || "(none)"}`);
29
+ }
@@ -0,0 +1,16 @@
1
+ import type { AnyDb } from "../query.ts";
2
+ export type ConnectedRoleAttributes = {
3
+ readonly roleName: string;
4
+ readonly superuser: boolean;
5
+ readonly bypassRowLevelSecurity: boolean;
6
+ };
7
+ export type TenantTableRowLevelSecurity = {
8
+ readonly tableName: string;
9
+ readonly enabled: boolean;
10
+ readonly forced: boolean;
11
+ readonly hasIsolationPolicy: boolean;
12
+ readonly hasSystemReadPolicy: boolean;
13
+ };
14
+ export declare function readConnectedRoleAttributes(db: AnyDb): Promise<ConnectedRoleAttributes | undefined>;
15
+ export declare function readTenantTablesRowLevelSecurity(db: AnyDb): Promise<readonly TenantTableRowLevelSecurity[]>;
16
+ export declare function readTablesWithRowLevelSecurityEnabled(db: AnyDb): Promise<readonly string[]>;
@@ -0,0 +1,39 @@
1
+ import { asRawClient } from "../query.js";
2
+ import { SYSTEM_TENANT_READ_POLICY, TENANT_ISOLATION_POLICY } from "../render-rls-ddl.js";
3
+ export async function readConnectedRoleAttributes(db) {
4
+ const rows = await asRawClient(db).unsafe(`SELECT rolname, rolsuper, rolbypassrls FROM pg_roles WHERE rolname = current_user`);
5
+ const row = rows[0];
6
+ if (row === undefined)
7
+ return undefined;
8
+ return {
9
+ roleName: row.rolname,
10
+ superuser: row.rolsuper,
11
+ bypassRowLevelSecurity: row.rolbypassrls,
12
+ };
13
+ }
14
+ export async function readTenantTablesRowLevelSecurity(db) {
15
+ const rows = await asRawClient(db).unsafe(`SELECT c.relname, c.relrowsecurity, c.relforcerowsecurity,
16
+ EXISTS (SELECT 1 FROM pg_policies p
17
+ WHERE p.schemaname = 'public' AND p.tablename = c.relname AND p.policyname = $1) AS has_isolation,
18
+ EXISTS (SELECT 1 FROM pg_policies p
19
+ WHERE p.schemaname = 'public' AND p.tablename = c.relname AND p.policyname = $2) AS has_system_read
20
+ FROM pg_class c
21
+ JOIN pg_namespace n ON n.oid = c.relnamespace
22
+ WHERE n.nspname = 'public' AND c.relkind IN ('r', 'p')
23
+ AND EXISTS (SELECT 1 FROM pg_attribute a
24
+ WHERE a.attrelid = c.oid AND a.attname = 'tenant_id' AND a.attnum > 0 AND NOT a.attisdropped)
25
+ ORDER BY c.relname COLLATE "C"`, [TENANT_ISOLATION_POLICY, SYSTEM_TENANT_READ_POLICY]);
26
+ return rows.map((row) => ({
27
+ tableName: row.relname,
28
+ enabled: row.relrowsecurity,
29
+ forced: row.relforcerowsecurity,
30
+ hasIsolationPolicy: row.has_isolation,
31
+ hasSystemReadPolicy: row.has_system_read,
32
+ }));
33
+ }
34
+ export async function readTablesWithRowLevelSecurityEnabled(db) {
35
+ const rows = await asRawClient(db).unsafe(`SELECT c.relname FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
36
+ WHERE n.nspname = 'public' AND c.relkind IN ('r', 'p') AND c.relrowsecurity
37
+ ORDER BY c.relname COLLATE "C"`);
38
+ return rows.map((row) => row.relname);
39
+ }
@@ -0,0 +1,6 @@
1
+ import { type AnyDb } from "../query.ts";
2
+ import { type RowLevelSecurityFlags, type TableRowLevelSecurity } from "../render-rls-ddl.ts";
3
+ export type RowLevelSecurityState = RowLevelSecurityFlags & TableRowLevelSecurity;
4
+ export declare function readRowLevelSecurityState(db: AnyDb, schemaName: string, tableName: string): Promise<RowLevelSecurityState | undefined>;
5
+ export declare function describeRowLevelSecurityDifference(expected: RowLevelSecurityState, actual: RowLevelSecurityState | undefined): string | undefined;
6
+ export declare function restoreRowLevelSecurityState(tx: AnyDb, tableName: string, expected: RowLevelSecurityState): Promise<void>;
@@ -0,0 +1,82 @@
1
+ // Reads and restores a table's row level security state from the Postgres catalog.
2
+ // The shadow swap rebuilds a table from EntityTableMeta, which carries no policies:
3
+ // the live state is read before the swap and re-applied (then verified) afterwards.
4
+ import { isOneOf } from "@cosmicdrift/kumiko-types/type-guards";
5
+ import { asRawClient } from "../query.js";
6
+ import { POLICY_COMMANDS, POLICY_KINDS, renderCreatePolicy, } from "../render-rls-ddl.js";
7
+ import { quoteTableIdent } from "./table-ops.js";
8
+ function toPolicy(row) {
9
+ if (!isOneOf(POLICY_COMMANDS, row.cmd) || !isOneOf(POLICY_KINDS, row.permissive)) {
10
+ throw new Error(`row level security: policy "${row.policyname}" has unsupported command "${row.cmd}" or kind "${row.permissive}"`);
11
+ }
12
+ return {
13
+ name: row.policyname,
14
+ command: row.cmd,
15
+ kind: row.permissive,
16
+ rolesSql: row.roles_sql,
17
+ ...(row.qual !== null && { using: row.qual }),
18
+ ...(row.with_check !== null && { withCheck: row.with_check }),
19
+ };
20
+ }
21
+ export async function readRowLevelSecurityState(db, schemaName, tableName) {
22
+ const raw = asRawClient(db);
23
+ const flagRows = await raw.unsafe(`SELECT c.relrowsecurity, c.relforcerowsecurity
24
+ FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
25
+ WHERE n.nspname = $1 AND c.relname = $2`, [schemaName, tableName]);
26
+ const flags = flagRows[0];
27
+ if (flags === undefined)
28
+ return undefined;
29
+ const policyRows = await raw.unsafe(`SELECT policyname, permissive, cmd, qual, with_check,
30
+ array_to_string(ARRAY(SELECT CASE WHEN r = 'public' THEN 'PUBLIC' ELSE quote_ident(r) END
31
+ FROM unnest(roles) AS r ORDER BY r::text COLLATE "C"), ', ') AS roles_sql
32
+ FROM pg_policies WHERE schemaname = $1 AND tablename = $2
33
+ ORDER BY policyname COLLATE "C"`, [schemaName, tableName]);
34
+ return {
35
+ enabled: flags.relrowsecurity,
36
+ forced: flags.relforcerowsecurity,
37
+ policies: policyRows.map(toPolicy),
38
+ };
39
+ }
40
+ function describePolicyDifference(expected, actual) {
41
+ if (actual === undefined)
42
+ return `policy ${expected.name} missing`;
43
+ const fields = ["command", "kind", "rolesSql", "using", "withCheck"];
44
+ const differing = fields.filter((field) => expected[field] !== actual[field]);
45
+ return differing.length > 0
46
+ ? `policy ${expected.name} differs in ${differing.join(", ")}`
47
+ : undefined;
48
+ }
49
+ export function describeRowLevelSecurityDifference(expected, actual) {
50
+ if (actual === undefined)
51
+ return "table missing";
52
+ const differences = [];
53
+ if (expected.enabled !== actual.enabled)
54
+ differences.push(`enabled ${expected.enabled} -> ${actual.enabled}`);
55
+ if (expected.forced !== actual.forced)
56
+ differences.push(`forced ${expected.forced} -> ${actual.forced}`);
57
+ const actualByName = new Map(actual.policies.map((policy) => [policy.name, policy]));
58
+ for (const policy of expected.policies) {
59
+ const difference = describePolicyDifference(policy, actualByName.get(policy.name));
60
+ if (difference !== undefined)
61
+ differences.push(difference);
62
+ }
63
+ const expectedNames = new Set(expected.policies.map((policy) => policy.name));
64
+ for (const policy of actual.policies) {
65
+ if (!expectedNames.has(policy.name))
66
+ differences.push(`unexpected policy ${policy.name}`);
67
+ }
68
+ return differences.length > 0 ? differences.join("; ") : undefined;
69
+ }
70
+ export async function restoreRowLevelSecurityState(tx, tableName, expected) {
71
+ const raw = asRawClient(tx);
72
+ const tableSql = `public.${quoteTableIdent(tableName)}`;
73
+ for (const policy of expected.policies) {
74
+ await raw.unsafe(renderCreatePolicy(tableSql, policy));
75
+ }
76
+ // One ALTER TABLE for both flags: each ALTER TABLE is a separate DDL command (and event-trigger firing) on the swap's critical path.
77
+ await raw.unsafe(`ALTER TABLE ${tableSql} ${expected.enabled ? "ENABLE" : "DISABLE"} ROW LEVEL SECURITY, ${expected.forced ? "FORCE" : "NO FORCE"} ROW LEVEL SECURITY`);
78
+ const difference = describeRowLevelSecurityDifference(expected, await readRowLevelSecurityState(tx, "public", tableName));
79
+ if (difference !== undefined) {
80
+ throw new Error(`shadow swap: row level security of public.${tableName} differs after the swap: ${difference}`);
81
+ }
82
+ }
@@ -0,0 +1,3 @@
1
+ import type { RowLevelSecurityScope } from "../row-level-security-scope.ts";
2
+ export declare function buildRowLevelSecurityScopeQuery(runner: unknown, scope: RowLevelSecurityScope | undefined): PromiseLike<unknown>;
3
+ export declare function applyRowLevelSecurityScope(runner: unknown, scope: RowLevelSecurityScope | undefined): Promise<void>;
@@ -0,0 +1,17 @@
1
+ import { asRawClient } from "../query.js";
2
+ import { SCOPE_SETTING, SYSTEM_SCOPE, TENANT_ID_SETTING } from "../render-rls-ddl.js";
3
+ function scopeSettings(scope) {
4
+ const tenantId = scope?.kind === "tenant" ? scope.tenantId : "";
5
+ const systemScope = scope?.kind === "system" ? SYSTEM_SCOPE : "";
6
+ return [TENANT_ID_SETTING, tenantId, SCOPE_SETTING, systemScope];
7
+ }
8
+ const SET_SCOPE_SQL = `SELECT set_config($1, $2, true), set_config($3, $4, true)`;
9
+ // Returns the lazy driver query without sending it, so a caller can pipeline it right behind BEGIN;
10
+ // the caller must start it itself and in order.
11
+ export function buildRowLevelSecurityScopeQuery(runner, scope) {
12
+ return asRawClient(runner).unsafe(SET_SCOPE_SQL, [...scopeSettings(scope)]);
13
+ }
14
+ // set_config(..., true) is transaction-local; undefined clears both settings to ''.
15
+ export async function applyRowLevelSecurityScope(runner, scope) {
16
+ await asRawClient(runner).unsafe(SET_SCOPE_SQL, [...scopeSettings(scope)]);
17
+ }
@@ -0,0 +1,10 @@
1
+ import { type AnyDb } from "../query.ts";
2
+ import type { RowLevelSecurityPolicy } from "../render-rls-ddl.ts";
3
+ export type RowLevelSecurityShape = {
4
+ readonly enabled: boolean;
5
+ readonly forced: boolean;
6
+ readonly policies: readonly (Pick<RowLevelSecurityPolicy, "name" | "kind" | "command"> & {
7
+ readonly appliesToPublic: boolean;
8
+ })[];
9
+ };
10
+ export declare function readRowLevelSecurityShape(db: AnyDb, tableName: string): Promise<RowLevelSecurityShape | undefined>;
@@ -0,0 +1,42 @@
1
+ // Flags plus policy name, kind and command of a table, read from the catalogs without deparsing
2
+ // policy expressions. pg_get_expr (behind the pg_policies view) opens the table with ACCESS SHARE,
3
+ // which queues behind a pending ACCESS EXCLUSIVE; this read never touches the table itself.
4
+ import { asRawClient } from "../query.js";
5
+ const COMMAND_BY_CODE = {
6
+ "*": "ALL",
7
+ r: "SELECT",
8
+ a: "INSERT",
9
+ w: "UPDATE",
10
+ d: "DELETE",
11
+ };
12
+ function commandOf(code) {
13
+ const command = Object.entries(COMMAND_BY_CODE).find(([key]) => key === code)?.[1];
14
+ // Fail closed: a policy command this check does not know cannot be judged harmless.
15
+ if (command === undefined)
16
+ throw new Error(`unknown pg_policy.polcmd code "${code}"`);
17
+ return command;
18
+ }
19
+ export async function readRowLevelSecurityShape(db, tableName) {
20
+ const rows = await asRawClient(db).unsafe(`SELECT c.relrowsecurity, c.relforcerowsecurity, p.polname, p.polpermissive, p.polcmd::text AS polcmd, (p.polroles = '{0}'::oid[]) AS polpublic
21
+ FROM pg_class c
22
+ JOIN pg_namespace n ON n.oid = c.relnamespace
23
+ LEFT JOIN pg_policy p ON p.polrelid = c.oid
24
+ WHERE n.nspname = 'public' AND c.relname = $1`, [tableName]);
25
+ const first = rows[0];
26
+ if (first === undefined)
27
+ return undefined;
28
+ const policies = rows.flatMap((row) => {
29
+ if (row.polname === null || row.polcmd === null)
30
+ return [];
31
+ const command = commandOf(row.polcmd);
32
+ return [
33
+ {
34
+ name: row.polname,
35
+ kind: row.polpermissive ? "PERMISSIVE" : "RESTRICTIVE",
36
+ command,
37
+ appliesToPublic: row.polpublic === true,
38
+ },
39
+ ];
40
+ });
41
+ return { enabled: first.relrowsecurity, forced: first.relforcerowsecurity, policies };
42
+ }
@@ -0,0 +1,2 @@
1
+ import type { AnyDb } from "../query.ts";
2
+ export declare function withSessionAdvisoryLock<T>(db: AnyDb, key: number, fn: () => Promise<T>): Promise<T>;
@@ -0,0 +1,23 @@
1
+ import { asRawClient } from "../query.js";
2
+ // Blocks until the lock is free. The lock is session-scoped, so lock, work and
3
+ // unlock share one reserved connection; pool calls may land on different sessions.
4
+ export async function withSessionAdvisoryLock(db, key, fn) {
5
+ const client = asRawClient(db);
6
+ if (typeof client.reserve !== "function") {
7
+ throw new Error("withSessionAdvisoryLock: db has no .reserve() (need a Bun.SQL pool)");
8
+ }
9
+ const conn = await client.reserve();
10
+ try {
11
+ await conn.unsafe(`SELECT pg_advisory_lock($1)`, [key]);
12
+ try {
13
+ return await fn();
14
+ }
15
+ finally {
16
+ // A dropped session already released its lock; failing here would mask fn's outcome.
17
+ await conn.unsafe(`SELECT pg_advisory_unlock($1)`, [key]).catch(() => undefined);
18
+ }
19
+ }
20
+ finally {
21
+ await conn.release();
22
+ }
23
+ }
@@ -6,7 +6,6 @@ export declare const PROJECTION_REBUILD_SCHEMA = "kumiko_rebuild";
6
6
  export declare function ensureRebuildSchema(db: AnyDb): Promise<void>;
7
7
  export declare function rebuildMetaOrThrow(table: unknown, projectionName: string): EntityTableMeta;
8
8
  export declare function assertLiveColumnsMatchMeta(db: DbConnection | DbTx, meta: EntityTableMeta, projectionName: string): Promise<void>;
9
- export declare function assertLiveTableHasNoRowLevelSecurity(tx: AnyDb, tableName: string, projectionName?: string): Promise<void>;
10
9
  export declare function buildShadowTable(tx: AnyDb, meta: EntityTableMeta): Promise<void>;
11
10
  export declare function fenceLiveTable(tx: AnyDb, tableName: string, lockTimeoutMs: number): Promise<void>;
12
11
  export declare function assertNoUnreachableLiveRows(tx: AnyDb, projectionName: string, tableName: string, aggregateTypes: readonly string[], derivedRowIds?: Readonly<Record<string, ProjectionRowIdOf>>, derivedRowIdBatchSize?: number): Promise<void>;
@@ -16,4 +15,4 @@ export type ColumnDriftResult = {
16
15
  readonly sample: readonly string[];
17
16
  };
18
17
  export declare function countColumnDrift(tx: AnyDb, tableName: string, meta: EntityTableMeta): Promise<ColumnDriftResult>;
19
- export declare function swapShadowIntoLive(tx: AnyDb, tableName: string): Promise<void>;
18
+ export declare function swapShadowIntoLive(tx: DbTx, tableName: string): Promise<void>;