@cosmicdrift/kumiko-framework 0.386.0 → 0.388.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (468) hide show
  1. package/AGENTS.md +6 -1
  2. package/LICENSE +21 -57
  3. package/README.md +2 -2
  4. package/agents/examples/anonymous-access.md +1 -1
  5. package/agents/examples/apex-landing.md +204 -0
  6. package/agents/examples/api-worker-split.md +64 -0
  7. package/agents/examples/app-config.md +14 -0
  8. package/agents/examples/auth-mfa.md +115 -0
  9. package/agents/examples/env-composition.md +28 -1
  10. package/agents/examples/legal-pages.md +1 -1
  11. package/agents/examples/prod-boot-options.md +16 -0
  12. package/agents/examples/rate-limiting.md +48 -0
  13. package/agents/examples/record-detail-layout.md +692 -0
  14. package/agents/examples/seed-migration.md +212 -0
  15. package/agents/examples/wizard-form.md +941 -0
  16. package/agents/examples/writeform-section.md +548 -0
  17. package/agents/features.md +19 -2
  18. package/agents/widgets.md +40 -39
  19. package/dist/api/api-constants.js +2 -2
  20. package/dist/api/auth-middleware.js +7 -7
  21. package/dist/api/auth-rate-limiters.js +7 -3
  22. package/dist/api/auth-routes-mfa.js +1 -1
  23. package/dist/api/auth-routes-session.js +1 -1
  24. package/dist/api/auth-routes-shared.js +7 -3
  25. package/dist/api/auth-routes-signup.js +1 -1
  26. package/dist/api/client-error-route.js +8 -2
  27. package/dist/api/client-ip.js +3 -1
  28. package/dist/api/extra-route.js +1 -1
  29. package/dist/api/index.d.ts +3 -1
  30. package/dist/api/index.js +3 -1
  31. package/dist/api/pii-leak-guard.js +4 -2
  32. package/dist/api/readiness.js +5 -5
  33. package/dist/api/redis-sse-broker.js +8 -6
  34. package/dist/api/request-context.d.ts +1 -1
  35. package/dist/api/request-context.js +1 -1
  36. package/dist/api/routes.js +2 -2
  37. package/dist/api/server-boot-checks.d.ts +10 -0
  38. package/dist/api/server-boot-checks.js +157 -0
  39. package/dist/api/server-event-dispatcher.d.ts +26 -0
  40. package/dist/api/server-event-dispatcher.js +208 -0
  41. package/dist/api/server-extra-routes.d.ts +11 -0
  42. package/dist/api/server-extra-routes.js +203 -0
  43. package/dist/api/server-middleware.d.ts +20 -0
  44. package/dist/api/server-middleware.js +211 -0
  45. package/dist/api/server-routes.d.ts +19 -0
  46. package/dist/api/server-routes.js +293 -0
  47. package/dist/api/server-websocket-routes.d.ts +4 -0
  48. package/dist/api/server-websocket-routes.js +108 -0
  49. package/dist/api/server-wiring.d.ts +32 -0
  50. package/dist/api/server-wiring.js +236 -0
  51. package/dist/api/server.d.ts +10 -15
  52. package/dist/api/server.js +48 -1250
  53. package/dist/api/session-cookies.js +4 -4
  54. package/dist/api/sse-broker.js +1 -1
  55. package/dist/api/sse-route.d.ts +13 -13
  56. package/dist/api/sse-route.js +31 -27
  57. package/dist/auth/index.js +4 -4
  58. package/dist/auth/roles.d.ts +4 -4
  59. package/dist/auth/roles.js +24 -24
  60. package/dist/compliance/duration-spec.js +18 -20
  61. package/dist/compliance/index.js +2 -3
  62. package/dist/compliance/override-schema.d.ts +4 -4
  63. package/dist/compliance/override-schema.js +19 -22
  64. package/dist/compliance/profiles.d.ts +26 -29
  65. package/dist/compliance/profiles.js +62 -69
  66. package/dist/compliance/sub-processors.d.ts +28 -30
  67. package/dist/compliance/sub-processors.js +17 -21
  68. package/dist/consumer-cli.js +4 -4
  69. package/dist/crypto/blind-index.js +18 -17
  70. package/dist/crypto/ciphertext-pattern.js +1 -1
  71. package/dist/crypto/event-pii.js +3 -3
  72. package/dist/crypto/kek-source.d.ts +1 -1
  73. package/dist/crypto/kek-source.js +3 -2
  74. package/dist/crypto/kms-adapter.d.ts +1 -1
  75. package/dist/crypto/kms-adapter.js +4 -6
  76. package/dist/crypto/kms-wiring.d.ts +1 -2
  77. package/dist/crypto/kms-wiring.js +2 -2
  78. package/dist/crypto/owner-transition-rekey.d.ts +5 -1
  79. package/dist/crypto/owner-transition-rekey.js +1 -1
  80. package/dist/crypto/pg-kms-adapter.js +2 -6
  81. package/dist/crypto/pii-field-encryption.d.ts +5 -1
  82. package/dist/crypto/pii-field-encryption.js +7 -5
  83. package/dist/crypto/subject-resolver.d.ts +1 -1
  84. package/dist/crypto/subject-resolver.js +2 -2
  85. package/dist/db/aggregate-sql.d.ts +21 -0
  86. package/dist/db/aggregate-sql.js +155 -0
  87. package/dist/db/apply-entity-event.d.ts +6 -6
  88. package/dist/db/apply-entity-event.js +55 -55
  89. package/dist/db/blind-index-cleanup.js +7 -7
  90. package/dist/db/closed-connection.d.ts +9 -0
  91. package/dist/db/closed-connection.js +95 -0
  92. package/dist/db/collect-table-metas.js +10 -11
  93. package/dist/db/compound-types.d.ts +1 -1
  94. package/dist/db/compound-types.js +12 -12
  95. package/dist/db/cursor.js +3 -4
  96. package/dist/db/dialect.d.ts +2 -2
  97. package/dist/db/dialect.js +3 -3
  98. package/dist/db/distinct-on.d.ts +1 -1
  99. package/dist/db/driver-row-coercion.d.ts +2 -0
  100. package/dist/db/driver-row-coercion.js +186 -0
  101. package/dist/db/driver-value-preparation.d.ts +16 -0
  102. package/dist/db/driver-value-preparation.js +99 -0
  103. package/dist/db/eagerload.d.ts +3 -3
  104. package/dist/db/eagerload.js +21 -12
  105. package/dist/db/entity-field-encryption.d.ts +1 -0
  106. package/dist/db/entity-field-encryption.js +1 -1
  107. package/dist/db/entity-table-meta.d.ts +2 -2
  108. package/dist/db/entity-table-meta.js +105 -112
  109. package/dist/db/event-store-executor-context.d.ts +3 -2
  110. package/dist/db/event-store-executor-context.js +13 -13
  111. package/dist/db/event-store-executor-read-aggregate.d.ts +8 -0
  112. package/dist/db/event-store-executor-read-aggregate.js +112 -0
  113. package/dist/db/event-store-executor-read-filter.d.ts +31 -0
  114. package/dist/db/event-store-executor-read-filter.js +203 -0
  115. package/dist/db/event-store-executor-read-paging.d.ts +14 -0
  116. package/dist/db/event-store-executor-read-paging.js +99 -0
  117. package/dist/db/event-store-executor-read-rows.d.ts +6 -0
  118. package/dist/db/event-store-executor-read-rows.js +32 -0
  119. package/dist/db/event-store-executor-read-search.d.ts +28 -0
  120. package/dist/db/event-store-executor-read-search.js +221 -0
  121. package/dist/db/event-store-executor-read-sort.d.ts +41 -0
  122. package/dist/db/event-store-executor-read-sort.js +130 -0
  123. package/dist/db/event-store-executor-read-where.d.ts +24 -0
  124. package/dist/db/event-store-executor-read-where.js +81 -0
  125. package/dist/db/event-store-executor-read.d.ts +2 -9
  126. package/dist/db/event-store-executor-read.js +50 -704
  127. package/dist/db/event-store-executor-write-steps.d.ts +20 -0
  128. package/dist/db/event-store-executor-write-steps.js +100 -0
  129. package/dist/db/event-store-executor-write.js +401 -510
  130. package/dist/db/index.d.ts +6 -2
  131. package/dist/db/index.js +5 -1
  132. package/dist/db/list-aggregate-labels.d.ts +14 -0
  133. package/dist/db/list-aggregate-labels.js +26 -0
  134. package/dist/db/located-timestamp.d.ts +11 -11
  135. package/dist/db/located-timestamp.js +29 -30
  136. package/dist/db/migrate-generator.d.ts +1 -1
  137. package/dist/db/migrate-generator.js +97 -105
  138. package/dist/db/migrate-runner.js +29 -26
  139. package/dist/db/money.d.ts +6 -6
  140. package/dist/db/money.js +29 -27
  141. package/dist/db/queries/backfill-pii.js +5 -5
  142. package/dist/db/queries/ddl.js +1 -1
  143. package/dist/db/queries/es-ops.d.ts +1 -1
  144. package/dist/db/queries/es-ops.js +1 -1
  145. package/dist/db/queries/event-consumer.js +3 -3
  146. package/dist/db/queries/event-store.js +4 -2
  147. package/dist/db/queries/projection-rebuild.js +3 -3
  148. package/dist/db/queries/raw-sql.d.ts +2 -2
  149. package/dist/db/queries/raw-sql.js +2 -2
  150. package/dist/db/queries/shadow-swap.js +19 -19
  151. package/dist/db/query.d.ts +3 -16
  152. package/dist/db/query.js +8 -761
  153. package/dist/db/rebuild-marker.js +20 -21
  154. package/dist/db/reference-data.js +6 -1
  155. package/dist/db/replay-migration-sql.js +2 -2
  156. package/dist/db/schema-inspection.js +4 -5
  157. package/dist/db/table-builder.d.ts +1 -2
  158. package/dist/db/table-builder.js +137 -190
  159. package/dist/db/table-info.d.ts +10 -0
  160. package/dist/db/table-info.js +45 -0
  161. package/dist/db/temporal-value-guards.d.ts +6 -0
  162. package/dist/db/temporal-value-guards.js +15 -0
  163. package/dist/db/tenant-db-runner.d.ts +1 -0
  164. package/dist/db/tenant-db-runner.js +3 -0
  165. package/dist/db/tenant-db.d.ts +2 -2
  166. package/dist/db/tenant-db.js +48 -11
  167. package/dist/db/where-clause.d.ts +6 -0
  168. package/dist/db/where-clause.js +236 -0
  169. package/dist/derivatives/delete-stored-file.js +6 -2
  170. package/dist/derivatives/derivatives-context.js +1 -1
  171. package/dist/engine/boot-validator/access-roles.js +4 -3
  172. package/dist/engine/boot-validator/api-ext.js +14 -14
  173. package/dist/engine/boot-validator/boot-check.js +1 -1
  174. package/dist/engine/boot-validator/config-deps.js +7 -6
  175. package/dist/engine/boot-validator/detail-screens.js +4 -4
  176. package/dist/engine/boot-validator/entity-handler.d.ts +1 -0
  177. package/dist/engine/boot-validator/entity-handler.js +77 -61
  178. package/dist/engine/boot-validator/form-derivations.js +35 -1
  179. package/dist/engine/boot-validator/gdpr-storage.js +4 -4
  180. package/dist/engine/boot-validator/i18n-keys.js +6 -4
  181. package/dist/engine/boot-validator/index.d.ts +2 -2
  182. package/dist/engine/boot-validator/index.js +22 -20
  183. package/dist/engine/boot-validator/nav.js +5 -5
  184. package/dist/engine/boot-validator/ownership.js +12 -9
  185. package/dist/engine/boot-validator/parent-ref.js +1 -1
  186. package/dist/engine/boot-validator/pii-retention.js +258 -187
  187. package/dist/engine/boot-validator/projection-list-screens.js +7 -7
  188. package/dist/engine/boot-validator/query-refs.js +1 -1
  189. package/dist/engine/boot-validator/screens-actions.d.ts +1 -0
  190. package/dist/engine/boot-validator/screens-actions.js +23 -17
  191. package/dist/engine/boot-validator/screens-dashboard.js +2 -5
  192. package/dist/engine/boot-validator/screens-forms.js +12 -17
  193. package/dist/engine/boot-validator/screens-kind-config-edit.d.ts +3 -0
  194. package/dist/engine/boot-validator/screens-kind-config-edit.js +88 -0
  195. package/dist/engine/boot-validator/screens-kind-entity-edit.d.ts +3 -0
  196. package/dist/engine/boot-validator/screens-kind-entity-edit.js +55 -0
  197. package/dist/engine/boot-validator/screens-kind-entity-list.d.ts +3 -0
  198. package/dist/engine/boot-validator/screens-kind-entity-list.js +228 -0
  199. package/dist/engine/boot-validator/screens-kind-projection-detail.d.ts +3 -0
  200. package/dist/engine/boot-validator/screens-kind-projection-detail.js +235 -0
  201. package/dist/engine/boot-validator/screens-kind-projection-list.d.ts +3 -0
  202. package/dist/engine/boot-validator/screens-kind-projection-list.js +52 -0
  203. package/dist/engine/boot-validator/screens-kind-secret-mint.d.ts +3 -0
  204. package/dist/engine/boot-validator/screens-kind-secret-mint.js +28 -0
  205. package/dist/engine/boot-validator/screens-kind-shared.d.ts +37 -0
  206. package/dist/engine/boot-validator/screens-kind-shared.js +142 -0
  207. package/dist/engine/boot-validator/screens-lists.d.ts +2 -1
  208. package/dist/engine/boot-validator/screens-lists.js +26 -24
  209. package/dist/engine/boot-validator/screens.d.ts +0 -2
  210. package/dist/engine/boot-validator/screens.js +102 -869
  211. package/dist/engine/boot-validator/security-baseline.js +3 -2
  212. package/dist/engine/boot-validator/transfer-graph.js +2 -2
  213. package/dist/engine/boot-validator/workspaces.js +3 -3
  214. package/dist/engine/bridge-stub.js +3 -4
  215. package/dist/engine/build-app-schema.js +130 -170
  216. package/dist/engine/build-config-feature-schema.js +50 -50
  217. package/dist/engine/build-target.js +20 -23
  218. package/dist/engine/codemod/pipeline-codemod.js +20 -13
  219. package/dist/engine/config-helpers.js +4 -4
  220. package/dist/engine/constants.js +2 -2
  221. package/dist/engine/create-app.js +2 -2
  222. package/dist/engine/define-feature.js +21 -13
  223. package/dist/engine/define-handler.js +13 -11
  224. package/dist/engine/define-step.js +1 -1
  225. package/dist/engine/define-workflow.d.ts +5 -5
  226. package/dist/engine/define-workflow.js +6 -6
  227. package/dist/engine/entity-handlers.d.ts +13 -0
  228. package/dist/engine/entity-handlers.js +22 -34
  229. package/dist/engine/entity-list-runtime-options.d.ts +8 -0
  230. package/dist/engine/entity-list-runtime-options.js +18 -0
  231. package/dist/engine/event-def.js +2 -2
  232. package/dist/engine/extension-names.d.ts +16 -17
  233. package/dist/engine/extension-names.js +37 -38
  234. package/dist/engine/extensions/escape-hatch-usage.js +1 -1
  235. package/dist/engine/extensions/tenant-data.js +2 -2
  236. package/dist/engine/extensions/user-data.d.ts +23 -24
  237. package/dist/engine/factories.d.ts +14 -13
  238. package/dist/engine/factories.js +38 -37
  239. package/dist/engine/feature-ast/entity-field-types.js +1 -1
  240. package/dist/engine/feature-ast/extractors/handlers.js +2 -1
  241. package/dist/engine/feature-ast/extractors/hooks.js +1 -1
  242. package/dist/engine/feature-ast/extractors/round2.js +25 -6
  243. package/dist/engine/feature-ast/extractors/shared.d.ts +2 -3
  244. package/dist/engine/feature-ast/extractors/shared.js +2 -3
  245. package/dist/engine/feature-ast/parse.js +13 -13
  246. package/dist/engine/feature-ast/patch.js +4 -182
  247. package/dist/engine/feature-ast/pattern-change-schema.d.ts +3 -0
  248. package/dist/engine/feature-ast/pattern-change-schema.js +2 -1
  249. package/dist/engine/feature-ast/pattern-id-matchers.d.ts +7 -0
  250. package/dist/engine/feature-ast/pattern-id-matchers.js +210 -0
  251. package/dist/engine/feature-ast/patterns.d.ts +11 -2
  252. package/dist/engine/feature-ast/patterns.js +43 -51
  253. package/dist/engine/feature-ast/render.js +50 -89
  254. package/dist/engine/feature-config-events-jobs.js +15 -12
  255. package/dist/engine/feature-entity-handlers.js +12 -5
  256. package/dist/engine/feature-manifest.d.ts +9 -9
  257. package/dist/engine/feature-manifest.js +6 -7
  258. package/dist/engine/feature-ui-extensions.d.ts +3 -3
  259. package/dist/engine/feature-ui-extensions.js +30 -22
  260. package/dist/engine/field-access.js +5 -6
  261. package/dist/engine/form-derivations.js +18 -7
  262. package/dist/engine/handler-field-references.js +1 -1
  263. package/dist/engine/index.d.ts +1 -0
  264. package/dist/engine/index.js +4 -4
  265. package/dist/engine/object-form.d.ts +1 -0
  266. package/dist/engine/object-form.js +14 -4
  267. package/dist/engine/ownership.js +3 -3
  268. package/dist/engine/parse-ref-target.js +5 -7
  269. package/dist/engine/pattern-library/index.js +2 -2
  270. package/dist/engine/pattern-library/library.js +2 -2
  271. package/dist/engine/pattern-library/types.js +3 -3
  272. package/dist/engine/pipeline.js +2 -2
  273. package/dist/engine/project-app-schema-for-roles.d.ts +2 -0
  274. package/dist/engine/project-app-schema-for-roles.js +7 -5
  275. package/dist/engine/projection-helpers.js +9 -9
  276. package/dist/engine/qualified-name.js +2 -2
  277. package/dist/engine/registry-facade.js +1 -1
  278. package/dist/engine/registry-ingest.js +13 -13
  279. package/dist/engine/registry-state.js +17 -19
  280. package/dist/engine/registry-validate.js +12 -12
  281. package/dist/engine/run-in.js +2 -2
  282. package/dist/engine/run-pipeline.js +7 -0
  283. package/dist/engine/schema-builder.js +251 -233
  284. package/dist/engine/screen-filter-ops.js +17 -20
  285. package/dist/engine/screen-helpers.js +4 -3
  286. package/dist/engine/screen-kind.d.ts +7 -0
  287. package/dist/engine/screen-kind.js +1 -0
  288. package/dist/engine/state-machine.d.ts +12 -12
  289. package/dist/engine/state-machine.js +7 -7
  290. package/dist/engine/steps/_resolver-utils.js +4 -3
  291. package/dist/engine/steps/for-each.js +4 -4
  292. package/dist/engine/steps/unsafe-projection-delete.js +1 -1
  293. package/dist/engine/steps/unsafe-projection-upsert.js +4 -4
  294. package/dist/engine/steps/wait-for-event.js +4 -4
  295. package/dist/engine/steps/workflow-system-events.js +3 -3
  296. package/dist/engine/strip-disabled-features-from-app-schema.js +51 -14
  297. package/dist/engine/tier-resolver-extension.d.ts +29 -27
  298. package/dist/engine/tier-resolver-extension.js +26 -26
  299. package/dist/engine/transfer-adjacency.js +2 -2
  300. package/dist/engine/validate-projection-allowlist.js +9 -28
  301. package/dist/engine/walk-step-instances.d.ts +2 -0
  302. package/dist/engine/walk-step-instances.js +38 -0
  303. package/dist/engine/where-rule-lint.js +1 -1
  304. package/dist/engine/write-handler-qn-extract.js +1 -1
  305. package/dist/entrypoint/index.d.ts +4 -1
  306. package/dist/entrypoint/index.js +10 -4
  307. package/dist/env/index.d.ts +7 -6
  308. package/dist/env/index.js +10 -9
  309. package/dist/errors/docs-url.d.ts +1 -1
  310. package/dist/errors/docs-url.js +22 -2
  311. package/dist/errors/transition-details.js +7 -7
  312. package/dist/errors/write-error-info.d.ts +6 -6
  313. package/dist/errors/write-error-info.js +6 -6
  314. package/dist/es-ops/context.d.ts +9 -9
  315. package/dist/es-ops/context.js +29 -29
  316. package/dist/es-ops/index.js +7 -9
  317. package/dist/es-ops/operations-schema.js +4 -10
  318. package/dist/es-ops/runner.d.ts +11 -11
  319. package/dist/es-ops/runner.js +49 -53
  320. package/dist/es-ops/types.d.ts +55 -55
  321. package/dist/es-ops/types.js +11 -11
  322. package/dist/event-store/admin-api.js +7 -8
  323. package/dist/event-store/errors.js +1 -1
  324. package/dist/event-store/event-store.js +12 -6
  325. package/dist/event-store/rebuild-dead-letter.js +1 -1
  326. package/dist/files/bind-pending-file-refs.js +3 -3
  327. package/dist/files/create-file-ref-from-bytes.js +4 -1
  328. package/dist/files/feature.js +9 -10
  329. package/dist/files/file-handle.js +7 -9
  330. package/dist/files/file-ref-entity.js +15 -15
  331. package/dist/files/file-ref-table.js +7 -8
  332. package/dist/files/file-routes.d.ts +7 -0
  333. package/dist/files/file-routes.js +22 -13
  334. package/dist/files/in-memory-provider.js +8 -9
  335. package/dist/files/local-provider.js +18 -25
  336. package/dist/files/provider-resolver.d.ts +7 -7
  337. package/dist/files/resolve-content-type.js +43 -0
  338. package/dist/files/storage-tracking.js +2 -2
  339. package/dist/files/upload-ledger.js +7 -0
  340. package/dist/files/zip-stream.d.ts +12 -19
  341. package/dist/files/zip-stream.js +44 -63
  342. package/dist/i18n/required-surface-keys.d.ts +1 -1
  343. package/dist/i18n/required-surface-keys.js +188 -212
  344. package/dist/internal/stack-support.d.ts +2 -1
  345. package/dist/internal/stack-support.js +1 -1
  346. package/dist/jobs/job-runner-context.d.ts +32 -0
  347. package/dist/jobs/job-runner-context.js +305 -0
  348. package/dist/jobs/job-runner-execution.d.ts +7 -0
  349. package/dist/jobs/job-runner-execution.js +160 -0
  350. package/dist/jobs/job-runner-gates.d.ts +19 -0
  351. package/dist/jobs/job-runner-gates.js +198 -0
  352. package/dist/jobs/job-runner-helpers.d.ts +17 -3
  353. package/dist/jobs/job-runner-helpers.js +69 -4
  354. package/dist/jobs/job-runner-schedulers.d.ts +1 -1
  355. package/dist/jobs/job-runner-schedulers.js +3 -4
  356. package/dist/jobs/job-runner-scope.d.ts +44 -0
  357. package/dist/jobs/job-runner-scope.js +102 -0
  358. package/dist/jobs/job-runner-types.d.ts +12 -0
  359. package/dist/jobs/job-runner.d.ts +0 -1
  360. package/dist/jobs/job-runner.js +57 -669
  361. package/dist/lifecycle/lifecycle.js +3 -5
  362. package/dist/lifecycle/signal-handlers.js +5 -4
  363. package/dist/logging/utils.d.ts +2 -1
  364. package/dist/logging/utils.js +38 -10
  365. package/dist/migrations/index.js +1 -1
  366. package/dist/migrations/kumiko-drift.js +14 -13
  367. package/dist/migrations/pending-rebuilds.d.ts +21 -20
  368. package/dist/migrations/pending-rebuilds.js +27 -27
  369. package/dist/migrations/projection-table-index.d.ts +3 -3
  370. package/dist/migrations/projection-table-index.js +8 -8
  371. package/dist/observability/escape-hatch-report.d.ts +3 -0
  372. package/dist/observability/escape-hatch-report.js +12 -1
  373. package/dist/observability/metric-validator.js +1 -1
  374. package/dist/observability/metrics-handle.js +3 -3
  375. package/dist/pipeline/append-event-core.js +11 -2
  376. package/dist/pipeline/cascade-handler.d.ts +1 -1
  377. package/dist/pipeline/cascade-handler.js +165 -138
  378. package/dist/pipeline/dispatch-batch.js +2 -2
  379. package/dist/pipeline/dispatch-query.js +20 -13
  380. package/dist/pipeline/dispatch-shared.d.ts +14 -3
  381. package/dist/pipeline/dispatch-shared.js +67 -721
  382. package/dist/pipeline/dispatch-stream.js +1 -1
  383. package/dist/pipeline/dispatch-write.js +297 -190
  384. package/dist/pipeline/dispatcher-utils.d.ts +1 -1
  385. package/dist/pipeline/dispatcher-utils.js +1 -1
  386. package/dist/pipeline/event-consumer-state.js +2 -2
  387. package/dist/pipeline/event-dispatcher-admin.js +2 -1
  388. package/dist/pipeline/event-dispatcher-delivery.js +4 -6
  389. package/dist/pipeline/event-dispatcher.js +17 -28
  390. package/dist/pipeline/handler-context-auth.d.ts +9 -0
  391. package/dist/pipeline/handler-context-auth.js +54 -0
  392. package/dist/pipeline/handler-context-config.d.ts +14 -0
  393. package/dist/pipeline/handler-context-config.js +113 -0
  394. package/dist/pipeline/handler-context-db.d.ts +24 -0
  395. package/dist/pipeline/handler-context-db.js +149 -0
  396. package/dist/pipeline/handler-context-dispatch.d.ts +21 -0
  397. package/dist/pipeline/handler-context-dispatch.js +89 -0
  398. package/dist/pipeline/handler-context-events.d.ts +43 -0
  399. package/dist/pipeline/handler-context-events.js +237 -0
  400. package/dist/pipeline/handler-context-notify.d.ts +12 -0
  401. package/dist/pipeline/handler-context-notify.js +55 -0
  402. package/dist/pipeline/handler-context-observability.d.ts +7 -0
  403. package/dist/pipeline/handler-context-observability.js +21 -0
  404. package/dist/pipeline/handler-context-scope.d.ts +48 -0
  405. package/dist/pipeline/handler-context-scope.js +88 -0
  406. package/dist/pipeline/irreversible-operation-gate.js +1 -1
  407. package/dist/pipeline/lifecycle-pipeline.js +9 -9
  408. package/dist/pipeline/member-read-only-transaction.js +1 -3
  409. package/dist/pipeline/msp-rebuild.js +1 -1
  410. package/dist/pipeline/notify-scope.js +12 -1
  411. package/dist/pipeline/pre-handler-gates.js +20 -4
  412. package/dist/pipeline/pre-transaction-reservations.d.ts +1 -0
  413. package/dist/pipeline/pre-transaction-reservations.js +9 -3
  414. package/dist/pipeline/projection-rebuild.js +15 -13
  415. package/dist/pipeline/system-hooks.js +39 -41
  416. package/dist/pipeline/system-identity-switch.js +1 -3
  417. package/dist/pipeline/tenant-timezone-cache.js +1 -1
  418. package/dist/random/generate.d.ts +29 -29
  419. package/dist/random/generate.js +30 -30
  420. package/dist/random/words.js +20 -20
  421. package/dist/rate-limit/middleware.js +9 -5
  422. package/dist/redis/client.js +2 -2
  423. package/dist/redis/pubsub-signal.js +14 -17
  424. package/dist/schema-cli/apply-command.d.ts +2 -0
  425. package/dist/schema-cli/apply-command.js +97 -0
  426. package/dist/schema-cli/baseline-command.d.ts +2 -0
  427. package/dist/schema-cli/baseline-command.js +33 -0
  428. package/dist/schema-cli/generate-command.d.ts +2 -0
  429. package/dist/schema-cli/generate-command.js +90 -0
  430. package/dist/schema-cli/shared.d.ts +39 -0
  431. package/dist/schema-cli/shared.js +95 -0
  432. package/dist/schema-cli/status-command.d.ts +2 -0
  433. package/dist/schema-cli/status-command.js +73 -0
  434. package/dist/schema-cli/validate-boot-input.d.ts +5 -0
  435. package/dist/schema-cli/validate-boot-input.js +130 -0
  436. package/dist/schema-cli/validate-command.d.ts +2 -0
  437. package/dist/schema-cli/validate-command.js +130 -0
  438. package/dist/schema-cli.d.ts +2 -18
  439. package/dist/schema-cli.js +47 -510
  440. package/dist/search/index.js +4 -4
  441. package/dist/search/meilisearch-adapter.js +2 -1
  442. package/dist/search/purge-subject.js +4 -5
  443. package/dist/search/reindex-entity.js +6 -9
  444. package/dist/time/boot-tz-warning.d.ts +2 -2
  445. package/dist/time/boot-tz-warning.js +8 -8
  446. package/dist/time/iana.js +10 -10
  447. package/dist/time/index.js +9 -9
  448. package/dist/time/legacy-date.d.ts +2 -2
  449. package/dist/time/legacy-date.js +6 -8
  450. package/dist/time/polyfill.js +1 -1
  451. package/dist/time/tz-context.d.ts +1 -1
  452. package/dist/time/tz-context.js +1 -1
  453. package/dist/ui-types/app-schema.js +7 -7
  454. package/dist/ui-types/list-row-meta.js +4 -4
  455. package/dist/upgrade-cli.js +159 -130
  456. package/dist/utils/compare.js +1 -1
  457. package/package.json +5 -5
  458. package/src/changes.json +358 -0
  459. package/src/scripts/codemod/crypto-shredding-testing-move.ts +2 -2
  460. package/src/scripts/codemod/feature-layout.ts +10 -2
  461. package/src/scripts/codemod/migrate-cross-tenant.ts +1 -1
  462. package/src/scripts/codemod/migrate-open-to-all.ts +1 -1
  463. package/src/scripts/codemod/object-form.ts +41 -4
  464. package/src/scripts/codemod/package-merge.ts +58 -8
  465. package/src/scripts/codemod/pii-personal-migration.ts +192 -153
  466. package/src/scripts/codemod/testing-injection.ts +122 -42
  467. package/src/scripts/codemod/testing-move.ts +29 -7
  468. package/src/scripts/codemod/unprocessable-error-details-reason.ts +2 -2
