@cosmicdrift/kumiko-framework 0.386.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 (288) 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/routes.js +2 -2
  17. package/dist/api/server.js +34 -33
  18. package/dist/api/sse-broker.js +1 -1
  19. package/dist/api/sse-route.d.ts +13 -13
  20. package/dist/api/sse-route.js +15 -15
  21. package/dist/auth/index.js +4 -4
  22. package/dist/auth/roles.d.ts +4 -4
  23. package/dist/auth/roles.js +24 -24
  24. package/dist/compliance/duration-spec.js +18 -20
  25. package/dist/compliance/index.js +2 -3
  26. package/dist/compliance/override-schema.d.ts +4 -4
  27. package/dist/compliance/override-schema.js +19 -22
  28. package/dist/compliance/profiles.d.ts +26 -29
  29. package/dist/compliance/profiles.js +62 -69
  30. package/dist/compliance/sub-processors.d.ts +28 -30
  31. package/dist/compliance/sub-processors.js +17 -21
  32. package/dist/consumer-cli.js +4 -4
  33. package/dist/crypto/blind-index.js +18 -17
  34. package/dist/crypto/ciphertext-pattern.js +1 -1
  35. package/dist/crypto/event-pii.js +3 -3
  36. package/dist/crypto/kek-source.d.ts +1 -1
  37. package/dist/crypto/kek-source.js +3 -2
  38. package/dist/crypto/kms-adapter.d.ts +1 -1
  39. package/dist/crypto/kms-adapter.js +4 -6
  40. package/dist/crypto/kms-wiring.d.ts +1 -2
  41. package/dist/crypto/kms-wiring.js +2 -2
  42. package/dist/crypto/pg-kms-adapter.js +2 -6
  43. package/dist/crypto/pii-field-encryption.js +4 -4
  44. package/dist/crypto/subject-resolver.d.ts +1 -1
  45. package/dist/crypto/subject-resolver.js +2 -2
  46. package/dist/db/apply-entity-event.d.ts +6 -6
  47. package/dist/db/apply-entity-event.js +55 -55
  48. package/dist/db/blind-index-cleanup.js +7 -7
  49. package/dist/db/collect-table-metas.js +10 -11
  50. package/dist/db/compound-types.d.ts +1 -1
  51. package/dist/db/compound-types.js +12 -12
  52. package/dist/db/cursor.js +3 -4
  53. package/dist/db/dialect.d.ts +2 -2
  54. package/dist/db/dialect.js +2 -2
  55. package/dist/db/eagerload.d.ts +3 -3
  56. package/dist/db/eagerload.js +21 -12
  57. package/dist/db/entity-table-meta.d.ts +2 -2
  58. package/dist/db/entity-table-meta.js +4 -4
  59. package/dist/db/event-store-executor-context.d.ts +1 -1
  60. package/dist/db/event-store-executor-context.js +13 -13
  61. package/dist/db/event-store-executor-read.js +27 -28
  62. package/dist/db/event-store-executor-write.js +65 -70
  63. package/dist/db/located-timestamp.d.ts +11 -11
  64. package/dist/db/located-timestamp.js +29 -30
  65. package/dist/db/migrate-generator.d.ts +1 -1
  66. package/dist/db/migrate-generator.js +26 -19
  67. package/dist/db/migrate-runner.js +25 -25
  68. package/dist/db/money.d.ts +6 -6
  69. package/dist/db/money.js +29 -27
  70. package/dist/db/queries/backfill-pii.js +5 -5
  71. package/dist/db/queries/ddl.js +1 -1
  72. package/dist/db/queries/es-ops.d.ts +1 -1
  73. package/dist/db/queries/es-ops.js +1 -1
  74. package/dist/db/queries/event-consumer.js +3 -3
  75. package/dist/db/queries/event-store.js +4 -2
  76. package/dist/db/queries/projection-rebuild.js +3 -3
  77. package/dist/db/queries/raw-sql.d.ts +2 -2
  78. package/dist/db/queries/raw-sql.js +2 -2
  79. package/dist/db/queries/shadow-swap.js +19 -19
  80. package/dist/db/query.d.ts +1 -1
  81. package/dist/db/query.js +6 -6
  82. package/dist/db/rebuild-marker.js +20 -21
  83. package/dist/db/replay-migration-sql.js +2 -2
  84. package/dist/db/schema-inspection.js +4 -5
  85. package/dist/db/table-builder.d.ts +1 -2
  86. package/dist/db/table-builder.js +82 -82
  87. package/dist/db/tenant-db.d.ts +2 -2
  88. package/dist/db/tenant-db.js +9 -2
  89. package/dist/derivatives/delete-stored-file.js +5 -2
  90. package/dist/derivatives/derivatives-context.js +1 -1
  91. package/dist/engine/boot-validator/access-roles.js +4 -3
  92. package/dist/engine/boot-validator/api-ext.js +14 -14
  93. package/dist/engine/boot-validator/config-deps.js +7 -6
  94. package/dist/engine/boot-validator/detail-screens.js +3 -3
  95. package/dist/engine/boot-validator/entity-handler.js +60 -61
  96. package/dist/engine/boot-validator/gdpr-storage.js +4 -4
  97. package/dist/engine/boot-validator/i18n-keys.js +1 -1
  98. package/dist/engine/boot-validator/index.d.ts +2 -2
  99. package/dist/engine/boot-validator/index.js +20 -20
  100. package/dist/engine/boot-validator/nav.js +5 -5
  101. package/dist/engine/boot-validator/ownership.js +12 -9
  102. package/dist/engine/boot-validator/parent-ref.js +1 -1
  103. package/dist/engine/boot-validator/pii-retention.js +50 -57
  104. package/dist/engine/boot-validator/projection-list-screens.js +7 -7
  105. package/dist/engine/boot-validator/query-refs.js +1 -1
  106. package/dist/engine/boot-validator/screens-actions.js +15 -16
  107. package/dist/engine/boot-validator/screens-forms.js +10 -11
  108. package/dist/engine/boot-validator/screens-lists.js +14 -15
  109. package/dist/engine/boot-validator/screens.js +34 -40
  110. package/dist/engine/boot-validator/security-baseline.js +3 -2
  111. package/dist/engine/boot-validator/transfer-graph.js +2 -2
  112. package/dist/engine/boot-validator/workspaces.js +3 -3
  113. package/dist/engine/bridge-stub.js +3 -4
  114. package/dist/engine/build-app-schema.js +75 -75
  115. package/dist/engine/build-config-feature-schema.js +50 -50
  116. package/dist/engine/build-target.js +20 -23
  117. package/dist/engine/codemod/pipeline-codemod.js +20 -13
  118. package/dist/engine/config-helpers.js +4 -4
  119. package/dist/engine/constants.js +2 -2
  120. package/dist/engine/create-app.js +2 -2
  121. package/dist/engine/define-feature.js +12 -11
  122. package/dist/engine/define-handler.js +12 -11
  123. package/dist/engine/define-step.js +1 -1
  124. package/dist/engine/define-workflow.d.ts +5 -5
  125. package/dist/engine/define-workflow.js +6 -6
  126. package/dist/engine/entity-handlers.js +13 -14
  127. package/dist/engine/event-def.js +2 -2
  128. package/dist/engine/extension-names.d.ts +16 -17
  129. package/dist/engine/extension-names.js +37 -38
  130. package/dist/engine/extensions/escape-hatch-usage.js +1 -1
  131. package/dist/engine/extensions/tenant-data.js +2 -2
  132. package/dist/engine/extensions/user-data.d.ts +23 -24
  133. package/dist/engine/factories.d.ts +14 -13
  134. package/dist/engine/factories.js +38 -37
  135. package/dist/engine/feature-ast/entity-field-types.js +1 -1
  136. package/dist/engine/feature-ast/extractors/handlers.js +1 -1
  137. package/dist/engine/feature-ast/extractors/hooks.js +1 -1
  138. package/dist/engine/feature-ast/extractors/shared.d.ts +2 -3
  139. package/dist/engine/feature-ast/extractors/shared.js +2 -3
  140. package/dist/engine/feature-ast/parse.js +13 -13
  141. package/dist/engine/feature-ast/patch.js +1 -1
  142. package/dist/engine/feature-ast/pattern-change-schema.js +1 -1
  143. package/dist/engine/feature-ast/patterns.d.ts +2 -2
  144. package/dist/engine/feature-ast/render.js +2 -2
  145. package/dist/engine/feature-config-events-jobs.js +15 -12
  146. package/dist/engine/feature-entity-handlers.js +9 -5
  147. package/dist/engine/feature-manifest.d.ts +9 -9
  148. package/dist/engine/feature-manifest.js +6 -7
  149. package/dist/engine/feature-ui-extensions.d.ts +3 -3
  150. package/dist/engine/feature-ui-extensions.js +30 -22
  151. package/dist/engine/field-access.js +5 -6
  152. package/dist/engine/index.d.ts +1 -0
  153. package/dist/engine/index.js +4 -4
  154. package/dist/engine/object-form.d.ts +1 -0
  155. package/dist/engine/object-form.js +14 -4
  156. package/dist/engine/ownership.js +3 -3
  157. package/dist/engine/parse-ref-target.js +5 -7
  158. package/dist/engine/pattern-library/index.js +2 -2
  159. package/dist/engine/pattern-library/library.js +2 -2
  160. package/dist/engine/pattern-library/types.js +3 -3
  161. package/dist/engine/pipeline.js +2 -2
  162. package/dist/engine/project-app-schema-for-roles.js +1 -1
  163. package/dist/engine/projection-helpers.js +9 -9
  164. package/dist/engine/qualified-name.js +2 -2
  165. package/dist/engine/registry-facade.js +1 -1
  166. package/dist/engine/registry-ingest.js +12 -12
  167. package/dist/engine/registry-state.js +17 -19
  168. package/dist/engine/registry-validate.js +12 -12
  169. package/dist/engine/run-in.js +2 -2
  170. package/dist/engine/run-pipeline.js +7 -0
  171. package/dist/engine/schema-builder.js +40 -38
  172. package/dist/engine/screen-filter-ops.js +17 -20
  173. package/dist/engine/screen-helpers.js +4 -3
  174. package/dist/engine/state-machine.d.ts +12 -12
  175. package/dist/engine/state-machine.js +7 -7
  176. package/dist/engine/steps/_resolver-utils.js +4 -3
  177. package/dist/engine/steps/for-each.js +4 -4
  178. package/dist/engine/steps/unsafe-projection-delete.js +1 -1
  179. package/dist/engine/steps/unsafe-projection-upsert.js +4 -4
  180. package/dist/engine/steps/wait-for-event.js +4 -4
  181. package/dist/engine/steps/workflow-system-events.js +3 -3
  182. package/dist/engine/tier-resolver-extension.d.ts +29 -27
  183. package/dist/engine/tier-resolver-extension.js +26 -26
  184. package/dist/engine/transfer-adjacency.js +2 -2
  185. package/dist/engine/validate-projection-allowlist.js +7 -27
  186. package/dist/engine/walk-step-instances.d.ts +2 -0
  187. package/dist/engine/walk-step-instances.js +38 -0
  188. package/dist/engine/where-rule-lint.js +1 -1
  189. package/dist/engine/write-handler-qn-extract.js +1 -1
  190. package/dist/entrypoint/index.js +3 -3
  191. package/dist/env/index.d.ts +7 -6
  192. package/dist/env/index.js +10 -9
  193. package/dist/errors/transition-details.js +7 -7
  194. package/dist/errors/write-error-info.d.ts +6 -6
  195. package/dist/errors/write-error-info.js +6 -6
  196. package/dist/es-ops/context.d.ts +9 -9
  197. package/dist/es-ops/context.js +28 -28
  198. package/dist/es-ops/index.js +7 -9
  199. package/dist/es-ops/operations-schema.js +4 -10
  200. package/dist/es-ops/runner.d.ts +11 -11
  201. package/dist/es-ops/runner.js +49 -53
  202. package/dist/es-ops/types.d.ts +55 -55
  203. package/dist/es-ops/types.js +11 -11
  204. package/dist/event-store/admin-api.js +6 -7
  205. package/dist/event-store/errors.js +1 -1
  206. package/dist/event-store/event-store.js +2 -2
  207. package/dist/event-store/rebuild-dead-letter.js +1 -1
  208. package/dist/files/feature.js +9 -10
  209. package/dist/files/file-handle.js +5 -6
  210. package/dist/files/file-ref-entity.js +15 -15
  211. package/dist/files/file-ref-table.js +7 -8
  212. package/dist/files/file-routes.d.ts +7 -0
  213. package/dist/files/file-routes.js +11 -5
  214. package/dist/files/in-memory-provider.js +8 -9
  215. package/dist/files/local-provider.js +18 -25
  216. package/dist/files/provider-resolver.d.ts +7 -7
  217. package/dist/files/storage-tracking.js +2 -2
  218. package/dist/files/zip-stream.d.ts +12 -19
  219. package/dist/files/zip-stream.js +44 -63
  220. package/dist/i18n/required-surface-keys.d.ts +1 -1
  221. package/dist/i18n/required-surface-keys.js +8 -8
  222. package/dist/jobs/job-runner-helpers.js +8 -2
  223. package/dist/jobs/job-runner-schedulers.d.ts +1 -1
  224. package/dist/jobs/job-runner-schedulers.js +3 -4
  225. package/dist/jobs/job-runner.js +13 -15
  226. package/dist/lifecycle/lifecycle.js +3 -5
  227. package/dist/lifecycle/signal-handlers.js +5 -4
  228. package/dist/logging/utils.d.ts +2 -1
  229. package/dist/logging/utils.js +38 -10
  230. package/dist/migrations/index.js +1 -1
  231. package/dist/migrations/kumiko-drift.js +14 -13
  232. package/dist/migrations/pending-rebuilds.d.ts +21 -20
  233. package/dist/migrations/pending-rebuilds.js +27 -27
  234. package/dist/migrations/projection-table-index.d.ts +3 -3
  235. package/dist/migrations/projection-table-index.js +8 -8
  236. package/dist/observability/metric-validator.js +1 -1
  237. package/dist/observability/metrics-handle.js +3 -3
  238. package/dist/pipeline/append-event-core.js +11 -2
  239. package/dist/pipeline/cascade-handler.d.ts +1 -1
  240. package/dist/pipeline/dispatch-batch.js +2 -2
  241. package/dist/pipeline/dispatch-query.js +10 -10
  242. package/dist/pipeline/dispatch-shared.d.ts +1 -0
  243. package/dist/pipeline/dispatch-shared.js +15 -17
  244. package/dist/pipeline/dispatch-stream.js +1 -1
  245. package/dist/pipeline/dispatch-write.js +3 -3
  246. package/dist/pipeline/dispatcher-utils.d.ts +1 -1
  247. package/dist/pipeline/dispatcher-utils.js +1 -1
  248. package/dist/pipeline/event-consumer-state.js +2 -2
  249. package/dist/pipeline/event-dispatcher-delivery.js +1 -1
  250. package/dist/pipeline/event-dispatcher.js +17 -28
  251. package/dist/pipeline/irreversible-operation-gate.js +1 -1
  252. package/dist/pipeline/lifecycle-pipeline.js +9 -9
  253. package/dist/pipeline/member-read-only-transaction.js +1 -3
  254. package/dist/pipeline/msp-rebuild.js +1 -1
  255. package/dist/pipeline/projection-rebuild.js +13 -12
  256. package/dist/pipeline/system-hooks.js +39 -41
  257. package/dist/pipeline/system-identity-switch.js +1 -3
  258. package/dist/pipeline/tenant-timezone-cache.js +1 -1
  259. package/dist/random/generate.d.ts +29 -29
  260. package/dist/random/generate.js +30 -30
  261. package/dist/random/words.js +20 -20
  262. package/dist/rate-limit/middleware.js +9 -5
  263. package/dist/redis/client.js +2 -2
  264. package/dist/redis/pubsub-signal.js +14 -17
  265. package/dist/schema-cli.js +116 -22
  266. package/dist/search/index.js +4 -4
  267. package/dist/search/purge-subject.js +4 -5
  268. package/dist/search/reindex-entity.js +6 -9
  269. package/dist/time/boot-tz-warning.d.ts +2 -2
  270. package/dist/time/boot-tz-warning.js +8 -8
  271. package/dist/time/iana.js +10 -10
  272. package/dist/time/index.js +9 -9
  273. package/dist/time/legacy-date.d.ts +2 -2
  274. package/dist/time/legacy-date.js +6 -8
  275. package/dist/time/polyfill.js +1 -1
  276. package/dist/time/tz-context.d.ts +1 -1
  277. package/dist/time/tz-context.js +1 -1
  278. package/dist/ui-types/app-schema.js +7 -7
  279. package/dist/ui-types/list-row-meta.js +4 -4
  280. package/dist/upgrade-cli.js +10 -9
  281. package/dist/utils/compare.js +1 -1
  282. package/package.json +4 -4
  283. package/src/changes.json +48 -0
  284. package/src/scripts/codemod/crypto-shredding-testing-move.ts +2 -2
  285. package/src/scripts/codemod/migrate-cross-tenant.ts +1 -1
  286. package/src/scripts/codemod/migrate-open-to-all.ts +1 -1
  287. package/src/scripts/codemod/pii-personal-migration.ts +17 -8
  288. package/src/scripts/codemod/unprocessable-error-details-reason.ts +2 -2
