@cosmicdrift/kumiko-framework 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (424) hide show
  1. package/README.md +9 -38
  2. package/package.json +21 -2
  3. package/src/__tests__/anonymous-access.integration.test.ts +185 -0
  4. package/src/__tests__/consumer-cli.integration.test.ts +172 -0
  5. package/src/__tests__/entity-permalink-open.integration.test.ts +94 -0
  6. package/src/__tests__/full-stack.integration.test.ts +1 -1
  7. package/src/__tests__/schema-cli-temporal-polyfill.test.ts +48 -0
  8. package/src/__tests__/schema-cli.integration.test.ts +50 -0
  9. package/src/__tests__/store-table.integration.test.ts +93 -0
  10. package/src/api/__tests__/api.test.ts +373 -0
  11. package/src/api/__tests__/auth-middleware-anonymous-access-boot.test.ts +40 -0
  12. package/src/api/__tests__/auth-routes-cookie.test.ts +17 -1
  13. package/src/api/__tests__/auth-routes-invalid-body-invite.test.ts +253 -0
  14. package/src/api/__tests__/auth-routes-mfa-preauth-confirm.test.ts +222 -0
  15. package/src/api/__tests__/auth-routes-mfa-preauth-enable-start.test.ts +249 -0
  16. package/src/api/__tests__/auth-routes-mfa-verify.test.ts +202 -0
  17. package/src/api/__tests__/auth-routes-trusted-proxy.test.ts +135 -0
  18. package/src/api/__tests__/batch.integration.test.ts +101 -11
  19. package/src/api/__tests__/csrf-constants-sync.test.ts +20 -0
  20. package/src/api/__tests__/dispatcher-live.integration.test.ts +74 -0
  21. package/src/api/__tests__/jwt.test.ts +200 -1
  22. package/src/api/__tests__/login-rate-limiter-sweep.test.ts +50 -0
  23. package/src/api/__tests__/pat-scope.test.ts +36 -0
  24. package/src/api/__tests__/pii-leak-guard.integration.test.ts +103 -0
  25. package/src/api/__tests__/redis-login-rate-limiter.integration.test.ts +138 -0
  26. package/src/api/__tests__/request-id-middleware.test.ts +51 -0
  27. package/src/api/__tests__/server-boot-guards.test.ts +71 -0
  28. package/src/api/__tests__/server-jwt-ttl.test.ts +58 -0
  29. package/src/api/__tests__/sse-broker.test.ts +57 -0
  30. package/src/api/__tests__/sse-route.test.ts +4 -0
  31. package/src/api/api-constants.ts +11 -0
  32. package/src/api/auth-middleware.ts +248 -40
  33. package/src/api/auth-routes.ts +576 -95
  34. package/src/api/index.ts +13 -4
  35. package/src/api/jwt.ts +170 -11
  36. package/src/api/pat-scope.ts +14 -0
  37. package/src/api/pii-leak-guard.ts +47 -0
  38. package/src/api/request-context.ts +3 -0
  39. package/src/api/request-id-middleware.ts +2 -0
  40. package/src/api/routes.ts +169 -2
  41. package/src/api/server.ts +112 -16
  42. package/src/api/sse-broker.ts +39 -0
  43. package/src/bun-db/__tests__/PATTERN.md +0 -1
  44. package/src/bun-db/__tests__/coerce-row-temporal.test.ts +41 -0
  45. package/src/bun-db/__tests__/select-many-retry.test.ts +79 -0
  46. package/src/bun-db/__tests__/write-brand.test.ts +21 -0
  47. package/src/bun-db/connection.ts +3 -3
  48. package/src/bun-db/index.ts +2 -0
  49. package/src/bun-db/query.ts +105 -32
  50. package/src/consumer-cli.ts +134 -0
  51. package/src/crypto/__tests__/blind-index.test.ts +130 -0
  52. package/src/crypto/__tests__/event-pii.test.ts +161 -0
  53. package/src/crypto/__tests__/kek-rotation.integration.test.ts +180 -0
  54. package/src/crypto/__tests__/kms-adapter-contract.ts +134 -0
  55. package/src/crypto/__tests__/kms-adapter.contract.test.ts +4 -0
  56. package/src/crypto/__tests__/pg-kms-adapter.integration.test.ts +117 -0
  57. package/src/crypto/__tests__/pii-field-encryption.test.ts +376 -0
  58. package/src/crypto/__tests__/request-kms-cache.test.ts +74 -0
  59. package/src/crypto/__tests__/subject-resolver.test.ts +108 -0
  60. package/src/crypto/blind-index.ts +118 -0
  61. package/src/crypto/event-pii.ts +70 -0
  62. package/src/crypto/in-memory-kms-adapter.ts +50 -0
  63. package/src/crypto/index.ts +67 -0
  64. package/src/crypto/kms-adapter.ts +2 -0
  65. package/src/crypto/pg-kms-adapter.ts +295 -0
  66. package/src/crypto/pii-field-encryption.ts +248 -0
  67. package/src/crypto/request-kms-cache.ts +38 -0
  68. package/src/crypto/subject-resolver.ts +91 -0
  69. package/src/db/__tests__/assert-no-unreachable-live-rows.integration.test.ts +191 -0
  70. package/src/db/__tests__/blind-index.integration.test.ts +248 -0
  71. package/src/db/__tests__/build-filter-where.test.ts +34 -0
  72. package/src/db/__tests__/collect-table-metas.test.ts +10 -10
  73. package/src/db/__tests__/config-seed.integration.test.ts +13 -5
  74. package/src/db/__tests__/dialect-instant.test.ts +1 -4
  75. package/src/db/__tests__/entity-field-encryption.test.ts +7 -7
  76. package/src/db/__tests__/entity-table-meta-source.test.ts +50 -0
  77. package/src/db/__tests__/event-store-executor-context.pii-roundtrip.test.ts +67 -0
  78. package/src/db/__tests__/event-store-executor-write-verbs.integration.test.ts +402 -0
  79. package/src/db/__tests__/event-store-executor.integration.test.ts +299 -29
  80. package/src/db/__tests__/feature-table-sources.test.ts +34 -0
  81. package/src/db/__tests__/implicit-projection-equivalence.integration.test.ts +118 -39
  82. package/src/db/__tests__/instant-to-driver-temporal.test.ts +21 -0
  83. package/src/db/__tests__/located-timestamp.test.ts +19 -0
  84. package/src/db/__tests__/migrate-generator.test.ts +21 -0
  85. package/src/db/__tests__/migrate-runner.test.ts +61 -0
  86. package/src/db/__tests__/number-field-fractional.integration.test.ts +58 -0
  87. package/src/db/__tests__/rebuild-marker.test.ts +13 -3
  88. package/src/db/__tests__/replay-migration-sql.test.ts +384 -0
  89. package/src/db/__tests__/schema-inspection.test.ts +7 -0
  90. package/src/db/__tests__/schema-migration.integration.test.ts +1 -1
  91. package/src/db/__tests__/table-builder-meta-lockstep.test.ts +39 -0
  92. package/src/db/__tests__/tenant-db-where-merge.test.ts +49 -3
  93. package/src/db/__tests__/tenant-db.integration.test.ts +6 -2
  94. package/src/db/api.ts +2 -2
  95. package/src/db/apply-entity-event.ts +18 -14
  96. package/src/db/blind-index-cleanup.ts +55 -0
  97. package/src/db/bun-provider.ts +2 -2
  98. package/src/db/collect-table-metas.ts +6 -7
  99. package/src/db/config-seed.ts +9 -9
  100. package/src/db/connection.ts +7 -12
  101. package/src/db/cursor.ts +1 -18
  102. package/src/db/dialect.ts +20 -26
  103. package/src/db/entity-field-encryption.ts +81 -24
  104. package/src/db/entity-table-meta-types.ts +2 -0
  105. package/src/db/entity-table-meta.ts +63 -90
  106. package/src/db/event-store-executor-context.ts +404 -0
  107. package/src/db/event-store-executor-read.ts +275 -0
  108. package/src/db/event-store-executor-write.ts +644 -0
  109. package/src/db/event-store-executor.ts +28 -1155
  110. package/src/db/feature-table-sources.ts +6 -5
  111. package/src/db/index.ts +20 -3
  112. package/src/db/located-timestamp.ts +4 -0
  113. package/src/db/migrate-generator.ts +39 -6
  114. package/src/db/migrate-runner.ts +107 -11
  115. package/src/db/pg-error.ts +9 -1
  116. package/src/db/postgres-provider.ts +2 -2
  117. package/src/db/queries/__tests__/event-store-idempotency-index.integration.test.ts +80 -0
  118. package/src/db/queries/backfill-pii.ts +277 -0
  119. package/src/db/queries/ddl.ts +45 -0
  120. package/src/db/queries/event-consumer.ts +35 -2
  121. package/src/db/queries/event-store.ts +126 -19
  122. package/src/db/queries/projection-rebuild.ts +22 -8
  123. package/src/db/queries/shadow-swap.ts +183 -0
  124. package/src/db/queries/test-stack.ts +4 -30
  125. package/src/db/query-api.ts +1 -0
  126. package/src/db/query.ts +1 -0
  127. package/src/db/rebuild-marker.ts +18 -2
  128. package/src/db/reference-data.ts +2 -3
  129. package/src/db/replay-migration-sql.ts +257 -0
  130. package/src/db/schema-inspection.ts +1 -1
  131. package/src/db/table-builder.ts +102 -71
  132. package/src/db/tenant-db.ts +15 -53
  133. package/src/engine/__tests__/boot-validator-action-wiring.test.ts +242 -0
  134. package/src/engine/__tests__/boot-validator-boot-check.test.ts +99 -0
  135. package/src/engine/__tests__/boot-validator-dashboard.test.ts +237 -0
  136. package/src/engine/__tests__/boot-validator-entity-list.test.ts +104 -0
  137. package/src/engine/__tests__/boot-validator-gdpr-storage.test.ts +7 -69
  138. package/src/engine/__tests__/boot-validator-i18n-keys.test.ts +26 -5
  139. package/src/engine/__tests__/boot-validator-located-timestamps.test.ts +3 -19
  140. package/src/engine/__tests__/boot-validator-pii-retention.test.ts +246 -4
  141. package/src/engine/__tests__/boot-validator.test.ts +219 -15
  142. package/src/engine/__tests__/build-app-schema.test.ts +82 -0
  143. package/src/engine/__tests__/build-config-feature-schema.test.ts +125 -0
  144. package/src/engine/__tests__/build-target.test.ts +3 -11
  145. package/src/engine/__tests__/codemod-pipeline.test.ts +152 -21
  146. package/src/engine/__tests__/config-helpers.test.ts +16 -0
  147. package/src/engine/__tests__/define-feature-entity-mapping.test.ts +6 -0
  148. package/src/engine/__tests__/define-roles.test.ts +21 -0
  149. package/src/engine/__tests__/engine.test.ts +163 -1
  150. package/src/engine/__tests__/entity-handlers.test.ts +80 -0
  151. package/src/engine/__tests__/event-migration-declarative.test.ts +65 -0
  152. package/src/engine/__tests__/event-type-map-augmentation.test.ts +24 -0
  153. package/src/engine/__tests__/extend-entity-projection.test.ts +123 -0
  154. package/src/engine/__tests__/factories-time.test.ts +2 -66
  155. package/src/engine/__tests__/feature-crud-shorthand.test.ts +28 -0
  156. package/src/engine/__tests__/feature-manifest.test.ts +31 -1
  157. package/src/engine/__tests__/field-access.test.ts +23 -1
  158. package/src/engine/__tests__/hook-phases.test.ts +5 -5
  159. package/src/engine/__tests__/membership-roles.test.ts +4 -10
  160. package/src/engine/__tests__/nav.test.ts +86 -1
  161. package/src/engine/__tests__/pipeline-engine.test.ts +9 -9
  162. package/src/engine/__tests__/pipeline-handler.integration.test.ts +18 -18
  163. package/src/engine/__tests__/pipeline-observability.integration.test.ts +2 -2
  164. package/src/engine/__tests__/pipeline-performance.integration.test.ts +3 -3
  165. package/src/engine/__tests__/pipeline-sub-pipelines.test.ts +9 -9
  166. package/src/engine/__tests__/post-query-hook.test.ts +7 -7
  167. package/src/engine/__tests__/registrar-object-form.test.ts +141 -0
  168. package/src/engine/__tests__/registry.test.ts +144 -0
  169. package/src/engine/__tests__/schema-builder.test.ts +18 -0
  170. package/src/engine/__tests__/screen.test.ts +147 -1
  171. package/src/engine/__tests__/soft-delete-cleanup.test.ts +3 -0
  172. package/src/engine/__tests__/store-table.test.ts +229 -0
  173. package/src/engine/__tests__/tier-resolver-extension.test.ts +19 -1
  174. package/src/engine/__tests__/{visual-tree-patterns.test.ts → tree-actions-patterns.test.ts} +7 -95
  175. package/src/engine/__tests__/validate-projection-allowlist.test.ts +15 -15
  176. package/src/engine/boot-validator/__tests__/config-deps.test.ts +92 -0
  177. package/src/engine/boot-validator/action-wiring.ts +149 -0
  178. package/src/engine/boot-validator/boot-check.ts +21 -0
  179. package/src/engine/boot-validator/config-deps.ts +40 -4
  180. package/src/engine/boot-validator/entity-handler.ts +20 -21
  181. package/src/engine/boot-validator/entity-list-screens.ts +88 -0
  182. package/src/engine/boot-validator/gdpr-storage.ts +0 -20
  183. package/src/engine/boot-validator/i18n-keys.ts +58 -4
  184. package/src/engine/boot-validator/index.ts +33 -12
  185. package/src/engine/boot-validator/nav.ts +125 -0
  186. package/src/engine/boot-validator/pii-retention.ts +95 -10
  187. package/src/engine/boot-validator/{screens-nav.ts → screens.ts} +214 -191
  188. package/src/engine/boot-validator/workspaces.ts +68 -0
  189. package/src/engine/build-app-schema.ts +16 -0
  190. package/src/engine/build-config-feature-schema.ts +44 -7
  191. package/src/engine/codemod/pipeline-codemod.ts +5 -5
  192. package/src/engine/config-helpers.ts +15 -0
  193. package/src/engine/constants.ts +32 -6
  194. package/src/engine/create-app.ts +11 -0
  195. package/src/engine/define-feature.ts +95 -947
  196. package/src/engine/define-handler.ts +28 -94
  197. package/src/engine/define-workflow.ts +1 -1
  198. package/src/engine/effective-features.ts +12 -2
  199. package/src/engine/entity-handlers.ts +76 -11
  200. package/src/engine/extensions/tenant-data.ts +19 -0
  201. package/src/engine/extensions/user-data.ts +29 -2
  202. package/src/engine/factories.ts +8 -49
  203. package/src/engine/feature-ast/__tests__/canonical-form.test.ts +26 -29
  204. package/src/engine/feature-ast/__tests__/fixtures/cross-file-registrar/feature.ts +9 -0
  205. package/src/engine/feature-ast/__tests__/fixtures/cross-file-registrar/screens.ts +4 -0
  206. package/src/engine/feature-ast/__tests__/parse-happy-path.test.ts +1 -2
  207. package/src/engine/feature-ast/__tests__/parse-real-features.test.ts +18 -8
  208. package/src/engine/feature-ast/__tests__/parse.test.ts +1640 -160
  209. package/src/engine/feature-ast/__tests__/patch.test.ts +206 -12
  210. package/src/engine/feature-ast/__tests__/patcher.test.ts +20 -23
  211. package/src/engine/feature-ast/__tests__/render-roundtrip.test.ts +380 -5
  212. package/src/engine/feature-ast/extractors/events.ts +322 -0
  213. package/src/engine/feature-ast/extractors/handlers.ts +234 -0
  214. package/src/engine/feature-ast/extractors/hooks.ts +243 -0
  215. package/src/engine/feature-ast/extractors/index.ts +34 -31
  216. package/src/engine/feature-ast/extractors/jobs-routes.ts +221 -0
  217. package/src/engine/feature-ast/extractors/projections-screens.ts +269 -0
  218. package/src/engine/feature-ast/extractors/round1.ts +3 -3
  219. package/src/engine/feature-ast/extractors/round5.ts +3 -3
  220. package/src/engine/feature-ast/extractors/round6.ts +2 -36
  221. package/src/engine/feature-ast/extractors/shared.ts +64 -6
  222. package/src/engine/feature-ast/index.ts +2 -4
  223. package/src/engine/feature-ast/parse.ts +130 -23
  224. package/src/engine/feature-ast/patch.ts +39 -50
  225. package/src/engine/feature-ast/patcher.ts +37 -38
  226. package/src/engine/feature-ast/patterns.ts +50 -62
  227. package/src/engine/feature-ast/render.ts +67 -44
  228. package/src/engine/feature-builder-state.ts +168 -0
  229. package/src/engine/feature-config-events-jobs.ts +403 -0
  230. package/src/engine/feature-entity-handlers.ts +208 -0
  231. package/src/engine/feature-manifest.ts +2 -1
  232. package/src/engine/feature-ui-extensions.ts +500 -0
  233. package/src/engine/field-access.ts +13 -2
  234. package/src/engine/field-helpers.ts +31 -0
  235. package/src/engine/handler-helpers.ts +26 -0
  236. package/src/engine/hook-helpers.ts +16 -0
  237. package/src/engine/index.ts +23 -7
  238. package/src/engine/membership-roles.ts +13 -0
  239. package/src/engine/object-form.ts +27 -0
  240. package/src/engine/ownership.ts +26 -79
  241. package/src/engine/pattern-library/__tests__/library.test.ts +7 -20
  242. package/src/engine/pattern-library/library.ts +44 -1152
  243. package/src/engine/pattern-library/mixed-schemas.ts +450 -0
  244. package/src/engine/pattern-library/opaque-schemas.ts +124 -0
  245. package/src/engine/pattern-library/shared-fields.ts +75 -0
  246. package/src/engine/pattern-library/static-schemas.ts +456 -0
  247. package/src/engine/pipeline.ts +6 -11
  248. package/src/engine/registry-facade.ts +362 -0
  249. package/src/engine/registry-ingest.ts +478 -0
  250. package/src/engine/registry-state.ts +388 -0
  251. package/src/engine/registry-validate.ts +642 -0
  252. package/src/engine/registry.ts +78 -1658
  253. package/src/engine/run-pipeline.ts +1 -1
  254. package/src/engine/schema-builder.ts +1 -0
  255. package/src/engine/screen-helpers.ts +54 -0
  256. package/src/engine/soft-delete-cleanup.ts +6 -2
  257. package/src/engine/steps/__tests__/duration-utils.test.ts +20 -0
  258. package/src/engine/steps/_duration-utils.ts +2 -0
  259. package/src/engine/steps/unsafe-projection-upsert.ts +1 -4
  260. package/src/engine/tier-resolver-extension.ts +3 -2
  261. package/src/engine/types/config.ts +2 -482
  262. package/src/engine/types/define-handler.ts +2 -0
  263. package/src/engine/types/entity-handlers.ts +2 -0
  264. package/src/engine/types/event-type-map.ts +1 -37
  265. package/src/engine/types/feature.ts +2 -972
  266. package/src/engine/types/fields.ts +2 -675
  267. package/src/engine/types/handlers.ts +2 -774
  268. package/src/engine/types/hooks.ts +2 -184
  269. package/src/engine/types/http-route.ts +1 -54
  270. package/src/engine/types/identifiers.ts +1 -47
  271. package/src/engine/types/index.ts +86 -40
  272. package/src/engine/types/nav.ts +2 -63
  273. package/src/engine/types/ownership.ts +2 -0
  274. package/src/engine/types/projection.ts +2 -138
  275. package/src/engine/types/relations.ts +1 -51
  276. package/src/engine/types/screen.ts +2 -574
  277. package/src/engine/types/step.ts +2 -334
  278. package/src/engine/types/target-ref.ts +1 -21
  279. package/src/engine/types/tree-node.ts +1 -132
  280. package/src/engine/types/workspace.ts +2 -49
  281. package/src/engine/validate-projection-allowlist.ts +6 -6
  282. package/src/entrypoint/__tests__/entrypoint-job-wiring.integration.test.ts +120 -1
  283. package/src/entrypoint/index.ts +49 -6
  284. package/src/errors/classes.ts +22 -1
  285. package/src/errors/field-issue.ts +0 -3
  286. package/src/errors/index.ts +1 -1
  287. package/src/errors/write-error-info.ts +10 -22
  288. package/src/es-ops/README.md +1 -1
  289. package/src/es-ops/__tests__/runner.integration.test.ts +74 -0
  290. package/src/es-ops/context.ts +4 -2
  291. package/src/es-ops/types.ts +8 -1
  292. package/src/event-store/__tests__/admin-api.integration.test.ts +27 -1
  293. package/src/event-store/__tests__/backfill-pii.integration.test.ts +279 -0
  294. package/src/event-store/__tests__/event-store.integration.test.ts +201 -0
  295. package/src/event-store/__tests__/row-to-stored-event.test.ts +2 -2
  296. package/src/event-store/__tests__/snapshot.integration.test.ts +86 -0
  297. package/src/event-store/__tests__/unscoped-stream-primitives.guard.test.ts +58 -0
  298. package/src/event-store/__tests__/upcaster.integration.test.ts +77 -45
  299. package/src/event-store/admin-api.ts +11 -4
  300. package/src/event-store/errors.ts +2 -35
  301. package/src/event-store/event-store.ts +64 -78
  302. package/src/event-store/events-schema.ts +11 -13
  303. package/src/event-store/index.ts +18 -3
  304. package/src/event-store/rebuild-dead-letter.ts +111 -0
  305. package/src/event-store/snapshot.ts +63 -40
  306. package/src/event-store/types.ts +2 -0
  307. package/src/files/__tests__/build-storage-key.test.ts +28 -0
  308. package/src/files/__tests__/file-ref-entity.test.ts +8 -0
  309. package/src/files/__tests__/files.integration.test.ts +24 -0
  310. package/src/files/__tests__/in-memory-provider.contract.test.ts +4 -0
  311. package/src/files/__tests__/local-provider.test.ts +31 -0
  312. package/src/files/__tests__/provider-resolver.test.ts +70 -0
  313. package/src/files/__tests__/write-stream.test.ts +13 -0
  314. package/src/files/file-handle.ts +2 -19
  315. package/src/files/file-ref-entity.ts +2 -2
  316. package/src/files/file-routes.ts +13 -0
  317. package/src/files/index.ts +1 -1
  318. package/src/files/local-provider.ts +27 -8
  319. package/src/files/provider-resolver.ts +31 -20
  320. package/src/files/types.ts +13 -55
  321. package/src/i18n/__tests__/required-surface-keys.test.ts +17 -0
  322. package/src/i18n/required-surface-keys.ts +120 -1
  323. package/src/jobs/__tests__/job-queue-depth.integration.test.ts +82 -0
  324. package/src/jobs/__tests__/jobs.integration.test.ts +329 -3
  325. package/src/jobs/job-runner.ts +82 -12
  326. package/src/logging/types.ts +1 -7
  327. package/src/migrations/pending-rebuilds.ts +1 -1
  328. package/src/observability/__tests__/observability.integration.test.ts +4 -1
  329. package/src/observability/__tests__/recording-tracer.test.ts +6 -4
  330. package/src/observability/index.ts +2 -0
  331. package/src/observability/noop-provider.ts +0 -9
  332. package/src/observability/recording-tracer.ts +0 -12
  333. package/src/observability/standard-metrics.ts +64 -2
  334. package/src/observability/types/index.ts +1 -29
  335. package/src/observability/types/metric.ts +1 -56
  336. package/src/observability/types/provider.ts +1 -32
  337. package/src/observability/types/span.ts +1 -64
  338. package/src/pipeline/__tests__/ctx-bridge.integration.test.ts +2 -2
  339. package/src/pipeline/__tests__/dispatcher.test.ts +366 -1
  340. package/src/pipeline/__tests__/event-consumer-state.integration.test.ts +31 -0
  341. package/src/pipeline/__tests__/event-dispatcher-delivery-max-attempts.test.ts +126 -0
  342. package/src/pipeline/__tests__/event-dispatcher-rearm.integration.test.ts +263 -0
  343. package/src/pipeline/__tests__/event-dispatcher.integration.test.ts +10 -0
  344. package/src/pipeline/__tests__/job-trigger-consumer.integration.test.ts +106 -0
  345. package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +308 -77
  346. package/src/pipeline/__tests__/load-aggregate-query.integration.test.ts +16 -8
  347. package/src/pipeline/__tests__/post-query-hook.integration.test.ts +3 -3
  348. package/src/pipeline/__tests__/projection-rebuild.integration.test.ts +80 -2
  349. package/src/pipeline/__tests__/rebuild-poison-quarantine.integration.test.ts +274 -0
  350. package/src/pipeline/__tests__/try-append-event.integration.test.ts +166 -0
  351. package/src/pipeline/dispatch-batch.ts +187 -0
  352. package/src/pipeline/dispatch-query.ts +165 -0
  353. package/src/pipeline/dispatch-shared.ts +826 -0
  354. package/src/pipeline/dispatch-stream.ts +90 -0
  355. package/src/pipeline/dispatch-write.ts +451 -0
  356. package/src/pipeline/dispatcher-utils.ts +1 -1
  357. package/src/pipeline/dispatcher.ts +44 -1372
  358. package/src/pipeline/entity-cache.ts +2 -33
  359. package/src/pipeline/event-consumer-state.ts +30 -2
  360. package/src/pipeline/event-dispatcher-admin.ts +293 -0
  361. package/src/pipeline/event-dispatcher-delivery.ts +305 -0
  362. package/src/pipeline/event-dispatcher.ts +85 -542
  363. package/src/pipeline/index.ts +2 -0
  364. package/src/pipeline/msp-rebuild.ts +42 -3
  365. package/src/pipeline/multi-stream-apply-context.ts +4 -42
  366. package/src/pipeline/projection-rebuild.ts +105 -3
  367. package/src/pipeline/system-hooks.ts +144 -1
  368. package/src/rate-limit/__tests__/resolver.integration.test.ts +18 -0
  369. package/src/rate-limit/resolver.ts +16 -32
  370. package/src/schema-cli.ts +76 -16
  371. package/src/search/__tests__/meilisearch-adapter.integration.test.ts +207 -185
  372. package/src/search/__tests__/meilisearch-ids.test.ts +30 -0
  373. package/src/search/__tests__/reindex-entity.integration.test.ts +144 -0
  374. package/src/search/index.ts +6 -0
  375. package/src/search/meilisearch-adapter.ts +13 -12
  376. package/src/search/reindex-entity.ts +177 -0
  377. package/src/search/types.ts +1 -39
  378. package/src/secrets/__tests__/contains-secret.test.ts +34 -0
  379. package/src/secrets/__tests__/envelope-cipher.test.ts +59 -0
  380. package/src/secrets/__tests__/envelope.test.ts +1 -1
  381. package/src/secrets/dek-cache.ts +26 -5
  382. package/src/secrets/envelope-cipher.ts +53 -0
  383. package/src/secrets/envelope.ts +5 -3
  384. package/src/secrets/index.ts +13 -1
  385. package/src/secrets/stored-envelope.ts +46 -0
  386. package/src/secrets/types.ts +2 -162
  387. package/src/stack/__tests__/event-collector.test.ts +42 -0
  388. package/src/stack/__tests__/setup-test-stack-jobs.integration.test.ts +125 -0
  389. package/src/stack/db.ts +2 -1
  390. package/src/stack/push-entity-projection-tables.ts +4 -3
  391. package/src/stack/redis.ts +8 -0
  392. package/src/stack/request-helper.ts +38 -2
  393. package/src/stack/table-helpers.ts +6 -4
  394. package/src/stack/test-stack.ts +249 -131
  395. package/src/testing/__tests__/late-bound.test.ts +32 -0
  396. package/src/testing/__tests__/wait-for.test.ts +59 -0
  397. package/src/testing/boot-validator-fixture.ts +121 -0
  398. package/src/testing/e2e-generator.ts +8 -0
  399. package/src/testing/file-provider-contract.ts +104 -0
  400. package/src/testing/handler-context.ts +3 -1
  401. package/src/testing/index.ts +5 -0
  402. package/src/testing/late-bound.ts +5 -3
  403. package/src/testing/mutable-master-key-provider.ts +29 -3
  404. package/src/testing/wait-for.ts +3 -0
  405. package/src/testing/without-ambient-temporal.ts +14 -0
  406. package/src/time/__tests__/tz-dateline.test.ts +8 -10
  407. package/src/time/geo-tz.ts +1 -32
  408. package/src/time/index.ts +1 -0
  409. package/src/time/legacy-date.ts +18 -0
  410. package/src/time/polyfill.ts +21 -38
  411. package/src/time/tz-context.ts +44 -85
  412. package/src/ui-types/app-schema.ts +12 -0
  413. package/src/ui-types/index.ts +20 -7
  414. package/src/utils/__tests__/safe-json-temporal.test.ts +18 -0
  415. package/src/utils/safe-json.ts +13 -1
  416. package/src/__tests__/raw-table.integration.test.ts +0 -116
  417. package/src/bun-db/__tests__/bun-test-stack.ts +0 -6
  418. package/src/db/__tests__/encryption.test.ts +0 -39
  419. package/src/db/encryption.ts +0 -39
  420. package/src/db/row-helpers.ts +0 -4
  421. package/src/engine/__tests__/raw-table.test.ts +0 -150
  422. package/src/engine/__tests__/unmanaged-table.test.ts +0 -127
  423. package/src/engine/feature-ast/__tests__/visual-tree-parse.test.ts +0 -184
  424. package/src/engine/feature-ast/extractors/round4.ts +0 -1366
