@cosmicdrift/kumiko-framework 0.385.0 → 0.387.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 (298) hide show
  1. package/LICENSE +21 -57
  2. package/README.md +2 -2
  3. package/agents/widgets.md +39 -39
  4. package/dist/api/api-constants.js +2 -2
  5. package/dist/api/auth-middleware.js +7 -7
  6. package/dist/api/auth-rate-limiters.js +7 -3
  7. package/dist/api/auth-routes-mfa.js +1 -1
  8. package/dist/api/auth-routes-session.js +1 -1
  9. package/dist/api/auth-routes-shared.js +7 -3
  10. package/dist/api/auth-routes-signup.js +1 -1
  11. package/dist/api/client-ip.js +3 -1
  12. package/dist/api/extra-route.js +1 -1
  13. package/dist/api/pii-leak-guard.js +4 -2
  14. package/dist/api/readiness.js +5 -5
  15. package/dist/api/redis-sse-broker.js +8 -6
  16. package/dist/api/request-context.d.ts +2 -0
  17. package/dist/api/request-context.js +16 -7
  18. package/dist/api/routes.js +2 -2
  19. package/dist/api/server.js +34 -33
  20. package/dist/api/sse-broker.js +1 -1
  21. package/dist/api/sse-route.d.ts +13 -13
  22. package/dist/api/sse-route.js +15 -15
  23. package/dist/auth/index.js +4 -4
  24. package/dist/auth/roles.d.ts +4 -4
  25. package/dist/auth/roles.js +24 -24
  26. package/dist/compliance/duration-spec.js +18 -20
  27. package/dist/compliance/index.js +2 -3
  28. package/dist/compliance/override-schema.d.ts +4 -4
  29. package/dist/compliance/override-schema.js +19 -22
  30. package/dist/compliance/profiles.d.ts +26 -29
  31. package/dist/compliance/profiles.js +62 -69
  32. package/dist/compliance/sub-processors.d.ts +28 -30
  33. package/dist/compliance/sub-processors.js +17 -21
  34. package/dist/consumer-cli.js +4 -4
  35. package/dist/crypto/blind-index.js +18 -17
  36. package/dist/crypto/ciphertext-pattern.js +1 -1
  37. package/dist/crypto/event-pii.js +3 -3
  38. package/dist/crypto/kek-source.d.ts +1 -1
  39. package/dist/crypto/kek-source.js +3 -2
  40. package/dist/crypto/kms-adapter.d.ts +1 -1
  41. package/dist/crypto/kms-adapter.js +4 -6
  42. package/dist/crypto/kms-wiring.d.ts +1 -2
  43. package/dist/crypto/kms-wiring.js +2 -2
  44. package/dist/crypto/pg-kms-adapter.js +2 -6
  45. package/dist/crypto/pii-field-encryption.js +4 -4
  46. package/dist/crypto/subject-resolver.d.ts +1 -1
  47. package/dist/crypto/subject-resolver.js +2 -2
  48. package/dist/db/apply-entity-event.d.ts +6 -6
  49. package/dist/db/apply-entity-event.js +55 -55
  50. package/dist/db/blind-index-cleanup.js +7 -7
  51. package/dist/db/collect-table-metas.js +10 -11
  52. package/dist/db/compound-types.d.ts +1 -1
  53. package/dist/db/compound-types.js +12 -12
  54. package/dist/db/cross-tenant-reads.d.ts +8 -0
  55. package/dist/db/cross-tenant-reads.js +23 -0
  56. package/dist/db/cursor.js +3 -4
  57. package/dist/db/dialect.d.ts +2 -2
  58. package/dist/db/dialect.js +2 -2
  59. package/dist/db/eagerload.d.ts +3 -3
  60. package/dist/db/eagerload.js +21 -12
  61. package/dist/db/entity-table-meta.d.ts +2 -2
  62. package/dist/db/entity-table-meta.js +4 -4
  63. package/dist/db/event-store-executor-context.d.ts +1 -1
  64. package/dist/db/event-store-executor-context.js +13 -13
  65. package/dist/db/event-store-executor-read.js +27 -28
  66. package/dist/db/event-store-executor-write.js +65 -70
  67. package/dist/db/located-timestamp.d.ts +11 -11
  68. package/dist/db/located-timestamp.js +29 -30
  69. package/dist/db/migrate-generator.d.ts +1 -1
  70. package/dist/db/migrate-generator.js +26 -19
  71. package/dist/db/migrate-runner.js +25 -25
  72. package/dist/db/money.d.ts +6 -6
  73. package/dist/db/money.js +29 -27
  74. package/dist/db/queries/backfill-pii.js +5 -5
  75. package/dist/db/queries/ddl.js +1 -1
  76. package/dist/db/queries/es-ops.d.ts +1 -1
  77. package/dist/db/queries/es-ops.js +1 -1
  78. package/dist/db/queries/event-consumer.js +3 -3
  79. package/dist/db/queries/event-store.js +4 -2
  80. package/dist/db/queries/projection-rebuild.js +3 -3
  81. package/dist/db/queries/raw-sql.d.ts +2 -2
  82. package/dist/db/queries/raw-sql.js +2 -2
  83. package/dist/db/queries/shadow-swap.js +19 -19
  84. package/dist/db/queries/test-stack.d.ts +4 -1
  85. package/dist/db/queries/test-stack.js +15 -2
  86. package/dist/db/query.d.ts +1 -1
  87. package/dist/db/query.js +6 -6
  88. package/dist/db/rebuild-marker.js +20 -21
  89. package/dist/db/replay-migration-sql.js +2 -2
  90. package/dist/db/schema-inspection.js +4 -5
  91. package/dist/db/table-builder.d.ts +1 -2
  92. package/dist/db/table-builder.js +82 -82
  93. package/dist/db/tenant-db.d.ts +2 -2
  94. package/dist/db/tenant-db.js +9 -2
  95. package/dist/derivatives/delete-stored-file.js +5 -2
  96. package/dist/derivatives/derivatives-context.js +1 -1
  97. package/dist/engine/boot-validator/access-roles.js +4 -3
  98. package/dist/engine/boot-validator/api-ext.js +14 -14
  99. package/dist/engine/boot-validator/config-deps.js +7 -6
  100. package/dist/engine/boot-validator/detail-screens.js +3 -3
  101. package/dist/engine/boot-validator/entity-handler.js +60 -61
  102. package/dist/engine/boot-validator/gdpr-storage.js +4 -4
  103. package/dist/engine/boot-validator/i18n-keys.js +1 -1
  104. package/dist/engine/boot-validator/index.d.ts +2 -2
  105. package/dist/engine/boot-validator/index.js +20 -20
  106. package/dist/engine/boot-validator/nav.js +5 -5
  107. package/dist/engine/boot-validator/ownership.js +12 -9
  108. package/dist/engine/boot-validator/parent-ref.js +1 -1
  109. package/dist/engine/boot-validator/pii-retention.js +50 -57
  110. package/dist/engine/boot-validator/projection-list-screens.js +7 -7
  111. package/dist/engine/boot-validator/query-refs.js +1 -1
  112. package/dist/engine/boot-validator/screens-actions.js +15 -16
  113. package/dist/engine/boot-validator/screens-forms.js +10 -11
  114. package/dist/engine/boot-validator/screens-lists.js +14 -15
  115. package/dist/engine/boot-validator/screens.js +34 -40
  116. package/dist/engine/boot-validator/security-baseline.js +3 -2
  117. package/dist/engine/boot-validator/transfer-graph.js +2 -2
  118. package/dist/engine/boot-validator/workspaces.js +3 -3
  119. package/dist/engine/bridge-stub.js +3 -4
  120. package/dist/engine/build-app-schema.js +75 -75
  121. package/dist/engine/build-config-feature-schema.js +50 -50
  122. package/dist/engine/build-target.js +20 -23
  123. package/dist/engine/codemod/pipeline-codemod.js +20 -13
  124. package/dist/engine/config-helpers.js +4 -4
  125. package/dist/engine/constants.js +2 -2
  126. package/dist/engine/create-app.js +2 -2
  127. package/dist/engine/define-feature.js +12 -11
  128. package/dist/engine/define-handler.js +12 -11
  129. package/dist/engine/define-step.js +1 -1
  130. package/dist/engine/define-workflow.d.ts +5 -5
  131. package/dist/engine/define-workflow.js +6 -6
  132. package/dist/engine/entity-handlers.js +13 -14
  133. package/dist/engine/event-def.js +2 -2
  134. package/dist/engine/extension-names.d.ts +16 -17
  135. package/dist/engine/extension-names.js +37 -38
  136. package/dist/engine/extensions/escape-hatch-usage.js +1 -1
  137. package/dist/engine/extensions/tenant-data.js +2 -2
  138. package/dist/engine/extensions/user-data.d.ts +23 -24
  139. package/dist/engine/factories.d.ts +14 -13
  140. package/dist/engine/factories.js +38 -37
  141. package/dist/engine/feature-ast/entity-field-types.js +1 -1
  142. package/dist/engine/feature-ast/extractors/handlers.js +1 -1
  143. package/dist/engine/feature-ast/extractors/hooks.js +1 -1
  144. package/dist/engine/feature-ast/extractors/shared.d.ts +2 -3
  145. package/dist/engine/feature-ast/extractors/shared.js +2 -3
  146. package/dist/engine/feature-ast/parse.js +13 -13
  147. package/dist/engine/feature-ast/patch.js +1 -1
  148. package/dist/engine/feature-ast/pattern-change-schema.d.ts +8 -0
  149. package/dist/engine/feature-ast/pattern-change-schema.js +1 -1
  150. package/dist/engine/feature-ast/patterns.d.ts +2 -2
  151. package/dist/engine/feature-ast/render.js +2 -2
  152. package/dist/engine/feature-config-events-jobs.js +15 -12
  153. package/dist/engine/feature-entity-handlers.js +9 -5
  154. package/dist/engine/feature-manifest.d.ts +9 -9
  155. package/dist/engine/feature-manifest.js +6 -7
  156. package/dist/engine/feature-ui-extensions.d.ts +3 -3
  157. package/dist/engine/feature-ui-extensions.js +30 -22
  158. package/dist/engine/field-access.js +5 -6
  159. package/dist/engine/index.d.ts +1 -0
  160. package/dist/engine/index.js +4 -4
  161. package/dist/engine/object-form.d.ts +1 -0
  162. package/dist/engine/object-form.js +14 -4
  163. package/dist/engine/ownership.js +3 -3
  164. package/dist/engine/parse-ref-target.js +5 -7
  165. package/dist/engine/pattern-library/index.js +2 -2
  166. package/dist/engine/pattern-library/library.js +2 -2
  167. package/dist/engine/pattern-library/types.js +3 -3
  168. package/dist/engine/pipeline.js +2 -2
  169. package/dist/engine/project-app-schema-for-roles.js +1 -1
  170. package/dist/engine/projection-helpers.js +9 -9
  171. package/dist/engine/qualified-name.js +2 -2
  172. package/dist/engine/registry-facade.js +1 -1
  173. package/dist/engine/registry-ingest.js +12 -12
  174. package/dist/engine/registry-state.js +17 -19
  175. package/dist/engine/registry-validate.js +12 -12
  176. package/dist/engine/run-in.js +2 -2
  177. package/dist/engine/run-pipeline.js +7 -0
  178. package/dist/engine/schema-builder.js +40 -38
  179. package/dist/engine/screen-filter-ops.js +17 -20
  180. package/dist/engine/screen-helpers.js +4 -3
  181. package/dist/engine/state-machine.d.ts +12 -12
  182. package/dist/engine/state-machine.js +7 -7
  183. package/dist/engine/steps/_resolver-utils.js +4 -3
  184. package/dist/engine/steps/for-each.js +4 -4
  185. package/dist/engine/steps/unsafe-projection-delete.js +1 -1
  186. package/dist/engine/steps/unsafe-projection-upsert.js +4 -4
  187. package/dist/engine/steps/wait-for-event.js +4 -4
  188. package/dist/engine/steps/workflow-system-events.js +3 -3
  189. package/dist/engine/tier-resolver-extension.d.ts +29 -27
  190. package/dist/engine/tier-resolver-extension.js +26 -26
  191. package/dist/engine/transfer-adjacency.js +2 -2
  192. package/dist/engine/validate-projection-allowlist.js +7 -27
  193. package/dist/engine/walk-step-instances.d.ts +2 -0
  194. package/dist/engine/walk-step-instances.js +38 -0
  195. package/dist/engine/where-rule-lint.js +1 -1
  196. package/dist/engine/write-handler-qn-extract.js +1 -1
  197. package/dist/entrypoint/index.js +3 -3
  198. package/dist/env/index.d.ts +7 -6
  199. package/dist/env/index.js +10 -9
  200. package/dist/errors/transition-details.js +7 -7
  201. package/dist/errors/write-error-info.d.ts +6 -6
  202. package/dist/errors/write-error-info.js +6 -6
  203. package/dist/es-ops/context.d.ts +9 -9
  204. package/dist/es-ops/context.js +28 -28
  205. package/dist/es-ops/index.js +7 -9
  206. package/dist/es-ops/operations-schema.js +4 -10
  207. package/dist/es-ops/runner.d.ts +11 -11
  208. package/dist/es-ops/runner.js +49 -53
  209. package/dist/es-ops/types.d.ts +55 -55
  210. package/dist/es-ops/types.js +11 -11
  211. package/dist/event-store/admin-api.js +6 -7
  212. package/dist/event-store/errors.js +1 -1
  213. package/dist/event-store/event-store.js +2 -2
  214. package/dist/event-store/rebuild-dead-letter.js +1 -1
  215. package/dist/files/feature.js +9 -10
  216. package/dist/files/file-handle.js +5 -6
  217. package/dist/files/file-ref-entity.js +15 -15
  218. package/dist/files/file-ref-table.js +7 -8
  219. package/dist/files/file-routes.d.ts +7 -0
  220. package/dist/files/file-routes.js +11 -5
  221. package/dist/files/in-memory-provider.js +8 -9
  222. package/dist/files/local-provider.js +18 -25
  223. package/dist/files/provider-resolver.d.ts +7 -7
  224. package/dist/files/storage-tracking.js +2 -2
  225. package/dist/files/zip-stream.d.ts +12 -19
  226. package/dist/files/zip-stream.js +44 -63
  227. package/dist/i18n/required-surface-keys.d.ts +1 -1
  228. package/dist/i18n/required-surface-keys.js +8 -8
  229. package/dist/internal/stack-support.d.ts +1 -1
  230. package/dist/internal/stack-support.js +1 -1
  231. package/dist/jobs/job-runner-helpers.d.ts +1 -2
  232. package/dist/jobs/job-runner-helpers.js +8 -18
  233. package/dist/jobs/job-runner-schedulers.d.ts +1 -1
  234. package/dist/jobs/job-runner-schedulers.js +3 -4
  235. package/dist/jobs/job-runner.js +17 -18
  236. package/dist/lifecycle/lifecycle.js +3 -5
  237. package/dist/lifecycle/signal-handlers.js +5 -4
  238. package/dist/logging/utils.d.ts +2 -1
  239. package/dist/logging/utils.js +38 -10
  240. package/dist/migrations/index.js +1 -1
  241. package/dist/migrations/kumiko-drift.js +14 -13
  242. package/dist/migrations/pending-rebuilds.d.ts +21 -20
  243. package/dist/migrations/pending-rebuilds.js +27 -27
  244. package/dist/migrations/projection-table-index.d.ts +3 -3
  245. package/dist/migrations/projection-table-index.js +8 -8
  246. package/dist/observability/metric-validator.js +1 -1
  247. package/dist/observability/metrics-handle.js +3 -3
  248. package/dist/pipeline/append-event-core.js +11 -2
  249. package/dist/pipeline/cascade-handler.d.ts +1 -1
  250. package/dist/pipeline/dispatch-batch.js +2 -2
  251. package/dist/pipeline/dispatch-query.js +10 -10
  252. package/dist/pipeline/dispatch-shared.d.ts +1 -0
  253. package/dist/pipeline/dispatch-shared.js +42 -19
  254. package/dist/pipeline/dispatch-stream.js +1 -1
  255. package/dist/pipeline/dispatch-write.js +3 -3
  256. package/dist/pipeline/dispatcher-utils.d.ts +1 -1
  257. package/dist/pipeline/dispatcher-utils.js +1 -1
  258. package/dist/pipeline/event-consumer-state.js +2 -2
  259. package/dist/pipeline/event-dispatcher-delivery.js +1 -1
  260. package/dist/pipeline/event-dispatcher.js +17 -28
  261. package/dist/pipeline/irreversible-operation-gate.js +1 -1
  262. package/dist/pipeline/lifecycle-pipeline.js +9 -9
  263. package/dist/pipeline/member-read-only-transaction.js +1 -3
  264. package/dist/pipeline/msp-rebuild.js +1 -1
  265. package/dist/pipeline/projection-rebuild.js +13 -12
  266. package/dist/pipeline/system-hooks.js +39 -41
  267. package/dist/pipeline/system-identity-switch.js +1 -3
  268. package/dist/pipeline/tenant-timezone-cache.js +1 -1
  269. package/dist/random/generate.d.ts +29 -29
  270. package/dist/random/generate.js +30 -30
  271. package/dist/random/words.js +20 -20
  272. package/dist/rate-limit/middleware.js +9 -5
  273. package/dist/redis/client.js +2 -2
  274. package/dist/redis/pubsub-signal.js +14 -17
  275. package/dist/schema-cli.js +116 -22
  276. package/dist/search/index.js +4 -4
  277. package/dist/search/purge-subject.js +4 -5
  278. package/dist/search/reindex-entity.js +6 -9
  279. package/dist/time/boot-tz-warning.d.ts +2 -2
  280. package/dist/time/boot-tz-warning.js +8 -8
  281. package/dist/time/iana.js +10 -10
  282. package/dist/time/index.js +9 -9
  283. package/dist/time/legacy-date.d.ts +2 -2
  284. package/dist/time/legacy-date.js +6 -8
  285. package/dist/time/polyfill.js +1 -1
  286. package/dist/time/tz-context.d.ts +1 -1
  287. package/dist/time/tz-context.js +1 -1
  288. package/dist/ui-types/app-schema.js +7 -7
  289. package/dist/ui-types/list-row-meta.js +4 -4
  290. package/dist/upgrade-cli.js +10 -9
  291. package/dist/utils/compare.js +1 -1
  292. package/package.json +4 -4
  293. package/src/changes.json +54 -0
  294. package/src/scripts/codemod/crypto-shredding-testing-move.ts +2 -2
  295. package/src/scripts/codemod/migrate-cross-tenant.ts +1 -1
  296. package/src/scripts/codemod/migrate-open-to-all.ts +1 -1
  297. package/src/scripts/codemod/pii-personal-migration.ts +17 -8
  298. package/src/scripts/codemod/unprocessable-error-details-reason.ts +2 -2
