@cosmicdrift/kumiko-framework 0.387.0 → 0.389.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 (375) hide show
  1. package/AGENTS.md +9 -1
  2. package/agents/examples/anonymous-access.md +1 -1
  3. package/agents/examples/apex-landing.md +204 -0
  4. package/agents/examples/api-worker-split.md +64 -0
  5. package/agents/examples/app-config.md +14 -0
  6. package/agents/examples/auth-mfa.md +115 -0
  7. package/agents/examples/env-composition.md +28 -1
  8. package/agents/examples/golden-path.md +13 -1
  9. package/agents/examples/legal-pages.md +1 -1
  10. package/agents/examples/lifecycle-hooks.md +55 -2
  11. package/agents/examples/list-table-options.md +466 -0
  12. package/agents/examples/pdf-audit-export.md +4 -2
  13. package/agents/examples/prod-boot-options.md +16 -0
  14. package/agents/examples/rate-limiting.md +48 -0
  15. package/agents/examples/record-detail-layout.md +692 -0
  16. package/agents/examples/seed-migration.md +212 -0
  17. package/agents/examples/state-machine.md +3 -2
  18. package/agents/examples/tenant-isolation.md +73 -1
  19. package/agents/examples/wizard-form.md +941 -0
  20. package/agents/examples/writeform-section.md +548 -0
  21. package/agents/examples.md +1 -0
  22. package/agents/features.md +19 -2
  23. package/agents/widgets.md +2 -0
  24. package/dist/api/api-constants.d.ts +1 -0
  25. package/dist/api/api-constants.js +2 -0
  26. package/dist/api/auth-routes-shared.d.ts +2 -0
  27. package/dist/api/auth-routes-shared.js +5 -0
  28. package/dist/api/client-error-route.js +10 -5
  29. package/dist/api/entity-read-access.d.ts +2 -1
  30. package/dist/api/entity-read-access.js +14 -0
  31. package/dist/api/export/export-cells-reference.d.ts +9 -0
  32. package/dist/api/export/export-cells-reference.js +21 -0
  33. package/dist/api/export/export-cells.d.ts +11 -0
  34. package/dist/api/export/export-cells.js +136 -0
  35. package/dist/api/export/export-ciphertext-guard.d.ts +2 -0
  36. package/dist/api/export/export-ciphertext-guard.js +27 -0
  37. package/dist/api/export/export-columns.d.ts +29 -0
  38. package/dist/api/export/export-columns.js +85 -0
  39. package/dist/api/export/export-errors.d.ts +4 -0
  40. package/dist/api/export/export-errors.js +14 -0
  41. package/dist/api/export/export-route.d.ts +4 -0
  42. package/dist/api/export/export-route.js +74 -0
  43. package/dist/api/export/export-rows.d.ts +13 -0
  44. package/dist/api/export/export-rows.js +62 -0
  45. package/dist/api/export/export-screen.d.ts +12 -0
  46. package/dist/api/export/export-screen.js +47 -0
  47. package/dist/api/export/export-xlsx-writer.d.ts +4 -0
  48. package/dist/api/export/export-xlsx-writer.js +21 -0
  49. package/dist/api/extra-route.d.ts +4 -0
  50. package/dist/api/index.d.ts +3 -1
  51. package/dist/api/index.js +3 -1
  52. package/dist/api/pii-leak-guard.d.ts +3 -0
  53. package/dist/api/pii-leak-guard.js +3 -2
  54. package/dist/api/request-context.d.ts +1 -1
  55. package/dist/api/request-context.js +1 -1
  56. package/dist/api/routes.d.ts +16 -0
  57. package/dist/api/routes.js +2 -2
  58. package/dist/api/server-boot-checks.d.ts +10 -0
  59. package/dist/api/server-boot-checks.js +157 -0
  60. package/dist/api/server-event-dispatcher.d.ts +26 -0
  61. package/dist/api/server-event-dispatcher.js +208 -0
  62. package/dist/api/server-extra-routes.d.ts +11 -0
  63. package/dist/api/server-extra-routes.js +206 -0
  64. package/dist/api/server-middleware.d.ts +20 -0
  65. package/dist/api/server-middleware.js +211 -0
  66. package/dist/api/server-routes.d.ts +19 -0
  67. package/dist/api/server-routes.js +299 -0
  68. package/dist/api/server-websocket-routes.d.ts +4 -0
  69. package/dist/api/server-websocket-routes.js +108 -0
  70. package/dist/api/server-wiring.d.ts +32 -0
  71. package/dist/api/server-wiring.js +238 -0
  72. package/dist/api/server.d.ts +10 -15
  73. package/dist/api/server.js +48 -1251
  74. package/dist/api/session-cookies.js +4 -4
  75. package/dist/api/sse-route.d.ts +2 -1
  76. package/dist/api/sse-route.js +27 -13
  77. package/dist/config/index.d.ts +3 -1
  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.d.ts +1 -0
  81. package/dist/crypto/pg-kms-adapter.js +12 -18
  82. package/dist/crypto/pii-field-encryption.d.ts +5 -1
  83. package/dist/crypto/pii-field-encryption.js +3 -1
  84. package/dist/db/aggregate-sql.d.ts +23 -0
  85. package/dist/db/aggregate-sql.js +165 -0
  86. package/dist/db/closed-connection.d.ts +9 -0
  87. package/dist/db/closed-connection.js +88 -0
  88. package/dist/db/connection.d.ts +4 -5
  89. package/dist/db/connection.js +29 -20
  90. package/dist/db/dialect.js +1 -1
  91. package/dist/db/distinct-on.d.ts +1 -1
  92. package/dist/db/driver-row-coercion.d.ts +2 -0
  93. package/dist/db/driver-row-coercion.js +186 -0
  94. package/dist/db/driver-value-preparation.d.ts +16 -0
  95. package/dist/db/driver-value-preparation.js +101 -0
  96. package/dist/db/entity-field-encryption.d.ts +1 -0
  97. package/dist/db/entity-field-encryption.js +1 -1
  98. package/dist/db/entity-table-meta.js +103 -110
  99. package/dist/db/event-store-executor-context.d.ts +2 -1
  100. package/dist/db/event-store-executor-read-aggregate-currencies.d.ts +17 -0
  101. package/dist/db/event-store-executor-read-aggregate-currencies.js +40 -0
  102. package/dist/db/event-store-executor-read-aggregate.d.ts +8 -0
  103. package/dist/db/event-store-executor-read-aggregate.js +143 -0
  104. package/dist/db/event-store-executor-read-filter.d.ts +31 -0
  105. package/dist/db/event-store-executor-read-filter.js +206 -0
  106. package/dist/db/event-store-executor-read-paging.d.ts +14 -0
  107. package/dist/db/event-store-executor-read-paging.js +99 -0
  108. package/dist/db/event-store-executor-read-rows.d.ts +6 -0
  109. package/dist/db/event-store-executor-read-rows.js +32 -0
  110. package/dist/db/event-store-executor-read-search.d.ts +28 -0
  111. package/dist/db/event-store-executor-read-search.js +221 -0
  112. package/dist/db/event-store-executor-read-sort.d.ts +41 -0
  113. package/dist/db/event-store-executor-read-sort.js +130 -0
  114. package/dist/db/event-store-executor-read-where.d.ts +24 -0
  115. package/dist/db/event-store-executor-read-where.js +81 -0
  116. package/dist/db/event-store-executor-read.d.ts +2 -9
  117. package/dist/db/event-store-executor-read.js +48 -701
  118. package/dist/db/event-store-executor-write-atomic.d.ts +4 -0
  119. package/dist/db/event-store-executor-write-atomic.js +41 -0
  120. package/dist/db/event-store-executor-write-steps.d.ts +17 -0
  121. package/dist/db/event-store-executor-write-steps.js +95 -0
  122. package/dist/db/event-store-executor-write.js +405 -509
  123. package/dist/db/event-store-executor.js +2 -1
  124. package/dist/db/executor-managed-tables.js +1 -1
  125. package/dist/db/index.d.ts +7 -3
  126. package/dist/db/index.js +6 -2
  127. package/dist/db/list-aggregate-labels.d.ts +14 -0
  128. package/dist/db/list-aggregate-labels.js +26 -0
  129. package/dist/db/migrate-generator.js +71 -86
  130. package/dist/db/migrate-runner.js +7 -4
  131. package/dist/db/pg-error.js +26 -21
  132. package/dist/db/queries/backfill-pii.js +3 -6
  133. package/dist/db/queries/database-now.js +2 -1
  134. package/dist/db/queries/shadow-swap.js +2 -3
  135. package/dist/db/query.d.ts +16 -15
  136. package/dist/db/query.js +76 -804
  137. package/dist/db/reference-data.js +19 -2
  138. package/dist/db/statement-timeout.js +2 -1
  139. package/dist/db/table-builder.js +113 -166
  140. package/dist/db/table-info.d.ts +10 -0
  141. package/dist/db/table-info.js +45 -0
  142. package/dist/db/temporal-value-guards.d.ts +6 -0
  143. package/dist/db/temporal-value-guards.js +15 -0
  144. package/dist/db/tenant-db-runner.d.ts +1 -0
  145. package/dist/db/tenant-db-runner.js +3 -0
  146. package/dist/db/tenant-db-upsert.d.ts +5 -0
  147. package/dist/db/tenant-db-upsert.js +91 -0
  148. package/dist/db/tenant-db.js +50 -10
  149. package/dist/db/where-clause.d.ts +6 -0
  150. package/dist/db/where-clause.js +236 -0
  151. package/dist/derivatives/delete-stored-file.js +2 -1
  152. package/dist/engine/boot-validator/boot-check.js +1 -1
  153. package/dist/engine/boot-validator/bundled-lists-table.d.ts +2 -0
  154. package/dist/engine/boot-validator/bundled-lists-table.js +36 -0
  155. package/dist/engine/boot-validator/detail-screens.js +2 -2
  156. package/dist/engine/boot-validator/entity-handler.d.ts +1 -0
  157. package/dist/engine/boot-validator/entity-handler.js +17 -0
  158. package/dist/engine/boot-validator/entity-list-aggregate-options.d.ts +3 -0
  159. package/dist/engine/boot-validator/entity-list-aggregate-options.js +23 -0
  160. package/dist/engine/boot-validator/form-derivations.js +35 -1
  161. package/dist/engine/boot-validator/i18n-keys.js +5 -3
  162. package/dist/engine/boot-validator/index.js +2 -0
  163. package/dist/engine/boot-validator/list-bulk-actions.d.ts +3 -0
  164. package/dist/engine/boot-validator/list-bulk-actions.js +49 -0
  165. package/dist/engine/boot-validator/list-column-layout.d.ts +8 -0
  166. package/dist/engine/boot-validator/list-column-layout.js +41 -0
  167. package/dist/engine/boot-validator/list-table-aggregate-options.d.ts +24 -0
  168. package/dist/engine/boot-validator/list-table-aggregate-options.js +145 -0
  169. package/dist/engine/boot-validator/list-table-aggregate-query.d.ts +10 -0
  170. package/dist/engine/boot-validator/list-table-aggregate-query.js +31 -0
  171. package/dist/engine/boot-validator/list-table-export-options.d.ts +1 -0
  172. package/dist/engine/boot-validator/list-table-export-options.js +27 -0
  173. package/dist/engine/boot-validator/list-table-options.d.ts +3 -0
  174. package/dist/engine/boot-validator/list-table-options.js +77 -0
  175. package/dist/engine/boot-validator/list-table-reject-unknown-keys.d.ts +1 -0
  176. package/dist/engine/boot-validator/list-table-reject-unknown-keys.js +7 -0
  177. package/dist/engine/boot-validator/pii-retention.js +233 -155
  178. package/dist/engine/boot-validator/projection-list-screens.d.ts +1 -1
  179. package/dist/engine/boot-validator/projection-list-screens.js +3 -13
  180. package/dist/engine/boot-validator/related-list-table-rules.d.ts +4 -0
  181. package/dist/engine/boot-validator/related-list-table-rules.js +42 -0
  182. package/dist/engine/boot-validator/screens-actions.d.ts +1 -0
  183. package/dist/engine/boot-validator/screens-actions.js +8 -1
  184. package/dist/engine/boot-validator/screens-dashboard.js +2 -5
  185. package/dist/engine/boot-validator/screens-forms.js +2 -6
  186. package/dist/engine/boot-validator/screens-kind-config-edit.d.ts +3 -0
  187. package/dist/engine/boot-validator/screens-kind-config-edit.js +88 -0
  188. package/dist/engine/boot-validator/screens-kind-entity-edit.d.ts +3 -0
  189. package/dist/engine/boot-validator/screens-kind-entity-edit.js +55 -0
  190. package/dist/engine/boot-validator/screens-kind-entity-list.d.ts +3 -0
  191. package/dist/engine/boot-validator/screens-kind-entity-list.js +235 -0
  192. package/dist/engine/boot-validator/screens-kind-projection-detail.d.ts +3 -0
  193. package/dist/engine/boot-validator/screens-kind-projection-detail.js +235 -0
  194. package/dist/engine/boot-validator/screens-kind-projection-list.d.ts +3 -0
  195. package/dist/engine/boot-validator/screens-kind-projection-list.js +56 -0
  196. package/dist/engine/boot-validator/screens-kind-secret-mint.d.ts +3 -0
  197. package/dist/engine/boot-validator/screens-kind-secret-mint.js +28 -0
  198. package/dist/engine/boot-validator/screens-kind-shared.d.ts +37 -0
  199. package/dist/engine/boot-validator/screens-kind-shared.js +142 -0
  200. package/dist/engine/boot-validator/screens-lists.d.ts +2 -1
  201. package/dist/engine/boot-validator/screens-lists.js +24 -11
  202. package/dist/engine/boot-validator/screens.d.ts +0 -2
  203. package/dist/engine/boot-validator/screens.js +99 -860
  204. package/dist/engine/build-app-schema.d.ts +2 -1
  205. package/dist/engine/build-app-schema.js +65 -97
  206. package/dist/engine/build-config-feature-schema.js +6 -1
  207. package/dist/engine/define-feature.js +13 -2
  208. package/dist/engine/define-handler.js +1 -0
  209. package/dist/engine/entity-handlers.d.ts +13 -0
  210. package/dist/engine/entity-handlers.js +10 -21
  211. package/dist/engine/entity-list-runtime-options.d.ts +8 -0
  212. package/dist/engine/entity-list-runtime-options.js +18 -0
  213. package/dist/engine/feature-ast/extractors/handlers.js +1 -0
  214. package/dist/engine/feature-ast/extractors/index.d.ts +1 -1
  215. package/dist/engine/feature-ast/extractors/index.js +1 -1
  216. package/dist/engine/feature-ast/extractors/round1.d.ts +2 -1
  217. package/dist/engine/feature-ast/extractors/round1.js +6 -0
  218. package/dist/engine/feature-ast/extractors/round2.js +25 -6
  219. package/dist/engine/feature-ast/index.d.ts +1 -1
  220. package/dist/engine/feature-ast/parse.js +3 -1
  221. package/dist/engine/feature-ast/patch.d.ts +2 -0
  222. package/dist/engine/feature-ast/patch.js +5 -182
  223. package/dist/engine/feature-ast/pattern-change-schema.d.ts +10 -245
  224. package/dist/engine/feature-ast/pattern-change-schema.js +10 -163
  225. package/dist/engine/feature-ast/pattern-id-matchers.d.ts +7 -0
  226. package/dist/engine/feature-ast/pattern-id-matchers.js +211 -0
  227. package/dist/engine/feature-ast/pattern-id-schema.d.ts +251 -0
  228. package/dist/engine/feature-ast/pattern-id-schema.js +166 -0
  229. package/dist/engine/feature-ast/patterns.d.ts +14 -1
  230. package/dist/engine/feature-ast/patterns.js +44 -51
  231. package/dist/engine/feature-ast/render.js +52 -87
  232. package/dist/engine/feature-builder-state.d.ts +1 -0
  233. package/dist/engine/feature-builder-state.js +1 -0
  234. package/dist/engine/feature-entity-handlers.js +3 -0
  235. package/dist/engine/feature-manifest.d.ts +1 -0
  236. package/dist/engine/feature-manifest.js +1 -0
  237. package/dist/engine/field-access.d.ts +1 -0
  238. package/dist/engine/field-access.js +12 -0
  239. package/dist/engine/form-derivations.js +18 -7
  240. package/dist/engine/handler-field-references.js +1 -1
  241. package/dist/engine/index.d.ts +2 -2
  242. package/dist/engine/index.js +1 -1
  243. package/dist/engine/pattern-library/library.js +2 -1
  244. package/dist/engine/pattern-library/static-schemas.d.ts +1 -0
  245. package/dist/engine/pattern-library/static-schemas.js +9 -0
  246. package/dist/engine/project-app-schema-for-roles.d.ts +2 -0
  247. package/dist/engine/project-app-schema-for-roles.js +6 -4
  248. package/dist/engine/registry-ingest.js +1 -1
  249. package/dist/engine/schema-builder.js +228 -212
  250. package/dist/engine/screen-filter-ops.js +2 -0
  251. package/dist/engine/screen-helpers.js +1 -1
  252. package/dist/engine/screen-kind.d.ts +7 -0
  253. package/dist/engine/screen-kind.js +1 -0
  254. package/dist/engine/strip-disabled-features-from-app-schema.js +58 -17
  255. package/dist/engine/types/feature.d.ts +1 -0
  256. package/dist/engine/types/index.d.ts +4 -3
  257. package/dist/engine/types/screen.d.ts +1 -1
  258. package/dist/engine/types/screen.js +1 -1
  259. package/dist/engine/validate-projection-allowlist.js +2 -1
  260. package/dist/entrypoint/index.d.ts +6 -1
  261. package/dist/entrypoint/index.js +10 -1
  262. package/dist/errors/docs-url.d.ts +1 -1
  263. package/dist/errors/docs-url.js +22 -2
  264. package/dist/es-ops/context.js +1 -1
  265. package/dist/event-store/admin-api.js +1 -1
  266. package/dist/event-store/event-store.d.ts +1 -0
  267. package/dist/event-store/event-store.js +17 -8
  268. package/dist/event-store/provenance-append.js +1 -1
  269. package/dist/files/bind-pending-file-refs.js +3 -3
  270. package/dist/files/create-file-ref-from-bytes.js +7 -1
  271. package/dist/files/create-file-ref.d.ts +3 -0
  272. package/dist/files/create-file-ref.js +11 -2
  273. package/dist/files/file-handle.js +2 -3
  274. package/dist/files/file-routes.d.ts +2 -0
  275. package/dist/files/file-routes.js +40 -10
  276. package/dist/files/provider-resolver.d.ts +1 -0
  277. package/dist/files/provider-resolver.js +1 -1
  278. package/dist/files/record-write-check.d.ts +6 -0
  279. package/dist/files/record-write-check.js +13 -0
  280. package/dist/files/resolve-content-type.js +43 -0
  281. package/dist/files/upload-ledger.d.ts +2 -2
  282. package/dist/files/upload-ledger.js +11 -4
  283. package/dist/i18n/required-surface-keys.js +189 -208
  284. package/dist/internal/stack-support.d.ts +2 -1
  285. package/dist/internal/stack-support.js +1 -1
  286. package/dist/internal/testing.js +1 -1
  287. package/dist/jobs/job-runner-context.d.ts +32 -0
  288. package/dist/jobs/job-runner-context.js +305 -0
  289. package/dist/jobs/job-runner-execution.d.ts +7 -0
  290. package/dist/jobs/job-runner-execution.js +160 -0
  291. package/dist/jobs/job-runner-gates.d.ts +19 -0
  292. package/dist/jobs/job-runner-gates.js +198 -0
  293. package/dist/jobs/job-runner-helpers.d.ts +17 -3
  294. package/dist/jobs/job-runner-helpers.js +61 -2
  295. package/dist/jobs/job-runner-scope.d.ts +44 -0
  296. package/dist/jobs/job-runner-scope.js +102 -0
  297. package/dist/jobs/job-runner-types.d.ts +12 -0
  298. package/dist/jobs/job-runner.d.ts +0 -1
  299. package/dist/jobs/job-runner.js +50 -657
  300. package/dist/observability/escape-hatch-report.d.ts +3 -0
  301. package/dist/observability/escape-hatch-report.js +21 -3
  302. package/dist/pipeline/after-rollback-sink.d.ts +6 -0
  303. package/dist/pipeline/after-rollback-sink.js +32 -0
  304. package/dist/pipeline/cascade-handler.js +165 -138
  305. package/dist/pipeline/dispatch-batch.js +8 -1
  306. package/dist/pipeline/dispatch-query.js +10 -3
  307. package/dist/pipeline/dispatch-shared.d.ts +13 -3
  308. package/dist/pipeline/dispatch-shared.js +62 -710
  309. package/dist/pipeline/dispatch-write.js +296 -189
  310. package/dist/pipeline/event-dispatcher-admin.js +2 -1
  311. package/dist/pipeline/event-dispatcher-delivery.js +6 -6
  312. package/dist/pipeline/event-dispatcher.d.ts +1 -0
  313. package/dist/pipeline/event-dispatcher.js +6 -9
  314. package/dist/pipeline/handler-context-auth.d.ts +9 -0
  315. package/dist/pipeline/handler-context-auth.js +54 -0
  316. package/dist/pipeline/handler-context-config.d.ts +14 -0
  317. package/dist/pipeline/handler-context-config.js +113 -0
  318. package/dist/pipeline/handler-context-db.d.ts +24 -0
  319. package/dist/pipeline/handler-context-db.js +149 -0
  320. package/dist/pipeline/handler-context-dispatch.d.ts +23 -0
  321. package/dist/pipeline/handler-context-dispatch.js +101 -0
  322. package/dist/pipeline/handler-context-events.d.ts +41 -0
  323. package/dist/pipeline/handler-context-events.js +239 -0
  324. package/dist/pipeline/handler-context-notify.d.ts +12 -0
  325. package/dist/pipeline/handler-context-notify.js +55 -0
  326. package/dist/pipeline/handler-context-observability.d.ts +7 -0
  327. package/dist/pipeline/handler-context-observability.js +21 -0
  328. package/dist/pipeline/handler-context-scope.d.ts +48 -0
  329. package/dist/pipeline/handler-context-scope.js +88 -0
  330. package/dist/pipeline/notify-scope.js +12 -1
  331. package/dist/pipeline/pre-handler-gates.js +20 -4
  332. package/dist/pipeline/pre-transaction-reservations.d.ts +1 -0
  333. package/dist/pipeline/pre-transaction-reservations.js +9 -3
  334. package/dist/pipeline/projection-rebuild.js +2 -1
  335. package/dist/pipeline/system-hooks.d.ts +3 -1
  336. package/dist/pipeline/system-hooks.js +55 -6
  337. package/dist/pipeline/write-result-masking.js +13 -10
  338. package/dist/schema-cli/apply-command.d.ts +2 -0
  339. package/dist/schema-cli/apply-command.js +97 -0
  340. package/dist/schema-cli/baseline-command.d.ts +2 -0
  341. package/dist/schema-cli/baseline-command.js +33 -0
  342. package/dist/schema-cli/generate-command.d.ts +2 -0
  343. package/dist/schema-cli/generate-command.js +90 -0
  344. package/dist/schema-cli/shared.d.ts +39 -0
  345. package/dist/schema-cli/shared.js +95 -0
  346. package/dist/schema-cli/status-command.d.ts +2 -0
  347. package/dist/schema-cli/status-command.js +73 -0
  348. package/dist/schema-cli/validate-boot-input.d.ts +5 -0
  349. package/dist/schema-cli/validate-boot-input.js +130 -0
  350. package/dist/schema-cli/validate-command.d.ts +2 -0
  351. package/dist/schema-cli/validate-command.js +130 -0
  352. package/dist/schema-cli.d.ts +2 -18
  353. package/dist/schema-cli.js +47 -604
  354. package/dist/search/meilisearch-adapter.js +2 -1
  355. package/dist/search/reindex-entity.js +15 -5
  356. package/dist/ui-types/index.d.ts +2 -2
  357. package/dist/ui-types/index.js +1 -1
  358. package/dist/upgrade-changelog.d.ts +2 -0
  359. package/dist/upgrade-changelog.js +14 -0
  360. package/dist/upgrade-cli.d.ts +3 -12
  361. package/dist/upgrade-cli.js +171 -197
  362. package/dist/upgrade-marker-parse.d.ts +32 -0
  363. package/dist/upgrade-marker-parse.js +73 -0
  364. package/package.json +9 -6
  365. package/src/changes.json +555 -0
  366. package/src/scripts/codemod/codemod-support.ts +1 -1
  367. package/src/scripts/codemod/db-factory.ts +1 -1
  368. package/src/scripts/codemod/feature-layout.ts +10 -2
  369. package/src/scripts/codemod/object-form.ts +41 -4
  370. package/src/scripts/codemod/package-merge.ts +58 -8
  371. package/src/scripts/codemod/pii-personal-migration.ts +175 -145
  372. package/src/scripts/codemod/testing-injection.ts +122 -42
  373. package/src/scripts/codemod/testing-move.ts +29 -7
  374. package/dist/engine/provider-plugin-context.d.ts +0 -3
  375. package/dist/engine/provider-plugin-context.js +0 -28
