@cosmicdrift/kumiko-framework 1.0.0 → 2.0.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 (424) hide show
  1. package/README.md +9 -38
  2. package/package.json +21 -2
  3. package/src/__tests__/anonymous-access.integration.test.ts +185 -0
  4. package/src/__tests__/consumer-cli.integration.test.ts +172 -0
  5. package/src/__tests__/entity-permalink-open.integration.test.ts +94 -0
  6. package/src/__tests__/full-stack.integration.test.ts +1 -1
  7. package/src/__tests__/schema-cli-temporal-polyfill.test.ts +48 -0
  8. package/src/__tests__/schema-cli.integration.test.ts +50 -0
  9. package/src/__tests__/store-table.integration.test.ts +93 -0
  10. package/src/api/__tests__/api.test.ts +373 -0
  11. package/src/api/__tests__/auth-middleware-anonymous-access-boot.test.ts +40 -0
  12. package/src/api/__tests__/auth-routes-cookie.test.ts +17 -1
  13. package/src/api/__tests__/auth-routes-invalid-body-invite.test.ts +253 -0
  14. package/src/api/__tests__/auth-routes-mfa-preauth-confirm.test.ts +222 -0
  15. package/src/api/__tests__/auth-routes-mfa-preauth-enable-start.test.ts +249 -0
  16. package/src/api/__tests__/auth-routes-mfa-verify.test.ts +202 -0
  17. package/src/api/__tests__/auth-routes-trusted-proxy.test.ts +135 -0
  18. package/src/api/__tests__/batch.integration.test.ts +101 -11
  19. package/src/api/__tests__/csrf-constants-sync.test.ts +20 -0
  20. package/src/api/__tests__/dispatcher-live.integration.test.ts +74 -0
  21. package/src/api/__tests__/jwt.test.ts +200 -1
  22. package/src/api/__tests__/login-rate-limiter-sweep.test.ts +50 -0
  23. package/src/api/__tests__/pat-scope.test.ts +36 -0
  24. package/src/api/__tests__/pii-leak-guard.integration.test.ts +103 -0
  25. package/src/api/__tests__/redis-login-rate-limiter.integration.test.ts +138 -0
  26. package/src/api/__tests__/request-id-middleware.test.ts +51 -0
  27. package/src/api/__tests__/server-boot-guards.test.ts +71 -0
  28. package/src/api/__tests__/server-jwt-ttl.test.ts +58 -0
  29. package/src/api/__tests__/sse-broker.test.ts +57 -0
  30. package/src/api/__tests__/sse-route.test.ts +4 -0
  31. package/src/api/api-constants.ts +11 -0
  32. package/src/api/auth-middleware.ts +248 -40
  33. package/src/api/auth-routes.ts +576 -95
  34. package/src/api/index.ts +13 -4
  35. package/src/api/jwt.ts +170 -11
  36. package/src/api/pat-scope.ts +14 -0
  37. package/src/api/pii-leak-guard.ts +47 -0
  38. package/src/api/request-context.ts +3 -0
  39. package/src/api/request-id-middleware.ts +2 -0
  40. package/src/api/routes.ts +169 -2
  41. package/src/api/server.ts +112 -16
  42. package/src/api/sse-broker.ts +39 -0
  43. package/src/bun-db/__tests__/PATTERN.md +0 -1
  44. package/src/bun-db/__tests__/coerce-row-temporal.test.ts +41 -0
  45. package/src/bun-db/__tests__/select-many-retry.test.ts +79 -0
  46. package/src/bun-db/__tests__/write-brand.test.ts +21 -0
  47. package/src/bun-db/connection.ts +3 -3
  48. package/src/bun-db/index.ts +2 -0
  49. package/src/bun-db/query.ts +105 -32
  50. package/src/consumer-cli.ts +134 -0
  51. package/src/crypto/__tests__/blind-index.test.ts +130 -0
  52. package/src/crypto/__tests__/event-pii.test.ts +161 -0
  53. package/src/crypto/__tests__/kek-rotation.integration.test.ts +180 -0
  54. package/src/crypto/__tests__/kms-adapter-contract.ts +134 -0
  55. package/src/crypto/__tests__/kms-adapter.contract.test.ts +4 -0
  56. package/src/crypto/__tests__/pg-kms-adapter.integration.test.ts +117 -0
  57. package/src/crypto/__tests__/pii-field-encryption.test.ts +376 -0
  58. package/src/crypto/__tests__/request-kms-cache.test.ts +74 -0
  59. package/src/crypto/__tests__/subject-resolver.test.ts +108 -0
  60. package/src/crypto/blind-index.ts +118 -0
  61. package/src/crypto/event-pii.ts +70 -0
  62. package/src/crypto/in-memory-kms-adapter.ts +50 -0
  63. package/src/crypto/index.ts +67 -0
  64. package/src/crypto/kms-adapter.ts +2 -0
  65. package/src/crypto/pg-kms-adapter.ts +295 -0
  66. package/src/crypto/pii-field-encryption.ts +248 -0
  67. package/src/crypto/request-kms-cache.ts +38 -0
  68. package/src/crypto/subject-resolver.ts +91 -0
  69. package/src/db/__tests__/assert-no-unreachable-live-rows.integration.test.ts +191 -0
  70. package/src/db/__tests__/blind-index.integration.test.ts +248 -0
  71. package/src/db/__tests__/build-filter-where.test.ts +34 -0
  72. package/src/db/__tests__/collect-table-metas.test.ts +10 -10
  73. package/src/db/__tests__/config-seed.integration.test.ts +13 -5
  74. package/src/db/__tests__/dialect-instant.test.ts +1 -4
  75. package/src/db/__tests__/entity-field-encryption.test.ts +7 -7
  76. package/src/db/__tests__/entity-table-meta-source.test.ts +50 -0
  77. package/src/db/__tests__/event-store-executor-context.pii-roundtrip.test.ts +67 -0
  78. package/src/db/__tests__/event-store-executor-write-verbs.integration.test.ts +402 -0
  79. package/src/db/__tests__/event-store-executor.integration.test.ts +299 -29
  80. package/src/db/__tests__/feature-table-sources.test.ts +34 -0
  81. package/src/db/__tests__/implicit-projection-equivalence.integration.test.ts +118 -39
  82. package/src/db/__tests__/instant-to-driver-temporal.test.ts +21 -0
  83. package/src/db/__tests__/located-timestamp.test.ts +19 -0
  84. package/src/db/__tests__/migrate-generator.test.ts +21 -0
  85. package/src/db/__tests__/migrate-runner.test.ts +61 -0
  86. package/src/db/__tests__/number-field-fractional.integration.test.ts +58 -0
  87. package/src/db/__tests__/rebuild-marker.test.ts +13 -3
  88. package/src/db/__tests__/replay-migration-sql.test.ts +384 -0
  89. package/src/db/__tests__/schema-inspection.test.ts +7 -0
  90. package/src/db/__tests__/schema-migration.integration.test.ts +1 -1
  91. package/src/db/__tests__/table-builder-meta-lockstep.test.ts +39 -0
  92. package/src/db/__tests__/tenant-db-where-merge.test.ts +49 -3
  93. package/src/db/__tests__/tenant-db.integration.test.ts +6 -2
  94. package/src/db/api.ts +2 -2
  95. package/src/db/apply-entity-event.ts +18 -14
  96. package/src/db/blind-index-cleanup.ts +55 -0
  97. package/src/db/bun-provider.ts +2 -2
  98. package/src/db/collect-table-metas.ts +6 -7
  99. package/src/db/config-seed.ts +9 -9
  100. package/src/db/connection.ts +7 -12
  101. package/src/db/cursor.ts +1 -18
  102. package/src/db/dialect.ts +20 -26
  103. package/src/db/entity-field-encryption.ts +81 -24
  104. package/src/db/entity-table-meta-types.ts +2 -0
  105. package/src/db/entity-table-meta.ts +63 -90
  106. package/src/db/event-store-executor-context.ts +404 -0
  107. package/src/db/event-store-executor-read.ts +275 -0
  108. package/src/db/event-store-executor-write.ts +644 -0
  109. package/src/db/event-store-executor.ts +28 -1155
  110. package/src/db/feature-table-sources.ts +6 -5
  111. package/src/db/index.ts +20 -3
  112. package/src/db/located-timestamp.ts +4 -0
  113. package/src/db/migrate-generator.ts +39 -6
  114. package/src/db/migrate-runner.ts +107 -11
  115. package/src/db/pg-error.ts +9 -1
  116. package/src/db/postgres-provider.ts +2 -2
  117. package/src/db/queries/__tests__/event-store-idempotency-index.integration.test.ts +80 -0
  118. package/src/db/queries/backfill-pii.ts +277 -0
  119. package/src/db/queries/ddl.ts +45 -0
  120. package/src/db/queries/event-consumer.ts +35 -2
  121. package/src/db/queries/event-store.ts +126 -19
  122. package/src/db/queries/projection-rebuild.ts +22 -8
  123. package/src/db/queries/shadow-swap.ts +183 -0
  124. package/src/db/queries/test-stack.ts +4 -30
  125. package/src/db/query-api.ts +1 -0
  126. package/src/db/query.ts +1 -0
  127. package/src/db/rebuild-marker.ts +18 -2
  128. package/src/db/reference-data.ts +2 -3
  129. package/src/db/replay-migration-sql.ts +257 -0
  130. package/src/db/schema-inspection.ts +1 -1
  131. package/src/db/table-builder.ts +102 -71
  132. package/src/db/tenant-db.ts +15 -53
  133. package/src/engine/__tests__/boot-validator-action-wiring.test.ts +242 -0
  134. package/src/engine/__tests__/boot-validator-boot-check.test.ts +99 -0
  135. package/src/engine/__tests__/boot-validator-dashboard.test.ts +237 -0
  136. package/src/engine/__tests__/boot-validator-entity-list.test.ts +104 -0
  137. package/src/engine/__tests__/boot-validator-gdpr-storage.test.ts +7 -69
  138. package/src/engine/__tests__/boot-validator-i18n-keys.test.ts +26 -5
  139. package/src/engine/__tests__/boot-validator-located-timestamps.test.ts +3 -19
  140. package/src/engine/__tests__/boot-validator-pii-retention.test.ts +246 -4
  141. package/src/engine/__tests__/boot-validator.test.ts +219 -15
  142. package/src/engine/__tests__/build-app-schema.test.ts +82 -0
  143. package/src/engine/__tests__/build-config-feature-schema.test.ts +125 -0
  144. package/src/engine/__tests__/build-target.test.ts +3 -11
  145. package/src/engine/__tests__/codemod-pipeline.test.ts +152 -21
  146. package/src/engine/__tests__/config-helpers.test.ts +16 -0
  147. package/src/engine/__tests__/define-feature-entity-mapping.test.ts +6 -0
  148. package/src/engine/__tests__/define-roles.test.ts +21 -0
  149. package/src/engine/__tests__/engine.test.ts +163 -1
  150. package/src/engine/__tests__/entity-handlers.test.ts +80 -0
  151. package/src/engine/__tests__/event-migration-declarative.test.ts +65 -0
  152. package/src/engine/__tests__/event-type-map-augmentation.test.ts +24 -0
  153. package/src/engine/__tests__/extend-entity-projection.test.ts +123 -0
  154. package/src/engine/__tests__/factories-time.test.ts +2 -66
  155. package/src/engine/__tests__/feature-crud-shorthand.test.ts +28 -0
  156. package/src/engine/__tests__/feature-manifest.test.ts +31 -1
  157. package/src/engine/__tests__/field-access.test.ts +23 -1
  158. package/src/engine/__tests__/hook-phases.test.ts +5 -5
  159. package/src/engine/__tests__/membership-roles.test.ts +4 -10
  160. package/src/engine/__tests__/nav.test.ts +86 -1
  161. package/src/engine/__tests__/pipeline-engine.test.ts +9 -9
  162. package/src/engine/__tests__/pipeline-handler.integration.test.ts +18 -18
  163. package/src/engine/__tests__/pipeline-observability.integration.test.ts +2 -2
  164. package/src/engine/__tests__/pipeline-performance.integration.test.ts +3 -3
  165. package/src/engine/__tests__/pipeline-sub-pipelines.test.ts +9 -9
  166. package/src/engine/__tests__/post-query-hook.test.ts +7 -7
  167. package/src/engine/__tests__/registrar-object-form.test.ts +141 -0
  168. package/src/engine/__tests__/registry.test.ts +144 -0
  169. package/src/engine/__tests__/schema-builder.test.ts +18 -0
  170. package/src/engine/__tests__/screen.test.ts +147 -1
  171. package/src/engine/__tests__/soft-delete-cleanup.test.ts +3 -0
  172. package/src/engine/__tests__/store-table.test.ts +229 -0
  173. package/src/engine/__tests__/tier-resolver-extension.test.ts +19 -1
  174. package/src/engine/__tests__/{visual-tree-patterns.test.ts → tree-actions-patterns.test.ts} +7 -95
  175. package/src/engine/__tests__/validate-projection-allowlist.test.ts +15 -15
  176. package/src/engine/boot-validator/__tests__/config-deps.test.ts +92 -0
  177. package/src/engine/boot-validator/action-wiring.ts +149 -0
  178. package/src/engine/boot-validator/boot-check.ts +21 -0
  179. package/src/engine/boot-validator/config-deps.ts +40 -4
  180. package/src/engine/boot-validator/entity-handler.ts +20 -21
  181. package/src/engine/boot-validator/entity-list-screens.ts +88 -0
  182. package/src/engine/boot-validator/gdpr-storage.ts +0 -20
  183. package/src/engine/boot-validator/i18n-keys.ts +58 -4
  184. package/src/engine/boot-validator/index.ts +33 -12
  185. package/src/engine/boot-validator/nav.ts +125 -0
  186. package/src/engine/boot-validator/pii-retention.ts +95 -10
  187. package/src/engine/boot-validator/{screens-nav.ts → screens.ts} +214 -191
  188. package/src/engine/boot-validator/workspaces.ts +68 -0
  189. package/src/engine/build-app-schema.ts +16 -0
  190. package/src/engine/build-config-feature-schema.ts +44 -7
  191. package/src/engine/codemod/pipeline-codemod.ts +5 -5
  192. package/src/engine/config-helpers.ts +15 -0
  193. package/src/engine/constants.ts +32 -6
  194. package/src/engine/create-app.ts +11 -0
  195. package/src/engine/define-feature.ts +95 -947
  196. package/src/engine/define-handler.ts +28 -94
  197. package/src/engine/define-workflow.ts +1 -1
  198. package/src/engine/effective-features.ts +12 -2
  199. package/src/engine/entity-handlers.ts +76 -11
  200. package/src/engine/extensions/tenant-data.ts +19 -0
  201. package/src/engine/extensions/user-data.ts +29 -2
  202. package/src/engine/factories.ts +8 -49
  203. package/src/engine/feature-ast/__tests__/canonical-form.test.ts +26 -29
  204. package/src/engine/feature-ast/__tests__/fixtures/cross-file-registrar/feature.ts +9 -0
  205. package/src/engine/feature-ast/__tests__/fixtures/cross-file-registrar/screens.ts +4 -0
  206. package/src/engine/feature-ast/__tests__/parse-happy-path.test.ts +1 -2
  207. package/src/engine/feature-ast/__tests__/parse-real-features.test.ts +18 -8
  208. package/src/engine/feature-ast/__tests__/parse.test.ts +1640 -160
  209. package/src/engine/feature-ast/__tests__/patch.test.ts +206 -12
  210. package/src/engine/feature-ast/__tests__/patcher.test.ts +20 -23
  211. package/src/engine/feature-ast/__tests__/render-roundtrip.test.ts +380 -5
  212. package/src/engine/feature-ast/extractors/events.ts +322 -0
  213. package/src/engine/feature-ast/extractors/handlers.ts +234 -0
  214. package/src/engine/feature-ast/extractors/hooks.ts +243 -0
  215. package/src/engine/feature-ast/extractors/index.ts +34 -31
  216. package/src/engine/feature-ast/extractors/jobs-routes.ts +221 -0
  217. package/src/engine/feature-ast/extractors/projections-screens.ts +269 -0
  218. package/src/engine/feature-ast/extractors/round1.ts +3 -3
  219. package/src/engine/feature-ast/extractors/round5.ts +3 -3
  220. package/src/engine/feature-ast/extractors/round6.ts +2 -36
  221. package/src/engine/feature-ast/extractors/shared.ts +64 -6
  222. package/src/engine/feature-ast/index.ts +2 -4
  223. package/src/engine/feature-ast/parse.ts +130 -23
  224. package/src/engine/feature-ast/patch.ts +39 -50
  225. package/src/engine/feature-ast/patcher.ts +37 -38
  226. package/src/engine/feature-ast/patterns.ts +50 -62
  227. package/src/engine/feature-ast/render.ts +67 -44
  228. package/src/engine/feature-builder-state.ts +168 -0
  229. package/src/engine/feature-config-events-jobs.ts +403 -0
  230. package/src/engine/feature-entity-handlers.ts +208 -0
  231. package/src/engine/feature-manifest.ts +2 -1
  232. package/src/engine/feature-ui-extensions.ts +500 -0
  233. package/src/engine/field-access.ts +13 -2
  234. package/src/engine/field-helpers.ts +31 -0
  235. package/src/engine/handler-helpers.ts +26 -0
  236. package/src/engine/hook-helpers.ts +16 -0
  237. package/src/engine/index.ts +23 -7
  238. package/src/engine/membership-roles.ts +13 -0
  239. package/src/engine/object-form.ts +27 -0
  240. package/src/engine/ownership.ts +26 -79
  241. package/src/engine/pattern-library/__tests__/library.test.ts +7 -20
  242. package/src/engine/pattern-library/library.ts +44 -1152
  243. package/src/engine/pattern-library/mixed-schemas.ts +450 -0
  244. package/src/engine/pattern-library/opaque-schemas.ts +124 -0
  245. package/src/engine/pattern-library/shared-fields.ts +75 -0
  246. package/src/engine/pattern-library/static-schemas.ts +456 -0
  247. package/src/engine/pipeline.ts +6 -11
  248. package/src/engine/registry-facade.ts +362 -0
  249. package/src/engine/registry-ingest.ts +478 -0
  250. package/src/engine/registry-state.ts +388 -0
  251. package/src/engine/registry-validate.ts +642 -0
  252. package/src/engine/registry.ts +78 -1658
  253. package/src/engine/run-pipeline.ts +1 -1
  254. package/src/engine/schema-builder.ts +1 -0
  255. package/src/engine/screen-helpers.ts +54 -0
  256. package/src/engine/soft-delete-cleanup.ts +6 -2
  257. package/src/engine/steps/__tests__/duration-utils.test.ts +20 -0
  258. package/src/engine/steps/_duration-utils.ts +2 -0
  259. package/src/engine/steps/unsafe-projection-upsert.ts +1 -4
  260. package/src/engine/tier-resolver-extension.ts +3 -2
  261. package/src/engine/types/config.ts +2 -482
  262. package/src/engine/types/define-handler.ts +2 -0
  263. package/src/engine/types/entity-handlers.ts +2 -0
  264. package/src/engine/types/event-type-map.ts +1 -37
  265. package/src/engine/types/feature.ts +2 -972
  266. package/src/engine/types/fields.ts +2 -675
  267. package/src/engine/types/handlers.ts +2 -774
  268. package/src/engine/types/hooks.ts +2 -184
  269. package/src/engine/types/http-route.ts +1 -54
  270. package/src/engine/types/identifiers.ts +1 -47
  271. package/src/engine/types/index.ts +86 -40
  272. package/src/engine/types/nav.ts +2 -63
  273. package/src/engine/types/ownership.ts +2 -0
  274. package/src/engine/types/projection.ts +2 -138
  275. package/src/engine/types/relations.ts +1 -51
  276. package/src/engine/types/screen.ts +2 -574
  277. package/src/engine/types/step.ts +2 -334
  278. package/src/engine/types/target-ref.ts +1 -21
  279. package/src/engine/types/tree-node.ts +1 -132
  280. package/src/engine/types/workspace.ts +2 -49
  281. package/src/engine/validate-projection-allowlist.ts +6 -6
  282. package/src/entrypoint/__tests__/entrypoint-job-wiring.integration.test.ts +120 -1
  283. package/src/entrypoint/index.ts +49 -6
  284. package/src/errors/classes.ts +22 -1
  285. package/src/errors/field-issue.ts +0 -3
  286. package/src/errors/index.ts +1 -1
  287. package/src/errors/write-error-info.ts +10 -22
  288. package/src/es-ops/README.md +1 -1
  289. package/src/es-ops/__tests__/runner.integration.test.ts +74 -0
  290. package/src/es-ops/context.ts +4 -2
  291. package/src/es-ops/types.ts +8 -1
  292. package/src/event-store/__tests__/admin-api.integration.test.ts +27 -1
  293. package/src/event-store/__tests__/backfill-pii.integration.test.ts +279 -0
  294. package/src/event-store/__tests__/event-store.integration.test.ts +201 -0
  295. package/src/event-store/__tests__/row-to-stored-event.test.ts +2 -2
  296. package/src/event-store/__tests__/snapshot.integration.test.ts +86 -0
  297. package/src/event-store/__tests__/unscoped-stream-primitives.guard.test.ts +58 -0
  298. package/src/event-store/__tests__/upcaster.integration.test.ts +77 -45
  299. package/src/event-store/admin-api.ts +11 -4
  300. package/src/event-store/errors.ts +2 -35
  301. package/src/event-store/event-store.ts +64 -78
  302. package/src/event-store/events-schema.ts +11 -13
  303. package/src/event-store/index.ts +18 -3
  304. package/src/event-store/rebuild-dead-letter.ts +111 -0
  305. package/src/event-store/snapshot.ts +63 -40
  306. package/src/event-store/types.ts +2 -0
  307. package/src/files/__tests__/build-storage-key.test.ts +28 -0
  308. package/src/files/__tests__/file-ref-entity.test.ts +8 -0
  309. package/src/files/__tests__/files.integration.test.ts +24 -0
  310. package/src/files/__tests__/in-memory-provider.contract.test.ts +4 -0
  311. package/src/files/__tests__/local-provider.test.ts +31 -0
  312. package/src/files/__tests__/provider-resolver.test.ts +70 -0
  313. package/src/files/__tests__/write-stream.test.ts +13 -0
  314. package/src/files/file-handle.ts +2 -19
  315. package/src/files/file-ref-entity.ts +2 -2
  316. package/src/files/file-routes.ts +13 -0
  317. package/src/files/index.ts +1 -1
  318. package/src/files/local-provider.ts +27 -8
  319. package/src/files/provider-resolver.ts +31 -20
  320. package/src/files/types.ts +13 -55
  321. package/src/i18n/__tests__/required-surface-keys.test.ts +17 -0
  322. package/src/i18n/required-surface-keys.ts +120 -1
  323. package/src/jobs/__tests__/job-queue-depth.integration.test.ts +82 -0
  324. package/src/jobs/__tests__/jobs.integration.test.ts +329 -3
  325. package/src/jobs/job-runner.ts +82 -12
  326. package/src/logging/types.ts +1 -7
  327. package/src/migrations/pending-rebuilds.ts +1 -1
  328. package/src/observability/__tests__/observability.integration.test.ts +4 -1
  329. package/src/observability/__tests__/recording-tracer.test.ts +6 -4
  330. package/src/observability/index.ts +2 -0
  331. package/src/observability/noop-provider.ts +0 -9
  332. package/src/observability/recording-tracer.ts +0 -12
  333. package/src/observability/standard-metrics.ts +64 -2
  334. package/src/observability/types/index.ts +1 -29
  335. package/src/observability/types/metric.ts +1 -56
  336. package/src/observability/types/provider.ts +1 -32
  337. package/src/observability/types/span.ts +1 -64
  338. package/src/pipeline/__tests__/ctx-bridge.integration.test.ts +2 -2
  339. package/src/pipeline/__tests__/dispatcher.test.ts +366 -1
  340. package/src/pipeline/__tests__/event-consumer-state.integration.test.ts +31 -0
  341. package/src/pipeline/__tests__/event-dispatcher-delivery-max-attempts.test.ts +126 -0
  342. package/src/pipeline/__tests__/event-dispatcher-rearm.integration.test.ts +263 -0
  343. package/src/pipeline/__tests__/event-dispatcher.integration.test.ts +10 -0
  344. package/src/pipeline/__tests__/job-trigger-consumer.integration.test.ts +106 -0
  345. package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +308 -77
  346. package/src/pipeline/__tests__/load-aggregate-query.integration.test.ts +16 -8
  347. package/src/pipeline/__tests__/post-query-hook.integration.test.ts +3 -3
  348. package/src/pipeline/__tests__/projection-rebuild.integration.test.ts +80 -2
  349. package/src/pipeline/__tests__/rebuild-poison-quarantine.integration.test.ts +274 -0
  350. package/src/pipeline/__tests__/try-append-event.integration.test.ts +166 -0
  351. package/src/pipeline/dispatch-batch.ts +187 -0
  352. package/src/pipeline/dispatch-query.ts +165 -0
  353. package/src/pipeline/dispatch-shared.ts +826 -0
  354. package/src/pipeline/dispatch-stream.ts +90 -0
  355. package/src/pipeline/dispatch-write.ts +451 -0
  356. package/src/pipeline/dispatcher-utils.ts +1 -1
  357. package/src/pipeline/dispatcher.ts +44 -1372
  358. package/src/pipeline/entity-cache.ts +2 -33
  359. package/src/pipeline/event-consumer-state.ts +30 -2
  360. package/src/pipeline/event-dispatcher-admin.ts +293 -0
  361. package/src/pipeline/event-dispatcher-delivery.ts +305 -0
  362. package/src/pipeline/event-dispatcher.ts +85 -542
  363. package/src/pipeline/index.ts +2 -0
  364. package/src/pipeline/msp-rebuild.ts +42 -3
  365. package/src/pipeline/multi-stream-apply-context.ts +4 -42
  366. package/src/pipeline/projection-rebuild.ts +105 -3
  367. package/src/pipeline/system-hooks.ts +144 -1
  368. package/src/rate-limit/__tests__/resolver.integration.test.ts +18 -0
  369. package/src/rate-limit/resolver.ts +16 -32
  370. package/src/schema-cli.ts +76 -16
  371. package/src/search/__tests__/meilisearch-adapter.integration.test.ts +207 -185
  372. package/src/search/__tests__/meilisearch-ids.test.ts +30 -0
  373. package/src/search/__tests__/reindex-entity.integration.test.ts +144 -0
  374. package/src/search/index.ts +6 -0
  375. package/src/search/meilisearch-adapter.ts +13 -12
  376. package/src/search/reindex-entity.ts +177 -0
  377. package/src/search/types.ts +1 -39
  378. package/src/secrets/__tests__/contains-secret.test.ts +34 -0
  379. package/src/secrets/__tests__/envelope-cipher.test.ts +59 -0
  380. package/src/secrets/__tests__/envelope.test.ts +1 -1
  381. package/src/secrets/dek-cache.ts +26 -5
  382. package/src/secrets/envelope-cipher.ts +53 -0
  383. package/src/secrets/envelope.ts +5 -3
  384. package/src/secrets/index.ts +13 -1
  385. package/src/secrets/stored-envelope.ts +46 -0
  386. package/src/secrets/types.ts +2 -162
  387. package/src/stack/__tests__/event-collector.test.ts +42 -0
  388. package/src/stack/__tests__/setup-test-stack-jobs.integration.test.ts +125 -0
  389. package/src/stack/db.ts +2 -1
  390. package/src/stack/push-entity-projection-tables.ts +4 -3
  391. package/src/stack/redis.ts +8 -0
  392. package/src/stack/request-helper.ts +38 -2
  393. package/src/stack/table-helpers.ts +6 -4
  394. package/src/stack/test-stack.ts +249 -131
  395. package/src/testing/__tests__/late-bound.test.ts +32 -0
  396. package/src/testing/__tests__/wait-for.test.ts +59 -0
  397. package/src/testing/boot-validator-fixture.ts +121 -0
  398. package/src/testing/e2e-generator.ts +8 -0
  399. package/src/testing/file-provider-contract.ts +104 -0
  400. package/src/testing/handler-context.ts +3 -1
  401. package/src/testing/index.ts +5 -0
  402. package/src/testing/late-bound.ts +5 -3
  403. package/src/testing/mutable-master-key-provider.ts +29 -3
  404. package/src/testing/wait-for.ts +3 -0
  405. package/src/testing/without-ambient-temporal.ts +14 -0
  406. package/src/time/__tests__/tz-dateline.test.ts +8 -10
  407. package/src/time/geo-tz.ts +1 -32
  408. package/src/time/index.ts +1 -0
  409. package/src/time/legacy-date.ts +18 -0
  410. package/src/time/polyfill.ts +21 -38
  411. package/src/time/tz-context.ts +44 -85
  412. package/src/ui-types/app-schema.ts +12 -0
  413. package/src/ui-types/index.ts +20 -7
  414. package/src/utils/__tests__/safe-json-temporal.test.ts +18 -0
  415. package/src/utils/safe-json.ts +13 -1
  416. package/src/__tests__/raw-table.integration.test.ts +0 -116
  417. package/src/bun-db/__tests__/bun-test-stack.ts +0 -6
  418. package/src/db/__tests__/encryption.test.ts +0 -39
  419. package/src/db/encryption.ts +0 -39
  420. package/src/db/row-helpers.ts +0 -4
  421. package/src/engine/__tests__/raw-table.test.ts +0 -150
  422. package/src/engine/__tests__/unmanaged-table.test.ts +0 -127
  423. package/src/engine/feature-ast/__tests__/visual-tree-parse.test.ts +0 -184
  424. package/src/engine/feature-ast/extractors/round4.ts +0 -1366