@@ -1,24 +1,23 @@
1
- // Runner für pending seed-migrations beim Boot.
1
+ // Runner for pending seed migrations during boot.
2
2
  //
3
3
  // Flow:
4
- // 1. List seeds/<*.ts> sorted ascending (filename = chronologische ID)
4
+ // 1. List seeds/<*.ts> sorted ascending (filename = chronological ID)
5
5
  // 2. SELECT id FROM kumiko_es_operations WHERE operation_type='seed-migration'
6
- // 3. pending = files \ applied
7
- // 4. Für jeden pending in Order:
8
- // a. dynamic import → default-export (SeedMigration)
9
- // b. wenn migration.skippable && env[KUMIKO_SKIP_ES_OPS_<sanitized>]='1':
10
- // → console.log + continue (kein Marker geschrieben)
11
- // c. Tx start
6
+ // 3. pending = files \\ applied
7
+ // 4. For each pending migration in order:
8
+ // a. dynamic import → default export (SeedMigration)
9
+ // b. if migration.skippable && env[KUMIKO_SKIP_ES_OPS_<sanitized>]='1':
10
+ // → log line + continue (no marker written)
11
+ // c. Start transaction
12
12
  // d. await migration.run(ctx)
13
- // e. INSERT marker mit duration_ms + appliedBy
14
- // f. Tx commit
15
- // 5. On any failure: Tx rollback + console.error + throw
16
- // → App-Boot bricht ab. Operator muss Failure fixen + retry.
13
+ // e. INSERT marker with duration_ms + appliedBy
14
+ // f. Commit transaction
15
+ // 5. On any failure: transaction rollback + error log + throw
16
+ // → App boot aborts. The operator must fix the failure and retry.
17
17
  //
