@cosmicdrift/kumiko-framework 0.386.0 → 0.388.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (468) hide show
  1. package/AGENTS.md +6 -1
  2. package/LICENSE +21 -57
  3. package/README.md +2 -2
  4. package/agents/examples/anonymous-access.md +1 -1
  5. package/agents/examples/apex-landing.md +204 -0
  6. package/agents/examples/api-worker-split.md +64 -0
  7. package/agents/examples/app-config.md +14 -0
  8. package/agents/examples/auth-mfa.md +115 -0
  9. package/agents/examples/env-composition.md +28 -1
  10. package/agents/examples/legal-pages.md +1 -1
  11. package/agents/examples/prod-boot-options.md +16 -0
  12. package/agents/examples/rate-limiting.md +48 -0
  13. package/agents/examples/record-detail-layout.md +692 -0
  14. package/agents/examples/seed-migration.md +212 -0
  15. package/agents/examples/wizard-form.md +941 -0
  16. package/agents/examples/writeform-section.md +548 -0
  17. package/agents/features.md +19 -2
  18. package/agents/widgets.md +40 -39
  19. package/dist/api/api-constants.js +2 -2
  20. package/dist/api/auth-middleware.js +7 -7
  21. package/dist/api/auth-rate-limiters.js +7 -3
  22. package/dist/api/auth-routes-mfa.js +1 -1
  23. package/dist/api/auth-routes-session.js +1 -1
  24. package/dist/api/auth-routes-shared.js +7 -3
  25. package/dist/api/auth-routes-signup.js +1 -1
  26. package/dist/api/client-error-route.js +8 -2
  27. package/dist/api/client-ip.js +3 -1
  28. package/dist/api/extra-route.js +1 -1
  29. package/dist/api/index.d.ts +3 -1
  30. package/dist/api/index.js +3 -1
  31. package/dist/api/pii-leak-guard.js +4 -2
  32. package/dist/api/readiness.js +5 -5
  33. package/dist/api/redis-sse-broker.js +8 -6
  34. package/dist/api/request-context.d.ts +1 -1
  35. package/dist/api/request-context.js +1 -1
  36. package/dist/api/routes.js +2 -2
  37. package/dist/api/server-boot-checks.d.ts +10 -0
  38. package/dist/api/server-boot-checks.js +157 -0
  39. package/dist/api/server-event-dispatcher.d.ts +26 -0
  40. package/dist/api/server-event-dispatcher.js +208 -0
  41. package/dist/api/server-extra-routes.d.ts +11 -0
  42. package/dist/api/server-extra-routes.js +203 -0
  43. package/dist/api/server-middleware.d.ts +20 -0
  44. package/dist/api/server-middleware.js +211 -0
  45. package/dist/api/server-routes.d.ts +19 -0
  46. package/dist/api/server-routes.js +293 -0
  47. package/dist/api/server-websocket-routes.d.ts +4 -0
  48. package/dist/api/server-websocket-routes.js +108 -0
  49. package/dist/api/server-wiring.d.ts +32 -0
  50. package/dist/api/server-wiring.js +236 -0
  51. package/dist/api/server.d.ts +10 -15
  52. package/dist/api/server.js +48 -1250
  53. package/dist/api/session-cookies.js +4 -4
  54. package/dist/api/sse-broker.js +1 -1
  55. package/dist/api/sse-route.d.ts +13 -13
  56. package/dist/api/sse-route.js +31 -27
  57. package/dist/auth/index.js +4 -4
  58. package/dist/auth/roles.d.ts +4 -4
  59. package/dist/auth/roles.js +24 -24
  60. package/dist/compliance/duration-spec.js +18 -20
  61. package/dist/compliance/index.js +2 -3
  62. package/dist/compliance/override-schema.d.ts +4 -4
  63. package/dist/compliance/override-schema.js +19 -22
  64. package/dist/compliance/profiles.d.ts +26 -29
  65. package/dist/compliance/profiles.js +62 -69
  66. package/dist/compliance/sub-processors.d.ts +28 -30
  67. package/dist/compliance/sub-processors.js +17 -21
  68. package/dist/consumer-cli.js +4 -4
  69. package/dist/crypto/blind-index.js +18 -17
  70. package/dist/crypto/ciphertext-pattern.js +1 -1
  71. package/dist/crypto/event-pii.js +3 -3
  72. package/dist/crypto/kek-source.d.ts +1 -1
  73. package/dist/crypto/kek-source.js +3 -2
  74. package/dist/crypto/kms-adapter.d.ts +1 -1
  75. package/dist/crypto/kms-adapter.js +4 -6
  76. package/dist/crypto/kms-wiring.d.ts +1 -2
  77. package/dist/crypto/kms-wiring.js +2 -2
  78. package/dist/crypto/owner-transition-rekey.d.ts +5 -1
  79. package/dist/crypto/owner-transition-rekey.js +1 -1
  80. package/dist/crypto/pg-kms-adapter.js +2 -6
  81. package/dist/crypto/pii-field-encryption.d.ts +5 -1
  82. package/dist/crypto/pii-field-encryption.js +7 -5
  83. package/dist/crypto/subject-resolver.d.ts +1 -1
  84. package/dist/crypto/subject-resolver.js +2 -2
  85. package/dist/db/aggregate-sql.d.ts +21 -0
  86. package/dist/db/aggregate-sql.js +155 -0
  87. package/dist/db/apply-entity-event.d.ts +6 -6
  88. package/dist/db/apply-entity-event.js +55 -55
  89. package/dist/db/blind-index-cleanup.js +7 -7
  90. package/dist/db/closed-connection.d.ts +9 -0
  91. package/dist/db/closed-connection.js +95 -0
  92. package/dist/db/collect-table-metas.js +10 -11
  93. package/dist/db/compound-types.d.ts +1 -1
  94. package/dist/db/compound-types.js +12 -12
  95. package/dist/db/cursor.js +3 -4
  96. package/dist/db/dialect.d.ts +2 -2
  97. package/dist/db/dialect.js +3 -3
  98. package/dist/db/distinct-on.d.ts +1 -1
  99. package/dist/db/driver-row-coercion.d.ts +2 -0
  100. package/dist/db/driver-row-coercion.js +186 -0
  101. package/dist/db/driver-value-preparation.d.ts +16 -0
  102. package/dist/db/driver-value-preparation.js +99 -0
  103. package/dist/db/eagerload.d.ts +3 -3
  104. package/dist/db/eagerload.js +21 -12
  105. package/dist/db/entity-field-encryption.d.ts +1 -0
  106. package/dist/db/entity-field-encryption.js +1 -1
  107. package/dist/db/entity-table-meta.d.ts +2 -2
  108. package/dist/db/entity-table-meta.js +105 -112
  109. package/dist/db/event-store-executor-context.d.ts +3 -2
  110. package/dist/db/event-store-executor-context.js +13 -13
  111. package/dist/db/event-store-executor-read-aggregate.d.ts +8 -0
  112. package/dist/db/event-store-executor-read-aggregate.js +112 -0
  113. package/dist/db/event-store-executor-read-filter.d.ts +31 -0
  114. package/dist/db/event-store-executor-read-filter.js +203 -0
  115. package/dist/db/event-store-executor-read-paging.d.ts +14 -0
  116. package/dist/db/event-store-executor-read-paging.js +99 -0
  117. package/dist/db/event-store-executor-read-rows.d.ts +6 -0
  118. package/dist/db/event-store-executor-read-rows.js +32 -0
  119. package/dist/db/event-store-executor-read-search.d.ts +28 -0
  120. package/dist/db/event-store-executor-read-search.js +221 -0
  121. package/dist/db/event-store-executor-read-sort.d.ts +41 -0
  122. package/dist/db/event-store-executor-read-sort.js +130 -0
  123. package/dist/db/event-store-executor-read-where.d.ts +24 -0
  124. package/dist/db/event-store-executor-read-where.js +81 -0
  125. package/dist/db/event-store-executor-read.d.ts +2 -9
  126. package/dist/db/event-store-executor-read.js +50 -704
  127. package/dist/db/event-store-executor-write-steps.d.ts +20 -0
  128. package/dist/db/event-store-executor-write-steps.js +100 -0
  129. package/dist/db/event-store-executor-write.js +401 -510
  130. package/dist/db/index.d.ts +6 -2
  131. package/dist/db/index.js +5 -1
  132. package/dist/db/list-aggregate-labels.d.ts +14 -0
  133. package/dist/db/list-aggregate-labels.js +26 -0
  134. package/dist/db/located-timestamp.d.ts +11 -11
  135. package/dist/db/located-timestamp.js +29 -30
  136. package/dist/db/migrate-generator.d.ts +1 -1
  137. package/dist/db/migrate-generator.js +97 -105
  138. package/dist/db/migrate-runner.js +29 -26
  139. package/dist/db/money.d.ts +6 -6
  140. package/dist/db/money.js +29 -27
  141. package/dist/db/queries/backfill-pii.js +5 -5
  142. package/dist/db/queries/ddl.js +1 -1
  143. package/dist/db/queries/es-ops.d.ts +1 -1
  144. package/dist/db/queries/es-ops.js +1 -1
  145. package/dist/db/queries/event-consumer.js +3 -3
  146. package/dist/db/queries/event-store.js +4 -2
  147. package/dist/db/queries/projection-rebuild.js +3 -3
  148. package/dist/db/queries/raw-sql.d.ts +2 -2
  149. package/dist/db/queries/raw-sql.js +2 -2
  150. package/dist/db/queries/shadow-swap.js +19 -19
  151. package/dist/db/query.d.ts +3 -16
  152. package/dist/db/query.js +8 -761
  153. package/dist/db/rebuild-marker.js +20 -21
  154. package/dist/db/reference-data.js +6 -1
  155. package/dist/db/replay-migration-sql.js +2 -2
  156. package/dist/db/schema-inspection.js +4 -5
  157. package/dist/db/table-builder.d.ts +1 -2
  158. package/dist/db/table-builder.js +137 -190
  159. package/dist/db/table-info.d.ts +10 -0
  160. package/dist/db/table-info.js +45 -0
  161. package/dist/db/temporal-value-guards.d.ts +6 -0
  162. package/dist/db/temporal-value-guards.js +15 -0
  163. package/dist/db/tenant-db-runner.d.ts +1 -0
  164. package/dist/db/tenant-db-runner.js +3 -0
  165. package/dist/db/tenant-db.d.ts +2 -2
  166. package/dist/db/tenant-db.js +48 -11
  167. package/dist/db/where-clause.d.ts +6 -0
  168. package/dist/db/where-clause.js +236 -0
  169. package/dist/derivatives/delete-stored-file.js +6 -2
  170. package/dist/derivatives/derivatives-context.js +1 -1
  171. package/dist/engine/boot-validator/access-roles.js +4 -3
  172. package/dist/engine/boot-validator/api-ext.js +14 -14
  173. package/dist/engine/boot-validator/boot-check.js +1 -1
  174. package/dist/engine/boot-validator/config-deps.js +7 -6
  175. package/dist/engine/boot-validator/detail-screens.js +4 -4
  176. package/dist/engine/boot-validator/entity-handler.d.ts +1 -0
  177. package/dist/engine/boot-validator/entity-handler.js +77 -61
  178. package/dist/engine/boot-validator/form-derivations.js +35 -1
  179. package/dist/engine/boot-validator/gdpr-storage.js +4 -4
  180. package/dist/engine/boot-validator/i18n-keys.js +6 -4
  181. package/dist/engine/boot-validator/index.d.ts +2 -2
  182. package/dist/engine/boot-validator/index.js +22 -20
  183. package/dist/engine/boot-validator/nav.js +5 -5
  184. package/dist/engine/boot-validator/ownership.js +12 -9
  185. package/dist/engine/boot-validator/parent-ref.js +1 -1
  186. package/dist/engine/boot-validator/pii-retention.js +258 -187
  187. package/dist/engine/boot-validator/projection-list-screens.js +7 -7
  188. package/dist/engine/boot-validator/query-refs.js +1 -1
  189. package/dist/engine/boot-validator/screens-actions.d.ts +1 -0
  190. package/dist/engine/boot-validator/screens-actions.js +23 -17
  191. package/dist/engine/boot-validator/screens-dashboard.js +2 -5
  192. package/dist/engine/boot-validator/screens-forms.js +12 -17
  193. package/dist/engine/boot-validator/screens-kind-config-edit.d.ts +3 -0
  194. package/dist/engine/boot-validator/screens-kind-config-edit.js +88 -0
  195. package/dist/engine/boot-validator/screens-kind-entity-edit.d.ts +3 -0
  196. package/dist/engine/boot-validator/screens-kind-entity-edit.js +55 -0
  197. package/dist/engine/boot-validator/screens-kind-entity-list.d.ts +3 -0
  198. package/dist/engine/boot-validator/screens-kind-entity-list.js +228 -0
  199. package/dist/engine/boot-validator/screens-kind-projection-detail.d.ts +3 -0
  200. package/dist/engine/boot-validator/screens-kind-projection-detail.js +235 -0
  201. package/dist/engine/boot-validator/screens-kind-projection-list.d.ts +3 -0
  202. package/dist/engine/boot-validator/screens-kind-projection-list.js +52 -0
  203. package/dist/engine/boot-validator/screens-kind-secret-mint.d.ts +3 -0
  204. package/dist/engine/boot-validator/screens-kind-secret-mint.js +28 -0
  205. package/dist/engine/boot-validator/screens-kind-shared.d.ts +37 -0
  206. package/dist/engine/boot-validator/screens-kind-shared.js +142 -0
  207. package/dist/engine/boot-validator/screens-lists.d.ts +2 -1
  208. package/dist/engine/boot-validator/screens-lists.js +26 -24
  209. package/dist/engine/boot-validator/screens.d.ts +0 -2
  210. package/dist/engine/boot-validator/screens.js +102 -869
  211. package/dist/engine/boot-validator/security-baseline.js +3 -2
  212. package/dist/engine/boot-validator/transfer-graph.js +2 -2
  213. package/dist/engine/boot-validator/workspaces.js +3 -3
  214. package/dist/engine/bridge-stub.js +3 -4
  215. package/dist/engine/build-app-schema.js +130 -170
  216. package/dist/engine/build-config-feature-schema.js +50 -50
  217. package/dist/engine/build-target.js +20 -23
  218. package/dist/engine/codemod/pipeline-codemod.js +20 -13
  219. package/dist/engine/config-helpers.js +4 -4
  220. package/dist/engine/constants.js +2 -2
  221. package/dist/engine/create-app.js +2 -2
  222. package/dist/engine/define-feature.js +21 -13
  223. package/dist/engine/define-handler.js +13 -11
  224. package/dist/engine/define-step.js +1 -1
  225. package/dist/engine/define-workflow.d.ts +5 -5
  226. package/dist/engine/define-workflow.js +6 -6
  227. package/dist/engine/entity-handlers.d.ts +13 -0
  228. package/dist/engine/entity-handlers.js +22 -34
  229. package/dist/engine/entity-list-runtime-options.d.ts +8 -0
  230. package/dist/engine/entity-list-runtime-options.js +18 -0
  231. package/dist/engine/event-def.js +2 -2
  232. package/dist/engine/extension-names.d.ts +16 -17
  233. package/dist/engine/extension-names.js +37 -38
  234. package/dist/engine/extensions/escape-hatch-usage.js +1 -1
  235. package/dist/engine/extensions/tenant-data.js +2 -2
  236. package/dist/engine/extensions/user-data.d.ts +23 -24
  237. package/dist/engine/factories.d.ts +14 -13
  238. package/dist/engine/factories.js +38 -37
  239. package/dist/engine/feature-ast/entity-field-types.js +1 -1
  240. package/dist/engine/feature-ast/extractors/handlers.js +2 -1
  241. package/dist/engine/feature-ast/extractors/hooks.js +1 -1
  242. package/dist/engine/feature-ast/extractors/round2.js +25 -6
  243. package/dist/engine/feature-ast/extractors/shared.d.ts +2 -3
  244. package/dist/engine/feature-ast/extractors/shared.js +2 -3
  245. package/dist/engine/feature-ast/parse.js +13 -13
  246. package/dist/engine/feature-ast/patch.js +4 -182
  247. package/dist/engine/feature-ast/pattern-change-schema.d.ts +3 -0
  248. package/dist/engine/feature-ast/pattern-change-schema.js +2 -1
  249. package/dist/engine/feature-ast/pattern-id-matchers.d.ts +7 -0
  250. package/dist/engine/feature-ast/pattern-id-matchers.js +210 -0
  251. package/dist/engine/feature-ast/patterns.d.ts +11 -2
  252. package/dist/engine/feature-ast/patterns.js +43 -51
  253. package/dist/engine/feature-ast/render.js +50 -89
  254. package/dist/engine/feature-config-events-jobs.js +15 -12
  255. package/dist/engine/feature-entity-handlers.js +12 -5
  256. package/dist/engine/feature-manifest.d.ts +9 -9
  257. package/dist/engine/feature-manifest.js +6 -7
  258. package/dist/engine/feature-ui-extensions.d.ts +3 -3
  259. package/dist/engine/feature-ui-extensions.js +30 -22
  260. package/dist/engine/field-access.js +5 -6
  261. package/dist/engine/form-derivations.js +18 -7
  262. package/dist/engine/handler-field-references.js +1 -1
  263. package/dist/engine/index.d.ts +1 -0
  264. package/dist/engine/index.js +4 -4
  265. package/dist/engine/object-form.d.ts +1 -0
  266. package/dist/engine/object-form.js +14 -4
  267. package/dist/engine/ownership.js +3 -3
  268. package/dist/engine/parse-ref-target.js +5 -7
  269. package/dist/engine/pattern-library/index.js +2 -2
  270. package/dist/engine/pattern-library/library.js +2 -2
  271. package/dist/engine/pattern-library/types.js +3 -3
  272. package/dist/engine/pipeline.js +2 -2
  273. package/dist/engine/project-app-schema-for-roles.d.ts +2 -0
  274. package/dist/engine/project-app-schema-for-roles.js +7 -5
  275. package/dist/engine/projection-helpers.js +9 -9
  276. package/dist/engine/qualified-name.js +2 -2
  277. package/dist/engine/registry-facade.js +1 -1
  278. package/dist/engine/registry-ingest.js +13 -13
  279. package/dist/engine/registry-state.js +17 -19
  280. package/dist/engine/registry-validate.js +12 -12
  281. package/dist/engine/run-in.js +2 -2
  282. package/dist/engine/run-pipeline.js +7 -0
  283. package/dist/engine/schema-builder.js +251 -233
  284. package/dist/engine/screen-filter-ops.js +17 -20
  285. package/dist/engine/screen-helpers.js +4 -3
  286. package/dist/engine/screen-kind.d.ts +7 -0
  287. package/dist/engine/screen-kind.js +1 -0
  288. package/dist/engine/state-machine.d.ts +12 -12
  289. package/dist/engine/state-machine.js +7 -7
  290. package/dist/engine/steps/_resolver-utils.js +4 -3
  291. package/dist/engine/steps/for-each.js +4 -4
  292. package/dist/engine/steps/unsafe-projection-delete.js +1 -1
  293. package/dist/engine/steps/unsafe-projection-upsert.js +4 -4
  294. package/dist/engine/steps/wait-for-event.js +4 -4
  295. package/dist/engine/steps/workflow-system-events.js +3 -3
  296. package/dist/engine/strip-disabled-features-from-app-schema.js +51 -14
  297. package/dist/engine/tier-resolver-extension.d.ts +29 -27
  298. package/dist/engine/tier-resolver-extension.js +26 -26
  299. package/dist/engine/transfer-adjacency.js +2 -2
  300. package/dist/engine/validate-projection-allowlist.js +9 -28
  301. package/dist/engine/walk-step-instances.d.ts +2 -0
  302. package/dist/engine/walk-step-instances.js +38 -0
  303. package/dist/engine/where-rule-lint.js +1 -1
  304. package/dist/engine/write-handler-qn-extract.js +1 -1
  305. package/dist/entrypoint/index.d.ts +4 -1
  306. package/dist/entrypoint/index.js +10 -4
  307. package/dist/env/index.d.ts +7 -6
  308. package/dist/env/index.js +10 -9
  309. package/dist/errors/docs-url.d.ts +1 -1
  310. package/dist/errors/docs-url.js +22 -2
  311. package/dist/errors/transition-details.js +7 -7
  312. package/dist/errors/write-error-info.d.ts +6 -6
  313. package/dist/errors/write-error-info.js +6 -6
  314. package/dist/es-ops/context.d.ts +9 -9
  315. package/dist/es-ops/context.js +29 -29
  316. package/dist/es-ops/index.js +7 -9
  317. package/dist/es-ops/operations-schema.js +4 -10
  318. package/dist/es-ops/runner.d.ts +11 -11
  319. package/dist/es-ops/runner.js +49 -53
  320. package/dist/es-ops/types.d.ts +55 -55
  321. package/dist/es-ops/types.js +11 -11
  322. package/dist/event-store/admin-api.js +7 -8
  323. package/dist/event-store/errors.js +1 -1
  324. package/dist/event-store/event-store.js +12 -6
  325. package/dist/event-store/rebuild-dead-letter.js +1 -1
  326. package/dist/files/bind-pending-file-refs.js +3 -3
  327. package/dist/files/create-file-ref-from-bytes.js +4 -1
  328. package/dist/files/feature.js +9 -10
  329. package/dist/files/file-handle.js +7 -9
  330. package/dist/files/file-ref-entity.js +15 -15
  331. package/dist/files/file-ref-table.js +7 -8
  332. package/dist/files/file-routes.d.ts +7 -0
  333. package/dist/files/file-routes.js +22 -13
  334. package/dist/files/in-memory-provider.js +8 -9
  335. package/dist/files/local-provider.js +18 -25
  336. package/dist/files/provider-resolver.d.ts +7 -7
  337. package/dist/files/resolve-content-type.js +43 -0
  338. package/dist/files/storage-tracking.js +2 -2
  339. package/dist/files/upload-ledger.js +7 -0
  340. package/dist/files/zip-stream.d.ts +12 -19
  341. package/dist/files/zip-stream.js +44 -63
  342. package/dist/i18n/required-surface-keys.d.ts +1 -1
  343. package/dist/i18n/required-surface-keys.js +188 -212
  344. package/dist/internal/stack-support.d.ts +2 -1
  345. package/dist/internal/stack-support.js +1 -1
  346. package/dist/jobs/job-runner-context.d.ts +32 -0
  347. package/dist/jobs/job-runner-context.js +305 -0
  348. package/dist/jobs/job-runner-execution.d.ts +7 -0
  349. package/dist/jobs/job-runner-execution.js +160 -0
  350. package/dist/jobs/job-runner-gates.d.ts +19 -0
  351. package/dist/jobs/job-runner-gates.js +198 -0
  352. package/dist/jobs/job-runner-helpers.d.ts +17 -3
  353. package/dist/jobs/job-runner-helpers.js +69 -4
  354. package/dist/jobs/job-runner-schedulers.d.ts +1 -1
  355. package/dist/jobs/job-runner-schedulers.js +3 -4
  356. package/dist/jobs/job-runner-scope.d.ts +44 -0
  357. package/dist/jobs/job-runner-scope.js +102 -0
  358. package/dist/jobs/job-runner-types.d.ts +12 -0
  359. package/dist/jobs/job-runner.d.ts +0 -1
  360. package/dist/jobs/job-runner.js +57 -669
  361. package/dist/lifecycle/lifecycle.js +3 -5
  362. package/dist/lifecycle/signal-handlers.js +5 -4
  363. package/dist/logging/utils.d.ts +2 -1
  364. package/dist/logging/utils.js +38 -10
  365. package/dist/migrations/index.js +1 -1
  366. package/dist/migrations/kumiko-drift.js +14 -13
  367. package/dist/migrations/pending-rebuilds.d.ts +21 -20
  368. package/dist/migrations/pending-rebuilds.js +27 -27
  369. package/dist/migrations/projection-table-index.d.ts +3 -3
  370. package/dist/migrations/projection-table-index.js +8 -8
  371. package/dist/observability/escape-hatch-report.d.ts +3 -0
  372. package/dist/observability/escape-hatch-report.js +12 -1
  373. package/dist/observability/metric-validator.js +1 -1
  374. package/dist/observability/metrics-handle.js +3 -3
  375. package/dist/pipeline/append-event-core.js +11 -2
  376. package/dist/pipeline/cascade-handler.d.ts +1 -1
  377. package/dist/pipeline/cascade-handler.js +165 -138
  378. package/dist/pipeline/dispatch-batch.js +2 -2
  379. package/dist/pipeline/dispatch-query.js +20 -13
  380. package/dist/pipeline/dispatch-shared.d.ts +14 -3
  381. package/dist/pipeline/dispatch-shared.js +67 -721
  382. package/dist/pipeline/dispatch-stream.js +1 -1
  383. package/dist/pipeline/dispatch-write.js +297 -190
  384. package/dist/pipeline/dispatcher-utils.d.ts +1 -1
  385. package/dist/pipeline/dispatcher-utils.js +1 -1
  386. package/dist/pipeline/event-consumer-state.js +2 -2
  387. package/dist/pipeline/event-dispatcher-admin.js +2 -1
  388. package/dist/pipeline/event-dispatcher-delivery.js +4 -6
  389. package/dist/pipeline/event-dispatcher.js +17 -28
  390. package/dist/pipeline/handler-context-auth.d.ts +9 -0
  391. package/dist/pipeline/handler-context-auth.js +54 -0
  392. package/dist/pipeline/handler-context-config.d.ts +14 -0
  393. package/dist/pipeline/handler-context-config.js +113 -0
  394. package/dist/pipeline/handler-context-db.d.ts +24 -0
  395. package/dist/pipeline/handler-context-db.js +149 -0
  396. package/dist/pipeline/handler-context-dispatch.d.ts +21 -0
  397. package/dist/pipeline/handler-context-dispatch.js +89 -0
  398. package/dist/pipeline/handler-context-events.d.ts +43 -0
  399. package/dist/pipeline/handler-context-events.js +237 -0
  400. package/dist/pipeline/handler-context-notify.d.ts +12 -0
  401. package/dist/pipeline/handler-context-notify.js +55 -0
  402. package/dist/pipeline/handler-context-observability.d.ts +7 -0
  403. package/dist/pipeline/handler-context-observability.js +21 -0
  404. package/dist/pipeline/handler-context-scope.d.ts +48 -0
  405. package/dist/pipeline/handler-context-scope.js +88 -0
  406. package/dist/pipeline/irreversible-operation-gate.js +1 -1
  407. package/dist/pipeline/lifecycle-pipeline.js +9 -9
  408. package/dist/pipeline/member-read-only-transaction.js +1 -3
  409. package/dist/pipeline/msp-rebuild.js +1 -1
  410. package/dist/pipeline/notify-scope.js +12 -1
  411. package/dist/pipeline/pre-handler-gates.js +20 -4
  412. package/dist/pipeline/pre-transaction-reservations.d.ts +1 -0
  413. package/dist/pipeline/pre-transaction-reservations.js +9 -3
  414. package/dist/pipeline/projection-rebuild.js +15 -13
  415. package/dist/pipeline/system-hooks.js +39 -41
  416. package/dist/pipeline/system-identity-switch.js +1 -3
  417. package/dist/pipeline/tenant-timezone-cache.js +1 -1
  418. package/dist/random/generate.d.ts +29 -29
  419. package/dist/random/generate.js +30 -30
  420. package/dist/random/words.js +20 -20
  421. package/dist/rate-limit/middleware.js +9 -5
  422. package/dist/redis/client.js +2 -2
  423. package/dist/redis/pubsub-signal.js +14 -17
  424. package/dist/schema-cli/apply-command.d.ts +2 -0
  425. package/dist/schema-cli/apply-command.js +97 -0
  426. package/dist/schema-cli/baseline-command.d.ts +2 -0
  427. package/dist/schema-cli/baseline-command.js +33 -0
  428. package/dist/schema-cli/generate-command.d.ts +2 -0
  429. package/dist/schema-cli/generate-command.js +90 -0
  430. package/dist/schema-cli/shared.d.ts +39 -0
  431. package/dist/schema-cli/shared.js +95 -0
  432. package/dist/schema-cli/status-command.d.ts +2 -0
  433. package/dist/schema-cli/status-command.js +73 -0
  434. package/dist/schema-cli/validate-boot-input.d.ts +5 -0
  435. package/dist/schema-cli/validate-boot-input.js +130 -0
  436. package/dist/schema-cli/validate-command.d.ts +2 -0
  437. package/dist/schema-cli/validate-command.js +130 -0
  438. package/dist/schema-cli.d.ts +2 -18
  439. package/dist/schema-cli.js +47 -510
  440. package/dist/search/index.js +4 -4
  441. package/dist/search/meilisearch-adapter.js +2 -1
  442. package/dist/search/purge-subject.js +4 -5
  443. package/dist/search/reindex-entity.js +6 -9
  444. package/dist/time/boot-tz-warning.d.ts +2 -2
  445. package/dist/time/boot-tz-warning.js +8 -8
  446. package/dist/time/iana.js +10 -10
  447. package/dist/time/index.js +9 -9
  448. package/dist/time/legacy-date.d.ts +2 -2
  449. package/dist/time/legacy-date.js +6 -8
  450. package/dist/time/polyfill.js +1 -1
  451. package/dist/time/tz-context.d.ts +1 -1
  452. package/dist/time/tz-context.js +1 -1
  453. package/dist/ui-types/app-schema.js +7 -7
  454. package/dist/ui-types/list-row-meta.js +4 -4
  455. package/dist/upgrade-cli.js +159 -130
  456. package/dist/utils/compare.js +1 -1
  457. package/package.json +5 -5
  458. package/src/changes.json +358 -0
  459. package/src/scripts/codemod/crypto-shredding-testing-move.ts +2 -2
  460. package/src/scripts/codemod/feature-layout.ts +10 -2
  461. package/src/scripts/codemod/migrate-cross-tenant.ts +1 -1
  462. package/src/scripts/codemod/migrate-open-to-all.ts +1 -1
  463. package/src/scripts/codemod/object-form.ts +41 -4
  464. package/src/scripts/codemod/package-merge.ts +58 -8
  465. package/src/scripts/codemod/pii-personal-migration.ts +192 -153
  466. package/src/scripts/codemod/testing-injection.ts +122 -42
  467. package/src/scripts/codemod/testing-move.ts +29 -7
  468. package/src/scripts/codemod/unprocessable-error-details-reason.ts +2 -2