@@ -11,42 +11,80 @@
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";
16
+ import { workspaceOwnerOf } from "./build-app-schema.js";
15
17
  import { isToggleableFeature } from "./effective-features.js";
18
+ import { isPureGroupingNav } from "./project-app-schema-for-roles.js";
16
19
  import { isValidQn, qualifyEntityName } from "./qualified-name.js";
20
+ const schemaLogger = createFallbackLogger("app-schema");
17
21
  // Sorted, so the result doubles as a stable cache key. The trial gate is
18
22
  // consulted like the dispatcher's feature gate does, but only for toggleable
19
23
  // features: a trial can only unlock tier (toggleable) features, so a
20
- // cascade-disabled always-on feature needs no gate call.
24
+ // cascade-disabled always-on feature needs no gate call. A failing gate counts
25
+ // as "not unlocked" (the handlers answer 403 then anyway), so a gate outage
26
+ // hides the add-on instead of taking the whole schema endpoint down.
21
27
  export async function resolveDisabledFeatureNames(registry, effectiveFeatures, tenantId) {
22
28
  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;
29
+ const trialGate = effectiveFeatures.trialGate;
30
+ const disabled = [...registry.features].filter(([featureName]) => !enabled.has(featureName));
31
+ const unlockedByTrial = await Promise.all(disabled.map(async ([featureName, feature]) => {
32
+ if (trialGate === undefined || !isToggleableFeature(feature))
33
+ return false;
34
+ try {
35
+ return await trialGate(tenantId, featureName);
30
36
  }
31
- disabled.push(featureName);
32
- }
33
- return disabled.sort();
37
+ catch (error) {
38
+ schemaLogger.warn("trial gate failed, treating feature as not unlocked", {
39
+ featureName,
40
+ error: scrubLogText(error instanceof Error ? error.message : String(error)),
41
+ });
42
+ return false;
43
+ }
44
+ }));
45
+ return disabled
46
+ .filter((_, index) => !unlockedByTrial[index])
47
+ .map(([featureName]) => featureName)
48
+ .sort();
34
49
  }
