@cosmicdrift/kumiko-framework 0.1.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 (388) hide show
  1. package/README.md +159 -0
  2. package/package.json +91 -0
  3. package/src/__tests__/anonymous-access.integration.ts +325 -0
  4. package/src/__tests__/error-contract.integration.ts +435 -0
  5. package/src/__tests__/field-access.integration.ts +269 -0
  6. package/src/__tests__/full-stack.integration.ts +914 -0
  7. package/src/__tests__/ownership.integration.ts +449 -0
  8. package/src/__tests__/reference-data.integration.ts +198 -0
  9. package/src/__tests__/transition-guard.integration.ts +340 -0
  10. package/src/api/__tests__/api.test.ts +337 -0
  11. package/src/api/__tests__/auth-middleware-transport.test.ts +80 -0
  12. package/src/api/__tests__/auth-routes-cookie.test.ts +179 -0
  13. package/src/api/__tests__/batch.integration.ts +404 -0
  14. package/src/api/__tests__/body-limit.test.ts +88 -0
  15. package/src/api/__tests__/csrf-middleware.test.ts +97 -0
  16. package/src/api/__tests__/dispatcher-live.integration.ts +216 -0
  17. package/src/api/__tests__/metrics-endpoint.test.ts +126 -0
  18. package/src/api/__tests__/nested-write.integration.ts +213 -0
  19. package/src/api/__tests__/readiness.test.ts +76 -0
  20. package/src/api/__tests__/request-id-middleware.test.ts +72 -0
  21. package/src/api/__tests__/sse-broker.test.ts +58 -0
  22. package/src/api/__tests__/sse-route.test.ts +112 -0
  23. package/src/api/anonymous-cookie.ts +60 -0
  24. package/src/api/api-constants.ts +64 -0
  25. package/src/api/auth-middleware.ts +418 -0
  26. package/src/api/auth-routes.ts +982 -0
  27. package/src/api/csrf-middleware.ts +77 -0
  28. package/src/api/index.ts +31 -0
  29. package/src/api/jwt.ts +66 -0
  30. package/src/api/observability-middleware.ts +89 -0
  31. package/src/api/readiness.ts +132 -0
  32. package/src/api/request-context.ts +49 -0
  33. package/src/api/request-id-middleware.ts +50 -0
  34. package/src/api/route-registrars.ts +195 -0
  35. package/src/api/routes.ts +135 -0
  36. package/src/api/server.ts +640 -0
  37. package/src/api/sse-broker.ts +71 -0
  38. package/src/api/sse-route.ts +62 -0
  39. package/src/api/tokens.ts +16 -0
  40. package/src/db/__tests__/apply-entity-event-tenant.integration.ts +159 -0
  41. package/src/db/__tests__/compound-types.test.ts +114 -0
  42. package/src/db/__tests__/connection-options.test.ts +68 -0
  43. package/src/db/__tests__/cursor.test.ts +41 -0
  44. package/src/db/__tests__/db-helpers.test.ts +369 -0
  45. package/src/db/__tests__/dialect-instant.test.ts +50 -0
  46. package/src/db/__tests__/drizzle-helpers.integration.ts +186 -0
  47. package/src/db/__tests__/drizzle-table-types.test.ts +162 -0
  48. package/src/db/__tests__/encryption.test.ts +39 -0
  49. package/src/db/__tests__/event-store-executor-list.integration.ts +313 -0
  50. package/src/db/__tests__/event-store-executor.integration.ts +235 -0
  51. package/src/db/__tests__/implicit-projection-equivalence.integration.ts +304 -0
  52. package/src/db/__tests__/located-timestamp.test.ts +184 -0
  53. package/src/db/__tests__/money.test.ts +199 -0
  54. package/src/db/__tests__/multi-row-insert.integration.ts +76 -0
  55. package/src/db/__tests__/parse-auto-verb.test.ts +70 -0
  56. package/src/db/__tests__/required-not-null-migration-safety.integration.ts +105 -0
  57. package/src/db/__tests__/row-helpers.test.ts +59 -0
  58. package/src/db/__tests__/schema-migration.integration.ts +273 -0
  59. package/src/db/__tests__/table-builder-indexes.test.ts +153 -0
  60. package/src/db/__tests__/table-builder-required.test.ts +216 -0
  61. package/src/db/__tests__/tenant-db.integration.ts +606 -0
  62. package/src/db/__tests__/unique-violation-mapping.integration.ts +166 -0
  63. package/src/db/apply-entity-event.ts +188 -0
  64. package/src/db/assert-exists-in.ts +59 -0
  65. package/src/db/compound-types.ts +47 -0
  66. package/src/db/connection.ts +104 -0
  67. package/src/db/cursor.ts +83 -0
  68. package/src/db/dialect.ts +109 -0
  69. package/src/db/eagerload.ts +174 -0
  70. package/src/db/encryption.ts +39 -0
  71. package/src/db/event-store-executor.ts +906 -0
  72. package/src/db/index.ts +55 -0
  73. package/src/db/located-timestamp.ts +114 -0
  74. package/src/db/money.ts +120 -0
  75. package/src/db/pg-error.ts +46 -0
  76. package/src/db/reference-data.ts +77 -0
  77. package/src/db/row-helpers.ts +53 -0
  78. package/src/db/schema-inspection.ts +25 -0
  79. package/src/db/table-builder.ts +475 -0
  80. package/src/db/tenant-db.ts +434 -0
  81. package/src/engine/__tests__/auth-claims-registrar.test.ts +74 -0
  82. package/src/engine/__tests__/boot-validator-located-timestamps.test.ts +108 -0
  83. package/src/engine/__tests__/boot-validator.test.ts +1865 -0
  84. package/src/engine/__tests__/build-app-schema.test.ts +154 -0
  85. package/src/engine/__tests__/claim-keys.test.ts +274 -0
  86. package/src/engine/__tests__/config-helpers.test.ts +236 -0
  87. package/src/engine/__tests__/effective-features.test.ts +86 -0
  88. package/src/engine/__tests__/engine.test.ts +1461 -0
  89. package/src/engine/__tests__/entity-handlers.test.ts +274 -0
  90. package/src/engine/__tests__/event-helpers.test.ts +68 -0
  91. package/src/engine/__tests__/extends-registrar.test.ts +159 -0
  92. package/src/engine/__tests__/factories-long-text.test.ts +84 -0
  93. package/src/engine/__tests__/factories-time.test.ts +158 -0
  94. package/src/engine/__tests__/field-predicates.test.ts +48 -0
  95. package/src/engine/__tests__/hook-phases.test.ts +132 -0
  96. package/src/engine/__tests__/identifiers.test.ts +35 -0
  97. package/src/engine/__tests__/lifecycle-hooks.test.ts +237 -0
  98. package/src/engine/__tests__/nav.test.ts +267 -0
  99. package/src/engine/__tests__/ownership.test.ts +421 -0
  100. package/src/engine/__tests__/parse-ref-target.test.ts +43 -0
  101. package/src/engine/__tests__/projection-helpers.test.ts +62 -0
  102. package/src/engine/__tests__/projection.test.ts +191 -0
  103. package/src/engine/__tests__/qualified-name.test.ts +264 -0
  104. package/src/engine/__tests__/resolve-config-or-param.test.ts +315 -0
  105. package/src/engine/__tests__/run-in.test.ts +38 -0
  106. package/src/engine/__tests__/schema-builder.test.ts +380 -0
  107. package/src/engine/__tests__/screen.test.ts +408 -0
  108. package/src/engine/__tests__/state-machine.test.ts +148 -0
  109. package/src/engine/__tests__/system-user.test.ts +57 -0
  110. package/src/engine/__tests__/validation-hooks.test.ts +71 -0
  111. package/src/engine/access.ts +23 -0
  112. package/src/engine/boot-validator.ts +1528 -0
  113. package/src/engine/build-app-schema.ts +125 -0
  114. package/src/engine/config-helpers.ts +115 -0
  115. package/src/engine/constants.ts +85 -0
  116. package/src/engine/create-app.ts +98 -0
  117. package/src/engine/define-feature.ts +702 -0
  118. package/src/engine/define-handler.ts +78 -0
  119. package/src/engine/define-roles.ts +19 -0
  120. package/src/engine/effective-features.ts +87 -0
  121. package/src/engine/entity-handlers.ts +364 -0
  122. package/src/engine/event-helpers.ts +73 -0
  123. package/src/engine/factories.ts +328 -0
  124. package/src/engine/feature-ast/__tests__/canonical-form.test.ts +416 -0
  125. package/src/engine/feature-ast/__tests__/parse-happy-path.test.ts +197 -0
  126. package/src/engine/feature-ast/__tests__/parse-real-features.test.ts +128 -0
  127. package/src/engine/feature-ast/__tests__/parse.test.ts +888 -0
  128. package/src/engine/feature-ast/__tests__/patch.test.ts +360 -0
  129. package/src/engine/feature-ast/__tests__/patcher.test.ts +469 -0
  130. package/src/engine/feature-ast/__tests__/render-roundtrip.test.ts +287 -0
  131. package/src/engine/feature-ast/extractors.ts +2562 -0
  132. package/src/engine/feature-ast/index.ts +105 -0
  133. package/src/engine/feature-ast/parse.ts +369 -0
  134. package/src/engine/feature-ast/patch.ts +525 -0
  135. package/src/engine/feature-ast/patcher.ts +518 -0
  136. package/src/engine/feature-ast/patterns.ts +434 -0
  137. package/src/engine/feature-ast/render.ts +602 -0
  138. package/src/engine/feature-ast/source-location.ts +45 -0
  139. package/src/engine/field-access.ts +120 -0
  140. package/src/engine/index.ts +254 -0
  141. package/src/engine/ownership.ts +337 -0
  142. package/src/engine/parse-ref-target.ts +22 -0
  143. package/src/engine/pattern-library/__tests__/library.test.ts +351 -0
  144. package/src/engine/pattern-library/index.ts +24 -0
  145. package/src/engine/pattern-library/library.ts +1117 -0
  146. package/src/engine/pattern-library/types.ts +255 -0
  147. package/src/engine/projection-helpers.ts +85 -0
  148. package/src/engine/qualified-name.ts +122 -0
  149. package/src/engine/read-claim.ts +31 -0
  150. package/src/engine/registry.ts +1325 -0
  151. package/src/engine/resolve-config-or-param.ts +153 -0
  152. package/src/engine/run-in.ts +29 -0
  153. package/src/engine/schema-builder.ts +175 -0
  154. package/src/engine/screen-filter-ops.ts +51 -0
  155. package/src/engine/state-machine.ts +70 -0
  156. package/src/engine/system-user.ts +32 -0
  157. package/src/engine/types/config.ts +306 -0
  158. package/src/engine/types/event-type-map.ts +37 -0
  159. package/src/engine/types/feature.ts +574 -0
  160. package/src/engine/types/fields.ts +422 -0
  161. package/src/engine/types/handlers.ts +742 -0
  162. package/src/engine/types/hooks.ts +142 -0
  163. package/src/engine/types/http-route.ts +54 -0
  164. package/src/engine/types/identifiers.ts +47 -0
  165. package/src/engine/types/index.ts +208 -0
  166. package/src/engine/types/nav.ts +46 -0
  167. package/src/engine/types/projection.ts +132 -0
  168. package/src/engine/types/relations.ts +51 -0
  169. package/src/engine/types/screen.ts +452 -0
  170. package/src/engine/types/workspace.ts +42 -0
  171. package/src/engine/validation.ts +33 -0
  172. package/src/entrypoint/__tests__/entrypoint-job-wiring.integration.ts +173 -0
  173. package/src/entrypoint/__tests__/split-deploy.integration.ts +297 -0
  174. package/src/entrypoint/index.ts +442 -0
  175. package/src/errors/__tests__/classes.test.ts +371 -0
  176. package/src/errors/__tests__/write-failures.test.ts +109 -0
  177. package/src/errors/classes.ts +249 -0
  178. package/src/errors/i18n/de.yaml +83 -0
  179. package/src/errors/i18n/en.yaml +80 -0
  180. package/src/errors/index.ts +41 -0
  181. package/src/errors/kumiko-error.ts +67 -0
  182. package/src/errors/reasons.ts +36 -0
  183. package/src/errors/serialize.ts +136 -0
  184. package/src/errors/transition-details.ts +30 -0
  185. package/src/errors/write-error-info.ts +123 -0
  186. package/src/errors/zod-bridge.ts +49 -0
  187. package/src/event-store/__tests__/admin-api.integration.ts +361 -0
  188. package/src/event-store/__tests__/event-store.integration.ts +584 -0
  189. package/src/event-store/__tests__/get-stream-version-perf.integration.ts +83 -0
  190. package/src/event-store/__tests__/perf.integration.ts +255 -0
  191. package/src/event-store/__tests__/snapshot.integration.ts +267 -0
  192. package/src/event-store/__tests__/upcaster-dead-letter.integration.ts +204 -0
  193. package/src/event-store/__tests__/upcaster.integration.ts +460 -0
  194. package/src/event-store/admin-api.ts +257 -0
  195. package/src/event-store/archive.ts +106 -0
  196. package/src/event-store/errors.ts +35 -0
  197. package/src/event-store/event-store.ts +405 -0
  198. package/src/event-store/events-schema.ts +90 -0
  199. package/src/event-store/index.ts +50 -0
  200. package/src/event-store/snapshot.ts +210 -0
  201. package/src/event-store/upcaster-dead-letter.ts +119 -0
  202. package/src/event-store/upcaster.ts +147 -0
  203. package/src/files/__tests__/content-disposition.test.ts +123 -0
  204. package/src/files/__tests__/file-field-column.integration.ts +103 -0
  205. package/src/files/__tests__/file-field-pipeline.integration.ts +211 -0
  206. package/src/files/__tests__/file-handle.test.ts +122 -0
  207. package/src/files/__tests__/files.integration.ts +830 -0
  208. package/src/files/__tests__/storage-tracking.integration.ts +153 -0
  209. package/src/files/content-disposition.ts +55 -0
  210. package/src/files/file-handle.ts +63 -0
  211. package/src/files/file-ref-table.ts +22 -0
  212. package/src/files/file-routes.ts +353 -0
  213. package/src/files/in-memory-provider.ts +62 -0
  214. package/src/files/index.ts +29 -0
  215. package/src/files/local-provider.ts +35 -0
  216. package/src/files/storage-tracking.ts +60 -0
  217. package/src/files/types.ts +118 -0
  218. package/src/i18n/__tests__/i18n.test.ts +72 -0
  219. package/src/i18n/index.ts +29 -0
  220. package/src/jobs/__tests__/job-event-trigger.integration.ts +172 -0
  221. package/src/jobs/__tests__/job-multi-trigger.integration.ts +144 -0
  222. package/src/jobs/__tests__/jobs.integration.ts +566 -0
  223. package/src/jobs/index.ts +2 -0
  224. package/src/jobs/job-runner.ts +574 -0
  225. package/src/lifecycle/__tests__/create-test-lifecycle.ts +19 -0
  226. package/src/lifecycle/__tests__/lifecycle-server.integration.ts +108 -0
  227. package/src/lifecycle/__tests__/lifecycle.test.ts +212 -0
  228. package/src/lifecycle/__tests__/signal-handlers.test.ts +106 -0
  229. package/src/lifecycle/index.ts +13 -0
  230. package/src/lifecycle/lifecycle.ts +160 -0
  231. package/src/lifecycle/signal-handlers.ts +62 -0
  232. package/src/logging/__tests__/pino-trace-bridge.test.ts +50 -0
  233. package/src/logging/index.ts +3 -0
  234. package/src/logging/pino-logger.ts +64 -0
  235. package/src/logging/types.ts +7 -0
  236. package/src/migrations/__tests__/compare-snapshots.test.ts +150 -0
  237. package/src/migrations/__tests__/detect-drift.integration.ts +320 -0
  238. package/src/migrations/__tests__/detect-projections-to-rebuild.integration.ts +134 -0
  239. package/src/migrations/__tests__/rebuild-marker.test.ts +79 -0
  240. package/src/migrations/index.ts +28 -0
  241. package/src/migrations/projection-detection.ts +149 -0
  242. package/src/migrations/rebuild-marker.ts +64 -0
  243. package/src/migrations/schema-drift.ts +395 -0
  244. package/src/observability/__tests__/console-provider.test.ts +67 -0
  245. package/src/observability/__tests__/metric-validator.test.ts +87 -0
  246. package/src/observability/__tests__/noop-provider.test.ts +82 -0
  247. package/src/observability/__tests__/observability.integration.ts +559 -0
  248. package/src/observability/__tests__/prometheus-meter.test.ts +144 -0
  249. package/src/observability/__tests__/recording-meter.test.ts +101 -0
  250. package/src/observability/__tests__/recording-tracer.test.ts +110 -0
  251. package/src/observability/__tests__/sensitive-filter.test.ts +98 -0
  252. package/src/observability/console-provider.ts +130 -0
  253. package/src/observability/context.ts +26 -0
  254. package/src/observability/fallback.ts +34 -0
  255. package/src/observability/ids.ts +25 -0
  256. package/src/observability/index.ts +79 -0
  257. package/src/observability/metric-validator.ts +86 -0
  258. package/src/observability/metrics-handle.ts +56 -0
  259. package/src/observability/noop-provider.ts +146 -0
  260. package/src/observability/prometheus-meter.ts +284 -0
  261. package/src/observability/recording-meter.ts +156 -0
  262. package/src/observability/recording-tracer.ts +198 -0
  263. package/src/observability/redis-wrapper.ts +132 -0
  264. package/src/observability/sensitive-filter.ts +108 -0
  265. package/src/observability/standard-metrics.ts +213 -0
  266. package/src/observability/types/index.ts +29 -0
  267. package/src/observability/types/metric.ts +56 -0
  268. package/src/observability/types/provider.ts +32 -0
  269. package/src/observability/types/span.ts +64 -0
  270. package/src/pipeline/__tests__/archive-stream.integration.ts +220 -0
  271. package/src/pipeline/__tests__/auth-claims-resolver.test.ts +279 -0
  272. package/src/pipeline/__tests__/cascade-handler.integration.ts +419 -0
  273. package/src/pipeline/__tests__/cascade-handler.test.ts +52 -0
  274. package/src/pipeline/__tests__/causation-chain.integration.ts +206 -0
  275. package/src/pipeline/__tests__/ctx-bridge.integration.ts +234 -0
  276. package/src/pipeline/__tests__/dispatcher.test.ts +379 -0
  277. package/src/pipeline/__tests__/distributed-lock.integration.ts +67 -0
  278. package/src/pipeline/__tests__/domain-events-projections.integration.ts +323 -0
  279. package/src/pipeline/__tests__/event-dedup.integration.ts +153 -0
  280. package/src/pipeline/__tests__/event-define-event-strict.integration.ts +202 -0
  281. package/src/pipeline/__tests__/event-dispatcher-lifecycle.integration.ts +220 -0
  282. package/src/pipeline/__tests__/event-dispatcher-multi-instance.integration.ts +423 -0
  283. package/src/pipeline/__tests__/event-dispatcher-pg-listen.integration.ts +123 -0
  284. package/src/pipeline/__tests__/event-dispatcher-recovery.integration.ts +202 -0
  285. package/src/pipeline/__tests__/event-dispatcher-second-audit.integration.ts +290 -0
  286. package/src/pipeline/__tests__/event-dispatcher-strict.test.ts +65 -0
  287. package/src/pipeline/__tests__/event-dispatcher.integration.ts +287 -0
  288. package/src/pipeline/__tests__/event-retention.integration.ts +239 -0
  289. package/src/pipeline/__tests__/fetch-for-writing.integration.ts +281 -0
  290. package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +430 -0
  291. package/src/pipeline/__tests__/load-aggregate-query.integration.ts +266 -0
  292. package/src/pipeline/__tests__/msp-error-mode.integration.ts +149 -0
  293. package/src/pipeline/__tests__/msp-multi-hop.integration.ts +228 -0
  294. package/src/pipeline/__tests__/msp-rebuild.integration.ts +368 -0
  295. package/src/pipeline/__tests__/multi-stream-projection.integration.ts +341 -0
  296. package/src/pipeline/__tests__/perf-rebuild.integration.ts +147 -0
  297. package/src/pipeline/__tests__/projection-rebuild.integration.ts +551 -0
  298. package/src/pipeline/__tests__/query-projection.integration.ts +201 -0
  299. package/src/pipeline/__tests__/redis-pipeline.integration.ts +306 -0
  300. package/src/pipeline/append-event-core.ts +117 -0
  301. package/src/pipeline/auth-claims-resolver.ts +103 -0
  302. package/src/pipeline/cascade-handler.ts +113 -0
  303. package/src/pipeline/dispatcher.ts +1585 -0
  304. package/src/pipeline/distributed-lock.ts +37 -0
  305. package/src/pipeline/entity-cache.ts +113 -0
  306. package/src/pipeline/event-consumer-state.ts +108 -0
  307. package/src/pipeline/event-dedup.ts +23 -0
  308. package/src/pipeline/event-dispatcher.ts +1016 -0
  309. package/src/pipeline/event-retention.ts +154 -0
  310. package/src/pipeline/idempotency.ts +76 -0
  311. package/src/pipeline/index.ts +66 -0
  312. package/src/pipeline/lifecycle-pipeline.ts +409 -0
  313. package/src/pipeline/msp-rebuild.ts +242 -0
  314. package/src/pipeline/multi-stream-apply-context.ts +115 -0
  315. package/src/pipeline/projection-rebuild.ts +334 -0
  316. package/src/pipeline/projection-state.ts +72 -0
  317. package/src/pipeline/projections-runner.ts +56 -0
  318. package/src/pipeline/redis-keys.ts +11 -0
  319. package/src/pipeline/system-hooks.ts +190 -0
  320. package/src/random/__tests__/generate.test.ts +149 -0
  321. package/src/random/generate.ts +141 -0
  322. package/src/random/index.ts +8 -0
  323. package/src/random/words.ts +392 -0
  324. package/src/rate-limit/__tests__/dispatcher-l3.integration.ts +111 -0
  325. package/src/rate-limit/__tests__/middleware.integration.ts +189 -0
  326. package/src/rate-limit/__tests__/resolver.integration.ts +189 -0
  327. package/src/rate-limit/bucket.ts +36 -0
  328. package/src/rate-limit/index.ts +14 -0
  329. package/src/rate-limit/middleware.ts +152 -0
  330. package/src/rate-limit/resolver.ts +267 -0
  331. package/src/redis/__tests__/redis-options.test.ts +54 -0
  332. package/src/redis/index.ts +74 -0
  333. package/src/search/__tests__/meilisearch-adapter.integration.ts +236 -0
  334. package/src/search/__tests__/search-adapter.test.ts +256 -0
  335. package/src/search/in-memory-adapter.ts +123 -0
  336. package/src/search/index.ts +12 -0
  337. package/src/search/meilisearch-adapter.ts +106 -0
  338. package/src/search/types.ts +39 -0
  339. package/src/secrets/__tests__/dek-cache.test.ts +213 -0
  340. package/src/secrets/__tests__/env-master-key-provider.test.ts +119 -0
  341. package/src/secrets/__tests__/envelope.test.ts +74 -0
  342. package/src/secrets/__tests__/leak-guard.test.ts +92 -0
  343. package/src/secrets/__tests__/rotation.test.ts +149 -0
  344. package/src/secrets/dek-cache.ts +116 -0
  345. package/src/secrets/env-master-key-provider.ts +162 -0
  346. package/src/secrets/envelope.ts +55 -0
  347. package/src/secrets/index.ts +19 -0
  348. package/src/secrets/leak-guard.ts +87 -0
  349. package/src/secrets/rotation.ts +34 -0
  350. package/src/secrets/types.ts +107 -0
  351. package/src/stack/db.ts +104 -0
  352. package/src/stack/event-collector.ts +23 -0
  353. package/src/stack/index.ts +32 -0
  354. package/src/stack/redis.ts +44 -0
  355. package/src/stack/request-helper.ts +168 -0
  356. package/src/stack/table-helpers.ts +104 -0
  357. package/src/stack/test-stack.ts +357 -0
  358. package/src/stack/test-users.ts +37 -0
  359. package/src/testing/__tests__/e2e-generator.test.ts +230 -0
  360. package/src/testing/__tests__/ensure-entity-table.integration.ts +54 -0
  361. package/src/testing/access-assertions.ts +15 -0
  362. package/src/testing/assertions.ts +35 -0
  363. package/src/testing/e2e-generator.ts +465 -0
  364. package/src/testing/expect-error.ts +25 -0
  365. package/src/testing/handler-context.ts +125 -0
  366. package/src/testing/http-cookies.ts +52 -0
  367. package/src/testing/index.ts +41 -0
  368. package/src/testing/late-bound.ts +39 -0
  369. package/src/testing/mutable-master-key-provider.ts +31 -0
  370. package/src/testing/observability-recorder.ts +54 -0
  371. package/src/testing/shared-entities.ts +49 -0
  372. package/src/testing/utils.ts +1 -0
  373. package/src/testing/wait-for.ts +31 -0
  374. package/src/time/__tests__/polyfill.test.ts +73 -0
  375. package/src/time/__tests__/tz-context.test.ts +121 -0
  376. package/src/time/index.ts +21 -0
  377. package/src/time/polyfill.ts +70 -0
  378. package/src/time/tz-context.ts +107 -0
  379. package/src/ui-types/app-schema.ts +57 -0
  380. package/src/ui-types/index.ts +65 -0
  381. package/src/utils/__tests__/assert.test.ts +17 -0
  382. package/src/utils/__tests__/env-parse.test.ts +54 -0
  383. package/src/utils/assert.ts +18 -0
  384. package/src/utils/env-parse.ts +16 -0
  385. package/src/utils/ids.ts +16 -0
  386. package/src/utils/index.ts +5 -0
  387. package/src/utils/safe-json.ts +30 -0
  388. package/src/utils/serialization.ts +7 -0