@@ -0,0 +1,263 @@
1
+ // Issue #1350 — bounded auto-revival of a dead consumer.
2
+ //
3
+ // acquireConsumerState no longer parks a dead consumer forever: once its
4
+ // last write is older than rearmCooldownMs it gets reset to idle and
5
+ // retried, up to maxRearmCount times. A poison event (permanent failure)
6
+ // still ends up permanently dead once the budget is exhausted — the
7
+ // mechanism is a bounded number of extra chances, not unlimited retry.
8
+
9
+ import { afterEach, beforeAll, describe, expect, test } from "bun:test";
10
+ import { createEventStoreExecutor } from "../../db/event-store-executor";
11
+ import { asRawClient } from "../../db/query";
12
+ import { createTenantDb, type TenantDb } from "../../db/tenant-db";
13
+ import { defineFeature } from "../../engine";
14
+ import {
15
+ DEFAULT_SENSITIVE_CONFIG,
16
+ type MetricEvent,
17
+ type ObservabilityProvider,
18
+ RecordingMeter,
19
+ RecordingTracer,
20
+ } from "../../observability";
21
+ import { getConsumerState } from "../../pipeline";
22
+ import {
23
+ resetEventStore,
24
+ setupTestStack,
25
+ type TestStack,
26
+ TestUsers,
27
+ unsafeCreateEntityTable,
28
+ } from "../../stack";
29
+ import { sharedWidgetEntity, sharedWidgetTable } from "../../testing";
30
+ import { withoutAmbientTemporal } from "../../testing/without-ambient-temporal";
31
+ import { SHARED_INSTANCE_SENTINEL } from "../event-consumer-state";
32
+
33
+ const executor = createEventStoreExecutor(sharedWidgetTable, sharedWidgetEntity, {
34
+ entityName: "widget",
35
+ });
36
+
37
+ let poisonNames = new Set<string>();
38
+ let observed: Array<{ name: string }> = [];
39
+
40
+ const rearmFeature = defineFeature("rearmtest", (r) => {
41
+ r.entity("widget", sharedWidgetEntity);
42
+
43
+ r.multiStreamProjection({
44
+ name: "observer",
45
+ apply: {
46
+ "widget.created": async (event) => {
47
+ const name = event.payload["name"] as string;
48
+ if (poisonNames.has(name)) {
49
+ throw new Error(`poisoned: ${name}`);
50
+ }
51
+ observed.push({ name });
52
+ },
53
+ },
54
+ });
55
+ });
56
+
57
+ const admin = TestUsers.admin;
58
+ const qn = "rearmtest:projection:observer";
59
+ let stack: TestStack;
60
+ let tdb: TenantDb;
61
+
62
+ beforeAll(async () => {
63
+ stack = await setupTestStack({
64
+ features: [rearmFeature],
65
+ systemHooks: [],
66
+ });
67
+ await unsafeCreateEntityTable(stack.db, sharedWidgetEntity, "widget");
68
+ tdb = createTenantDb(stack.db, admin.tenantId);
69
+ });
70
+
71
+ afterEach(async () => {
72
+ poisonNames = new Set();
73
+ observed = [];
74
+ await resetEventStore(stack, ["read_widgets"]);
75
+ });
76
+
77
+ async function appendWidget(name: string): Promise<void> {
78
+ await executor.create({ name }, admin, tdb);
79
+ }
80
+
81
+ async function driveUntilDead(): Promise<void> {
82
+ for (let i = 0; i < 10; i++) {
83
+ await stack.eventDispatcher?.runOnce();
84
+ }
85
+ }
86
+
87
+ // Simulates cooldown elapsed without waiting real wall-clock time.
88
+ async function backdateUpdatedAt(minutesAgo: number): Promise<void> {
89
+ await asRawClient(stack.db).unsafe(
90
+ `UPDATE "kumiko_event_consumers" SET "updated_at" = now() - make_interval(mins => $1)
91
+ WHERE "name" = $2 AND "instance_id" = $3`,
92
+ [minutesAgo, qn, SHARED_INSTANCE_SENTINEL],
93
+ );
94
+ }
95
+
96
+ describe("issue #1350 — bounded dead-consumer re-arm", () => {
97
+ test("stays dead within the cooldown window", async () => {
98
+ poisonNames.add("poison");
99
+ await appendWidget("poison");
100
+ await driveUntilDead();
101
+
102
+ const dead = await getConsumerState(stack.db, qn);
103
+ expect(dead?.status).toBe("dead");
104
+ expect(dead?.rearmCount).toBe(0);
105
+
106
+ // No backdate — cooldown (5min default) hasn't elapsed.
107
+ await stack.eventDispatcher?.runOnce();
108
+ const stillDead = await getConsumerState(stack.db, qn);
109
+ expect(stillDead?.status).toBe("dead");
110
+ expect(stillDead?.rearmCount).toBe(0);
111
+ });
112
+
113
+ test("auto-revives once cooldown elapses, and re-dies on the same poison event", async () => {
114
+ poisonNames.add("poison");
115
+ await appendWidget("poison");
116
+ await driveUntilDead();
117
+
118
+ await backdateUpdatedAt(10);
119
+ await stack.eventDispatcher?.runOnce();
120
+
121
+ const revived = await getConsumerState(stack.db, qn);
122
+ expect(revived?.status).toBe("idle");
123
+ expect(revived?.rearmCount).toBe(1);
124
+ // Same pass that revives it also retries the pending poisoned event —
125
+ // acquireConsumerState's reset (attempts=0) is followed immediately by
126
+ // one failed delivery attempt within this runOnce() call.
127
+ expect(revived?.attempts).toBe(1);
128
+
129
+ // Handler still throws on "poison" — climbs back to dead.
130
+ await driveUntilDead();
131
+ const deadAgain = await getConsumerState(stack.db, qn);
132
+ expect(deadAgain?.status).toBe("dead");
133
+ expect(deadAgain?.rearmCount).toBe(1);
134
+ });
135
+
136
+ test("a re-arm that leads to a real delivery resets rearmCount to 0", async () => {
137
+ poisonNames.add("poison");
138
+ await appendWidget("poison");
139
+ await driveUntilDead();
140
+
141
+ // Cause is gone by the time the cooldown elapses — the retried event
142
+ // now succeeds instead of re-poisoning the consumer.
143
+ poisonNames.delete("poison");
144
+ await backdateUpdatedAt(10);
145
+ await stack.eventDispatcher?.runOnce();
146
+
147
+ const recovered = await getConsumerState(stack.db, qn);
148
+ expect(recovered?.status).toBe("idle");
149
+ expect(recovered?.rearmCount).toBe(0);
150
+ expect(observed.map((o) => o.name)).toEqual(["poison"]);
151
+
152
+ // A later, unrelated poison event gets its own fresh maxRearmCount
153
+ // budget instead of inheriting the spent counter from the resolved one.
154
+ poisonNames.add("later-poison");
155
+ await appendWidget("later-poison");
156
+ await driveUntilDead();
157
+ const deadAgain = await getConsumerState(stack.db, qn);
158
+ expect(deadAgain?.status).toBe("dead");
159
+ expect(deadAgain?.rearmCount).toBe(0);
160
+ });
161
+
162
+ test("kumiko-framework#1525: cooldown-elapsed re-arm check works without relying on globalThis.Temporal", async () => {
163
+ poisonNames.add("poison");
164
+ await appendWidget("poison");
165
+ await driveUntilDead();
166
+ await backdateUpdatedAt(10);
167
+
168
+ // ensureTemporalPolyfill re-installs if the ambient global was torn down,
169
+ // so runOnce stays safe if a nested path calls it during this window.
170
+ await withoutAmbientTemporal(async () => {
171
+ await stack.eventDispatcher?.runOnce();
172
+ });
173
+
174
+ const revived = await getConsumerState(stack.db, qn);
175
+ expect(revived?.status).toBe("idle");
176
+ expect(revived?.rearmCount).toBe(1);
177
+ });
178
+
179
+ test("permanently dead after maxRearmCount cycles, even once cooldown elapses again", async () => {
180
+ poisonNames.add("poison");
181
+ await appendWidget("poison");
182
+ await driveUntilDead();
183
+
184
+ // Cycles 1..3: each backdate+drive re-arms once, then re-dies on the
185
+ // same poison event. Default maxRearmCount is 3.
186
+ for (let cycle = 1; cycle <= 3; cycle++) {
187
+ await backdateUpdatedAt(10);
188
+ await stack.eventDispatcher?.runOnce();
189
+ const revived = await getConsumerState(stack.db, qn);
190
+ expect(revived?.rearmCount).toBe(cycle);
191
+ await driveUntilDead();
192
+ }
193
+
194
+ const exhausted = await getConsumerState(stack.db, qn);
195
+ expect(exhausted?.status).toBe("dead");
196
+ expect(exhausted?.rearmCount).toBe(3);
197
+
198
+ // Cooldown elapsed again, but the budget is spent — stays dead.
199
+ await backdateUpdatedAt(10);
200
+ await stack.eventDispatcher?.runOnce();
201
+ const stillExhausted = await getConsumerState(stack.db, qn);
202
+ expect(stillExhausted?.status).toBe("dead");
203
+ expect(stillExhausted?.rearmCount).toBe(3);
204
+ });
205
+
206
+ test("kumiko_event_consumer_rearm_exhausted_total fires once, not per still-dead pass (#1362)", async () => {
207
+ // Dedicated stack with a RecordingMeter — module-level `stack` above is
208
+ // shared across every test in this file and has no metric capture.
209
+ const metricEvents: MetricEvent[] = [];
210
+ const meter = new RecordingMeter((e) => metricEvents.push(e));
211
+ const tracer = new RecordingTracer({
212
+ sensitiveConfig: DEFAULT_SENSITIVE_CONFIG,
213
+ onSpanEnd: () => {},
214
+ });
215
+ const recordingProvider: ObservabilityProvider = {
216
+ name: "recording",
217
+ meter,
218
+ tracer,
219
+ shutdown: async () => {},
220
+ };
221
+
222
+ const recStack = await setupTestStack({
223
+ features: [rearmFeature],
224
+ systemHooks: [],
225
+ observability: recordingProvider,
226
+ });
227
+ try {
228
+ await unsafeCreateEntityTable(recStack.db, sharedWidgetEntity, "widget");
229
+ const recTdb = createTenantDb(recStack.db, admin.tenantId);
230
+
231
+ poisonNames.add("poison-metric");
232
+ await executor.create({ name: "poison-metric" }, admin, recTdb);
233
+ for (let i = 0; i < 10; i++) {
234
+ await recStack.eventDispatcher?.runOnce();
235
+ }
236
+ // The 10th pass is the one whose failed delivery flips status to
237
+ // "dead" (persisted for the NEXT pass to see) — acquireConsumerState's
238
+ // dead-branch (where the exhausted-metric fires) only runs starting
239
+ // from the pass AFTER that transition, same as the "stays dead within
240
+ // cooldown window" test above needing one extra runOnce().
241
+ await recStack.eventDispatcher?.runOnce();
242
+
243
+ const exhaustedEvents = () =>
244
+ metricEvents.filter(
245
+ (e) =>
246
+ e.type === "counter.inc" && e.name === "kumiko_event_consumer_rearm_exhausted_total",
247
+ );
248
+ expect(exhaustedEvents().length).toBe(1);
249
+
250
+ // Several more passes while it's still dead — must stay at 1, not
251
+ // re-fire on every pass that observes "still dead" (log/metric spam
252
+ // the finding flagged).
253
+ for (let i = 0; i < 5; i++) {
254
+ await recStack.eventDispatcher?.runOnce();
255
+ }
256
+ expect(exhaustedEvents().length).toBe(1);
257
+ expect(exhaustedEvents()[0]?.labels?.["consumer"]).toBe(qn);
258
+ } finally {
259
+ await resetEventStore(recStack, ["read_widgets"]);
260
+ await recStack.cleanup();
261
+ }
262
+ });
263
+ });
@@ -159,10 +159,20 @@ describe("event-dispatcher — happy path", () => {
159
159
  await stack.eventDispatcher?.runOnce();
160
160
  expect(captureA).toHaveLength(1);
161
161
 
162
+ const stateBefore = await getConsumerState(stack.db, qnA);
163
+
162
164
  const second = await stack.eventDispatcher?.runOnce();
163
165
  expect(second?.byConsumer[qnA]).toEqual({ processed: 0, failed: 0 });
164
166
  // Still only one — consumer correctly saw "nothing new past my cursor".
165
167
  expect(captureA).toHaveLength(1);
168
+
169
+ // No pending events means no write at all — an idle pass must not touch
170
+ // updated_at/status, otherwise every empty poll tick burns a WAL record.
171
+ const stateAfter = await getConsumerState(stack.db, qnA);
172
+ expect(stateAfter?.updatedAt.epochMilliseconds).toEqual(
173
+ stateBefore?.updatedAt.epochMilliseconds,
174
+ );
175
+ expect(stateAfter?.status).toBe(stateBefore?.status);
166
176
  });
