@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,22 +1,22 @@
1
1
  import { createBigIntField, createEntity, createTextField } from "../engine/index.js";
2
- // fileRef — das File-Metadata-Entity. Ganz normales ES-Entity: Upload/Delete
3
- // laufen über den Standard-Executor (file-routes.ts), die Tabelle `file_refs`
4
- // wird via buildEntityTable aus dieser Definition gebaut.
2
+ // fileRef — the file-metadata entity. A standard ES entity: uploads/deletes
3
+ // run through the standard executor (file-routes.ts), and the `file_refs` table
4
+ // is built from this definition via buildEntityTable.
5
5
  //
6
- // softDelete: true — wie das `user`-Entity + die data-retention-Strategien.
7
- // Ein Delete markiert `isDeleted=true` (wiederherstellbar, kein "sofort weg");
8
- // echtes Erasure (Art. 17) läuft über den Forget-Hook + Retention-Cleanup.
6
+ // softDelete: true — as with the `user` entity and the data-retention strategies.
7
+ // A delete sets `isDeleted=true` (recoverable, not "gone immediately"); actual
8
+ // erasure (Art. 17) runs through the Forget hook and retention cleanup.
9
9
  //
10
- // `insertedAt`/`insertedById` sind framework-managed base columns (siehe
11
- // buildBaseColumns in table-builder.ts) und dürfen NICHT als Entity-Fields
12
- // dupliziert werden — fieldColumns gewinnen beim Merge, und die Field-Variante
13
- // ohne `.default(now()).notNull()` macht inserted_at still nullable.
10
+ // `insertedAt`/`insertedById` are framework-managed base columns (see
11
+ // buildBaseColumns in table-builder.ts) and must NOT be duplicated as entity
12
+ // fields — fieldColumns win during the merge, and the field variant without
13
+ // `.default(now()).notNull()` silently makes inserted_at nullable.
14
14
  //
15
- // PII-Annotations:
16
- // - fileName → personal: "self" (original filename often carries a personal
17
- // reference: "Marc-Lebenslauf.pdf", "Krankheitsattest-Mai.pdf"). The other
18
- // fields (storageKey, mimeType, entityType, entityId, fieldName) are
19
- // technical references or system metadata, not subject content.
15
+ // PII annotations:
16
+ // - fileName → personal: "self" (the original filename often carries a
17
+ // personal reference: "Marc-Lebenslauf.pdf", "Krankheitsattest-Mai.pdf").
18
+ // The other fields (storageKey, mimeType, entityType, entityId, fieldName)
19
+ // are technical references or system metadata, not subject content.
20
20
  export { FILE_REF_ENTITY_NAME } from "./types.js";