@@ -0,0 +1,77 @@
1
+ import { timingSafeEqual } from "node:crypto";
2
+ import type { Context, Next } from "hono";
3
+ import { getCookie } from "hono/cookie";
4
+ import { CSRF_COOKIE_NAME, CSRF_HEADER_NAME, getAuthTransport } from "./auth-middleware";
5
+
6
+ // Methods that can mutate server state. GET/HEAD/OPTIONS are safe under
7
+ // CORS + SameSite-cookie semantics and skip the CSRF check entirely.
8
+ const STATE_CHANGING_METHODS: ReadonlySet<string> = new Set(["POST", "PUT", "PATCH", "DELETE"]);
9
+
10
+ // Constant-time byte compare. `a !== b` short-circuits at the first
11
+ // differing byte and leaks the common prefix length to anyone who can
12
+ // time requests — in principle exploitable against sufficiently small
13
+ // tokens. CSRF tokens are UUIDs so the practical risk is low, but this
14
+ // is the standard production pattern for any secret-vs-secret compare.
15
+ // Length-check first because timingSafeEqual throws on size mismatch;
16
+ // the length itself isn't a secret (the UUID format is known).
17
+ const encoder = new TextEncoder();
18
+ function tokensMatch(a: string, b: string): boolean {
19
+ if (a.length !== b.length) return false;
20
+ return timingSafeEqual(encoder.encode(a), encoder.encode(b));
21
+ }
22
+
23
+ // Double-submit CSRF guard. Runs AFTER authMiddleware — reads the
24
+ // authTransport flag set there. Only enforces on cookie-authenticated,
25
+ // state-changing requests. Bearer-auth requests skip the check because
26
+ // browsers cannot set the Authorization header on cross-origin requests
27
+ // (same-origin policy), so there is no CSRF vector to defend against.
28
+ //
29
+ // Mechanic: the framework sets two cookies on login — `kumiko_auth`
30
+ // (HttpOnly, carries the JWT) and `kumiko_csrf` (JS-readable, carries a
31
+ // token). The web client reads `kumiko_csrf` from document.cookie and
32
+ // echoes the value in an `X-CSRF-Token` header on every state-changing
33
+ // request. An attacker on bad.com cannot read the cookie (same-origin)
34
+ // and therefore cannot forge the header, so any cross-site POST from the
35
+ // attacker's page will fail the match even if the browser sent the
36
+ // cookies along (which SameSite=Lax already prevents for all methods
37
+ // other than top-level GETs — CSRF-middleware is belt-and-braces).
38
+ //
39
+ // Token rotation: issued at login + switch-tenant only, tied to the same
40
+ // lifetime as the auth-cookie. No per-request rotation — that's the
41
+ // Synchronizer Token pattern, needed only when token leakage via URL
42
+ // logs or referrers is on the threat model. We keep cookies out of URLs.
43
+ export function csrfMiddleware() {
44
+ return async (c: Context, next: Next) => {
45
+ // Not authenticated (public route) or bearer-only — no CSRF vector.
46
+ const transport = getAuthTransport(c);
47
+ if (transport !== "cookie") return next();
48
+
49
+ // Safe method — no CSRF check. SameSite=Lax blocks cross-site
50
+ // navigation-GETs from sending cookies, which is the only plausible
51
+ // CSRF-via-GET vector.
52
+ if (!STATE_CHANGING_METHODS.has(c.req.method)) return next();
53
+
54
+ const cookieToken = getCookie(c, CSRF_COOKIE_NAME);
55
+ const headerToken = c.req.header(CSRF_HEADER_NAME);
56
+
57
+ // Both must exist and match byte-for-byte. A missing cookie means the
58
+ // token was never issued (stale session or cross-origin attempt);
59
+ // a missing header means the client didn't attach it (attacker's
60
+ // cross-origin form submission can't read the cookie to forge one).
61
+ if (!cookieToken || !headerToken || !tokensMatch(cookieToken, headerToken)) {
62
+ return c.json(
63
+ {
64
+ error: {
65
+ code: "csrf_token_mismatch",
66
+ httpStatus: 403,
67
+ message: "csrf token missing or mismatch",
68
+ i18nKey: "auth.errors.csrfTokenMismatch",
69
+ },
70
+ },
71
+ 403,
72
+ );
73
+ }
74
+
75
+ return next();
76
+ };
77
+ }
@@ -0,0 +1,31 @@
1
+ export type { SetTenantCookieOptions } from "./anonymous-cookie";
2
+ export { deleteTenantCookie, setTenantCookie } from "./anonymous-cookie";
3
+ export type {
4
+ AnonymousAccessConfig,
5
+ AuthMiddlewareOptions,
6
+ AuthSessionChecker,
7
+ AuthSessionStatus,
8
+ TenantExists,
9
+ TenantResolver,
10
+ } from "./auth-middleware";
11
+ export { authMiddleware, getUser } from "./auth-middleware";
12
+ export type {
13
+ AuthRoutesConfig,
14
+ LoginRateLimiter,
15
+ SessionChecker,
16
+ SessionCreator,
17
+ SessionMetadata,
18
+ SessionRevoker,
19
+ } from "./auth-routes";
20
+ export { createAuthRoutes, createInMemoryLoginRateLimiter } from "./auth-routes";
21
+ export type { JwtHelper, JwtPayload } from "./jwt";
22
+ export { createJwtHelper } from "./jwt";
23
+ export { type RequestContextData, requestContext } from "./request-context";
24
+ export { requestIdMiddleware } from "./request-id-middleware";
25
+ export { createApiRoutes } from "./routes";
26
+ export type { KumikoServer, ServerOptions } from "./server";
27
+ export { buildServer } from "./server";
28
+ export type { SseBroker, SseClient, SseEvent } from "./sse-broker";
29
+ export { createSseBroker } from "./sse-broker";
30
+ export { createSseRoute, SSE_HEARTBEAT_INTERVAL_MS } from "./sse-route";
31
+ export { generateToken } from "./tokens";
package/src/api/jwt.ts ADDED
@@ -0,0 +1,66 @@
1
+ import * as jose from "jose";
2
+ import type { DbRow } from "../db/connection";
3
+ import type { SessionUser, TenantId } from "../engine/types";
4
+
5
+ export type JwtPayload = {
6
+ // JWT `sub` is a string per RFC 7519. Matches SessionUser.id — a UUID-string
7
+ // under the ES migration. `sign()` already stringifies via String(user.id);
8
+ // `verify()` just passes it through.
9
+ sub: string;
10
+ tenantId: TenantId;
11
+ roles: string[];
12
+ // Optional — present when a feature has registered auth claims via the
13
+ // `r.authClaims()` hook system. Absent for stateless-JWT deployments
14
+ // without auth-claims wiring.
15
+ claims?: Record<string, unknown>;
16
+ // Optional session-ID, carried in the standard `jti` JWT claim.
17
+ // Present when the app wires a `sessionCreator` callback (see sessions
18
+ // feature). Absent → stateless-JWT mode, no revocation possible.
19
+ jti?: string;
20
+ };
21
+
22
+ export type JwtHelper = {
23
+ sign(user: SessionUser): Promise<string>;
24
+ verify(token: string): Promise<JwtPayload>;
25
+ };
26
+
27
+ export function createJwtHelper(secret: string, issuer = "kumiko"): JwtHelper {
28
+ const encodedSecret = new TextEncoder().encode(secret);
29
+
30
+ return {
31
+ async sign(user) {
32
+ const body: Omit<JwtPayload, "sub" | "jti"> = {
33
+ tenantId: user.tenantId,
34
+ roles: [...user.roles],
35
+ };
36
+ if (user.claims) body.claims = { ...user.claims };
37
+
38
+ const builder = new jose.SignJWT(body)
39
+ .setProtectedHeader({ alg: "HS256" })
40
+ .setSubject(String(user.id))
41
+ .setIssuer(issuer)
42
+ .setIssuedAt()
43
+ .setExpirationTime("24h");
44
+ if (user.sid) builder.setJti(user.sid);
45
+
46
+ return builder.sign(encodedSecret);
47
+ },
48
+
49
+ async verify(token) {
50
+ const { payload } = await jose.jwtVerify(token, encodedSecret, { issuer });
51
+ const result: JwtPayload = {
52
+ sub: String(payload.sub),
53
+ tenantId: payload["tenantId"] as string,
54
+ roles: payload["roles"] as string[],
55
+ };
56
+ const claims = payload["claims"];
57
+ if (claims && typeof claims === "object") {
58
+ result.claims = claims as DbRow;
59
+ }
60
+ if (typeof payload.jti === "string") {
61
+ result.jti = payload.jti;
62
+ }
63
+ return result;
64
+ },
65
+ };
66
+ }
@@ -0,0 +1,89 @@
1
+ import type { Context, Next } from "hono";
2
+ import {
3
+ emitHttpRequest,
4
+ type Meter,
5
+ observabilityContext,
6
+ redactQueryString,
7
+ type SensitiveFilterConfig,
8
+ type Tracer,
9
+ } from "../observability";
10
+ import { getUser } from "./auth-middleware";
11
+ import { requestContext } from "./request-context";
12
+
13
+ // Wraps each incoming /api/* request in an `http.request` span. Must be
14
+ // installed AFTER requestIdMiddleware so the active request-id is available
15
+ // as a span attribute. Installed BEFORE auth so auth verification shows up
16
+ // as a child span later when auth-middleware itself is instrumented (v2).
17
+
18
+ export type ObservabilityMiddlewareOptions = {
19
+ readonly tracer: Tracer;
20
+ readonly meter: Meter;
21
+ readonly sensitiveConfig: SensitiveFilterConfig;
22
+ };
23
+
24
+ export function observabilityMiddleware(opts: ObservabilityMiddlewareOptions) {
25
+ const { tracer, meter, sensitiveConfig } = opts;
26
+
27
+ return async (c: Context, next: Next) => {
28
+ const method = c.req.method;
29
+ const path = c.req.path;
30
+ const target = redactQueryString(c.req.url.replace(/^https?:\/\/[^/]+/, ""), sensitiveConfig);
31
+
32
+ // Start the root span for this request. kind=server marks it as an
33
+ // incoming server-side span in OTel terms.
34
+ const span = tracer.startSpan("http.request", {
35
+ kind: "server",
36
+ attributes: {
37
+ "http.method": method,
38
+ "http.route": path,
39
+ "http.target": target,
40
+ },
41
+ });
42
+
43
+ const reqCtx = requestContext.get();
44
+ if (reqCtx?.requestId) {
45
+ span.setAttribute("kumiko.request_id", reqCtx.requestId);
46
+ }
47
+
48
+ const startTime = performance.now();
49
+ try {
50
+ await observabilityContext.run({ activeSpan: span }, () => next());
51
+
52
+ // Auth middleware runs inside `next()` and sets the user on the
53
+ // Hono context if the token was valid. Enrich the span after the
54
+ // fact so public paths (health, login) don't emit empty user attrs.
55
+ try {
56
+ const user = getUser(c);
57
+ if (user) {
58
+ span.setAttribute("kumiko.user_id", user.id);
59
+ span.setAttribute("kumiko.tenant_id", user.tenantId);
60
+ }
61
+ } catch {
62
+ // getUser throws if called before auth ran — public paths, fine.
63
+ }
64
+
65
+ span.setAttribute("http.status_code", c.res.status);
66
+ if (c.res.status >= 500) {
67
+ span.setStatus("error", `HTTP ${c.res.status}`);
68
+ } else {
69
+ span.setStatus("ok");
70
+ }
71
+ } catch (error) {
72
+ if (error instanceof Error) {
73
+ span.recordException(error);
74
+ span.setStatus("error", error.message);
75
+ } else {
76
+ span.setStatus("error", String(error));
77
+ }
78
+ span.setAttribute("http.status_code", 500);
79
+ throw error;
80
+ } finally {
81
+ const durationSec = (performance.now() - startTime) / 1000;
82
+ // c.res may be undefined on very early throws (before any route handler);
83
+ // fall back to 500 for the metric so the counter is always incremented.
84
+ const status = c.res?.status ?? 500;
85
+ emitHttpRequest(meter, { route: path, method, status }, durationSec);
86
+ span.end();
87
+ }
88
+ };
89
+ }
@@ -0,0 +1,132 @@
1
+ // Readiness probe: runs a set of checks in parallel with a per-check timeout
2
+ // and aggregates into a single result. Used by /health/ready when the caller
3
+ // wires DB / Redis / Dispatcher — any of those down drops the probe to 503
4
+ // so load balancers stop routing new traffic even while `lifecycle.state()`
5
+ // is still "ready".
6
+ //
7
+ // Design:
8
+ // - Every check produces a `ReadinessCheckResult` — no thrown errors leak.
9
+ // - Timeout is enforced per-check, not per-probe, so a single hung dependency
10
+ // can't starve siblings of their budget.
11
+ // - Checks run in parallel — the probe is called on every kubelet/ALB poll,
12
+ // so total latency must stay ≈ slowest check, not sum.
13
+
14
+ import { sql } from "drizzle-orm";
15
+ import type Redis from "ioredis";
16
+ import type { DbConnection } from "../db/connection";
17
+ import { getAllConsumerProgress } from "../pipeline/event-dispatcher";
18
+
19
+ export type ReadinessCheck = {
20
+ readonly name: string;
21
+ readonly run: () => Promise<void>;
22
+ };
23
+
24
+ export type ReadinessCheckResult = {
25
+ readonly name: string;
26
+ readonly ok: boolean;
27
+ readonly latencyMs: number;
28
+ readonly error?: string;
29
+ };
30
+
31
+ export type ReadinessResult = {
32
+ readonly ok: boolean;
33
+ readonly checks: readonly ReadinessCheckResult[];
34
+ };
35
+
36
+ export type ReadinessProbeOptions = {
37
+ readonly timeoutMs?: number;
38
+ };
39
+
40
+ const DEFAULT_TIMEOUT_MS = 2_000;
41
+
42
+ export function createReadinessProbe(
43
+ checks: readonly ReadinessCheck[],
44
+ opts: ReadinessProbeOptions = {},
45
+ ): () => Promise<ReadinessResult> {
46
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
47
+
48
+ return async () => {
49
+ const results = await Promise.all(checks.map((check) => runOne(check, timeoutMs)));
50
+ return {
51
+ ok: results.every((r) => r.ok),
52
+ checks: results,
53
+ };
54
+ };
55
+ }
56
+
57
+ async function runOne(check: ReadinessCheck, timeoutMs: number): Promise<ReadinessCheckResult> {
58
+ const start = performance.now();
59
+ let timer: ReturnType<typeof setTimeout> | undefined;
60
+ try {
61
+ await Promise.race([
62
+ check.run(),
63
+ new Promise<never>((_, reject) => {
64
+ timer = setTimeout(() => reject(new Error(`timeout after ${timeoutMs}ms`)), timeoutMs);
65
+ // Don't keep the event loop alive on a hung probe during shutdown.
66
+ timer.unref?.();
67
+ }),
68
+ ]);
69
+ return {
70
+ name: check.name,
71
+ ok: true,
72
+ latencyMs: Math.round(performance.now() - start),
73
+ };
74
+ } catch (err) {
75
+ const message = err instanceof Error ? err.message : String(err);
76
+ return {
77
+ name: check.name,
78
+ ok: false,
79
+ latencyMs: Math.round(performance.now() - start),
80
+ error: message,
81
+ };
82
+ } finally {
83
+ if (timer) clearTimeout(timer);
84
+ }
85
+ }
86
+
87
+ // --- Standard checks --------------------------------------------------------
88
+
89
+ export function dbPingCheck(db: DbConnection): ReadinessCheck {
90
+ return {
91
+ name: "db",
92
+ run: async () => {
93
+ await db.execute(sql`SELECT 1`);
94
+ },
95
+ };
96
+ }
97
+
98
+ export function redisPingCheck(redis: Redis): ReadinessCheck {
99
+ return {
100
+ name: "redis",
101
+ run: async () => {
102
+ const reply = await redis.ping();
103
+ if (reply !== "PONG") throw new Error(`unexpected PING reply: ${reply}`);
104
+ },
105
+ };
106
+ }
107
+
108
+ // Lag-Check für den Async-Event-Dispatcher. Liest HWM + Consumer-Cursor aus
109
+ // der DB — kein extra Redis/Runtime-State. Fail-Schwelle ist in EventIds
110
+ // (bigint), weil das die natürliche Einheit ist (events sind monoton id'd).
111
+ // Ein Tuning-Beispiel: maxLagEvents = 1_000 bedeutet "wenn die Projection
112
+ // mehr als 1k Events hinter HWM ist, stoppe neue Traffic-Zuweisung".
113
+ export function dispatcherLagCheck(
114
+ db: DbConnection,
115
+ consumerNames: readonly string[],
116
+ maxLagEvents: bigint,
117
+ ): ReadinessCheck {
118
+ return {
119
+ name: "dispatcher_lag",
120
+ run: async () => {
121
+ // skip: no registered consumers means no dispatcher active — lag check has
122
+ // nothing to measure. Not a failure mode, just a no-op.
123
+ if (consumerNames.length === 0) return;
124
+ const progress = await getAllConsumerProgress(db, consumerNames);
125
+ for (const p of progress) {
126
+ if (p.lag > maxLagEvents) {
127
+ throw new Error(`consumer "${p.name}" lag=${p.lag} exceeds threshold ${maxLagEvents}`);
128
+ }
129
+ }
130
+ },
131
+ };
132
+ }
@@ -0,0 +1,49 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import { generateId } from "../utils";
3
+
4
+ // Request-scoped propagation. Populated by the HTTP middleware and by the
5
+ // event-dispatcher when it runs an MSP-apply, so ctx.appendEvent downstream
6
+ // automatically stamps the right provenance on every event it writes.
7
+ //
8
+ // requestId — unique per HTTP request (or Job run). Log correlation.
9
+ // correlationId — the end-to-end business operation id; propagates across
10
+ // service boundaries and MSP causation chains. Comes from
11
+ // the `x-correlation-id` header if set, otherwise mirrors
12
+ // requestId (clients that don't set the header pay no
13
+ // penalty — a single HTTP call == one correlation).
14
+ // causationId — the events.id that triggered THIS execution. Null for
15
+ // root HTTP commands; set when an MSP-apply is running
16
+ // (event-dispatcher wraps the handler call). Together
17
+ // with correlationId, forms a causal DAG across streams.
18
+ // signal — AbortSignal from the underlying HTTP request. Aborts
19
+ // when the client disconnects (mobile back-press, tab
20
+ // close). Long-running framework code (event streaming,
21
+ // projection rebuild) checks signal.aborted at chunk
22
+ // boundaries; short queries don't pay the overhead.
23
+ // Undefined for non-HTTP entry-points (jobs, MSP-applies).
24
+ export type RequestContextData = {
25
+ readonly requestId: string;
26
+ readonly correlationId: string;
27
+ readonly causationId?: string;
28
+ readonly signal?: AbortSignal;
29
+ // Client IP for per-IP rate limiting (L1, L2, L3 with per: "ip*").
30
+ // Populated by requestIdMiddleware from x-forwarded-for or the
31
+ // socket address. Undefined for non-HTTP entry points (jobs, MSP).
32
+ readonly ip?: string;
33
+ };
34
+
35
+ const storage = new AsyncLocalStorage<RequestContextData>();
36
+
37
+ export const requestContext = {
38
+ run<T>(data: RequestContextData, fn: () => T): T {
39
+ return storage.run(data, fn);
40
+ },
41
+
42
+ get(): RequestContextData | undefined {
43
+ return storage.getStore();
44
+ },
45
+
46
+ generateId(): string {
47
+ return generateId();
48
+ },
49
+ };
@@ -0,0 +1,50 @@
1
+ import type { Context, Next } from "hono";
2
+ import { requestContext } from "./request-context";
3
+
4
+ const REQUEST_ID_HEADER = "X-Request-ID";
5
+ const CORRELATION_ID_HEADER = "X-Correlation-ID";
6
+
7
+ /**
8
+ * Assigns a requestId + correlationId to every request and wraps execution
9
+ * in AsyncLocalStorage. Runs BEFORE auth — both ids are available even for
10
+ * 401 responses.
11
+ *
12
+ * correlationId defaults to the requestId if the client didn't set
13
+ * `x-correlation-id` — clients that don't care about cross-service tracing
14
+ * still get sensible single-request correlation for free.
15
+ */
16
+ export function requestIdMiddleware() {
17
+ return async (c: Context, next: Next) => {
18
+ const requestId = c.req.header(REQUEST_ID_HEADER) ?? requestContext.generateId();
19
+ const correlationId = c.req.header(CORRELATION_ID_HEADER) ?? requestId;
20
+ c.header(REQUEST_ID_HEADER, requestId);
21
+ c.header(CORRELATION_ID_HEADER, correlationId);
22
+ c.set("requestId", requestId);
23
+
24
+ // Hono exposes the underlying Fetch Request — its `signal` aborts
25
+ // when the client disconnects (mobile back-press, tab close). We
26
+ // propagate it through requestContext so framework internals can
27
+ // honour cancellation at long-running checkpoints. Older Hono /
28
+ // adapter combos may not populate `c.req.raw.signal`; conditional
29
+ // spread keeps `signal: undefined` out of the stored record so
30
+ // downstream `signal?` checks behave as if no signal exists.
31
+ const signal = c.req.raw?.signal;
32
+ // Client IP for per-IP rate limiting. Trust `x-forwarded-for` when
33
+ // present (proxy/CDN) — first hop is the originating client. Adapter-
34
+ // specific socket-address fallback (bun, node) is not standardized
35
+ // in Hono; deployments behind a proxy should always set xff. Without
36
+ // either we leave `ip` undefined and skip ip-bucketed checks rather
37
+ // than fabricate one.
38
+ const xff = c.req.header("x-forwarded-for");
39
+ const ip = xff?.split(",")[0]?.trim();
40
+ await requestContext.run(
41
+ {
42
+ requestId,
43
+ correlationId,
44
+ ...(signal ? { signal } : {}),
45
+ ...(ip && ip.length > 0 ? { ip } : {}),
46
+ },
47
+ () => next(),
48
+ );
49
+ };
50
+ }