35
50
  function navParentQn(featureName, parent) {
36
51
  return isValidQn(parent) ? parent : qualifyEntityName(featureName, "nav", parent);
37
52
  }
38
- // Navs of enabled features whose parent chain ends in a removed nav.
53
+ function navQnsByParent(features) {
54
+ const childrenByParent = new Map();
55
+ for (const feature of features) {
56
+ for (const nav of feature.navs ?? []) {
57
+ if (nav.parent === undefined)
58
+ continue;
59
+ const parentQn = navParentQn(feature.featureName, nav.parent);
60
+ const children = childrenByParent.get(parentQn) ?? [];
61
+ children.push(qualifyEntityName(feature.featureName, "nav", nav.id));
62
+ childrenByParent.set(parentQn, children);
63
+ }
64
+ }
65
+ return childrenByParent;
66
+ }
67
+ // Navs of enabled features whose parent chain ends in a removed nav, plus pure grouping
68
+ // navs whose children are all removed (an empty section the role projection cannot drop,
69
+ // because it only drops groups that still have children).
39
70
  function collectOrphanedNavQns(features, removedNavQns) {
40
71
  const orphaned = new Set();
72
+ const childrenByParent = navQnsByParent(features);
41
73
  let changed = true;
42
74
  while (changed) {
43
75
  changed = false;
44
76
  for (const feature of features) {
45
77
  for (const nav of feature.navs ?? []) {
46
78
  const qn = qualifyEntityName(feature.featureName, "nav", nav.id);
47
- if (removedNavQns.has(qn) || nav.parent === undefined)
79
+ if (removedNavQns.has(qn))
48
80
  continue;
49
- if (removedNavQns.has(navParentQn(feature.featureName, nav.parent))) {
81
+ const parentRemoved = nav.parent !== undefined &&
82
+ removedNavQns.has(navParentQn(feature.featureName, nav.parent));
83
+ const children = childrenByParent.get(qn) ?? [];
84
+ const emptiedGroup = isPureGroupingNav(nav) &&
85
+ children.length > 0 &&
86
+ children.every((childQn) => removedNavQns.has(childQn));
87
+ if (parentRemoved || emptiedGroup) {
50
88
  removedNavQns.add(qn);
51
89
  orphaned.add(qn);
52
90
  changed = true;
@@ -104,10 +142,13 @@ export function stripDisabledFeaturesFromAppSchema(schema, registry, disabledFea
104
142
  const { navs: _navs, ...rest } = feature;
105
143
  return navs.length > 0 ? { ...rest, navs } : rest;
106
144
  });
107
- // Workspace definitions carry the short id the author declared; the owning
108
- // feature is only known to the registry.
145
+ // Workspace ids are unique per feature only, so the owner decides. A definition the build did
146
+ // not register (schema assembled elsewhere) falls back to the short id.
109
147
  const disabledWorkspaceIds = new Set(disabledFeatureNames.flatMap((featureName) => Object.keys(registry.features.get(featureName)?.workspaces ?? {})));
110
- const workspaces = schema.workspaces?.filter((ws) => !disabledWorkspaceIds.has(ws.definition.id));
148
+ const workspaces = schema.workspaces?.filter((ws) => {
149
+ const owner = workspaceOwnerOf(ws.definition);
150
+ return owner === undefined ? !disabledWorkspaceIds.has(ws.definition.id) : !disabled.has(owner);
151
+ });
111
152
  const { workspaces: _original, ...restSchema } = schema;
112
153
  return {
113
154
  ...restSchema,
@@ -1 +1,2 @@
1
1
  export type * from "@cosmicdrift/kumiko-types/feature";
2
+ export type * from "@cosmicdrift/kumiko-types/feature-registrar";
@@ -4,10 +4,11 @@ export type { QueryHandlerDefinition, StreamHandlerDefinition, WriteHandlerDefin
4
4
  export type { EntityCrudRegistrar, EntityCrudVerb, EntityHandlerOptions, EntityQueryHandlerOptions, EntityWriteHandlerOptions, RegisterEntityCrudOptions, } from "@cosmicdrift/kumiko-types/entity-handlers";
5
5
  export type { KumikoEntityTypeMap, KumikoEventTypeMap, KumikoHandlerPayloadMap, KumikoHandlerResultMap, } from "@cosmicdrift/kumiko-types/event-type-map";
6
6
  export type { ExtensionOptionsArgs, ExtensionOptionsFor, KumikoExtensionOptionsMap, } from "@cosmicdrift/kumiko-types/extension-options-map";
7
- export type { BootCheckContext, BootCheckFn, BootOptionName, EntityObjectDefinition, FeatureDefinition, FeatureMetricDef, FeatureMetricType, FeatureRegistrar, HookDefinition, MetricOptions, Registry, SecretKeyDefinition, SecretKeyHandle, SecretNamespaceDefinition, SecretNamespaceHandle, SecretNamespaceOptions, SecretOptions, StackBootRequirement, StoreTableDef, StoreTableEntry, StoreTableOptions, UiHintOption, UiHints, } from "@cosmicdrift/kumiko-types/feature";
7
+ export type { BootCheckContext, BootCheckFn, BootOptionName, FeatureDefinition, FeatureMetricDef, FeatureMetricType, MetricOptions, Registry, SecretKeyDefinition, SecretKeyHandle, SecretNamespaceDefinition, SecretNamespaceHandle, SecretNamespaceOptions, SecretOptions, StackBootRequirement, StoreTableDef, StoreTableEntry, StoreTableOptions, UiHintOption, UiHints, } from "@cosmicdrift/kumiko-types/feature";
8
+ export type { EntityObjectDefinition, FeatureRegistrar, HookDefinition, } from "@cosmicdrift/kumiko-types/feature-registrar";
8
9
  export type { FieldIconKey } from "@cosmicdrift/kumiko-types/field-icon";
9
10
  export type { AnyFileFieldDef, BigIntFieldDef, BooleanFieldDef, ClientDerivedFieldDef, DateFieldDef, DecimalFieldDef, DefaultCurrency, DeriveContext, DerivedFieldDef, DerivedFieldsMap, DerivedValueType, EmbeddedDerivedCellDef, EmbeddedFieldDef, EmbeddedSubFieldDef, EntityDefinition, EntityEventMigration, EntityFieldDefinition, EntityIndexDef, EntityLongTextFieldDef, EntityTextFieldDef, FieldAccess, FieldDefinition, FieldsMap, FileFieldDef, FilesFieldDef, Findability, ImageFieldDef, ImagesFieldDef, JsonbFieldDef, LocatedTimestampFieldDef, LongTextFieldDef, LongTextFindability, MoneyCurrencySource, MoneyFieldDef, MultiSelectFieldDef, NumberFieldDef, OptionsQueryPayload, OptionsQueryPayloadValue, ParentRefDef, PersonalAnnotations, PersonalAnnotationsLongText, PersonalAnnotationsNoFind, PersonalSubject, ReferenceFieldDef, ResolvedPiiFlags, ResolvedPiiStance, RetentionDef, SelectFieldDef, SelectOptionTone, TextFieldDef, TimestampFieldDef, TransitionMap, TzFieldDef, } from "@cosmicdrift/kumiko-types/fields";
10
- export type { AccessRule, ActiveMembership, ActiveMembershipRejection, ActiveMembershipResult, AgentExposure, AgentHandlerHints, AgentRisk, AggregateStreamHandle, AppContext, AppendEventArgs, AppendEventFn, AuthClaimsContext, AuthClaimsFn, AuthClaimsHookDef, BeforeTransactionPhase, BeforeTransactionResult, CamelToKebab, ChatSendFailureCode, ClaimKeyDefinition, ClaimKeyHandle, ClaimKeyJsType, ClaimKeyType, DeclarativeEventMigration, DeliveryErrorCode, DeliveryFailureCode, DeliverySkipReason, DispatchWriteRef, EntityRef, EscapeHatchAuditSink, EscapeHatchDeclaration, EscapeHatchKind, EscapeHatchReporter, EscapeHatchTarget, EscapeHatchUseEvent, EventDef, EventMigrationDef, EventPiiFields, EventPiiStance, EventUpcastCtx, EventUpcastFn, FetchForWritingArgs, HandlerContext, HandlerRef, JobContext, JobDeferral, JobRunnerRef, JobTriggerEvent, MemberReader, NameOrRef, NotifyDeferred, NotifyDelivery, NotifyDeliveryStatus, NotifyDispatched, NotifyFactory, NotifyFn, NotifyJobDispatcher, NotifyOptions, NotifyPriority, NotifyResult, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, PayloadBucketNormalization, PayloadRateLimitOption, PreparedWriteEvent, ProviderPluginContext, QualifiedEventName, QueryEvent, QueryHandlerDef, QueryHandlerFn, RateLimitDeclaration, RateLimitDisabled, RateLimitOption, RateLimitOptions, RateLimitPer, RoleAccessPersonalData, RoleAccessRule, SessionUser, SessionUserOrigin, StreamHandlerDef, StreamHandlerFn, TenantWriteTarget, TryAppendEventFn, TryAppendEventResult, UnsafeAppendEventFn, WriteEvent, WriteHandlerDef, WriteHandlerFn, WriteResult, } from "@cosmicdrift/kumiko-types/handlers";
11
+ export type { AccessRule, ActiveMembership, ActiveMembershipRejection, ActiveMembershipResult, AgentExposure, AgentHandlerHints, AgentRisk, AggregateStreamHandle, AppContext, AppendEventArgs, AppendEventFn, AuthClaimsContext, AuthClaimsFn, AuthClaimsHookDef, BeforeTransactionPhase, BeforeTransactionResult, CamelToKebab, ChatSendFailureCode, ClaimKeyDefinition, ClaimKeyHandle, ClaimKeyJsType, ClaimKeyType, DeclarativeEventMigration, DeliveryErrorCode, DeliveryFailureCode, DeliverySkipReason, DispatchWriteRef, EntityRef, EscapeHatchAuditSink, EscapeHatchDeclaration, EscapeHatchKind, EscapeHatchReporter, EscapeHatchTarget, EscapeHatchUseEvent, EventDef, EventMigrationDef, EventPiiFields, EventPiiStance, EventUpcastCtx, EventUpcastFn, FetchForWritingArgs, HandlerContext, HandlerRef, JobContext, JobDeferral, JobRunnerRef, JobTriggerEvent, MemberReader, NameOrRef, NotifyDeferred, NotifyDelivery, NotifyDeliveryStatus, NotifyDispatched, NotifyFactory, NotifyFn, NotifyJobDispatcher, NotifyOptions, NotifyPriority, NotifyResult, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, PayloadBucketNormalization, PayloadRateLimitOption, PreparedWriteEvent, QualifiedEventName, QueryEvent, QueryHandlerDef, QueryHandlerFn, RateLimitDeclaration, RateLimitDisabled, RateLimitOption, RateLimitOptions, RateLimitPer, RoleAccessPersonalData, RoleAccessRule, SessionUser, SessionUserOrigin, StreamHandlerDef, StreamHandlerFn, TenantWriteTarget, TryAppendEventFn, TryAppendEventResult, UnsafeAppendEventFn, WriteEvent, WriteHandlerDef, WriteHandlerFn, WriteResult, } from "@cosmicdrift/kumiko-types/handlers";
11
12
  export { DELIVERY_FAILURE_CODES, DELIVERY_SKIP_REASONS, declaredRateLimitOptions, HTTP_ERROR_CODE_PATTERN, isDeliveryErrorCode, isOpenToAllGranted, isRateLimitDisabled, isRateLimitOption, isUiAccessGranted, } from "@cosmicdrift/kumiko-types/handlers";
12
13
  export type { DeleteContext, EntityHookMap, HookMap, HookOwner, HookPhase, LifecycleHookFn, LifecycleResult, OwnedFn, PhasedHook, PostDeleteBatchHookFn, PostDeleteHookFn, PostQueryHookFn, PostSaveBatchHookFn, PostSaveHookFn, PreDeleteHookFn, PreQueryHookFn, PreSaveHookFn, SaveContext, SearchPayloadContributorFn, ValidationError, ValidationHookFn, } from "@cosmicdrift/kumiko-types/hooks";
13
14
  export type { HttpRouteDefinition, HttpRouteHandler, HttpRouteHandlerDeps, HttpRouteMethod, } from "@cosmicdrift/kumiko-types/http-route";
@@ -17,7 +18,7 @@ export type { ContentCollectionDefinition, NavDefinition, NavIconKey, } from "@c
17
18
  export type { FromRule, FromRuleKind, OwnershipClause, OwnershipMap, OwnershipRef, OwnershipRule, SqlFragment, WhereRule, WhereRuleContext, } from "@cosmicdrift/kumiko-types/ownership";
18
19
  export type { EntityProjectionExtension, MspErrorMode, MspErrorPolicy, MultiStreamApplyFn, MultiStreamProjectionDefinition, ProjectionDefinition, ProjectionRowIdOf, ProjectionTable, SingleStreamApplyFn, } from "@cosmicdrift/kumiko-types/projection";
19
20
  export type { BelongsToRelation, EntityRelations, HasManyRelation, ManyToManyRelation, RelationDefinition, } from "@cosmicdrift/kumiko-types/relations";
20
- export type { ActionFormScreenDefinition, ConfigEditScreenDefinition, CustomScreenDefinition, CustomScreenRoute, DashboardChartMarkerKind, DashboardChartPanel, DashboardChartRangeOption, DashboardChartRanges, DashboardCurrencyFormat, DashboardCustomPanel, DashboardDateParam, DashboardFeedPanel, DashboardFilterDefinition, DashboardI18nText, DashboardListPanel, DashboardMoneyParam, DashboardPanelDefinition, DashboardPanelGate, DashboardPanelVisibility, DashboardProgressListPanel, DashboardProgressTone, DashboardRecordBinding, DashboardRefresh, DashboardRelativeTimeFormat, DashboardRelativeTimeParam, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DashboardText, DashboardTextBlock, DashboardTextPanel, DashboardTextParam, DashboardValueFormat, EditExtensionSection, EditFieldPrefill, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EditWriteFormSection, EmbeddedListEditSpec, EntityEditAction, EntityEditActionPlacement, EntityEditScreenDefinition, EntityListExpandableRow, EntityListScreenDefinition, FieldCondition, FieldFormatRegistry, FieldRenderer, FormatSpec, FormFieldDerivation, FormWidth, ListColumnSpec, ListEmptyStateSpec, ListFacetSpec, ListPaginationMode, ListSortDir, ListSortSpec, MetricNavigate, MetricSpec, PlatformComponent, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, RecordHeaderSpec, RecordHeaderSubtitlePart, RowAction, RowActionNavigate, RowActionNavigateBase, RowActionWriteHandler, RowFieldExtractor, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenNavSugar, ScreenSlots, SecretMintConfirmStep, SecretMintScreenDefinition, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, ToolbarAction, UnitKey, } from "@cosmicdrift/kumiko-types/screen";
21
+ export type { ActionFormScreenDefinition, BundledListsOptions, ConfigEditScreenDefinition, CustomScreenDefinition, CustomScreenRoute, DashboardChartMarkerKind, DashboardChartPanel, DashboardChartRangeOption, DashboardChartRanges, DashboardCurrencyFormat, DashboardCustomPanel, DashboardDateParam, DashboardFeedPanel, DashboardFilterDefinition, DashboardI18nText, DashboardListPanel, DashboardMoneyParam, DashboardPanelDefinition, DashboardPanelGate, DashboardPanelVisibility, DashboardProgressListPanel, DashboardProgressTone, DashboardRecordBinding, DashboardRefresh, DashboardRelativeTimeFormat, DashboardRelativeTimeParam, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DashboardText, DashboardTextBlock, DashboardTextPanel, DashboardTextParam, DashboardValueFormat, EditExtensionSection, EditFieldPrefill, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EditWriteFormSection, EmbeddedListEditSpec, EntityEditAction, EntityEditActionPlacement, EntityEditScreenDefinition, EntityListExpandableRow, EntityListScreenDefinition, ExpandableRowGrouping, FieldCondition, FieldFormatRegistry, FieldRenderer, FormatSpec, FormFieldDerivation, FormWidth, ListColumnSpec, ListEmptyStateSpec, ListFacetSpec, ListPaginationMode, ListSortDir, ListSortSpec, ListTableOptions, MetricNavigate, MetricSpec, PlatformComponent, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, RecordHeaderSpec, RecordHeaderSubtitlePart, RowAction, RowActionNavigate, RowActionNavigateBase, RowActionWriteHandler, RowFieldExtractor, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenNavSugar, ScreenSlots, SecretMintConfirmStep, SecretMintScreenDefinition, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, ToolbarAction, UnitKey, } from "@cosmicdrift/kumiko-types/screen";
21
22
  export { relatedListGroupHeaderLabel, relatedListGroupKey, } from "@cosmicdrift/kumiko-types/screen";
22
23
  export type { TargetRef } from "@cosmicdrift/kumiko-types/target-ref";
23
24
  export type { Subscribe, TreeAction, TreeActionDef, TreeActionsHandle, TreeChildrenSubscribe, TreeNode, TreeNodeState, } from "@cosmicdrift/kumiko-types/tree-node";
@@ -1,2 +1,2 @@
1
1
  export type * from "@cosmicdrift/kumiko-types/screen";
2
- export { relatedListGroupHeaderLabel, relatedListGroupKey, } from "@cosmicdrift/kumiko-types/screen";
2
+ export { DEFAULT_EXPORT_MAX_ROWS, LIST_BULK_ACTION_KEYS, LIST_COLUMN_PINS, LIST_TABLE_DENSITIES, LIST_TABLE_DENSITY_KEYS, LIST_TABLE_EXPORT_KEYS, LIST_TABLE_GROUPING_KEYS, LIST_TABLE_OPTION_KEYS, LIST_TABLE_TOTAL_KEYS, MAX_BULK_SELECTION, MAX_EXPORT_MAX_ROWS, relatedListGroupHeaderLabel, relatedListGroupKey, } from "@cosmicdrift/kumiko-types/screen";
@@ -1 +1 @@
1
- export { relatedListGroupHeaderLabel, relatedListGroupKey, } from "@cosmicdrift/kumiko-types/screen";
1
+ export { DEFAULT_EXPORT_MAX_ROWS, LIST_BULK_ACTION_KEYS, LIST_COLUMN_PINS, LIST_TABLE_DENSITIES, LIST_TABLE_DENSITY_KEYS, LIST_TABLE_EXPORT_KEYS, LIST_TABLE_GROUPING_KEYS, LIST_TABLE_OPTION_KEYS, LIST_TABLE_TOTAL_KEYS, MAX_BULK_SELECTION, MAX_EXPORT_MAX_ROWS, relatedListGroupHeaderLabel, relatedListGroupKey, } from "@cosmicdrift/kumiko-types/screen";
@@ -24,6 +24,7 @@
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
26
  import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
27
+ import { resolveTableName } from "../db/entity-table-meta.js";
27
28
  import { schemaTableName } from "../db/table-introspection.js";
28
29
  import { getStep } from "./define-step.js";
29
30
  import { buildPipelineSteps } from "./pipeline.js";
@@ -79,7 +80,7 @@ export function validateProjectionAllowlist(features) {
79
80
  const aggregateTables = new Map();
80
81
  for (const f of features) {
81
82
  for (const [entityName, entity] of Object.entries(f.entities ?? {})) {
82
- const tableName = entity.table ?? entityName;
83
+ const tableName = resolveTableName(entityName, entity, undefined);
83
84
  const existing = aggregateTables.get(tableName);
84
85
  if (existing && existing !== f.name) {
85
86
  throw new Error(`Aggregate-table "${tableName}" is registered by both feature "${existing}" and feature "${f.name}" via r.entity. ` +
@@ -10,7 +10,7 @@ import type { Lifecycle } from "../lifecycle/index.ts";
10
10
  import type { ObservabilityProvider } from "../observability/index.ts";
11
11
  import type { Dispatcher, DispatcherOptions } from "../pipeline/dispatcher.ts";
12
12
  import type { EventDedup, EventDispatcher } from "../pipeline/index.ts";
13
- import type { SystemHooks } from "../pipeline/lifecycle-pipeline.ts";
13
+ import type { LifecycleHooks, SystemHooks } from "../pipeline/lifecycle-pipeline.ts";
14
14
  export type BaseEntrypointOptions = {
15
15
  readonly registry: Registry;
16
16
  readonly context: AppContext;
@@ -21,6 +21,7 @@ export type BaseEntrypointOptions = {
21
21
  readonly systemHooks?: SystemHooks;
22
22
  readonly eventDedup?: EventDedup;
23
23
  readonly lifecycle?: Lifecycle;
24
+ readonly instanceId?: ServerOptions["instanceId"];
24
25
  };
25
26
  type JobsBlock = {
26
27
  readonly redisUrl: string;
@@ -49,6 +50,7 @@ export type ApiEntrypointOptions = BaseEntrypointOptions & {
49
50
  readonly runLocal?: boolean;
50
51
  };
51
52
  readonly trustedProxyHops?: ServerOptions["trustedProxyHops"];
53
+ readonly errorDocs?: ServerOptions["errorDocs"];
52
54
  };
53
55
  export type WorkerEntrypointOptions = BaseEntrypointOptions & JobsBlock & {
54
56
  readonly eventDispatcher?: ServerOptions["eventDispatcher"];
@@ -61,6 +63,8 @@ export type ApiEntrypoint = {
61
63
  readonly lifecycle: Lifecycle;
62
64
  readonly observability: ObservabilityProvider;
63
65
  readonly dispatcher: Dispatcher;
66
+ readonly context: AppContext;
67
+ readonly lifecycleHooks: LifecycleHooks;
64
68
  readonly eventDispatcher?: EventDispatcher;
65
69
  readonly mode: "api";
66
70
  start(): Promise<void>;
@@ -71,6 +75,7 @@ export type WorkerEntrypoint = {
71
75
  readonly eventDispatcher: EventDispatcher;
72
76
  readonly jobRunner: JobRunner;
73
77
  readonly observability: ObservabilityProvider;
78
+ readonly context: AppContext;
74
79
  readonly dispatcher: Dispatcher;
75
80
  readonly mode: "worker";
76
81
  start(): Promise<void>;
@@ -40,7 +40,8 @@
40
40
  // connected to any API instance via Redis Pub/Sub, no per-instance
41
41
  // cursor needed. Without REDIS_URL, the in-memory broker still only
42
42
  // reaches clients on the same process.
43
- import { buildServer, withFileProviderResolver } from "../api/server.js";
43
+ import { buildServer } from "../api/server.js";
44
+ import { withFileProviderResolver } from "../api/server-wiring.js";
44
45
  import { createJobRunner } from "../jobs/job-runner.js";
45
46
  import { createLifecycle } from "../lifecycle/index.js";
46
47
  import { createNoopProvider } from "../observability/index.js";
@@ -119,6 +120,8 @@ function buildApiServer(opts, lifecycle, dispatcherOverride, jobRunner, processL
119
120
  jwtIssuer: opts.jwtIssuer,
120
121
  auth: opts.auth,
121
122
  trustedProxyHops: opts.trustedProxyHops,
123
+ errorDocs: opts.errorDocs,
124
+ instanceId: opts.instanceId,
122
125
  anonymousAccess: opts.anonymousAccess,
123
126
  sseBroker: opts.sseBroker,
124
127
  rateLimit: opts.rateLimit,
@@ -145,6 +148,7 @@ function buildWorkerServer(opts, lifecycle, jobRunner) {
145
148
  jwtSecret: opts.jwtSecret,
146
149
  lifecycle,
147
150
  processLane: "worker",
151
+ instanceId: opts.instanceId,
148
152
  observability: opts.observability,
149
153
  dispatcherOptions,
150
154
  systemHooks: opts.systemHooks,
@@ -240,6 +244,8 @@ export function createApiEntrypoint(options) {
240
244
  lifecycle,
241
245
  observability: server.observability,
242
246
  dispatcher: server.dispatcher,
247
+ context: server.context,
248
+ lifecycleHooks: server.lifecycleHooks,
243
249
  ...(server.eventDispatcher && { eventDispatcher: server.eventDispatcher }),
244
250
  mode: "api",
245
251
  async start() {
@@ -274,6 +280,7 @@ export function createWorkerEntrypoint(options) {
274
280
  jobRunner,
275
281
  observability: server.observability,
276
282
  dispatcher: server.dispatcher,
283
+ context: server.context,
277
284
  mode: "worker",
278
285
  async start() {
279
286
  await eventDispatcher.start();
@@ -319,6 +326,8 @@ export function createAllInOneEntrypoint(options) {
319
326
  jobRunner: workerJobRunner,
320
327
  observability: server.observability,
321
328
  dispatcher: server.dispatcher,
329
+ context: server.context,
330
+ lifecycleHooks: server.lifecycleHooks,
322
331
  mode: "all-in-one",
323
332
  async start() {
324
333
  await eventDispatcher.start();
@@ -1,5 +1,5 @@
1
1
  import type { KumikoError } from "./kumiko-error.ts";
2
- /** Links an app's own error reasons to its own docs; without it only framework reasons carry a docsUrl. */
2
+ /** Links an app's own error reasons to its own docs; uncovered reasons fall back to the framework page of the error code. */
3
3
  export type ErrorDocsConfig = {
4
4
  readonly baseUrl: string;
5
5
  readonly reasons?: readonly string[] | "all";
@@ -5,6 +5,26 @@ const frameworkReasons = new Set([
5
5
  ...Object.values(FrameworkReasons),
6
6
  ...Object.values(AgentReasons),
7
7
  ]);
8
+ // Codes of the built-in error classes: only these have a framework docs page.
9
+ const frameworkErrorCodes = new Set([
10
+ "validation_error",
11
+ "feature_disabled",
12
+ "access_denied",
13
+ "unauthenticated",
14
+ "not_found",
15
+ "conflict",
16
+ "version_conflict",
17
+ "precondition_failed",
18
+ "record_locked",
19
+ "idempotent_replay",
20
+ "unique_violation",
21
+ "unprocessable",
22
+ "unconfigured",
23
+ "internal_error",
24
+ "member_read_timeout",
25
+ "rate_limited",
26
+ "rate_limit_unavailable",
27
+ ]);
8
28
  function reasonOf(err) {
9
29
  if (!err.details || typeof err.details !== "object")
10
30
  return undefined;
@@ -22,7 +42,7 @@ function errorsUrl(baseUrl, slug) {
22
42
  }
23
43
  // A reason the framework documents (or no reason, or the code itself) links to the framework docs, overridable via
24
44
  // KUMIKO_DOCS_URL. An app's own reason links to the app docs where `errorDocs` covers it; every
25
- // other reason falls back to the framework page of the error code, which exists for each code.
45
+ // other reason falls back to the framework page of the error code, unless an app subclass overrode the code.
26
46
  export function resolveErrorDocsUrl(err, errorDocs) {
27
47
  const reason = reasonOf(err);
28
48
  const frameworkBaseUrl = process.env["KUMIKO_DOCS_URL"] ?? DEFAULT_DOCS_BASE_URL;
@@ -32,5 +52,5 @@ export function resolveErrorDocsUrl(err, errorDocs) {
32
52
  if (errorDocs && isCoveredByConfig(reason, errorDocs)) {
33
53
  return errorsUrl(errorDocs.baseUrl, reason);
34
54
  }
35
- return errorsUrl(frameworkBaseUrl, err.code);
55
+ return frameworkErrorCodes.has(err.code) ? errorsUrl(frameworkBaseUrl, err.code) : undefined;
36
56
  }
@@ -43,7 +43,7 @@ export function createSeedMigrationContext(args) {
43
43
  if (!result.isSuccess) {
44
44
  const code = result.error?.code ?? "unknown";
45
45
  const message = result.error?.message ?? "(no message)";
46
- throw new Error(`[es-ops/seed-migration] systemWriteAs("${handlerQualifiedName}") failed: ${code} — ${message}`);
46
+ throw new Error(`systemWriteAs("${handlerQualifiedName}") failed: ${code} — ${message}`);
47
47
  }
48
48
  return result;
49
49
  },
@@ -6,10 +6,10 @@
6
6
  // path `@cosmicdrift/kumiko-framework/event-store/admin-api`. The guard script
7
7
  // `scripts/guard-admin-api.ts` blocks calls from application code. Allowlist: samples/*/migration/,
8
8
  // scripts/migrations/, this definition, and the guard script itself.
9
+ import { instantToTimestamptzParam } from "../db/driver-value-preparation.js";
9
10
  import { constraintOf, isUniqueViolation } from "../db/pg-error.js";
10
11
  import { claimXactId, runAtomicEventWrite } from "../db/queries/event-store.js";
11
12
  import { eventPredecessorExists, findExistingEventVersion, insertRawEventBatch, insertRawFirstEvent, insertRawSubsequentEvent, } from "../db/queries/event-store-admin.js";
12
- import { instantToTimestamptzParam } from "../db/query.js";
13
13
  import { IdempotentAppendConflictError, VersionConflictError } from "./errors.js";
14
14
  function mapEventUniqueViolation(e, event) {
15
15
  if (constraintOf(e) === "events_idempotency_uq" && event.metadata.idempotencyKey) {
@@ -19,6 +19,7 @@ export declare function append(db: DbRunner, event: EventToAppend): Promise<Stor
19
19
  export declare function loadAggregate(db: DbRunner, aggregateId: string, tenantId: TenantId, options?: {
20
20
  readonly includeArchived?: boolean;
21
21
  readonly aggregateType?: string;
22
+ readonly atVersion?: number;
22
23
  }): Promise<readonly StoredEvent[]>;
23
24
  export declare function loadAggregateForWriting(db: DbRunner, aggregateId: string, tenantId: TenantId, options: {
24
25
  readonly aggregateType: string;
@@ -3,9 +3,10 @@ import { isPersonalDataGated, UNATTRIBUTED_ORIGIN, } from "@cosmicdrift/kumiko-t
3
3
  import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
4
4
  import { requestContext } from "../api/request-context.js";
5
5
  import { encryptEventPayloadPii } from "../crypto/event-pii.js";
6
+ import { instantToTimestamptzParam } from "../db/driver-value-preparation.js";
6
7
  import { constraintOf, isUniqueViolation } from "../db/pg-error.js";
7
8
  import { claimXactId, insertFirstEventRow, insertSubsequentEventRow, notifyPgChannel, runAtomicEventWrite, selectAggregateMaxVersion, selectEventsHighWaterMark, selectStreamMaxVersion, selectStreamMaxVersions, } from "../db/queries/event-store.js";
8
- import { instantToTimestamptzParam, selectMany } from "../db/query.js";
9
+ import { selectMany } from "../db/query.js";
9
10
  import { isStreamArchived } from "./archive.js";
10
11
  import { IdempotentAppendConflictError, SeedTimeOrderError, VersionConflictError, } from "./errors.js";
11
12
  import { eventsTable } from "./events-schema.js";
@@ -14,8 +15,11 @@ import { currentSeedCreatedAt } from "./seed-clock.js";
14
15
  // Append one event atomically. Two guarantees combined:
15
16
  //
16
17
  // 1. UNIQUE (tenant_id, aggregate_id, version) serializes concurrent writers
17
- // within a tenant — a second writer racing the same expectedVersion
18
- // receives a PG unique violation (SQLSTATE 23505) → VersionConflictError.
18
+ // within a tenant. A second writer racing the same expectedVersion hits
19
+ // ON CONFLICT DO NOTHING (no row), then the stream version is read inside
20
+ // the still-intact transaction → VersionConflictError. That relies on
21
+ // READ COMMITTED; REPEATABLE READ/SERIALIZABLE raise 40001 instead. Only
22
+ // the idempotency index still raises 23505.
19
23
  // Cross-tenant aggregate_id collisions are not conflicts by definition:
20
24
  // two tenants owning a row with the same UUID is just isolation, not a
21
25
  // race.
@@ -28,7 +32,7 @@ import { currentSeedCreatedAt } from "./seed-clock.js";
28
32
  // stays semantically obvious when read in isolation.
29
33
  //
30
34
  // Creates (expectedVersion === 0) skip the predecessor check — no predecessor
31
- // exists yet. Colliding creates fall out via UNIQUE (tenant_id, aggregate_id, version=1).
35
+ // exists yet. Colliding creates fall out via the same index (tenant_id, aggregate_id, version=1).
32
36
  // Channel name used by append() → NOTIFY and the event-dispatcher → LISTEN
33
37
  // (Sprint E.4). The event-dispatcher subscribes to this channel on start and
34
38
  // fires a runOnce immediately on each commit, so delivery latency is bounded
@@ -66,6 +70,8 @@ export async function append(db, event) {
66
70
  }
67
71
  catch (e) {
68
72
  if (isUniqueViolation(e)) {
73
+ // The version index normally never throws (ON CONFLICT DO NOTHING); it
74
+ // only lands here for an unknown or renamed constraint.
69
75
  // Two unique constraints on this table: events_aggregate_version_uq
70
76
  // (tenant_id, aggregate_id, version) — a concurrent writer won the
71
77
  // race to the next version — and events_idempotency_uq (tenant_id,
@@ -207,9 +213,12 @@ export async function loadAggregate(db, aggregateId, tenantId, options) {
207
213
  if (archived)
208
214
  return [];
209
215
  }
210
- const rows = await selectMany(db, eventsTable, options?.aggregateType !== undefined
211
- ? { aggregateId, tenantId, aggregateType: options.aggregateType }
212
- : { aggregateId, tenantId }, { orderBy: { col: "version", direction: "asc" } });
216
+ const rows = await selectMany(db, eventsTable, {
217
+ aggregateId,
218
+ tenantId,
219
+ ...(options?.aggregateType !== undefined && { aggregateType: options.aggregateType }),
220
+ ...(options?.atVersion !== undefined && { version: options.atVersion }),
221
+ }, { orderBy: { col: "version", direction: "asc" } });
213
222
  return rows.map(toStoredEvent);
214
223
  }
215
224
  // Events and the stream's latest version from ONE read, so both come from the same
@@ -305,7 +314,7 @@ export async function loadEventsAfterVersion(db, aggregateId, tenantId, afterVer
305
314
  // abort iteration. Checked both at batch boundaries (before the next
306
315
  // fetch) AND between yields (so abort lands within one event regardless
307
316
  // of batch size). Throws AbortError on the first check after abort;
308
- // in-flight queries are not actively cancelled (postgres-js connection-
317
+ // in-flight queries are not actively cancelled (connection
309
318
  // cancel is a separate, riskier concern handled per-query at the TenantDb
310
319
  // boundary).
311
320
  export async function* streamAllEventsByType(db, aggregateType, batchSize = 1000, signal) {
@@ -32,7 +32,7 @@ export async function appendProvenanceEvent(db, event) {
32
32
  message: `appendProvenanceEvent: event tenant "${event.tenantId}" does not match the TenantDb's tenant "${db.tenantId}".`,
33
33
  });
34
34
  }
35
- // postgres-js aborts the whole surrounding begin() on a statement
35
+ // Postgres aborts the whole surrounding transaction on a statement
36
36
  // error (25P02) even when the caller's catch swallows it — the savepoint
37
37
  // confines that to this scope. Pool connections have no ambient tx to poison.
38
38
  await runInSavepointIfSupported(runner, async (sp) => {
@@ -13,6 +13,9 @@ export function fileRefIdsOf(value) {
13
13
  return value.filter((item) => typeof item === "string" && isUuid(item));
14
14
  }
15
15
  function isPendingUploadFor(row, entityName, fieldName, user) {
16
+ // Every anonymous visitor shares one user id, so insertedById cannot tell their uploads apart.
17
+ if (isAnonymousSessionUser(user))
18
+ return false;
16
19
  return (row["entityId"] === null &&
17
20
  row["entityType"] === entityName &&
18
21
  row["fieldName"] === fieldName &&
@@ -79,9 +82,6 @@ export async function bindPendingFileRefs(args) {
79
82
  }
80
83
  async function loadPendingRow(lookup) {
81
84
  const { fileRefId, entityName, fieldName, user, db } = lookup;
82
- // Every anonymous visitor shares one user id, so insertedById cannot tell their uploads apart.
83
- if (isAnonymousSessionUser(user))
84
- return undefined;
85
85
  if (!isUuid(fileRefId))
86
86
  return undefined;
87
87
  const row = (await loadFileRefRows(db, [fileRefId], user.tenantId)).get(fileRefId);
@@ -27,7 +27,10 @@ export async function createFileRefFromBytes(ctx, input) {
27
27
  registry: ctx.registry,
28
28
  defaultMaxUploadSize: readFilesRouteOptions(ctx.registry).maxUploadSize ?? DEFAULT_MAX_UPLOAD_SIZE,
29
29
  writeBytes: (key, data, mimeType) => files.writeUploadBytes(key, data, mimeType),
30
- deleteBytes: (key) => files.ref(key).delete(),
30
+ deleteBytes: async (key) => {
31
+ await files.ref(key).delete();
32
+ await files.forgetUploadBytes(key);
33
+ },
31
34
  }, {
32
35
  fileName: input.fileName,
33
36
  declaredMimeType: input.mimeType,
@@ -43,6 +46,9 @@ export async function createFileRefFromBytes(ctx, input) {
43
46
  return fileRejectedFailure(outcome.error);
44
47
  case "failed":
45
48
  return outcome.failure;
49
+ case "target-locked":
50
+ // Only the HTTP route supplies the lock re-check, so this path cannot produce it.
51
+ return writeFailure(new InternalError({ message: "createFileRefFromBytes: unexpected target-locked outcome" }));
46
52
  }
47
53
  }
48
54
  function fileRejectedFailure(error) {
@@ -50,6 +50,7 @@ export type StoreFileDeps = {
50
50
  readonly defaultMaxUploadSize: string;
51
51
  readonly writeBytes: (storageKey: string, data: Uint8Array, mimeType: string) => Promise<void>;
52
52
  readonly deleteBytes: (storageKey: string) => Promise<void>;
53
+ readonly isAttachTargetLocked?: (rowDb: TenantDb) => Promise<boolean>;
53
54
  };
54
55
  export type CreatedFileRef = {
55
56
  readonly id: string;
@@ -80,6 +81,8 @@ export type StoreFileOutcome = {
80
81
  } | {
81
82
  readonly kind: "rejected";
82
83
  readonly error: string;
84
+ } | {
85
+ readonly kind: "target-locked";
83
86
  } | {
84
87
  readonly kind: "failed";
85
88
  readonly failure: WriteFailure;
@@ -41,6 +41,8 @@ export function resolveAttachedField(registry, entityType, fieldName) {
41
41
  return { kind: "unresolvable" };
42
42
  return { kind: "resolved", fieldDef };
43
43
  }
44
+ class AttachTargetLockedError extends Error {
45
+ }
44
46
  function resolveUploadField(registry, input) {
45
47
  const { entityType, entityId, fieldName, actionForm } = input;
46
48
  if (actionForm === undefined)
@@ -196,14 +198,21 @@ export async function storeFileAsFileRef(deps, input) {
196
198
  // skip: the create failure is the error worth reporting; the leftover bytes stay in storage.
197
199
  }
198
200
  };
201
+ const createRow = async (rowDb) => {
202
+ if (await deps.isAttachTargetLocked?.(rowDb))
203
+ throw new AttachTargetLockedError();
204
+ return createFileRefRow({ user: deps.user, tenantDb: rowDb }, upload.uploaded);
205
+ };
199
206
  let row;
200
207
  try {
201
208
  row = deps.rowInOwnTransaction
202
- ? await runInOwnTransaction(deps.tenantDb, (txDb) => createFileRefRow({ user: deps.user, tenantDb: txDb }, upload.uploaded))
203
- : await createFileRefRow(deps, upload.uploaded);
209
+ ? await runInOwnTransaction(deps.tenantDb, createRow)
210
+ : await createRow(deps.tenantDb);
204
211
  }
205
212
  catch (e) {
206
213
  await deleteUploadedBytes();
214
+ if (e instanceof AttachTargetLockedError)
215
+ return { kind: "target-locked" };
207
216
  throw e;
208
217
  }
209
218
  if ("failure" in row) {
@@ -10,7 +10,7 @@
10
10
  // suffix before the file extension — `foo/bar.jpg` + `"medium-<16 hex>"` →
11
11
  // `foo/bar.medium-<16 hex>.jpg`. The suffix must be a `variantSuffix(...)` so
12
12
  // the erasure sweep recognizes it. Stable, reversible, no extra lookup tables.
13
- import { isDerivativeSuffix } from "../derivatives/variant-key.js";
13
+ import { derivativeListPrefix, isDerivativeSuffix } from "../derivatives/variant-key.js";
14
14
  import { InternalError } from "../errors/index.js";
15
15
  import { assertSafeStorageKey } from "./types.js";
16
16
  // `getProvider` is a lazily-resolved, memoized accessor — the provider is
@@ -127,6 +127,5 @@ export function deriveKey(key, suffix) {
127
127
  // fileRef's original plus every already-rendered derivative, none of which have their own
128
128
  // `file_refs` row.
129
129
  export function storageKeyStemPrefix(key) {
130
- const { stem, ext } = splitKeyExtension(key);
131
- return ext === "" ? `${key}.` : `${stem}.`;
130
+ return derivativeListPrefix(key);
132
131
  }
@@ -1,3 +1,4 @@
1
+ import type { RateLimitResolver } from "@cosmicdrift/kumiko-types/rate-limit-types";
1
2
  import type { WriteErrorInfo } from "@cosmicdrift/kumiko-types/write-error-info-types";
2
3
  import { Hono } from "hono";
3
4
  import type { DbConnection } from "../db/connection.ts";
@@ -30,6 +31,7 @@ export type FileAccessGuard = (args: {
30
31
  }) => FileAccessDecision | Promise<FileAccessDecision>;
31
32
  export type FileRoutesOptions = {
32
33
  readonly db: DbConnection;
34
+ readonly metaBatchRateLimitResolver?: RateLimitResolver;
33
35
  readonly resolveProvider: FileProviderResolver;
34
36
  readonly registry?: Registry;
35
37
  readonly maxUploadSize?: string;