21
21
  export const fileRefEntity = createEntity({
22
22
  table: "file_refs",
@@ -1,12 +1,11 @@
1
1
  import { buildEntityTable } from "../db/table-builder.js";
2
2
  import { FILE_REF_ENTITY_NAME, fileRefEntity } from "./file-ref-entity.js";
3
- // `file_refs` ist die read-table des `fileRef`-Entity. Aus der Entity-
4
- // Definition gebaut (kein hand-gepflegtes pgTable mehr), damit die implizite
5
- // Entity-Projektion (Rebuild) und der Live-Executor-Write spaltengleich auf
6
- // dieselbe Tabelle schreiben — Single Source, keine Dual-Definition-Drift.
3
+ // `file_refs` is the read table for the `fileRef` entity. It is built from the entity
4
+ // definition rather than a manually maintained `pgTable`, so the implicit entity
5
+ // projection (rebuild) and live executor writes use the same columns and table—one
6
+ // source of truth, with no drift between duplicate definitions.
7
7
  //
8
- // `id` ist UUID und doppelt als Aggregate-Id des fileRef-Event-Streams:
9
- // jeder Upload appended `fileRef.created`, jeder Delete `fileRef.deleted`
10
- // (Standard-Entity-Auto-Verben). UUIDs schließen die Enumeration-Attacke
11
- // auf /files/:id.
8
+ // `id` is a UUID and also serves as the aggregate ID for the `fileRef` event stream:
9
+ // each upload appends `fileRef.created`, and each deletion appends `fileRef.deleted`
10
+ // (standard entity auto-verbs). UUIDs prevent enumeration attacks against /files/:id.
12
11
  export const fileRefsTable = buildEntityTable(FILE_REF_ENTITY_NAME, fileRefEntity);
@@ -1,9 +1,15 @@
1
+ import type { WriteErrorInfo } from "@cosmicdrift/kumiko-types/write-error-info-types";
1
2
  import { Hono } from "hono";
2
3
  import type { DbConnection } from "../db/connection.ts";
3
4
  import { type Registry, type SessionUser, type TenantId } from "../engine/types/index.ts";
4
5
  import type { FileProviderResolver } from "./provider-resolver.ts";
5
6
  import type { RecordReadCheck } from "./record-read-check.ts";
6
7
  import { type RecordWriteCheck } from "./record-write-check.ts";
8
+ declare const ERROR_STATUSES: readonly [400, 401, 403, 404, 409, 422, 429, 500, 503, 504];
9
+ export declare function deleteFailureResponseFor(error: WriteErrorInfo): {
10
+ readonly status: (typeof ERROR_STATUSES)[number];
11
+ readonly code: string;
12
+ };
7
13
  export type FileAccessDecision = "allow" | "deny";
8
14
  export type FileRef = {
9
15
  id: string;
@@ -36,3 +42,4 @@ export type FileRoutesOptions = {
36
42
  export declare function resolveMaxUploadBodyBytes(options: Pick<FileRoutesOptions, "registry" | "maxUploadSize">): number;
37
43
  export declare function readFilesRouteOptions(registry: Registry): Pick<FileRoutesOptions, "accessGuard" | "privilegedRoles" | "unclaimedUploadReaders" | "maxUploadSize">;
38
44
  export declare function createFileRoutes(options: FileRoutesOptions): Hono;
45
+ export {};
@@ -1,3 +1,4 @@
1
+ import { isOneOf } from "@cosmicdrift/kumiko-types/type-guards";
1
2
  import { Hono } from "hono";
2
3
  import { bodyLimit } from "hono/body-limit";
3
4
  import { getUser } from "../api/auth-middleware.js";
@@ -11,13 +12,19 @@ import { isFileField, isUuid, } from "../engine/types/index.js";
11
12
  import { listActionFormFileFields, mayUseActionForm } from "./action-form-upload.js";
12
13
  import { buildContentDispositionHeader } from "./content-disposition.js";
13
14
  import { DEFAULT_MAX_UPLOAD_SIZE, getFileRefExecutor, storeFileAsFileRef, } from "./create-file-ref.js";
14
- import { createFileContext } from "./file-handle.js";
15
+ import { createFileContext, createTenantFileContext } from "./file-handle.js";
15
16
  import { fileRefEntity } from "./file-ref-entity.js";
16
17
  import { fileRefsTable } from "./file-ref-table.js";
17
18
  import { RecordWriteDecisions } from "./record-write-check.js";
18
19
  import { neutralFileNameForMimeType } from "./resolve-content-type.js";
19
20
  import { parseMaxSize, resolveServedContentType } from "./types.js";
20
- import { createFileUploadLedger } from "./upload-ledger.js";
21
+ import { withUploadLedger } from "./upload-ledger.js";
22
+ // Exactly the httpStatus values declared by KumikoError subclasses across packages/*.
23
+ const ERROR_STATUSES = [400, 401, 403, 404, 409, 422, 429, 500, 503, 504];
24
+ export function deleteFailureResponseFor(error) {
25
+ const status = isOneOf(ERROR_STATUSES, error.httpStatus) ? error.httpStatus : 500;
26
+ return { status, code: status === 404 ? "not_found" : error.code };
27
+ }
21
28
  const DEFAULT_PRIVILEGED_ROLES = ["Admin", "SystemAdmin"];
22
29
  // 15 minutes — long enough for a download to start, short enough that a
23
30
  // leaked URL (e.g. from a browser history screenshot) isn't a long-lived
@@ -95,6 +102,10 @@ export function readFilesRouteOptions(registry) {
95
102
  }
96
103
  export function createFileRoutes(options) {
97
104
  const { db } = options;
105
+ // Plain resolvers (not built via withFileProviderResolver) get the ledger on the route's own root connection.
106
+ const resolveProvider = options.resolveProvider.uploadLedgerFor
107
+ ? options.resolveProvider
108
+ : withUploadLedger(options.resolveProvider, db);
98
109
  const privilegedRoles = options.privilegedRoles ?? DEFAULT_PRIVILEGED_ROLES;
99
110
  const guard = options.accessGuard ??
100
111
  createDefaultGuard(privilegedRoles, options.canReadRecord, options.unclaimedUploadReaders ?? []);
@@ -193,19 +204,18 @@ export function createFileRoutes(options) {
193
204
  }
194
205
  }
195
206
  const data = new Uint8Array(await file.arrayBuffer());
196
- const storageProvider = await options.resolveProvider(user.tenantId);
197
- const uploadLedger = createFileUploadLedger(db, user.tenantId);
207
+ const files = createTenantFileContext(resolveProvider, user.tenantId);
198
208
  const outcome = await storeFileAsFileRef({
199
209
  user,
200
210
  tenantDb: tenantDbFor(user.tenantId),
201
211
  rowInOwnTransaction: true,
202
212
  registry: options.registry,
203
213
  defaultMaxUploadSize: options.maxUploadSize ?? DEFAULT_MAX_UPLOAD_SIZE,
204
- writeBytes: async (key, bytes, mimeType) => {
205
- await uploadLedger.record(key);
206
- await storageProvider.write(key, bytes, mimeType);
214
+ writeBytes: (key, bytes, mimeType) => files.writeUploadBytes(key, bytes, mimeType),
215
+ deleteBytes: async (key) => {
216
+ await files.ref(key).delete();
217
+ await files.forgetUploadBytes(key);
207
218
  },
208
- deleteBytes: (key) => storageProvider.delete(key),
209
219
  }, {
210
220
  fileName: file.name,
211
221
  declaredMimeType: file.type,
@@ -281,7 +291,7 @@ export function createFileRoutes(options) {
281
291
  // GET /files/:id/variant/:name — a named derived version of the file.
282
292
  // Same tenant + guard gate as the download above; the original stays
283
293
  // reachable only through that route. The public, anonymous counterpart is
284
- // a separate cut (#1951) and does not go through createFileRoutes.
294
+ // a separate cut and does not go through createFileRoutes.
285
295
  api.get("/files/:id/variant/:name", async (c) => {
286
296
  const user = getUser(c);
287
297
  const id = c.req.param("id");
@@ -358,9 +368,8 @@ export function createFileRoutes(options) {
358
368
  // pattern from the access-deny path above. Everything else (version
359
369
  // conflict 409, ownership 403, validation 422, internal 500) gets the
360
370
  // real status off the error so callers can distinguish recoverable
361
- // from terminal — pauschales 404 hier hat genau das verschleiert.
362
- const status = result.error.httpStatus;
363
- const code = status === 404 ? "not_found" : result.error.code;
371
+ // from terminal — a blanket 404 here had obscured exactly that.
372
+ const { status, code } = deleteFailureResponseFor(result.error);
364
373
  return c.json({ error: code }, status);
365
374
  }
366
375
  return c.json({ ok: true });
@@ -411,7 +420,7 @@ export function createFileRoutes(options) {
411
420
  return c.json({ url, expiresAt });
412
421
  });
413
422
  // Metadata without the bytes. Unlike the byte-serving routes, this returns
414
- // fileName as JSON — and fileName is `personal: "self"` (fw#2423), so it must
423
+ // fileName as JSON — and fileName is `personal: "self"`, so it must
415
424
  // go through the entity executor's detail() to decrypt instead of
416
425
  // loadFileForTenant's raw, still-encrypted read.
417
426
  async function loadReadableFileMeta(id, user) {
@@ -12,9 +12,8 @@ export function createInMemoryFileProvider() {
12
12
  store.set(key, { data: new Uint8Array(data), mimeType });
13
13
  },
14
14
  async writeStream(key, source, options) {
15
- // In-Memory hat kein "Streaming" im real-physikalischen Sinn —
16
- // wir collecten chunks in einen Uint8Array. Test-Tauglich, kein
17
- // Production-Pfad.
15
+ // In-memory storage does not provide real streaming; it collects chunks
16
+ // into a Uint8Array. Suitable for tests, not a production path.
18
17
  const chunks = [];
19
18
  let total = 0;
20
19
  for await (const chunk of source) {
@@ -36,12 +35,12 @@ export function createInMemoryFileProvider() {
36
35
  return new Uint8Array(entry.data);
37
36
  },
38
37
  readStream(key) {
39
- // In-Memory hat technisch keine Chunks, aber das Surface muss
40
- // identisch zu echten Streamern (Local/S3) sein damit Test-Code
41
- // den Pfad genauso geht. Wir yielden die Bytes als single-chunk.
42
- // Map-lookup ist O(1), also kein Verlust durch eager-resolve.
43
- // Throw passiert beim ersten chunk-pull — gleicher Lazy-Pattern
44
- // wie S3 (request abgesetzt beim ersten Iterator-Step).
38
+ // In-memory storage has no chunks, but the surface must match real
39
+ // streamers (Local/S3) so test code exercises the same path. Yield the
40
+ // bytes as a single chunk.
41
+ // The O(1) map lookup avoids a cost from eager resolution.
42
+ // Errors are thrown on the first chunk pull, matching S3's lazy pattern
43
+ // (the request starts on the first iterator step).
45
44
  const entry = store.get(key);
46
45
  const captured = entry ? new Uint8Array(entry.data) : null;
47
46
  return {
@@ -33,16 +33,12 @@ export function createLocalProvider(basePath) {
33
33
  await writeFile(filePath, data);
34
34
  },
35
35
  async writeStream(key, source, _options) {
36
- // Atomar via tmp-File + rename: ein Reader der den finalen Pfad
37
- // sieht, sieht entweder die alte Version (falls vorhanden) oder
38
- // die vollstaendige neue. Niemals einen halb-fertigen Stream.
39
- // Falls der Stream mid-write bricht, ist das tmp-Cleanup
40
- // best-effort — je nach OS-Race im stream-destroy-Pfad kann
41
- // das `.tmp`-File kurz liegen bleiben. Kein Korrektheits-
42
- // Problem (kein Reader sucht `*.tmp`-Patterns), nur Operations-
43
- // Hygiene; ein periodischer cron-cleanup auf alten `.tmp`-Files
44
- // ist die saubere Loesung wenn das in Production realistisch
45
- // greift.
36
+ // Atomic via a temporary file and rename: readers see either the previous version (if any) or
37
+ // the complete new version, never a partially written stream. If the stream fails during
38
+ // writing, temporary-file cleanup is best-effort; an OS race in stream destruction can leave
39
+ // the `.tmp` file briefly. This affects operational hygiene only, because readers never
40
+ // search `*.tmp` patterns. A periodic cleanup of old `.tmp` files is appropriate if this runs
41
+ // in production.
46
42
  const filePath = resolveContainedPath(key);
47
43
  const tmpPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
48
44
  await mkdir(dirname(filePath), { recursive: true });
@@ -51,8 +47,8 @@ export function createLocalProvider(basePath) {
51
47
  await rename(tmpPath, filePath);
52
48
  }
53
49
  catch (e) {
54
- // Best-effort tmp-Cleanup; wenn das auch failt, hat der
55
- // Filesystem ein Problem das nicht in unserem Scope liegt.
50
+ // Best-effort temporary-file cleanup; if that also fails, the filesystem problem is outside
51
+ // this component's scope.
56
52
  await unlink(tmpPath).catch(() => { });
57
53
  throw e;
58
54
  }
@@ -62,24 +58,21 @@ export function createLocalProvider(basePath) {
62
58
  return readFile(filePath);
63
59
  },
64
60
  readStream(key) {
65
- // node:fs createReadStream ist ein AsyncIterable<Buffer>; Buffer
66
- // extends Uint8Array. Wir bauen einen kleinen Adapter weil
67
- // @types/node das asyncIterator als AsyncIterableIterator<any>
68
- // typt — Adapter sichert die Surface auf Uint8Array.
69
- // Default-highWaterMark = 64KB Chunks, was fuer ZIP-Stream-Konsum
70
- // gut ist. Errors landen im for-await-Loop des Konsumenten
71
- // (z.B. ENOENT bei Missing-File faellt erst beim ersten chunk-
72
- // pull, nicht beim readStream-Aufruf — gleiches Lazy-Verhalten
73
- // wie inmemory + S3).
61
+ // node:fs createReadStream is an AsyncIterable<Buffer>; Buffer extends Uint8Array. This
62
+ // adapter preserves a Uint8Array surface because @types/node types the async iterator as
63
+ // AsyncIterableIterator<any>. The default highWaterMark produces 64KB chunks, which suits ZIP
64
+ // stream consumption. Errors reach the consumer's for-await loop; for example, a missing file
65
+ // raises ENOENT on the first chunk pull rather than when readStream is called, matching the
66
+ // lazy behavior of in-memory and S3 providers.
74
67
  const filePath = resolveContainedPath(key);
75
68
  const stream = createReadStream(filePath);
76
69
  return {
77
70
  async *[Symbol.asyncIterator]() {
78
71
  for await (const chunk of stream) {
79
- // Stream ohne encoding liefert Buffer. Buffer extends Uint8Array,
80
- // aber @types/node typt asyncIterator als string|Buffer. View
81
- // ohne copy auf dasselbe ArrayBuffer; runtime-check schliesst
82
- // den string-Branch aus (Stream wurde nicht mit encoding= gesetzt).
72
+ // A stream without an encoding yields Buffer. Buffer extends Uint8Array, but
73
+ // @types/node types the async iterator as string|Buffer. Take a zero-copy view
74
+ // onto the same ArrayBuffer; the runtime check rules out the string branch
75
+ // because the stream was not opened with an encoding.
83
76
  if (typeof chunk === "string") {
84
77
  throw new Error("local-provider readStream: unexpected string chunk (encoding leaked)");
85
78
  }
@@ -36,13 +36,13 @@ export type FileProviderContext = {
36
36
  * file-provider-inmemory, ...) registers an implementation via
37
37
  * `r.useExtension(EXT_FILE_PROVIDER, "<name>", { build })`.
38
38
  *
39
- * **Plugin-Author-Warnung:** `ctx` ist EXPLIZIT ein FileProviderContext, nicht
40
- * ein voller HandlerContext. Felder ausserhalb der schmalen Surface (z.B.
41
- * `ctx.tx`, `ctx.actor`, `ctx.signal`) sind im Worker-Pfad NICHT vorhanden.
42
- * Cast `ctx as unknown as HandlerContext` fliegt zur Runtime im Worker — der
43
- * Crash kommt erst in production mit dem ersten S3-Tenant. Braucht ein Plugin
44
- * Felder ausserhalb FileProviderContext: lieber FileProviderContext explizit
45
- * erweitern (sichtbarer breaking change) als ctx-cast.
39
+ * **Plugin-Author-Warning:** `ctx` is EXPLICITLY a FileProviderContext, not
40
+ * a full HandlerContext. Fields outside the narrow surface (e.g.
41
+ * `ctx.tx`, `ctx.actor`, `ctx.signal`) are NOT available on the worker path.
42
+ * Casting `ctx as unknown as HandlerContext` fails at runtime on the worker —
43
+ * the crash only appears in production with the first S3 tenant. If a plugin
44
+ * needs fields outside FileProviderContext, extend FileProviderContext
45
+ * explicitly (a visible breaking change) rather than casting `ctx`.
46
46
  */
47
47
  export type FileProviderPlugin = {
48
48
  readonly build: (ctx: FileProviderContext, tenantId: string) => Promise<FileStorageProvider>;
@@ -46,6 +46,49 @@ const NON_XML_ROOT_ELEMENTS = new Set([
46
46
  "article",
47
47
  "font",
48
48
  "center",
49
+ "form",
50
+ "iframe",
51
+ "input",
52
+ "button",
53
+ "nav",
54
+ "header",
55
+ "footer",
56
+ "main",
57
+ "pre",
58
+ "code",
59
+ "b",
60
+ "i",
61
+ "strong",
62
+ "em",
63
+ "label",
64
+ "textarea",
65
+ "select",
66
+ "fieldset",
67
+ "legend",
68
+ "tr",
69
+ "td",
70
+ "th",
71
+ "thead",
72
+ "tbody",
73
+ "tfoot",
74
+ "object",
75
+ "embed",
76
+ "video",
77
+ "audio",
78
+ "canvas",
79
+ "math",
80
+ "picture",
81
+ "figure",
82
+ "figcaption",
83
+ "blockquote",
84
+ "dl",
85
+ "dt",
86
+ "dd",
87
+ "aside",
88
+ "details",
89
+ "summary",
90
+ "dialog",
91
+ "noscript",
49
92
  ]);
50
93
  const ROOT_ELEMENT_PATTERN = /^<(?:[A-Za-z_][\w.-]*:)?([A-Za-z_][\w.-]*)/;
51
94
  const EXTENSION_FALLBACK = {
@@ -1,7 +1,7 @@
1
1
  // Tenant storage usage — counts bytes + files per tenant from the event log.
2
2
  //
3
- // Tracking-only for Phase 1: no hard limit, no upload gatekeeping. Apps read
4
- // the row to decide what to do (show a warning, soft-throttle, bill, …).
3
+ // Tracking-only: no hard limit, no upload gatekeeping. Apps read the row to
4
+ // decide what to do (show a warning, soft-throttle, bill, …).
5
5
  // Enforcement is a conscious deferred call — we want production numbers
6
6
  // before picking thresholds (see "Storage tracking: counted now, enforced
7
7
  // later" in core-files.md).
@@ -1,6 +1,8 @@
1
1
  import { defineUnmanagedTable } from "../db/entity-table-meta.js";
2
+ import { runnerCanBeginTransaction } from "../db/query.js";
2
3
  import { createTenantDb } from "../db/tenant-db.js";
3
4
  import { tenantDbRunner } from "../db/tenant-db-runner.js";
5
+ import { InternalError } from "../errors/index.js";
4
6
  const CREATED_AT_INDEX = "store_file_upload_ledger_created_at_idx";
5
7
  // One row per upload whose bytes are in storage but whose fileRef row is not committed yet. The
6
8
  // commit path deletes it inside the transaction that creates the fileRef; the sweep finds the
@@ -21,6 +23,11 @@ function isTenantDb(db) {
21
23
  // handler must not take the row with it, or the bytes it uploaded would be unfindable.
22
24
  export function createFileUploadLedger(db, tenantId) {
23
25
  const runner = isTenantDb(db) ? tenantDbRunner(db) : db;
26
+ if (!runnerCanBeginTransaction(runner)) {
27
+ throw new InternalError({
28
+ message: "createFileUploadLedger: the connection is already inside a transaction; the ledger row would roll back with it and the uploaded bytes become unfindable",
29
+ });
30
+ }
24
31
  const ledgerDb = createTenantDb({ db: runner, tenantId });
25
32
  return {
26
33
  record: async (storageKey) => {
@@ -1,31 +1,24 @@
1
1
  import { getTemporal } from "../time/index.ts";
2
2
  export interface ZipEntry {
3
- /** Pfad im ZIP, slash-separated. ASCII (kein UTF-8-Flag gesetzt). */
3
+ /** Path inside the ZIP, slash-separated and UTF-8 encoded (general-purpose flag bit 11). */
4
4
  readonly path: string;
5
- /** Body als AsyncIterable — kann lazy generated sein (kein Upfront-Memory). */
5
+ /** Body as an AsyncIterable; it may be generated lazily to avoid upfront memory use. */
6
6
  readonly data: AsyncIterable<Uint8Array>;
7
7
  /**
8
- * Optional Modification-Time. Default = Now. ZIP nutzt MS-DOS-Format
9
- * (2-Sek-Aufloesung). Wer Audit-Trail-Zeitpunkte haben will, setzt
10
- * den Generation-Timestamp ueber alle Entries.
8
+ * Optional modification time. Defaults to the current time and uses the ZIP
9
+ * MS-DOS format with two-second precision. Set the generation timestamp on
10
+ * every entry when consistent audit-trail timestamps are required.
11
11
  */
12
12
  readonly mtime?: InstanceType<ReturnType<typeof getTemporal>["Instant"]>;
13
13
  }
14
14
  /**
15
- * Erzeugt einen ZIP-Stream als `AsyncIterable<Uint8Array>`. Caller (z.B.
16
- * `FileStorageProvider.writeStream`) konsumiert chunk-fuer-chunk, kein
17
- * Entry haengt im Memory bis fertig.
15
+ * Creates a ZIP stream as `AsyncIterable<Uint8Array>`. The caller, such as
16
+ * `FileStorageProvider.writeStream`, consumes the output chunk by chunk.
18
17
  *
19
- * Streaming-Lifecycle pro Entry:
20
- * 1. Local File Header (mit CRC32+size=0 als Placeholder; wir nutzen
21
- * KEINE Streaming-Data-Descriptor weil Storage-Provider Random-
22
- * Access nicht garantiert — wir collecten den Body in Memory pro
23
- * Entry um CRC + size **vor** dem Header zu kennen.
24
- * Trade-off: 1 Entry-Body im Memory at-a-time; bei <100 MB Files
25
- * OK, bei >1 GB single-Files braeuchte ZIP64+Streaming-Descriptor).
26
- * 2. File Body (raw bytes)
27
- * Am Ende:
28
- * 3. Central Directory (eine Liste-Entry pro File mit CRC + size)
29
- * 4. EOCD (End Of Central Directory)
18
+ * Each entry's body is collected in memory before its local file header is
19
+ * emitted because non-seekable storage providers cannot use a streaming data
20
+ * descriptor, and the header must contain the CRC32 and size. This limits
21
+ * memory use to one entry body at a time; ZIP64 plus streaming descriptors
22
+ * would be needed for larger single files.
30
23
  */
31
24
  export declare function createZipStream(entries: AsyncIterable<ZipEntry>): AsyncIterable<Uint8Array>;
@@ -1,27 +1,22 @@
1
1
  import { getTemporal } from "../time/index.js";
2
- // Streaming-ZIP-Builder (S2.U3 Atom 3a) — pure-JS, dependency-frei.
2
+ // Pure-JS streaming ZIP builder without dependencies.
3
3
  //
4
- // **Format-Wahl: STORE (kein DEFLATE).** Compression-frei + minimal —
5
- // fuer User-Data-Export-Bundles akzeptabel weil:
6
- // - Storage-Provider (S3/R2) komprimiert eh on-the-wire (HTTP gzip)
7
- // - JSON-Snippets sind klein, File-Binaries sind oft schon komprimiert
8
- // (PNG/JPG/PDF/MP4/...)
9
- // - DEFLATE braucht zlib + buffering pro Entry, killt das Streaming-
10
- // Property
4
+ // **Format: STORE (no DEFLATE).** Avoiding compression keeps the implementation minimal:
5
+ // - Storage providers (S3/R2) compress data on the wire (HTTP gzip).
6
+ // - JSON snippets are small, and file binaries are often already compressed
7
+ // (PNG/JPG/PDF/MP4/...).
8
+ // - DEFLATE requires zlib and per-entry buffering, which defeats streaming.
11
9
  //
12
- // **Streaming-Property:** Caller gibt `AsyncIterable<ZipEntry>`, jeder
13
- // Entry hat `data: AsyncIterable<Uint8Array>`. ZIP-Bytes werden
14
- // chunk-fuer-chunk yieldet — kein Entry haengt im Memory bis er fertig
15
- // ist. Storage-Provider's writeStream konsumiert direkt durch.
10
+ // **Streaming:** The caller provides `AsyncIterable<ZipEntry>`, and each entry
11
+ // has `data: AsyncIterable<Uint8Array>`. ZIP bytes are yielded chunk by chunk.
12
+ // The storage provider's writeStream consumes them directly.
16
13
  //
17
- // **ZIP-Standard:** APPNOTE.TXT v6.3.10 (April 2022). STORE-Method,
18
- // kein ZIP64 (max 4 GB pro Entry, max 65535 Entries — fuer User-Daten-
19
- // Exports ausreichend; ZIP64-Support kommt wenn ein realer User mit
20
- // >4 GB single-File auftaucht). Kein UTF-8-Flag (filenames sind ASCII
21
- // in unserem Use-Case: "profile.json", "files/<id>.<ext>").
14
+ // **ZIP standard:** APPNOTE.TXT v6.3.10 (April 2022). STORE method, no ZIP64
15
+ // (maximum 4 GB per entry and 65535 entries). ZIP64 support is needed before
16
+ // exporting a single file larger than 4 GB. The UTF-8 flag is enabled because
17
+ // filenames may contain non-ASCII characters.
22
18
  //
23
- // **CRC32:** klassisches IEEE-802.3-Polynom (0xEDB88320). Lookup-Table
24
- // einmal initialisiert + cached.
19
+ // **CRC32:** Classic IEEE-802.3 polynomial (0xEDB88320), with a cached lookup table.
25
20
  const ZIP_VERSION_NEEDED = 20; // 2.0 = STORE
26
21
  const ZIP_METHOD_STORE = 0;
27
22
  // Local file header signature: 0x04034b50 = "PK\x03\x04"
@@ -31,50 +26,38 @@ const CENTRAL_DIR_HEADER_SIG = 0x02014b50;
31
26
  // End of central directory record signature: 0x06054b50 = "PK\x05\x06"
32
27
  const EOCD_SIG = 0x06054b50;
33
28
  const VERSION_MADE_BY = 0x031e; // 0x03 = UNIX, 0x1e = ZIP 3.0
34
- // General Purpose Flag Bit 11 (0x0800) = UTF-8 filename + comment encoding.
35
- // Wird default-on gesetzt: ASCII ist gueltiges UTF-8, also Win-Win.
36
- // Ohne Flag interpretieren aeltere ZIP-Tools filenames als CP437 — bei
37
- // Umlauten ("Bügel.pdf") wird das Mojibake.
29
+ // General Purpose Flag Bit 11 (0x0800) enables UTF-8 encoding for filenames and comments.
30
+ // ASCII is valid UTF-8, while omitting the flag makes older ZIP tools interpret
31
+ // filenames as CP437, producing mojibake for non-ASCII characters such as umlauts.
38
32
  const GENERAL_PURPOSE_FLAGS = 0x0800;
39
- // External-Attrs (UNIX-Mode in High-Bytes) fuer regulaere Dateien mit
40
- // 0644-Permissions: `(S_IFREG | 0644) << 16 = 0o100644 << 16 = 0x81a40000`.
41
- // S_IFREG = 0o100000 (regular file), 0o644 = rw-r--r--. Ohne diese
42
- // Bits entpackt Info-ZIP mit Mode 0000 (EACCES beim Read).
33
+ // Store UNIX file permissions in the high bytes of the external attributes.
34
+ // Without `(S_IFREG | 0644) << 16 = 0o100644 << 16 = 0x81a40000`, Info-ZIP
35
+ // extracts regular files with mode 0000, causing EACCES when they are read.
43
36
  const UNIX_REGULAR_FILE_0644 = 0o100644 << 16; // 0x81a40000
44
- // ZIP-Format-Limits ohne ZIP64-Extension. Atom 3a-Scope ist STORE-only;
45
- // ZIP64 kommt wenn ein realer User-Export diese Caps reisst.
37
+ // STORE without ZIP64 is limited to 4 GB per entry and 65535 entries.
38
+ // ZIP64 support is required before these limits can be exceeded.
46
39
  const ZIP_MAX_ENTRY_SIZE = 0xffffffff; // uint32 → 4 GB
47
40
  const ZIP_MAX_ENTRIES = 0xffff; // uint16 → 65535
48
41
  /**
49
- * Erzeugt einen ZIP-Stream als `AsyncIterable<Uint8Array>`. Caller (z.B.
50
- * `FileStorageProvider.writeStream`) konsumiert chunk-fuer-chunk, kein
51
- * Entry haengt im Memory bis fertig.
42
+ * Creates a ZIP stream as `AsyncIterable<Uint8Array>`. The caller, such as
43
+ * `FileStorageProvider.writeStream`, consumes the output chunk by chunk.
52
44
  *
53
- * Streaming-Lifecycle pro Entry:
54
- * 1. Local File Header (mit CRC32+size=0 als Placeholder; wir nutzen
55
- * KEINE Streaming-Data-Descriptor weil Storage-Provider Random-
56
- * Access nicht garantiert — wir collecten den Body in Memory pro
57
- * Entry um CRC + size **vor** dem Header zu kennen.
58
- * Trade-off: 1 Entry-Body im Memory at-a-time; bei <100 MB Files
59
- * OK, bei >1 GB single-Files braeuchte ZIP64+Streaming-Descriptor).
60
- * 2. File Body (raw bytes)
61
- * Am Ende:
62
- * 3. Central Directory (eine Liste-Entry pro File mit CRC + size)
63
- * 4. EOCD (End Of Central Directory)
45
+ * Each entry's body is collected in memory before its local file header is
46
+ * emitted because non-seekable storage providers cannot use a streaming data
47
+ * descriptor, and the header must contain the CRC32 and size. This limits
48
+ * memory use to one entry body at a time; ZIP64 plus streaming descriptors
49
+ * would be needed for larger single files.
64
50
  */
65
51
  export async function* createZipStream(entries) {
66
52
  const centralDirRecords = [];
67
- let offset = 0; // Bytes-Counter fuer central-dir entry's local-header-offset
53
+ let offset = 0; // Tracks the local-header offset for each central-directory entry
68
54
  for await (const entry of entries) {
69
- // Body in Memory collecten — wir brauchen CRC32 + size VOR dem
70
- // local-file-header (kein streaming-data-descriptor weil
71
- // file-storage-providers nicht zwingend seek-able sind, aber wir
72
- // wollen vermeiden dass der konsumierende Storage zwei-pass laesst).
55
+ // Collect the body before writing the local file header: non-seekable storage
56
+ // providers cannot use a streaming data descriptor, and a two-pass read
57
+ // would be required otherwise.
73
58
  const body = await collectBody(entry.data);
74
- // Hard-Limit: STORE-Mode ohne ZIP64-Extension cappt bei uint32 (4 GB).
75
- // Bei Ueberschreitung wuerde der Integer-Wrap silent ein korruptes
76
- // ZIP produzieren (Decoder lesen Muell-Sizes). Lieber early-fail mit
77
- // klarer Begruendung — Worker (Atom 3b) fängt das + setzt Job=failed.
59
+ // STORE without ZIP64 uses uint32 sizes. Fail early instead of allowing integer
60
+ // wraparound to produce a corrupt ZIP with incorrect sizes.
78
61
  if (body.byteLength > ZIP_MAX_ENTRY_SIZE) {
79
62
  throw new Error(`ZIP-Entry "${entry.path}" exceeds 4 GB limit (${body.byteLength} bytes). ` +
80
63
  `STORE-mode without ZIP64-extension caps at uint32. ` +
@@ -105,8 +88,7 @@ export async function* createZipStream(entries) {
105
88
  lfh.set(filenameBytes, 30);
106
89
  yield lfh;
107
90
  yield body;
108
- // Central-Directory-Eintrag fuer diesen Entry — wir collecten ihn,
109
- // emittieren ihn am Ende.
91
+ // Keep central-directory entries until all file bodies have been emitted.
110
92
  const cdh = new Uint8Array(46 + filenameBytes.byteLength);
111
93
  const cdhView = new DataView(cdh.buffer);
112
94
  cdhView.setUint32(0, CENTRAL_DIR_HEADER_SIG, true);
@@ -130,7 +112,7 @@ export async function* createZipStream(entries) {
130
112
  centralDirRecords.push(cdh);
131
113
  offset += lfh.byteLength + body.byteLength;
132
114
  }
133
- // Central Directory: alle entries hintereinander
115
+ // Central directory: all entries back to back.
134
116
  const centralDirStart = offset;
135
117
  let centralDirSize = 0;
136
118
  for (const record of centralDirRecords) {
@@ -189,15 +171,14 @@ function crc32(data) {
189
171
  }
190
172
  return (crc ^ 0xffffffff) >>> 0;
191
173
  }
192
- // MS-DOS Date+Time encoding fuer ZIP-Header.
193
- // Date: bits 9-15=year-1980, 5-8=month, 0-4=day.
194
- // Time: bits 11-15=hour, 5-10=minute, 0-4=second/2.
174
+ // MS-DOS date and time encoding for ZIP headers.
175
+ // Date: bits 9-15 = year - 1980, 5-8 = month, 0-4 = day.
176
+ // Time: bits 11-15 = hour, 5-10 = minute, 0-4 = second / 2.
195
177
  //
196
- // **UTC** statt lokaler Zeitzone: in einem DSGVO-Kontext ist der
197
- // ZIP-mtime Teil des Auskunfts-Artefakts. Verschiedene Server-Zeitzonen
198
- // wuerden sonst verschiedene mtime-Werte fuer denselben Generation-
199
- // Instant produzieren — Audit-Drift. UTC ist der Standard fuer alle
200
- // server-side-Timestamps im Repo (Temporal.Instant ueberall).
178
+ // **UTC** instead of the local time zone: in a GDPR context the ZIP mtime is
179
+ // part of the data-access export. Different server time zones would otherwise produce
180
+ // different values for the same generation instant, causing audit drift. UTC is
181
+ // used for server-side timestamps throughout the repository.
201
182
  function toDosTime(i) {
202
183
  const dt = i.toZonedDateTimeISO("UTC");
203
184
  const year = dt.year;
@@ -16,7 +16,7 @@ export declare function booleanFacetOptionKeys(featureName: string, entityName:
16
16
  export declare function selectFacetOptionKey(featureName: string, entityName: string, fieldName: string, value: string): string;
17
17
  export declare function screenTitleKey(screenId: string): string;
18
18
  export type RequiredKeysOptions = {
19
- /** Bypass `isI18nKey`'s colon-only check for generated dot-form labels (fw#2260). */
19
+ /** Bypass `isI18nKey`'s colon-only check for generated dot-form labels. */
20
20
  readonly treatDotFormAsKey?: boolean;
21
21
  };
22
22
  export declare function requiredKeysFromScreen(featureName: string, screen: ScreenDefinition, options?: RequiredKeysOptions): readonly string[];