@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
@@ -7,7 +7,7 @@ export function isRowClickAction(action) {
7
7
  return (action.kind === "navigate" || action.kind === "drawer") && action.rowClick === true;
8
8
  }
9
9
  // entityList and projectionList both allow a rowAction to double as the
10
- // row-body click target (rowClick: true, fw#1708/#2164) — at most one per
10
+ // row-body click target (rowClick: true) — at most one per
11
11
  // screen, or the renderer can't tell which one should fire.
12
12
  export function validateAtMostOneRowClick(featureName, screenId, screenType, rowActions) {
13
13
  const rowClickActions = rowActions.filter(isRowClickAction);
@@ -30,18 +30,17 @@ export function readsNavigateParamsAsFormPrefill(target, explicitEntityId, sourc
30
30
  return false;
31
31
  return sourceScreenEntity === undefined || target.entity !== sourceScreenEntity;
32
32
  }
33
- // Tier 2.7e navigate rowAction → target-screen params validity. Shared by
34
- // entityList and projectionList (framework#1708) — projectionList has no
33
+ // Navigate rowAction → target-screen params validity. Shared by
34
+ // entityList and projectionList — projectionList has no
35
35
  // `screen.entity`, so there's no same-entity row["id"] auto-fill case: any
36
36
  // entityEdit target without an explicit entityId reaches create there.
37
37
  //
38
- // dashboard targets (framework#1708 follow-up, commit 4e0d6cb26) also read
39
- // URL search params — but only for the single `filter.id` a dashboard
40
- // declares (useFilterParams in dashboard-body.tsx seeds its value from
41
- // `nav.searchParams[filter.id]`). A dashboard with no `filter` has nowhere
42
- // for the value to land, and a params extractor whose keys don't include
43
- // the filter's id would silently miss it — both are boot errors instead of
44
- // a silently-empty dashboard.
38
+ // Dashboard targets also read URL search params — but only for the single
39
+ // `filter.id` a dashboard declares (useFilterParams in dashboard-body.tsx
40
+ // seeds its value from `nav.searchParams[filter.id]`). A dashboard with no
41
+ // `filter` has nowhere for the value to land, and a params extractor whose
42
+ // keys don't include the filter's id would silently miss it — both are boot
43
+ // errors instead of a silently-empty dashboard.
45
44
  export function validateRowActionNavigateParams(featureName, screenId, screenType, screenEntity, action, target, actionLabel = "rowAction") {
46
45
  // skip: not a navigate-with-params action — nothing to validate here.
47
46
  if (action.kind !== "navigate" || action.params === undefined)
@@ -122,7 +121,7 @@ export function validateActionHasIcon(featureName, screenId, screenType, context
122
121
  `its kebab segments via the default icon map (resolveActionIcon).`);
123
122
  }
124
123
  }
125
- // Tier 2.7e-3: pin an action's declarative field references against the entity
124
+ // Pin an action's declarative field references against the entity
126
125
  // fields — a typo in pick/map source fields or visible.field would otherwise
127
126
  // silently yield `undefined` in the payload or permanently wrong visibility
128
127
  // (same "typo only surfaces on click" class as navigate/handler). Exported for
@@ -203,12 +202,12 @@ export function validateScreenNavTarget(featureName, screenId, screenKind, field
203
202
  }
204
203
  // kind:"drawer" resolves same-feature only — unlike navigate/redirect,
205
204
  // which the runtime router resolves app-wide (see the comment on
206
- // validateScreens below), the drawer mounts the target inline using this
205
+ // buildScreenValidationContext in screens.ts), the drawer mounts the target inline using this
207
206
  // feature's schema, so a cross-feature reference could never actually
208
207
  // render. Two distinct failure messages: dangling reference vs. wrong
