@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,14 +1,14 @@
1
1
  /**
2
2
  * Type-safe transition graph. Wraps the underlying Map so callers don't
3
3
  * accidentally fall into the `transitions[from]?.includes(to)` footgun
4
- * (Object-Index-Access auf einer Map returnt undefined und der ganze
5
- * Check kollabiert silently). Stattdessen forciert die API explizite
6
- * Methoden — kein Map-shape mehr extern sichtbar.
4
+ * (indexing a Map like an object returns undefined and silently breaks the
5
+ * entire check). The API therefore enforces explicit methods, with no Map
6
+ * shape exposed externally.
7
7
  *
8
8
  * - `canTransition(from, to)` → boolean
9
- * - `allowedFrom(from)` → readonly string[] (leer wenn `from` unbekannt
10
- * oder terminaler Zustand)
11
- * - `assertTransition(from, to)` → wirft UnprocessableError(invalid_transition)
9
+ * - `allowedFrom(from)` → readonly string[] (empty when `from` is unknown
10
+ * or terminal)
11
+ * - `assertTransition(from, to)` → throws UnprocessableError(invalid_transition)
12
12
  */
13
13
  export type TransitionGraph<TStates extends string = string> = {
14
14
  readonly canTransition: (from: TStates, to: TStates) => boolean;
@@ -17,8 +17,8 @@ export type TransitionGraph<TStates extends string = string> = {
17
17
  };
18
18
  /**
19
19
  * Defines allowed state transitions. Returns a typed graph (see
20
- * TransitionGraph) — nicht eine Map. Damit ist `transitions[x]`-Zugriff
21
- * type-error statt silent undefined.
20
+ * TransitionGraph), not a Map. This makes `transitions[x]` access a
21
+ * type error instead of silently returning undefined.
22
22
  */
23
23
  export declare function defineTransitions<const TMap extends Record<string, readonly string[]>>(map: TMap): TransitionGraph<keyof TMap & string>;
24
24
  /**
@@ -26,9 +26,9 @@ export declare function defineTransitions<const TMap extends Record<string, read
26
26
  * reason="invalid_transition" if not — the 422 status lets the client
27
27
  * distinguish a logical rejection from a validation or auth failure.
28
28
  *
29
- * Convenience-Wrapper um `transitions.assertTransition(from, to)` —
30
- * existiert weil bestehende Aufrufer `guardTransition(graph, ...)`-Form
31
- * nutzen. Beides ist erlaubt; die Method-Form auf dem Graph ist die
32
- * idiomatische API für neuen Code.
29
+ * Convenience wrapper around `transitions.assertTransition(from, to)` —
30
+ * it exists because existing callers use the `guardTransition(graph, ...)`
31
+ * form. Both are supported; the graph method is the idiomatic API for new
32
+ * code.
33
33
  */
34
34
  export declare function guardTransition<TStates extends string>(transitions: TransitionGraph<TStates>, from: TStates, to: TStates): void;
@@ -1,8 +1,8 @@
1
1
  import { buildInvalidTransitionDetails, FrameworkReasons, UnprocessableError, } from "../errors/index.js";
2
2
  /**
3
3
  * Defines allowed state transitions. Returns a typed graph (see
4
- * TransitionGraph) — nicht eine Map. Damit ist `transitions[x]`-Zugriff
5
- * type-error statt silent undefined.
4
+ * TransitionGraph), not a Map. This makes `transitions[x]` access a
5
+ * type error instead of silently returning undefined.
6
6
  */
7
7
  export function defineTransitions(map) {
8
8
  const internal = new Map();
@@ -17,7 +17,7 @@ export function defineTransitions(map) {
17
17
  },
18
18
  assertTransition: (from, to) => {
19
19
  const set = internal.get(from);
20
- // skip: erlaubter Übergang — kein Throw, fall through.
20
+ // skip: allowed transition — no throw; fall through.
21
21
  if (set?.has(to) === true)
22
22
  return;
23
23
  const allowed = set ? [...set] : [];
@@ -33,10 +33,10 @@ export function defineTransitions(map) {
33
33
  * reason="invalid_transition" if not — the 422 status lets the client
34
34
  * distinguish a logical rejection from a validation or auth failure.
35
35
  *
36
- * Convenience-Wrapper um `transitions.assertTransition(from, to)` —
37
- * existiert weil bestehende Aufrufer `guardTransition(graph, ...)`-Form
38
- * nutzen. Beides ist erlaubt; die Method-Form auf dem Graph ist die
39
- * idiomatische API für neuen Code.
36
+ * Convenience wrapper around `transitions.assertTransition(from, to)` —
37
+ * it exists because existing callers use the `guardTransition(graph, ...)`
38
+ * form. Both are supported; the graph method is the idiomatic API for new
39
+ * code.
40
40
  */
41
41
  // @wrapper-known semantic-alias
42
42
  export function guardTransition(transitions, from, to) {
@@ -13,14 +13,15 @@
13
13
  // was needed to satisfy TS narrowing on a property-access; passing the
14
14
  // arg through a function-call achieves the same narrowing trivially via
15
15
  // the function-parameter binding.
16
- //
17
- // Followup #10 (closed at the M.1.6 cleanup-pass).
16
+ function isResolverFunction(arg) {
17
+ return typeof arg === "function";
18
+ }
18
19
  /**
19
20
  * Resolve a required StepResolver — either a static value or a function.
20
21
  * Throws if `arg` is undefined; caller must guarantee presence.
21
22
  */
22
23
  export function resolveRequired(arg, ctx) {
23
- if (typeof arg === "function") {
24
+ if (isResolverFunction(arg)) {
24
25
  return arg(ctx);
25
26
  }
26
27
  return arg;
@@ -24,12 +24,12 @@
24
24
  // ```
25
25
  //
26
26
  // M.1.6 supports `concurrency: 1` only (sequential). Concurrent
27
- // execution (Promise.all-style) is Followup #12 — TX-sharing,
27
+ // execution (Promise.all-style) requires its own audit cycle: TX-sharing,
28
28
  // AbortSignal-cancellation, and lifecycle-hook ordering each warrant
29
- // their own audit cycle (Q16).
29
+ // separate review.
30
30
  //
31
- // Q15: `as` is required — without it, the current item is unreachable
32
- // from the sub-pipeline's resolvers. Q12: r.step.return inside `do` is
31
+ // `as` is required — without it, the current item is unreachable
32
+ // from the sub-pipeline's resolvers. `r.step.return` inside `do` is
33
33
  // rejected at build time (would trigger the discriminated-union
34
34
  // TData-Inference trap).
35
35
  import { defineStep } from "../define-step.js";
@@ -6,7 +6,7 @@
6
6
  // r.requires.projection allowlist, must NOT be a registered
7
7
  // r.entity-aggregate-table. Skips the same set of framework-protections
8
8
  // (lifecycle hooks, field-access, audit-trail, etc.) — see
9
- // step-vocabulary.md "Was unsafeProjection.* überspringt".
9
+ // step-vocabulary.md "What unsafeProjection.* skips".
10
10
  //
11
11
  // Convention (not enforced): most legitimate read-side deletions are
12
12
  // downstream of an aggregate event (e.g. delete-user → cascading
@@ -2,13 +2,13 @@
2
2
  //
3
3
  // Idempotent on the supplied conflict-key columns (typically the
4
4
  // natural key + tenantId). Skips lifecycle hooks, field-access,
5
- // crypto-shredding, schema-versioning, audit-trail, read-access-log.
6
- // See "Was unsafeProjection.* überspringt" in
5
+ // crypto-shredding, schema-versioning, audit-trail, and read-access-log.
6
+ // See "What unsafeProjection.* skips" in
7
7
  // kumiko-platform/docs/archive/plans/architecture/intern/step-vocabulary.md.
8
8
  //
9
9
  // Use only on tables explicitly declared via r.requires.projection in
10
- // the owning feature. Aggregate-tables (registered via r.entity) are
11
- // rejected by boot-validation — domain mutation MUST go through
10
+ // the owning feature. Aggregate tables (registered via r.entity) are
11
+ // rejected by boot validation — domain mutation MUST go through
12
12
  // r.step.aggregate.*.
13
13
  import { extractTableName } from "../../db/index.js";
14
14
  import { executeRawQuery } from "../../db/queries/raw-sql.js";
@@ -16,10 +16,10 @@
16
16
  // resultKey is `args.event` itself (the branded event-type string) rather
17
17
  // than a new `name` argument — a workflow only ever has one `awaits` entry
18
18
  // per event type, so `ctx.steps[awaits.someKey]` is already a unique,
19
- // intention-revealing handle. resume-run.write.ts (framework#2513 Phase 3b)
20
- // is the only producer of this key's value: the step itself always
21
- // suspends (never returns a real result), so a subsequent step only ever
22
- // sees the matched event's payload after a resume seeds it in.
19
+ // intention-revealing handle. resume-run.write.ts is the only producer of
20
+ // this key's value: the step itself always suspends (never returns a real
21
+ // result), so a subsequent step only ever sees the matched event's payload
22
+ // after a resume seeds it in.
23
23
  import { defineStep } from "../define-step.js";
24
24
  import { addDuration } from "./_duration-utils.js";
25
25
  import { resolveOptional, resolveRequired } from "./_resolver-utils.js";
@@ -15,7 +15,7 @@ const triggerEventRefSchema = z.object({
15
15
  aggregateId: z.string(),
16
16
  version: z.number().int(),
17
17
  });
18
- // Optional on every step event: runs started before the Q7 fingerprint existed
18
+ // Optional on every step event: runs started before the fingerprint existed
19
19
  // have none.
20
20
  const stepContext = {
21
21
  workflowName: z.string(),
@@ -29,8 +29,8 @@ const WORKFLOW_SYSTEM_EVENT_SCHEMAS = [
29
29
  z.object({
30
30
  workflowName: z.string(),
31
31
  triggerEventType: z.string(),
32
- // Current streams reference the trigger event; streams written before
33
- // #3474 carry a copy of its payload instead.
32
+ // Current streams reference the trigger event; older streams carry a copy of
33
+ // its payload instead.
34
34
  triggerEventRef: triggerEventRefSchema.optional(),
35
35
  triggerPayload: z.unknown().optional(),
36
36
  definitionFingerprint: z.string().optional(),
@@ -11,42 +11,79 @@
11
11
  // gone it also drops every action, redirect, nav and workspace member that
12
12
  // pointed at them. The one reference it does not resolve is a nav `parent`
13
13
  // pointing at a missing nav; that is pruned here.
14
+ import { createFallbackLogger, scrubLogText } from "../logging/index.js";
14
15
  import { resolveScreenTargetQn } from "./boot-validator/screens.js";
15
16
  import { isToggleableFeature } from "./effective-features.js";
17
+ import { isPureGroupingNav } from "./project-app-schema-for-roles.js";
16
18
  import { isValidQn, qualifyEntityName } from "./qualified-name.js";
19
+ const schemaLogger = createFallbackLogger("app-schema");
17
20
  // Sorted, so the result doubles as a stable cache key. The trial gate is
18
21
  // consulted like the dispatcher's feature gate does, but only for toggleable
19
22
  // features: a trial can only unlock tier (toggleable) features, so a
20
- // cascade-disabled always-on feature needs no gate call.
23
+ // cascade-disabled always-on feature needs no gate call. A failing gate counts
24
+ // as "not unlocked" (the handlers answer 403 then anyway), so a gate outage
25
+ // hides the add-on instead of taking the whole schema endpoint down.
21
26
  export async function resolveDisabledFeatureNames(registry, effectiveFeatures, tenantId) {
22
27
  const enabled = effectiveFeatures(tenantId);
23
- const disabled = [];
24
- for (const [featureName, feature] of registry.features) {
25
- if (enabled.has(featureName))
26
- continue;
27
- if (isToggleableFeature(feature) &&
28
- (await effectiveFeatures.trialGate?.(tenantId, featureName))) {
29
- continue;
28
+ const trialGate = effectiveFeatures.trialGate;
29
+ const disabled = [...registry.features].filter(([featureName]) => !enabled.has(featureName));
30
+ const unlockedByTrial = await Promise.all(disabled.map(async ([featureName, feature]) => {
31
+ if (trialGate === undefined || !isToggleableFeature(feature))
32
+ return false;
33
+ try {
34
+ return await trialGate(tenantId, featureName);
30
35
  }
31
- disabled.push(featureName);
32
- }
33
- return disabled.sort();
36
+ catch (error) {
37
+ schemaLogger.warn("trial gate failed, treating feature as not unlocked", {
38
+ featureName,
39
+ error: scrubLogText(error instanceof Error ? error.message : String(error)),
40
+ });
41
+ return false;
42
+ }
43
+ }));
44
+ return disabled
45
+ .filter((_, index) => !unlockedByTrial[index])
46
+ .map(([featureName]) => featureName)
47
+ .sort();
34
48
  }
35
49
  function navParentQn(featureName, parent) {
36
50
  return isValidQn(parent) ? parent : qualifyEntityName(featureName, "nav", parent);
37
51
  }
38
- // Navs of enabled features whose parent chain ends in a removed nav.
52
+ function navQnsByParent(features) {
53
+ const childrenByParent = new Map();
54
+ for (const feature of features) {
55
+ for (const nav of feature.navs ?? []) {
56
+ if (nav.parent === undefined)
57
+ continue;
58
+ const parentQn = navParentQn(feature.featureName, nav.parent);
59
+ const children = childrenByParent.get(parentQn) ?? [];
60
+ children.push(qualifyEntityName(feature.featureName, "nav", nav.id));
61
+ childrenByParent.set(parentQn, children);
62
+ }
63
+ }
64
+ return childrenByParent;
65
+ }
66
+ // Navs of enabled features whose parent chain ends in a removed nav, plus pure grouping
67
+ // navs whose children are all removed (an empty section the role projection cannot drop,
68
+ // because it only drops groups that still have children).
39
69
  function collectOrphanedNavQns(features, removedNavQns) {
40
70
  const orphaned = new Set();
71
+ const childrenByParent = navQnsByParent(features);
41
72
  let changed = true;
42
73
  while (changed) {
43
74
  changed = false;
44
75
  for (const feature of features) {
45
76
  for (const nav of feature.navs ?? []) {
46
77
  const qn = qualifyEntityName(feature.featureName, "nav", nav.id);
47
- if (removedNavQns.has(qn) || nav.parent === undefined)
78
+ if (removedNavQns.has(qn))
48
79
  continue;
49
- if (removedNavQns.has(navParentQn(feature.featureName, nav.parent))) {
80
+ const parentRemoved = nav.parent !== undefined &&
81
+ removedNavQns.has(navParentQn(feature.featureName, nav.parent));
82
+ const children = childrenByParent.get(qn) ?? [];
83
+ const emptiedGroup = isPureGroupingNav(nav) &&
84
+ children.length > 0 &&
85
+ children.every((childQn) => removedNavQns.has(childQn));
86
+ if (parentRemoved || emptiedGroup) {
50
87
  removedNavQns.add(qn);
51
88
  orphaned.add(qn);
52
89
  changed = true;
@@ -4,30 +4,32 @@ import type { RegistrarExtensionRegistration } from "./types/config.ts";
4
4
  import type { FeatureDefinition, Registry } from "./types/feature.ts";
5
5
  import type { TenantId } from "./types/identifiers.ts";
6
6
  /**
7
- * Extension-name unter dem ein tier-resolver-plugin im registry registriert
8
- * werden muss. Konstante damit `r.useExtension(TENANT_TIER_RESOLVER_EXT, ...)`
9
- * + framework's `getExtensionUsages(TENANT_TIER_RESOLVER_EXT)` typo-resistent
10
- * gegen den selben Wert prüfen.
7
+ * Extension name under which a tier-resolver plugin must be registered in the
8
+ * registry. The constant keeps `r.useExtension(TENANT_TIER_RESOLVER_EXT, ...)`
9
+ * and the framework's `getExtensionUsages(TENANT_TIER_RESOLVER_EXT)` aligned
10
+ * on the same value, preventing typos.
11
11
  */
12
12
  export declare const TENANT_TIER_RESOLVER_EXT = "tenantTierResolver";
13
13
  /**
14
- * Resolver-callback shape: synchron (dispatcher hot-path), per-tenant.
15
- * Returnt das effective feature-Set für den tenant. Implementations sollten
16
- * einen in-memory cache pflegen + per `r.entityHook` invalidieren.
14
+ * Resolver callback shape: synchronous (on the dispatcher's hot path), per
15
+ * tenant. Returns the tenant's effective feature set. Implementations should
16
+ * maintain an in-memory cache and invalidate it via `r.entityHook`.
17
17
  *
18
- * **System-context convention:** call mit SYSTEM_TENANT_ID erwartet die union
19
- * aller tier-features (siehe DispatcherOptions.effectiveFeatures doc-block).
18
+ * **System-context convention:** calls with SYSTEM_TENANT_ID must return the
19
+ * union of all tier features (see the DispatcherOptions.effectiveFeatures
20
+ * doc block).
20
21
  */
21
22
  export type EffectiveFeaturesResolver = ((tenantId: TenantId) => ReadonlySet<string>) & {
22
23
  /**
23
- * Optionaler Live-Trial-Gate, vom dispatcher-feature-gate NUR konsultiert
24
- * wenn der synchrone Resolver ein Feature als disabled meldet. Liest das
25
- * Signup-Datum des Tenants live (tenant.inserted_at) und returnt true wenn
26
- * der Tenant im Trial-Fenster ist UND das Feature zum Trial-Tier gehört.
27
- * Async + nicht im Boot-Cache, weil der Trial zeit-abgeleitet ist (ändert
28
- * sich zwischen Requests). Konsultiert an 3 Stellen: den 2 Dispatch-Gate-
29
- * Aufrufstellen (checkFeatureEnabled) UND ctx.hasFeature im Handler-Body
30
- * (fällt darauf zurück, wenn der synchrone Feature-Set-Check negativ ist).
24
+ * Optional live-trial gate, consulted by the dispatcher feature gate ONLY
25
+ * when the synchronous resolver reports a feature as disabled. Reads the
26
+ * tenant's signup date live (tenant.inserted_at) and returns true when the
27
+ * tenant is within the trial window AND the feature belongs to the trial
28
+ * tier. It is async and not part of the boot cache because the trial is
29
+ * time-dependent and can change between requests. Consulted at three
30
+ * locations: the two dispatch-gate call sites (checkFeatureEnabled) AND
31
+ * ctx.hasFeature in the handler body (as a fallback when the synchronous
32
+ * feature-set check is negative).
31
33
  */
32
34
  readonly trialGate?: TrialGate;
33
35
  };
@@ -36,10 +38,10 @@ export type EffectiveFeaturesResolver = ((tenantId: TenantId) => ReadonlySet<str
36
38
  */
37
39
  export type TrialGate = (tenantId: TenantId, featureName: string) => Promise<boolean>;
38
40
  /**
39
- * Plugin-shape für tier-resolver-extension. Plugins implementieren `build`
40
- * als boot-time factory: kriegen `db` + `registry` (post-stack-setup),
41
- * laden initial cache aus DB, returnen den synchronen resolver-callback
42
- * (optional mit angehängtem `trialGate`).
41
+ * Plugin shape for the tier-resolver extension. Plugins implement `build`
42
+ * as a boot-time factory: they receive `db` + `registry` (after stack setup),
43
+ * load the initial cache from the database, and return the synchronous
44
+ * resolver callback, optionally with an attached `trialGate`.
43
45
  */
44
46
  export type TierResolverPlugin = {
45
47
  readonly build: (deps: {
@@ -49,13 +51,13 @@ export type TierResolverPlugin = {
49
51
  }) => Promise<EffectiveFeaturesResolver>;
50
52
  };
51
53
  /**
52
- * Scan a composed feature-list for a `tenantTierResolver`-extension usage.
53
- * Single plugin assumption — multiple wären ambiguous (welcher resolver
54
- * gewinnt). Memory `feedback_no_options_without_need`: kein multi-merge-
55
- * pattern bis es echten Use-Case gibt.
54
+ * Scans a composed feature list for a `tenantTierResolver` extension usage.
55
+ * Only one plugin is supported because multiple plugins would be ambiguous:
56
+ * it would be unclear which resolver should win. No multi-merge pattern is
57
+ * needed until there is a real use case.
56
58
  *
57
- * Geteilter helper für runDevApp + runProdApp damit der Pickup-Pfad
58
- * bit-identisch ist (drift-resistent).
59
+ * Shared by runDevApp + runProdApp so the pickup path remains byte-for-byte
60
+ * identical and cannot drift.
59
61
  */
60
62
  /** Narrows a registration's untyped `options` bag to the tier-resolver
61
63
  * plugin shape — the only thing every implementation must provide is the
@@ -1,40 +1,40 @@
1
- // Sprint-8a Tier-Composition: framework-extension-point für per-tenant
1
+ // Tier composition: framework extension point for per-tenant
2
2
  // effective-feature resolution.
3
3
  //
4
- // **Pattern (analog mail-foundation / file-foundation):** ein Feature
5
- // declares `r.extendsRegistrar("tenantTierResolver")` und plugins
6
- // implementieren via `r.useExtension("tenantTierResolver", "id", { build })`.
4
+ // **Pattern (analogous to mail-foundation / file-foundation):** a feature
5
+ // declares `r.extendsRegistrar("tenantTierResolver")`, and plugins
6
+ // implement it via `r.useExtension("tenantTierResolver", "id", { build })`.
7
7
  //
8
- // **Auto-wiring im Framework:** runDevApp + runProdApp scannen das registry
9
- // nach genau diesem extension-name. Wenn ein plugin gefunden ist UND der
10
- // App-Author nicht selbst eine `effectiveFeatures`-callback gesetzt hat,
11
- // wird `plugin.build({ db, registry })` einmalig in onAfterSetup aufgerufen.
12
- // Der returned callback landet als `effectiveFeatures` im dispatcher.
8
+ // **Framework auto-wiring:** runDevApp + runProdApp scan the registry
9
+ // for this exact extension name. If a plugin is found AND the app author
10
+ // has not set an `effectiveFeatures` callback, `plugin.build({ db, registry })`
11
+ // is called once in onAfterSetup. The returned callback becomes
12
+ // `effectiveFeatures` in the dispatcher.
13
13
  //
14
- // **Warum dieser Pattern:** App-Author mountet `createTierEngineFeature(opts)`
15
- // und das war's — kein Late-Bound-Holder im run-config, kein
16
- // effectiveFeatures-callback wiren. Das Framework macht den Late-Bound-Trick
17
- // intern. Memory `feedback_alles_ist_ein_feature`: Tier-Composition ist
18
- // ein Feature, nicht ein Subsystem mit App-spezifischem Wiring.
14
+ // **Why this pattern:** the app author mounts
15
+ // `createTierEngineFeature(opts)` and is done—no late-bound holder in the
16
+ // run config and no need to wire an effectiveFeatures callback. The framework
17
+ // handles the late-binding internally. Tier composition is a feature, not a
18
+ // subsystem with app-specific wiring.
19
19
  //
20
- // **Apps ohne tier-engine:** wenn keine plugin registriert ist, framework
21
- // macht nichts — `effectiveFeatures` bleibt undefined, alle features sind on.
20
+ // **Apps without a tier engine:** if no plugin is registered, the framework
21
+ // does nothing—`effectiveFeatures` remains undefined, and all features are on.
22
22
  import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
23
23
  /**
24
- * Extension-name unter dem ein tier-resolver-plugin im registry registriert
25
- * werden muss. Konstante damit `r.useExtension(TENANT_TIER_RESOLVER_EXT, ...)`
26
- * + framework's `getExtensionUsages(TENANT_TIER_RESOLVER_EXT)` typo-resistent
27
- * gegen den selben Wert prüfen.
24
+ * Extension name under which a tier-resolver plugin must be registered in the
25
+ * registry. The constant keeps `r.useExtension(TENANT_TIER_RESOLVER_EXT, ...)`
26
+ * and the framework's `getExtensionUsages(TENANT_TIER_RESOLVER_EXT)` aligned
27
+ * on the same value, preventing typos.
28
28
  */
29
29
  export const TENANT_TIER_RESOLVER_EXT = "tenantTierResolver";
30
30
  /**
31
- * Scan a composed feature-list for a `tenantTierResolver`-extension usage.
32
- * Single plugin assumption — multiple wären ambiguous (welcher resolver
33
- * gewinnt). Memory `feedback_no_options_without_need`: kein multi-merge-
34
- * pattern bis es echten Use-Case gibt.
31
+ * Scans a composed feature list for a `tenantTierResolver` extension usage.
32
+ * Only one plugin is supported because multiple plugins would be ambiguous:
33
+ * it would be unclear which resolver should win. No multi-merge pattern is
34
+ * needed until there is a real use case.
35
35
  *
36
- * Geteilter helper für runDevApp + runProdApp damit der Pickup-Pfad
37
- * bit-identisch ist (drift-resistent).
36
+ * Shared by runDevApp + runProdApp so the pickup path remains byte-for-byte
37
+ * identical and cannot drift.
38
38
  */
39
39
  /** Narrows a registration's untyped `options` bag to the tier-resolver
40
40
  * plugin shape — the only thing every implementation must provide is the
@@ -61,7 +61,7 @@ function parentRefEdgesFrom(entityName, entity, allEntityNames) {
61
61
  //
62
62
  // A type can legitimately be reached by more than one edge — `note.vehicleId`
63
63
  // and `note.campaignId` are distinct, and collapsing them by entity name would
64
- // leave one edge's rows behind, the silent partial move #3088 exists to end.
64
+ // leave one edge's rows behind, causing a silent partial move.
65
65
  export function resolveTransferAdjacency(entities, rootEntityType) {
66
66
  const allEntityNames = [...entities.keys()];
67
67
  const byParent = new Map();
@@ -96,7 +96,7 @@ export function findOverDeepTransferChain(adjacency, onPath) {
96
96
  // skip: a cycle revisits a type already on this path, and schema depth
97
97
  // cannot measure how far it actually runs — that depends on the rows, not
98
98
  // the declaration. The mover carries this one instead, failing with
99
- // `transfer_graph_too_deep` when its rounds run out (#3131).
99
+ // `transfer_graph_too_deep` when its rounds run out.
100
100
  if (onPath.includes(edge.entityName))
101
101
  continue;
102
102
  const deeper = findOverDeepTransferChain(adjacency, [...onPath, edge.entityName]);
@@ -23,35 +23,17 @@
23
23
  // caught by this validator. A future lint-rule will enforce the contract
24
24
  // statically; today it lives in this comment + the StepBuilder doc.
25
25
  import { KUMIKO_META_SYMBOL } from "@cosmicdrift/kumiko-types/schema-table-types";
26
+ import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
27
+ import { resolveTableName } from "../db/entity-table-meta.js";
26
28
  import { schemaTableName } from "../db/table-introspection.js";
27
29
  import { getStep } from "./define-step.js";
28
30
  import { buildPipelineSteps } from "./pipeline.js";
31
+ import { walkStepInstances } from "./walk-step-instances.js";
29
32
  // Listed step-kinds whose `args.table` must be in the owning feature's
30
33
  // r.requires.projection allowlist. Extend as further unsafeProjection.*
31
34
  // steps land — don't pre-list hypothetical kinds (CLAUDE.md: don't design
32
35
  // for scenarios that can't happen).
33
36
  const UNSAFE_PROJECTION_KINDS = new Set(["unsafeProjectionUpsert", "unsafeProjectionDelete"]);
34
- // Sub-pipeline arg-paths come from `defineStep({ subPaths: [...] })` —
35
- // each builder declares its own (e.g. branch's onTrue/onFalse, forEach's
36
- // do). Walking via the registry means nested unsafeProjection-* in NEW
37
- // sub-step-builders is automatically caught the moment the builder
38
- // registers itself; no central map to keep in sync. Followup #15.
39
- function* walkAllSteps(steps) {
40
- for (const step of steps) {
41
- yield step;
42
- const def = getStep(step.kind);
43
- const subPaths = def?.subPaths;
44
- if (!subPaths || subPaths.length === 0)
45
- continue;
46
- const args = step.args;
47
- for (const path of subPaths) {
48
- const subSteps = args[path];
49
- if (Array.isArray(subSteps)) {
50
- yield* walkAllSteps(subSteps);
51
- }
52
- }
53
- }
54
- }
55
37
  // table()/buildEntityTable spread column handles as enumerable props, so an
56
38
  // entity field named `tableName`/`source` would shadow the matching meta key —
57
39
  // the canonical meta under KUMIKO_META_SYMBOL is the only collision-safe source.
@@ -95,11 +77,10 @@ export function validateProjectionAllowlist(features) {
95
77
  // the first feature's projections (and vice-versa). Detect and surface
96
78
  // here so the error names BOTH owners; without this, the silent-set
97
79
  // would point any later unsafeProjection-error at the wrong feature.
98
- // Followup #8.
99
80
  const aggregateTables = new Map();
100
81
  for (const f of features) {
101
82
  for (const [entityName, entity] of Object.entries(f.entities ?? {})) {
102
- const tableName = entity.table ?? entityName;
83
+ const tableName = resolveTableName(entityName, entity, undefined);
103
84
  const existing = aggregateTables.get(tableName);
104
85
  if (existing && existing !== f.name) {
105
86
  throw new Error(`Aggregate-table "${tableName}" is registered by both feature "${existing}" and feature "${f.name}" via r.entity. ` +
@@ -129,8 +110,8 @@ export function validateProjectionAllowlist(features) {
129
110
  `Closure body must produce the step list without reading event-payload fields ` +
130
111
  `(those belong inside step resolvers).`);
131
112
  }
132
- for (const step of walkAllSteps(steps)) {
133
- // Tier-2 step-discovery (Q9): require explicit opt-in via
113
+ for (const step of walkStepInstances(steps, `[Feature ${f.name}] writeHandler "${handlerName}"`)) {
114
+ // Tier-2 step-discovery: require explicit opt-in via
134
115
  // r.requires.step("<kind>") for any tier-2+ step. Tier-1 is implicit.
135
116
  const def = getStep(step.kind);
136
117
  if (def?.tier === 2 && !f.requiredSteps.has(step.kind)) {
@@ -140,12 +121,12 @@ export function validateProjectionAllowlist(features) {
140
121
  }
141
122
  if (!UNSAFE_PROJECTION_KINDS.has(step.kind))
142
123
  continue;
143
- const stepArgs = step.args;
144
- if (!stepArgs.table) {
124
+ const stepTable = isPlainObject(step.args) ? step.args["table"] : undefined;
125
+ if (!stepTable) {
145
126
  throw new Error(`[Feature ${f.name}] writeHandler "${handlerName}" has a ${step.kind} step ` +
146
127
  `without a \`table\` argument.`);
147
128
  }
148
- const tableName = resolveTableNameFromStep(stepArgs.table);
129
+ const tableName = resolveTableNameFromStep(stepTable);
149
130
  const aggregateOwner = aggregateTables.get(tableName);
150
131
  if (aggregateOwner) {
151
132
  throw new Error(`[Feature ${f.name}] writeHandler "${handlerName}" uses ${step.kind} on table "${tableName}", ` +
@@ -0,0 +1,2 @@
1
+ import type { StepInstance } from "./types/step.ts";
2
+ export declare function walkStepInstances(steps: readonly StepInstance[], where: string): Generator<StepInstance, void, void>;
@@ -0,0 +1,38 @@
1
+ import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
2
+ import { getStep } from "./define-step.js";
3
+ function isStepInstance(value) {
4
+ return isPlainObject(value) && typeof value["kind"] === "string";
5
+ }
6
+ // Sub-pipeline arg-paths come from `defineStep({ subPaths })`, so nested steps of new
7
+ // sub-step builders are walked without a central map.
8
+ // Fail-closed: an entry the walker cannot read would hide a step from the caller's check
9
+ // (projection allowlist, event-trigger support), so malformed input throws instead of being skipped.
10
+ export function* walkStepInstances(steps, where) {
11
+ for (const [stepIndex, step] of steps.entries()) {
12
+ yield step;
13
+ const subPaths = getStep(step.kind)?.subPaths;
14
+ if (!subPaths || subPaths.length === 0)
15
+ continue;
16
+ const stepWhere = `${where} step[${stepIndex}] "${step.kind}"`;
17
+ if (!isPlainObject(step.args)) {
18
+ throw new Error(`${stepWhere} declares sub-paths (${subPaths.join(", ")}) but its args is not an object.`);
19
+ }
20
+ for (const path of subPaths) {
21
+ const subSteps = step.args[path];
22
+ // skip: optional sub-path not provided (branch.run treats onFalse: null like absent)
23
+ if (subSteps === undefined || subSteps === null)
24
+ continue;
25
+ if (!Array.isArray(subSteps)) {
26
+ throw new Error(`${stepWhere} sub-path "${path}" must be a list of steps.`);
27
+ }
28
+ const checked = [];
29
+ for (const [entryIndex, entry] of subSteps.entries()) {
30
+ if (!isStepInstance(entry)) {
31
+ throw new Error(`${stepWhere} sub-path "${path}"[${entryIndex}] is not a step (expected an object with a string "kind").`);
32
+ }
33
+ checked.push(entry);
34
+ }
35
+ yield* walkStepInstances(checked, `${stepWhere} ${path}`);
36
+ }
37
+ }
38
+ }
@@ -1,4 +1,4 @@
1
- // Static lint for `WhereRule` SQL fragments (fw#2639).
1
+ // Static lint for `WhereRule` SQL fragments.
2
2
  //
3
3
  // A `where`-rule hands the framework raw SQL. If the author writes an
4
4
  // unqualified column name inside a correlated subquery, Postgres binds it
@@ -6,7 +6,7 @@ import { toKebab } from "./qualified-name.js";
6
6
  export const WRITE_HANDLER_QN_FORMAT_RE = /^[a-zA-Z][a-zA-Z0-9-]*:write:[a-zA-Z][a-zA-Z0-9-]*(:[a-zA-Z][a-zA-Z0-9-]*)*$/;
7
7
  // Matches `dispatcher.write("qn", ...)` and `<expr>.write("qn", ...)` —
8
8
  // same surface the CI guard scans. Dynamic QNs (variables, templates) are
9
- // intentionally skipped (known limitation, documented in #403).
9
+ // intentionally skipped (known limitation).
10
10
  const DISPATCHER_WRITE_LITERAL_RE = /\.write\s*\(\s*["']([^"']+)["']/g;
11
11
  /** Extract string-literal write-handler QNs from TS/TSX source text. */
12
12
  export function extractDispatcherWriteQnsFromSource(source) {