18
- // Skippable-Pattern: seed.skippable=true erlaubt im Notfall ein
19
- // `KUMIKO_SKIP_ES_OPS_<id>=1` env-flag um eine kaputte Migration zu
20
- // überspringen ohne ihr Code touchen zu müssen. NICHT als
21
- // Standard-Workflow — wirklich Notfall.
18
+ // Emergency skip pattern: seed.skippable=true permits a
19
+ // `KUMIKO_SKIP_ES_OPS_<id>=1` env flag to skip a broken migration
20
+ // without touching its code. NOT a standard workflow — emergency use only.
22
21
  var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
23
22
  if (typeof path === "string" && /^\.\.?\//.test(path)) {
24
23
  return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
@@ -31,32 +30,30 @@ import { readdir, readFile } from "node:fs/promises";
31
30
  import path from "node:path";
32
31
  import { acquireEsOpsAdvisoryLock, esOperationExists } from "../db/queries/es-ops.js";
33
32
  import { insertOne, selectMany } from "../db/query.js";
33
+ import { createFallbackLogger } from "../logging/utils.js";
34
34
  import { esOperationsTable } from "./operations-schema.js";
35
- const DEFAULT_LOGGER = (line) => {
36
- // biome-ignore lint/suspicious/noConsole: boot-time-output, kein Logger-Inject hier
37
- console.log(line);
38
- };
39
- const LOG_PREFIX = "[es-ops/seed-migration]";
35
+ const seedMigrationLog = createFallbackLogger("es-ops/seed-migration");
36
+ const DEFAULT_LOGGER = (line) => seedMigrationLog.info(line);
40
37
  export async function runPendingSeedMigrations(args) {
41
38
  const log = args.logger ?? DEFAULT_LOGGER;
42
39
  const onDisk = await listSeedFiles(args.seedsDir);
43
40
  if (onDisk.length === 0) {
44
- log(`${LOG_PREFIX} no seed files in ${args.seedsDir} — skipping`);
41
+ log(`no seed files in ${args.seedsDir} — skipping`);
45
42
  return { appliedIds: [], skippedIds: [] };
46
43
  }
47
44
  const applied = await loadAppliedIds(args.db);
48
45
  const pending = onDisk.filter((entry) => !applied.has(entry.id));
49
46
  if (pending.length === 0) {
50
- log(`${LOG_PREFIX} ${onDisk.length} on disk, all applied — nothing to do`);
47
+ log(`${onDisk.length} on disk, all applied — nothing to do`);
51
48
  return { appliedIds: [], skippedIds: [] };
52
49
  }
53
- log(`${LOG_PREFIX} ${pending.length}/${onDisk.length} pending`);
50
+ log(`${pending.length}/${onDisk.length} pending`);
54
51
  const appliedIds = [];
55
52
  const skippedIds = [];
56
- // Dry-Run-Pass (Phase 1.5 / A2): vor JEDER migration alle handler-QNs aus
57
- // den seed-files parsen + gegen registry checken. Fail-fast vor erstem
58
- // write — gibt klare error-message mit Datei + qn statt zur runtime
59
- // "handler not found" mitten im migration-flow.
53
+ // Dry-run pass: before any migration, parse all handler QNs from the seed
54
+ // files and check them against the registry. Fail fast before the first
55
+ // write, with a clear error containing the file and QN instead of a runtime
56
+ // "handler not found" error in the middle of the migration flow.
60
57
  if (args.registry !== undefined) {
61
58
  const unknownQns = [];
62
59
  for (const entry of pending) {
@@ -71,27 +68,26 @@ export async function runPendingSeedMigrations(args) {
71
68
  const lines = unknownQns.map((u) => ` - ${u.id}: "${u.qn}" not registered`);
72
69
  throw new Error(`[es-ops/seed-migration] dry-run found ${unknownQns.length} unknown handler-QN(s):\n${lines.join("\n")}\n Check spelling against your TenantHandlers/AuthHandlers constants (kebab-case after the colon).`);
73
70
  }
74
- log(`${LOG_PREFIX} dry-run ok — all referenced handler-QNs registered`);
71
+ log(`dry-run ok — all referenced handler-QNs registered`);
75
72
  }
76
73
  for (const entry of pending) {
77
74
  const migration = await loadSeedModule(entry.filePath);
78
75
  const envFlag = `KUMIKO_SKIP_ES_OPS_${sanitizeForEnv(entry.id)}`;
79
76
  if (migration.skippable === true && process.env[envFlag] === "1") {
80
- log(`${LOG_PREFIX} skip "${entry.id}" — ${envFlag}=1`);
77
+ log(`skip "${entry.id}" — ${envFlag}=1`);
81
78
  skippedIds.push(entry.id);
82
79
  continue;
83
80
  }
84
81
  const start = Date.now();
85
82
  try {
86
83
  await args.db.begin(async (tx) => {
87
- // Advisory-Lock: sequentialisiert Multi-Replica-Boots. Zweiter
88
- // Pod blockt bis erster fertig ist, dann re-checked sein
89
- // applied-set (außerhalb dieser Funktion in nächster Iteration)
90
- // und findet den Marker → skip. Lock wird beim Tx-Commit
91
- // automatisch released (xact-scope).
84
+ // Advisory lock: serializes multi-replica boots. The second pod blocks until
85
+ // the first finishes, then rechecks its applied set in the next
86
+ // iteration and finds the marker, so it skips the migration. The lock
87
+ // is released automatically when the transaction commits (xact scope).
92
88
  await acquireEsOpsAdvisoryLock(tx);
93
89
  if (await esOperationExists(tx, entry.id)) {
94
- log(`${LOG_PREFIX} race-skip "${entry.id}" — applied by parallel boot`);
90
+ log(`race-skip "${entry.id}" — applied by parallel boot`);
95
91
  // skip: parallel boot won the advisory-lock and already applied
96
92
  // this migration. Nothing more to do in this tx.
97
93
  return;
@@ -107,13 +103,13 @@ export async function runPendingSeedMigrations(args) {
107
103
  });
108
104
  });
109
105
  const elapsed = Date.now() - start;
110
- log(`${LOG_PREFIX} ✓ ${entry.id} (${elapsed}ms) — ${migration.description}`);
106
+ log(`✓ ${entry.id} (${elapsed}ms) — ${migration.description}`);
111
107
  appliedIds.push(entry.id);
112
108
  }
113
109
  catch (err) {
114
110
  const elapsed = Date.now() - start;
115
- log(`${LOG_PREFIX} ✗ ${entry.id} (${elapsed}ms) — ${stringifyError(err)}`);
116
- log(`${LOG_PREFIX} ABORT — ${pending.length - appliedIds.length - skippedIds.length - 1} pending migrations were NOT attempted`);
111
+ log(`✗ ${entry.id} (${elapsed}ms) — ${stringifyError(err)}`);
112
+ log(`ABORT — ${pending.length - appliedIds.length - skippedIds.length - 1} pending migrations were NOT attempted`);
117
113
  throw err;
118
114
  }
119
115
  }
@@ -125,8 +121,8 @@ async function listSeedFiles(seedsDir) {
125
121
  entries = await readdir(seedsDir);
126
122
  }
127
123
  catch {
128
- // Directory doesn't exist → treat as empty. App ohne seeds-Dir ist
129
- // ein valider Zustand (no-op).
124
+ // A missing directory is treated as empty. An app without a seeds directory
125
+ // is a valid no-op state.
130
126
  return [];
131
127
  }
132
128
  return entries
@@ -135,11 +131,11 @@ async function listSeedFiles(seedsDir) {
135
131
  .sort() // filename = chronologische ID (date-prefix-convention)
136
132
  .map((name) => ({
137
133
  id: name.replace(/\.(ts|mts|js)$/, ""),
138
- // resolve, nicht join: Bun's await import() braucht absolute Pfade.
139
- // Wenn seedsDir relativ ist (z.B. "./seeds" aus runProdApp-Option),
140
- // wäre der join-Pfad auch relativ → Bun's import-resolver such
141
- // relativ zum runner.ts-Modul, nicht zu process.cwd() → fail mit
142
- // "Cannot find module 'seeds/...' from '<runner-path>'".
134
+ // Resolve rather than join: Bun's await import() requires absolute paths.
135
+ // If seedsDir is relative (for example "./seeds" from a runProdApp
136
+ // option), the joined path would also be relative. Bun's import resolver
137
+ // would resolve it relative to runner.ts rather than process.cwd(),
138
+ // causing "Cannot find module 'seeds/...' from '<runner-path>'".
143
139
  filePath: path.resolve(seedsDir, name),
144
140
  }));
145
141
  }
@@ -171,16 +167,16 @@ function isSeedMigration(value) {
171
167
  function sanitizeForEnv(id) {
172
168
  return id.toUpperCase().replace(/[^A-Z0-9]+/g, "_");
173
169
  }
174
- // Parse seed-file source + extract handler-QNs aus `systemWriteAs(...)`-
175
- // Calls. Reine regex (kein AST) — fängt die häufigen Inline-String-Cases:
170
+ // Parse seed-file source and extract handler QNs from `systemWriteAs(...)`
171
+ // calls. A regex (rather than an AST) handles common inline string cases:
176
172
  // ctx.systemWriteAs("foo:write:bar", payload)
177
173
  // systemWriteAs("foo:write:bar", ...) (destructured)
178
174
  //
179
- // Edge-Cases die NICHT geguckt werden:
180
- // - QN aus Variable: `const qn = "..."; ctx.systemWriteAs(qn, ...)`
181
- // - String-Concat / Template-Literals mit dynamic vars
182
- // Diese Pattern sind selten in real seed-migrations + bleibt als known-
183
- // limitation dokumentiert. Wer dynamic-QN braucht, weiß was er tut.
175
+ // Unsupported cases:
176
+ // - QN from a variable: `const qn = "..."; ctx.systemWriteAs(qn, ...)`
177
+ // - String concatenation or template literals with dynamic variables
178
+ // These patterns are uncommon in real seed migrations. This limitation is
179
+ // documented so callers requiring dynamic QNs understand the constraint.
184
180
  function extractWriteHandlerQns(source) {
185
181
  const pattern = /systemWriteAs\s*\(\s*["']([^"']+)["']/g;
186
182
  const out = new Set();
@@ -3,49 +3,50 @@ import type { TenantId, WriteResult } from "../engine/index.ts";
3
3
  export type EsOperationAppliedBy = "boot" | "cli" | "ci-pipeline";
4
4
  /** Default-Export einer seed-Migration-File. */
5
5
  export type SeedMigration = {
6
- /** Kurze Beschreibung was die Migration tut. Wird in kumiko_es_operations.notes
7
- * und im `ops seed:status`-Output gezeigt. */
6
+ /** A short description of what the migration does. Shown in
7
+ * kumiko_es_operations.notes and in the `ops seed:status` output. */
8
8
  readonly description: string;
9
- /** Optional: skippe diesen Seed wenn die env-var
10
- * `KUMIKO_SKIP_ES_OPS_<sanitized-filename>=1` gesetzt ist (für Recovery /
11
- * Debug-Boots). Default false = always-run-pending. */
9
+ /** Optional: skip this seed when the env var
10
+ * `KUMIKO_SKIP_ES_OPS_<sanitized-filename>=1` is set (for recovery or
11
+ * debug boots). Default false = always-run-pending. */
12
12
  readonly skippable?: boolean;
13
- /** Hauptarbeit. ctx liefert systemWriteAs (Event-Store-konformer Pfad,
14
- * bypassed Access-Checks via system-user) plus Read-Helpers.
13
+ /** Main migration work. ctx provides systemWriteAs (the Event Store-compliant
14
+ * path, bypassing access checks through the system user) plus read helpers.
15
15
  *
16
- * Throws → Marker NICHT geschrieben, App-Boot bricht ab, Retry bei
17
- * nächstem Boot. Pro-Migration eigene Transaction; ein Failure stoppt
18
- * alle nachfolgenden pending-Migrations (Order-Erhalt). */
16
+ * If this throws, the marker is NOT written, app boot aborts, and the
17
+ * migration is retried on the next boot. Each migration has its own
18
+ * transaction; a failure stops all subsequent pending migrations, preserving
19
+ * their order. */
19
20
  readonly run: (ctx: SeedMigrationContext) => Promise<void>;
20
21
  };
21
- /** Read-shape eines User-Eintrags wie an Seed-Helpers exposed.
22
- * Schmaler Subset von AuthUserRow — Seeds brauchen typischerweise nur
23
- * diese Felder zur Identifikation. */
22
+ /** Read shape of a user entry exposed to seed helpers.
23
+ * A narrow subset of AuthUserRow, since seeds typically need only
24
+ * these fields for identification. */
24
25
  export type SeedUserRow = {
25
26
  readonly id: string;
26
27
  readonly email: string;
27
28
  readonly tenantId: string;
28
29
  };
29
- /** Read-shape eines Membership-Eintrags wie an Seed-Helpers exposed.
30
- * Unterscheidet zwei tenantIds: die "logische" aus dem Read-Projektion
31
- * (`tenantId`) und die "physische" aus dem Aggregate-Stream
32
- * (`streamTenantId`). Die beiden weichen voneinander ab wenn das
33
- * Aggregate von einem Executor mit anderer tenantId angelegt wurde
34
- * (z.B. seedTenantMembership-by=systemAdmin) — typischer
35
- * publicstatus-Driver-Use-Case. */
30
+ /** Read shape of a membership entry exposed to seed helpers.
31
+ * Distinguishes two tenantIds: the logical one from the read projection
32
+ * (`tenantId`) and the physical one from the aggregate stream
33
+ * (`streamTenantId`). They differ when the aggregate was created by an
34
+ * executor with a different tenantId (for example,
35
+ * seedTenantMembership-by=systemAdmin), which is a typical
36
+ * publicstatus driver use case. */
36
37
  export type SeedMembershipRow = {
37
38
  readonly userId: string;
38
- /** Payload-tenant aus `read_tenant_memberships.tenant_id`. Geht ins
39
- * write-payload als `tenantId`. */
39
+ /** Payload tenant from `read_tenant_memberships.tenant_id`; it is used
40
+ * as `tenantId` in the write payload. */
40
41
  readonly tenantId: string;
41
- /** Stream-tenant aus `kumiko_events.tenant_id` der v1-Row. MUSS als
42
- * `tenantIdOverride` an `systemWriteAs` durchgereicht werden, sonst
43
- * sucht der Event-Store-Executor den Stream im falschen Tenant und
44
- * liefert `version_conflict`. */
42
+ /** Stream tenant from `kumiko_events.tenant_id` in the v1 row. MUST be
43
+ * passed as `tenantIdOverride` to `systemWriteAs`; otherwise the Event
44
+ * Store executor looks for the stream in the wrong tenant and returns
45
+ * `version_conflict`. */
45
46
  readonly streamTenantId: string;
46
47
  readonly roles: readonly string[];
47
48
  };
48
- /** Read-shape eines Tenant-Eintrags wie an Seed-Helpers exposed. */
49
+ /** Read shape of a tenant entry exposed to seed helpers. */
49
50
  export type SeedTenantRow = {
50
51
  readonly id: string;
51
52
  readonly name: string;
@@ -69,34 +70,32 @@ export type SeedTemplateResourceFilter = {
69
70
  readonly locale?: string;
70
71
  };
71
72
  export type SeedMigrationContext = {
72
- /** Event-Store-konformer Write via existing write-handler. System-User
73
- * als Executor bypassed Access-Check (Standard-Seed-Pattern). Events
74
- * haben inserted_by_id = SYSTEM_TENANT_ID-User → audit-fähig.
73
+ /** Event Store-compliant write through the existing write handler. The system
74
+ * user executes it while bypassing access checks. Events have
75
+ * inserted_by_id = SYSTEM_TENANT_ID-user, making them auditable.
75
76
  *
76
- * Typ-Signatur folgt existing ctx.writeAs (payload als unknown) — Type-
77
- * Safety kommt über handler-spezifische Wrapper im Aufrufer ("ich weiß
78
- * was updateMemberRoles braucht"). Versucht NICHT Generic-Magic.
77
+ * The type signature follows existing ctx.writeAs (with payload as unknown).
78
+ * Type safety comes from handler-specific wrappers in the caller; do not
79
+ * attempt generic magic.
79
80
  *
80
- * **tenantIdOverride (Phase 1.5):** wenn das Ziel-Aggregate in einem
81
- * spezifischen Tenant-Stream lebt (nicht SYSTEM_TENANT_ID, was Default
82
- * ist), MUSS der Caller die Stream-tenantId mitgeben — sonst sucht der
83
- * Event-Store-Executor den Aggregate-Stream gegen `SYSTEM_TENANT_ID`
84
- * und liefert `version_conflict` (siehe Memory
85
- * `feedback_event_store_tenant_consistency.md` + Driver-Use-Case
86
- * publicstatus-admin-roles in `project_es_ops_phase1_retro.md`).
81
+ * **tenantIdOverride:** when the target aggregate lives in a specific
82
+ * tenant stream (rather than SYSTEM_TENANT_ID, the default), the caller
83
+ * MUST provide the stream tenantId. Otherwise the Event Store executor
84
+ * looks for the aggregate stream under `SYSTEM_TENANT_ID` and returns
85
+ * `version_conflict`.
87
86
  *
88
- * Typische Pattern:
89
- * - System-scope-Aggregate (config-values, system text-content) →
90
- * tenantIdOverride weglassen (Default SYSTEM_TENANT_ID).
91
- * - Tenant-scope-Aggregate (memberships, tenant-config, app-data) →
92
- * `tenantIdOverride: m.tenantId` (oder den Stream-Tenant aus
93
- * einem find*-Helper).
87
+ * Typical patterns:
88
+ * - System-scope aggregates (config-values, system text-content): omit
89
+ * tenantIdOverride (default SYSTEM_TENANT_ID).
90
+ * - Tenant-scope aggregates (memberships, tenant-config, app-data): use
91
+ * `tenantIdOverride: m.tenantId` or the stream tenant from a find*
92
+ * helper.
94
93
  *
95
- * **extraRoles:** hasAccess has no system-bypass — a handler with an
96
- * explicit role gate (e.g. `access: { roles: ["SystemAdmin"] }` or
97
- * `["anonymous"]`) otherwise rejects the bare `system` actor with
98
- * `access_denied`. Pass the required role(s) here — see
99
- * `createSystemUser`'s `extraRoles` doc. */
94
+ * **extraRoles:** hasAccess has no system bypass. A handler with an
95
+ * explicit role gate (for example, `access: { roles: ["SystemAdmin"] }` or
96
+ * `["anonymous"]`) otherwise rejects the bare `system` actor with
97
+ * `access_denied`. Pass the required role(s) here; see
98
+ * `createSystemUser`'s `extraRoles` documentation. */
100
99
  readonly systemWriteAs: (handlerQualifiedName: string, payload: unknown, tenantIdOverride?: TenantId, extraRoles?: readonly string[]) => Promise<WriteResult>;
101
100
  readonly findUserByEmail: (email: string) => Promise<SeedUserRow | null>;
102
101
  readonly findMembershipsOfUser: (userId: string) => Promise<readonly SeedMembershipRow[]>;
@@ -104,9 +103,10 @@ export type SeedMigrationContext = {
104
103
  /** Template rows ordered by slug, locale. Empty list when the
105
104
  * template-resolver feature (its table) is not present. */
106
105
  readonly findTemplateResources: (filter?: SeedTemplateResourceFilter) => Promise<readonly SeedTemplateResourceRow[]>;
107
- /** Escape-Hatch — direkter DB-Zugang. Nur für READ-only Lookups die der
108
- * Context nicht standard-mäßig anbietet. WRITES via systemWriteAs!
109
- * Type ist DbRunner (Connection oder aktive Tx) weil der Runner den
110
- * Context pro-Migration im Tx-Scope erzeugt. */
106
+ /** Escape hatch for direct database access. Use only for READ-only lookups
107
+ * that the context does not provide by default; WRITES must use
108
+ * systemWriteAs. The type is DbRunner (a connection or active transaction)
109
+ * because the runner creates the context within each migration's
110
+ * transaction scope. */
111
111
  readonly db: DbRunner;
112
112
  };
@@ -1,14 +1,14 @@
1
- // Public API für seed-migrations. Files in /seeds/<date>-<slug>.ts
2
- // exportieren ein default-Object dieses Typs. Runner ruft `run(ctx)` in
3
- // chronologischer Reihenfolge auf, einmal pro App-Boot (track in
4
- // kumiko_es_operations).
1
+ // Public API for seed migrations. Files in /seeds/<date>-<slug>.ts
2
+ // export a default object of this type. The runner calls `run(ctx)` in
3
+ // chronological order, once per app boot, tracking runs in
4
+ // kumiko_es_operations.
5
5
  //
6
- // API-Stabilität: Phase 1 ist "minimal-viable". Helper am Context
7
- // (findUserByEmail, etc.) wachsen on-demand — wenn eine real-Migration
8
- // einen Lookup braucht den der Context nicht anbietet, fügen wir ihn
9
- // dort hinzu. Das vermeidet "wir-bauen-alle-möglichen-Helper-spekulativ".
6
+ // API stability: the initial API is intentionally minimal. Context helpers
7
+ // (findUserByEmail, etc.) grow on demand: when a real migration needs a
8
+ // lookup the context does not provide, add it there. This avoids speculative
9
+ // helper APIs.
10
10
  //
11
- // ctx.db als Escape-Hatch ist erlaubt für READ-only lookups. WRITES
12
- // IMMER via ctx.systemWriteAs damit Event-Store-Invariants bleiben
13
- // (Source-of-Truth + Projection läuft automatisch).
11
+ // ctx.db is permitted as an escape hatch for READ-only lookups. WRITES must
12
+ // always go through ctx.systemWriteAs so Event Store invariants remain intact
13
+ // (source of truth plus automatic projection).
14
14
  export {};
@@ -1,12 +1,11 @@
1
1
  /// <reference types="temporal-polyfill/global" preserve="true" />
2
- // Event-Store Admin-API — Marten-Bypass für Legacy-Daten-Importe.
3
- // Spec + Verhalten: docs/plans/features/event-store-admin-api.md
2
+ // Event-store admin API — bypasses Marten for legacy-data imports.
3
+ // Specification and behavior: docs/plans/features/event-store-admin-api.md
4
4
  //
5
- // Guard-Rail: dieses Modul ist NICHT aus event-store/index.ts re-exportiert.
6
- // Import nur via deep-path `@cosmicdrift/kumiko-framework/event-store/admin-api`. Das
7
- // Guard-Script `scripts/guard-admin-api.ts` blockt Aufrufe aus App-Code —
8
- // Allowlist: samples/*/migration/, scripts/migrations/, die Definition
9
- // selbst, das Guard-Script selbst.
5
+ // Guard rail: this module is NOT re-exported from event-store/index.ts. Import only via the deep
6
+ // path `@cosmicdrift/kumiko-framework/event-store/admin-api`. The guard script
7
+ // `scripts/guard-admin-api.ts` blocks calls from application code. Allowlist: samples/*/migration/,
8
+ // scripts/migrations/, this definition, and the guard script itself.
10
9
  import { constraintOf, isUniqueViolation } from "../db/pg-error.js";
11
10
  import { claimXactId, runAtomicEventWrite } from "../db/queries/event-store.js";
12
11
  import { eventPredecessorExists, findExistingEventVersion, insertRawEventBatch, insertRawFirstEvent, insertRawSubsequentEvent, } from "../db/queries/event-store-admin.js";
@@ -3,7 +3,7 @@
3
3
  // errors so the executor layer can map them to the framework's
4
4
  // WriteResult error contract (version_conflict).
5
5
  //
6
- // These live here and not in kumiko-types (#1629): `instanceof` needs a
6
+ // These live here and not in kumiko-types: `instanceof` needs a
7
7
  // single copy of the class, which forced kumiko-types to be a peerDependency
8
8
  // and made every minor changeset resolve to a major bump.
9
9
  export class VersionConflictError extends Error {
@@ -35,7 +35,7 @@ import { currentSeedCreatedAt } from "./seed-clock.js";
35
35
  // by TCP round-trip instead of pollIntervalMs.
36
36
  export const EVENTS_PUBSUB_CHANNEL = "kumiko_events_new";
37
37
  export async function append(db, event) {
38
- // Event-PII (#799): stored payload AND returned echo carry ciphertext, so
38
+ // Event-PII: stored payload AND returned echo carry ciphertext, so
39
39
  // inline projections and rebuilds materialize identical rows.
40
40
  const payload = await encryptEventPayloadPii(event.type, event.payload, {
41
41
  tenantId: event.tenantId,
@@ -80,7 +80,7 @@ export async function append(db, event) {
80
80
  throw e;
81
81
  }
82
82
  }
83
- // #3043 — attribution is derived from the execution scope, never taken from
83
+ // Attribution is derived from the execution scope, never taken from
84
84
  // the caller: a passed-in value can lie about who wrote the row, a derived
85
85
  // one cannot. Hence overwrite rather than merge. appendRaw/appendRawBatch
86
86
  // bypass this deliberately — they replay historical rows verbatim.
@@ -5,7 +5,7 @@
5
5
  // whose (possibly years-old) payload makes its apply throw rolls the whole
6
6
  // replay back — the projection stays permanently un-rebuildable until the
7
7
  // event is repaired by hand. The upcaster dead-letter only quarantines
8
- // upcast-TRANSFORM failures, not apply failures (#760).
8
+ // upcast-TRANSFORM failures, not apply failures.
9
9
  //
10
10
  // Quarantine mode (errorPolicy.skipApplyErrors on RebuildDeps, or
11
11
  // MspErrorMode.rebuild.skipApplyErrors on the MSP definition) confines each
@@ -4,17 +4,16 @@ import { FILE_REF_ENTITY_NAME, fileRefEntity } from "./file-ref-entity.js";
4
4
  import { DEFAULT_UPLOAD_SWEEP_GRACE_HOURS, MIN_UPLOAD_SWEEP_GRACE_HOURS, sweepOrphanedUploads, } from "./sweep-orphaned-uploads.js";
5
5
  import { fileUploadLedgerTable } from "./upload-ledger.js";
6
6
  export { fileRefEntity } from "./file-ref-entity.js";
7
- // files — `fileRef` als ganz normales ES-Entity. Upload/Delete laufen über
8
- // den Standard-Entity-Executor (file-routes.ts: executor.create/delete →
9
- // `fileRef.created`/`fileRef.deleted` → applyEntityEvent materialisiert
10
- // `file_refs`). Soft-Delete/Anonymize/Restore/Retention kommen damit
11
- // generisch aus dem Entity-Lifecycle + data-retention — keine
12
- // file-spezifische Lösch-Logik.
7
+ // `fileRef` is a regular ES entity. Uploads and deletes go through the standard
8
+ // entity executor (file-routes.ts: executor.create/delete → `fileRef.created` /
9
+ // `fileRef.deleted` → applyEntityEvent materializes `file_refs`), so soft-delete,
10
+ // anonymization, restore and retention come generically from the entity lifecycle
11
+ // and data-retention, with no file-specific deletion logic.
13
12
  //
14
- // `r.entity` macht die Tabelle für PII-Boot-Validation +
15
- // userData/tenantData-Extensions sichtbar und registriert die implizite
16
- // Entity-Projektion (für rebuildProjection). Liegt im Framework neben
17
- // file-routes + fileRefsTable; bundled-features/files re-exportiert nur.
13
+ // `r.entity` exposes the table to PII boot validation and the userData/tenantData
14
+ // extensions, and registers the implicit entity projection used by
15
+ // rebuildProjection. It lives in the framework next to file-routes and
16
+ // fileRefsTable; bundled-features/files only re-exports it.
18
17
  export const FILES_FEATURE = "files";
19
18
  function assertValidSweepGraceHours(hours) {
20
19
  if (!Number.isInteger(hours) || hours < MIN_UPLOAD_SWEEP_GRACE_HOURS) {
@@ -121,12 +121,11 @@ export function deriveKey(key, suffix) {
121
121
  const { stem, ext } = splitKeyExtension(key);
122
122
  return ext === "" ? `${key}.${suffix}` : `${stem}.${suffix}${ext}`;
123
123
  }
124
- // The prefix that covers a key AND every `derive()`d variant of it —
125
- // `foo/bar.jpg` → `foo/bar.`, matching `foo/bar.jpg` and `foo/bar.medium.jpg`
126
- // alike, since derive() only ever inserts a suffix between the stem and the
127
- // extension. Used by the tenant-handover destroy sweep (kumiko-framework#3035)
128
- // to reach a moved fileRef's original plus every already-rendered derivative,
129
- // none of which have their own `file_refs` row.
124
+ // The prefix that covers a key AND every `derive()`d variant of it — `foo/bar.jpg` → `foo/bar.`,
125
+ // matching `foo/bar.jpg` and `foo/bar.medium.jpg` alike, since derive() only ever inserts a suffix
126
+ // between the stem and the extension. Used by the tenant-handover destroy sweep to reach a moved
127
+ // fileRef's original plus every already-rendered derivative, none of which have their own
128
+ // `file_refs` row.
130
129
  export function storageKeyStemPrefix(key) {
131
130
  const { stem, ext } = splitKeyExtension(key);
132
131
  return ext === "" ? `${key}.` : `${stem}.`;
@@ -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 {};