@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
@@ -1,3 +1,5 @@
1
+ import { createFallbackLogger } from "../../logging/utils.js";
2
+ const log = createFallbackLogger("kumiko:boot");
1
3
  export const SECURITY_BASELINE_FEATURE_NAMES = [
2
4
  "sessions",
3
5
  "crypto-shredding",
@@ -13,6 +15,5 @@ export function warnOnMissingSecurityBaseline(features, nodeEnv = process.env["N
13
15
  // skip: every baseline feature is mounted
14
16
  if (missing.length === 0)
15
17
  return;
16
- // biome-ignore lint/suspicious/noConsole: boot-time hint, no logger available yet
17
- console.warn(`[kumiko:boot] NODE_ENV=production but the security baseline is incomplete — missing feature(s): ${missing.join(", ")}. Mount securityBaselineFeatures() from @cosmicdrift/kumiko-bundled-features/presets (or the missing features individually).`);
18
+ log.warn(`NODE_ENV=production but the security baseline is incomplete — missing feature(s): ${missing.join(", ")}. Mount securityBaselineFeatures() from @cosmicdrift/kumiko-bundled-features/presets (or the missing features individually).`);
18
19
  }
@@ -1,12 +1,12 @@
1
1
  import { parseRefTargetEntityName } from "../parse-ref-target.js";
2
2
  import { findOverDeepTransferChain, MAX_TRANSFER_DEPTH, resolveTransferAdjacency, } from "../transfer-adjacency.js";
3
- // --- Transfer-graph boot validation (fw#3088) ---
3
+ // --- Transfer-graph boot validation ---
4
4
  //
5
5
  // tenant-handover walks an entity graph across the tenant boundary along two
6
6
  // declared edge kinds: `parentRef` and plain `reference` fields. Two shapes
7
7
  // cannot be walked, and both have to fail loudly here rather than silently
8
8
  // leaving rows behind in the source tenant — the partial move is exactly what
9
- // #3088 exists to end.
9
+ // this validation exists to end.
10
10
  //
11
11
  // Scoped to entities declaring `transferable: true`, so a consumer that has
12
12
  // one of these shapes but never hands that entity over is unaffected.
@@ -33,9 +33,9 @@ export function validateWorkspaces(feature, allNavQns) {
33
33
  }
34
34
  }
35
35
  const SETTINGS_HUB_AUDIENCE_NAV_QN_SET = new Set(SETTINGS_HUB_AUDIENCE_NAV_QNS);
36
- // fw#3019: apps that assign navs to workspaces via `r.workspace({ nav })`
37
- // or `r.nav({ workspaces })` never pass an explicit navAllowlist (solon#113)
38
- // — without this, warnOnUnreachableNavScreens never runs for them. Both
36
+ // Apps that assign navs to workspaces via `r.workspace({ nav })`
37
+ // or `r.nav({ workspaces })` never pass an explicit navAllowlist — without
38
+ // this, warnOnUnreachableNavScreens never runs for them. Both
39
39
  // `WorkspaceDefinition.nav` and `NavDefinition.workspaces` already hold
40
40
  // fully-qualified QNs (validateWorkspaces/validateNavs compare them
41
41
  // directly against allNavQns/allWorkspaceQns), so no re-qualification is
@@ -12,7 +12,7 @@ import { createAnonymousUser } from "./system-user.js";
12
12
  // anything downstream accidentally reaches for them.
13
13
  //
14
14
  // Use: `{ db, registry, ...bridgeStub() }`
15
- const notAvailable = (what) => async () => {
15
+ const notAvailable = (what) => async (..._args) => {
16
16
  throw new Error(`ctx.${what} not available in this context — use the dispatcher, not a stubbed handler context`);
17
17
  };
18
18
  // Noop observability — hand back the shared fallback tracer so ctx.tracer has
@@ -24,11 +24,10 @@ export function bridgeStub(opts) {
24
24
  // anonymous default values when no user is passed explicitly. Test code
25
25
  // that cares about identity passes its own SessionUser here and gets it
26
26
  // back on ctx.
27
- const stubUser = opts?.user ??
28
- createAnonymousUser("00000000-0000-0000-0000-000000000000"); // @cast-boundary engine-bridge
27
+ const stubUser = opts?.user ?? createAnonymousUser("00000000-0000-0000-0000-000000000000");
29
28
  return {
30
29
  user: stubUser,
31
- query: notAvailable("query"), // @cast-boundary engine-bridge
30
+ query: notAvailable("query"),
32
31
  queryAs: notAvailable("queryAs"),
33
32
  write: notAvailable("write"),
34
33
  writeAs: notAvailable("writeAs"),
@@ -1,22 +1,22 @@
1
- // Erzeugt aus einer Server-Registry das client-safe AppSchema das die
2
- // Browser-Renderer-Pipeline konsumiert. Genauer Zweck: dev-server kann
3
- // die ganze hand-geschriebene `clientSchema`-Spiegelung abschaffen, der
4
- // Server schickt einfach das aufgelöste AppSchema beim Boot mit.
1
+ // Builds the client-safe AppSchema from a server registry for the
2
+ // browser renderer pipeline. The dev server can therefore drop its entire
3
+ // hand-written `clientSchema` mirror and simply send the resolved AppSchema
4
+ // during boot.
5
5
  //
6
- // JSON-Safety: Wir projezieren explizit auf eine Whitelist statt
7
- // JSON.stringify-roundtripping. Functions würden silent gedroppt und
8
- // Zod-Schemas (v4 hat .toJSON, aber emittiert ein _zod-Envelope) würden
9
- // als komische Ghost-Properties auftauchen. Das hier ist ein bewusster
10
- // Vertrag: was die Browser-Renderer-Pipeline liest, taucht hier auf.
11
- // Neue Browser-needed-Fields müssen explizit erweitert werden.
6
+ // JSON safety: project explicitly onto a whitelist instead of round-tripping
7
+ // through JSON.stringify. Functions would be silently dropped, and Zod schemas
8
+ // (v4 has .toJSON, but emits a _zod envelope) would appear as strange ghost
9
+ // properties. This is an intentional contract: everything read by the browser
10
+ // renderer pipeline must appear here. New fields needed by the browser must be
11
+ // added explicitly.
12
12
  //
13
- // Aktuell projeziert (Stand 2026-04-25):
13
+ // Currently projected:
14
14
  // Entity: { table?, fields: { type, required?, sortable?, default? } }
15
- // Screen: verbatim (ScreenDefinition ist von Haus aus JSON-safe —
16
- // custom-screens haben keine functions, layout/columns/etc.
17
- // sind plain literals)
18
- // Nav: verbatim (NavDefinition ist nur strings + literals)
19
- // Workspace: verbatim definition + getWorkspaceNavs() von der Registry
15
+ // Screen: verbatim (ScreenDefinition is JSON-safe by design —
16
+ // custom screens have no functions; layout/columns/etc. are
17
+ // plain literals)
18
+ // Nav: verbatim (NavDefinition contains only strings + literals)
19
+ // Workspace: verbatim definition + getWorkspaceNavs() from the registry
20
20
  // Translations: verbatim feature.translations (raw r.translations keys,
21
21
  // unfiltered — see FeatureSchema.translations doc)
22
22
  //
@@ -24,11 +24,13 @@
24
24
  // The schema route strips features the caller's tenant has switched off
25
25
  // (r.toggleable) via stripDisabledFeaturesFromAppSchema.
26
26
  import { ZodObject } from "zod";
27
+ import { createFallbackLogger } from "../logging/utils.js";
27
28
  import { collectUrlPrefillFieldsByScreenQn } from "./boot-validator/url-prefill-fields.js";
28
29
  import { buildConfigFeatureSchema, SETTINGS_HUB_FEATURE, } from "./build-config-feature-schema.js";
29
30
  import { isEncryptedAtRest } from "./config-helpers.js";
30
31
  import { isListQueryWithoutSearchableFields } from "./entity-list-searchability.js";
31
32
  import { QnTypes, qualifyEntityName } from "./qualified-name.js";
33
+ const log = createFallbackLogger("kumiko");
32
34
  export function buildAppSchema(registry, options = {}) {
33
35
  const features = [];
34
36
  const urlPrefillFieldsByScreenQn = collectUrlPrefillFieldsByScreenQn([
@@ -52,7 +54,7 @@ export function buildAppSchema(registry, options = {}) {
52
54
  screens: projectScreens(featureName, feature.screens, registry, urlPrefillFieldsByScreenQn, encryptedConfigKeyQns),
53
55
  ...(navs.length > 0 && { navs }),
54
56
  ...(contentCollections.length > 0 && { contentCollections }),
55
- // #1059: verbatim r.translations({keys}) — see FeatureSchema.translations
57
+ // Verbatim r.translations({keys}) — see FeatureSchema.translations
56
58
  // doc for why this must NOT go through registry.getAllTranslations()
57
59
  // (double-prefixes features that already qualify their own keys).
58
60
  ...(Object.keys(feature.translations ?? {}).length > 0 && {
@@ -62,12 +64,12 @@ export function buildAppSchema(registry, options = {}) {
62
64
  };
63
65
  features.push(featureSchema);
64
66
  }
65
- // Workspaces: getAllWorkspaces() liefert mit QUALIFIZIERTEN ids (das
66
- // schreibt die Registry beim Store-Overwrite ein). Die Browser-
67
- // Renderer erwartet aber kurze ids (matcht gegen URL-Segment, gegen
68
- // navigate({ workspaceId })). Wir gehen direkt durch `feature.workspaces`
69
- // — dort sind die ids noch in der Autor-Form (short) — und ziehen die
70
- // pre-resolved navMembers aus der Registry.
67
+ // Workspaces: getAllWorkspaces() returns QUALIFIED ids (the Registry writes
68
+ // them during store overwrite). The browser renderer expects short ids
69
+ // (matching the URL segment and navigate({ workspaceId })). We therefore
70
+ // read directly from `feature.workspaces`, where the ids are still in the
71
+ // author's short form, and obtain the pre-resolved navMembers from the
72
+ // registry.
71
73
  let workspaces = [];
72
74
  for (const [featureName, feature] of registry.features) {
73
75
  for (const [shortId, definition] of Object.entries(feature.workspaces)) {
@@ -78,16 +80,16 @@ export function buildAppSchema(registry, options = {}) {
78
80
  });
79
81
  }
80
82
  }
81
- // Self-Populating Settings-Hub: aus den deklarierten Config-Keys mit `mask`
82
- // werden Screens/Navs (+ eine Workspace) abgeleitet und hier eingehängt —
83
- // kein manuelles r.screen/r.nav am App-Author.
83
+ // Self-populating Settings Hub: screens/navs (plus one workspace) are
84
+ // derived from the declared config keys with `mask` and attached here,
85
+ // so the app author does not need manual r.screen/r.nav declarations.
84
86
  const appHadWorkspaces = workspaces.length > 0;
85
87
  const generated = buildConfigFeatureSchema(registry);
86
88
  if (generated.screens.length > 0) {
87
89
  mergeSettingsHubIntoConfigFeature(features, generated);
88
- // Flip-Schutz: nur für Apps die schon Workspaces nutzen. Bei einer
89
- // workspace-losen App bleibt app.workspaces undefined → der Renderer zeigt
90
- // alle Navs ungefiltert, die Hub-Navs inklusive.
90
+ // Flip protection: applies only to apps that already use workspaces. For an
91
+ // app without workspaces, app.workspaces remains undefined, so the renderer
92
+ // shows all navs without filtering, including the Hub navs.
91
93
  if (appHadWorkspaces) {
92
94
  const placed = placeSettingsHub(workspaces, generated);
93
95
  workspaces = placed.workspaces;
@@ -109,16 +111,16 @@ export function buildAppSchema(registry, options = {}) {
109
111
  // instead so a leaked function renderer is actually caught.
110
112
  const offender = findNonJsonSafePath(schema, "schema");
111
113
  if (offender !== null) {
112
- // biome-ignore lint/suspicious/noConsole: dev-only assertion
113
- console.error(`[kumiko] buildAppSchema: Output ist nicht JSON-safe — nicht-serialisierbarer Wert bei "${offender}" (Funktions-Renderer oder Klassen-Instanz im Schema?).`, schema);
114
+ log.error(`buildAppSchema: output is not JSON-safe, non-serializable value at "${offender}" (a function renderer or class instance in the schema?).`, { offender });
114
115
  }
115
116
  }
116
117
  return schema;
117
118
  }
118
- // Hängt die generierten Hub-Screens/Navs an die config-FeatureSchema (qualified
119
- // dann als config:screen:* / config:nav:*). Existiert sie noch nicht (config
120
- // bundled-feature nicht gemountet), wird sie angelegt. find-or-create statt
121
- // fixem Push verhindert eine zweite FeatureSchema mit demselben featureName.
119
+ // Attaches the generated Hub screens/navs to the config FeatureSchema
120
+ // (qualified as config:screen:* / config:nav:*). If it does not yet exist
121
+ // (because the config bundled feature is not mounted), it is created. Using
122
+ // find-or-create instead of an unconditional push prevents a second
123
+ // FeatureSchema with the same featureName.
122
124
  function mergeSettingsHubIntoConfigFeature(features, generated) {
123
125
  const existing = features.find((f) => f.featureName === SETTINGS_HUB_FEATURE);
124
126
  if (existing === undefined) {
@@ -202,8 +204,7 @@ function warnUnplacedAudiences(unplaced) {
202
204
  // skip: every audience placed — nothing to warn about
203
205
  if (unplaced.length === 0)
204
206
  return;
205
- // biome-ignore lint/suspicious/noConsole: dev-only authoring hint
206
- console.warn(`[kumiko] Settings-Hub: ${unplaced.join(", ")} nicht in einer App-Workspace platziert — ` +
207
+ log.warn(`Settings-Hub: ${unplaced.join(", ")} nicht in einer App-Workspace platziert — ` +
207
208
  `erscheint im Standalone-"Einstellungen"-Tab. Referenziere ` +
208
209
  `${unplaced.map((id) => `${SETTINGS_HUB_FEATURE}:nav:${id}`).join(", ")} ` +
209
210
  `in einer r.workspace.nav, um die Gruppe inline zu zeigen.`);
@@ -212,8 +213,7 @@ function warnDanglingAudienceRefs(dangling) {
212
213
  // skip: no dangling refs — nothing to warn about
213
214
  if (dangling.length === 0)
214
215
  return;
215
- // biome-ignore lint/suspicious/noConsole: dev-only authoring hint
216
- console.warn(`[kumiko] Settings-Hub: ${dangling
216
+ log.warn(`Settings-Hub: ${dangling
217
217
  .map((id) => `${SETTINGS_HUB_FEATURE}:nav:${id}`)
218
218
  .join(", ")} in einer Workspace referenziert, aber nie generiert — ` +
219
219
  `keine Config-Keys für diesen Scope registriert. Tippfehler oder ` +
@@ -260,8 +260,8 @@ export function findNonJsonSafePath(value, path) {
260
260
  }
261
261
  // projectionList screens don't declare searchable/sortable/paginated as
262
262
  // authoring intent — they're derived here from the bound query handler's
263
- // Zod schema, the source of truth for what parameters it actually accepts
264
- // (fw#2165). A hand-written `searchable: true` still wins over the derived
263
+ // Zod schema, the source of truth for what parameters it actually accepts.
264
+ // A hand-written `searchable: true` still wins over the derived
265
265
  // default (screen.searchable ?? derived); the boot-validator (3a) rejects
266
266
  // one that contradicts the schema.
267
267
  function projectScreens(featureName, screens, registry, urlPrefillFieldsByScreenQn, encryptedConfigKeyQns) {
@@ -372,30 +372,30 @@ function projectEntities(entities) {
372
372
  }
373
373
  return out;
374
374
  }
375
- // EntityDefinition ist eine Discriminated-Union an Field-Types — wir
376
- // kennen alle JSON-safe Properties pro Field-Type. Statt jede Variante
377
- // einzeln auszuhandeln, walken wir die Field-Map und filtern auf die
378
- // Whitelist. Was nicht durchkommt: Server-only-runtime wie ZodValidate-
379
- // Functions, Computed-Functions, Default-Functions.
375
+ // EntityDefinition is a discriminated union of field types, and we know the
376
+ // JSON-safe properties for each type. Rather than handling every variant
377
+ // separately, walk the field map and filter it through the whitelist. Values
378
+ // that do not pass through include server-only runtime values such as
379
+ // ZodValidate functions, computed functions, and default functions.
380
380
  function projectEntity(entityName, entity) {
381
381
  const fieldsOut = {};
382
382
  for (const [fieldName, fieldDef] of Object.entries(entity.fields)) {
383
383
  assertValidMultilineRows(`Entity "${entityName}"`, fieldName, fieldDef);
384
384
  fieldsOut[fieldName] = projectField(fieldDef);
385
385
  }
386
- // derivedFields MÜSSEN mit ins Client-Schema (nur die Metadaten, nicht die
387
- // derive-fn): computeListViewModel löst eine entityList-Spalte über
388
- // `entity.derivedFields[field].valueType` auf — fehlt der Eintrag, wirft es
389
- // "references unknown field". Der executor hat den Wert server-seitig schon
390
- // an die Row gehängt; der Client braucht nur den valueType für den Renderer.
386
+ // derivedFields MUST be included in the client schema (metadata only, not the
387
+ // derive function): computeListViewModel resolves an entityList column via
388
+ // `entity.derivedFields[field].valueType`; without the entry it throws
389
+ // "references unknown field". The executor has already attached the value
390
+ // server-side to the row; the client only needs valueType for rendering.
391
391
  const derivedOut = {};
392
392
  for (const [name, derivedDef] of Object.entries(entity.derivedFields ?? {})) {
393
393
  derivedOut[name] = projectDerivedField(derivedDef);
394
394
  }
395
- // EntityDefinition akzeptiert idType/access/searchWeight als optional —
396
- // wir lassen die weg weil der Browser-Renderer sie nicht liest. `table`
397
- // schicken wir mit, falls Apps `entity.table` direkt referenzieren.
398
- // Kein Cast nötig: alle weggelassenen Felder sind `?`-optional.
395
+ // EntityDefinition accepts idType/access/searchWeight as optional, but we omit
396
+ // them because the browser renderer does not read them. `table` is included
397
+ // in case apps reference `entity.table` directly.
398
+ // No cast is needed: all omitted fields are optional (`?`).
399
399
  return {
400
400
  fields: fieldsOut,
401
401
  // @cast-boundary schema-walk: EntityDefinition.derivedFields is typed for
@@ -413,10 +413,10 @@ function projectEntity(entityName, entity) {
413
413
  ...(entity.lockedWhen !== undefined && { lockedWhen: entity.lockedWhen }),
414
414
  };
415
415
  }
416
- // Nur valueType durch — die derive-fn ist Server-only und NICHT JSON-safe
417
- // (würde sonst die Output-Walk-Guard triggern). Gibt ehrlich
418
- // ClientDerivedFieldDef zurück statt eine DerivedFieldDef vorzutäuschen,
419
- // die keine derive-fn hat.
416
+ // Only valueType is forwarded — the derive function is server-only and is not
417
+ // JSON-safe (it would otherwise trigger the output-walk guard). Return
418
+ // ClientDerivedFieldDef honestly instead of pretending to return a
419
+ // DerivedFieldDef without its derive function.
420
420
  function projectDerivedField(derivedDef) {
421
421
  return { valueType: derivedDef.valueType };
422
422
  }
@@ -446,138 +446,98 @@ function assertValidScreenMultilineRows(featureName, shortId, screen) {
446
446
  check(screen.confirm.fields);
447
447
  }
448
448
  }
449
- // Per-field whitelist. Every forwarded value must be JSON-safe (literal, or
450
- // an array/plain-object built only from JSON-safe values) — even though the
451
- // FieldDefinition types only type `default` as a literal, the sample-authoring
452
- // pattern `as unknown as EntityDefinition` has already let function-valued
453
- // defaults slip through. This whitelist is defense-in-depth that catches them
454
- // BEFORE JSON.stringify silently drops them in the browser-injection pipeline.
455
- // The same check gates the structured properties below (`multiline`,
456
- // `derived`, `totals`, `totalsMatch`) — a top-level `Array.isArray`/plain-
457
- // object check alone would let a function survive one level deep.
458
- //
459
- // Cast at the exit `as EntityFieldDefinition`: type-system-wise our out-map only
460
- // satisfies the discriminated union with an unnarrowed `type` string — the
461
- // cast bridges the variant inference TS can't recompute from a generic
462
- // Record.
463
- // kumiko-lint-ignore complexity-budget flat per-property whitelist walk, one independent `if` per FieldDefinition property — same shape as schema-builder.ts's field→zod switch
464
- function projectField(fieldDef) {
465
- const def = fieldDef; // @cast-boundary schema-walk
466
- const out = {};
467
- if (typeof def["type"] === "string")
468
- out["type"] = def["type"];
469
- if (typeof def["required"] === "boolean")
470
- out["required"] = def["required"];
471
- if (typeof def["sortable"] === "boolean")
472
- out["sortable"] = def["sortable"];
473
- // filterable steuert die Faceted-Filter-Dropdowns im Renderer (select/
474
- // boolean) — muss daher ins Client-Schema.
475
- if (typeof def["filterable"] === "boolean")
476
- out["filterable"] = def["filterable"];
477
- if (typeof def["searchable"] === "boolean")
478
- out["searchable"] = def["searchable"];
479
- if (isJsonSafeValue(def["default"]))
480
- out["default"] = def["default"];
481
- // Select: options-Liste ist plain JSON, durchschicken.
482
- if (Array.isArray(def["options"]))
483
- out["options"] = def["options"];
484
- if (isJsonSafeObject(def["optionTones"]))
485
- out["optionTones"] = def["optionTones"];
486
- // Reference: entity-Target + labelField + multiple müssen zum Renderer.
487
- // Der ReferenceInput baut die Options-Query aus refEntity/refFeature und
488
- // resolved das Label über labelField — ohne diese Properties fällt das
489
- // Dropdown leer aus (QN wird `<feature>:query::list` → 404).
490
- if (typeof def["entity"] === "string")
491
- out["entity"] = def["entity"];
492
- if (typeof def["labelField"] === "string")
493
- out["labelField"] = def["labelField"];
494
- // fw#2780: author-supplied picker source — without it the client falls back to the list handler.
495
- if (typeof def["optionsQuery"] === "string")
496
- out["optionsQuery"] = def["optionsQuery"];
449
+ const isStringValue = (value) => typeof value === "string";
450
+ const isBooleanValue = (value) => typeof value === "boolean";
451
+ const isNumberValue = (value) => typeof value === "number";
452
+ const isTrueValue = (value) => value === true;
453
+ const isNumberOrStringValue = (value) => typeof value === "number" || typeof value === "string";
454
+ const isBooleanOrJsonSafeObject = (value) => typeof value === "boolean" || isJsonSafeObject(value);
455
+ const isUnitSpec = (value) => typeof value === "string" || (isPlainObject(value) && typeof value["field"] === "string");
456
+ // Forwarded in this order, so the client schema keeps its key order.
457
+ const FORWARDED_FIELD_PROPERTIES = [
458
+ ["type", isStringValue],
459
+ ["required", isBooleanValue],
460
+ ["sortable", isBooleanValue],
461
+ // filterable controls the faceted filter dropdowns in the renderer (select/
462
+ // boolean), so it must be included in the client schema.
463
+ ["filterable", isBooleanValue],
464
+ ["searchable", isBooleanValue],
465
+ ["default", isJsonSafeValue],
466
+ // Select: the options list is plain JSON, so forward it.
467
+ ["options", Array.isArray],
468
+ ["optionTones", isJsonSafeObject],
469
+ // Reference: entity target, labelField, and multiple are required by the
470
+ // renderer. ReferenceInput builds the options query from refEntity/refFeature
471
+ // and resolves the label through labelField; without these properties, the
472
+ // dropdown is empty (the QN becomes `<feature>:query::list` → 404).
473
+ ["entity", isStringValue],
474
+ ["labelField", isStringValue],
475
+ // Author-supplied picker source — without it the client falls back to the list handler.
476
+ ["optionsQuery", isStringValue],
497
477
  // Select: tier/tenant availability of the static options, loaded by the renderer.
498
- if (typeof def["optionsAvailabilityQuery"] === "string")
499
- out["optionsAvailabilityQuery"] = def["optionsAvailabilityQuery"];
478
+ ["optionsAvailabilityQuery", isStringValue],
500
479
  // Select: options only offered while a sibling-field condition holds; the renderer filters, the write path enforces.
501
- if (Array.isArray(def["conditionalOptions"]))
502
- out["conditionalOptions"] = def["conditionalOptions"];
503
- if (typeof def["multiple"] === "boolean")
504
- out["multiple"] = def["multiple"];
480
+ ["conditionalOptions", Array.isArray],
481
+ ["multiple", isBooleanValue],
505
482
  // MultiSelect: display picks checkboxes vs. combobox in the renderer,
506
483
  // columns/maxRows size the checkbox grid — without these the renderer
507
- // always falls back to the combobox (fw#2494). Select: display picks
484
+ // always falls back to the combobox. Select: display picks
508
485
  // "radio" | "dropdown" the same way.
509
- if (typeof def["display"] === "string")
510
- out["display"] = def["display"];
511
- if (typeof def["columns"] === "number")
512
- out["columns"] = def["columns"];
513
- if (typeof def["maxRows"] === "number")
514
- out["maxRows"] = def["maxRows"];
486
+ ["display", isStringValue],
487
+ ["columns", isNumberValue],
488
+ ["maxRows", isNumberValue],
515
489
  // text/longText: textarea row count — DefaultInput renders a single-line
516
- // input otherwise (fw#2497).
517
- if (typeof def["multiline"] === "boolean" || isJsonSafeObject(def["multiline"]))
518
- out["multiline"] = def["multiline"];
490
+ // input otherwise.
491
+ ["multiline", isBooleanOrJsonSafeObject],
519
492
  // number: Zod write-boundary bounds. date/timestamp/locatedTimestamp:
520
- // ISO-string picker bounds — same keys, different literal type (fw#2497).
521
- if (typeof def["min"] === "number" || typeof def["min"] === "string")
522
- out["min"] = def["min"];
523
- if (typeof def["max"] === "number" || typeof def["max"] === "string")
524
- out["max"] = def["max"];
525
- // date/timestamp/locatedTimestamp: display/parsing locale override (fw#2497).
526
- if (typeof def["locale"] === "string")
527
- out["locale"] = def["locale"];
528
- // image/images: which camera a mobile capture opens (fw#2497).
529
- if (typeof def["capture"] === "string")
530
- out["capture"] = def["capture"];
493
+ // ISO-string picker bounds — same keys, different literal type.
494
+ ["min", isNumberOrStringValue],
495
+ ["max", isNumberOrStringValue],
496
+ // date/timestamp/locatedTimestamp: display/parsing locale override.
497
+ ["locale", isStringValue],
498
+ // image/images: which camera a mobile capture opens.
499
+ ["capture", isStringValue],
531
500
  // number: display-only suffix, static or read from a sibling field of the row.
532
- const unit = def["unit"];
533
- if (typeof unit === "string" || (isPlainObject(unit) && typeof unit["field"] === "string"))
534
- out["unit"] = unit;
501
+ ["unit", isUnitSpec],
535
502
  // number/decimal/bigInt: `false` drops thousands separators (e.g. a year).
536
- if (typeof def["grouping"] === "boolean")
537
- out["grouping"] = def["grouping"];
503
+ ["grouping", isBooleanValue],
538
504
  // text: "password" masks the input and blocks URL prefill in the renderer.
539
- if (typeof def["format"] === "string")
540
- out["format"] = def["format"];
505
+ ["format", isStringValue],
541
506
  // Write-response redaction stays server-side; the renderer needs the flag to
542
507
  // refuse any prefill (URL or handoff) into the field.
543
- if (def["sensitive"] === true)
544
- out["sensitive"] = true;
508
+ ["sensitive", isTrueValue],
545
509
  // Without it the renderer shows a plain text input for a field whose value never leaves the server.
546
- if (def["writeOnly"] === true)
547
- out["writeOnly"] = true;
510
+ ["writeOnly", isTrueValue],
548
511
  // timestamp: the edit view-model derives its wall-clock input mode from this.
549
- if (typeof def["locatedBy"] === "string")
550
- out["locatedBy"] = def["locatedBy"];
512
+ ["locatedBy", isStringValue],
551
513
  // file/image/images: upload picker constraints; the first variant key picks
552
514
  // the preview variant.
553
- if (isJsonSafeArray(def["accept"]))
554
- out["accept"] = def["accept"];
555
- if (typeof def["maxSize"] === "string")
556
- out["maxSize"] = def["maxSize"];
557
- if (isJsonSafeObject(def["variants"]))
558
- out["variants"] = def["variants"];
515
+ ["accept", isJsonSafeArray],
516
+ ["maxSize", isStringValue],
517
+ ["variants", isJsonSafeObject],
559
518
  // decimal, incl. embedded sub-fields: rounding of derived embedded-list cells.
560
- if (typeof def["scale"] === "number")
561
- out["scale"] = def["scale"];
562
- // money: declared currency source (MoneyCurrencySource, fw#2933) — without
519
+ ["scale", isNumberValue],
520
+ // money: declared currency source (MoneyCurrencySource) — without
563
521
  // it the renderer can't tell a "currency: { kind: 'tenant' }" field apart
564
522
  // from an undeclared one and always falls back to entity.defaultCurrency.
565
- if (isJsonSafeObject(def["currency"]))
566
- out["currency"] = def["currency"];
523
+ ["currency", isJsonSafeObject],
567
524
  // embedded lists: row-count bounds, computed cells, totals row, and the
568
- // sibling-money-field totals check (fw#2497).
569
- if (typeof def["minItems"] === "number")
570
- out["minItems"] = def["minItems"];
571
- if (typeof def["maxItems"] === "number")
572
- out["maxItems"] = def["maxItems"];
573
- if (isJsonSafeObject(def["derived"]))
574
- out["derived"] = def["derived"];
575
- if (isJsonSafeArray(def["totals"]))
576
- out["totals"] = def["totals"];
577
- if (isJsonSafeObject(def["totalsMatch"]))
578
- out["totalsMatch"] = def["totalsMatch"];
525
+ // sibling-money-field totals check.
526
+ ["minItems", isNumberValue],
527
+ ["maxItems", isNumberValue],
528
+ ["derived", isJsonSafeObject],
529
+ ["totals", isJsonSafeArray],
530
+ ["totalsMatch", isJsonSafeObject],
531
+ ];
532
+ function projectField(fieldDef) {
533
+ const def = fieldDef; // @cast-boundary schema-walk
534
+ const out = {};
535
+ for (const [key, isForwarded] of FORWARDED_FIELD_PROPERTIES) {
536
+ if (isForwarded(def[key]))
537
+ out[key] = def[key];
538
+ }
579
539
  // embedded lists: the sub-field map itself — without it the client-side
580
- // edit view-model iterates `undefined` and the screen crashes (fw#2507).
540
+ // edit view-model iterates `undefined` and the screen crashes.
581
541
  // Recursive per sub-field, NOT a blanket isJsonSafeValue gate: a single
582
542
  // sub-field carrying a validator fn would otherwise drop the whole map,
583
543
  // and a blanket copy would ship server internals.