209
- // screen type. Shared by ToolbarAction (fw#2225) and RowAction (fw#2710)
210
- // drawer variants — same target-resolution rule, only the label in the
211
- // error message ("toolbarAction" vs. "rowAction" vs. "action") differs.
208
+ // screen type. Shared by ToolbarAction and RowAction drawer variants —
209
+ // same target-resolution rule, only the label in the error message
210
+ // ("toolbarAction" vs. "rowAction" vs. "action") differs.
212
211
  export function validateDrawerTargetAction(featureName, screenId, screenKind, actionLabel, action, screens) {
213
212
  const target = screens[action.screen];
214
213
  if (target === undefined) {
@@ -337,8 +336,8 @@ export function resolveRowActionNavigateTarget(featureName, screenId, screenType
337
336
  // redirect is either a short screen id (same-feature, e.g. "item-list") or
338
337
  // a fully-qualified cross-feature QN (`<feature>:screen:<id>`) — the
339
338
  // renderer strips the latter to the short id (lastSegment) when
340
- // navigating, which the nav-router resolves app-wide (#1946). The object
341
- // form (fw#2670) carries the same target under `screen` plus the payload
339
+ // navigating, which the nav-router resolves app-wide. The object
340
+ // form carries the same target under `screen` plus the payload
342
341
  // field `idFrom`. Shared by actionForm/secretMint (validateInlineFormNavTargets)
343
342
  // and entityEdit — same rule, same error message, one place to keep them in sync.
344
343
  export function validateRedirectTarget(feature, screenId, screenKind, redirect, lookups,
@@ -486,3 +485,10 @@ export function validateNavigateActionTab(featureName, screenId, screenType, act
486
485
  return;
487
486
  resolveRowActionNavigateTarget(featureName, screenId, screenType, actionLabel, action, lookups.allScreenQns, lookups.navTargetShortIds, lookups.screensByShortId, lookups.detailForScreens);
488
487
  }
488
+ export function assertWriteHandlerRegistered(featureName, subject, handler, allWriteHandlerQns) {
489
+ if (!allWriteHandlerQns.has(handler)) {
490
+ throw new Error(`[Feature ${featureName}] ${subject} handler "${handler}" ` +
491
+ `is not a registered write-handler. Check the QN spelling (expected ` +
492
+ `"<feature>:write:<short>") and that the handler is declared via r.writeHandler(...).`);
493
+ }
494
+ }
@@ -1,6 +1,6 @@
1
1
  import { DASHBOARD_MIN_REFRESH_SECONDS } from "@cosmicdrift/kumiko-types/screen";
2
2
  import { normalizeListColumn } from "../screen-helpers.js";
3
- import { isRowClickAction, resolveRowActionNavigateTarget, resolveScreenTargetQn, validateActionHasIcon, validateClientActionName, validateRowActionNavigateParams, } from "./screens-actions.js";
3
+ import { assertWriteHandlerRegistered, isRowClickAction, resolveRowActionNavigateTarget, resolveScreenTargetQn, validateActionHasIcon, validateClientActionName, validateRowActionNavigateParams, } from "./screens-actions.js";
4
4
  import { assertRefTargetRegistered, validateColumnRendererForm } from "./screens-lists.js";
5
5
  // Panel-driven, no entity — structural checks (unique panel ids, non-empty
6
6
  // queries/columns/valueField); the query contracts (stat record, points
@@ -68,10 +68,7 @@ export function validateDashboardActions(featureName, screenId, screen, allWrite
68
68
  throw new Error(`${where} sets redirect — a dashboard action stays on the dashboard and refetches ` +
69
69
  `after success. Remove redirect.`);
70
70
  }
71
- if (!allWriteHandlerQns.has(action.handler)) {
72
- throw new Error(`${where} handler "${action.handler}" is not a registered write-handler. Check the QN ` +
73
- `spelling (expected "<feature>:write:<short>") and that the handler is declared via r.writeHandler(...).`);
74
- }
71
+ assertWriteHandlerRegistered(featureName, `Screen "${screenId}" (dashboard) action "${action.id}"`, action.handler, allWriteHandlerQns);
75
72
  }
76
73
  }
77
74
  }
@@ -2,9 +2,9 @@ import { NO_WIDGET_FIELD_TYPES, optionsQueryFieldRefs } from "@cosmicdrift/kumik
2
2
  import { isKebabSegment } from "../qualified-name.js";
3
3
  import { isExtensionEditSection, isWriteFormEditSection, normalizeEditField, sectionFieldSpecs, } from "../screen-helpers.js";
4
4
  import { validateFormDerivations, validateFormDerivationsEnforced } from "./form-derivations.js";
5
- import { isSensitiveOrPasswordField, validateIdlessSuccessLink, validateRedirectTarget, validateScreenNavTarget, } from "./screens-actions.js";
5
+ import { assertWriteHandlerRegistered, isSensitiveOrPasswordField, validateIdlessSuccessLink, validateRedirectTarget, validateScreenNavTarget, } from "./screens-actions.js";
6
6
  // A field type in NO_WIDGET_FIELD_TYPES renders read-only on the auto-wired
7
- // entityEdit path (#1925) — a required field the user can never fill would
7
+ // entityEdit path — a required field the user can never fill would
8
8
  // block every save. Only the statically-resolvable case is caught here: a
9
9
  // literal `required: true` (screen-spec override or entity-level default).
10
10
  // A dynamic FieldCondition depends on runtime form values and can't be
@@ -95,7 +95,7 @@ export function validateNoWidgetRequiredField(featureName, screenId, entityDef,
95
95
  if (fieldDef === undefined || !NO_WIDGET_FIELD_TYPES.includes(fieldDef.type))
96
96
  return;
97
97
  // Embedded LIST fields (`multiple: true`) get their own EmbeddedListField
98
- // grid widget (#1838) — only plain (non-list) embedded has no widget.
98
+ // grid widget — only plain (non-list) embedded has no widget.
99
99
  const isEmbeddedList = fieldDef.type === "embedded" && fieldDef.multiple === true;
100
100
  // skip: list variant has a widget — not the no-widget case this guard targets.
101
101
  if (isEmbeddedList)
@@ -115,8 +115,7 @@ export function validateNoWidgetRequiredField(featureName, screenId, entityDef,
115
115
  }
116
116
  // Shared by projectionDetail and entityEdit: a tab strip needs something to
117
117
  // label each tab with and a stable id per tab — the id anchors the ?tab=
118
- // param on projectionDetail and the jump-to-erroring-tab logic on entityEdit
119
- // (fw#3134).
118
+ // param on projectionDetail and the jump-to-erroring-tab logic on entityEdit.
120
119
  export function validateTabSections(featureName, screenId, screenType, sections) {
121
120
  if (sections.length < 2) {
122
121
  throw new Error(`[Feature ${featureName}] Screen "${screenId}" (${screenType}) has mode: "tabs" but only ` +
@@ -149,7 +148,7 @@ export function validateTabSections(featureName, screenId, screenType, sections)
149
148
  export function validateWizardLayout(featureName, screenId, screenType, layout, featureMap) {
150
149
  // entityEdit renders tabs like wizard steps: every section stays mounted,
151
150
  // submit validates across all of them, and a field error jumps to the tab
152
- // holding it (fw#3134). The other three have no such jump — actionForm and
151
+ // holding it. The other three have no such jump — actionForm and
153
152
  // secretMint are one-shot forms, configEdit fires a write per field — so a
154
153
  // required field on a hidden tab would still block their submit silently.
155
154
  if (layout.mode === "tabs") {
@@ -201,7 +200,7 @@ export function validateWizardLayout(featureName, screenId, screenType, layout,
201
200
  //
202
201
  // Field-level renderer QN strings (cross-feature `component:` references)
203
202
  // are NOT validated here — the r.uiComponent registry that would resolve
204
- // them ships in M4/M5. Until then those are kept opaque on purpose.
203
+ // them ships later. Until then those are kept opaque on purpose.
205
204
  // Shared by the mint step's own handler (screen.handler) and its optional
206
205
  // confirm step (confirm.handler) — both are a bare write-handler QN with the
207
206
  // same registration requirement.
@@ -209,11 +208,7 @@ function validateWriteHandlerRegistered(featureName, screenId, context, handler,
209
208
  if (!handler || typeof handler !== "string") {
210
209
  throw new Error(`[Feature ${featureName}] Screen "${screenId}" (${context}) has empty or non-string handler. Set handler to a non-empty handler name.`);
211
210
  }
212
- if (!allWriteHandlerQns.has(handler)) {
213
- throw new Error(`[Feature ${featureName}] Screen "${screenId}" (${context}) handler "${handler}" ` +
214
- `is not a registered write-handler. Check the QN spelling (expected ` +
215
- `"<feature>:write:<short>") and that the handler is declared via r.writeHandler(...).`);
216
- }
211
+ assertWriteHandlerRegistered(featureName, `Screen "${screenId}" (${context})`, handler, allWriteHandlerQns);
217
212
  }
218
213
  // Every field entry must carry a `type` discriminator. An author typo
219
214
  // (`title: { required: true }` without a type) would otherwise let
@@ -226,7 +221,7 @@ function validateWriteHandlerRegistered(featureName, screenId, context, handler,
226
221
  // secretMint's own `confirm` step — a second, independent inline form on the
227
222
  // same screen — can reuse the same checks as its `fields`/`layout` pair.
228
223
  // `allowEmpty` is only ever true for a secretMint's own mint step (an
229
- // input-less mint, fw#2838): the secret is server-generated and the mint
224
+ // input-less mint): the secret is server-generated and the mint
230
225
  // step is just its submit button — actionForm and the confirm step always
231
226
  // require at least one field.
232
227
  function validateFormFieldsMap(featureName, screenId, context, fields, allowEmpty) {
@@ -330,7 +325,7 @@ export function validateFormSelectOptions(featureName, screenId, context, fieldN
330
325
  }
331
326
  }
332
327
  }
333
- // Fail-closed currency-source gate (fw#2839), the runtime half of the
328
+ // Fail-closed currency-source gate, the runtime half of the
334
329
  // narrowed `FormFieldDefinition` — an untyped JS consumer has no compiler to
335
330
  // stop it. An inline form screen has no entity, so a money field there can't
336
331
  // borrow `entity.defaultCurrency`: without a declared source the renderer
@@ -454,7 +449,7 @@ function validateInlineFormNavTargets(feature, screenId, screen, kind, lookups)
454
449
  export function validateInlineFormScreen(feature, screenId, screen, kind, allWriteHandlerQns, lookups, featureMap) {
455
450
  validateWriteHandlerRegistered(feature.name, screenId, kind, screen.handler, allWriteHandlerQns);
456
451
  // Only a secretMint's own mint step may skip fields/sections entirely
457
- // (input-less mint, fw#2838) — actionForm always needs at least one field.
452
+ // (input-less mint) — actionForm always needs at least one field.
458
453
  const allowEmptyMintForm = kind === "secretMint";
459
454
  const fieldNames = validateFormFieldsMap(feature.name, screenId, kind, screen.fields, allowEmptyMintForm);
460
455
  validateFormLayoutSections(feature.name, screenId, kind, screen.layout, fieldNames, screen.fields, allowEmptyMintForm && fieldNames.size === 0);
@@ -472,14 +467,14 @@ export function validateInlineFormScreen(feature, screenId, screen, kind, allWri
472
467
  }
473
468
  validateInlineFormNavTargets(feature, screenId, screen, kind, lookups);
474
469
  }
475
- // SecretMintConfirmStep (fw#2838): an optional proof-of-receipt form rendered
470
+ // SecretMintConfirmStep: an optional proof-of-receipt form rendered
476
471
  // on the reveal card in place of the bare acknowledge button (TOTP enrollment:
477
472
  // scan the code, then enter one). Unlike the mint step, confirm always needs
478
473
  // at least one field — a step whose only job is proving receipt with no input
479
474
  // would be meaningless — and its layout is a single plain form, never a
480
475
  // wizard/tabs flow and never a persisted draft: a one-time reveal-confirm is
481
476
  // not a multi-step flow, and persisting a draft of it would re-open the leak
482
- // fw#2548 closed (a lingering copy of the just-revealed secret in storage).
477
+ // (a lingering copy of the just-revealed secret in storage).
483
478
  export function validateSecretMintConfirm(feature, screenId, screen, allWriteHandlerQns) {
484
479
  const confirm = screen.confirm;
485
480
  // skip: the screen declares no confirm step — nothing to validate.
@@ -0,0 +1,3 @@
1
+ import type { ConfigEditScreenDefinition } from "../types/screen.ts";
2
+ import { type ScreenValidationContext } from "./screens-kind-shared.ts";
3
+ export declare function validateConfigEditScreen(ctx: ScreenValidationContext, screenId: string, screen: ConfigEditScreenDefinition): void;
@@ -0,0 +1,88 @@
1
+ import { isEncryptedAtRest } from "../config-helpers.js";
2
+ import { isExtensionEditSection, normalizeEditField, sectionFieldSpecs, } from "../screen-helpers.js";
3
+ import { isWriteOnlyField, validateConfigEditSecretField, validateFieldsXorGroups, validateFormSelectOptions, validateWizardLayout, } from "./screens-forms.js";
4
+ import { assertExtensionSectionHasComponent, rejectEmptySections, rejectProjectionDetailOnlySection, } from "./screens-kind-shared.js";
5
+ function validateConfigEditFields(featureName, screenId, screen, fieldNames) {
6
+ if (fieldNames.size === 0) {
7
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (configEdit) has empty fields map — ` +
8
+ `declare at least one field.`);
9
+ }
10
+ for (const [fname, fdef] of Object.entries(screen.fields)) {
11
+ const fieldType = fdef.type;
12
+ if (typeof fieldType !== "string" || fieldType.length === 0) {
13
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (configEdit) field "${fname}" has no ` +
14
+ `\`type\` set. Each field must declare a type (e.g. "text", "number", "select").`);
15
+ }
16
+ validateFormSelectOptions(featureName, screenId, "configEdit", fname, fdef, fieldNames);
17
+ }
18
+ }
19
+ function validateConfigEditFieldsSection(featureName, screenId, fieldNames, section) {
20
+ rejectProjectionDetailOnlySection(featureName, screenId, "configEdit", section);
21
+ validateFieldsXorGroups(`[Feature ${featureName}] Screen "${screenId}" (configEdit)`, section);
22
+ for (const fieldSpec of sectionFieldSpecs(section)) {
23
+ const normalized = normalizeEditField(fieldSpec);
24
+ if (!fieldNames.has(normalized.field)) {
25
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (configEdit) layout references unknown ` +
26
+ `field "${normalized.field}". Known fields: ${[...fieldNames].sort().join(", ")}`);
27
+ }
28
+ }
29
+ }
30
+ function validateConfigEditSection(featureName, screenId, fieldNames, section) {
31
+ if (isExtensionEditSection(section)) {
32
+ assertExtensionSectionHasComponent(featureName, screenId, "configEdit", section);
33
+ }
34
+ else {
35
+ validateConfigEditFieldsSection(featureName, screenId, fieldNames, section);
36
+ }
37
+ }
38
+ // configKeys: every fieldName must have a mapping entry, and every qualified
39
+ // key must exist in the registry.
40
+ function validateConfigEditFieldKey(ctx, screenId, screen, fname) {
41
+ const secretQn = screen.secretKeys?.[fname];
42
+ if (secretQn !== undefined) {
43
+ validateConfigEditSecretField(ctx.feature.name, screen, fname, secretQn, ctx.featureMap);
44
+ }
45
+ else {
46
+ validateConfigKeyMapping(ctx, screenId, screen, fname);
47
+ }
48
+ }
49
+ function validateConfigKeyMapping(ctx, screenId, screen, fname) {
50
+ const featureName = ctx.feature.name;
51
+ const qualified = screen.configKeys[fname];
52
+ if (qualified === undefined) {
53
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (configEdit) field "${fname}" has ` +
54
+ `no entry in the configKeys map. Every declared field needs a mapping to a ` +
55
+ `qualified config key (\`<feature>:config:<short>\`). Add one to configKeys.`);
56
+ }
57
+ const configKeyDef = ctx.configKeyDefsByQn.get(qualified);
58
+ if (configKeyDef === undefined) {
59
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (configEdit) field "${fname}" → ` +
60
+ `Config key "${qualified}" is not declared in any feature registry. Typo? ` +
61
+ `Expected format: "<feature>:config:<short>". Known keys: ${[...ctx.configKeyDefsByQn.keys()].sort().join(", ") || "(none)"}`);
62
+ }
63
+ if (isWriteOnlyField(screen.fields[fname]) && !isEncryptedAtRest(configKeyDef)) {
64
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (configEdit) field "${fname}" declares writeOnly ` +
65
+ `but config key "${qualified}" is not encrypted at rest — writeOnly on a configEdit field ` +
66
+ `only hides a stored secret (declare the key with encrypted: true or backing: "secrets").`);
67
+ }
68
+ }
69
+ // Validate configEdit layout and fields like actionForm, then verify that
70
+ // every qualified config key is registered and its scope matches the key.
71
+ export function validateConfigEditScreen(ctx, screenId, screen) {
72
+ const featureName = ctx.feature.name;
73
+ const fieldNames = new Set(Object.keys(screen.fields));
74
+ validateConfigEditFields(featureName, screenId, screen, fieldNames);
75
+ rejectEmptySections(featureName, screenId, "configEdit", screen.layout.sections.length);
76
+ for (const section of screen.layout.sections) {
77
+ validateConfigEditSection(featureName, screenId, fieldNames, section);
78
+ }
79
+ validateWizardLayout(featureName, screenId, "configEdit", screen.layout, ctx.featureMap);
80
+ for (const fname of Object.keys(screen.secretKeys ?? {})) {
81
+ if (!fieldNames.has(fname)) {
82
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (configEdit) secretKeys names unknown field "${fname}".`);
83
+ }
84
+ }
85
+ for (const fname of fieldNames) {
86
+ validateConfigEditFieldKey(ctx, screenId, screen, fname);
87
+ }
88
+ }
@@ -0,0 +1,3 @@
1
+ import type { EntityEditScreenDefinition } from "../types/screen.ts";
2
+ import { type ScreenValidationContext } from "./screens-kind-shared.ts";
3
+ export declare function validateEntityEditScreen(ctx: ScreenValidationContext, screenId: string, screen: EntityEditScreenDefinition): void;
@@ -0,0 +1,55 @@
1
+ import { isExtensionEditSection, normalizeEditField, sectionFieldSpecs, } from "../screen-helpers.js";
2
+ import { validateRedirectTarget } from "./screens-actions.js";
3
+ import { validateEditFieldExtras, validateFieldFormRefs, validateFieldsXorGroups, validateFillActionTarget, validateNoWidgetRequiredField, validateWizardLayout, } from "./screens-forms.js";
4
+ import { assertExtensionSectionHasComponent, buildUnknownFieldMessage, rejectEmptySections, rejectProjectionDetailOnlySection, resolveScreenEntity, validateRecordHeaderAction, validateRecordSectionActions, } from "./screens-kind-shared.js";
5
+ function validateEntityEditField(featureName, screenId, entityName, entityDef, fieldNames, fieldSpec) {
6
+ const normalized = normalizeEditField(fieldSpec);
7
+ if (!fieldNames.has(normalized.field)) {
8
+ throw new Error(buildUnknownFieldMessage(featureName, screenId, normalized.field, entityName, fieldNames));
9
+ }
10
+ validateNoWidgetRequiredField(featureName, screenId, entityDef, normalized);
11
+ validateEditFieldExtras(featureName, screenId, entityDef, normalized);
12
+ const fieldWhere = `[Feature ${featureName}] Screen "${screenId}" (entityEdit) field "${normalized.field}"`;
13
+ validateFieldFormRefs(fieldWhere, normalized, fieldNames);
14
+ validateFillActionTarget(fieldWhere, normalized, entityDef.fields[normalized.field]);
15
+ }
16
+ function validateEntityEditSection(ctx, screenId, screen, entityDef, fieldNames, section) {
17
+ const featureName = ctx.feature.name;
18
+ validateRecordSectionActions(ctx, screenId, "entityEdit", section);
19
+ if (isExtensionEditSection(section)) {
20
+ assertExtensionSectionHasComponent(featureName, screenId, "entityEdit", section);
21
+ }
22
+ else {
23
+ rejectProjectionDetailOnlySection(featureName, screenId, "entityEdit", section);
24
+ validateFieldsXorGroups(`[Feature ${featureName}] Screen "${screenId}" (entityEdit)`, section);
25
+ for (const fieldSpec of sectionFieldSpecs(section)) {
26
+ validateEntityEditField(featureName, screenId, screen.entity, entityDef, fieldNames, fieldSpec);
27
+ }
28
+ }
29
+ }
30
+ export function validateEntityEditScreen(ctx, screenId, screen) {
31
+ const featureName = ctx.feature.name;
32
+ const entityDef = resolveScreenEntity(ctx, screenId, screen.entity);
33
+ const fieldNames = new Set(Object.keys(entityDef.fields));
34
+ rejectEmptySections(featureName, screenId, "entityEdit", screen.layout.sections.length);
35
+ for (const section of screen.layout.sections) {
36
+ validateEntityEditSection(ctx, screenId, screen, entityDef, fieldNames, section);
37
+ }
38
+ validateWizardLayout(featureName, screenId, "entityEdit", screen.layout, ctx.featureMap);
39
+ for (const action of screen.actions ?? []) {
40
+ validateRecordHeaderAction(ctx, screenId, "entityEdit", screen.entity, action);
41
+ }
42
+ if (screen.recordTitleField !== undefined && !fieldNames.has(screen.recordTitleField)) {
43
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (entityEdit) recordTitleField ` +
44
+ `"${screen.recordTitleField}" is not a field of entity "${screen.entity}" ` +
45
+ `(known: ${[...fieldNames].sort().join(", ")}).`);
46
+ }
47
+ if (typeof screen.readOnly === "object" && !fieldNames.has(screen.readOnly.field)) {
48
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (entityEdit) readOnly condition field ` +
49
+ `"${screen.readOnly.field}" is not a field of entity "${screen.entity}" ` +
50
+ `(known: ${[...fieldNames].sort().join(", ")}).`);
51
+ }
52
+ if (screen.redirect !== undefined) {
53
+ validateRedirectTarget(ctx.feature, screenId, "entityEdit", screen.redirect, ctx);
54
+ }
55
+ }
@@ -0,0 +1,3 @@
1
+ import type { EntityListScreenDefinition } from "../types/screen.ts";
2
+ import { type ScreenValidationContext } from "./screens-kind-shared.ts";
3
+ export declare function validateEntityListScreen(ctx: ScreenValidationContext, screenId: string, screen: EntityListScreenDefinition): void;
@@ -0,0 +1,228 @@
1
+ import { isListFilterAllowed } from "../../db/entity-field-encryption.js";
2
+ import { rowMetaFieldNames } from "../../db/table-builder.js";
3
+ import { qualifyEntityName } from "../qualified-name.js";
4
+ import { getAllowedFilterOps, isFieldFilterable } from "../screen-filter-ops.js";
5
+ import { normalizeListColumn } from "../screen-helpers.js";
6
+ import { LIST_ROW_META_COLUMN_NAMES } from "./entity-list-screens.js";
7
+ import { assertWriteHandlerRegistered, rejectRedirectOnListRowAction, resolveRowActionNavigateTarget, validateActionFieldRefs, validateAtMostOneRowClick, validateDrawerTargetAction, validateRowActionNavigateParams, } from "./screens-actions.js";
8
+ import { buildUnknownFieldMessage, listActionContext, rejectEmptyColumns, rejectNonArrayInFilter, resolveScreenEntity, validateListColumnRendering, } from "./screens-kind-shared.js";
9
+ import { validateRelatedListSection, validateRowActionTarget } from "./screens-lists.js";
10
+ function resolveEntityListFields(ctx, screenId, entityName) {
11
+ const entityDef = resolveScreenEntity(ctx, screenId, entityName);
12
+ const fieldNames = new Set(Object.keys(entityDef.fields));
13
+ // List columns may also name a read-time derived field (not a stored
14
+ // column). Allowed for display; deliberately NOT added to `fieldNames`, so
15
+ // defaultSort/filter on a derived field still fails — server-side sort over
16
+ // a non-column is a silent no-op (see DerivedFieldDef).
17
+ const columnFieldNames = entityDef.derivedFields !== undefined
18
+ ? new Set([...fieldNames, ...Object.keys(entityDef.derivedFields)])
19
+ : fieldNames;
20
+ const rowMeta = rowMetaFieldNames(entityDef.softDelete ?? false);
21
+ return { entityDef, fieldNames, columnFieldNames, rowMeta };
22
+ }
23
+ function validateEntityListColumns(ctx, screenId, screen, fields) {
24
+ const featureName = ctx.feature.name;
25
+ rejectEmptyColumns(featureName, screenId, "entityList", screen.columns.length);
26
+ for (const col of screen.columns) {
27
+ const normalized = normalizeListColumn(col);
28
+ // A row-meta column (id/tenantId/version/insertedAt/modifiedAt/...) is a
29
+ // real DB column without being a declared entity field — e.g. a
30
+ // SystemAdmin cross-tenant list exposing tenantId. Accept it like a
31
+ // regular field name — but only the columns computeListViewModel can
32
+ // actually render (LIST_ROW_META_COLUMN_NAMES), not the wider
33
+ // softDelete-aware rowMeta set used for payload/visible refs.
34
+ //
35
+ // A virtual presentational column (drawn by a columnRenderer component
36
+ // from the row, e.g. tag chips) needs BOTH a label AND a renderer —
37
+ // renderer is what actually makes it "virtual" (label alone still
38
+ // needs list.ts's virtual-branch to have something to draw). A column
39
+ // with neither matching a real field/row-meta column NOR a renderer is
40
+ // a typo worth failing the boot.
41
+ if (!fields.columnFieldNames.has(normalized.field) &&
42
+ !LIST_ROW_META_COLUMN_NAMES.has(normalized.field) &&
43
+ !(normalized.label !== undefined && normalized.renderer !== undefined)) {
44
+ throw new Error(buildUnknownFieldMessage(featureName, screenId, normalized.field, screen.entity, fields.columnFieldNames));
45
+ }
46
+ validateListColumnRendering(ctx, screenId, "entityList", normalized);
47
+ }
48
+ }
49
+ function validateEntityListDefaultSort(featureName, screenId, defaultSort, fields) {
50
+ const sortField = defaultSort.field;
51
+ if (!fields.fieldNames.has(sortField)) {
52
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (entityList) defaultSort references unknown ` +
53
+ `field "${sortField}". Known fields: ${[...fields.fieldNames].sort().join(", ")}`);
54
+ }
55
+ // `sortable: true` is required so the UI never sorts on a column that has
56
+ // no server-side DB index or that the schema deliberately keeps unsortable
57
+ // (audit fields, computed values). `sortable` currently
58
+ // exists only on TextFieldDef; the `in` narrowing also permits other
59
+ // field types without an explicit flag, which is acceptable because
60
+ // Number/Date fields are naturally sortable. Authors can mark them
61
+ // sortable when their field type supports it.
62
+ const fieldDef = fields.entityDef.fields[sortField];
63
+ const isSortable = fieldDef !== undefined && "sortable" in fieldDef && fieldDef.sortable === true;
64
+ if (!isSortable) {
65
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (entityList) defaultSort.field "${sortField}" ` +
66
+ `is not sortable. Set sortable: true on the field definition or pick another field.`);
67
+ }
68
+ }
69
+ // Screen filters are checked in three layers:
70
+ // 1) The field exists on the entity; a typo would otherwise produce an
71
+ // empty list instead of a clear boot failure.
72
+ // 2) The field has `filterable: true`, preventing accidental filtering
73
+ // of audit, computed, or encrypted fields.
74
+ // 3) The operation matches the field type, so lt/gt on text fields
75
+ // fails at boot instead of causing surprising runtime behavior.
76
+ // Additionally, "in" requires a readonly array.
77
+ function validateEntityListFilter(featureName, screenId, filter, fields) {
78
+ const filterField = filter.field;
79
+ if (!fields.fieldNames.has(filterField)) {
80
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (entityList) filter references unknown ` +
81
+ `field "${filterField}". Known fields: ${[...fields.fieldNames].sort().join(", ")}`);
82
+ }
83
+ if (!isListFilterAllowed(fields.entityDef, filterField, filter)) {
84
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (entityList) filter references field ` +
85
+ `"${filterField}" which is encrypted, personal or secret, so the list handler rejects ` +
86
+ `this filter at runtime. Only op "eq" works on a personal field with find: "exact" or ` +
87
+ `"fuzzy"; otherwise drop the filter or filter on another field.`);
88
+ }
89
+ const fieldDef = fields.entityDef.fields[filterField];
90
+ if (fieldDef !== undefined && !isFieldFilterable(fieldDef)) {
91
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (entityList) filter references field ` +
92
+ `"${filterField}" which is not filterable. Set filterable: true on the field ` +
93
+ `definition or pick another field.`);
94
+ }
95
+ if (fieldDef !== undefined) {
96
+ const allowedOps = getAllowedFilterOps(fieldDef);
97
+ if (!allowedOps.includes(filter.op)) {
98
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (entityList) filter.op ` +
99
+ `"${filter.op}" is not allowed on field "${filterField}" ` +
100
+ `(type "${fieldDef.type}"). Allowed ops: ${allowedOps.join(", ") || "(none)"}.`);
101
+ }
102
+ }
103
+ rejectNonArrayInFilter(featureName, screenId, "entityList", filter);
104
+ }
105
+ // The renderer's default-entityId fallback (row["id"]) only fires for a
106
+ // same-feature entityEdit target — it can't safely guess the id for a screen
107
+ // owned by a different feature. Cross-feature + entityEdit therefore MUST set
108
+ // an explicit entityId, or the edit screen silently opens with no entity
109
+ // context at runtime. Entity-targets are exempt: the renderer always supplies
110
+ // an id for them (explicit entityId, else row["id"]), regardless of which
111
+ // feature the resolved detailFor screen belongs to.
112
+ function rejectCrossFeatureEditTargetWithoutEntityId(featureName, screenId, action, target) {
113
+ if (action.screen !== undefined &&
114
+ target !== undefined &&
115
+ target.featureName !== featureName &&
116
+ target.screen.type === "entityEdit" &&
117
+ action.entityId === undefined) {
118
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (entityList) rowAction "${action.id}" ` +
119
+ `navigates cross-feature to entityEdit screen "${action.screen}" (feature ` +
120
+ `"${target.featureName}") without an explicit entityId field — the renderer's ` +
121
+ `same-feature row["id"] fallback does not apply across features. Set entityId to ` +
122
+ `the row field that names the target entity's id.`);
123
+ }
124
+ }
125
+ // Pin rowActions at boot: a navigate target must exist and a writeHandler QN
126
+ // must be registered. Typos would otherwise only surface on the first click as
127
+ // "Screen not found" or a 404.
128
+ function validateEntityListRowAction(ctx, screenId, screen, fields, action) {
129
+ const featureName = ctx.feature.name;
130
+ rejectRedirectOnListRowAction(featureName, screenId, "entityList", action);
131
+ if (action.kind === "navigate") {
132
+ const target = resolveRowActionNavigateTarget(featureName, screenId, "entityList", "rowAction", action, ctx.allScreenQns, ctx.navTargetShortIds, ctx.screensByShortId, ctx.detailForScreens);
133
+ rejectCrossFeatureEditTargetWithoutEntityId(featureName, screenId, action, target);
134
+ // params only have a reader in actionForm and entityEdit-CREATE; on
135
+ // projectionDetail/dashboard/configEdit/entityEdit-update they are
136
+ // silently ignored at runtime. `custom` is deliberately exempt: it renders
137
+ // an app-registered component the framework has no visibility into, so
138
+ // flagging it would be a false positive on working code.
139
+ //
140
+ // Whether an entityEdit target lands in create or update mode is decided
141
+ // by the same rule the renderer's runNavigate() uses: an explicit
142
+ // entityId always forces update mode, and (absent an explicit entityId) a
143
+ // same-entity target gets row["id"] auto-injected — only a cross-entity
144
+ // target with no explicit entityId reaches create.
145
+ validateRowActionNavigateParams(featureName, screenId, "entityList", screen.entity, action, target);
146
+ }
147
+ else {
148
+ validateRowActionTarget(listActionContext(ctx, screenId, "entityList", screen.entity, `Screen "${screenId}" (entityList)`), "rowAction", "rowAction", action);
149
+ }
150
+ validateActionFieldRefs(featureName, screenId, "rowAction", action.id, action, fields.fieldNames, fields.rowMeta);
151
+ }
152
+ // toolbarActions, analogous to rowActions: misspelled navigate targets and
153
+ // unregistered writeHandler QNs fail at boot instead of on the first click.
154
+ function validateEntityListToolbarAction(ctx, screenId, fields, action) {
155
+ const featureName = ctx.feature.name;
156
+ if (action.kind === "navigate") {
157
+ const candidateQn = qualifyEntityName(featureName, "screen", action.screen);
158
+ if (!ctx.allScreenQns.has(candidateQn) && !ctx.navTargetShortIds.has(action.screen)) {
159
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (entityList) toolbarAction "${action.id}" ` +
160
+ `navigate-target "${action.screen}" does not resolve to a registered screen in any feature. Use "<feature>:screen:<id>" of an existing screen.`);
161
+ }
162
+ if (action.tab !== undefined) {
163
+ resolveRowActionNavigateTarget(featureName, screenId, "entityList", "toolbarAction", action, ctx.allScreenQns, ctx.navTargetShortIds, ctx.screensByShortId, ctx.detailForScreens);
164
+ }
165
+ }
166
+ else if (action.kind === "drawer") {
167
+ validateDrawerTargetAction(featureName, screenId, "entityList", "toolbarAction", action, ctx.feature.screens);
168
+ }
169
+ else {
170
+ assertWriteHandlerRegistered(featureName, `Screen "${screenId}" (entityList) toolbarAction "${action.id}"`, action.handler, ctx.allWriteHandlerQns);
171
+ }
172
+ validateActionFieldRefs(featureName, screenId, "toolbarAction", action.id, action, fields.fieldNames, fields.rowMeta);
173
+ }
174
+ function validateEntityListExpandableRow(ctx, screenId, expandableRow, fields) {
175
+ validateRelatedListSection({
176
+ featureName: ctx.feature.name,
177
+ featureMap: ctx.featureMap,
178
+ screenId,
179
+ screenType: "entityList expandableRow",
180
+ where: `Screen "${screenId}" (entityList) expandableRow "${expandableRow.title}"`,
181
+ section: expandableRow,
182
+ screens: ctx.feature.screens,
183
+ allWriteHandlerQns: ctx.allWriteHandlerQns,
184
+ allScreenQns: ctx.allScreenQns,
185
+ navTargetShortIds: ctx.navTargetShortIds,
186
+ screensByShortId: ctx.screensByShortId,
187
+ detailForScreens: ctx.detailForScreens,
188
+ });
189
+ // Toolbar actions of the expansion evaluate against the host row.
190
+ for (const action of expandableRow.toolbarActions ?? []) {
191
+ validateActionFieldRefs(ctx.feature.name, screenId, "toolbarAction", action.id, action, fields.fieldNames, fields.rowMeta);
192
+ }
193
+ }
194
+ export function validateEntityListScreen(ctx, screenId, screen) {
195
+ const featureName = ctx.feature.name;
196
+ const fields = resolveEntityListFields(ctx, screenId, screen.entity);
197
+ validateEntityListColumns(ctx, screenId, screen, fields);
198
+ // Validate pagination, sorting, and search at boot so authoring errors
199
+ // fail before runtime debugging of empty or incorrectly sorted lists.
200
+ if (screen.pageSize !== undefined && screen.pageSize <= 0) {
201
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (entityList) has pageSize=${screen.pageSize} — ` +
202
+ `must be a positive integer.`);
203
+ }
204
+ if (screen.defaultSort !== undefined) {
205
+ validateEntityListDefaultSort(featureName, screenId, screen.defaultSort, fields);
206
+ }
207
+ if (screen.createScreen !== undefined &&
208
+ !Object.hasOwn(ctx.feature.screens, screen.createScreen)) {
209
+ throw new Error(`[Feature ${featureName}] Screen "${screenId}" (entityList) createScreen ` +
210
+ `"${screen.createScreen}" does not resolve to a registered screen in this feature. ` +
211
+ `Known screens in this feature: ${Object.keys(ctx.feature.screens).sort().join(", ") || "(none)"}.`);
212
+ }
213
+ if (screen.filter !== undefined) {
214
+ validateEntityListFilter(featureName, screenId, screen.filter, fields);
215
+ }
216
+ if (screen.rowActions !== undefined) {
217
+ for (const action of screen.rowActions) {
218
+ validateEntityListRowAction(ctx, screenId, screen, fields, action);
219
+ }
220
+ validateAtMostOneRowClick(featureName, screenId, "entityList", screen.rowActions);
221
+ }
222
+ for (const action of screen.toolbarActions ?? []) {
223
+ validateEntityListToolbarAction(ctx, screenId, fields, action);
224
+ }
225
+ if (screen.expandableRow !== undefined) {
226
+ validateEntityListExpandableRow(ctx, screenId, screen.expandableRow, fields);
227
+ }
228
+ }
@@ -0,0 +1,3 @@
1
+ import type { ProjectionDetailScreenDefinition } from "../types/screen.ts";
2
+ import { type ScreenValidationContext } from "./screens-kind-shared.ts";
3
+ export declare function validateProjectionDetailScreen(ctx: ScreenValidationContext, screenId: string, screen: ProjectionDetailScreenDefinition): void;