167
177
 
168
178
  test("cursor advances only past successfully-consumed events", async () => {
@@ -0,0 +1,106 @@
1
+ // createJobTriggerEventConsumer — proves r.job's trigger.on can fire on an
2
+ // r.defineEvent QN appended by a multiStreamProjection's unsafeAppendEvent
3
+ // (kumiko-framework#1505). Mirrors document-ingest-foundation's actual
4
+ // request-ingest MSP (upload → fileRef.created → an owned defineEvent),
5
+ // the motivating case for this fix — fileRef.created itself never reaches
6
+ // jobRunner.handleEvent because the upload route appends it via the raw
7
+ // event-store executor, not a write-handler dispatch (see #1505).
8
+ //
9
+ // Not covered here: a job triggered on a write/query-handler QN still
10
+ // firing exactly once (unaffected by the new consumer). The full suite
11
+ // stays green (e.g. the lane-routing sample), but that's not a positive
12
+ // test of the partition guard — no stored event's `type` is ever a
13
+ // handler QN in practice (entity events are "entity.verb"; custom
14
+ // write-handlers like lane-routing's don't append to the store at all),
15
+ // so `getWriteHandler`/`getQueryHandler` in the new consumer's handler is
16
+ // defense-in-depth for an input shape the framework doesn't currently
17
+ // produce, not something exercised end-to-end by any test today.
18
+
19
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
20
+ import { z } from "zod";
21
+ import { entityEventName } from "../../db";
22
+ import { defineFeature } from "../../engine";
23
+ import { createInMemoryFileProvider, type InMemoryFileProvider } from "../../files";
24
+ import { setupTestStack, type TestStack, TestUsers } from "../../stack";
25
+ import { waitFor } from "../../testing";
26
+
27
+ const ITEM_REQUESTED_EVENT_QN = "job-trigger-fixture:event:item-requested";
28
+ const FILE_REF_CREATED = entityEventName("fileRef", "created");
29
+
30
+ const processedItems: Array<{ readonly fileRefId: string }> = [];
31
+
32
+ const jobTriggerFixtureFeature = defineFeature("job-trigger-fixture", (r) => {
33
+ r.defineEvent("item-requested", z.object({ fileRefId: z.string().min(1) }));
34
+
35
+ // Mirrors document-ingest-foundation's request-ingest MSP exactly: reacts
36
+ // to fileRef.created, appends a NEW event via unsafeAppendEvent — no
37
+ // write-handler behind the appended event itself.
38
+ r.multiStreamProjection({
39
+ name: "request-item",
40
+ apply: {
41
+ [FILE_REF_CREATED]: async (event, _tx, ctx) => {
42
+ await ctx.unsafeAppendEvent({
43
+ aggregateId: event.aggregateId,
44
+ aggregateType: "job-trigger-fixture-request",
45
+ type: ITEM_REQUESTED_EVENT_QN,
46
+ payload: { fileRefId: event.aggregateId },
47
+ });
48
+ },
49
+ },
50
+ });
51
+
52
+ // Under test: only reachable via createJobTriggerEventConsumer, since
53
+ // ITEM_REQUESTED_EVENT_QN is an r.defineEvent QN, not a handler QN.
54
+ r.job(
55
+ "process-item",
56
+ { trigger: { on: ITEM_REQUESTED_EVENT_QN }, runIn: "worker" },
57
+ async (payload) => {
58
+ processedItems.push({ fileRefId: payload["fileRefId"] as string });
59
+ },
60
+ );
61
+ });
62
+
63
+ let stack: TestStack;
64
+ let provider: InMemoryFileProvider;
65
+
66
+ beforeAll(async () => {
67
+ provider = createInMemoryFileProvider();
68
+ stack = await setupTestStack({
69
+ features: [jobTriggerFixtureFeature],
70
+ files: { storageProvider: provider },
71
+ jobs: { consumerLane: "worker" },
72
+ });
73
+ });
74
+
75
+ afterAll(async () => {
76
+ await stack.cleanup();
77
+ });
78
+
79
+ beforeEach(() => {
80
+ processedItems.length = 0;
81
+ provider.clear();
82
+ });
83
+
84
+ describe("job-trigger event consumer", () => {
85
+ test("a job triggers on an r.defineEvent QN appended by an MSP's unsafeAppendEvent", async () => {
86
+ const token = await stack.jwt.sign(TestUsers.admin);
87
+ const formData = new FormData();
88
+ formData.append("file", new File([Buffer.from("hello")], "note.txt", { type: "text/plain" }));
89
+ const res = await stack.app.request("/api/files", {
90
+ method: "POST",
91
+ headers: { Authorization: `Bearer ${token}` },
92
+ body: formData,
93
+ });
94
+ expect(res.status).toBe(201);
95
+
96
+ await waitFor(async () => {
97
+ // Drives both the MSP (appends item-requested off fileRef.created)
98
+ // and the new job-trigger consumer (reacts to it) — may need more
99
+ // than one pass since the MSP's append happens mid-drain.
100
+ await stack.eventDispatcher?.runOnce();
101
+ expect(processedItems).toHaveLength(1);
102
+ });
103
+
104
+ expect(processedItems[0]?.fileRefId).toBeTruthy();
105
+ });
106
+ });