@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
@@ -63,9 +63,7 @@ export async function runInMemberReadOnlyTransaction(ctx, tx, fn) {
63
63
  });
64
64
  }
65
65
  // Savepoint even on the pool path: Postgres rejects a READ WRITE reset (SQLSTATE 25001) inside a subtransaction.
66
- return await transaction(pool, (outer) =>
67
- // @cast-boundary driver begin() handle — structurally a DbTx, same shape runInSavepoint's caller relies on
68
- runInDiscardedReadOnlySavepoint(outer, timeoutMs, true, fn));
66
+ return await transaction(pool, (outer) => runInDiscardedReadOnlySavepoint(outer, timeoutMs, true, fn));
69
67
  }
70
68
  catch (e) {
71
69
  if (e instanceof AccessDeniedError)
@@ -159,7 +159,7 @@ async function replayMultiStreamProjectionIntoShadow(mspName, deps) {
159
159
  durationMs: Date.now() - startedAt,
160
160
  // countColumnDrift is single-table (public.<t> vs kumiko_rebuild.<t>) and
161
161
  // not wired into the multi-stream rebuild path — MSPs don't run the
162
- // implicit-projection ghost-row guard either. #916 scope is single-table.
162
+ // implicit-projection ghost-row guard either. The scope is single-table.
163
163
  columnDriftCount: 0,
164
164
  };
165
165
  if (deps.meter) {
@@ -1,5 +1,6 @@
1
1
  import { assertNotificationVariables } from "../engine/template-variables.js";
2
2
  import { AccessDeniedError, FrameworkReasons } from "../errors/index.js";
3
+ import { parseJsonOrThrow } from "../utils/safe-json.js";
3
4
  function isTenantBroadcast(to) {
4
5
  return typeof to === "object" && to !== null && !Array.isArray(to) && "tenant" in to;
5
6
  }
@@ -12,6 +13,16 @@ export function assertNotifyTargetsOwnTenant(options, tenantId) {
12
13
  });
13
14
  }
14
15
  }
16
+ // Temporal values, functions and class instances in `data` are not structured-cloneable; the
17
+ // job pipeline serializes them to JSON on enqueue anyway, so the JSON form is the faithful snapshot.
18
+ function snapshotNotifyOptions(options) {
19
+ try {
20
+ return structuredClone(options);
21
+ }
22
+ catch {
23
+ return parseJsonOrThrow(JSON.stringify(options), "notify options snapshot");
24
+ }
25
+ }
15
26
  // Without `immediate`, a notify inside a write transaction waits for the commit so a
16
27
  // rolled-back write leaves no notification behind.