@@ -26,6 +26,9 @@ const writesDeniedReasons = new WeakMap();
26
26
  export function denyTenantDbWrites(tenantDb, reason) {
27
27
  writesDeniedReasons.set(tenantDb, reason);
28
28
  }
29
+ export function tenantDbWritesDeniedReason(tenantDb) {
30
+ return writesDeniedReasons.get(tenantDb);
31
+ }
29
32
  export function tenantDbWritesDeniedError(operation, reason) {
30
33
  return new InternalError({
31
34
  message: `${operation}: writes are not available ${reason}; run the write through ctx.write so it gets its own transaction`,
@@ -16,7 +16,7 @@ export declare function withExecutorManagedWrites(runner: DbRunner): ExecutorMan
16
16
  export declare function withoutExecutorManagedWrites(runner: DbRunner): DbRunner;
17
17
  export declare function assertPersonalDataWrite(db: TenantDb, tableName: string, keys: readonly string[], entity: EntityDefinition): void;
18
18
  export declare function unsafeRawForDeclaredStep(holder: TenantDb | UncheckedSystemDb, reason: string): DbRunner;
19
- export declare function withSystemDbUnsafeRawGrant(systemDb: UncheckedSystemDb, grant: EscapeHatchDeclaration | undefined, deniedCallerLabel: string): UncheckedSystemDb;
19
+ export declare function withSystemDbUnsafeRawGrant<T extends object>(systemDb: T, grant: EscapeHatchDeclaration | undefined, deniedCallerLabel: string): T | UncheckedSystemDb;
20
20
  export declare function createUncheckedSystemDb(db: TenantDb, dbOutsideTransaction?: TenantDb, report?: EscapeHatchReporter): UncheckedSystemDb;
21
21
  export declare function createSystemDbView(db: TenantDb, dbOutsideTransaction?: TenantDb, report?: EscapeHatchReporter): UncheckedSystemDb;
22
22
  export declare function scopeTenantDbToTenant(db: TenantDb, tenantId: TenantId): TenantDb;
@@ -34,7 +34,7 @@ export type TenantDbGrants = {
34
34
  readonly writesDeniedReason?: string;
35
35
  };
36
36
  export type PersonalDataGate = (tableName: string, keys: readonly string[], entity?: EntityDefinition) => void;
37
- export declare function withUnsafeRawGrant(tenantDb: TenantDb, grant: EscapeHatchDeclaration | undefined): TenantDb;
37
+ export declare function withUnsafeRawGrant<T extends object>(tenantDb: T, grant: EscapeHatchDeclaration | undefined): T | TenantDb;
38
38
  export declare function acknowledgeConventionCrossTenant(tenantDb: TenantDb, reason: string, options?: {
39
39
  readonly deferReport?: boolean;
40
40
  }): TenantDb;
@@ -10,7 +10,7 @@ import { bindProjectionRegistry, projectionRegistryOf } from "./projection-regis
10
10
  import { acquireNamespacedAdvisoryLock } from "./queries/advisory-lock.js";
11
11
  import { queryDatabaseNow } from "./queries/database-now.js";
12
12
  import { hasFieldProperty, tableNameOf } from "./table-introspection.js";
13
- import { bindTenantDbRunner, denyTenantDbWrites, tenantDbRunner, tenantDbWritesDeniedError, } from "./tenant-db-runner.js";
13
+ import { bindTenantDbRunner, denyTenantDbWrites, tenantDbRunner, tenantDbWritesDeniedError, tenantDbWritesDeniedReason, } from "./tenant-db-runner.js";
14
14
  export { SYSTEM_SCOPE_CHECK_BRAND, } from "@cosmicdrift/kumiko-types/tenant-db-types";
15
15
  const declaredUnsafeRawRunners = new WeakMap();
16
16
  // The CRUD executor writes through tenantDbRunner, not insertOne, so it asks the
@@ -26,6 +26,9 @@ function boundRunner(runner, gate, projectionRegistry, executorManagedWrites) {
26
26
  const proxy = new Proxy(runner, {
27
27
  // Tagged-template calls need the real driver object as `this`.
28
28
  apply(target, _thisArg, args) {
29
+ if (typeof target !== "function") {
30
+ throw new TypeError("tenant-db runner proxy applied on a non-callable runner");
31
+ }
29
32
  return Reflect.apply(target, target, args);
30
33
  },
31
34
  get(target, prop, _receiver) {
@@ -51,7 +54,11 @@ function boundRunner(runner, gate, projectionRegistry, executorManagedWrites) {
51
54
  }
52
55
  const gatedArgs = [
53
56
  ...args.slice(0, -1),
54
- (tx) => callback(boundRunner(tx, gate, projectionRegistry, executorManagedWrites)),
57
+ (tx) => {
58
+ // @cast-boundary db-runner — driver callback hands over the tx handle untyped
59
+ const txRunner = tx;
60
+ return callback(boundRunner(txRunner, gate, projectionRegistry, executorManagedWrites));
61
+ },
55
62
  ];
56
63
  return Reflect.apply(value, target, gatedArgs);
57
64
  };
@@ -98,9 +105,7 @@ export function assertPersonalDataWrite(db, tableName, keys, entity) {
98
105
  }
99
106
  // Framework-private (not re-exported from db/index.ts): same grant check + audit as unsafeRaw, for engine forwarding.
100
107
  export function unsafeRawForDeclaredStep(holder, reason) {
101
- if (reason.trim().length === 0) {
102
- throw new Error("unsafeRawForDeclaredStep requires a non-empty reason");
103
- }
108
+ assertReason("unsafeRawForDeclaredStep", reason);
104
109
  const runner = declaredUnsafeRawRunners.get(holder);
105
110
  if (!runner) {
106
111
  throw new InternalError({
@@ -179,7 +184,7 @@ function buildUncheckedSystemDb(db, dbOutsideTransaction, report, gate) {
179
184
  // Reference rows (tenantId === SYSTEM_TENANT_ID) are allowed, mirroring
180
185
  // "tenant"-mode readWhere's own [tenantId, SYSTEM_TENANT_ID] allowlist.
181
186
  assertRowsTenant(rows, tenantField) {
182
- const hasOffender = rows.some((row) => !allowedTenantIds.includes(row[tenantField]));
187
+ const hasOffender = rows.some((row) => !allowedTenantIds.some((allowed) => allowed === row[tenantField]));
183
188
  if (hasOffender) {
184
189
  throw new AccessDeniedError({
185
190
  message: `systemScope() row tenant self-check failed on field "${String(tenantField)}"`,
@@ -250,7 +255,13 @@ export function createSystemDbView(db, dbOutsideTransaction, report = fallbackEs
250
255
  // same connection/transaction. For code that must run caller-supplied callbacks (e.g. config
251
256
  // validators) without handing them a cross-tenant view.
252
257
  export function scopeTenantDbToTenant(db, tenantId) {
253
- return createTenantDb({ db: tenantDbRunner(db), tenantId, mode: "tenant" });
258
+ const writesDeniedReason = tenantDbWritesDeniedReason(db);
259
+ return createTenantDb({
260
+ db: tenantDbRunner(db),
261
+ tenantId,
262
+ mode: "tenant",
263
+ ...(writesDeniedReason !== undefined && { grants: { writesDeniedReason } }),
264
+ });
254
265
  }
255
266
  // @cast-boundary tenant-db-row
256
267
  export function castTenantRows(rows) {
@@ -261,6 +272,18 @@ export { tableNameOf };
261
272
  function hasGrant(decl) {
262
273
  return decl !== undefined && decl.reason.trim().length > 0;
263
274
  }
275
+ // Tenant ids left over when the caller excludes some via ne/notIn; undefined for other operator forms.
276
+ function tenantIdsAfterExclusion(requested, allowed) {
277
+ if (!isPlainObject(requested))
278
+ return undefined;
279
+ if ("ne" in requested)
280
+ return allowed.filter((t) => t !== requested["ne"]);
281
+ if (Array.isArray(requested["notIn"])) {
282
+ const excluded = requested["notIn"];
283
+ return allowed.filter((t) => !excluded.includes(t));
284
+ }
285
+ return undefined;
286
+ }
264
287
  // Tenant ids a where.tenantId filter asks for; undefined for operator forms
265
288
  // that cannot narrow to a tenant list (ne, like, ...).
266
289
  export function requestedTenantIds(requested) {
@@ -422,7 +445,13 @@ function buildTenantDb(options) {
422
445
  const allowed = [tenantId, SYSTEM_TENANT_ID];
423
446
  const requested = where?.["tenantId"];
424
447
  if (requested !== undefined) {
425
- const requestedList = requestedTenantIds(requested) ?? [];
448
+ const requestedList = requestedTenantIds(requested);
449
+ // An explicit exclusion or empty list must keep excluding, never widen to the full scope.
450
+ if (requestedList === undefined) {
451
+ return { ...where, tenantId: tenantIdsAfterExclusion(requested, allowed) ?? allowed };
452
+ }
453
+ if (requestedList.length === 0)
454
+ return { ...where, tenantId: [] };
426
455
  const narrowed = requestedList.filter((t) => typeof t === "string" && allowed.includes(t));
427
456
  return { ...where, tenantId: narrowed.length > 0 ? narrowed : allowed };
428
457
  }
@@ -457,6 +486,14 @@ function buildTenantDb(options) {
457
486
  function globalWriteReason() {
458
487
  return grants?.globalWrites?.reason ?? "";
459
488
  }
489
+ // The table is the audit target: process-level dedup would otherwise collapse a cron's
490
+ // writes to several global tables into one audit entry.
491
+ function reportGlobalWrite(table) {
492
+ report("global-write", globalWriteReason(), {
493
+ id: tableNameOf(table),
494
+ tenantId: tenantId ?? SYSTEM_TENANT_ID,
495
+ });
496
+ }
460
497
  function personalDataDenied(table, keys) {
461
498
  if (!personalDataGate)
462
499
  return undefined;
@@ -528,7 +565,7 @@ function buildTenantDb(options) {
528
565
  personalDataDenied(table, Object.keys(values));
529
566
  if (denied)
530
567
  return Promise.reject(denied);
531
- report("global-write", globalWriteReason());
568
+ reportGlobalWrite(table);
532
569
  return withDbSpan("insert", table, async () => insertRow(table, values));
533
570
  },
534
571
  updateMany(set, where) {
@@ -542,7 +579,7 @@ function buildTenantDb(options) {
542
579
  if (!where || Object.keys(where).length === 0) {
543
580
  return Promise.reject(new Error("db.global().updateMany without where would mass-update every tenant's rows. Pass at least one where condition."));
544
581
  }
545
- report("global-write", globalWriteReason());
582
+ reportGlobalWrite(table);
546
583
  return withDbSpan("update", table, async () => updateRows(table, set, where));
547
584
  },
548
585
  deleteMany(where) {
@@ -554,7 +591,7 @@ function buildTenantDb(options) {
554
591
  if (!where || Object.keys(where).length === 0) {
555
592
  return Promise.reject(new Error("db.global().deleteMany without where would mass-delete every tenant's rows. Pass at least one where condition."));
556
593
  }
557
- report("global-write", globalWriteReason());
594
+ reportGlobalWrite(table);
558
595
  return withDbSpan("delete", table, async () => {
559
596
  await deleteRows(table, where);
560
597
  });
@@ -0,0 +1,6 @@
1
+ import type { WhereObject } from "@cosmicdrift/kumiko-types/where-clause-types";
2
+ import type { TableInfo } from "./table-info.ts";
3
+ export declare function buildWhereClause(info: TableInfo, where: WhereObject, startIndex: number, qualifier?: string): {
4
+ sqlText: string;
5
+ values: unknown[];
6
+ };
@@ -0,0 +1,236 @@
1
+ import { propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
2
+ import { computeBlindIndex, configuredBlindIndexKey } from "../crypto/blind-index.js";
3
+ import { quoteIdent } from "../crypto/ciphertext-pattern.js";
4
+ import { prepareJsonbValue, prepareValue } from "./driver-value-preparation.js";
5
+ function isJsonTextMatchList(value) {
6
+ return Array.isArray(value);
7
+ }
8
+ function isWhereOperator(v) {
9
+ if (v === null || typeof v !== "object" || Array.isArray(v))
10
+ return false;
11
+ // Don't false-positive on Date / Temporal / other domain-objects.
12
+ // WhereOperator is plain object literal with at most these keys.
13
+ const keys = Object.keys(v);
14
+ if (keys.length === 0)
15
+ return false;
16
+ return keys.every((key) => Object.hasOwn(OPERATOR_EMITTERS, key));
17
+ }
18
+ function isJsonbScalar(value) {
19
+ return value !== null && typeof value !== "object";
20
+ }
21
+ function valueList(target, values) {
22
+ return values
23
+ .map((v) => {
24
+ const p = prepareValue(v, target.pgType);
25
+ return p.kind === "literal" ? p.literal : `${target.bind(p.bound)}${p.sql}`;
26
+ })
27
+ .join(", ");
28
+ }
29
+ // multiSelect (and any other jsonb-array-of-scalars column) stores an
30
+ // array — a filter value is one option, not the whole array, so eq/ne/in
31
+ // must check array containment (`@>`) instead of scalar `=`/`<>`/`IN`
32
+ // against the jsonb column, which Postgres rejects outright.
33
+ // Mirrors applyMultiSelectFilter in event-store-executor-read-filter.ts — keep in lock-step.
34
+ function jsonbContainsAny(target, candidates) {
35
+ const { column, ref, bind } = target;
36
+ if (candidates.length === 0)
37
+ return "FALSE";
38
+ const parts = [];
39
+ for (const v of candidates) {
40
+ const p = prepareJsonbValue([v]);
41
+ // prepareJsonbValue never returns kind:"literal" (only prepareValue's
42
+ // isSqlExpression branch does) — narrow anyway, PreparedValue is a union.
43
+ if (p && p.kind === "param") {
44
+ parts.push(`${ref(column)} @> ${bind(p.bound)}${p.sql}`);
45
+ }
46
+ // A jsonb column holding the scalar itself is never `@>` an array of it.
47
+ if (isJsonbScalar(v)) {
48
+ parts.push(`${ref(column)} = ${bind(JSON.stringify(v))}::text::jsonb`);
49
+ }
50
+ }
51
+ return parts.length > 0 ? `(${parts.join(" OR ")})` : "FALSE";
52
+ }
53
+ function isValidJsonTextMatch(match) {
54
+ if (match === null || typeof match !== "object")
55
+ return false;
56
+ const keys = propertyOf(match, "keys");
57
+ return (Array.isArray(keys) &&
58
+ keys.length > 0 &&
59
+ keys.every((k) => typeof k === "string") &&
60
+ typeof propertyOf(match, "eq") === "string");
61
+ }
62
+ function jsonTextMatchCondition(target, match) {
63
+ const { column, ref, bind } = target;
64
+ if (!isValidJsonTextMatch(match)) {
65
+ throw new Error(`where: jsonText on column "${column}" needs non-empty string keys and a string eq`);
66
+ }
67
+ const lookups = match.keys.map((key) => `${ref(column)}->>${bind(key)}::text`);
68
+ const lhs = lookups.length === 1 ? lookups[0] : `COALESCE(${lookups.join(", ")})`;
69
+ return `(${lhs} = ${bind(match.eq)})`;
70
+ }
71
+ function emitJsonText(target, operator) {
72
+ const { column, pgType } = target;
73
+ const operand = operator.jsonText;
74
+ if (operand === undefined)
75
+ return undefined;
76
+ if (Object.keys(operator).length > 1) {
77
+ throw new Error(`where: jsonText on column "${column}" cannot be combined with other operators`);
78
+ }
79
+ if (pgType !== "jsonb") {
80
+ throw new Error(`where: jsonText requires a jsonb column, "${column}" is ${pgType}`);
81
+ }
82
+ const matches = isJsonTextMatchList(operand) ? operand : [operand];
83
+ if (matches.length === 0)
84
+ return "FALSE";
85
+ return matches.map((match) => jsonTextMatchCondition(target, match)).join(" AND ");
86
+ }
87
+ function comparisonCondition(target, symbol, operand) {
88
+ const { column, pgType, ref, bind } = target;
89
+ const p = prepareValue(operand, pgType);
90
+ return p.kind === "literal"
91
+ ? `${ref(column)} ${symbol} ${p.literal}`
92
+ : `${ref(column)} ${symbol} ${bind(p.bound)}${p.sql}`;
93
+ }
94
+ function comparisonEmitter(key, symbol) {
95
+ return (target, operator) => {
96
+ const operand = operator[key];
97
+ if (operand === undefined)
98
+ return undefined;
99
+ // Mirrors applyMultiSelectFilter: ordering/pattern ops have no meaning on a jsonb
100
+ // option array (`like` would crash as `jsonb ~~ jsonb`), so match nothing.
101
+ if (target.pgType === "jsonb")
102
+ return "FALSE";
103
+ return comparisonCondition(target, symbol, operand);
104
+ };
105
+ }
106
+ // Mirrors applyMultiSelectFilter: an array `ne` means "does not contain all of these".
107
+ function jsonbNotEqualCondition(target, operand) {
108
+ const { column, ref, bind } = target;
109
+ if (Array.isArray(operand)) {
110
+ if (operand.length === 0)
111
+ return "FALSE";
112
+ const p = prepareJsonbValue(operand);
113
+ if (p && p.kind === "param") {
114
+ return `(${ref(column)} IS NULL OR NOT (${ref(column)} @> ${bind(p.bound)}${p.sql}))`;
115
+ }
116
+ }
117
+ if (isJsonbScalar(operand)) {
118
+ const p = prepareJsonbValue([operand]);
119
+ if (p && p.kind === "param") {
120
+ return `(${ref(column)} IS NULL OR NOT (${ref(column)} @> ${bind(p.bound)}${p.sql} OR ${ref(column)} = ${bind(JSON.stringify(operand))}::text::jsonb))`;
121
+ }
122
+ }
123
+ return undefined;
124
+ }
125
+ function emitNotEqual(target, operator) {
126
+ const operand = operator.ne;
127
+ if (operand === undefined)
128
+ return undefined;
129
+ if (operand === null && Object.keys(operator).length === 1) {
130
+ return `${target.ref(target.column)} IS NOT NULL`;
131
+ }
132
+ if (target.pgType === "jsonb") {
133
+ const jsonbCondition = jsonbNotEqualCondition(target, operand);
134
+ if (jsonbCondition !== undefined)
135
+ return jsonbCondition;
136
+ }
137
+ return comparisonCondition(target, "<>", operand);
138
+ }
139
+ function emitIn(target, operator) {
140
+ const operand = operator.in;
141
+ if (!Array.isArray(operand))
142
+ return undefined;
143
+ if (target.pgType === "jsonb")
144
+ return jsonbContainsAny(target, operand);
145
+ if (operand.length === 0)
146
+ return "FALSE";
147
+ return `${target.ref(target.column)} IN (${valueList(target, operand)})`;
148
+ }
149
+ function emitNotIn(target, operator) {
150
+ const operand = operator.notIn;
151
+ if (!Array.isArray(operand))
152
+ return undefined;
153
+ if (target.pgType === "jsonb") {
154
+ throw new Error(`where: notIn is not supported on jsonb column "${target.column}"`);
155
+ }
156
+ if (operand.length === 0)
157
+ return "TRUE";
158
+ return `${target.ref(target.column)} NOT IN (${valueList(target, operand)})`;
159
+ }
160
+ // Insertion order is the SQL emission order. jsonText comes first so its "cannot be combined" error wins over any other
161
+ // operator's error; the rest keep the historical order gt, gte, lt, lte, ne, like, in, notIn.
162
+ const OPERATOR_EMITTERS = {
163
+ jsonText: emitJsonText,
164
+ gt: comparisonEmitter("gt", ">"),
165
+ gte: comparisonEmitter("gte", ">="),
166
+ lt: comparisonEmitter("lt", "<"),
167
+ lte: comparisonEmitter("lte", "<="),
168
+ ne: emitNotEqual,
169
+ like: comparisonEmitter("like", "LIKE"),
170
+ in: emitIn,
171
+ notIn: emitNotIn,
172
+ };
173
+ function operatorConditions(target, operator) {
174
+ const conditions = [];
175
+ for (const emit of Object.values(OPERATOR_EMITTERS)) {
176
+ const condition = emit(target, operator);
177
+ if (condition !== undefined)
178
+ conditions.push(condition);
179
+ }
180
+ return conditions;
181
+ }
182
+ // Blind-index OR rewrite: if the column has a bidx
183
+ // counterpart (suffix convention `<col>_bidx`, framework-reserved)
184
+ // and a key is configured, equality matches both arms —
185
+ // plaintext legacy rows via the plaintext arm, encrypted rows
186
+ // via the HMAC. The `kumiko-pii:` prefix on ciphertexts makes
187
+ // a random match on the plaintext arm practically impossible.
188
+ function equalityCondition(target, value) {
189
+ const { info, column, pgType, ref, bind } = target;
190
+ const p = prepareValue(value, pgType);
191
+ if (p.kind === "literal")
192
+ return `${ref(column)} = ${p.literal}`;
193
+ const bidxKey = configuredBlindIndexKey();
194
+ const bidxCol = `${column}_bidx`;
195
+ if (bidxKey !== undefined &&
196
+ typeof value === "string" &&
197
+ !column.endsWith("_bidx") &&
198
+ info.hasColumn(bidxCol)) {
199
+ return `(${ref(column)} = ${bind(p.bound)} OR ${ref(bidxCol)} = ${bind(computeBlindIndex(bidxKey, value))})`;
200
+ }
201
+ return `${ref(column)} = ${bind(p.bound)}${p.sql}`;
202
+ }
203
+ function arrayCondition(target, values) {
204
+ if (values.length === 0)
205
+ return "FALSE";
206
+ if (target.pgType === "jsonb")
207
+ return jsonbContainsAny(target, values);
208
+ return `${target.ref(target.column)} IN (${valueList(target, values)})`;
209
+ }
210
+ function conditionsForValue(target, value) {
211
+ if (value === null)
212
+ return [`${target.ref(target.column)} IS NULL`];
213
+ if (Array.isArray(value))
214
+ return [arrayCondition(target, value)];
215
+ if (isWhereOperator(value))
216
+ return operatorConditions(target, value);
217
+ if (target.pgType === "jsonb" && isJsonbScalar(value)) {
218
+ return [jsonbContainsAny(target, [value])];
219
+ }
220
+ return [equalityCondition(target, value)];
221
+ }
222
+ export function buildWhereClause(info, where, startIndex, qualifier) {
223
+ const values = [];
224
+ // Single place that renders a column reference, so a join qualifier reaches
225
+ // every arm (blind index, jsonb containment, IS NULL, jsonText) alike.
226
+ const ref = (column) => qualifier === undefined ? quoteIdent(column) : `${quoteIdent(qualifier)}.${quoteIdent(column)}`;
227
+ const bind = (value) => {
228
+ values.push(value);
229
+ return `$${startIndex + values.length - 1}`;
230
+ };
231
+ const conditions = Object.entries(where).flatMap(([field, value]) => {
232
+ const column = info.columnOf(field);
233
+ return conditionsForValue({ info, column, pgType: info.pgTypeOf(column), ref, bind }, value);
234
+ });
235
+ return { sqlText: conditions.join(" AND "), values };
236
+ }
@@ -12,6 +12,7 @@
12
12
  // Used by both the user-data-rights-defaults forget hook and the
13
13
  // data-retention hardDelete cleanup — moved here so neither copies the logic.
14
14
  import { assertSafeStorageKey } from "../files/types.js";
15
+ import { createFallbackLogger } from "../logging/utils.js";
15
16
  import { derivativeListPrefix, isDerivativeKeyOf } from "./variant-key.js";
16
17
  // Wrap rather than let a raw provider error (e.g. S3 AccessDenied) surface
17
18
  // unexplained — list() is a requirement on top of delete(); a bucket policy
@@ -55,6 +56,7 @@ export async function deleteStoredFileAndDerivatives(originalKey, store, errorCo
55
56
  ...(await listDerivativeKeys(originalKey, store, errorContext)),
56
57
  ];
57
58
  const failedKeys = [];
59
+ const log = createFallbackLogger(errorContext);
58
60
  for (const key of keysToDelete) {
59
61
  try {
60
62
  assertSafeStorageKey(key);
@@ -62,8 +64,10 @@ export async function deleteStoredFileAndDerivatives(originalKey, store, errorCo
62
64
  }
63
65
  catch (err) {
64
66
  failedKeys.push(key);
65
- // biome-ignore lint/suspicious/noConsole: operator-visibility for binary-cleanup-failure
66
- console.warn(`[${errorContext}] storage delete failed key=${key} err=${err instanceof Error ? err.message : String(err)}`);
67
+ log.warn("storage delete failed", {
68
+ key,
69
+ error: err instanceof Error ? err.message : String(err),
70
+ });
67
71
  }
68
72
  }
69
73
  return failedKeys;
@@ -116,7 +116,7 @@ async function resolveOverlaySpec(spec, row, registry, tenantId) {
116
116
  // allowlisted before it becomes a response Content-Type or storage-provider
117
117
  // metadata. Otherwise an app whose renderer wildcard is broad enough (e.g.
118
118
  // `image/*`) would let a client-declared `image/svg+xml` ride straight
119
- // through as an honestly-declared (non-sniffed) active-content type (#2021).
119
+ // through as an honestly-declared (non-sniffed) active-content type.
120
120
  const KNOWN_SAFE_VARIANT_MIME_TYPES = new Set([
121
121
  "image/jpeg",
122
122
  "image/png",
@@ -1,4 +1,6 @@
1
+ import { createFallbackLogger } from "../../logging/utils.js";
1
2
  import { normalizeAccessEntry } from "../ownership.js";
3
+ const log = createFallbackLogger("kumiko:boot");
2
4
  const BUILTIN_ROLES = new Set(["all", "system"]);
3
5
  function addRole(roleHandlers, role, identifier) {
4
6
  let handlers = roleHandlers.get(role);
@@ -50,7 +52,7 @@ function addEntityFieldRoles(roleHandlers, f) {
50
52
  // purpose) are the normal case, not a typo — and until every access
51
53
  // surface is scanned, a role can look unique here while it's really used
52
54
  // elsewhere (configKeys / entity+field access), a false positive in the
53
- // other direction. Both is why this stays opt-in (#1711) rather than a
55
+ // other direction. This is why this stays opt-in rather than a
54
56
  // default-on prod warning.
55
57
  export function warnOnUniqueAccessRoles(features) {
56
58
  // role → set of distinct identifiers using it, across every access
@@ -68,8 +70,7 @@ export function warnOnUniqueAccessRoles(features) {
68
70
  if (handlers.size !== 1)
69
71
  continue;
70
72
  const [identifier] = handlers;
71
- // biome-ignore lint/suspicious/noConsole: boot-time dev hint, no logger available yet
72
- console.warn(`[kumiko:boot] Access role "${role}" is only used by one handler (${identifier}). ` +
73
+ log.warn(`Access role "${role}" is only used by one handler (${identifier}). ` +
73
74
  `This is often a typo — the role is unknown to every other handler in the boot scan. ` +
74
75
  `If this is intentional, ignore this warning.`);
75
76
  }
@@ -1,16 +1,17 @@
1
+ import { createFallbackLogger } from "../../logging/utils.js";
2
+ const log = createFallbackLogger("kumiko:boot");
1
3
  // --- Cross-feature API exposure / usage matching ---
2
4
  //
3
5
  // `r.exposesApi(name, impl)` registers a callable; `r.usesApi(name)`
4
- // declares a caller. Boot-Validator prüft drei Invarianten:
5
- // 1. Jeder usesApi(name) findet einen exposesApi(name) in irgendeinem
6
- // Feature.
7
- // 2. Das exposing-Feature ist in requires/optionalRequires des callers
8
- // gelisted (sonst klappt die Cross-Feature-Aufruf-Reihenfolge nicht).
9
- // 3. Self-exposure ist erlaubt (Feature ruft eigene API), wird aber
10
- // mit Warning markiert weil es typisch ein Refactor-Restbestand ist.
6
+ // declares a caller. Boot validation checks three invariants:
7
+ // 1. Every usesApi(name) finds an exposesApi(name) in some feature.
8
+ // 2. The exposing feature is listed in the caller's
9
+ // requires/optionalRequires so cross-feature call ordering works.
10
+ // 3. Self-exposure is allowed (a feature calling its own API), but is
11
+ // warned about because it is typically a refactoring leftover.
11
12
  //
12
- // Globale Eindeutigkeit der apiNames (kein Dublicate über Features)
13
- // wird in validateBoot() vor dem Per-Feature-Walk geprüft.
13
+ // apiNames must be globally unique across features; validateBoot() checks
14
+ // this before the per-feature walk.
14
15
  export function validateApiExposureMatching(feature, allExposedApis, featureMap) {
15
16
  for (const apiName of feature.usedApis) {
16
17
  const providerFeature = allExposedApis.get(apiName);
@@ -19,17 +20,16 @@ export function validateApiExposureMatching(feature, allExposedApis, featureMap)
19
20
  throw new Error(`[Feature ${feature.name}] r.usesApi("${apiName}") but no feature exposes that API. Known exposed APIs: ${known}`);
20
21
  }
21
22
  if (providerFeature === feature.name) {
22
- // biome-ignore lint/suspicious/noConsole: boot-time dev hint, no logger available yet
23
- console.warn(`[kumiko:boot] [Feature ${feature.name}] r.usesApi("${apiName}") on its own r.exposesApi — typically a refactor leftover. Call the impl directly instead.`);
23
+ log.warn(`[Feature ${feature.name}] r.usesApi("${apiName}") on its own r.exposesApi — typically a refactor leftover. Call the impl directly instead.`);
24
24
  continue;
25
25
  }
26
26
  const allDeps = [...feature.requires, ...feature.optionalRequires];
27
27
  if (!allDeps.includes(providerFeature)) {
28
28
  throw new Error(`[Feature ${feature.name}] r.usesApi("${apiName}") is exposed by "${providerFeature}" but feature is not in requires/optionalRequires. Add r.requires({ features: ["${providerFeature}"] }).`);
29
29
  }
30
- // Sanity: provider feature actually exists in this app's feature set.
31
- // Should always be true if allExposedApis was built from `features`,
32
- // aber defensiv für unklare Constructor-Pfade.
30
+ // Sanity: the provider feature must exist in this app's feature set.
31
+ // This should always hold when allExposedApis was built from `features`,
32
+ // but remains defensive for unclear constructor paths.
33
33
  if (!featureMap.has(providerFeature)) {
34
34
  throw new Error(`[Feature ${feature.name}] internal: r.usesApi("${apiName}") points to provider "${providerFeature}" which is not in feature map. Register the provider feature in the app's feature list.`);
35
35
  }
@@ -13,7 +13,7 @@ export function validateFeatureBootChecks(features) {
13
13
  }
14
14
  catch (err) {
15
15
  const message = err instanceof Error ? err.message : String(err);
16
- throw new Error(`[Feature ${feature.name}] r.bootCheck failed: ${message}. Fix the condition this check reports.`);
16
+ throw new Error(`[Feature ${feature.name}] r.bootCheck failed: ${message}`);
17
17
  }
18
18
  }
19
19
  }
@@ -1,5 +1,7 @@
1
1
  import { optionsQueryFieldRefs } from "@cosmicdrift/kumiko-types/fields";
2
+ import { createFallbackLogger } from "../../logging/utils.js";
2
3
  import { isEncryptedAtRest } from "../config-helpers.js";
4
+ const log = createFallbackLogger("kumiko:boot");
3
5
  // --- Toggleable-dependency warnings ---
4
6
  //
5
7
  // When feature A declares r.requires({ features: ["B"] }) and B is toggleable with
@@ -15,8 +17,7 @@ export function warnOnToggleableDependencies(features, featureMap) {
15
17
  if (!depFeature)
16
18
  continue; // requires-target-missing is handled elsewhere
17
19
  if (depFeature.toggleableDefault === false) {
18
- // biome-ignore lint/suspicious/noConsole: boot-time dev hint, no logger available yet
19
- console.warn(`[kumiko:boot] Feature "${f.name}" requires "${dep}", which is toggleable(default=false). ` +
20
+ log.warn(`Feature "${f.name}" requires "${dep}", which is toggleable(default=false). ` +
20
21
  `"${f.name}" will be effectively disabled until "${dep}" is enabled globally via the feature-toggles feature. ` +
21
22
  `If this is intentional, ignore this warning; otherwise consider r.optionalRequires() or default=true.`);
22
23
  }
@@ -70,9 +71,9 @@ export function validateConfigKeyRequired(feature) {
70
71
  for (const [keyName, keyDef] of Object.entries(feature.configKeys ?? {})) {
71
72
  if (keyDef.required !== true)
72
73
  continue;
73
- // required heißt "Tenant MUSS konfigurieren" — ein non-empty default
74
- // oder ein computed-Resolver macht den Key nie unset, readiness könnte
75
- // die Lücke nie melden: der required-Flag wäre eine stille Lüge.
74
+ // `required` means the tenant must configure the key. A non-empty default
75
+ // or computed resolver means the key can never be unset, so readiness could
76
+ // never report the omission and the flag would be misleading.
76
77
  if (keyDef.computed !== undefined) {
77
78
  throw new Error(`[Feature ${feature.name}] Config key "${keyName}" has required=true AND a computed resolver — a computed key can never be missing; drop one of the two`);
78
79
  }
@@ -160,7 +161,7 @@ export function validateConfigKeyBacking(feature) {
160
161
  // read/write without it throws loud at request time, not silently.
161
162
  }
162
163
  }
163
- // --- Config key piiEncrypted compatibility (kumiko-platform#231/#459) ---
164
+ // --- Config key piiEncrypted compatibility ---
164
165
  export function validateConfigKeyPiiEncrypted(feature) {
165
166
  for (const [keyName, keyDef] of Object.entries(feature.configKeys)) {
166
167
  if (!keyDef.piiEncrypted)
@@ -1,10 +1,10 @@
1
1
  import { qualifyEntityName } from "../qualified-name.js";
2
- import { findEntityFeature } from "./screens.js";
2
+ import { findEntityFeature } from "./screens-kind-shared.js";
3
3
  // Entity name → the one screen that declares detailFor: "<entity>". Built
4
4
  // once, up front (before the per-feature validateScreens loop) so both this
5
- // module's own uniqueness check AND rowAction entity-targets (fw#2228,
6
- // screens.ts) resolve against the same map instead of re-walking every
7
- // feature's screens twice.
5
+ // module's own uniqueness check AND rowAction entity-targets (in screens-actions.ts)
6
+ // resolve against the same map instead of re-walking every feature's
7
+ // screens twice.
8
8
  export function collectDetailForScreens(features, featureMap) {
9
9
  const screenQnByEntity = new Map();
10
10
  const result = new Map();
@@ -9,6 +9,7 @@ export declare function validateHandlerAccess(feature: FeatureDefinition): void;
9
9
  export declare function validateAdditionalRateLimits(featureName: string, handlerName: string, handler: FeatureDefinition["writeHandlers"][string]): void;
10
10
  export declare function validatePerCommandTransaction(featureName: string, handlerName: string, handler: FeatureDefinition["writeHandlers"][string]): void;
11
11
  export declare function validateTenantlessAnonymous(featureName: string, handlerName: string, handler: FeatureDefinition["writeHandlers"][string]): void;
12
+ export declare function validateRateLimitCost(featureName: string, handlerName: string, handler: FeatureDefinition["writeHandlers"][string]): void;
12
13
  export declare function validateRateLimitDisabledReason(featureName: string, kind: "write" | "query" | "stream", handlerName: string, rateLimit: FeatureDefinition["writeHandlers"][string]["rateLimit"]): void;
13
14
  export declare function validateRateLimitBucketList(featureName: string, kind: "write" | "query" | "stream", handlerName: string, rateLimit: FeatureDefinition["writeHandlers"][string]["rateLimit"]): void;
14
15
  export declare function validateNoAllRoleInHandlerAccess(featureName: string, kind: "write" | "query" | "stream", handlerName: string, access: NonNullable<FeatureDefinition["writeHandlers"][string]["access"]>): void;