@@ -117,6 +117,55 @@ describe("validateBoot — PII annotations", () => {
117
117
  expect(() => validateBoot([feature])).toThrow(/multiple subject-key annotations/);
118
118
  });
119
119
 
120
+ test("naked sensitive (no pii/encrypted) throws — event log needs ciphertext-at-rest (#967)", () => {
121
+ const feature = defineFeature("test", (r) => {
122
+ r.entity(
123
+ "vault",
124
+ createEntity({
125
+ fields: {
126
+ apiToken: createTextField({ sensitive: true }),
127
+ },
128
+ }),
129
+ );
130
+ });
131
+ expect(() => validateBoot([feature])).toThrow(/sensitive: true.*without ciphertext-at-rest/);
132
+ });
133
+
134
+ test("sensitive with pii subject annotation passes", () => {
135
+ const feature = defineFeature("test", (r) => {
136
+ r.entity(
137
+ "vault",
138
+ createEntity({
139
+ fields: {
140
+ apiToken: createTextField({ sensitive: true, pii: true }),
141
+ },
142
+ }),
143
+ );
144
+ });
145
+ expect(() => validateBoot([feature])).not.toThrow();
146
+ });
147
+
148
+ test("sensitive with encrypted passes", () => {
149
+ process.env["KUMIKO_SECRETS_MASTER_KEY_V1"] = Buffer.from(
150
+ "0123456789abcdef0123456789abcdef",
151
+ ).toString("base64");
152
+ try {
153
+ const feature = defineFeature("test", (r) => {
154
+ r.entity(
155
+ "vault",
156
+ createEntity({
157
+ fields: {
158
+ apiToken: createTextField({ sensitive: true, encrypted: true }),
159
+ },
160
+ }),
161
+ );
162
+ });
163
+ expect(() => validateBoot([feature])).not.toThrow();
164
+ } finally {
165
+ delete process.env["KUMIKO_SECRETS_MASTER_KEY_V1"];
166
+ }
167
+ });
168
+
120
169
  test("userOwned.ownerField pointing to non-existent field throws", () => {
121
170
  const feature = defineFeature("test", (r) => {
122
171
  r.entity(
@@ -133,19 +182,36 @@ describe("validateBoot — PII annotations", () => {
133
182
  );
134
183
  });
135
184
 
136
- test("userOwned.ownerField pointing to non-reference field throws", () => {
185
+ test("userOwned.ownerField on a text field passes (ES userId-by-convention)", () => {
137
186
  const feature = defineFeature("test", (r) => {
138
187
  r.entity(
139
188
  "comment",
140
189
  createEntity({
141
190
  fields: {
142
- body: createLongTextField({ userOwned: { ownerField: "authorName" } }),
143
- authorName: createTextField(),
191
+ body: createLongTextField({ userOwned: { ownerField: "authorId" } }),
192
+ authorId: createTextField(),
144
193
  },
145
194
  }),
146
195
  );
147
196
  });
148
- expect(() => validateBoot([feature])).toThrow(/must be a reference field, got type "text"/);
197
+ expect(() => validateBoot([feature])).not.toThrow();
198
+ });
199
+
200
+ test("userOwned.ownerField pointing to a non-id-capable field throws", () => {
201
+ const feature = defineFeature("test", (r) => {
202
+ r.entity(
203
+ "comment",
204
+ createEntity({
205
+ fields: {
206
+ body: createLongTextField({ userOwned: { ownerField: "isPublic" } }),
207
+ isPublic: { type: "boolean" },
208
+ },
209
+ }),
210
+ );
211
+ });
212
+ expect(() => validateBoot([feature])).toThrow(
213
+ /must be a reference or text \(userId\) field, got type "boolean"/,
214
+ );
149
215
  });
150
216
 
151
217
  test("userOwned.ownerField referencing non-user entity warns", () => {
@@ -568,3 +634,179 @@ describe("validateBoot — retention", () => {
568
634
  expect(matchingWarn).toBeUndefined();
569
635
  });
570
636
  });
637
+
638
+ describe("validateBoot — lookupable / blind-index (#818)", () => {
639
+ test("lookupable on subject-annotated text field passes", () => {
640
+ const feature = defineFeature("test", (r) => {
641
+ r.entity(
642
+ "user",
643
+ createEntity({
644
+ fields: {
645
+ email: createTextField({ pii: true, lookupable: true }),
646
+ },
647
+ }),
648
+ );
649
+ });
650
+ expect(() => validateBoot([feature])).not.toThrow();
651
+ });
652
+
653
+ test("lookupable without subject annotation throws", () => {
654
+ const feature = defineFeature("test", (r) => {
655
+ r.entity(
656
+ "doc",
657
+ createEntity({
658
+ fields: {
659
+ slug: createTextField({ lookupable: true }),
660
+ },
661
+ }),
662
+ );
663
+ });
664
+ expect(() => validateBoot([feature])).toThrow(/lookupable.*without a subject annotation/);
665
+ });
666
+
667
+ test("lookupable on non-text field throws", () => {
668
+ const feature = defineFeature("test", (r) => {
669
+ r.entity(
670
+ "person",
671
+ createEntity({
672
+ fields: {
673
+ // longText carries PiiAnnotations too — lookupable must still be rejected.
674
+ bio: createLongTextField({ userOwned: { ownerField: "ownerId" }, lookupable: true }),
675
+ ownerId: createTextField({ required: true }),
676
+ },
677
+ }),
678
+ );
679
+ });
680
+ expect(() => validateBoot([feature])).toThrow(/only apply to text fields/);
681
+ });
682
+
683
+ test("searchable combined with a subject annotation throws", () => {
684
+ const feature = defineFeature("test", (r) => {
685
+ r.entity(
686
+ "user",
687
+ createEntity({
688
+ fields: {
689
+ displayName: createTextField({ pii: true, searchable: true }),
690
+ },
691
+ }),
692
+ );
693
+ });
694
+ expect(() => validateBoot([feature])).toThrow(/searchable.*cannot work/);
695
+ });
696
+
697
+ test("sortable combined with a subject annotation throws", () => {
698
+ const feature = defineFeature("test", (r) => {
699
+ r.entity(
700
+ "user",
701
+ createEntity({
702
+ fields: {
703
+ email: createTextField({ pii: true, sortable: true }),
704
+ },
705
+ }),
706
+ );
707
+ });
708
+ expect(() => validateBoot([feature])).toThrow(/sortable.*cannot work/);
709
+ });
710
+ });
711
+
712
+ describe("validateBoot — piiEncrypted (kumiko-platform#231/#456)", () => {
713
+ test("piiEncrypted on a plain text field passes", () => {
714
+ const feature = defineFeature("test", (r) => {
715
+ r.entity(
716
+ "tenant",
717
+ createEntity({
718
+ fields: {
719
+ iban: createTextField({
720
+ piiEncrypted: true,
721
+ tenantOwned: true,
722
+ access: { read: ["TenantAdmin"] },
723
+ }),
724
+ },
725
+ }),
726
+ );
727
+ });
728
+ expect(() => validateBoot([feature])).not.toThrow();
729
+ });
730
+
731
+ test("piiEncrypted on a non-text field throws", () => {
732
+ const feature = defineFeature("test", (r) => {
733
+ r.entity(
734
+ "tenant",
735
+ createEntity({
736
+ fields: {
737
+ amount: { ...createNumberField(), piiEncrypted: true } as unknown as ReturnType<
738
+ typeof createNumberField
739
+ >,
740
+ },
741
+ }),
742
+ );
743
+ });
744
+ expect(() => validateBoot([feature])).toThrow(/piiEncrypted.*only applies to text fields/);
745
+ });
746
+
747
+ test("piiEncrypted without a subject annotation throws (kumiko-platform#457)", () => {
748
+ const feature = defineFeature("test", (r) => {
749
+ r.entity(
750
+ "tenant",
751
+ createEntity({
752
+ fields: {
753
+ iban: createTextField({ piiEncrypted: true }),
754
+ },
755
+ }),
756
+ );
757
+ });
758
+ expect(() => validateBoot([feature])).toThrow(/piiEncrypted.*without a subject annotation/);
759
+ });
760
+
761
+ test("piiEncrypted combined with searchable throws", () => {
762
+ const feature = defineFeature("test", (r) => {
763
+ r.entity(
764
+ "tenant",
765
+ createEntity({
766
+ fields: {
767
+ iban: createTextField({
768
+ piiEncrypted: true,
769
+ tenantOwned: true,
770
+ access: { read: ["TenantAdmin"] },
771
+ searchable: true,
772
+ }),
773
+ },
774
+ }),
775
+ );
776
+ });
777
+ expect(() => validateBoot([feature])).toThrow(/piiEncrypted.*searchable.*cannot work/);
778
+ });
779
+
780
+ test("piiEncrypted combined with sortable throws", () => {
781
+ const feature = defineFeature("test", (r) => {
782
+ r.entity(
783
+ "tenant",
784
+ createEntity({
785
+ fields: {
786
+ iban: createTextField({
787
+ piiEncrypted: true,
788
+ tenantOwned: true,
789
+ access: { read: ["TenantAdmin"] },
790
+ sortable: true,
791
+ }),
792
+ },
793
+ }),
794
+ );
795
+ });
796
+ expect(() => validateBoot([feature])).toThrow(/piiEncrypted.*sortable.*cannot work/);
797
+ });
798
+
799
+ test("piiEncrypted without access.read throws (kumiko-platform#460)", () => {
800
+ const feature = defineFeature("test", (r) => {
801
+ r.entity(
802
+ "tenant",
803
+ createEntity({
804
+ fields: {
805
+ iban: createTextField({ piiEncrypted: true, tenantOwned: true }),
806
+ },
807
+ }),
808
+ );
809
+ });
810
+ expect(() => validateBoot([feature])).toThrow(/piiEncrypted.*without.*access.*read/);
811
+ });
812
+ });
@@ -2,7 +2,8 @@ import { describe, expect, test } from "bun:test";
2
2
  import { z } from "zod";
3
3
  import type { SchemaTable } from "../../db/dialect";
4
4
  import { table, text } from "../../db/dialect";
5
- import { validateBoot } from "../boot-validator";
5
+ import { withBootValidatorFixture } from "../../testing/boot-validator-fixture";
6
+ import { validateBoot as validateBootRaw } from "../boot-validator";
6
7
  import { createSystemConfig, createTenantConfig } from "../config-helpers";
7
8
  import {
8
9
  createDerivedField,
@@ -11,8 +12,13 @@ import {
11
12
  createTextField,
12
13
  defineFeature,
13
14
  from,
15
+ registerEntityCrud,
14
16
  } from "../index";
15
17
 
18
+ function validateBoot(features: Parameters<typeof validateBootRaw>[0]): void {
19
+ validateBootRaw(withBootValidatorFixture(features));
20
+ }
21
+
16
22
  describe("boot-validator", () => {
17
23
  test("passes for valid features with no issues", () => {
18
24
  const features = [
@@ -106,8 +112,10 @@ describe("boot-validator", () => {
106
112
  expect(() => validateBoot(features)).toThrow(/token.*cannot be both encrypted and sortable/i);
107
113
  });
108
114
 
109
- test("allows encrypted field when ENCRYPTION_KEY is set", () => {
110
- process.env["ENCRYPTION_KEY"] = "test-key";
115
+ test("allows encrypted field when a master key keyring is available", () => {
116
+ process.env["KUMIKO_SECRETS_MASTER_KEY_V1"] = Buffer.from(
117
+ "0123456789abcdef0123456789abcdef",
118
+ ).toString("base64");
111
119
  try {
112
120
  const features = [
113
121
  defineFeature("a", (r) => {
@@ -124,12 +132,34 @@ describe("boot-validator", () => {
124
132
  ];
125
133
  expect(() => validateBoot(features)).not.toThrow();
126
134
  } finally {
127
- delete process.env["ENCRYPTION_KEY"];
135
+ delete process.env["KUMIKO_SECRETS_MASTER_KEY_V1"];
136
+ }
137
+ });
138
+
139
+ test("throws at boot when the master key is malformed (not 32 bytes)", () => {
140
+ process.env["KUMIKO_SECRETS_MASTER_KEY_V1"] = Buffer.from("too-short").toString("base64");
141
+ try {
142
+ const features = [
143
+ defineFeature("a", (r) => {
144
+ r.entity(
145
+ "secret",
146
+ createEntity({
147
+ table: "Secrets",
148
+ fields: {
149
+ apiKey: { type: "text", encrypted: true },
150
+ },
151
+ }),
152
+ );
153
+ }),
154
+ ];
155
+ expect(() => validateBoot(features)).toThrow(/master key/i);
156
+ } finally {
157
+ delete process.env["KUMIKO_SECRETS_MASTER_KEY_V1"];
128
158
  }
129
159
  });
130
160
 
131
- test("throws when encrypted fields exist but ENCRYPTION_KEY not set", () => {
132
- delete process.env["ENCRYPTION_KEY"];
161
+ test("throws when encrypted fields exist but no master key is available", () => {
162
+ delete process.env["KUMIKO_SECRETS_MASTER_KEY_V1"];
133
163
  const features = [
134
164
  defineFeature("a", (r) => {
135
165
  r.entity(
@@ -143,16 +173,16 @@ describe("boot-validator", () => {
143
173
  );
144
174
  }),
145
175
  ];
146
- expect(() => validateBoot(features)).toThrow(/ENCRYPTION_KEY.*required/i);
176
+ expect(() => validateBoot(features)).toThrow(/master key/i);
147
177
  });
148
178
 
149
- test("throws when longText encrypted field exists but ENCRYPTION_KEY not set", () => {
179
+ test("throws when longText encrypted field exists but no master key is available", () => {
150
180
  // Drift-pin Sprint-5b-vorab-Audit Issue 1: validateEncryptedFields
151
181
  // hatte `if (field.type !== "text") continue;` und ignorierte
152
182
  // longText-encrypted-fields silently — ENCRYPTION_KEY-check wurde
153
183
  // nie getriggert, encryption silent broken. Jetzt: beide string-
154
184
  // typed fields werden gechecked.
155
- delete process.env["ENCRYPTION_KEY"];
185
+ delete process.env["KUMIKO_SECRETS_MASTER_KEY_V1"];
156
186
  const features = [
157
187
  defineFeature("a", (r) => {
158
188
  r.entity(
@@ -166,7 +196,7 @@ describe("boot-validator", () => {
166
196
  );
167
197
  }),
168
198
  ];
169
- expect(() => validateBoot(features)).toThrow(/ENCRYPTION_KEY.*required/i);
199
+ expect(() => validateBoot(features)).toThrow(/master key/i);
170
200
  });
171
201
 
172
202
  test("rejects encrypted text field that is also filterable", () => {
@@ -523,6 +553,21 @@ describe("boot-validator", () => {
523
553
  expect(() => validateBoot(features)).toThrow(/a:query:list.*missing an access rule/i);
524
554
  });
525
555
 
556
+ test("registerEntityCrud without access options fails boot (no implicit openToAll)", () => {
557
+ const widgetEntity = createEntity({
558
+ table: "phase0_widgets",
559
+ fields: { name: createTextField({ required: true }) },
560
+ });
561
+ const features = [
562
+ defineFeature("phase0", (r) => {
563
+ registerEntityCrud(r, "widget", widgetEntity);
564
+ }),
565
+ ];
566
+ expect(() => validateBoot(features)).toThrow(
567
+ /phase0:write:widget:create.*missing an access rule/i,
568
+ );
569
+ });
570
+
526
571
  test("accepts role-based access rule", () => {
527
572
  const features = [
528
573
  defineFeature("a", (r) => {
@@ -545,6 +590,66 @@ describe("boot-validator", () => {
545
590
  expect(() => validateBoot(features)).not.toThrow();
546
591
  });
547
592
 
593
+ test("throws when a stream handler has no access rule", () => {
594
+ const features = [
595
+ defineFeature("a", (r) => {
596
+ r.streamHandler("chat:complete", z.object({}), async function* () {});
597
+ }),
598
+ ];
599
+ expect(() => validateBoot(features)).toThrow(/a:stream:chat:complete.*missing an access rule/i);
600
+ });
601
+
602
+ test("object-form streamHandler with access rule passes boot", () => {
603
+ const features = [
604
+ defineFeature("a", (r) => {
605
+ r.streamHandler({
606
+ name: "chat:complete",
607
+ schema: z.object({}),
608
+ handler: async function* () {},
609
+ access: { roles: ["User"] },
610
+ });
611
+ }),
612
+ ];
613
+ expect(() => validateBoot(features)).not.toThrow();
614
+ });
615
+
616
+ test("accepts openToAll access rule on a stream handler", () => {
617
+ const features = [
618
+ defineFeature("a", (r) => {
619
+ r.streamHandler("chat:complete", z.object({}), async function* () {}, {
620
+ access: { openToAll: true },
621
+ });
622
+ }),
623
+ ];
624
+ expect(() => validateBoot(features)).not.toThrow();
625
+ });
626
+
627
+ test("throws when an anonymous stream handler uses a user-bucketed rate limit", () => {
628
+ const features = [
629
+ defineFeature("a", (r) => {
630
+ r.streamHandler("chat:complete", z.object({}), async function* () {}, {
631
+ access: { roles: ["anonymous"] },
632
+ rateLimit: { per: "user", limit: 10, windowSeconds: 60 },
633
+ });
634
+ }),
635
+ ];
636
+ expect(() => validateBoot(features)).toThrow(
637
+ /stream handler "a:stream:chat:complete" allows anonymous callers/i,
638
+ );
639
+ });
640
+
641
+ test("accepts an anonymous stream handler with an ip-bucketed rate limit", () => {
642
+ const features = [
643
+ defineFeature("a", (r) => {
644
+ r.streamHandler("chat:complete", z.object({}), async function* () {}, {
645
+ access: { roles: ["anonymous"] },
646
+ rateLimit: { per: "ip", limit: 10, windowSeconds: 60 },
647
+ });
648
+ }),
649
+ ];
650
+ expect(() => validateBoot(features)).not.toThrow();
651
+ });
652
+
548
653
  describe("config key bounds consistency", () => {
549
654
  test("accepts number key with consistent bounds + default", () => {
550
655
  const features = [
@@ -1186,8 +1291,9 @@ describe("boot-validator", () => {
1186
1291
  // --- entityList column-renderer form-check ---
1187
1292
  // Validator akzeptiert die `{ react: { __component: "Name" } }`-Form
1188
1293
  // (PlatformComponent → client-side Registry-Lookup) und prüft sie
1189
- // strukturell. String-Funktionen, null/undefined, native-only und
1190
- // andere Formen bleiben opak.
1294
+ // strukturell. null/undefined, native-only und andere Formen bleiben
1295
+ // opak. Function-Renderer werfen (action-wiring.ts validateFieldWiring) —
1296
+ // von JSON.stringify verworfen, sonst stiller No-op auf dem Client.
1191
1297
  describe("entityList column renderer form", () => {
1192
1298
  function shopFeature(renderer: unknown) {
1193
1299
  return defineFeature("shop", (r) => {
@@ -1205,8 +1311,10 @@ describe("boot-validator", () => {
1205
1311
  });
1206
1312
  }
1207
1313
 
1208
- test("function-renderer → kein Throw (Bestand)", () => {
1209
- expect(() => validateBoot([shopFeature((v: unknown) => String(v))])).not.toThrow();
1314
+ test("function-renderer → Throw (Function wird von JSON.stringify verworfen)", () => {
1315
+ expect(() => validateBoot([shopFeature((v: unknown) => String(v))])).toThrow(
1316
+ /column "name" renderer is a function/,
1317
+ );
1210
1318
  });
1211
1319
 
1212
1320
  test("undefined renderer → kein Throw (Spalte ohne Renderer)", () => {
@@ -1249,6 +1357,102 @@ describe("boot-validator", () => {
1249
1357
  });
1250
1358
  });
1251
1359
 
1360
+ // --- function-renderer check on the non-entityEdit/entityList screen
1361
+ // types that share the same EditLayout/ListColumnSpec shapes
1362
+ // (action-wiring.ts's validateEditLayoutNoFunctions/validateColumnsNoFunctions) ---
1363
+ describe("function-renderer check on other EditLayout/columns screens", () => {
1364
+ test("actionForm field renderer as a function → Throw", () => {
1365
+ const features = [
1366
+ defineFeature("shop", (r) => {
1367
+ r.screen({
1368
+ id: "restock",
1369
+ type: "actionForm",
1370
+ handler: "shop:write:restock",
1371
+ fields: { qty: { type: "number" } } as never,
1372
+ layout: {
1373
+ sections: [
1374
+ { fields: [{ field: "qty", renderer: (v: unknown) => String(v) }] as never },
1375
+ ],
1376
+ },
1377
+ });
1378
+ }),
1379
+ ];
1380
+ expect(() => validateBoot(features)).toThrow(
1381
+ /\(actionForm\) field "qty" renderer is a function/,
1382
+ );
1383
+ });
1384
+
1385
+ test("configEdit field renderer as a function → Throw", () => {
1386
+ const features = [
1387
+ defineFeature("shop", (r) => {
1388
+ r.config({
1389
+ keys: { "site-name": createTenantConfig("text", { default: "" }) },
1390
+ });
1391
+ r.screen({
1392
+ id: "settings",
1393
+ type: "configEdit",
1394
+ scope: "tenant",
1395
+ configKeys: { siteName: "shop:config:site-name" },
1396
+ fields: { siteName: { type: "text" } } as never,
1397
+ layout: {
1398
+ sections: [
1399
+ {
1400
+ fields: [{ field: "siteName", renderer: (v: unknown) => String(v) }] as never,
1401
+ },
1402
+ ],
1403
+ },
1404
+ });
1405
+ }),
1406
+ ];
1407
+ expect(() => validateBoot(features)).toThrow(
1408
+ /\(configEdit\) field "siteName" renderer is a function/,
1409
+ );
1410
+ });
1411
+
1412
+ test("projectionDetail field renderer as a function → Throw", () => {
1413
+ const features = [
1414
+ defineFeature("shop", (r) => {
1415
+ r.screen({
1416
+ id: "order-detail",
1417
+ type: "projectionDetail",
1418
+ query: "shop:query:order-detail",
1419
+ layout: {
1420
+ sections: [
1421
+ { fields: [{ field: "total", renderer: (v: unknown) => String(v) }] as never },
1422
+ ],
1423
+ },
1424
+ });
1425
+ }),
1426
+ ];
1427
+ expect(() => validateBoot(features)).toThrow(
1428
+ /\(projectionDetail\) field "total" renderer is a function/,
1429
+ );
1430
+ });
1431
+
1432
+ test("dashboard list-panel column renderer as a function → Throw", () => {
1433
+ const features = [
1434
+ defineFeature("shop", (r) => {
1435
+ r.screen({
1436
+ id: "overview",
1437
+ type: "dashboard",
1438
+ panels: [
1439
+ {
1440
+ kind: "list",
1441
+ id: "recent-orders",
1442
+ label: "Recent Orders",
1443
+ query: "shop:query:recent-orders",
1444
+ columns: [{ field: "total", renderer: (v: unknown) => String(v) }] as never,
1445
+ },
1446
+ ],
1447
+ });
1448
+ }),
1449
+ ];
1450
+ expect(() => validateBoot(features)).toThrow(
1451
+ /\(dashboard-list\) column "total" renderer is a function/,
1452
+ );
1453
+ });
1454
+ });
1455
+
1252
1456
  // --- entityList virtual (labeled) columns ---
1253
1457
  // A column whose `field` is not an entity field is allowed IF it carries a
1254
1458
  // `label` — a presentational column drawn by a columnRenderer component (e.g.
@@ -2558,7 +2762,7 @@ describe("boot-validator", () => {
2558
2762
  });
2559
2763
 
2560
2764
  test("number-Field OHNE sortable → Throw (sortable: true ist Pflicht)", () => {
2561
- expect(() => validateBoot([buildFeature("rank", { rank: { type: "number" } })])).toThrow(
2765
+ expect(() => validateBootRaw([buildFeature("rank", { rank: { type: "number" } })])).toThrow(
2562
2766
  /is not sortable/,
2563
2767
  );
2564
2768
  });