@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,402 @@
1
+ // The five write-verbs (create/update/delete/forget/restore) have several
2
+ // error paths the existing suites (event-store-executor.integration.test.ts,
3
+ // unique-violation-mapping.integration.test.ts) don't touch: entity- and
4
+ // field-level ownership denials, explicit version conflicts, the forget()
5
+ // verb entirely, and restore()'s two precondition failures.
6
+
7
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
8
+ import { asRawClient } from "../../db/query";
9
+ import { createEntity, createTextField } from "../../engine";
10
+ import { from } from "../../engine/ownership";
11
+ import { createEventsTable } from "../../event-store";
12
+ import type { EntityCache } from "../../pipeline/entity-cache";
13
+ import { createTestDb, type TestDb, TestUsers, unsafeCreateEntityTable } from "../../stack";
14
+ import { createEventStoreExecutor } from "../event-store-executor";
15
+ import { buildEntityTable } from "../table-builder";
16
+ import { createTenantDb, type TenantDb } from "../tenant-db";
17
+
18
+ let testDb: TestDb;
19
+ let tdb: TenantDb;
20
+ const admin = TestUsers.admin;
21
+ const nonAdmin = TestUsers.user;
22
+
23
+ beforeAll(async () => {
24
+ testDb = await createTestDb();
25
+ await createEventsTable(testDb.db);
26
+ tdb = createTenantDb(testDb.db, admin.tenantId);
27
+ });
28
+
29
+ afterAll(async () => {
30
+ await testDb.cleanup();
31
+ });
32
+
33
+ // =============================================================================
34
+ // entity-level ownership_denied — create/update/delete/forget/restore
35
+ // =============================================================================
36
+
37
+ const restrictedEntity = createEntity({
38
+ table: "read_es_write_restricted",
39
+ fields: {
40
+ email: createTextField({ required: true }),
41
+ },
42
+ softDelete: true,
43
+ access: { write: { Admin: "all" } },
44
+ });
45
+ const restrictedTable = buildEntityTable("esWriteRestricted", restrictedEntity);
46
+
47
+ describe("event-store-executor write-verbs — entity-level ownership_denied", () => {
48
+ const crud = createEventStoreExecutor(restrictedTable, restrictedEntity, {
49
+ entityName: "esWriteRestricted",
50
+ });
51
+
52
+ beforeAll(async () => {
53
+ await unsafeCreateEntityTable(testDb.db, restrictedEntity, "esWriteRestricted");
54
+ });
55
+
56
+ beforeEach(async () => {
57
+ await asRawClient(testDb.db).unsafe(
58
+ `TRUNCATE kumiko_events, read_es_write_restricted RESTART IDENTITY CASCADE`,
59
+ );
60
+ });
61
+
62
+ test("create: role without a write-rule → ownership_denied", async () => {
63
+ const result = await crud.create({ email: "denied@test.de" }, nonAdmin, tdb);
64
+ expect(result.isSuccess).toBe(false);
65
+ if (result.isSuccess) return;
66
+ expect((result.error.details as { reason?: string }).reason).toBe("ownership_denied");
67
+ });
68
+
69
+ test("update: role without a write-rule → ownership_denied", async () => {
70
+ const created = await crud.create({ email: "owner@test.de" }, admin, tdb);
71
+ if (!created.isSuccess) throw new Error("setup failed");
72
+
73
+ const result = await crud.update(
74
+ { id: created.data.id, version: 1, changes: { email: "changed@test.de" } },
75
+ nonAdmin,
76
+ tdb,
77
+ );
78
+ expect(result.isSuccess).toBe(false);
79
+ if (result.isSuccess) return;
80
+ expect((result.error.details as { reason?: string }).reason).toBe("ownership_denied");
81
+ });
82
+
83
+ test("delete: role without a write-rule → ownership_denied", async () => {
84
+ const created = await crud.create({ email: "todelete@test.de" }, admin, tdb);
85
+ if (!created.isSuccess) throw new Error("setup failed");
86
+
87
+ const result = await crud.delete({ id: created.data.id }, nonAdmin, tdb);
88
+ expect(result.isSuccess).toBe(false);
89
+ if (result.isSuccess) return;
90
+ expect((result.error.details as { reason?: string }).reason).toBe("ownership_denied");
91
+ });
92
+
93
+ test("forget: role without a write-rule → ownership_denied", async () => {
94
+ const created = await crud.create({ email: "toforget@test.de" }, admin, tdb);
95
+ if (!created.isSuccess) throw new Error("setup failed");
96
+
97
+ const result = await crud.forget({ id: created.data.id }, nonAdmin, tdb);
98
+ expect(result.isSuccess).toBe(false);
99
+ if (result.isSuccess) return;
100
+ expect((result.error.details as { reason?: string }).reason).toBe("ownership_denied");
101
+ });
102
+
103
+ test("forget: happy path hard-deletes the row and appends a forgotten event", async () => {
104
+ const created = await crud.create({ email: "gone@test.de" }, admin, tdb);
105
+ if (!created.isSuccess) throw new Error("setup failed");
106
+
107
+ const result = await crud.forget({ id: created.data.id }, admin, tdb);
108
+ expect(result.isSuccess).toBe(true);
109
+
110
+ const rows = (await asRawClient(testDb.db).unsafe(
111
+ `SELECT id FROM read_es_write_restricted WHERE email = 'gone@test.de'`,
112
+ )) as unknown[];
113
+ expect(rows).toHaveLength(0);
114
+
115
+ const events = (await asRawClient(testDb.db).unsafe(
116
+ `SELECT type FROM kumiko_events WHERE type = 'esWriteRestricted.forgotten'`,
117
+ )) as unknown[];
118
+ expect(events).toHaveLength(1);
119
+ });
120
+
121
+ test("restore: role without a write-rule → ownership_denied", async () => {
122
+ const created = await crud.create({ email: "torestore@test.de" }, admin, tdb);
123
+ if (!created.isSuccess) throw new Error("setup failed");
124
+ const deleted = await crud.delete({ id: created.data.id }, admin, tdb);
125
+ if (!deleted.isSuccess) throw new Error("setup failed: delete");
126
+
127
+ const result = await crud.restore({ id: created.data.id }, nonAdmin, tdb);
128
+ expect(result.isSuccess).toBe(false);
129
+ if (result.isSuccess) return;
130
+ expect((result.error.details as { reason?: string }).reason).toBe("ownership_denied");
131
+ });
132
+
133
+ test("restore: not yet deleted row → not_deleted", async () => {
134
+ const created = await crud.create({ email: "notdeleted@test.de" }, admin, tdb);
135
+ if (!created.isSuccess) throw new Error("setup failed");
136
+
137
+ const result = await crud.restore({ id: created.data.id }, admin, tdb);
138
+ expect(result.isSuccess).toBe(false);
139
+ if (result.isSuccess) return;
140
+ expect((result.error.details as { reason?: string }).reason).toBe("not_deleted");
141
+ });
142
+ });
143
+
144
+ // =============================================================================
145
+ // restore() on a non-soft-delete entity → soft_delete_not_enabled
146
+ // =============================================================================
147
+
148
+ const hardDeleteEntity = createEntity({
149
+ table: "read_es_write_hard",
150
+ fields: {
151
+ email: createTextField({ required: true }),
152
+ },
153
+ });
154
+ const hardDeleteTable = buildEntityTable("esWriteHard", hardDeleteEntity);
155
+
156
+ describe("event-store-executor write-verbs — restore without softDelete", () => {
157
+ const crud = createEventStoreExecutor(hardDeleteTable, hardDeleteEntity, {
158
+ entityName: "esWriteHard",
159
+ });
160
+
161
+ beforeAll(async () => {
162
+ await unsafeCreateEntityTable(testDb.db, hardDeleteEntity, "esWriteHard");
163
+ });
164
+
165
+ beforeEach(async () => {
166
+ await asRawClient(testDb.db).unsafe(
167
+ `TRUNCATE kumiko_events, read_es_write_hard RESTART IDENTITY CASCADE`,
168
+ );
169
+ });
170
+
171
+ test("restore on an entity without softDelete → soft_delete_not_enabled", async () => {
172
+ const created = await crud.create({ email: "hard@test.de" }, admin, tdb);
173
+ if (!created.isSuccess) throw new Error("setup failed");
174
+
175
+ const result = await crud.restore({ id: created.data.id }, admin, tdb);
176
+ expect(result.isSuccess).toBe(false);
177
+ if (result.isSuccess) return;
178
+ expect((result.error.details as { reason?: string }).reason).toBe("soft_delete_not_enabled");
179
+ });
180
+ });
181
+
182
+ // =============================================================================
183
+ // field-level ownership_denied — checkWriteFieldOwnership via a row-scoped rule
184
+ // =============================================================================
185
+
186
+ const ownedFieldEntity = createEntity({
187
+ table: "read_es_write_owned_field",
188
+ fields: {
189
+ authorId: createTextField({ required: true }),
190
+ note: createTextField({
191
+ access: { write: { Admin: "all", User: from("user:id", "authorId") } },
192
+ }),
193
+ },
194
+ });
195
+ const ownedFieldTable = buildEntityTable("esWriteOwnedField", ownedFieldEntity);
196
+
197
+ describe("event-store-executor write-verbs — field-level ownership_denied", () => {
198
+ const crud = createEventStoreExecutor(ownedFieldTable, ownedFieldEntity, {
199
+ entityName: "esWriteOwnedField",
200
+ });
201
+
202
+ beforeAll(async () => {
203
+ await unsafeCreateEntityTable(testDb.db, ownedFieldEntity, "esWriteOwnedField");
204
+ });
205
+
206
+ beforeEach(async () => {
207
+ await asRawClient(testDb.db).unsafe(
208
+ `TRUNCATE kumiko_events, read_es_write_owned_field RESTART IDENTITY CASCADE`,
209
+ );
210
+ });
211
+
212
+ test("create: writing `note` while authorId names someone else → ownership_denied (field scope)", async () => {
213
+ const result = await crud.create(
214
+ { authorId: TestUsers.driver.id, note: "not mine" },
215
+ nonAdmin,
216
+ tdb,
217
+ );
218
+ expect(result.isSuccess).toBe(false);
219
+ if (result.isSuccess) return;
220
+ const details = result.error.details as { reason?: string; scope?: string; field?: string };
221
+ expect(details.reason).toBe("ownership_denied");
222
+ expect(details.scope).toBe("field");
223
+ expect(details.field).toBe("note");
224
+ });
225
+
226
+ test("create: writing `note` as the named author succeeds", async () => {
227
+ const result = await crud.create({ authorId: nonAdmin.id, note: "mine" }, nonAdmin, tdb);
228
+ expect(result.isSuccess).toBe(true);
229
+ });
230
+
231
+ test("update: changing `note` on someone else's row → ownership_denied (field scope)", async () => {
232
+ const created = await crud.create(
233
+ { authorId: TestUsers.driver.id, note: "original" },
234
+ admin,
235
+ tdb,
236
+ );
237
+ if (!created.isSuccess) throw new Error("setup failed");
238
+
239
+ const result = await crud.update(
240
+ { id: created.data.id, version: 1, changes: { note: "hijacked" } },
241
+ nonAdmin,
242
+ tdb,
243
+ );
244
+ expect(result.isSuccess).toBe(false);
245
+ if (result.isSuccess) return;
246
+ expect((result.error.details as { reason?: string }).reason).toBe("ownership_denied");
247
+ });
248
+ });
249
+
250
+ // =============================================================================
251
+ // version_conflict — explicit-id create collision, missing version on update
252
+ // =============================================================================
253
+
254
+ const versionEntity = createEntity({
255
+ table: "read_es_write_version",
256
+ fields: {
257
+ email: createTextField({ required: true }),
258
+ },
259
+ });
260
+ const versionTable = buildEntityTable("esWriteVersion", versionEntity);
261
+
262
+ describe("event-store-executor write-verbs — version_conflict edge cases", () => {
263
+ const crud = createEventStoreExecutor(versionTable, versionEntity, {
264
+ entityName: "esWriteVersion",
265
+ });
266
+
267
+ beforeAll(async () => {
268
+ await unsafeCreateEntityTable(testDb.db, versionEntity, "esWriteVersion");
269
+ });
270
+
271
+ beforeEach(async () => {
272
+ await asRawClient(testDb.db).unsafe(
273
+ `TRUNCATE kumiko_events, read_es_write_version RESTART IDENTITY CASCADE`,
274
+ );
275
+ });
276
+
277
+ test("create with an explicit id that already has a stream → version_conflict", async () => {
278
+ const first = await crud.create({ email: "first@test.de" }, admin, tdb);
279
+ if (!first.isSuccess) throw new Error("setup failed");
280
+
281
+ const collision = await crud.create({ id: first.data.id, email: "second@test.de" }, admin, tdb);
282
+ expect(collision.isSuccess).toBe(false);
283
+ if (collision.isSuccess) return;
284
+ expect(collision.error.code).toBe("version_conflict");
285
+ });
286
+
287
+ test("update without a version field (and no skipOptimisticLock) → version_conflict", async () => {
288
+ const created = await crud.create({ email: "noversion@test.de" }, admin, tdb);
289
+ if (!created.isSuccess) throw new Error("setup failed");
290
+
291
+ const result = await crud.update(
292
+ { id: created.data.id, changes: { email: "changed@test.de" } },
293
+ admin,
294
+ tdb,
295
+ );
296
+ expect(result.isSuccess).toBe(false);
297
+ if (result.isSuccess) return;
298
+ expect(result.error.code).toBe("version_conflict");
299
+ });
300
+ });
301
+
302
+ // =============================================================================
303
+ // Concurrent update race → EventStoreVersionConflict catch + entityCache.del
304
+ // on forget/restore (create/update/delete already exercise cache in the
305
+ // main suite; forget/restore del() stayed uncovered).
306
+ // =============================================================================
307
+
308
+ const raceEntity = createEntity({
309
+ table: "read_es_write_race",
310
+ fields: {
311
+ email: createTextField({ required: true }),
312
+ },
313
+ softDelete: true,
314
+ });
315
+ const raceTable = buildEntityTable("esWriteRace", raceEntity);
316
+
317
+ describe("event-store-executor write-verbs — concurrent version race + cache", () => {
318
+ const store = new Map<string, Record<string, unknown>>();
319
+ const entityCache: EntityCache = {
320
+ get: async (tenantId, name, id) => store.get(`${tenantId}:${name}:${id}`) ?? null,
321
+ mget: async () => new Map(),
322
+ set: async (tenantId, name, id, data) => {
323
+ store.set(`${tenantId}:${name}:${id}`, data);
324
+ },
325
+ mset: async (tenantId, name, entries) => {
326
+ for (const { id, data } of entries) store.set(`${tenantId}:${name}:${id}`, data);
327
+ },
328
+ del: async (tenantId, name, id) => {
329
+ store.delete(`${tenantId}:${name}:${id}`);
330
+ },
331
+ };
332
+ const crud = createEventStoreExecutor(raceTable, raceEntity, {
333
+ entityName: "esWriteRace",
334
+ entityCache,
335
+ });
336
+
337
+ beforeAll(async () => {
338
+ await unsafeCreateEntityTable(testDb.db, raceEntity, "esWriteRace");
339
+ });
340
+
341
+ beforeEach(async () => {
342
+ store.clear();
343
+ await asRawClient(testDb.db).unsafe(
344
+ `TRUNCATE kumiko_events, read_es_write_race RESTART IDENTITY CASCADE`,
345
+ );
346
+ });
347
+
348
+ test("two concurrent updates with the same version → one wins, one version_conflict", async () => {
349
+ const created = await crud.create({ email: "race@test.de" }, admin, tdb);
350
+ if (!created.isSuccess) throw new Error("setup failed");
351
+ const id = created.data.id;
352
+
353
+ const [a, b] = await Promise.all([
354
+ crud.update({ id, version: 1, changes: { email: "a@test.de" } }, admin, tdb),
355
+ crud.update({ id, version: 1, changes: { email: "b@test.de" } }, admin, tdb),
356
+ ]);
357
+
358
+ const results = [a, b];
359
+ expect(results.filter((r) => r.isSuccess)).toHaveLength(1);
360
+ expect(results.filter((r) => !r.isSuccess && r.error.code === "version_conflict")).toHaveLength(
361
+ 1,
362
+ );
363
+
364
+ // Guard against the known Bun.SQL pooled-connection-poisoning class: the
365
+ // losing update above provoked a real 23505 unique-violation on the
366
+ // shared pool. The two tests below this one reuse `tdb`/`testDb.db`, so
367
+ // a poisoned connection would surface as their unrelated queries
368
+ // failing — a trivial round-trip here catches that immediately instead
369
+ // of leaving it to whichever later test happens to hit the bad
370
+ // connection.
371
+ const healthCheck = (await asRawClient(testDb.db).unsafe(`SELECT 1 AS ok`)) as Array<{
372
+ ok: number;
373
+ }>;
374
+ expect(healthCheck[0]?.ok).toBe(1);
375
+ });
376
+
377
+ test("forget with entityCache clears the cache entry", async () => {
378
+ const created = await crud.create({ email: "cache-forget@test.de" }, admin, tdb);
379
+ if (!created.isSuccess) throw new Error("setup failed");
380
+ const id = created.data.id;
381
+ const key = `${admin.tenantId}:esWriteRace:${id}`;
382
+ store.set(key, { email: "poison" });
383
+
384
+ const result = await crud.forget({ id }, admin, tdb);
385
+ expect(result.isSuccess).toBe(true);
386
+ expect(store.has(key)).toBe(false);
387
+ });
388
+
389
+ test("restore with entityCache clears the cache entry", async () => {
390
+ const created = await crud.create({ email: "cache-restore@test.de" }, admin, tdb);
391
+ if (!created.isSuccess) throw new Error("setup failed");
392
+ const id = created.data.id;
393
+ await crud.delete({ id }, admin, tdb);
394
+
395
+ const key = `${admin.tenantId}:esWriteRace:${id}`;
396
+ store.set(key, { email: "poison" });
397
+
398
+ const result = await crud.restore({ id }, admin, tdb);
399
+ expect(result.isSuccess).toBe(true);
400
+ expect(store.has(key)).toBe(false);
401
+ });
402
+ });