package/AGENTS.md CHANGED
@@ -208,7 +208,12 @@ produces code that compiles and then fails in production.
208
208
  one page (default 50, `limit` at most 200). Pass
209
209
  `filter: { field, op, value }` (`eq`, `ne`, `lt`, `gt`, `lte`, `gte`, `in`)
210
210
  so the database filters, and `totalCount: true` to get `total` over all
211
- matching rows; `limit: 1, totalCount: true` is a count query.
211
+ matching rows; `limit: 1, totalCount: true` is a count query. Groups and
212
+ sums are the same: pass `aggregate: { groupBy?, measures: [{ field, kind }] }`
213
+ (`kind` is `sum`, `avg`, `count`, `min` or `max`) and the list answers with
214
+ `{ groups: [{ key, label?, count, measures }] }` over the same filters,
215
+ search and ownership, at most 200 groups (`truncated: true` beyond that).
216
+ Custom handlers call `executor.aggregate(...)` or `answerListAggregate`.
212
217
  - Never change an event schema in place once it has been emitted. Version it
213
218
  (see `agents/examples/event-sourcing.md`).
214
219
  - Never read or write another tenant's data. `ctx.db` is tenant scoped on
package/LICENSE CHANGED
@@ -1,57 +1,21 @@
1
- Business Source License 1.1
2
-
3
- Parameters
4
-
5
- Licensor: Marc Frost
6
-
7
- Licensed Work: @cosmicdrift/kumiko-framework
8
- The Licensed Work is © 2026 Marc Frost.
9
-
10
- Additional Use Grant:
11
- You may use the Licensed Work in production for any purpose, including
12
- commercially, EXCEPT for the Restricted Use.
13
-
14
- "Restricted Use" is defined as using the Licensed Work to provide a platform
15
- or service to third parties that allows them to host, deploy, or run their
16
- own applications built with the Licensed Work. This includes, but is not
17
- limited to: managed hosting services, software-as-a-service (SaaS) platforms,
18
- platform-as-a-service (PaaS), developer platforms, or any multi-tenant
19
- managed offering of the Licensed Work.
20
-
21
- This restriction does not apply to the Licensor, any entity controlled by,
22
- controlling, or under common control with the Licensor ("Affiliates"), or
23
- contractors acting on their behalf. The Licensor remains free to use the
24
- Licensed Work for any purpose, including for the operation of kumiko.rocks.
25
-
26
- Change Date: 2030-05-05
27
- Change License: Apache License, Version 2.0
28
-
29
-
30
- Terms
31
-
32
- The Licensor hereby grants you the right to copy, modify, create derivative works,
33
- redistribute, and make non-production use of the Licensed Work. The Licensor may
34
- make an Additional Use Grant, above, permitting limited production use.
35
-
36
- Effective on the Change Date, or the fourth anniversary of the first publicly
37
- available distribution of the Licensed Work under this License, whichever comes
38
- first, this License will convert to the Change License.
39
-
40
- This Business Source License governs use of the Licensed Work in all cases, except
41
- as to any use that is explicitly granted in the Additional Use Grant above or
42
- under the Change License after the Change Date.
43
-
44
- If your use of the Licensed Work does not comply with the requirements of this
45
- License, you must cease use of the Licensed Work immediately.
46
-
47
- All copies of the Licensed Work, and all derivative works thereof, must include
48
- this License.
49
-
50
- This License does not grant you any right, title, or interest in any trademark,
51
- logo, or branding of the Licensor, except as required to comply with this License.
52
-
53
- TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON AN
54
- “AS IS” BASIS. LICENSOR DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
55
- WITHOUT LIMITATION WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
56
- TITLE, AND NON-INFRINGEMENT. IN NO EVENT WILL LICENSOR BE LIABLE FOR ANY DAMAGES
57
- ARISING OUT OF OR RELATED TO THIS LICENSE OR THE USE OF THE LICENSED WORK.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Marc Frost
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @cosmicdrift/kumiko-framework
2
2
 