@@ -1,11 +1,11 @@
1
- // Gemeinsame Detail-Shape für invalid_transition-Errors. Beide Pfade
2
- // (state-machine.assertTransition + failTransition) müssen identisch
3
- // strukturierte Details liefern, sonst können HTTP-Clients den 422-Body
4
- // nicht uniform parsen — `from`/`to`/`allowed` sind die strukturierten
5
- // Felder, `message` ist die menschen-lesbare Form.
1
+ // Shared detail shape for invalid_transition errors. Both paths
2
+ // (`state-machine.assertTransition` and `failTransition`) must provide
3
+ // identically structured details so HTTP clients can parse the 422 body uniformly:
4
+ // `from`/`to`/`allowed` are the structured fields, while `message` is the
5
+ // human-readable form.
6
6
  //
7
- // Kein Re-Export von "validTargets" (CSV-string) mehr — wer CSV will
8
- // baut's via `allowed.join(", ")`. Eine Shape, ein Vertrag.
7
+ // `validTargets` is not re-exported as a CSV string; callers that need CSV can use
8
+ // `allowed.join(", ")`. One shape defines the contract.
9
9
  export function buildInvalidTransitionDetails(from, to, allowed) {
10
10
  return {
11
11
  from,
@@ -7,13 +7,13 @@ export declare function failUnprocessable(reason: string, details?: Readonly<Rec
7
7
  readonly reason?: never;
8
8
  }): WriteFailure;
9
9
  /**
10
- * Convenience für State-Transition-Rejects: produces a WriteFailure mit
11
- * reason="invalid_transition" + strukturiertem `from`/`to`/`allowed`-
12
- * Detail-Block + lesbarer message. Pattern hat sich in publicstatus-
13
- * Maintenance + bestehenden state-machine-Helpers wiederholt — Helper
14
- * sammelt das in einem Aufruf statt drei manuelle Detail-Felder.
10
+ * Convenience for state-transition rejects: produces a WriteFailure with
11
+ * reason="invalid_transition", a structured `from`/`to`/`allowed` detail
12
+ * block, and a readable message. This pattern recurs in publicstatus
13
+ * maintenance and existing state-machine helpers, so the helper collects it
14
+ * in one call instead of requiring three manual detail fields.
15
15
  *
16
- * `allowed` ist typisch `MAINTENANCE_TRANSITIONS.allowedFrom(from)`.
16
+ * `allowed` is typically `MAINTENANCE_TRANSITIONS.allowedFrom(from)`.
17
17
  */
18
18
  export declare function failTransition(from: string, to: string, allowed: readonly string[]): WriteFailure;
19
19
  export declare function toWriteErrorInfo(err: KumikoError): WriteErrorInfo;
@@ -24,13 +24,13 @@ export function failUnprocessable(reason, details) {
24
24
  return writeFailure(new UnprocessableError(reason, details ? { details } : undefined));
25
25
  }
26
26
  /**
27
- * Convenience für State-Transition-Rejects: produces a WriteFailure mit
28
- * reason="invalid_transition" + strukturiertem `from`/`to`/`allowed`-
29
- * Detail-Block + lesbarer message. Pattern hat sich in publicstatus-
30
- * Maintenance + bestehenden state-machine-Helpers wiederholt — Helper
31
- * sammelt das in einem Aufruf statt drei manuelle Detail-Felder.
27
+ * Convenience for state-transition rejects: produces a WriteFailure with
28
+ * reason="invalid_transition", a structured `from`/`to`/`allowed` detail
29
+ * block, and a readable message. This pattern recurs in publicstatus
30
+ * maintenance and existing state-machine helpers, so the helper collects it
31
+ * in one call instead of requiring three manual detail fields.
32
32
  *
33
- * `allowed` ist typisch `MAINTENANCE_TRANSITIONS.allowedFrom(from)`.
33
+ * `allowed` is typically `MAINTENANCE_TRANSITIONS.allowedFrom(from)`.
34
34
  */
35
35
  export function failTransition(from, to, allowed) {
36
36
  return writeFailure(new UnprocessableError(FrameworkReasons.invalidTransition, {
@@ -5,15 +5,15 @@ export type CreateSeedMigrationContextArgs = {
5
5
  readonly dispatcher: Dispatcher;
6
6
  readonly dbRunner: DbRunner;
7
7
  };
8
- /** Builder: gibt eine factory-function zurück die der Runner pro-Migration
9
- * aufruft. Der dbRunner kann eine Top-Connection oder eine Tx sein —
10
- * Read-Helpers nutzen ihn direkt, systemWriteAs delegiert an dispatcher.
8
+ /** Builder that returns a factory function called by the runner for each
9
+ * migration. dbRunner may be a top-level connection or a transaction—read
10
+ * helpers use it directly, while systemWriteAs delegates to the dispatcher.
11
11
  *
12
- * Hinweis: dispatcher.write bekommt den Runner-tx NICHT durchgereicht —
13
- * runBatch öffnet eine eigene Transaktion auf context.db (eigene Pool-
14
- * Connection). Die Writes committen daher unabhängig: ein Runner-Failure
15
- * rollt NUR den Marker-Insert + dbRunner-reads zurück, die dispatcher-Writes
16
- * bleiben committed. Seeds MÜSSEN deshalb idempotent sein (verifiziert in
17
- * runner.integration.test.ts „dispatcher-writes vor throw bleiben committed"). */
12
+ * dispatcher.write does not receive the runner transaction: runBatch opens
13
+ * a separate transaction on context.db (using a separate pool connection).
14
+ * Writes therefore commit independently: a runner failure rolls back only
15
+ * the marker insert and dbRunner reads; dispatcher writes remain committed.
16
+ * Seeds must therefore be idempotent (verified in
17
+ * runner.integration.test.ts “dispatcher-writes vor throw bleiben committed”). */
18
18
  export declare function createSeedMigrationContext(args: CreateSeedMigrationContextArgs): SeedMigrationContext;
19
19
  export type { SeedMigrationContext } from "./types.ts";
@@ -1,45 +1,45 @@
1
- // SeedMigrationContext-Builder. Caller (runProdApp/CLI) übergibt einen
2
- // schon-konfigurierten Dispatcher; der Builder produziert pro-Migration
3
- // einen tx-scoped Context der via dispatcher.write den existierenden
4
- // Handler-Pfad nutzt — gleiches Pattern wie ein User-UI-Click.
1
+ // SeedMigrationContext builder. The caller (runProdApp/CLI) supplies a
2
+ // preconfigured dispatcher; the builder creates a transaction-scoped context
3
+ // for each migration that uses the existing handler path through
4
+ // dispatcher.write—the same pattern as a user UI click.
5
5
  //
6
- // SystemUser bypassed Access-Checks (Standard-Seed-Pattern, siehe
7
- // config-seed.ts:40). Events haben createdBy = SYSTEM_TENANT_ID-User
8
- // → audit-fähig.
6
+ // The system user bypasses access checks (the standard seed pattern; see
7
+ // config-seed.ts:40). Events have createdBy = SYSTEM_TENANT_ID user,
8
+ // making them auditable.
9
9
  import { configuredPiiSubjectKms, decryptPiiFieldValues } from "../crypto/index.js";
10
10
  import { selectAllTenants, selectMembershipsOfUser, selectTemplateResources, selectUserByEmail, } from "../db/queries/seed-context.js";
11
11
  import { createSystemUser, SYSTEM_TENANT_ID } from "../engine/index.js";
12
12
  import { parseStringArrayJson } from "../utils/parse-string-array-json.js";
13
- /** Builder: gibt eine factory-function zurück die der Runner pro-Migration
14
- * aufruft. Der dbRunner kann eine Top-Connection oder eine Tx sein —
15
- * Read-Helpers nutzen ihn direkt, systemWriteAs delegiert an dispatcher.
13
+ /** Builder that returns a factory function called by the runner for each
14
+ * migration. dbRunner may be a top-level connection or a transaction—read
15
+ * helpers use it directly, while systemWriteAs delegates to the dispatcher.
16
16
  *
17
- * Hinweis: dispatcher.write bekommt den Runner-tx NICHT durchgereicht —
18
- * runBatch öffnet eine eigene Transaktion auf context.db (eigene Pool-
19
- * Connection). Die Writes committen daher unabhängig: ein Runner-Failure
20
- * rollt NUR den Marker-Insert + dbRunner-reads zurück, die dispatcher-Writes
21
- * bleiben committed. Seeds MÜSSEN deshalb idempotent sein (verifiziert in
22
- * runner.integration.test.ts „dispatcher-writes vor throw bleiben committed"). */
17
+ * dispatcher.write does not receive the runner transaction: runBatch opens
18
+ * a separate transaction on context.db (using a separate pool connection).
19
+ * Writes therefore commit independently: a runner failure rolls back only
20
+ * the marker insert and dbRunner reads; dispatcher writes remain committed.
21
+ * Seeds must therefore be idempotent (verified in
22
+ * runner.integration.test.ts “dispatcher-writes vor throw bleiben committed”). */
23
23
  export function createSeedMigrationContext(args) {
24
- // Default-Executor für System-scope-Aggregates (config-values, system
25
- // text-content, etc.). Bei Tenant-scope-Aggregates muss der Caller
26
- // explizit `tenantIdOverride` übergeben — siehe types.ts Doku.
24
+ // Default executor for system-scope aggregates (config-values, system
25
+ // text-content, etc.). For tenant-scope aggregates, the caller must
26
+ // explicitly provide `tenantIdOverride`—see the documentation in types.ts.
27
27
  const defaultSystemUser = createSystemUser(SYSTEM_TENANT_ID);
28
28
  return {
29
29
  systemWriteAs: async (handlerQualifiedName, payload, tenantIdOverride, extraRoles) => {
30
- // tenantIdOverride: baut einen System-User mit der Stream-tenantId
31
- // damit der Event-Store-Executor das Aggregate im richtigen Stream
32
- // findet. Verhindert die version_conflict-Falle (siehe Memory
33
- // feedback_event_store_tenant_consistency.md).
30
+ // tenantIdOverride creates a system user with the stream tenant ID so that
31
+ // the event-store executor finds the aggregate in the correct stream.
32
+ // This prevents the version_conflict failure.
34
33
  const executor = tenantIdOverride !== undefined || (extraRoles && extraRoles.length > 0)
35
34
  ? createSystemUser(tenantIdOverride ?? SYSTEM_TENANT_ID, extraRoles)
36
35
  : defaultSystemUser;
37
36
  const result = await args.dispatcher.write(handlerQualifiedName, payload, executor);
38
- // Critical: WriteResult{isSuccess: false} würde sonst silent durchlaufen
39
- // → Marker landet trotz failed-Write → Migration falsch als "applied"
40
- // markiert. Hier throw damit der Runner's outer-tx rollback macht und
41
- // Marker NICHT geschrieben wird. Seed-Author kann via try/catch eigene
42
- // Fehler-Behandlung machen wenn ein soft-failure erwartet ist.
37
+ // Without this check, WriteResult{isSuccess: false} would pass silently,
38
+ // causing the marker to be written despite the failed write and the
39
+ // migration to be incorrectly marked as "applied". Throw so the
40
+ // runner's outer transaction rolls back and the marker is not written.
41
+ // Seed authors can use try/catch for custom handling when a soft failure
42
+ // is expected.
43
43
  if (!result.isSuccess) {
44
44
  const code = result.error?.code ?? "unknown";
45
45
  const message = result.error?.message ?? "(no message)";
@@ -1,14 +1,12 @@
1
- // es-ops — ES-Operations-Pattern für Kumiko-Apps.
1
+ // es-ops — ES operations pattern for Kumiko apps.
2
2
  //
3
- // Phase 1: seed-migrations. Phase 2+ docken an dieselbe Infra an
4
- // (kumiko_es_operations-Table mit operation_type-Discriminator).
3
+ // Seed migrations use the same infrastructure as later operation types
4
+ // (the `kumiko_es_operations` table with an `operation_type` discriminator).
5
5
  //
6
- // App-Author-API:
7
- // - SeedMigration: default-export-Typ einer seed-file
8
- // - runProdApp({ seedsDir }): Framework runs pending bei Boot
9
- // - bunx kumiko ops seed:new|status|apply (CLI)
10
- //
11
- // Plan-Doc: kumiko-platform/docs/plans/features/es-ops.md
6
+ // App author API:
7
+ // - `SeedMigration`: default export type of a seed file
8
+ // - `runProdApp({ seedsDir })`: the framework runs pending migrations at boot
9
+ // - `bunx kumiko ops seed:new|status|apply` (CLI)
12
10
  export { createSeedMigrationContext, } from "./context.js";
13
11
  export { createEsOperationsTable, esOperationsTable, } from "./operations-schema.js";
14
12
  export { runPendingSeedMigrations, } from "./runner.js";
@@ -1,12 +1,6 @@
1
- // Tracking-Table für ES-Operations (Phase 1: seed-migrations; Phase 2+:
2
- // projection-rebuild, event-replay, stream-migration, ... — siehe
3
- // kumiko-platform/docs/plans/features/es-ops.md).
4
- //
5
- // File-ID-Tracking analog drizzle-kit: filename ist die ID, applied-set
6
- // liegt in dieser Tabelle. Pending = files-on-disk MINUS applied-set.
7
- //
8
- // operation_type-Discriminator lässt Phase 2+ dieselbe Tabelle nutzen,
9
- // kein Schema-Sprawl. CLI-Status filtert per type:
1
+ // Tracks ES operations. The filename is the ID, so pending operations are files on disk minus the
2
+ // applied set. The operation_type discriminator lets different operation types share one table
3
+ // without schema sprawl; CLI status commands filter by type:
10
4
  // bunx kumiko ops seed:status → operation_type = "seed-migration"
11
5
  // bunx kumiko ops projection:status → operation_type = "projection-rebuild"
12
6
  import { index, integer, table as pgTable, sql, text, timestamp } from "../db/dialect.js";
@@ -24,7 +18,7 @@ export const esOperationsTable = pgTable("kumiko_es_operations", {
24
18
  .default(sql `now()`),
25
19
  durationMs: integer("duration_ms").notNull(),
26
20
  // Trace: came from boot-time auto-apply, explicit CLI, or CI step.
27
- // Helps when forensics ask "wer hat das wann angestoßen".
21
+ // Supports forensic questions about who initiated the operation and when.
28
22
  appliedBy: text("applied_by").notNull().$type(),
29
23
  // Optional human-readable annotation — surfaced in `ops <op>:status`.
30
24
  notes: text("notes"),
@@ -3,23 +3,23 @@ import type { Registry } from "../engine/index.ts";
3
3
  import type { EsOperationAppliedBy, SeedMigrationContext } from "./types.ts";
4
4
  export type RunPendingSeedMigrationsArgs = {
5
5
  readonly db: DbConnection;
6
- /** Absoluter Pfad zum seeds-Directory (typically <appRoot>/seeds). */
6
+ /** Absolute path to the seeds directory (typically <appRoot>/seeds). */
7
7
  readonly seedsDir: string;
8
- /** Factory die den Context für jede einzelne seed-Migration erzeugt.
9
- * Caller bekommt einen DbRunner (Connection ODER aktive Tx) — Runner
10
- * ruft das pro-migration im tx-Scope auf. */
8
+ /** Factory that creates the context for each seed migration.
9
+ * The caller receives a DbRunner (connection OR active transaction); the
10
+ * runner invokes this once per migration within the transaction scope. */
11
11
  readonly createContext: (dbRunner: DbRunner) => SeedMigrationContext;
12
12
  /** Trace-marker: boot | cli | ci-pipeline. Landet in applied_by. */
13
13
  readonly appliedBy: EsOperationAppliedBy;
14
- /** Optional registry für Dry-Run-Validation: parsed jeden seed-file und
15
- * checkt dass alle referenzierten handler-QNs in der Registry existieren
16
- * BEVOR die Migration läuft. Catched camelCase-typos + andere QN-Drift
17
- * zur Boot-Zeit statt mitten im write-cycle (Phase 1.5 / A2).
14
+ /** Optional registry for dry-run validation: parses each seed file and
15
+ * checks that all referenced handler QNs exist in the registry
16
+ * BEFORE the migration runs. This catches camelCase typos and other QN
17
+ * drift at boot rather than in the middle of a write cycle.
18
18
  *
19
- * Wenn weggelassen → kein Dry-Run (backward-compat für tests die ohne
20
- * Registry arbeiten). runProdApp reicht den richtigen Registry rein. */
19
+ * If omitted, no dry run is performed (backward compatibility for tests
20
+ * that do not provide a registry). runProdApp supplies the correct one. */
21
21
  readonly registry?: Registry;
22
- /** Optional log-prefix override, default "[es-ops/seed-migration]". */
22
+ /** Optional line sink; the default logs under the "es-ops/seed-migration" namespace. */
23
23
  readonly logger?: (line: string) => void;
24
24
  };
25
25
  export type RunPendingSeedMigrationsResult = {
@@ -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();