17
28
  export function createDeferringNotify(deps) {
@@ -24,7 +35,7 @@ export function createDeferringNotify(deps) {
24
35
  if (options.immediate === true)
25
36
  return notifyNow(notificationType, options);
26
37
  // The hook runs after the handler returned; a caller reusing `data` meanwhile must not change what is sent.
27
- const optionsAtCallTime = structuredClone(options);
38
+ const optionsAtCallTime = snapshotNotifyOptions(options);
28
39
  scheduleAfterCommit(async () => {
29
40
  await notifyAfterCommit(notificationType, optionsAtCallTime);
30
41
  });
@@ -2,16 +2,17 @@ import { hasAccess } from "../engine/access.js";
2
2
  import { tenantOverrideDenied } from "../engine/cross-tenant.js";
3
3
  import { checkWriteFieldRoles } from "../engine/field-access.js";
4
4
  import { runValidation } from "../engine/validation.js";
5
- import { AccessDeniedError, FrameworkReasons, isKumikoError, ValidationError, validationErrorFromZod, writeFailure, } from "../errors/index.js";
5
+ import { AccessDeniedError, FrameworkReasons, InternalError, isKumikoError, ValidationError, validationErrorFromZod, writeFailure, } from "../errors/index.js";
6
6
  import { checkFeatureEnabled, enforcePayloadRateLimits, enforceRateLimit, } from "./dispatch-shared.js";
7
7
  import { handlerAccessError } from "./handler-access-error.js";
8
8
  async function rejectDisabledFeature(ctx, type, user) {
9
9
  const disabledErr = await checkFeatureEnabled(ctx, type, user.tenantId);
10
10
  return disabledErr ? writeFailure(disabledErr) : undefined;
11
11
  }
12
- async function rejectRateLimited(ctx, handler, type, user) {
12
+ async function rejectRateLimited(ctx, handler, type, user, resolveCostOverride) {
13
13
  try {
14
- await enforceRateLimit(ctx, handler.rateLimit, type, user, ctx.registry.isHandlerSystemScoped(type));
14
+ const costOverride = resolveCostOverride?.();
15
+ await enforceRateLimit(ctx, handler.rateLimit, type, user, ctx.registry.isHandlerSystemScoped(type), costOverride);
15
16
  }
16
17
  catch (e) {
17
18
  if (isKumikoError(e))
@@ -20,6 +21,14 @@ async function rejectRateLimited(ctx, handler, type, user) {
20
21
  }
21
22
  return undefined;
22
23
  }
24
+ function normalizeRateLimitCost(handlerName, cost) {
25
+ if (!Number.isFinite(cost)) {
26
+ throw new InternalError({
27
+ message: `Handler "${handlerName}" rateLimitCost returned ${cost}; expected a finite number.`,
28
+ });
29
+ }
30
+ return Math.max(1, Math.ceil(cost));
31
+ }
23
32
  async function rejectPayloadRateLimited(ctx, handler, type, user, payload) {
24
33
  try {
25
34
  await enforcePayloadRateLimits(ctx, handler.additionalRateLimits, type, user, payload);
@@ -70,7 +79,7 @@ async function rejectBeforeParse(ctx, handler, type, user, mode) {
70
79
  const disabled = await rejectDisabledFeature(ctx, type, user);
71
80
  if (disabled)
72
81
  return disabled;
73
- if (mode === "charge") {
82
+ if (mode === "charge" && !handler.rateLimitCost) {
74
83
  const rateLimited = await rejectRateLimited(ctx, handler, type, user);
75
84
  if (rateLimited)
76
85
  return rateLimited;
@@ -85,6 +94,13 @@ async function rejectAfterParse(ctx, handler, type, user, payload, mode) {
85
94
  const overrideDenied = tenantOverrideDenied(user, payload);
86
95
  if (overrideDenied)
87
96
  return writeFailure(overrideDenied);
97
+ if (mode === "charge" && handler.rateLimitCost) {
98
+ // Cost comes from the validated payload, so this bucket is billed after the parse instead of before it.
99
+ const { rateLimitCost } = handler;
100
+ const rateLimited = await rejectRateLimited(ctx, handler, type, user, () => normalizeRateLimitCost(type, rateLimitCost(payload)));
101
+ if (rateLimited)
102
+ return rateLimited;
103
+ }
88
104
  if (mode === "charge") {
89
105
  const rateLimited = await rejectPayloadRateLimited(ctx, handler, type, user, payload);
90
106
  if (rateLimited)
@@ -7,6 +7,7 @@ type ReservationConfirm = ReservationHandle["confirmInTransaction"];
7
7
  export type ConsumedReservation = {
8
8
  readonly confirmInTransaction: ReservationConfirm | undefined;
9
9
  readonly prepared: unknown;
10
+ readonly parsedPayload: unknown;
10
11
  };
11
12
  export declare function hasPreTransactionPhase(handler: WriteHandlerDef): boolean;
12
13
  export declare function runAsCommand<T>(reserved: {
@@ -62,10 +62,10 @@ export async function reserveBeforeTransaction(ctx, commands, user, inheritedOri
62
62
  return { isSuccess: false, error: gates.error, failedIndex: index };
63
63
  }
64
64
  const origin = effectiveWriteOrigin(rootWriteOrigin(ctx.registry, command.type, user), inheritedOrigin);
65
- const handlerContext = await buildHandlerContext(ctx, command.type, user, origin);
66
65
  const event = { type: command.type, payload: gates.payload, user };
67
66
  let confirmInTransaction;
68
67
  if (handler.reserveBeforeTransaction) {
68
+ const handlerContext = await buildHandlerContext(ctx, command.type, user, origin);
69
69
  const handle = toReservationHandle(await handler.reserveBeforeTransaction(event, denyTransactionBoundSurfaces(handlerContext)));
70
70
  if (handle)
71
71
  releases.push(handle.release);
@@ -73,7 +73,9 @@ export async function reserveBeforeTransaction(ctx, commands, user, inheritedOri
73
73
  }
74
74
  let prepared;
75
75
  if (handler.beforeTransaction) {
76
- const phaseContext = await buildHandlerContext(ctx, command.type, user, origin, undefined, undefined, undefined, undefined, undefined, true);
76
+ const phaseContext = await buildHandlerContext(ctx, command.type, user, origin, {
77
+ readOnlyForTenant: true,
78
+ });
77
79
  const phase = await handler.beforeTransaction(event, applyBeforeTransactionReadOnly(phaseContext));
78
80
  if (!phase.isSuccess) {
79
81
  await releaseAll(ctx, releases);
@@ -83,7 +85,11 @@ export async function reserveBeforeTransaction(ctx, commands, user, inheritedOri
83
85
  releases.push(phase.release);
84
86
  prepared = phase.data;
85
87
  }
86
- reservedCommands.set(index, { confirmInTransaction, prepared });
88
+ reservedCommands.set(index, {
89
+ confirmInTransaction,
90
+ prepared,
91
+ parsedPayload: gates.payload,
92
+ });
87
93
  }
88
94
  catch (error) {
89
95
  await releaseAll(ctx, releases);
@@ -1,12 +1,15 @@
1
1
  import { countSubscribedEvents, finalizeProjectionRebuild, markProjectionRebuildFailed, markProjectionRebuilding, selectEventsForProjectionRebuildBatch, } from "../db/queries/projection-rebuild.js";
2
2
  import { assertLiveColumnsMatchMeta, assertLiveTableHasNoRowLevelSecurity, assertNoBlindIndexLoss, assertNoUnreachableLiveRows, buildShadowTable, countColumnDrift, ensureRebuildSchema, fenceLiveTable, rebuildMetaOrThrow, swapShadowIntoLive, } from "../db/queries/shadow-swap.js";
3
- import { coerceRow, extractTableInfo, runInSavepoint, selectMany } from "../db/query.js";
3
+ import { coerceRow, runInSavepoint, selectMany } from "../db/query.js";
4
4
  import { withoutStatementTimeout } from "../db/statement-timeout.js";
5
+ import { extractTableInfo } from "../db/table-info.js";
5
6
  import { withExecutorManagedWrites } from "../db/tenant-db.js";
6
7
  import { eventsTable, getEventsHighWaterMark, upcastStoredEvent, } from "../event-store/index.js";
7
8
  import { createRebuildDeadLetterTable, recordRebuildDeadLetters, } from "../event-store/rebuild-dead-letter.js";
9
+ import { createFallbackLogger } from "../logging/utils.js";
8
10
  import { emitProjectionRebuild } from "../observability/standard-metrics.js";
9
11
  import { projectionStateTable } from "./projection-state.js";
12
+ const log = createFallbackLogger("kumiko:projection-rebuild");
10
13
  // Events replayed per catch-up batch. Each batch is a fresh READ COMMITTED
11
14
  // SELECT, so a batch shorter than this means the currently-committed tail is
12
15
  // drained.
@@ -32,7 +35,7 @@ function rowToStoredEvent(row) {
32
35
  createdBy: row.createdBy,
33
36
  };
34
37
  }
35
- // Ops-facing log line for countColumnDrift's result. No-op when clean. #916.
38
+ // Ops-facing log line for countColumnDrift's result. No-op when clean.
36
39
  function warnOnColumnDrift(projectionName, tableName, drift) {
37
40
  // skip: no drift found — nothing to warn about
38
41
  if (drift.rowCount === 0)
@@ -41,7 +44,7 @@ function warnOnColumnDrift(projectionName, tableName, drift) {
41
44
  const countLabel = sampledIds.size < drift.rowCount
42
45
  ? `${drift.rowCount} (sample truncated)`
43
46
  : String(drift.rowCount);
44
- console.warn(`projection-rebuild "${projectionName}": ${countLabel} live row(s) in "${tableName}" have ` +
47
+ log.warn(`projection "${projectionName}": ${countLabel} live row(s) in "${tableName}" have ` +
45
48
  `column values that differ from the freshly-replayed shadow (sample: ${drift.sample.join(", ")}). ` +
46
49
  "Not blocking — replay wins on swap. Likely cause: a legacy direct-write predating the row's " +
47
50
  "event history (#494), or a sensitive custom field pending #972. Investigate if unexpected.");
@@ -71,7 +74,7 @@ async function replayProjectionIntoShadow(projectionName, deps) {
71
74
  let lastProcessedEventId = 0n;
72
75
  const skipApplyErrors = deps.errorPolicy?.skipApplyErrors === true;
73
76
  // Quarantined events, collected in memory and written once after the
74
- // replay settles — the #443 full-re-replay path would otherwise duplicate
77
+ // replay settles — the full-re-replay path would otherwise duplicate
75
78
  // the dead-letter rows.
76
79
  let skipped = [];
77
80
  // One chronological batch of events after lastProcessedEventId, applied into
@@ -145,7 +148,7 @@ async function replayProjectionIntoShadow(projectionName, deps) {
145
148
  // Fence the live table, then drain the final delta. Once ACCESS
146
149
  // EXCLUSIVE is held no concurrent apply can commit a new event, so this
147
150
  // loop terminates and the shadow ends up reflecting every committed
148
- // event — closing Phase 1's write-loss window for single-pod rebuilds.
151
+ // event — closing the write-loss window for single-pod rebuilds.
149
152
  await fenceLiveTable(tx, meta.tableName, fenceLockTimeoutMs);
150
153
  while ((await drainBatch(tx)) === REBUILD_BATCH_SIZE) {
151
154
  // keep draining full batches; a short batch ends the loop
@@ -153,7 +156,7 @@ async function replayProjectionIntoShadow(projectionName, deps) {
153
156
  // Fenced → the subscribed-event set is final (every live apply blocks
154
157
  // on the live-table lock). If fewer events were applied than exist, a
155
158
  // lower-id event committed late during the unlocked phase and the
156
- // id-cursor leapt past it (#443). Rebuild the shadow from scratch and
159
+ // id-cursor leapt past it. Rebuild the shadow from scratch and
157
160
  // replay the whole log under the fence: a fresh shadow means no
158
161
  // double-apply (the rejected "re-drain into a populated shadow" hazard),
159
162
  // and the full-replay cost is paid only on this rare detected path.
@@ -184,12 +187,12 @@ async function replayProjectionIntoShadow(projectionName, deps) {
184
187
  }
185
188
  // Guard the swap: abort if the process has no blind-index key configured
186
189
  // but the live table already has populated bidx columns — the rebuild
187
- // would otherwise silently null them out (fw#3091). Applies to every
190
+ // would otherwise silently null them out. Applies to every
188
191
  // projection, not just implicit ones: the shadow always gets a fresh
189
192
  // computeBlindIndexValues() pass regardless of projection kind.
190
193
  await assertNoBlindIndexLoss(tx, meta.tableName, meta, projectionName);
191
194
  // Guard the swap: abort if the live table holds a row no event can
192
- // reconstruct (#498 ghost — direct-inserted without a .created event),
195
+ // reconstruct (ghost — direct-inserted without a .created event),
193
196
  // which the swap would silently drop. Implicit projections only.
194
197
  if (projection.isImplicit === true) {
195
198
  await assertNoUnreachableLiveRows(tx, projectionName, meta.tableName, sourcesList, projection.extraSourceRowIds);
@@ -249,11 +252,10 @@ export async function getProjectionState(db, projectionName) {
249
252
  // The registry is the source-of-truth for which projections exist; the
250
253
  // state table holds per-projection rebuild info and may be sparse.
251
254
  //
252
- // Implicit-Projections (auto-registered pro r.entity, eine pro entity)
253
- // werden default ausgefiltert — sie sind als rebuild-Ziele weiter mit
254
- // `<feature>:projection:<entity>-entity` adressierbar, aber in `kumiko
255
- // project list` würden sie das Bild dominieren ohne Mehrwert. Mit
256
- // `{ includeImplicit: true }` opt-in einschalten.
255
+ // Implicit projections (auto-registered by r.entity, one per entity) are filtered
256
+ // out by default. They stay addressable as rebuild targets via
257
+ // `<feature>:projection:<entity>-entity`, but would dominate `kumiko project list`
258
+ // without adding information. Opt in with `{ includeImplicit: true }`.
257
259
  export async function listProjectionsWithState(db, registry, options = {}) {
258
260
  const projections = registry.getAllProjections();
259
261
  const stateRows = await selectMany(db, projectionStateTable);
@@ -4,25 +4,26 @@ import { collectSearchableSubjectFields, configuredPiiSubjectKms, decryptPiiFiel
4
4
  import { createTenantDb, entityTableFromRegistry, rehydrateCompoundTypes } from "../db/index.js";
5
5
  import { tenantChannel } from "../engine/constants.js";
6
6
  import { InternalError } from "../errors/index.js";
7
+ import { createFallbackLogger } from "../logging/utils.js";
8
+ const log = createFallbackLogger("kumiko:search");
7
9
  // --- Search Index Consumer (async, via event-dispatcher) ---
8
10
  //
9
- // Search-Indexierung läuft seit D.4 als async EventConsumer über den event-
10
- // dispatcher, nicht mehr als synchroner postSave/postDelete-hook. Das
11
- // spiegelt Marten's ISubscription-Pattern: ein einziger async Pfad für alle
12
- // non-inline side-effects.
11
+ // Search indexing runs as an async EventConsumer through the event dispatcher,
12
+ // rather than as a synchronous postSave/postDelete hook. This mirrors Marten's
13
+ // ISubscription pattern: one async path for all non-inline side effects.
13
14
  //
14
15
  // Event → Search-Op Mapping:
15
16
  //
16
17
  // <entity>.created → index(tenantId, doc) // state = event.payload
17
18
  // <entity>.updated → index(tenantId, doc) // state = { ...previous, ...changes }
18
19
  // <entity>.restored → index(tenantId, doc) // state = event.payload.previous
19
- // <entity>.deleted → remove(tenantId, type, id)
20
- // <entity>.forgotten → remove(tenantId, type, id)
21
- // <entity>.<named> → index or remove, state read from the live
22
- // projection row (#2765) — a named domain event
23
- // carries only a payload slice, not the full
24
- // entity, so there's no fixed shape to reconstruct
25
- // state from the way created/updated/restored do.
20
+ // <entity>.deleted → remove(tenantId, type, id)
21
+ // <entity>.forgotten → remove(tenantId, type, id)
22
+ // <entity>.<named> → index or remove, state read from the live
23
+ // projection row — a named domain event carries
24
+ // only a payload slice, not the full entity, so
25
+ // there's no fixed shape to reconstruct state from
26
+ // as there is for created/updated/restored.
26
27
  // Only queried when the entity declares searchable
27
28
  // fields/extensions; no row (hard/soft-deleted) →
28
29
  // remove, same as the deleted branch.
@@ -36,7 +37,7 @@ import { InternalError } from "../errors/index.js";
36
37
  // batchHandler collapses a turn's events to one search-op per
37
38
  // (tenantId, entityType, entityId) and issues one indexBatch/removeBatch
38
39
  // call per tenant instead of one round-trip per event; handler stays the
39
- // per-event fallback the dispatcher falls back to when a batch throws.
40
+ // per-event fallback the dispatcher uses when a batch throws.
40
41
  export const SEARCH_CONSUMER_NAME = "system:consumer:search";
41
42
  // An executor update with includeDeleted leaves the row deleted; indexing it would make it findable again.
42
43
  function isUpdateOfSoftDeletedRow(event, verb) {
@@ -58,7 +59,7 @@ async function resolveSearchOperation(event, ctx, registry) {
58
59
  state = reconstructStateForSearch(event.payload, verb);
59
60
  }
60
61
  else {
61
- // #2765 — named domain event: no fixed payload shape to reconstruct
62
+ // Named domain event: no fixed payload shape to reconstruct
62
63
  // state from. Only worth a projection read when the entity actually
63
64
  // declared searchable fields/extensions — same check
64
65
  // buildSearchDocument makes below, done here first so a non-
@@ -68,7 +69,7 @@ async function resolveSearchOperation(event, ctx, registry) {
68
69
  (registry.getSearchableFields(entityName).length > 0 ||
69
70
  registry.getSearchPayloadExtensions(entityName).length > 0);
70
71
  // skip: entity declares no searchable fields/extensions — nothing to
71
- // index, stays a no-op with no extra query, same as before #2765.
72
+ // index, stays a no-op with no extra query, same as before.
72
73
  if (!entity || !isSearchable)
73
74
  return undefined;
74
75
  const row = await readProjectionRowForSearch(ctx, registry, entityName, entity, tenantId, event.aggregateId);
@@ -183,7 +184,7 @@ export function createSearchEventConsumer(searchAdapter, registry) {
183
184
  },
184
185
  };
185
186
  }
186
- // #2765 — reads the live projection row for a named domain event, tenant-
187
+ // Reads the live projection row for a named domain event, tenant-
187
188
  // scoped through createTenantDb the same way MultiStreamProjection appliers
188
189
  // scope their apply()-writes (see api/server.ts), on the table the registry
189
190
  // actually booted (entityTableFromRegistry) rather than one freshly derived
@@ -217,7 +218,7 @@ async function readProjectionRowForSearch(ctx, registry, entityName, entity, ten
217
218
  }
218
219
  return state;
219
220
  }
220
- // #1610 — subject-annotated searchable fields are ciphertext in the event
221
+ // Subject-annotated searchable fields are ciphertext in the event
221
222
  // payload; decrypt into the derived index only. No KMS → omit ciphertext
222
223
  // values rather than indexing blobs.
223
224
  export async function decryptSearchableSubjectFields(entityName, state, registry) {
@@ -242,8 +243,8 @@ export async function decryptSearchableSubjectFields(entityName, state, registry
242
243
  });
243
244
  }
244
245
  catch (err) {
245
- console.warn(`[kumiko:search] decryptSearchableSubjectFields failed for "${entityName}" — ` +
246
- `dropping ciphertext fields for this document instead of wedging the consumer.`, err);
246
+ log.warn(`decryptSearchableSubjectFields failed for "${entityName}" — ` +
247
+ `dropping ciphertext fields for this document instead of wedging the consumer.`, { err });
247
248
  const out = { ...state };
248
249
  for (const name of fields) {
249
250
  if (isPiiCiphertext(out[name]))
@@ -295,7 +296,7 @@ function warnOncePerKeyCollision(registry, entityName, key, isBaseField) {
295
296
  return;
296
297
  warned.add(dedupKey);
297
298
  const collidesWith = isBaseField ? `base field "${key}"` : `earlier contributor key "${key}"`;
298
- console.warn(`[kumiko:search] searchPayloadExtension on "${entityName}" tried to overwrite ` +
299
+ log.warn(`searchPayloadExtension on "${entityName}" tried to overwrite ` +
299
300
  `${collidesWith} — keeping the first value. Rename the contributor key.`);
300
301
  }
301
302
  // Build a SearchDocument from raw field-state. Parallel to the old
@@ -307,10 +308,9 @@ export async function buildSearchDocument(entityName, entityId, state, registry)
307
308
  return null;
308
309
  const searchableFields = registry.getSearchableFields(entityName);
309
310
  const extensions = registry.getSearchPayloadExtensions(entityName);
310
- // Skip-Guard: kein indexable payload UND keine Extensions → kein doc.
311
- // Extensions können auch ohne searchable-Stammfields den index befüllen
312
- // (z.B. customFields-only-indexierung), daher muss die check beide
313
- // berücksichtigen.
311
+ // Skip guard: no indexable payload and no extensions means there is no doc.
312
+ // Extensions can populate the index without searchable base fields
313
+ // (for example, customFields-only indexing), so both must be checked.
314
314
  if (searchableFields.length === 0 && extensions.length === 0)
315
315
  return null;
316
316
  const embeddedFields = new Set();
@@ -348,15 +348,15 @@ export async function buildSearchDocument(entityName, entityId, state, registry)
348
348
  fields[f] = state[f];
349
349
  }
350
350
  }
351
- // F3 — Search-Payload-Extensions: contributors merge flat fields into the
351
+ // Search-Payload-Extensions: contributors merge flat fields into the
352
352
  // search-doc (customFields-bundle / tags / computed-counts / etc.).
353
353
  // Sequential await — extensions are expected sync or sub-millisecond async;
354
354
  // sequential keeps the path simple and deterministic.
355
355
  //
356
356
  // Precedence is base-fields-win: a contributor key that collides with a
357
- // searchable Stammfield is dropped (not silently merged over the real value)
358
- // and warned. A jsonb custom-field that happens to share a Stammfield name
359
- // must not shadow the indexed Stammfield.
357
+ // searchable base field is dropped (not silently merged over the real value)
358
+ // and warned. A jsonb custom-field that happens to share a base field name
359
+ // must not shadow the indexed base field.
360
360
  const baseFieldKeys = new Set(Object.keys(fields));
361
361
  for (const contribute of extensions) {
362
362
  const contributed = await contribute({ entityName, entityId, state });
@@ -395,7 +395,7 @@ export const SSE_BROADCAST_CONSUMER_NAME = "system:consumer:sse-broadcast";
395
395
  export function createSseBroadcastEventConsumer(sseBroker) {
396
396
  return {
397
397
  name: SSE_BROADCAST_CONSUMER_NAME,
398
- // Shared delivery: one cursor reads every event exactly once (fw#2625).
398
+ // Shared delivery: one cursor reads every event exactly once.
399
399
  // Fanout to every process's own SSE clients happens below, through
400
400
  // sseBroker.pushToChannel — a Redis-backed broker (the default once
401
401
  // REDIS_URL is set) republishes across replicas, so every pod's
@@ -425,7 +425,7 @@ export function createSseBroadcastEventConsumer(sseBroker) {
425
425
  // r.multiStreamProjection's ctx.unsafeAppendEvent, or a raw
426
426
  // event-store-executor write (e.g. `files`' fileRef.created) — never
427
427
  // reached it, so a job could never trigger on an r.defineEvent-registered
428
- // event (kumiko-framework#1505).
428
+ // event.
429
429
  //
430
430
  // This consumer closes that gap the same way search/SSE do: read every
431
431
  // committed event off the shared cursor and re-check job triggers. It is
@@ -467,10 +467,9 @@ export function createJobTriggerEventConsumer(jobRunner, registry) {
467
467
  }
468
468
  // --- Access-Invalidation Consumer (async, via event-dispatcher) ---
469
469
  //
470
- // #1524 (Design), #1558 (channel + stream subscribe), #1559 (session-revoke
471
- // emits an event). This consumer is the last leg: it watches for the two
472
- // event types that can make an already-issued JWT stale mid-stream and
473
- // pushes an invalidation through sseBroker.publishAccessInvalidation, which
470
+ // This consumer is the last leg: it watches for the two event types that
471
+ // can make an already-issued JWT stale mid-stream and pushes an invalidation
472
+ // through sseBroker.publishAccessInvalidation, which
474
473
  // dispatch-stream.ts's subscribeAccessInvalidation listener turns into an
475
474
  // AccessDeniedError thrown into the open stream.
476
475
  //
@@ -499,21 +498,20 @@ export function createJobTriggerEventConsumer(jobRunner, registry) {
499
498
  // payload.previous, the full pre-write entity snapshot written by
500
499
  // createEventStoreExecutor. tenantMembershipEntity declares no
501
500
  // encrypted/PII fields, so previous.userId is plaintext — no KMS
502
- // decrypt step (see kumiko-framework#1560 PR discussion; the issue's
503
- // original handoff assumed encryption that doesn't apply here).
501
+ // decrypt step; the original handoff assumed encryption that doesn't
502
+ // apply here.
504
503
  //
505
504
  // Over-invalidation (e.g. tenant-membership.updated fired by something
506
505
  // other than a role change) is fail-safe: the stream closes, the client
507
- // reconnects and re-authorizes. No opt-out/allowlist — #1524 chose
508
- // global-by-default specifically so this can't be silently disabled per
509
- // handler.
506
+ // reconnects and re-authorizes. No opt-out/allowlist — global-by-default
507
+ // specifically so this can't be silently disabled per handler.
510
508
  //
511
509
  // Scope note: entityEventName also emits "tenant-membership.forgotten" (DSGVO
512
510
  // erasure) and ".restored" — .restored re-grants access so ignoring it is
513
511
  // correct, .forgotten is not currently produced by any user-data-rights
514
512
  // pipeline for memberships but would be revocation-relevant if it ever is.
515
- // Out of #1560's stated scope (session-revoke + role/membership change),
516
- // tracked rather than handled speculatively here.
513
+ // Out of the stated scope (session-revoke + role/membership change), tracked
514
+ // rather than handled speculatively here.
517
515
  export const ACCESS_INVALIDATION_CONSUMER_NAME = "system:consumer:access-invalidation";
518
516
  const SESSION_REVOKED_EVENT_TYPE = "sessions:event:session-revoked";
519
517
  const PAT_REVOKED_EVENT_TYPE = "personal-access-tokens:event:pat-revoked";
@@ -557,7 +555,7 @@ function readPatRevokedScope(payload) {
557
555
  export function createAccessInvalidationEventConsumer(sseBroker) {
558
556
  return {
559
557
  name: ACCESS_INVALIDATION_CONSUMER_NAME,
560
- // Shared delivery, same reasoning as SSE broadcast (fw#2625): one
558
+ // Shared delivery, same reasoning as SSE broadcast: one
561
559
  // cursor reads every event once and calls sseBroker.publishAccessInvalidation
562
560
  // below, which a Redis-backed broker republishes to every replica's
563
561
  // subscribeAccessInvalidation listeners — no per-instance cursor needed
@@ -1,5 +1,5 @@
1
1
  import { escapeHatchFor } from "@cosmicdrift/kumiko-types/handlers";
2
- import { withSystemDbUnsafeRawGrant, withUnsafeRawGrant, } from "../db/tenant-db.js";
2
+ import { withSystemDbUnsafeRawGrant, withUnsafeRawGrant } from "../db/tenant-db.js";
3
3
  import { SYSTEM_ROLE, SYSTEM_USER_ID } from "../engine/system-user.js";
4
4
  import { AccessDeniedError, FrameworkReasons, InternalError } from "../errors/index.js";
5
5
  export function isSystemIdentity(user) {
@@ -235,12 +235,10 @@ export function withHookEscapeHatchGrant(context, callerLabel, escapeHatch) {
235
235
  }),
236
236
  ...gatedMemberReaderField(callerLabel, identityHatch, ctxQueryAsMember),
237
237
  ...gatedProjectionReaderField(callerLabel, identityHatch, ctxQueryProjection),
238
- // @cast-boundary engine-bridge — withUnsafeRawGrant passes non-TenantDb values (e.g. a guard Proxy) through unchanged.
239
238
  ...(ctxDb && { db: withUnsafeRawGrant(ctxDb, rawHatch) }),
240
239
  ...(ctxDbOutsideTransaction && {
241
240
  dbOutsideTransaction: withUnsafeRawGrant(ctxDbOutsideTransaction, rawHatch),
242
241
  }),
243
- // @cast-boundary engine-bridge — withSystemDbUnsafeRawGrant passes non-UncheckedSystemDb values through unchanged.
244
242
  ...(ctxSystemDb && {
245
243
  systemDb: withSystemDbUnsafeRawGrant(ctxSystemDb, rawHatch, callerLabel),
246
244
  }),
@@ -1,5 +1,5 @@
1
1
  // Per-dispatcher in-process cache for the resolved tenant:config:timezone
2
- // value (fw#2462). dispatch-shared.ts's buildHandlerContext resolves that
2
+ // value. dispatch-shared.ts's buildHandlerContext resolves that
3
3
  // key on every dispatch to build ctx.tz — without a cache that's a config
4
4
  // SELECT (sometimes two, cascade + fallback) per request, often inside an
5
5
  // open write transaction. A tenant-keyed TTL cache removes the steady-state
@@ -1,56 +1,56 @@
1
1
  export type AdjNounNameOptions = {
2
- /** Trennzeichen zwischen adj/noun (und ggf. suffix). Default "-". */
2
+ /** Separator between adj/noun (and, if applicable, suffix). Default "-". */
3
3
  readonly separator?: string;
4
- /** Wenn gesetzt, wird ein random-suffix der Länge .length angehängt
5
- * (no-confusable-Alphabet). Empfohlen 3 Zeichen = 32^3 = 32.768
6
- * zusätzliche Combinations pro Wortpaar. */
4
+ /** If set, appends a random suffix of length .length
5
+ * (no-confusable alphabet). 3 characters are recommended = 32^3 = 32,768
6
+ * additional combinations per word pair. */
7
7
  readonly suffix?: {
8
8
  readonly length: number;
9
9
  };
10
10
  /** Custom Adjective-Liste — default ADJECTIVES (191 generic). */
11
11
  readonly adjectives?: readonly string[];
12
- /** Custom Noun-Liste — default NOUNS (173 generic). Apps die Domain-
13
- * spezifische Slugs wollen (z.B. webhook-feature mit eigenen
14
- * -receiver/-listener-Substantiven) reichen ihre eigene Liste. */
12
+ /** Custom noun list — default NOUNS (173 generic). Apps that want
13
+ * domain-specific slugs (e.g. webhook features with their own
14
+ * -receiver/-listener nouns) provide their own list. */
15
15
  readonly nouns?: readonly string[];
16
16
  };
17
- /** Sync Generator: produziert "happy-cloud" oder "happy-cloud-k8x"
18
- * (mit suffix). Kein Conflict-Check — Caller verantwortlich für
19
- * Uniqueness, oder generateUniqueName() für die async Variante. */
17
+ /** Sync generator: produces "happy-cloud" or "happy-cloud-k8x"
18
+ * (with suffix). No conflict check — the caller is responsible for
19
+ * uniqueness, or can use generateUniqueName() for the async variant. */
20
20
  export declare function generateAdjNounName(options?: AdjNounNameOptions): string;
21
- /** Random-String aus dem no-confusable-Alphabet. Für Suffix-bei-Kollision
22
- * oder als standalone short-IDs (z.B. Webhook-Verifizierungs-Codes
23
- * zum Vorlesen am Telefon). length ≥ 1. */
21
+ /** Random string from the no-confusable alphabet. For suffixes after
22
+ * collisions or as standalone short IDs (e.g. webhook verification codes
23
+ * to be read aloud over the phone). length ≥ 1. */
24
24
  export declare function generateNoConfusableId(length: number): string;
25
25
  export type GenerateUniqueNameOptions = {
26
- /** Caller-Predicate. Returns true wenn der Name noch nicht vergeben
27
- * ist (typisch: DB-Query "select where slug=$1" → row count === 0). */
26
+ /** Caller predicate. Returns true if the name is not yet assigned
27
+ * (typically: DB query "select where slug=$1" → row count === 0). */
28
28
  readonly isAvailable: (name: string) => Promise<boolean>;
29
- /** Max Versuche OHNE Suffix bevor wir auf suffix-mode wechseln.
30
- * Default 3. Bei 33.043 Default-Combos und ~150 existierenden
31
- * Tenants liegt p(Kollision) < 1% — 3 Versuche reichen weit. */
29
+ /** Maximum attempts WITHOUT a suffix before switching to suffix mode.
30
+ * Default 3. With 33,043 default combinations and ~150 existing
31
+ * tenants, p(collision) < 1% — 3 attempts are more than sufficient. */
32
32
  readonly maxCleanAttempts?: number;
33
- /** Suffix-Länge bei Kollision-Mode. Default 3 (= 32.768 Combinations
34
- * pro Wortpaar). */
33
+ /** Suffix length in collision mode. Default 3 (= 32,768 combinations
34
+ * per word pair). */
35
35
  readonly suffixLength?: number;
36
- /** Hard-Cap an Total-Versuchen bevor wir aufgeben. Default 20.
37
- * Praktisch nie erreicht — wenn doch, ist die Wortliste leer oder
38
- * isAvailable() returnt durchgängig false (DB-Bug). */
36
+ /** Hard cap on total attempts before giving up. Default 20.
37
+ * Rarely reached in practice — if it is, the word list may be empty or
38
+ * isAvailable() may return false consistently (DB bug). */
39
39
  readonly maxTotalAttempts?: number;
40
40
  readonly separator?: string;
41
41
  readonly adjectives?: readonly string[];
42
42
  readonly nouns?: readonly string[];
43
43
  };
44
- /** Generiert einen unique Adj-Noun-Slug indem es bis zu maxCleanAttempts
45
- * saubere Wortpaare versucht, danach mit random Suffix bis maxTotal-
46
- * Attempts. Wirft wenn auch das nicht hilft (= caller hat ein Bug
47
- * mit isAvailable, oder die Wortliste ist defekt).
44
+ /** Generates a unique Adj-Noun slug by trying up to maxCleanAttempts
45
+ * clean word pairs, then using a random suffix up to maxTotal-
46
+ * Attempts. Throws if that also fails (= the caller has a bug
47
+ * in isAvailable, or the word list is defective).
48
48
  *
49
- * Beispiel:
49
+ * Example:
50
50
  * const slug = await generateUniqueName({
51
51
  * isAvailable: async (s) =>
52
52
  * !(await db.select().from(tenants).where(eq(tenants.tenantKey, s)).then(r => r.length > 0)),
53
53
  * });
54
- * // → "happy-cloud" oder "happy-cloud-k8x" bei Kollision
54
+ * // → "happy-cloud" or "happy-cloud-k8x" on collision
55
55
  */
56
56
  export declare function generateUniqueName(options: GenerateUniqueNameOptions): Promise<string>;