@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
@@ -61,7 +61,7 @@ function parentRefEdgesFrom(entityName, entity, allEntityNames) {
61
61
  //
62
62
  // A type can legitimately be reached by more than one edge — `note.vehicleId`
63
63
  // and `note.campaignId` are distinct, and collapsing them by entity name would
64
- // leave one edge's rows behind, the silent partial move #3088 exists to end.
64
+ // leave one edge's rows behind, causing a silent partial move.
65
65
  export function resolveTransferAdjacency(entities, rootEntityType) {
66
66
  const allEntityNames = [...entities.keys()];
67
67
  const byParent = new Map();
@@ -96,7 +96,7 @@ export function findOverDeepTransferChain(adjacency, onPath) {
96
96
  // skip: a cycle revisits a type already on this path, and schema depth
97
97
  // cannot measure how far it actually runs — that depends on the rows, not
98
98
  // the declaration. The mover carries this one instead, failing with
99
- // `transfer_graph_too_deep` when its rounds run out (#3131).
99
+ // `transfer_graph_too_deep` when its rounds run out.
100
100
  if (onPath.includes(edge.entityName))
101
101
  continue;
102
102
  const deeper = findOverDeepTransferChain(adjacency, [...onPath, edge.entityName]);
@@ -23,35 +23,16 @@
23
23
  // caught by this validator. A future lint-rule will enforce the contract
24
24
  // statically; today it lives in this comment + the StepBuilder doc.
25
25
  import { KUMIKO_META_SYMBOL } from "@cosmicdrift/kumiko-types/schema-table-types";
26
+ import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
26
27
  import { schemaTableName } from "../db/table-introspection.js";
27
28
  import { getStep } from "./define-step.js";
28
29
  import { buildPipelineSteps } from "./pipeline.js";
30
+ import { walkStepInstances } from "./walk-step-instances.js";
29
31
  // Listed step-kinds whose `args.table` must be in the owning feature's
30
32
  // r.requires.projection allowlist. Extend as further unsafeProjection.*
31
33
  // steps land — don't pre-list hypothetical kinds (CLAUDE.md: don't design
32
34
  // for scenarios that can't happen).
33
35
  const UNSAFE_PROJECTION_KINDS = new Set(["unsafeProjectionUpsert", "unsafeProjectionDelete"]);
34
- // Sub-pipeline arg-paths come from `defineStep({ subPaths: [...] })` —
35
- // each builder declares its own (e.g. branch's onTrue/onFalse, forEach's
36
- // do). Walking via the registry means nested unsafeProjection-* in NEW
37
- // sub-step-builders is automatically caught the moment the builder
38
- // registers itself; no central map to keep in sync. Followup #15.
39
- function* walkAllSteps(steps) {
40
- for (const step of steps) {
41
- yield step;
42
- const def = getStep(step.kind);
43
- const subPaths = def?.subPaths;
44
- if (!subPaths || subPaths.length === 0)
45
- continue;
46
- const args = step.args;
47
- for (const path of subPaths) {
48
- const subSteps = args[path];
49
- if (Array.isArray(subSteps)) {
50
- yield* walkAllSteps(subSteps);
51
- }
52
- }
53
- }
54
- }
55
36
  // table()/buildEntityTable spread column handles as enumerable props, so an
56
37
  // entity field named `tableName`/`source` would shadow the matching meta key —
57
38
  // the canonical meta under KUMIKO_META_SYMBOL is the only collision-safe source.
@@ -95,7 +76,6 @@ export function validateProjectionAllowlist(features) {
95
76
  // the first feature's projections (and vice-versa). Detect and surface
96
77
  // here so the error names BOTH owners; without this, the silent-set
97
78
  // would point any later unsafeProjection-error at the wrong feature.
98
- // Followup #8.
99
79
  const aggregateTables = new Map();
100
80
  for (const f of features) {
101
81
  for (const [entityName, entity] of Object.entries(f.entities ?? {})) {
@@ -129,8 +109,8 @@ export function validateProjectionAllowlist(features) {
129
109
  `Closure body must produce the step list without reading event-payload fields ` +
130
110
  `(those belong inside step resolvers).`);
131
111
  }
132
- for (const step of walkAllSteps(steps)) {
133
- // Tier-2 step-discovery (Q9): require explicit opt-in via
112
+ for (const step of walkStepInstances(steps, `[Feature ${f.name}] writeHandler "${handlerName}"`)) {
113
+ // Tier-2 step-discovery: require explicit opt-in via
134
114
  // r.requires.step("<kind>") for any tier-2+ step. Tier-1 is implicit.
135
115
  const def = getStep(step.kind);
136
116
  if (def?.tier === 2 && !f.requiredSteps.has(step.kind)) {
@@ -140,12 +120,12 @@ export function validateProjectionAllowlist(features) {
140
120
  }
141
121
  if (!UNSAFE_PROJECTION_KINDS.has(step.kind))
142
122
  continue;
143
- const stepArgs = step.args;
144
- if (!stepArgs.table) {
123
+ const stepTable = isPlainObject(step.args) ? step.args["table"] : undefined;
124
+ if (!stepTable) {
145
125
  throw new Error(`[Feature ${f.name}] writeHandler "${handlerName}" has a ${step.kind} step ` +
146
126
  `without a \`table\` argument.`);
147
127
  }
148
- const tableName = resolveTableNameFromStep(stepArgs.table);
128
+ const tableName = resolveTableNameFromStep(stepTable);
149
129
  const aggregateOwner = aggregateTables.get(tableName);
150
130
  if (aggregateOwner) {
151
131
  throw new Error(`[Feature ${f.name}] writeHandler "${handlerName}" uses ${step.kind} on table "${tableName}", ` +
@@ -0,0 +1,2 @@
1
+ import type { StepInstance } from "./types/step.ts";
2
+ export declare function walkStepInstances(steps: readonly StepInstance[], where: string): Generator<StepInstance, void, void>;
@@ -0,0 +1,38 @@
1
+ import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
2
+ import { getStep } from "./define-step.js";
3
+ function isStepInstance(value) {
4
+ return isPlainObject(value) && typeof value["kind"] === "string";
5
+ }
6
+ // Sub-pipeline arg-paths come from `defineStep({ subPaths })`, so nested steps of new
7
+ // sub-step builders are walked without a central map.
8
+ // Fail-closed: an entry the walker cannot read would hide a step from the caller's check
9
+ // (projection allowlist, event-trigger support), so malformed input throws instead of being skipped.
10
+ export function* walkStepInstances(steps, where) {
11
+ for (const [stepIndex, step] of steps.entries()) {
12
+ yield step;
13
+ const subPaths = getStep(step.kind)?.subPaths;
14
+ if (!subPaths || subPaths.length === 0)
15
+ continue;
16
+ const stepWhere = `${where} step[${stepIndex}] "${step.kind}"`;
17
+ if (!isPlainObject(step.args)) {
18
+ throw new Error(`${stepWhere} declares sub-paths (${subPaths.join(", ")}) but its args is not an object.`);
19
+ }
20
+ for (const path of subPaths) {
21
+ const subSteps = step.args[path];
22
+ // skip: optional sub-path not provided (branch.run treats onFalse: null like absent)
23
+ if (subSteps === undefined || subSteps === null)
24
+ continue;
25
+ if (!Array.isArray(subSteps)) {
26
+ throw new Error(`${stepWhere} sub-path "${path}" must be a list of steps.`);
27
+ }
28
+ const checked = [];
29
+ for (const [entryIndex, entry] of subSteps.entries()) {
30
+ if (!isStepInstance(entry)) {
31
+ throw new Error(`${stepWhere} sub-path "${path}"[${entryIndex}] is not a step (expected an object with a string "kind").`);
32
+ }
33
+ checked.push(entry);
34
+ }
35
+ yield* walkStepInstances(checked, `${stepWhere} ${path}`);
36
+ }
37
+ }
38
+ }
@@ -1,4 +1,4 @@
1
- // Static lint for `WhereRule` SQL fragments (fw#2639).
1
+ // Static lint for `WhereRule` SQL fragments.
2
2
  //
3
3
  // A `where`-rule hands the framework raw SQL. If the author writes an
4
4
  // unqualified column name inside a correlated subquery, Postgres binds it
@@ -6,7 +6,7 @@ import { toKebab } from "./qualified-name.js";
6
6
  export const WRITE_HANDLER_QN_FORMAT_RE = /^[a-zA-Z][a-zA-Z0-9-]*:write:[a-zA-Z][a-zA-Z0-9-]*(:[a-zA-Z][a-zA-Z0-9-]*)*$/;
7
7
  // Matches `dispatcher.write("qn", ...)` and `<expr>.write("qn", ...)` —
8
8
  // same surface the CI guard scans. Dynamic QNs (variables, templates) are
9
- // intentionally skipped (known limitation, documented in #403).
9
+ // intentionally skipped (known limitation).
10
10
  const DISPATCHER_WRITE_LITERAL_RE = /\.write\s*\(\s*["']([^"']+)["']/g;
11
11
  /** Extract string-literal write-handler QNs from TS/TSX source text. */
12
12
  export function extractDispatcherWriteQnsFromSource(source) {
@@ -36,7 +36,7 @@
36
36
  //
37
37
  // SSE fanout across processes (split api/worker deploy, replicas > 1):
38
38
  // buildServer's default sseBroker is Redis-backed whenever REDIS_URL is
39
- // set (fw#2625) — a push on the worker's dispatcher reaches clients
39
+ // set — a push on the worker's dispatcher reaches clients
40
40
  // connected to any API instance via Redis Pub/Sub, no per-instance
41
41
  // cursor needed. Without REDIS_URL, the in-memory broker still only
42
42
  // reaches clients on the same process.
@@ -55,7 +55,7 @@ import { resolveCacheSyncBus } from "../redis/cache-sync-bus.js";
55
55
  // so the command-dispatcher fires handleEvent as an afterCommit-hook for
56
56
  // event-triggered jobs (dispatcher.ts:997). Without this plumbing,
57
57
  // `r.job({ trigger: { on: … } })` silently drops on every write — that
58
- // was the hidden Welle-2.5 gap.
58
+ // was the hidden gap.
59
59
  //
60
60
  // Caller-supplied `dispatcherOptions.jobRunner` wins (tests sometimes
61
61
  // inject a mock runner directly).
@@ -198,7 +198,7 @@ export function createApiEntrypoint(options) {
198
198
  const lifecycle = options.lifecycle ?? createLifecycle({ startReady: true });
199
199
  const observability = resolveObservability(options.observability);
200
200
  const { options: busOptions, context } = resolveEntrypointCacheSync(options, lifecycle);
201
- // Boot-validation (Welle 2.6.c) — fail loud before traffic arrives:
201
+ // Boot-validation — fail loud before traffic arrives:
202
202
  // (a) Any jobs declared + no jobs-block → command-dispatcher would
203
203
  // silently drop every event-triggered enqueue. Fix: add jobs:
204
204
  // { redisUrl } so the API holds lane-queue-clients.
@@ -116,12 +116,13 @@ export type ParseEnvOptions = {
116
116
  * is `undefined` in the result despite its `string` type — it is only set once
117
117
  * the boot-time decrypt (resolvePlatformKeks) has run. */
118
118
  export declare function parseEnv<S extends z.ZodObject<z.ZodRawShape>>(schema: S, env: Record<string, string | undefined>, options?: ParseEnvOptions): z.infer<S>;
119
- /** Dry-Run-Gegenstück zu parseEnv (KUMIKO_DRY_RUN_ENV-Pfad): parst jedes
120
- * Feld einzeln und liefert ein ehrliches Partial — vorhandene Werte kommen
121
- * typisiert (gecoerct) durch, fehlende/invalide fehlen einfach. Wirft nie:
122
- * der Dry-Run soll das Inventory rendern, nicht an required-Feldern
123
- * scheitern. Ersetzt das `({} as Shape)`-Muster, bei dem JEDER Zugriff
124
- * silent undefined war und das Compile-Level nichts davon wusste. */
119
+ /** Dry-run counterpart to parseEnv (KUMIKO_DRY_RUN_ENV path): parses each
120
+ * field individually and returns an honest Partial — present values pass
121
+ * through with their types (coerced), while missing or invalid values are
122
+ * simply omitted. Never throws: the dry run renders the inventory instead
123
+ * of failing on required fields. Replaces the `({} as Shape)` pattern, where
124
+ * every access silently returned undefined and the compiler could not detect
125
+ * it. */
125
126
  export declare function parseEnvDryRun<S extends z.ZodObject<z.ZodRawShape>>(schema: S, env: Record<string, string | undefined>): Partial<z.infer<S>>;
126
127
  export type EnvFieldStatus = "set" | "unset" | "missing" | "invalid";
127
128
  export type EnvFieldInspection = {
package/dist/env/index.js CHANGED
@@ -209,7 +209,7 @@ export function composeEnvSchema(options) {
209
209
  // rather than silently overwriting a deploy-critical default. The
210
210
  // conflict-detection makes the order safe in both directions — both
211
211
  // throw — but downstream tools that consume `sources` (e.g. the
212
- // upcoming Phase-5 lint-guard) may rely on this insertion order.
212
+ // upcoming lint-guard) may rely on this insertion order.
213
213
  if (options.core) {
214
214
  for (const [key, field] of Object.entries(zodShape(options.core))) {
215
215
  merged[key] = field;
@@ -356,12 +356,13 @@ export function parseEnv(schema, env, options = {}) {
356
356
  function ucfirst(s) {
357
357
  return s.length === 0 ? s : s.charAt(0).toUpperCase() + s.slice(1);
358
358
  }
359
- /** Dry-Run-Gegenstück zu parseEnv (KUMIKO_DRY_RUN_ENV-Pfad): parst jedes
360
- * Feld einzeln und liefert ein ehrliches Partial — vorhandene Werte kommen
361
- * typisiert (gecoerct) durch, fehlende/invalide fehlen einfach. Wirft nie:
362
- * der Dry-Run soll das Inventory rendern, nicht an required-Feldern
363
- * scheitern. Ersetzt das `({} as Shape)`-Muster, bei dem JEDER Zugriff
364
- * silent undefined war und das Compile-Level nichts davon wusste. */
359
+ /** Dry-run counterpart to parseEnv (KUMIKO_DRY_RUN_ENV path): parses each
360
+ * field individually and returns an honest Partial — present values pass
361
+ * through with their types (coerced), while missing or invalid values are
362
+ * simply omitted. Never throws: the dry run renders the inventory instead
363
+ * of failing on required fields. Replaces the `({} as Shape)` pattern, where
364
+ * every access silently returned undefined and the compiler could not detect
365
+ * it. */
365
366
  export function parseEnvDryRun(schema, env) {
366
367
  const out = {};
367
368
  for (const [name, field] of Object.entries(zodShape(schema))) {
@@ -372,8 +373,8 @@ export function parseEnvDryRun(schema, env) {
372
373
  if (result.success)
373
374
  out[name] = result.data;
374
375
  }
375
- // @cast-boundary schema-walk — Partial<z.infer<S>> erasure über den
376
- // per-Feld-safeParse; jeder enthaltene Wert hat seinen Feld-Parse bestanden.
376
+ // @cast-boundary schema-walk — Partial<z.infer<S>> erasure through the
377
+ // per-field safeParse; every included value passed its field-level parse.
377
378
  return out;
378
379
  }
379
380
  /** Checks every field of `schema` on its own against `env`, for the boot dry
@@ -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 = {