3
- [![License: BUSL-1.1](https://img.shields.io/badge/License-BUSL--1.1-blue.svg)](../../LICENSE)
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](../../LICENSE)
4
4
  [![TypeScript](https://img.shields.io/badge/TypeScript-strict-blue.svg)](https://www.typescriptlang.org/)
5
5
 
6
6
  Framework core for Kumiko — engine, pipeline, API, DB, event-store, and
@@ -131,4 +131,4 @@ sample under `samples/`; the roadmap lives in [docs/plans/uebersicht.md](../../d
131
131
 
132
132
  ## License
133
133
 
134
- BUSL-1.1 — see [LICENSE](../../LICENSE).
134
+ MIT, see [LICENSE](../../LICENSE).
@@ -60,7 +60,7 @@ import {
60
60
 
61
61
  const TENANT_ID = "00000000-0000-4000-8000-000000000001" as TenantId;
62
62
 
63
- // Unique per test: the tenant is fixed, so rows are told apart by value, not by reset.
63
+ // Each value is written by exactly one test; the tenant is fixed, so rows are told apart by value, not by reset.
64
64
  const PRODUCT_NAME = "Espresso Beans (anonymous read)";
65
65
  const GUEST_EMAIL = "guest-checkout@example.com";
66
66
 
@@ -41,6 +41,210 @@ bun run screenshot # → screenshots/landing.png + lightbox.png
41
41
 
42
42
  ## Source files
43
43
 
44
+ ### e2e/header-layout.spec.ts
45
+
46
+ ```ts
47
+ // Regression for issue #2038: below the 1120px .container breakpoint, the
48
+ // header's `<div class="container nav">` lost its horizontal padding because
49
+ // `.nav`'s `padding` shorthand (same specificity, declared later) zeroed out
50
+ // the inline padding `.container` set. Verified against a real narrow
51
+ // viewport, not derived from source — matches how the bug was first found.
52
+
53
+ import { expect, boundaryGuardedTest as test } from "@cosmicdrift/kumiko-testing/e2e";
54
+ import { renderLanding, SAMPLE_PLANS } from "../src/feature";
55
+
56
+ test("header keeps the page's horizontal padding below 1120px (#2038)", async ({ page }) => {
57
+ await page.setViewportSize({ width: 390, height: 844 });
58
+ await page.setContent(renderLanding({ plans: SAMPLE_PLANS }), {
59
+ waitUntil: "domcontentloaded",
60
+ url: "http://localhost/",
61
+ });
62
+
63
+ const headerContainer = page.locator("header .container.nav");
64
+ const footerContainer = page.locator("footer .container");
65
+
66
+ const [headerPadding, footerPadding] = await Promise.all([
67
+ headerContainer.evaluate((el) => {
68
+ const s = getComputedStyle(el);
69
+ return { left: s.paddingLeft, right: s.paddingRight };
70
+ }),
71
+ footerContainer.evaluate((el) => {
72
+ const s = getComputedStyle(el);
73
+ return { left: s.paddingLeft, right: s.paddingRight };
74
+ }),
75
+ ]);
76
+
77
+ // The header is indented exactly like the rest of the page — not 0.
78
+ expect(headerPadding.left).not.toBe("0px");
79
+ expect(headerPadding).toEqual(footerPadding);
80
+
81
+ // The nav's rightmost action stays inside the viewport, not clipped at the edge.
82
+ const actionBox = await page.locator("header .nav-actions a").last().boundingBox();
83
+ expect(actionBox).not.toBeNull();
84
+ expect(actionBox!.x + actionBox!.width).toBeLessThanOrEqual(390);
85
+ });
86
+ ```
87
+
88
+ ### e2e/screenshot.spec.ts
89
+
90
+ ```ts
91
+ // E2E screenshot renderer for the Apex landing example. Renders the sample page
92
+ // to HTML, paints it in a real browser and shoots a full-page PNG. The output is
93
+ // what the docs guide embeds — regenerate with `bun run screenshot`, never hand-
94
+ // captured, so it can't drift from the renderer.
95
+ //
96
+ // SCREENSHOT_DIR overrides the output dir (CI writes straight into the docs repo).
97
+
98
+ import { existsSync, mkdirSync, readFileSync, statSync } from "node:fs";
99
+ import { resolve } from "node:path";
100
+ import { expect, boundaryGuardedTest as test } from "@cosmicdrift/kumiko-testing/e2e";
101
+ import type { Page } from "@playwright/test";
102
+ import { HERO_SCREENSHOT } from "../src/constants";
103
+ import { renderLanding, SAMPLE_PLANS } from "../src/feature";
104
+
105
+ const OUT_DIR = process.env["SCREENSHOT_DIR"] ?? resolve(import.meta.dirname, "../screenshots");
106
+ const HERO_APP_PNG = resolve(OUT_DIR, "hero-app.png");
107
+ const LANDING_PNG = resolve(OUT_DIR, "landing.png");
108
+ const LIGHTBOX_PNG = resolve(OUT_DIR, "lightbox.png");
109
+
110
+ /** Minimal in-app board mock — hero frame shows product UI, not the full landing PNG. */
111
+ const HERO_APP_HTML = `<!doctype html>
112
+ <html lang="en">
113
+ <head>
114
+ <meta charset="utf-8" />
115
+ <style>
116
+ body { margin: 0; background: #f4f4f5; font-family: system-ui, sans-serif; }
117
+ #board {
118
+ width: 720px; padding: 16px; background: #fff; border-radius: 12px;
119
+ border: 1px solid #e4e4e7; box-sizing: border-box;
120
+ }
121
+ .top { display: flex; justify-content: space-between; margin-bottom: 16px; font-weight: 600; }
122
+ .cols { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
123
+ .col { background: #fafafa; border-radius: 8px; padding: 10px; min-height: 140px; }
124
+ .label { font-size: 11px; color: #71717a; margin-bottom: 8px; }
125
+ .card {
126
+ background: #fff; border: 1px solid #e4e4e7; border-radius: 6px;
127
+ padding: 8px; margin-bottom: 8px; font-size: 13px;
128
+ }
129
+ .tag {
130
+ display: inline-block; font-size: 10px; padding: 2px 6px; border-radius: 4px;
131
+ background: #ede9fe; color: #5b21b6; margin-left: 4px;
132
+ }
133
+ </style>
134
+ </head>
135
+ <body>
136
+ <div id="board">
137
+ <div class="top"><span>Tasklane — Q3 roadmap</span><span style="color:#71717a;font-weight:400">3 projects</span></div>
138
+ <div class="cols">
139
+ <div class="col">
140
+ <div class="label">Backlog</div>
141
+ <div class="card">Auth hardening<span class="tag">P1</span></div>
142
+ <div class="card">Export CSV</div>
143
+ </div>
144
+ <div class="col">
145
+ <div class="label">In progress</div>
146
+ <div class="card">Board filters</div>
147
+ <div class="card">Invite flow</div>
148
+ </div>
149
+ <div class="col">
150
+ <div class="label">Done</div>
151
+ <div class="card">Dark mode</div>
152
+ </div>
153
+ </div>
154
+ </div>
155
+ </body>
156
+ </html>`;
157
+
158
+ function heroForMount(): typeof HERO_SCREENSHOT {
159
+ const b64 = readFileSync(HERO_APP_PNG).toString("base64");
160
+ return { ...HERO_SCREENSHOT, src: `data:image/png;base64,${b64}` };
161
+ }
162
+
163
+ async function assertHeroImageLoaded(page: Page): Promise<void> {
164
+ const img = page.locator(".shot-frame img");
165
+ await expect(img).toBeVisible();
166
+ await expect
167
+ .poll(async () => img.evaluate((el) => (el as HTMLImageElement).naturalWidth))
168
+ .toBeGreaterThan(0);
169
+ }
170
+
171
+ /** setContent does not reliably fetch routed /screenshots/* — inline data: URL instead. */
172
+ async function mountLanding(page: Page): Promise<void> {
173
+ if (!existsSync(HERO_APP_PNG)) {
174
+ throw new Error("hero-app.png missing — run hero-app test first (serial suite)");
175
+ }
176
+ await page.setContent(renderLanding({ plans: SAMPLE_PLANS, heroScreenshot: heroForMount() }), {
177
+ waitUntil: "networkidle",
178
+ url: "http://localhost/",
179
+ });
180
+ await assertHeroImageLoaded(page);
181
+ }
182
+
183
+ test.describe.configure({ mode: "serial" });
184
+
185
+ test("hero-app — product screenshot asset", async ({ page }) => {
186
+ mkdirSync(OUT_DIR, { recursive: true });
187
+ await page.setContent(HERO_APP_HTML, { waitUntil: "networkidle" });
188
+ await page.locator("#board").screenshot({ path: HERO_APP_PNG });
189
+ expect(statSync(HERO_APP_PNG).size).toBeGreaterThan(5 * 1024);
190
+ });
191
+
192
+ test("apex landing — full page", async ({ page }) => {
193
+ await mountLanding(page);
194
+ await page.screenshot({ path: LANDING_PNG, fullPage: true });
195
+ expect(statSync(LANDING_PNG).size).toBeGreaterThan(5 * 1024);
196
+ });
197
+
198
+ test("apex lightbox — open (screenshot)", async ({ page }) => {
199
+ await mountLanding(page);
200
+ await page.locator(".shot-frame img").click();
201
+ await expect(page.locator("#apex-lightbox")).toBeVisible();
202
+ await expect
203
+ .poll(async () =>
204
+ page.locator("#apex-lightbox img").evaluate((el) => (el as HTMLImageElement).naturalWidth),
205
+ )
206
+ .toBeGreaterThan(0);
207
+ await page.screenshot({ path: LIGHTBOX_PNG });
208
+ expect(statSync(LIGHTBOX_PNG).size).toBeGreaterThan(5 * 1024);
209
+ });
210
+
211
+ test("apex lightbox — closes on button", async ({ page }) => {
212
+ await mountLanding(page);
213
+ await page.locator(".shot-frame img").click();
214
+ await expect(page.locator("#apex-lightbox")).toBeVisible();
215
+ await page.locator(".apex-lightbox__close").click();
216
+ await expect(page.locator("#apex-lightbox")).not.toBeVisible();
217
+ });
218
+ ```
219
+
220
+ ### playwright.config.ts
221
+
222
+ ```ts
223
+ import { defineConfig, devices } from "@playwright/test";
224
+
225
+ // No webServer: the Apex renderer is a pure function, so the runner feeds its
226
+ // HTML straight into the page via setContent — nothing to boot.
227
+ export default defineConfig({
228
+ testDir: "./e2e",
229
+ fullyParallel: false,
230
+ forbidOnly: !!process.env["CI"],
231
+ retries: 0,
232
+ workers: 1,
233
+ reporter: [["list"]],
234
+ timeout: 60_000,
235
+ use: {
236
+ viewport: { width: 1920, height: 1080 },
237
+ deviceScaleFactor: 2,
238
+ },
239
+ projects: [
240
+ {
241
+ name: "chromium",
242
+ use: { ...devices["Desktop Chrome"], viewport: { width: 1920, height: 1080 } },
243
+ },
244
+ ],
245
+ });
246
+ ```
247
+
44
248
  ### src/__tests__/landing.test.ts
45
249
 
46
250
  ```ts
@@ -85,6 +85,70 @@ before the worker picks up the job and writes the fulfillment back.
85
85
 
86
86
  ## Source files
87
87
 
88
+ ### bin/api.ts
89
+
90
+ ```ts
91
+ // API process — `runSingleInstance: false` makes this process API-only:
92
+ // HTTP + event writes + BullMQ ENQUEUE, no worker-lane consumption, no
93
+ // multiStreamProjection application. A dedicated worker MUST run next to
94
+ // it (bin/worker.ts) or the read-side stays empty.
95
+ import { frameworkCoreEnvSchema } from "@cosmicdrift/kumiko-dev-server/env-schema";
96
+ import { composeEnvSchema } from "@cosmicdrift/kumiko-framework/env";
97
+ import { runProdApp } from "@cosmicdrift/kumiko-server-runtime";
98
+ import { createApiWorkerSplitFeature } from "../src/feature";
99
+
100
+ const features = [createApiWorkerSplitFeature()];
101
+
102
+ await runProdApp({
103
+ features,
104
+ envSchema: composeEnvSchema({ core: frameworkCoreEnvSchema, features }),
105
+ runSingleInstance: false,
106
+ // worker-lane queue prefix; must match bin/worker.ts.
107
+ jobs: { queueNamePrefix: "api-worker-split" },
108
+ });
109
+ ```
110
+
111
+ ### bin/kumiko.ts
112
+
113
+ ```ts
114
+ #!/usr/bin/env bun
115
+ // Schema CLI for the sample: creates the framework infra tables
116
+ // (idempotent) and applies kumiko/migrations. Requires DATABASE_URL.
117
+ import { runStandaloneSchemaCli } from "@cosmicdrift/kumiko-dev-server/schema-apply";
118
+ import { createApiWorkerSplitFeature } from "../src/feature";
119
+
120
+ await runStandaloneSchemaCli({ features: [createApiWorkerSplitFeature()] });
121
+ ```
122
+
123
+ ### bin/worker.ts
124
+
125
+ ```ts
126
+ // Worker process — consumes the worker-lane BullMQ queue and applies the
127
+ // multiStreamProjections the API skipped. No HTTP surface.
128
+ //
129
+ // wireComponents runs after the entrypoint is up and hands over
130
+ // `dispatchSystemWrite` — a dispatcher-scoped system write. The job's
131
+ // write-bridge is wired here (fw#1717): JobContext has no write/query, so
132
+ // background components persist through this dispatcher instead.
133
+ //
134
+ // ponytail: BullMQ may pop jobs between entrypoint.start() and this hook —
135
+ // those hits throw "no fulfill-write bridge wired" and rely on BullMQ retry
136
+ // until the bridge is set. Upgrade: defer job registration until after wire,
137
+ // or hold fulfillWrite behind a deferred Promise.
138
+ import { runWorkerApp } from "@cosmicdrift/kumiko-server-runtime";
139
+ import { createApiWorkerSplitFeature, setOrderFulfillWrite } from "../src/feature";
140
+
141
+ await runWorkerApp({
142
+ features: [createApiWorkerSplitFeature()],
143
+ jobs: { queueNamePrefix: "api-worker-split" },
144
+ wireComponents: async ({ dispatchSystemWrite }) => {
145
+ setOrderFulfillWrite(({ handlerQn, payload, tenantId }) =>
146
+ dispatchSystemWrite({ handlerQn, payload, tenantId }),
147
+ );
148
+ },
149
+ });
150
+ ```
151
+
88
152
  ### src/__tests__/feature.integration.test.ts
89
153
 
90
154
  ```ts
@@ -37,6 +37,20 @@ for the files around it.
37
37
 
38
38
  ## Source files
39
39
 
40
+ ### kumiko.config.ts
41
+
42
+ ```ts
43
+ import { defineAppConfig } from "@cosmicdrift/kumiko-framework/config";
44
+ import { auditExportFeature, notesFeature } from "./src/feature";
45
+
46
+ export default defineAppConfig({
47
+ features: [notesFeature],
48
+ prodFeatures: [auditExportFeature],
49
+ client: { entries: ["src/client.tsx"] },
50
+ ports: { dev: 4510, e2e: 4511, infra: { postgres: 15510 } },
51
+ });
52
+ ```
53
+
40
54
  ### src/__tests__/app-config.test.ts
41
55
 
42
56
  ```ts
@@ -83,11 +83,16 @@ features: [..., createSessionsFeature(), mfaFeature, createAccountSecurityFeatur
83
83
  5. **MFA state changes revoke every OTHER live session** (stolen-session defense) — never the session that made the change.
84
84
  6. **Enforcement is opt-in.** Without a tenant enabling `auth-mfa:config:required`, users choose for themselves whether to enroll.
85
85
 
86
+ ## Confirmation code input
87
+
88
+ `src/web/confirmation-code-step.tsx` shows the `Input kind="code"` renderer primitive outside the MFA screen, for example an e-mail or invite confirmation code. Pass `length` (here 6), `autoFocus` and `onComplete`. The input accepts digits only, so pasting "123 456" yields `123456`, and `onComplete` fires once when the value reaches `length`. `autoComplete` defaults to `one-time-code`, so mobile keyboards offer the code from the SMS or mail. After a wrong code the step clears the value and bumps a `key`, which remounts the input and returns focus to it.
89
+
86
90
  ## Tests
87
91
 
88
92
  - **Baseline:** a user without MFA enrolled still gets a JWT straight from `/auth/login`.
89
93
  - **End-to-end:** enroll via `enable-start`/`enable-confirm`, log in (gets a challenge, no JWT), complete via `/auth/mfa/verify` (gets a JWT) — copies the `buildServer({ auth: ... })` block from the sample comment verbatim.
90
94
  - **Account security:** the composed screen boot-validates next to `auth-mfa` and `sessions` and shows a sidebar entry once `nav` is set.
95
+ - **Confirmation code step:** typing or pasting six digits calls `onConfirm` once; a rejected code shows the error, clears the field and refocuses it.
91
96
 
92
97
  ## Source files
93
98
 
@@ -388,3 +393,113 @@ export {
388
393
  } from "@cosmicdrift/kumiko-bundled-features/auth-mfa";
389
394
  export { currentTotpCode } from "@cosmicdrift/kumiko-bundled-features/auth-mfa/testing";
390
395
  ```
396
+
397
+ ### src/web/__tests__/confirmation-code-step.test.tsx
398
+
399
+ ```tsx
400
+ import { describe, expect, mock, test } from "bun:test";
401
+ import { PrimitivesProvider } from "@cosmicdrift/kumiko-renderer";
402
+ import { defaultPrimitives } from "@cosmicdrift/kumiko-renderer/web";
403
+ import { fireEvent, render, screen, waitFor } from "@testing-library/react";
404
+ import { ConfirmationCodeStep } from "../confirmation-code-step.tsx";
405
+
406
+ function renderStep(onConfirm: (code: string) => Promise<boolean>): void {
407
+ render(
408
+ <PrimitivesProvider value={defaultPrimitives}>
409
+ <ConfirmationCodeStep onConfirm={onConfirm} />
410
+ </PrimitivesProvider>,
411
+ );
412
+ }
413
+
414
+ function type(value: string): void {
415
+ fireEvent.change(screen.getByTestId("confirmation-code"), { target: { value } });
416
+ }
417
+
418
+ describe("ConfirmationCodeStep", () => {
419
+ test("focuses the input on mount", () => {
420
+ renderStep(async () => true);
421
+ expect(document.activeElement).toBe(screen.getByTestId("confirmation-code"));
422
+ });
423
+
424
+ test("does not confirm before six digits", () => {
425
+ const onConfirm = mock(async (_code: string) => true);
426
+ renderStep(onConfirm);
427
+ type("12345");
428
+ expect(onConfirm).not.toHaveBeenCalled();
429
+ });
430
+
431
+ test("pasted '123 456' confirms once with 123456", async () => {
432
+ const onConfirm = mock(async (_code: string) => true);
433
+ renderStep(onConfirm);
434
+ type("123 456");
435
+ await waitFor(() => expect(onConfirm).toHaveBeenCalledTimes(1));
436
+ expect(onConfirm).toHaveBeenCalledWith("123456");
437
+ expect(screen.queryByRole("alert")).toBeNull();
438
+ });
439
+
440
+ test("a rejected code shows the error, clears the field and refocuses", async () => {
441
+ const onConfirm = mock(async (_code: string) => false);
442
+ renderStep(onConfirm);
443
+ type("123456");
444
+ await waitFor(() => expect(screen.getByRole("alert")).toBeTruthy());
445
+ const input = screen.getByTestId("confirmation-code");
446
+ expect(input).toHaveProperty("value", "");
447
+ await waitFor(() => expect(document.activeElement).toBe(input));
448
+ expect(onConfirm).toHaveBeenCalledTimes(1);
449
+ });
450
+ });
451
+ ```
452
+
453
+ ### src/web/confirmation-code-step.tsx
454
+
455
+ ```tsx
456
+ import { usePrimitives } from "@cosmicdrift/kumiko-renderer";
457
+ import { type ReactNode, useState } from "react";
458
+
459
+ const CONFIRMATION_CODE_LENGTH = 6;
460
+
461
+ export type ConfirmationCodeStepProps = {
462
+ /** Resolves true when the server accepted the code. */
463
+ readonly onConfirm: (code: string) => Promise<boolean>;
464
+ };
465
+
466
+ export function ConfirmationCodeStep({ onConfirm }: ConfirmationCodeStepProps): ReactNode {
467
+ const { Input, Banner } = usePrimitives();
468
+ const [code, setCode] = useState("");
469
+ const [attempt, setAttempt] = useState(0);
470
+ const [pending, setPending] = useState(false);
471
+ const [rejected, setRejected] = useState(false);
472
+
473
+ const confirm = async (submittedCode: string): Promise<void> => {
474
+ if (pending) return;
475
+ setPending(true);
476
+ setRejected(false);
477
+ const accepted = await onConfirm(submittedCode).finally(() => setPending(false));
478
+ if (accepted) return;
479
+ setRejected(true);
480
+ setCode("");
481
+ // The input was disabled while pending and lost focus; remounting restores autoFocus.
482
+ setAttempt((n) => n + 1);
483
+ };
484
+
485
+ return (
486
+ <>
487
+ <Input
488
+ key={attempt}
489
+ kind="code"
490
+ length={CONFIRMATION_CODE_LENGTH}
491
+ id="confirmation-code"
492
+ name="confirmation-code"
493
+ testId="confirmation-code"
494
+ value={code}
495
+ onChange={setCode}
496
+ onComplete={(next) => void confirm(next)}
497
+ disabled={pending}
498
+ hasError={rejected}
499
+ autoFocus
500
+ />
501
+ {rejected ? <Banner variant="error">That code is not valid. Try again.</Banner> : null}
502
+ </>
503
+ );
504
+ }
505
+ ```
@@ -18,7 +18,8 @@ instead of keeping a hand-written copy of every feature's variables.
18
18
  `kmsEnvSchema` and `s3EnvSchema`. `composeEnvSchema` collects them and
19
19
  records which feature declared each variable.
20
20
  - **App-level blocks in `extend`**: `buildInfoEnvSchema`, `demoAdminEnvSchema`,
21
- `stripeEnvSchema({ prices })` and `createS3EnvSchema({ prefix })` for a
21
+ `stripeEnvSchema({ prices })`, `dodoEnvSchema({ prices })` and
22
+ `createS3EnvSchema({ prefix })` for a
22
23
  second bucket. These belong to no mounted feature, so the app adds them.
23
24
  - **Typed result**: `parseEnv(composed.schema, …)` returns the merged type,
24
25
  so `parsed.S3_BUCKET` is `string | undefined` and `parsed.APP_PUBLIC_URL` is
@@ -34,6 +35,7 @@ instead of keeping a hand-written copy of every feature's variables.
34
35
  | `smtpEnvSchema` | `channel-email` | `createChannelEmailFeature()` |
35
36
  | `s3EnvSchema`, `createS3EnvSchema({ prefix })` | `file-provider-s3` | `createFileProviderS3Feature({ fromEnv: true })` |
36
37
  | `stripeEnvSchema({ prices })` | `subscription-stripe` | app (`extend`); the feature registers the bridge keys |
38
+ | `dodoEnvSchema({ prices })` | `subscription-dodo` | app (`extend`); the feature registers the bridge keys |
37
39
  | `mfaTokenSecretsEnvSchema` | `auth-mfa` | `createAuthMfaFeature()` |
38
40
  | `buildInfoEnvSchema`, `demoAdminEnvSchema` | `foundation-shared` | app (`extend`) |
39
41
  | `prometheusMetricsEnvSchema` | `framework/observability` | app (`extend`) |
@@ -45,6 +47,26 @@ Every app that boots with `runProdApp`. Pass the composed schema as
45
47
 
46
48
  ## Source files
47
49
 
50
+ ### bin/server.ts
51
+
52
+ ```ts
53
+ // Production entrypoint. The env schema covers the same feature list that
54
+ // runProdApp mounts, so `KUMIKO_DRY_RUN_ENV=boot bun bin/server.ts` lists
55
+ // every variable of this app without connecting anywhere.
56
+ import { runProdApp } from "@cosmicdrift/kumiko-server-runtime";
57
+ import { composeAppEnv, createEnvCompositionFeatures } from "../src/env";
58
+
59
+ const features = createEnvCompositionFeatures();
60
+
61
+ await runProdApp({
62
+ features: [...features],
63
+ envSchema: composeAppEnv(features),
64
+ // The recipe declares no PII fields; crypto-shredding is mounted only for
65
+ // its env block.
66
+ allowPlaintextPii: "env-composition recipe has no PII fields",
67
+ });
68
+ ```
69
+
48
70
  ### src/__tests__/dry-run-boot.test.ts
49
71
 
50
72
  ```ts
@@ -175,6 +197,7 @@ describe("env composition", () => {
175
197
  expect(sources["S3_BUCKET"]).toBe("file-provider-s3-env");
176
198
  expect(sources["RELEASES_S3_BUCKET"]).toBe("app");
177
199
  expect(sources["STRIPE_PRICE_PRO"]).toBe("app");
200
+ expect(sources["DODO_PRODUCT_PRO"]).toBe("app");
178
201
  });
179
202
 
180
203
  test("parses into a typed env", () => {
@@ -183,6 +206,7 @@ describe("env composition", () => {
183
206
  ...minimalValidValues,
184
207
  S3_BUCKET: "uploads",
185
208
  STRIPE_PRICE_PRO: "price_pro",
209
+ DODO_PRODUCT_PRO: "pdt_pro",
186
210
  });
187
211
 
188
212
  const bucket: string | undefined = parsed.S3_BUCKET;
@@ -190,6 +214,7 @@ describe("env composition", () => {
190
214
  expect(bucket).toBe("uploads");
191
215
  expect(publicUrl).toBe("https://app.example.com");
192
216
  expect(parsed.KUMIKO_SECRETS_MASTER_KEY_CURRENT_VERSION).toBe("1");
217
+ expect(parsed.DODO_PRODUCT_PRO).toBe("pdt_pro");
193
218
  });
194
219
 
195
220
  test("reports every missing required variable with its source", () => {
@@ -245,6 +270,7 @@ import {
245
270
  demoAdminEnvSchema,
246
271
  } from "@cosmicdrift/kumiko-bundled-features/foundation-shared";
247
272
  import { createSecretsFeature } from "@cosmicdrift/kumiko-bundled-features/secrets";
273
+ import { dodoEnvSchema } from "@cosmicdrift/kumiko-bundled-features/subscription-dodo";
248
274
  import { stripeEnvSchema } from "@cosmicdrift/kumiko-bundled-features/subscription-stripe";
249
275
  import { frameworkCoreEnvSchema } from "@cosmicdrift/kumiko-dev-server/env-schema";
250
276
  import { composeEnvSchema } from "@cosmicdrift/kumiko-framework/env";
@@ -266,6 +292,7 @@ const appEnvSchema = z.object({
266
292
  ...buildInfoEnvSchema.shape,
267
293
  ...demoAdminEnvSchema.shape,
268
294
  ...stripeEnvSchema({ prices: ["PRO", "TEAM"] }).shape,
295
+ ...dodoEnvSchema({ prices: ["PRO", "TEAM"] }).shape,
269
296
  ...createS3EnvSchema({ prefix: "RELEASES_S3_" }).shape,
270
297
  APP_PUBLIC_URL: z.url().describe("Public origin of the app, used in mail links."),
271
298
  });
@@ -80,7 +80,7 @@ await runProdApp({
80
80
  // Required (1): routes run anonymous, need tenant resolution
81
81
  anonymousAccess: { defaultTenantId: SYSTEM_TENANT_ID },
82
82
  // Required (2): boot check + internal lookup use ctx.templateResolver
83
- extraContext: ({ db }) => ({
83
+ extraContext: ({ db, registry }) => ({
84
84
  templateResolver: createTemplateResolverApi(db, registry),
85
85
  }),
86
86
  });
@@ -57,6 +57,22 @@ bun test samples/recipes/prod-boot-options
57
57
 
58
58
  ## Source files
59
59
 
60
+ ### bin/server.ts
61
+
62
+ ```ts
63
+ import { composeEnvSchema } from "@cosmicdrift/kumiko-framework/env";
64
+ import { runProdApp } from "@cosmicdrift/kumiko-server-runtime";
65
+ import { appFeatures, bootOptions } from "../src/app";
66
+
67
+ const features = appFeatures();
68
+
69
+ await runProdApp({
70
+ features,
71
+ envSchema: composeEnvSchema({ features }),
72
+ ...bootOptions(process.env),
73
+ });
74
+ ```
75
+
60
76
  ### src/__tests__/boot-options.test.ts
61
77
 
62
78
  ```ts