@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
@@ -16,10 +16,11 @@ export function isRepresentableAtScale(n, scale) {
16
16
  const tolerance = Math.abs(scaled) * 8 * Number.EPSILON + Number.EPSILON;
17
17
  return Math.abs(scaled - Math.round(scaled)) <= tolerance;
18
18
  }
19
- // Lexikografischer ISO-Vergleich — exakt für `yyyy-mm-dd` (date) und korrekt
20
- // für ISO-Datetime in konsistenter Repräsentation (gleiche Offset-/Präzisions-
21
- // Form). Bewusst ohne Date-API (no-date-api-Guard); die Tag-genaue Grenze
22
- // reicht für min/max-Use-Cases (z.B. Geburtsdatum nicht in der Zukunft).
19
+ // Lexicographic ISO comparison — exact for `yyyy-mm-dd` (date) and correct
20
+ // for ISO datetimes in a consistent representation (same offset/precision
21
+ // form). Deliberately avoids the Date API (no-date-api-Guard); day-level
22
+ // comparison is sufficient for min/max use cases (e.g. a birth date not in
23
+ // the future).
23
24
  function withDateBounds(schema, min, max) {
24
25
  if (min === undefined && max === undefined)
25
26
  return schema;
@@ -127,8 +128,9 @@ export function fieldToZod(field, currencies, opts = {}) {
127
128
  return field.default !== undefined && applyDefaults ? schema.default(field.default) : schema;
128
129
  }
129
130
  case "longText": {
130
- // longText hat keine `format`-Variante (per type-design). Nur
131
- // optional maxLength + required, sonst ein offener z.string().
131
+ // longText has no `format` variant (by type design). Only optional
132
+ // maxLength and required are supported; otherwise it is an unrestricted
133
+ // z.string().
132
134
  let schema = z.string();
133
135
  if (field.maxLength)
134
136
  schema = schema.max(field.maxLength);
@@ -148,13 +150,13 @@ export function fieldToZod(field, currencies, opts = {}) {
148
150
  if (field.default !== undefined) {
149
151
  // Untouched <select> sends "" too; with a default that maps to the
150
152
  // default (same semantics as undefined) instead of the invalid-value
151
- // rejection from #1702. A field with a default is never "unset" —
153
+ // rejection. A field with a default is never "unset" —
152
154
  // true on both insert AND update, so this branch (and its "" → default
153
155
  // mapping) fires regardless of applyDefaults; only the `.default(...)`
154
156
  // schema-level fallback for OMITTED input is update-gated below.
155
157
  // `null` maps the same way: the no-default branch below normalizes
156
158
  // an untouched select to null, and a client that reuses that value
157
- // against a since-defaulted field must not get rejected either.
159
+ // against a defaulted field must not get rejected either.
158
160
  const mapped = z.preprocess((value) => (value === "" || value === null ? field.default : value), enumSchema);
159
161
  return applyDefaults ? mapped.default(field.default) : mapped;
160
162
  }
@@ -171,9 +173,9 @@ export function fieldToZod(field, currencies, opts = {}) {
171
173
  const [first, ...rest] = field.options;
172
174
  if (!first)
173
175
  return z.array(z.string());
174
- // `required: true` heißt non-empty — Analogie zu `text`-Field.
175
- // Leeres Array wird rejected; das globale `.optional()`-Wrapping
176
- // in buildInsertSchema kümmert sich um „darf fehlen".
176
+ // `required: true` means non-empty, as with a `text` field.
177
+ // An empty array is rejected; the global `.optional()` wrapping
178
+ // in buildInsertSchema determines whether the field may be omitted.
177
179
  let schema = z.array(z.enum([first, ...rest]));
178
180
  if (field.required)
179
181
  schema = schema.min(1);
@@ -209,10 +211,10 @@ export function fieldToZod(field, currencies, opts = {}) {
209
211
  return field.default !== undefined && applyDefaults ? schema.default(field.default) : schema;
210
212
  }
211
213
  case "bigInt": {
212
- // JS-`number`-Round-trip via mode:"number"; sicher bis 2^53.
213
- // safe-integer-Cap ist explizit damit ein Caller, der einen
214
- // Float reinwirft (z.B. parseFloat-Bug), beim Insert sofort
215
- // failed statt silent-Truncation zu kassieren.
214
+ // JS-`number` round-trip via mode:"number"; exact up to 2^53.
215
+ // The safe-integer cap ensures that a caller passing a float (for example,
216
+ // due to a parseFloat bug) fails immediately at insert instead of being
217
+ // silently truncated.
216
218
  const schema = z.number().int().safe();
217
219
  return field.default !== undefined && applyDefaults ? schema.default(field.default) : schema;
218
220
  }
@@ -267,8 +269,8 @@ export function fieldToZod(field, currencies, opts = {}) {
267
269
  });
268
270
  }
269
271
  case "jsonb": {
270
- // Free-form jsonb — keys sind tenant-/runtime-defined. Validation
271
- // passthrough: any plain object passt durch.
272
+ // Free-form jsonb — keys are tenant-/runtime-defined. Validation
273
+ // passes through any plain object.
272
274
  return z.record(z.string(), z.unknown());
273
275
  }
274
276
  case "date": {
@@ -276,26 +278,26 @@ export function fieldToZod(field, currencies, opts = {}) {
276
278
  return withDateBounds(schema, field.min, field.max);
277
279
  }
278
280
  case "timestamp": {
279
- // Wenn locatedBy gesetzt: Wall-Clock OHNE Offset (ISO-Datetime ohne `Z`).
280
- // Sonst: ISO-UTC-Datetime (mit `Z`). Beide werden über z.iso.datetime
281
- // gegen das ISO-8601-Schema validiert; die Präzision (mit/ohne Offset)
282
- // hängt von locatedBy ab.
281
+ // When locatedBy is set: wall-clock time without an offset (ISO datetime
282
+ // without `Z`). Otherwise: ISO UTC datetime (with `Z`). Both are validated
283
+ // against the ISO-8601 schema through z.iso.datetime; precision (with or
284
+ // without an offset) depends on locatedBy.
283
285
  const schema = field.locatedBy !== undefined ? z.iso.datetime({ local: true }) : z.iso.datetime();
284
286
  return withDateBounds(schema, field.min, field.max);
285
287
  }
286
288
  case "tz": {
287
- // IANA-Zonenname, validiert gegen die Runtime-Zonenliste
288
- // (isValidIanaTimeZone). Ein ungültiger Name failt hier am
289
- // Write-Boundary statt erst später in ctx.tz.parse / Temporal.
289
+ // IANA time-zone name, validated against the runtime's zone list
290
+ // (isValidIanaTimeZone). An invalid name fails at the write boundary
291
+ // instead of later in ctx.tz.parse / Temporal.
290
292
  return z.string().refine(isValidIanaTimeZone, { message: "invalid IANA time zone" });
291
293
  }
292
294
  case "locatedTimestamp": {
293
- // Combined Wall-Clock+TZ Object. Beim Write akzeptieren wir entweder
294
- // { at, tz } (typisch UI-Form, utc wird berechnet) oder { utc, tz }
295
- // (typisch Server-zu-Server, at wird berechnet). Beim Read liefert
296
- // der Read-Wrapper alle drei Felder (siehe Phase D in MIGRATION.md).
295
+ // Combined wall-clock and time-zone object. On write, either
296
+ // { at, tz } (typical for a UI form, with utc calculated) or { utc, tz }
297
+ // (typical for server-to-server communication, with at calculated) is
298
+ // accepted. On read, the read wrapper provides all three fields.
297
299
  //
298
- // Hier nur die Schema-Garantie: mindestens tz + (at ODER utc).
300
+ // The schema guarantees only tz plus at least one of at or utc.
299
301
  const at = z.iso.datetime({ local: true });
300
302
  const tz = z.string().refine(isValidIanaTimeZone, { message: "invalid IANA time zone" });
301
303
  const utc = z.iso.datetime();
@@ -322,11 +324,12 @@ export function fieldToZod(field, currencies, opts = {}) {
322
324
  return field.maxCount === undefined ? ids : ids.max(field.maxCount);
323
325
  }
324
326
  case "reference":
325
- // Tier 2.7e-3: Validiert UUID-shape. Existenz-Check der Reference
326
- // (Row im referenced Table existiert + Tenant-Scope) ist Server-
327
- // side-Verantwortung im Handler / Foreign-Key-Constraint, nicht
328
- // im Schema-Validator (würde sonst Round-trip zur DB beim Parse).
329
- // Multi-Mode (Tier 2.7e-Multi): Array von UUIDs.
327
+ // Validates the UUID shape. Checking that the reference exists
328
+ // (the row exists in the referenced table and belongs to the tenant) is
329
+ // the handler / foreign-key constraint's responsibility, not the schema
330
+ // validator's; checking it here would require a database round trip during
331
+ // parsing.
332
+ // Multi-mode accepts an array of UUIDs.
330
333
  return field.multiple === true ? z.array(z.uuid()) : z.uuid();
331
334
  default:
332
335
  assertUnreachable(field, "field type");
@@ -342,8 +345,7 @@ export function fieldToZod(field, currencies, opts = {}) {
342
345
  // fails the check even if the raw minor-unit amounts happen to match.
343
346
  //
344
347
  // Known limitation: compares against rounded `derived` cells, i.e.
345
- // "sum-of-rounded" not "round-of-sum" (kumiko-framework#1866). Follow-up
346
- // for a computed, read-only sibling total: kumiko-framework#1873.
348
+ // "sum-of-rounded" not "round-of-sum".
347
349
  // kumiko-lint-ignore complexity-budget currency-equality check on sibling money payloads
348
350
  function applyTotalsMatchRefinements(entity, schema) {
349
351
  let result = schema;
@@ -360,7 +362,7 @@ function applyTotalsMatchRefinements(entity, schema) {
360
362
  continue;
361
363
  // One side of a totalsMatch pair without the other → reject. Update
362
364
  // payloads only carry `changes`, so omitting the sibling would otherwise
363
- // silently leave sum ≠ total (fw#1841).
365
+ // silently leave sum ≠ total.
364
366
  if (rawRows === undefined || siblingRaw === undefined) {
365
367
  ctx.addIssue({
366
368
  code: "custom",
@@ -478,7 +480,7 @@ export function buildUpdateSchema(entity, currencies = [...DEFAULT_CURRENCIES],
478
480
  // sends only `{ title }` means "only change title"; zod defaults would
479
481
  // silently inject default values for every omitted field and clobber
480
482
  // existing data via the event-store-executor's `changes` payload.
481
- // The field is passed through un-stripped (unlike before fw#1703) so
483
+ // The field is passed through un-stripped so
482
484
  // fieldToZod still knows the default for its "" → default mapping
483
485
  // (e.g. select) — applyDefaults: false only suppresses the schema-level
484
486
  // `.default(...)` fallback for a genuinely omitted key.
@@ -1,18 +1,16 @@
1
- // Screen-Filter (Tier 2.7c) — Op-vs-Field-Type-Compatibility.
1
+ // Screen-Filter — operator-to-field-type compatibility.
2
2
  //
3
- // Wer darf was filtern?
4
- // - text/select/multiSelect: nur equality-Ops (eq, ne, in). lt/gt
5
- // auf Strings ist semantisch fast immer Tippfehler (lexicographic
6
- // compare nutzt der Author selten bewusst); Author kann den
7
- // Filter dann einfach nicht setzen.
8
- // - boolean: eq, ne (in/lt/gt sinnlos für 2-Werte-Type).
9
- // - number/money/decimal/date/timestamp/locatedTimestamp: alle 7
10
- // Ops — die Felder sind natürlich vergleichbar.
3
+ // Allowed operators depend on the field type:
4
+ // - text/select/multiSelect: equality operators only (eq, ne, in). lt/gt
5
+ // on strings are usually accidental; authors can simply omit the filter.
6
+ // - boolean: eq, ne (in/lt/gt are meaningless for a two-value type).
7
+ // - number/money/decimal/date/timestamp/locatedTimestamp: all 7 operators,
8
+ // because these fields are naturally comparable.
11
9
  //
12
- // Boot-Validator nutzt diese Map um Author-Fehler früh zu fangen
13
- // ("filter mit op:lt auf einem text-Feld" → Boot-Fail). Erweitert sich
14
- // transparent: neuer Field-Type → hier eintragen + sortable/filterable-
15
- // Flag im Type-Def, sonst lehnt der Validator das Field generell ab.
10
+ // The boot validator uses this map to catch authoring errors early
11
+ // ("filter with op:lt on a text field" → boot failure). When adding a field
12
+ // type, add it here and set its sortable/filterable flag in the type definition;
13
+ // otherwise the validator rejects the field entirely.
16
14
  const EQUALITY_ONLY = ["eq", "ne", "in"];
17
15
  const COMPARABLE = [
18
16
  "eq",
@@ -39,17 +37,16 @@ export function getAllowedFilterOps(field) {
39
37
  case "timestamp":
40
38
  case "locatedTimestamp":
41
39
  return COMPARABLE;
42
- // tz/embedded/file/image/files/images: nicht filterbar — Author
43
- // kann das Feld nicht als filterable: true markieren (Boot-
44
- // Validator weist `filterable: true` auf den Types ohnehin schon
45
- // ab, weil das Flag dort gar nicht im Type-Def steht).
40
+ // tz/embedded/file/image/files/images: not filterable — authors cannot
41
+ // mark the field as filterable: true because the boot validator already
42
+ // rejects `filterable: true` for these types; the flag is not present in
43
+ // their type definitions.
46
44
  default:
47
45
  return [];
48
46
  }
49
47
  }
50
- // Author hat das Feld als filterable markiert? Tz/Embedded/File-Types
51
- // haben das Flag gar nicht im Type-Def, also fangen wir das per
52
- // `"filterable" in field`-Narrow ab.
48
+ // If an author marked the field as filterable, catch types without that
49
+ // flag in their type definition with the `"filterable" in field` narrowing.
53
50
  export function isFieldFilterable(field) {
54
51
  return "filterable" in field && field.filterable === true;
55
52
  }
@@ -1,3 +1,5 @@
1
+ import { createFallbackLogger } from "../logging/utils.js";
2
+ const log = createFallbackLogger("kumiko");
1
3
  // Entity name the renderer gives the synthesized entity of an actionForm, secretMint or confirm
2
4
  // step. It is not registered, so nothing server-side may treat it as a real entity.
3
5
  export const ACTION_FORM_PSEUDO_ENTITY = "__action-form__";
@@ -71,7 +73,7 @@ export function isWriteFormEditSection(section) {
71
73
  }
72
74
  // Union of both sources, unlike the boot-validator's either-or flattening: that
73
75
  // one runs after the fields-XOR-groups check, collectors run without it and must
74
- // not drop a source (fw#2986).
76
+ // not drop a source.
75
77
  export function sectionFieldSpecs(section) {
76
78
  return [...(section.fields ?? []), ...(section.groups?.flatMap((group) => group.fields) ?? [])];
77
79
  }
@@ -90,8 +92,7 @@ export function normalizeListColumn(c) {
90
92
  process.env.NODE_ENV !== "production" &&
91
93
  col.renderer !== undefined &&
92
94
  typeof col.renderer === "function") {
93
- // biome-ignore lint/suspicious/noConsole: dev-only warning
94
- console.warn(`[kumiko] normalizeListColumn: Feld "${col.field}" hat einen Funktions-Renderer — dieser wird von JSON.stringify verworfen. Bitte auf FormatSpec ({ format: "..." }) migrieren.`);
95
+ log.warn(`normalizeListColumn: Feld "${col.field}" hat einen Funktions-Renderer — dieser wird von JSON.stringify verworfen. Bitte auf FormatSpec ({ format: "..." }) migrieren.`);
95
96
  }
96
97
  return col;
97
98
  }
@@ -1,14 +1,14 @@
1
1
  /**
2
2
  * Type-safe transition graph. Wraps the underlying Map so callers don't
3
3
  * accidentally fall into the `transitions[from]?.includes(to)` footgun
4
- * (Object-Index-Access auf einer Map returnt undefined und der ganze
5
- * Check kollabiert silently). Stattdessen forciert die API explizite
6
- * Methoden — kein Map-shape mehr extern sichtbar.
4
+ * (indexing a Map like an object returns undefined and silently breaks the
5
+ * entire check). The API therefore enforces explicit methods, with no Map
6
+ * shape exposed externally.
7
7
  *
8
8
  * - `canTransition(from, to)` → boolean
9
- * - `allowedFrom(from)` → readonly string[] (leer wenn `from` unbekannt
10
- * oder terminaler Zustand)
11
- * - `assertTransition(from, to)` → wirft UnprocessableError(invalid_transition)
9
+ * - `allowedFrom(from)` → readonly string[] (empty when `from` is unknown
10
+ * or terminal)
11
+ * - `assertTransition(from, to)` → throws UnprocessableError(invalid_transition)
12
12
  */
13
13
  export type TransitionGraph<TStates extends string = string> = {
14
14
  readonly canTransition: (from: TStates, to: TStates) => boolean;
@@ -17,8 +17,8 @@ export type TransitionGraph<TStates extends string = string> = {
17
17
  };
18
18
  /**
19
19
  * Defines allowed state transitions. Returns a typed graph (see
20
- * TransitionGraph) — nicht eine Map. Damit ist `transitions[x]`-Zugriff
21
- * type-error statt silent undefined.
20
+ * TransitionGraph), not a Map. This makes `transitions[x]` access a
21
+ * type error instead of silently returning undefined.
22
22
  */
23
23
  export declare function defineTransitions<const TMap extends Record<string, readonly string[]>>(map: TMap): TransitionGraph<keyof TMap & string>;
24
24
  /**
@@ -26,9 +26,9 @@ export declare function defineTransitions<const TMap extends Record<string, read
26
26
  * reason="invalid_transition" if not — the 422 status lets the client
27
27
  * distinguish a logical rejection from a validation or auth failure.
28
28
  *
29
- * Convenience-Wrapper um `transitions.assertTransition(from, to)` —
30
- * existiert weil bestehende Aufrufer `guardTransition(graph, ...)`-Form
31
- * nutzen. Beides ist erlaubt; die Method-Form auf dem Graph ist die
32
- * idiomatische API für neuen Code.
29
+ * Convenience wrapper around `transitions.assertTransition(from, to)` —
30
+ * it exists because existing callers use the `guardTransition(graph, ...)`
31
+ * form. Both are supported; the graph method is the idiomatic API for new
32
+ * code.
33
33
  */
34
34
  export declare function guardTransition<TStates extends string>(transitions: TransitionGraph<TStates>, from: TStates, to: TStates): void;
@@ -1,8 +1,8 @@
1
1
  import { buildInvalidTransitionDetails, FrameworkReasons, UnprocessableError, } from "../errors/index.js";
2
2
  /**
3
3
  * Defines allowed state transitions. Returns a typed graph (see
4
- * TransitionGraph) — nicht eine Map. Damit ist `transitions[x]`-Zugriff
5
- * type-error statt silent undefined.
4
+ * TransitionGraph), not a Map. This makes `transitions[x]` access a
5
+ * type error instead of silently returning undefined.
6
6
  */
7
7
  export function defineTransitions(map) {
8
8
  const internal = new Map();
@@ -17,7 +17,7 @@ export function defineTransitions(map) {
17
17
  },
18
18
  assertTransition: (from, to) => {
19
19
  const set = internal.get(from);
20
- // skip: erlaubter Übergang — kein Throw, fall through.
20
+ // skip: allowed transition — no throw; fall through.
21
21
  if (set?.has(to) === true)
22
22
  return;
23
23
  const allowed = set ? [...set] : [];
@@ -33,10 +33,10 @@ export function defineTransitions(map) {
33
33
  * reason="invalid_transition" if not — the 422 status lets the client
34
34
  * distinguish a logical rejection from a validation or auth failure.
35
35
  *
36
- * Convenience-Wrapper um `transitions.assertTransition(from, to)` —
37
- * existiert weil bestehende Aufrufer `guardTransition(graph, ...)`-Form
38
- * nutzen. Beides ist erlaubt; die Method-Form auf dem Graph ist die
39
- * idiomatische API für neuen Code.
36
+ * Convenience wrapper around `transitions.assertTransition(from, to)` —
37
+ * it exists because existing callers use the `guardTransition(graph, ...)`
38
+ * form. Both are supported; the graph method is the idiomatic API for new
39
+ * code.
40
40
  */
41
41
  // @wrapper-known semantic-alias
42
42
  export function guardTransition(transitions, from, to) {
@@ -13,14 +13,15 @@
13
13
  // was needed to satisfy TS narrowing on a property-access; passing the
14
14
  // arg through a function-call achieves the same narrowing trivially via
15
15
  // the function-parameter binding.
16
- //
17
- // Followup #10 (closed at the M.1.6 cleanup-pass).
16
+ function isResolverFunction(arg) {
17
+ return typeof arg === "function";
18
+ }
18
19
  /**
19
20
  * Resolve a required StepResolver — either a static value or a function.
20
21
  * Throws if `arg` is undefined; caller must guarantee presence.
21
22
  */
22
23
  export function resolveRequired(arg, ctx) {
23
- if (typeof arg === "function") {
24
+ if (isResolverFunction(arg)) {
24
25
  return arg(ctx);
25
26
  }
26
27
  return arg;
@@ -24,12 +24,12 @@
24
24
  // ```
25
25
  //
26
26
  // M.1.6 supports `concurrency: 1` only (sequential). Concurrent
27
- // execution (Promise.all-style) is Followup #12 — TX-sharing,
27
+ // execution (Promise.all-style) requires its own audit cycle: TX-sharing,
28
28
  // AbortSignal-cancellation, and lifecycle-hook ordering each warrant
29
- // their own audit cycle (Q16).
29
+ // separate review.
30
30
  //
31
- // Q15: `as` is required — without it, the current item is unreachable
32
- // from the sub-pipeline's resolvers. Q12: r.step.return inside `do` is
31
+ // `as` is required — without it, the current item is unreachable
32
+ // from the sub-pipeline's resolvers. `r.step.return` inside `do` is
33
33
  // rejected at build time (would trigger the discriminated-union
34
34
  // TData-Inference trap).
35
35
  import { defineStep } from "../define-step.js";
@@ -6,7 +6,7 @@
6
6
  // r.requires.projection allowlist, must NOT be a registered
7
7
  // r.entity-aggregate-table. Skips the same set of framework-protections
8
8
  // (lifecycle hooks, field-access, audit-trail, etc.) — see
9
- // step-vocabulary.md "Was unsafeProjection.* überspringt".
9
+ // step-vocabulary.md "What unsafeProjection.* skips".
10
10
  //
11
11
  // Convention (not enforced): most legitimate read-side deletions are
12
12
  // downstream of an aggregate event (e.g. delete-user → cascading
@@ -2,13 +2,13 @@
2
2
  //
3
3
  // Idempotent on the supplied conflict-key columns (typically the
4
4
  // natural key + tenantId). Skips lifecycle hooks, field-access,
5
- // crypto-shredding, schema-versioning, audit-trail, read-access-log.
6
- // See "Was unsafeProjection.* überspringt" in
5
+ // crypto-shredding, schema-versioning, audit-trail, and read-access-log.
6
+ // See "What unsafeProjection.* skips" in
7
7
  // kumiko-platform/docs/archive/plans/architecture/intern/step-vocabulary.md.
8
8
  //
9
9
  // Use only on tables explicitly declared via r.requires.projection in
10
- // the owning feature. Aggregate-tables (registered via r.entity) are
11
- // rejected by boot-validation — domain mutation MUST go through
10
+ // the owning feature. Aggregate tables (registered via r.entity) are
11
+ // rejected by boot validation — domain mutation MUST go through
12
12
  // r.step.aggregate.*.
13
13
  import { extractTableName } from "../../db/index.js";
14
14
  import { executeRawQuery } from "../../db/queries/raw-sql.js";
@@ -16,10 +16,10 @@
16
16
  // resultKey is `args.event` itself (the branded event-type string) rather
17
17
  // than a new `name` argument — a workflow only ever has one `awaits` entry
18
18
  // per event type, so `ctx.steps[awaits.someKey]` is already a unique,
19
- // intention-revealing handle. resume-run.write.ts (framework#2513 Phase 3b)
20
- // is the only producer of this key's value: the step itself always
21
- // suspends (never returns a real result), so a subsequent step only ever
22
- // sees the matched event's payload after a resume seeds it in.
19
+ // intention-revealing handle. resume-run.write.ts is the only producer of
20
+ // this key's value: the step itself always suspends (never returns a real
21
+ // result), so a subsequent step only ever sees the matched event's payload
22
+ // after a resume seeds it in.
23
23
  import { defineStep } from "../define-step.js";
24
24
  import { addDuration } from "./_duration-utils.js";
25
25
  import { resolveOptional, resolveRequired } from "./_resolver-utils.js";
@@ -15,7 +15,7 @@ const triggerEventRefSchema = z.object({
15
15
  aggregateId: z.string(),
16
16
  version: z.number().int(),
17
17
  });
18
- // Optional on every step event: runs started before the Q7 fingerprint existed
18
+ // Optional on every step event: runs started before the fingerprint existed
19
19
  // have none.
20
20
  const stepContext = {
21
21
  workflowName: z.string(),
@@ -29,8 +29,8 @@ const WORKFLOW_SYSTEM_EVENT_SCHEMAS = [
29
29
  z.object({
30
30
  workflowName: z.string(),
31
31
  triggerEventType: z.string(),
32
- // Current streams reference the trigger event; streams written before
33
- // #3474 carry a copy of its payload instead.
32
+ // Current streams reference the trigger event; older streams carry a copy of
33
+ // its payload instead.
34
34
  triggerEventRef: triggerEventRefSchema.optional(),
35
35
  triggerPayload: z.unknown().optional(),
36
36
  definitionFingerprint: z.string().optional(),
@@ -4,30 +4,32 @@ import type { RegistrarExtensionRegistration } from "./types/config.ts";
4
4
  import type { FeatureDefinition, Registry } from "./types/feature.ts";
5
5
  import type { TenantId } from "./types/identifiers.ts";
6
6
  /**
7
- * Extension-name unter dem ein tier-resolver-plugin im registry registriert
8
- * werden muss. Konstante damit `r.useExtension(TENANT_TIER_RESOLVER_EXT, ...)`
9
- * + framework's `getExtensionUsages(TENANT_TIER_RESOLVER_EXT)` typo-resistent
10
- * gegen den selben Wert prüfen.
7
+ * Extension name under which a tier-resolver plugin must be registered in the
8
+ * registry. The constant keeps `r.useExtension(TENANT_TIER_RESOLVER_EXT, ...)`
9
+ * and the framework's `getExtensionUsages(TENANT_TIER_RESOLVER_EXT)` aligned
10
+ * on the same value, preventing typos.
11
11
  */
12
12
  export declare const TENANT_TIER_RESOLVER_EXT = "tenantTierResolver";
13
13
  /**
14
- * Resolver-callback shape: synchron (dispatcher hot-path), per-tenant.
15
- * Returnt das effective feature-Set für den tenant. Implementations sollten
16
- * einen in-memory cache pflegen + per `r.entityHook` invalidieren.
14
+ * Resolver callback shape: synchronous (on the dispatcher's hot path), per
15
+ * tenant. Returns the tenant's effective feature set. Implementations should
16
+ * maintain an in-memory cache and invalidate it via `r.entityHook`.
17
17
  *
18
- * **System-context convention:** call mit SYSTEM_TENANT_ID erwartet die union
19
- * aller tier-features (siehe DispatcherOptions.effectiveFeatures doc-block).
18
+ * **System-context convention:** calls with SYSTEM_TENANT_ID must return the
19
+ * union of all tier features (see the DispatcherOptions.effectiveFeatures
20
+ * doc block).
20
21
  */
21
22
  export type EffectiveFeaturesResolver = ((tenantId: TenantId) => ReadonlySet<string>) & {
22
23
  /**
23
- * Optionaler Live-Trial-Gate, vom dispatcher-feature-gate NUR konsultiert
24
- * wenn der synchrone Resolver ein Feature als disabled meldet. Liest das
25
- * Signup-Datum des Tenants live (tenant.inserted_at) und returnt true wenn
26
- * der Tenant im Trial-Fenster ist UND das Feature zum Trial-Tier gehört.
27
- * Async + nicht im Boot-Cache, weil der Trial zeit-abgeleitet ist (ändert
28
- * sich zwischen Requests). Konsultiert an 3 Stellen: den 2 Dispatch-Gate-
29
- * Aufrufstellen (checkFeatureEnabled) UND ctx.hasFeature im Handler-Body
30
- * (fällt darauf zurück, wenn der synchrone Feature-Set-Check negativ ist).
24
+ * Optional live-trial gate, consulted by the dispatcher feature gate ONLY
25
+ * when the synchronous resolver reports a feature as disabled. Reads the
26
+ * tenant's signup date live (tenant.inserted_at) and returns true when the
27
+ * tenant is within the trial window AND the feature belongs to the trial
28
+ * tier. It is async and not part of the boot cache because the trial is
29
+ * time-dependent and can change between requests. Consulted at three
30
+ * locations: the two dispatch-gate call sites (checkFeatureEnabled) AND
31
+ * ctx.hasFeature in the handler body (as a fallback when the synchronous
32
+ * feature-set check is negative).
31
33
  */
32
34
  readonly trialGate?: TrialGate;
33
35
  };
@@ -36,10 +38,10 @@ export type EffectiveFeaturesResolver = ((tenantId: TenantId) => ReadonlySet<str
36
38
  */
37
39
  export type TrialGate = (tenantId: TenantId, featureName: string) => Promise<boolean>;
38
40
  /**
39
- * Plugin-shape für tier-resolver-extension. Plugins implementieren `build`
40
- * als boot-time factory: kriegen `db` + `registry` (post-stack-setup),
41
- * laden initial cache aus DB, returnen den synchronen resolver-callback
42
- * (optional mit angehängtem `trialGate`).
41
+ * Plugin shape for the tier-resolver extension. Plugins implement `build`
42
+ * as a boot-time factory: they receive `db` + `registry` (after stack setup),
43
+ * load the initial cache from the database, and return the synchronous
44
+ * resolver callback, optionally with an attached `trialGate`.
43
45
  */
44
46
  export type TierResolverPlugin = {
45
47
  readonly build: (deps: {
@@ -49,13 +51,13 @@ export type TierResolverPlugin = {
49
51
  }) => Promise<EffectiveFeaturesResolver>;
50
52
  };
51
53
  /**
52
- * Scan a composed feature-list for a `tenantTierResolver`-extension usage.
53
- * Single plugin assumption — multiple wären ambiguous (welcher resolver
54
- * gewinnt). Memory `feedback_no_options_without_need`: kein multi-merge-
55
- * pattern bis es echten Use-Case gibt.
54
+ * Scans a composed feature list for a `tenantTierResolver` extension usage.
55
+ * Only one plugin is supported because multiple plugins would be ambiguous:
56
+ * it would be unclear which resolver should win. No multi-merge pattern is
57
+ * needed until there is a real use case.
56
58
  *
57
- * Geteilter helper für runDevApp + runProdApp damit der Pickup-Pfad
58
- * bit-identisch ist (drift-resistent).
59
+ * Shared by runDevApp + runProdApp so the pickup path remains byte-for-byte
60
+ * identical and cannot drift.
59
61
  */
60
62
  /** Narrows a registration's untyped `options` bag to the tier-resolver
61
63
  * plugin shape — the only thing every implementation must provide is the
@@ -1,40 +1,40 @@
1
- // Sprint-8a Tier-Composition: framework-extension-point für per-tenant
1
+ // Tier composition: framework extension point for per-tenant
2
2
  // effective-feature resolution.
3
3
  //
4
- // **Pattern (analog mail-foundation / file-foundation):** ein Feature
5
- // declares `r.extendsRegistrar("tenantTierResolver")` und plugins
6
- // implementieren via `r.useExtension("tenantTierResolver", "id", { build })`.
4
+ // **Pattern (analogous to mail-foundation / file-foundation):** a feature
5
+ // declares `r.extendsRegistrar("tenantTierResolver")`, and plugins
6
+ // implement it via `r.useExtension("tenantTierResolver", "id", { build })`.
7
7
  //
8
- // **Auto-wiring im Framework:** runDevApp + runProdApp scannen das registry
9
- // nach genau diesem extension-name. Wenn ein plugin gefunden ist UND der
10
- // App-Author nicht selbst eine `effectiveFeatures`-callback gesetzt hat,
11
- // wird `plugin.build({ db, registry })` einmalig in onAfterSetup aufgerufen.
12
- // Der returned callback landet als `effectiveFeatures` im dispatcher.
8
+ // **Framework auto-wiring:** runDevApp + runProdApp scan the registry
9
+ // for this exact extension name. If a plugin is found AND the app author
10
+ // has not set an `effectiveFeatures` callback, `plugin.build({ db, registry })`
11
+ // is called once in onAfterSetup. The returned callback becomes
12
+ // `effectiveFeatures` in the dispatcher.
13
13
  //
14
- // **Warum dieser Pattern:** App-Author mountet `createTierEngineFeature(opts)`
15
- // und das war's — kein Late-Bound-Holder im run-config, kein
16
- // effectiveFeatures-callback wiren. Das Framework macht den Late-Bound-Trick
17
- // intern. Memory `feedback_alles_ist_ein_feature`: Tier-Composition ist
18
- // ein Feature, nicht ein Subsystem mit App-spezifischem Wiring.
14
+ // **Why this pattern:** the app author mounts
15
+ // `createTierEngineFeature(opts)` and is done—no late-bound holder in the
16
+ // run config and no need to wire an effectiveFeatures callback. The framework
17
+ // handles the late-binding internally. Tier composition is a feature, not a
18
+ // subsystem with app-specific wiring.
19
19
  //
20
- // **Apps ohne tier-engine:** wenn keine plugin registriert ist, framework
21
- // macht nichts — `effectiveFeatures` bleibt undefined, alle features sind on.
20
+ // **Apps without a tier engine:** if no plugin is registered, the framework
21
+ // does nothing—`effectiveFeatures` remains undefined, and all features are on.
22
22
  import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
23
23
  /**
24
- * Extension-name unter dem ein tier-resolver-plugin im registry registriert
25
- * werden muss. Konstante damit `r.useExtension(TENANT_TIER_RESOLVER_EXT, ...)`
26
- * + framework's `getExtensionUsages(TENANT_TIER_RESOLVER_EXT)` typo-resistent
27
- * gegen den selben Wert prüfen.
24
+ * Extension name under which a tier-resolver plugin must be registered in the
25
+ * registry. The constant keeps `r.useExtension(TENANT_TIER_RESOLVER_EXT, ...)`
26
+ * and the framework's `getExtensionUsages(TENANT_TIER_RESOLVER_EXT)` aligned
27
+ * on the same value, preventing typos.
28
28
  */
29
29
  export const TENANT_TIER_RESOLVER_EXT = "tenantTierResolver";
30
30
  /**
31
- * Scan a composed feature-list for a `tenantTierResolver`-extension usage.
32
- * Single plugin assumption — multiple wären ambiguous (welcher resolver
33
- * gewinnt). Memory `feedback_no_options_without_need`: kein multi-merge-
34
- * pattern bis es echten Use-Case gibt.
31
+ * Scans a composed feature list for a `tenantTierResolver` extension usage.
32
+ * Only one plugin is supported because multiple plugins would be ambiguous:
33
+ * it would be unclear which resolver should win. No multi-merge pattern is
34
+ * needed until there is a real use case.
35
35
  *
36
- * Geteilter helper für runDevApp + runProdApp damit der Pickup-Pfad
37
- * bit-identisch ist (drift-resistent).
36
+ * Shared by runDevApp + runProdApp so the pickup path remains byte-for-byte
37
+ * identical and cannot drift.
38
38
  */
39
39
  /** Narrows a registration's untyped `options` bag to the tier-resolver
40
40
  * plugin shape — the only thing every implementation must provide is the