@cosmicdrift/kumiko-framework 0.386.0 → 0.387.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. package/LICENSE +21 -57
  2. package/README.md +2 -2
  3. package/agents/widgets.md +39 -39
  4. package/dist/api/api-constants.js +2 -2
  5. package/dist/api/auth-middleware.js +7 -7
  6. package/dist/api/auth-rate-limiters.js +7 -3
  7. package/dist/api/auth-routes-mfa.js +1 -1
  8. package/dist/api/auth-routes-session.js +1 -1
  9. package/dist/api/auth-routes-shared.js +7 -3
  10. package/dist/api/auth-routes-signup.js +1 -1
  11. package/dist/api/client-ip.js +3 -1
  12. package/dist/api/extra-route.js +1 -1
  13. package/dist/api/pii-leak-guard.js +4 -2
  14. package/dist/api/readiness.js +5 -5
  15. package/dist/api/redis-sse-broker.js +8 -6
  16. package/dist/api/routes.js +2 -2
  17. package/dist/api/server.js +34 -33
  18. package/dist/api/sse-broker.js +1 -1
  19. package/dist/api/sse-route.d.ts +13 -13
  20. package/dist/api/sse-route.js +15 -15
  21. package/dist/auth/index.js +4 -4
  22. package/dist/auth/roles.d.ts +4 -4
  23. package/dist/auth/roles.js +24 -24
  24. package/dist/compliance/duration-spec.js +18 -20
  25. package/dist/compliance/index.js +2 -3
  26. package/dist/compliance/override-schema.d.ts +4 -4
  27. package/dist/compliance/override-schema.js +19 -22
  28. package/dist/compliance/profiles.d.ts +26 -29
  29. package/dist/compliance/profiles.js +62 -69
  30. package/dist/compliance/sub-processors.d.ts +28 -30
  31. package/dist/compliance/sub-processors.js +17 -21
  32. package/dist/consumer-cli.js +4 -4
  33. package/dist/crypto/blind-index.js +18 -17
  34. package/dist/crypto/ciphertext-pattern.js +1 -1
  35. package/dist/crypto/event-pii.js +3 -3
  36. package/dist/crypto/kek-source.d.ts +1 -1
  37. package/dist/crypto/kek-source.js +3 -2
  38. package/dist/crypto/kms-adapter.d.ts +1 -1
  39. package/dist/crypto/kms-adapter.js +4 -6
  40. package/dist/crypto/kms-wiring.d.ts +1 -2
  41. package/dist/crypto/kms-wiring.js +2 -2
  42. package/dist/crypto/pg-kms-adapter.js +2 -6
  43. package/dist/crypto/pii-field-encryption.js +4 -4
  44. package/dist/crypto/subject-resolver.d.ts +1 -1
  45. package/dist/crypto/subject-resolver.js +2 -2
  46. package/dist/db/apply-entity-event.d.ts +6 -6
  47. package/dist/db/apply-entity-event.js +55 -55
  48. package/dist/db/blind-index-cleanup.js +7 -7
  49. package/dist/db/collect-table-metas.js +10 -11
  50. package/dist/db/compound-types.d.ts +1 -1
  51. package/dist/db/compound-types.js +12 -12
  52. package/dist/db/cursor.js +3 -4
  53. package/dist/db/dialect.d.ts +2 -2
  54. package/dist/db/dialect.js +2 -2
  55. package/dist/db/eagerload.d.ts +3 -3
  56. package/dist/db/eagerload.js +21 -12
  57. package/dist/db/entity-table-meta.d.ts +2 -2
  58. package/dist/db/entity-table-meta.js +4 -4
  59. package/dist/db/event-store-executor-context.d.ts +1 -1
  60. package/dist/db/event-store-executor-context.js +13 -13
  61. package/dist/db/event-store-executor-read.js +27 -28
  62. package/dist/db/event-store-executor-write.js +65 -70
  63. package/dist/db/located-timestamp.d.ts +11 -11
  64. package/dist/db/located-timestamp.js +29 -30
  65. package/dist/db/migrate-generator.d.ts +1 -1
  66. package/dist/db/migrate-generator.js +26 -19
  67. package/dist/db/migrate-runner.js +25 -25
  68. package/dist/db/money.d.ts +6 -6
  69. package/dist/db/money.js +29 -27
  70. package/dist/db/queries/backfill-pii.js +5 -5
  71. package/dist/db/queries/ddl.js +1 -1
  72. package/dist/db/queries/es-ops.d.ts +1 -1
  73. package/dist/db/queries/es-ops.js +1 -1
  74. package/dist/db/queries/event-consumer.js +3 -3
  75. package/dist/db/queries/event-store.js +4 -2
  76. package/dist/db/queries/projection-rebuild.js +3 -3
  77. package/dist/db/queries/raw-sql.d.ts +2 -2
  78. package/dist/db/queries/raw-sql.js +2 -2
  79. package/dist/db/queries/shadow-swap.js +19 -19
  80. package/dist/db/query.d.ts +1 -1
  81. package/dist/db/query.js +6 -6
  82. package/dist/db/rebuild-marker.js +20 -21
  83. package/dist/db/replay-migration-sql.js +2 -2
  84. package/dist/db/schema-inspection.js +4 -5
  85. package/dist/db/table-builder.d.ts +1 -2
  86. package/dist/db/table-builder.js +82 -82
  87. package/dist/db/tenant-db.d.ts +2 -2
  88. package/dist/db/tenant-db.js +9 -2
  89. package/dist/derivatives/delete-stored-file.js +5 -2
  90. package/dist/derivatives/derivatives-context.js +1 -1
  91. package/dist/engine/boot-validator/access-roles.js +4 -3
  92. package/dist/engine/boot-validator/api-ext.js +14 -14
  93. package/dist/engine/boot-validator/config-deps.js +7 -6
  94. package/dist/engine/boot-validator/detail-screens.js +3 -3
  95. package/dist/engine/boot-validator/entity-handler.js +60 -61
  96. package/dist/engine/boot-validator/gdpr-storage.js +4 -4
  97. package/dist/engine/boot-validator/i18n-keys.js +1 -1
  98. package/dist/engine/boot-validator/index.d.ts +2 -2
  99. package/dist/engine/boot-validator/index.js +20 -20
  100. package/dist/engine/boot-validator/nav.js +5 -5
  101. package/dist/engine/boot-validator/ownership.js +12 -9
  102. package/dist/engine/boot-validator/parent-ref.js +1 -1
  103. package/dist/engine/boot-validator/pii-retention.js +50 -57
  104. package/dist/engine/boot-validator/projection-list-screens.js +7 -7
  105. package/dist/engine/boot-validator/query-refs.js +1 -1
  106. package/dist/engine/boot-validator/screens-actions.js +15 -16
  107. package/dist/engine/boot-validator/screens-forms.js +10 -11
  108. package/dist/engine/boot-validator/screens-lists.js +14 -15
  109. package/dist/engine/boot-validator/screens.js +34 -40
  110. package/dist/engine/boot-validator/security-baseline.js +3 -2
  111. package/dist/engine/boot-validator/transfer-graph.js +2 -2
  112. package/dist/engine/boot-validator/workspaces.js +3 -3
  113. package/dist/engine/bridge-stub.js +3 -4
  114. package/dist/engine/build-app-schema.js +75 -75
  115. package/dist/engine/build-config-feature-schema.js +50 -50
  116. package/dist/engine/build-target.js +20 -23
  117. package/dist/engine/codemod/pipeline-codemod.js +20 -13
  118. package/dist/engine/config-helpers.js +4 -4
  119. package/dist/engine/constants.js +2 -2
  120. package/dist/engine/create-app.js +2 -2
  121. package/dist/engine/define-feature.js +12 -11
  122. package/dist/engine/define-handler.js +12 -11
  123. package/dist/engine/define-step.js +1 -1
  124. package/dist/engine/define-workflow.d.ts +5 -5
  125. package/dist/engine/define-workflow.js +6 -6
  126. package/dist/engine/entity-handlers.js +13 -14
  127. package/dist/engine/event-def.js +2 -2
  128. package/dist/engine/extension-names.d.ts +16 -17
  129. package/dist/engine/extension-names.js +37 -38
  130. package/dist/engine/extensions/escape-hatch-usage.js +1 -1
  131. package/dist/engine/extensions/tenant-data.js +2 -2
  132. package/dist/engine/extensions/user-data.d.ts +23 -24
  133. package/dist/engine/factories.d.ts +14 -13
  134. package/dist/engine/factories.js +38 -37
  135. package/dist/engine/feature-ast/entity-field-types.js +1 -1
  136. package/dist/engine/feature-ast/extractors/handlers.js +1 -1
  137. package/dist/engine/feature-ast/extractors/hooks.js +1 -1
  138. package/dist/engine/feature-ast/extractors/shared.d.ts +2 -3
  139. package/dist/engine/feature-ast/extractors/shared.js +2 -3
  140. package/dist/engine/feature-ast/parse.js +13 -13
  141. package/dist/engine/feature-ast/patch.js +1 -1
  142. package/dist/engine/feature-ast/pattern-change-schema.js +1 -1
  143. package/dist/engine/feature-ast/patterns.d.ts +2 -2
  144. package/dist/engine/feature-ast/render.js +2 -2
  145. package/dist/engine/feature-config-events-jobs.js +15 -12
  146. package/dist/engine/feature-entity-handlers.js +9 -5
  147. package/dist/engine/feature-manifest.d.ts +9 -9
  148. package/dist/engine/feature-manifest.js +6 -7
  149. package/dist/engine/feature-ui-extensions.d.ts +3 -3
  150. package/dist/engine/feature-ui-extensions.js +30 -22
  151. package/dist/engine/field-access.js +5 -6
  152. package/dist/engine/index.d.ts +1 -0
  153. package/dist/engine/index.js +4 -4
  154. package/dist/engine/object-form.d.ts +1 -0
  155. package/dist/engine/object-form.js +14 -4
  156. package/dist/engine/ownership.js +3 -3
  157. package/dist/engine/parse-ref-target.js +5 -7
  158. package/dist/engine/pattern-library/index.js +2 -2
  159. package/dist/engine/pattern-library/library.js +2 -2
  160. package/dist/engine/pattern-library/types.js +3 -3
  161. package/dist/engine/pipeline.js +2 -2
  162. package/dist/engine/project-app-schema-for-roles.js +1 -1
  163. package/dist/engine/projection-helpers.js +9 -9
  164. package/dist/engine/qualified-name.js +2 -2
  165. package/dist/engine/registry-facade.js +1 -1
  166. package/dist/engine/registry-ingest.js +12 -12
  167. package/dist/engine/registry-state.js +17 -19
  168. package/dist/engine/registry-validate.js +12 -12
  169. package/dist/engine/run-in.js +2 -2
  170. package/dist/engine/run-pipeline.js +7 -0
  171. package/dist/engine/schema-builder.js +40 -38
  172. package/dist/engine/screen-filter-ops.js +17 -20
  173. package/dist/engine/screen-helpers.js +4 -3
  174. package/dist/engine/state-machine.d.ts +12 -12
  175. package/dist/engine/state-machine.js +7 -7
  176. package/dist/engine/steps/_resolver-utils.js +4 -3
  177. package/dist/engine/steps/for-each.js +4 -4
  178. package/dist/engine/steps/unsafe-projection-delete.js +1 -1
  179. package/dist/engine/steps/unsafe-projection-upsert.js +4 -4
  180. package/dist/engine/steps/wait-for-event.js +4 -4
  181. package/dist/engine/steps/workflow-system-events.js +3 -3
  182. package/dist/engine/tier-resolver-extension.d.ts +29 -27
  183. package/dist/engine/tier-resolver-extension.js +26 -26
  184. package/dist/engine/transfer-adjacency.js +2 -2
  185. package/dist/engine/validate-projection-allowlist.js +7 -27
  186. package/dist/engine/walk-step-instances.d.ts +2 -0
  187. package/dist/engine/walk-step-instances.js +38 -0
  188. package/dist/engine/where-rule-lint.js +1 -1
  189. package/dist/engine/write-handler-qn-extract.js +1 -1
  190. package/dist/entrypoint/index.js +3 -3
  191. package/dist/env/index.d.ts +7 -6
  192. package/dist/env/index.js +10 -9
  193. package/dist/errors/transition-details.js +7 -7
  194. package/dist/errors/write-error-info.d.ts +6 -6
  195. package/dist/errors/write-error-info.js +6 -6
  196. package/dist/es-ops/context.d.ts +9 -9
  197. package/dist/es-ops/context.js +28 -28
  198. package/dist/es-ops/index.js +7 -9
  199. package/dist/es-ops/operations-schema.js +4 -10
  200. package/dist/es-ops/runner.d.ts +11 -11
  201. package/dist/es-ops/runner.js +49 -53
  202. package/dist/es-ops/types.d.ts +55 -55
  203. package/dist/es-ops/types.js +11 -11
  204. package/dist/event-store/admin-api.js +6 -7
  205. package/dist/event-store/errors.js +1 -1
  206. package/dist/event-store/event-store.js +2 -2
  207. package/dist/event-store/rebuild-dead-letter.js +1 -1
  208. package/dist/files/feature.js +9 -10
  209. package/dist/files/file-handle.js +5 -6
  210. package/dist/files/file-ref-entity.js +15 -15
  211. package/dist/files/file-ref-table.js +7 -8
  212. package/dist/files/file-routes.d.ts +7 -0
  213. package/dist/files/file-routes.js +11 -5
  214. package/dist/files/in-memory-provider.js +8 -9
  215. package/dist/files/local-provider.js +18 -25
  216. package/dist/files/provider-resolver.d.ts +7 -7
  217. package/dist/files/storage-tracking.js +2 -2
  218. package/dist/files/zip-stream.d.ts +12 -19
  219. package/dist/files/zip-stream.js +44 -63
  220. package/dist/i18n/required-surface-keys.d.ts +1 -1
  221. package/dist/i18n/required-surface-keys.js +8 -8
  222. package/dist/jobs/job-runner-helpers.js +8 -2
  223. package/dist/jobs/job-runner-schedulers.d.ts +1 -1
  224. package/dist/jobs/job-runner-schedulers.js +3 -4
  225. package/dist/jobs/job-runner.js +13 -15
  226. package/dist/lifecycle/lifecycle.js +3 -5
  227. package/dist/lifecycle/signal-handlers.js +5 -4
  228. package/dist/logging/utils.d.ts +2 -1
  229. package/dist/logging/utils.js +38 -10
  230. package/dist/migrations/index.js +1 -1
  231. package/dist/migrations/kumiko-drift.js +14 -13
  232. package/dist/migrations/pending-rebuilds.d.ts +21 -20
  233. package/dist/migrations/pending-rebuilds.js +27 -27
  234. package/dist/migrations/projection-table-index.d.ts +3 -3
  235. package/dist/migrations/projection-table-index.js +8 -8
  236. package/dist/observability/metric-validator.js +1 -1
  237. package/dist/observability/metrics-handle.js +3 -3
  238. package/dist/pipeline/append-event-core.js +11 -2
  239. package/dist/pipeline/cascade-handler.d.ts +1 -1
  240. package/dist/pipeline/dispatch-batch.js +2 -2
  241. package/dist/pipeline/dispatch-query.js +10 -10
  242. package/dist/pipeline/dispatch-shared.d.ts +1 -0
  243. package/dist/pipeline/dispatch-shared.js +15 -17
  244. package/dist/pipeline/dispatch-stream.js +1 -1
  245. package/dist/pipeline/dispatch-write.js +3 -3
  246. package/dist/pipeline/dispatcher-utils.d.ts +1 -1
  247. package/dist/pipeline/dispatcher-utils.js +1 -1
  248. package/dist/pipeline/event-consumer-state.js +2 -2
  249. package/dist/pipeline/event-dispatcher-delivery.js +1 -1
  250. package/dist/pipeline/event-dispatcher.js +17 -28
  251. package/dist/pipeline/irreversible-operation-gate.js +1 -1
  252. package/dist/pipeline/lifecycle-pipeline.js +9 -9
  253. package/dist/pipeline/member-read-only-transaction.js +1 -3
  254. package/dist/pipeline/msp-rebuild.js +1 -1
  255. package/dist/pipeline/projection-rebuild.js +13 -12
  256. package/dist/pipeline/system-hooks.js +39 -41
  257. package/dist/pipeline/system-identity-switch.js +1 -3
  258. package/dist/pipeline/tenant-timezone-cache.js +1 -1
  259. package/dist/random/generate.d.ts +29 -29
  260. package/dist/random/generate.js +30 -30
  261. package/dist/random/words.js +20 -20
  262. package/dist/rate-limit/middleware.js +9 -5
  263. package/dist/redis/client.js +2 -2
  264. package/dist/redis/pubsub-signal.js +14 -17
  265. package/dist/schema-cli.js +116 -22
  266. package/dist/search/index.js +4 -4
  267. package/dist/search/purge-subject.js +4 -5
  268. package/dist/search/reindex-entity.js +6 -9
  269. package/dist/time/boot-tz-warning.d.ts +2 -2
  270. package/dist/time/boot-tz-warning.js +8 -8
  271. package/dist/time/iana.js +10 -10
  272. package/dist/time/index.js +9 -9
  273. package/dist/time/legacy-date.d.ts +2 -2
  274. package/dist/time/legacy-date.js +6 -8
  275. package/dist/time/polyfill.js +1 -1
  276. package/dist/time/tz-context.d.ts +1 -1
  277. package/dist/time/tz-context.js +1 -1
  278. package/dist/ui-types/app-schema.js +7 -7
  279. package/dist/ui-types/list-row-meta.js +4 -4
  280. package/dist/upgrade-cli.js +10 -9
  281. package/dist/utils/compare.js +1 -1
  282. package/package.json +4 -4
  283. package/src/changes.json +48 -0
  284. package/src/scripts/codemod/crypto-shredding-testing-move.ts +2 -2
  285. package/src/scripts/codemod/migrate-cross-tenant.ts +1 -1
  286. package/src/scripts/codemod/migrate-open-to-all.ts +1 -1
  287. package/src/scripts/codemod/pii-personal-migration.ts +17 -8
  288. package/src/scripts/codemod/unprocessable-error-details-reason.ts +2 -2
@@ -17,8 +17,8 @@
17
17
  // - Top-level code outside `defineFeature(...)`. Designer/AI treat
18
18
  // such files as "not a feature file".
19
19
  //
20
- // **Skeleton status (C1.3):** interface fixed, extractors are TODO.
21
- // Per-pattern extractors fill in iteratively (C1.5) — each round adds
20
+ // **Skeleton status:** interface fixed, extractors are TODO.
21
+ // Per-pattern extractors fill in iteratively — each iteration adds
22
22
  // one extractor + a focused test.
23
23
  import { Project, SyntaxKind } from "ts-morph";
24
24
  import { extractAiClassify, extractAiExtract, extractAiGenerate, extractAuthClaims, extractBootCheck, extractClaimKey, extractConfig, extractDefineEvent, extractDescribe, extractEntity, extractEnvSchema, extractExposesApi, extractExtendsRegistrar, extractHook, extractHttpRoute, extractJob, extractMetric, extractMultiStreamProjection, extractNav, extractNotification, extractOptionalRequires, extractProjection, extractQueryHandler, extractReadsConfig, extractReferenceData, extractRelation, extractRequires, extractScreen, extractSecret, extractSecretNamespace, extractStoreTable, extractStreamHandler, extractSystemScope, extractToggleable, extractTranslations, extractTreeActions, extractUiHints, extractUseExtension, extractUsesApi, extractWorkspace, extractWriteHandler, findFunctionLiteral, findImportBindingForLocalName, readObjectPropertyInitializer, resolveModuleFile, resolveSameFileObjectLiteral, } from "./extractors/index.js";
@@ -185,7 +185,7 @@ function walkSetupCallback(body, registrarParamName, sourceFile, patterns, error
185
185
  // a registrar-wrapper call — a function that receives the registrar as
186
186
  // a bare argument, e.g. `registerShowPonyScreens(r)`. Such wrappers are
187
187
  // invisible to a receiver-shape match, so we resolve the callee (same
188
- // file, or the declaring file of a named import — #1008) and recurse
188
+ // file, or the declaring file of a named import) and recurse
189
189
  // into its body with its own parameter name for the registrar slot.
190
190
  const wrapper = resolveRegistrarWrapperCall(call, registrarParamName);
191
191
  if (!wrapper)
@@ -368,7 +368,7 @@ function walkAiStepCalls(body, registrarParamName, sourceFile, patterns, errors,
368
368
  }
369
369
  }
370
370
  // =============================================================================
371
- // Internal — pattern extractors (skeleton, implementation in C1.5)
371
+ // Internal — pattern extractors
372
372
  // =============================================================================
373
373
  /**
374
374
  * Route an `<r>.<method>(...)` call to its concrete extractor. New
@@ -377,12 +377,12 @@ function walkAiStepCalls(body, registrarParamName, sourceFile, patterns, errors,
377
377
  * commit to the new kind via compile errors.
378
378
  *
379
379
  * Skeleton: every recognised method currently returns an UnknownPattern
380
- * with the right method name. C1.5 replaces the cases with concrete
381
- * extractors one at a time.
380
+ * with the right method name. Extractors replace the cases with concrete
381
+ * implementations one at a time.
382
382
  */
383
383
  function dispatchExtractor(methodName, call, sourceFile) {
384
384
  switch (methodName) {
385
- // Round 1 — simplest static patterns
385
+ // Simplest static patterns
386
386
  case "requires":
387
387
  return extractRequires(call, sourceFile);
388
388
  case "optionalRequires":
@@ -397,7 +397,7 @@ function dispatchExtractor(methodName, call, sourceFile) {
397
397
  return extractDescribe(call, sourceFile);
398
398
  case "uiHints":
399
399
  return extractUiHints(call, sourceFile);
400
- // Round 2 — object-literal-based static patterns
400
+ // Object-literal-based static patterns
401
401
  case "entity":
402
402
  return extractEntity(call, sourceFile);
403
403
  case "relation":
@@ -406,7 +406,7 @@ function dispatchExtractor(methodName, call, sourceFile) {
406
406
  return extractNav(call, sourceFile);
407
407
  case "workspace":
408
408
  return extractWorkspace(call, sourceFile);
409
- // Round 3 — complex static patterns
409
+ // Complex static patterns
410
410
  case "config":
411
411
  return extractConfig(call, sourceFile);
412
412
  case "translations":
@@ -423,7 +423,7 @@ function dispatchExtractor(methodName, call, sourceFile) {
423
423
  return extractReferenceData(call, sourceFile);
424
424
  case "useExtension":
425
425
  return extractUseExtension(call, sourceFile);
426
- // Round 4 — mixed (header + body) patterns
426
+ // Mixed (header + body) patterns
427
427
  case "hook":
428
428
  return extractHook(call, sourceFile);
429
429
  case "authClaims":
@@ -448,7 +448,7 @@ function dispatchExtractor(methodName, call, sourceFile) {
448
448
  return extractMultiStreamProjection(call, sourceFile);
449
449
  case "screen":
450
450
  return extractScreen(call, sourceFile);
451
- // Round 5 — opaque patterns
451
+ // Opaque patterns
452
452
  case "extendsRegistrar":
453
453
  return extractExtendsRegistrar(call, sourceFile);
454
454
  case "usesApi":
@@ -459,10 +459,10 @@ function dispatchExtractor(methodName, call, sourceFile) {
459
459
  return extractStoreTable(call, sourceFile);
460
460
  case "bootCheck":
461
461
  return extractBootCheck(call, sourceFile);
462
- // Round 6 — Tree-Actions pattern
462
+ // Tree-Actions pattern
463
463
  case "treeActions":
464
464
  return extractTreeActions(call, sourceFile);
465
- // Round 7 — env-schema contract (opaque, Zod-expression argument)
465
+ // Env-schema contract (opaque, Zod-expression argument)
466
466
  case "envSchema":
467
467
  return extractEnvSchema(call, sourceFile);
468
468
  // Unknown method — UnknownPattern signal so Designer/AI surface it
@@ -668,7 +668,7 @@ function callMatchesId(call, id) {
668
668
  // a string literal directly, or a bare identifier following its
669
669
  // declaration (same-file or imported) to a string-literal initializer —
670
670
  // the dominant naming style in the framework's own bundled-features
671
- // (`r.entity(ENTITY, ...)`, `r.useExtension(EXT_X, ...)`, see #1746).
671
+ // (`r.entity(ENTITY, ...)`, `r.useExtension(EXT_X, ...)`).
672
672
  //
673
673
  // Narrow on purpose: every kind's arg-0 (and relation's arg-1) is parsed
674
674
  // via readNameLiteral, never readNameOrRef (see round2.ts/round3.ts) —
@@ -2,7 +2,7 @@
2
2
  // tool (or any external caller) hands us JSON; before it ever reaches
3
3
  // `applyChanges` it must pass through here. Every rejection carries an
4
4
  // exact dotted/bracketed path so the caller (frequently an LLM) can
5
- // self-correct without a human in the loop — see kumiko-framework#3137.
5
+ // self-correct without a human in the loop.
6
6
  //
7
7
  // **Zod v4 constraint driving this file's shape:** every discriminated-
8
8
  // union member below stays a plain `z.object(...)` (or `.superRefine`
@@ -258,14 +258,14 @@ export type EnvSchemaPattern = {
258
258
  readonly source: SourceLocation;
259
259
  readonly schemaBody: SourceLocation;
260
260
  };
261
- /** Live-editable policy defaults for an AI pipeline step instance (#256). */
261
+ /** Live-editable policy defaults for an AI pipeline step instance. */
262
262
  export type AiStepPolicy = {
263
263
  readonly enabled: boolean;
264
264
  readonly providerId?: string;
265
265
  readonly model?: string;
266
266
  readonly params: Record<string, unknown>;
267
267
  };
268
- /** Preserves an unresolvable argument reference across parse → render (#998). */
268
+ /** Preserves an unresolvable argument reference across parse → render. */
269
269
  export type AiStepOpaqueArgs = {
270
270
  readonly __raw: string;
271
271
  };
@@ -278,7 +278,7 @@ function renderUseExtension(p) {
278
278
  if (p.extensionNameRaw !== undefined) {
279
279
  // A raw-ref name can't be spread into the merged Object-Form without
280
280
  // losing it to renderValue's plain-string serialization — fall back
281
- // to positional form, which keeps the reference verbatim (#2111).
281
+ // to positional form, which keeps the reference verbatim.
282
282
  const optionsArg = p.options !== undefined ? `, ${renderValue(p.options)}` : "";
283
283
  return `r.useExtension(${nameLiteral}, ${JSON.stringify(p.entityName)}${optionsArg});`;
284
284
  }
@@ -568,7 +568,7 @@ function renderMultiStreamProjection(p) {
568
568
  function renderDefineEvent(p) {
569
569
  const nameLiteral = p.eventNameRaw ?? JSON.stringify(p.eventName);
570
570
  const migrationEntries = p.migrations !== undefined ? Object.entries(p.migrations) : [];
571
- // piiFields is a mandatory option (fw#2558) — the options object is
571
+ // piiFields is a mandatory option — the options object is
572
572
  // always rendered, unlike the old version/migrations-only shortcut.
573
573
  const lines = [`r.defineEvent(${nameLiteral}, ${p.schemaSource.raw}, {`];
574
574
  lines.push(` piiFields: ${p.piiFields.raw},`);
@@ -4,7 +4,7 @@ import { isValidIanaTimeZone } from "../time/iana.js";
4
4
  import { reasonProblem } from "./escape-hatch-reason.js";
5
5
  import { createEventDef } from "./event-def.js";
6
6
  import { resolveName } from "./handler-helpers.js";
7
- import { splitNamedDefinition, unwrapArrayForm } from "./object-form.js";
7
+ import { requireArgument, splitNamedDefinition, unwrapArrayForm } from "./object-form.js";
8
8
  import { QnTypes, qn, toKebab } from "./qualified-name.js";
9
9
  // Builds config/secrets/claims/events/jobs/notifications registrar methods.
10
10
  export function buildConfigEventsJobsMethods(state, name) {
@@ -49,7 +49,7 @@ export function buildConfigEventsJobsMethods(state, name) {
49
49
  }
50
50
  // Shared by defineEvent's `migrations` option — each entry registers a
51
51
  // single upcast step, same validation/dedup as the old standalone
52
- // r.eventMigration() call (folded into defineEvent, #1082 step 8).
52
+ // r.eventMigration() call (folded into defineEvent).
53
53
  function registerEventMigration(eventName, fromVersion, toVersion, transform) {
54
54
  if (toVersion !== fromVersion + 1) {
55
55
  throw new Error(`[Feature ${name}] defineEvent("${eventName}") migrations: only single-step migrations are allowed — toVersion must be fromVersion + 1 (got ${fromVersion} -> ${toVersion}). ` +
@@ -73,8 +73,8 @@ export function buildConfigEventsJobsMethods(state, name) {
73
73
  const [jobName, jobOptions, jobHandler] = typeof jobNameOrDefinition === "string"
74
74
  ? [
75
75
  jobNameOrDefinition,
76
- options,
77
- handler,
76
+ requireArgument(options, name, "job", "options"),
77
+ requireArgument(handler, name, "job", "handler"),
78
78
  ]
79
79
  : (() => {
80
80
  const { name: definedJobName, handler: h, ...rest } = jobNameOrDefinition;
@@ -97,9 +97,9 @@ export function buildConfigEventsJobsMethods(state, name) {
97
97
  'tz must be an IANA time zone name such as "Europe/Berlin".');
98
98
  }
99
99
  }
100
- // Resolve NameOrRef(s) in trigger.on. Multi-Trigger-Form: Array
101
- // wird zu Array von resolved strings, Single bleibt single string —
102
- // job-runner unterscheidet anhand Array.isArray.
100
+ // Resolve NameOrRef(s) in trigger.on. For the multi-trigger form, arrays
101
+ // become arrays of resolved strings while a single value remains a string;
102
+ // the job runner distinguishes them with Array.isArray.
103
103
  const trigger = "on" in jobOptions.trigger
104
104
  ? {
105
105
  on: Array.isArray(jobOptions.trigger.on)
@@ -114,7 +114,10 @@ export function buildConfigEventsJobsMethods(state, name) {
114
114
  },
115
115
  notification(notificationNameOrDefinition, definition) {
116
116
  const [notificationName, resolvedDefinition] = typeof notificationNameOrDefinition === "string"
117
- ? [notificationNameOrDefinition, definition]
117
+ ? [
118
+ notificationNameOrDefinition,
119
+ requireArgument(definition, name, "notification", "definition"),
120
+ ]
118
121
  : splitNamedDefinition(notificationNameOrDefinition);
119
122
  state.notifications[notificationName] = {
120
123
  name: notificationName,
@@ -167,7 +170,7 @@ export function buildConfigEventsJobsMethods(state, name) {
167
170
  },
168
171
  metric(shortNameOrDefinition, options) {
169
172
  const [shortName, metricOptions] = typeof shortNameOrDefinition === "string"
170
- ? [shortNameOrDefinition, options]
173
+ ? [shortNameOrDefinition, requireArgument(options, name, "metric", "options")]
171
174
  : splitNamedDefinition(shortNameOrDefinition);
172
175
  if (state.metrics[shortName]) {
173
176
  throw new Error(`[Feature ${name}] Metric "${shortName}" already registered. ` +
@@ -183,7 +186,7 @@ export function buildConfigEventsJobsMethods(state, name) {
183
186
  },
184
187
  secret(shortNameOrDefinition, options) {
185
188
  const [shortName, secretOptions] = typeof shortNameOrDefinition === "string"
186
- ? [shortNameOrDefinition, options]
189
+ ? [shortNameOrDefinition, requireArgument(options, name, "secret", "options")]
187
190
  : splitNamedDefinition(shortNameOrDefinition);
188
191
  if (state.secretKeys[shortName]) {
189
192
  throw new Error(`[Feature ${name}] Secret "${shortName}" already registered. ` +
@@ -208,7 +211,7 @@ export function buildConfigEventsJobsMethods(state, name) {
208
211
  },
209
212
  secretNamespace(shortNameOrDefinition, options) {
210
213
  const [shortName, namespaceOptions] = typeof shortNameOrDefinition === "string"
211
- ? [shortNameOrDefinition, options]
214
+ ? [shortNameOrDefinition, requireArgument(options, name, "secretNamespace", "options")]
212
215
  : splitNamedDefinition(shortNameOrDefinition);
213
216
  if (state.secretNamespaces[shortName]) {
214
217
  throw new Error(`[Feature ${name}] Secret namespace "${shortName}" already registered. ` +
@@ -227,7 +230,7 @@ export function buildConfigEventsJobsMethods(state, name) {
227
230
  ? shortNameOrDefinition
228
231
  : shortNameOrDefinition.name;
229
232
  const claimType = typeof shortNameOrDefinition === "string"
230
- ? options.type
233
+ ? requireArgument(options, name, "claimKey", "options").type
231
234
  : shortNameOrDefinition.type;
232
235
  if (state.claimKeys[shortName]) {
233
236
  throw new Error(`[Feature ${name}] Claim key "${shortName}" already declared. ` +
@@ -3,7 +3,7 @@ import { toTableName } from "../db/table-builder.js";
3
3
  import { isEntityConventionQueryHandler, isPagedQueryHandler, PAGED_QUERY_HANDLER_BRAND, } from "./define-handler.js";
4
4
  import { assertEscapeHatchAccessIsSystemAdminOnly, registerCrudHandlers, } from "./entity-handlers.js";
5
5
  import { resolveName } from "./handler-helpers.js";
6
- import { isObjectArgument, throwObjectArgumentRequired } from "./object-form.js";
6
+ import { isObjectArgument, requireArgument, throwObjectArgumentRequired } from "./object-form.js";
7
7
  const CRUD_VERBS = new Set(["create", "update", "delete"]);
8
8
  const CRUD_REGISTERED_VERBS = new Set([...CRUD_VERBS, "restore", "list", "detail"]);
9
9
  // A silent overwrite would drop framework behaviour that rides on the r.crud
@@ -42,9 +42,9 @@ function tryMapEntity(state, name, handlerName) {
42
42
  // skip: feature-name entity match is the preferred mapping
43
43
  return;
44
44
  }
45
- const entityKeys = Object.keys(state.entities);
46
- if (entityKeys.length === 1) {
47
- state.handlerEntityMappings[handlerName] = entityKeys[0];
45
+ const [onlyEntityName, ...otherEntityNames] = Object.keys(state.entities);
46
+ if (onlyEntityName !== undefined && otherEntityNames.length === 0) {
47
+ state.handlerEntityMappings[handlerName] = onlyEntityName;
48
48
  }
49
49
  }
50
50
  }
@@ -97,7 +97,11 @@ export function buildEntityHandlerMethods(state, name) {
97
97
  const { entity, name: innerName, ...rest } = entityRefOrDefinition;
98
98
  return [entity, innerName, rest];
99
99
  })()
100
- : [entityRefOrDefinition, relationName, definition];
100
+ : [
101
+ entityRefOrDefinition,
102
+ requireArgument(relationName, name, "relation", "relation name"),
103
+ requireArgument(definition, name, "relation", "definition"),
104
+ ];
101
105
  const entityName = resolveName(entityRef);
102
106
  if (!state.relations[entityName])
103
107
  state.relations[entityName] = {};
@@ -48,10 +48,10 @@ export type ManifestFeature = {
48
48
  readonly extensionsUsed: readonly ManifestExtension[];
49
49
  readonly configKeys: readonly ManifestConfigKey[];
50
50
  readonly secrets: readonly ManifestSecret[];
51
- /** Alle registrierten Write-Handler-QNs dieses Features
52
- * (z.B. "user:write:user:create", "auth-email-password:write:login").
53
- * Von `collectWriteHandlerQns` abgeleitet — dient als Source-of-Truth
54
- * für den Client-seitigen Typcheck von `dispatcher.write`-Calls. */
51
+ /** All registered write-handler QNs for this feature
52
+ * (e.g. "user:write:user:create", "auth-email-password:write:login").
53
+ * Derived from `collectWriteHandlerQns` and used as the source of truth
54
+ * for client-side type checking of `dispatcher.write` calls. */
55
55
  readonly writeHandlers: readonly string[];
56
56
  /** Picker/scaffolder UI metadata declared via r.uiHints(). Optional —
57
57
  * absent for features that haven't been annotated yet (the picker falls
@@ -68,13 +68,13 @@ export type FeatureManifest = {
68
68
  readonly tier?: string;
69
69
  };
70
70
  export type BuildManifestOptions = {
71
- /** Herkunfts-Beschreibung fürs Manifest (landet 1:1 im JSON). */
71
+ /** Description of the manifest's origin, included unchanged in the JSON. */
72
72
  readonly source: string;
73
- /** Nur diese Features emittieren (z.B. die 16 enterprise-Features einer
74
- * Registry, die auch deren bundled-requires gemountet hat). Default:
75
- * alle Features der Registry. */
73
+ /** Emit only these features (e.g. the 16 enterprise features from a
74
+ * Registry that also mounted their bundled requirements). Default:
75
+ * all features in the Registry. */
76
76
  readonly featureNames?: ReadonlySet<string>;
77
- /** Taggt jedes Feature + das Manifest top-level (z.B. "enterprise"). */
77
+ /** Tags each feature and the top-level manifest (e.g. "enterprise"). */
78
78
  readonly tier?: string;
79
79
  };
80
80
  export declare function buildManifestFromRegistry(registry: Registry, options: BuildManifestOptions): FeatureManifest;
@@ -1,10 +1,9 @@
1
- // Runtime-introspected feature-manifest — die geteilte Extraktionslogik
2
- // hinter `gen-feature-manifest.ts` (use-all-bundled) und dem enterprise-
3
- // Generator. Vorher waren das zwei fast wortgleiche Forks mit divergentem
4
- // Schema (enterprise#95): erweitert das Framework die Introspektion, driftete
5
- // die Kopie still. Quelle der Wahrheit ist die GEBOOTETE Registry — der
6
- // AST-Parser kann die imperativen Factory-Helper der bundled features nicht
7
- // lesen.
1
+ // Runtime-introspected feature manifest — shared extraction logic for
2
+ // `gen-feature-manifest.ts` (use-all-bundled) and the enterprise generator.
3
+ // These were previously two nearly identical forks with diverging schemas:
4
+ // when framework introspection was extended, the copy silently drifted. The
5
+ // booted Registry is authoritative because the AST parser cannot read the
6
+ // imperative factory helpers of bundled features.
8
7
  import { SYSTEM_EVENT_PREFIX } from "../crypto/system-event-pii.js";
9
8
  import { compareByCodepoint } from "../utils/index.js";
10
9
  import { isEncryptedAtRest } from "./config-helpers.js";
@@ -38,9 +38,9 @@ export declare function buildUiExtensionsMethods<TName extends string>(state: Fe
38
38
  */
39
39
  exposesApi(apiName: string): void;
40
40
  /**
41
- * Declares that this feature calls a cross-feature API. Boot-Validator
42
- * checkt dass irgendein anderes Feature `r.exposesApi(name)` macht und
43
- * dass dieses Feature `r.requires` darauf hat.
41
+ * Declares that this feature calls a cross-feature API. The boot validator
42
+ * checks that another feature calls `r.exposesApi(name)` and that this
43
+ * feature declares a corresponding `r.requires` dependency.
44
44
  */
45
45
  usesApi(apiName: string): void;
46
46
  bootCheck(fn: BootCheckFn): void;
@@ -6,7 +6,7 @@ import { bindHookEscapeHatchGrant } from "../pipeline/system-identity-switch.js"
6
6
  import { LifecycleHookTypes } from "./constants.js";
7
7
  import { reasonProblem } from "./escape-hatch-reason.js";
8
8
  import { resolveName } from "./handler-helpers.js";
9
- import { isObjectArgument, throwObjectArgumentRequired } from "./object-form.js";
9
+ import { isObjectArgument, requireArgument, throwObjectArgumentRequired } from "./object-form.js";
10
10
  import { isKebabSegment, isValidQn, qualifyEntityName, toKebab } from "./qualified-name.js";
11
11
  import { HookPhases } from "./types/index.js";
12
12
  // Builds hooks/extensions/projections/screens/nav/workspace/tables/tree-actions
@@ -159,7 +159,7 @@ export function buildUiExtensionsMethods(state, name) {
159
159
  }
160
160
  const targets = Array.isArray(target) ? target : [target];
161
161
  const names = targets.map(resolveName);
162
- // Hook-fn casts unten alle: @cast-boundary engine-bridge
162
+ // All hook-function casts below: @cast-boundary engine-bridge
163
163
  // — typed Dev-API (LifecycleHookFn|ValidationHookFn) → erased Map<name, fn>.
164
164
  if (type === "validation") {
165
165
  for (const n of names) {
@@ -211,7 +211,11 @@ export function buildUiExtensionsMethods(state, name) {
211
211
  },
212
212
  useExtension(extensionNameOrDefinition, entityRef, options) {
213
213
  const [extensionName, resolvedEntityRef, resolvedOptions] = typeof extensionNameOrDefinition === "string"
214
- ? [extensionNameOrDefinition, entityRef, options]
214
+ ? [
215
+ extensionNameOrDefinition,
216
+ requireArgument(entityRef, name, "useExtension", "entity"),
217
+ options,
218
+ ]
215
219
  : (() => {
216
220
  const { name, entity, ...rest } = extensionNameOrDefinition;
217
221
  return [name, entity, rest];
@@ -219,13 +223,14 @@ export function buildUiExtensionsMethods(state, name) {
219
223
  const resolvedEntityName = resolveName(resolvedEntityRef);
220
224
  // @cast-boundary engine-bridge — typed per-extension options → erased registration bag
221
225
  const optionsBag = resolvedOptions;
222
- // fw#2914 — cross-cutting escapeHatch convention for hook-context db
226
+ // Cross-cutting escapeHatch convention for hook-context db
223
227
  // access (mirrors r.hook's validation above). Validated the same way
224
228
  // for every extension regardless of its typed hook shape.
225
229
  const escapeHatch = optionsBag?.["escapeHatch"];
226
230
  if (escapeHatch !== undefined) {
227
- const reason = isPlainObject(escapeHatch) ? escapeHatch["reason"] : undefined;
228
- if (typeof reason !== "string") {
231
+ const escapeHatchObject = isPlainObject(escapeHatch) ? escapeHatch : undefined;
232
+ const reason = escapeHatchObject?.["reason"];
233
+ if (escapeHatchObject === undefined || typeof reason !== "string") {
229
234
  throw new Error(`[Feature ${name}] r.useExtension("${extensionName}", "${resolvedEntityName}", ...) declares an invalid { escapeHatch } — ` +
230
235
  `must be { reason: "<explanation>" } explaining why this usage needs unfiltered db access.`);
231
236
  }
@@ -233,7 +238,7 @@ export function buildUiExtensionsMethods(state, name) {
233
238
  if (extReasonProblem !== undefined) {
234
239
  throw new Error(`[Feature ${name}] r.useExtension("${extensionName}", "${resolvedEntityName}", ...) declares an invalid { escapeHatch } — ${extReasonProblem}`);
235
240
  }
236
- const extGrantsProblem = escapeHatchGrantsProblem(escapeHatch);
241
+ const extGrantsProblem = escapeHatchGrantsProblem(escapeHatchObject);
237
242
  if (extGrantsProblem) {
238
243
  throw new Error(`[Feature ${name}] r.useExtension("${extensionName}", "${resolvedEntityName}", ...) declares an invalid { escapeHatch } — ${extGrantsProblem}`);
239
244
  }
@@ -282,9 +287,9 @@ export function buildUiExtensionsMethods(state, name) {
282
287
  state.exposedApis.add(apiName);
283
288
  },
284
289
  /**
285
- * Declares that this feature calls a cross-feature API. Boot-Validator
286
- * checkt dass irgendein anderes Feature `r.exposesApi(name)` macht und
287
- * dass dieses Feature `r.requires` darauf hat.
290
+ * Declares that this feature calls a cross-feature API. The boot validator
291
+ * checks that another feature calls `r.exposesApi(name)` and that this
292
+ * feature declares a corresponding `r.requires` dependency.
288
293
  */
289
294
  usesApi(apiName) {
290
295
  state.usedApis.add(apiName);
@@ -344,7 +349,11 @@ export function buildUiExtensionsMethods(state, name) {
344
349
  entityRefOrDefinition.data,
345
350
  entityRefOrDefinition.upsertKey,
346
351
  ]
347
- : [entityRefOrDefinition, data, options?.upsertKey];
352
+ : [
353
+ entityRefOrDefinition,
354
+ requireArgument(data, name, "referenceData", "data"),
355
+ options?.upsertKey,
356
+ ];
348
357
  state.referenceData.push({
349
358
  entityName: resolveName(entityRef),
350
359
  data: resolvedData,
@@ -424,10 +433,9 @@ export function buildUiExtensionsMethods(state, name) {
424
433
  state.workspaces[definition.id] = definition;
425
434
  },
426
435
  httpRoute(definition) {
427
- // Path-Validation: muss mit "/" beginnen, keine /api/-Routes (die
428
- // sind dem Dispatcher reserviert; eine HTTP-Route die /api/foo
429
- // belegt, würde die Auth-Middleware umgehen ohne dass der Author
430
- // das ausgesprochen hat — bewusster Block).
436
+ // Path validation: must start with "/"; /api/ routes are reserved for the
437
+ // dispatcher. An HTTP route occupying /api/foo could bypass the auth
438
+ // middleware without the author explicitly intending it, so block it.
431
439
  if (!definition.path.startsWith("/")) {
432
440
  throw new Error(`[Feature ${name}] httpRoute path "${definition.path}" must start with "/". ` +
433
441
  `Got "${definition.path}".`);
@@ -505,7 +513,7 @@ export function buildUiExtensionsMethods(state, name) {
505
513
  }
506
514
  // `read_` is reserved for r.entity()/r.projection() (managed,
507
515
  // event-sourced, rebuildable). storeTable is the unmanaged
508
- // direct-write escape hatch — the prefix must say so (#1220).
516
+ // direct-write escape hatch — the prefix must say so.
509
517
  if (tableName.startsWith("read_")) {
510
518
  throw new Error(`[Feature ${name}] r.storeTable("${tableName}"): the "read_" prefix is reserved ` +
511
519
  `for managed r.entity()/r.projection() tables. Pick an unprefixed name or a ` +
@@ -514,7 +522,7 @@ export function buildUiExtensionsMethods(state, name) {
514
522
  // meta.source must agree with the r.storeTable() escape hatch, or the
515
523
  // migrate-generator treats schema drift on this table as safe to
516
524
  // DROP+rebuild-from-events — wiping direct-write data with no events
517
- // to replay it from (#1209).
525
+ // to replay it from.
518
526
  if (meta.source !== "unmanaged") {
519
527
  throw new Error(`[Feature ${name}] r.storeTable("${tableName}") was given an EntityTableMeta with ` +
520
528
  `source: "${meta.source}". r.storeTable() requires source: "unmanaged" (via ` +
@@ -538,16 +546,16 @@ export function buildUiExtensionsMethods(state, name) {
538
546
  };
539
547
  },
540
548
  treeActions(actions) {
541
- // Only-once-guard: zweiter Aufruf ist Author-Bug, soll am
542
- // Feature-File aufschlagen (gleicher Stil wie r.toggleable).
549
+ // Only-once guard: a second call is an authoring error that should surface in
550
+ // the feature file (matching the behavior of r.toggleable).
543
551
  if (state.treeActions !== undefined) {
544
552
  throw new Error(`[Feature ${name}] r.treeActions() already called. ` +
545
553
  `Each feature may declare a single tree-actions schema.`);
546
554
  }
547
555
  state.treeActions = actions;
548
- // Return typed handle für setup-export. Frozen damit Caller die
549
- // Map nicht nachträglich mutieren (würde Pattern-AST + Runtime-
550
- // Lookup divergieren lassen).
556
+ // Return a typed handle for the setup export. Freeze it so callers cannot
557
+ // mutate the map afterward, which would make the pattern AST and runtime
558
+ // lookup diverge.
551
559
  return Object.freeze({
552
560
  id: name,
553
561
  treeActions: actions,
@@ -119,18 +119,17 @@ export function checkWriteFieldRoles(entity, changes, user) {
119
119
  // row. For creates, pass oldRow = undefined; the check degenerates to a
120
120
  // newRow-only evaluation.
121
121
  //
122
- // `submittedChanges` and `rowContext` are deliberately separate (fw#1685):
122
+ // `submittedChanges` and `rowContext` are deliberately separate:
123
123
  // - `submittedChanges` drives WHICH fields get checked — only what the user
124
124
  // actually wrote in the request. A preSave-hook-derived field the user
125
125
  // never touched (e.g. a system hook setting `assignedTo`) must not be
126
126
  // field-ownership-checked against the user at all.
127
127
  // - `rowContext` drives what a checked field's rule is evaluated AGAINST —
128
128
  // this needs the full post-hook row, because an ownership rule can
129
- // reference a DIFFERENT column that only a hook populates (kumiko-
130
- // framework#1672: a hook derives `authorId`, a user-submitted `secretNote`
131
- // field's rule is `from("user:id", "authorId")` — the check needs the
132
- // hook-derived `authorId` in scope even though the user only wrote
133
- // `secretNote`). Defaults to `submittedChanges` when omitted.
129
+ // reference a DIFFERENT column that only a hook populates (a hook derives
130
+ // `authorId`, a user-submitted `secretNote` field's rule is `from("user:id", "authorId")`
131
+ // — the check needs the hook-derived `authorId` in scope even though the
132
+ // user only wrote `secretNote`). Defaults to `submittedChanges` when omitted.
134
133
  //
135
134
  // Returns the denied field name for the caller to wrap into an
136
135
  // `ownership_denied` error with scope: "field", or null if all fields pass.
@@ -91,4 +91,5 @@ export type { AccessRule, ActionFormScreenDefinition, ActiveMembership, ActiveMe
91
91
  export { DEFAULT_CURRENCIES, DEFAULT_LOCALES, DELIVERY_FAILURE_CODES, DELIVERY_SKIP_REASONS, HookPhases, HTTP_ERROR_CODE_PATTERN, isAgentVisibleScreen, isDeliveryErrorCode, isOpenToAllGranted, resolveAgentExposure, } from "./types/index.ts";
92
92
  export type { AwaitedEventType, PipelineBuildCtx, PipelineCtx, PipelineDef, StepBuilder, StepDef, StepFailureStrategy, StepInstance, StepKind, StepNamespace, StepResolver, } from "./types/step.ts";
93
93
  export { runValidation } from "./validation.ts";
94
+ export { walkStepInstances } from "./walk-step-instances.ts";
94
95
  export { formDerivationsOfSchema, withFormDerivations } from "./with-form-derivations.ts";
@@ -22,10 +22,9 @@ export { defineStep, getStep, listStepKinds } from "./define-step.js";
22
22
  export { computeDefinitionFingerprint, defineWorkflow } from "./define-workflow.js";
23
23
  export { computeEffectiveFeatures, isToggleableFeature } from "./effective-features.js";
24
24
  export { createEntityExecutor, defineEntityCreateHandler, defineEntityDeleteHandler, defineEntityDetailHandler, defineEntityListHandler,
25
- // Legacy single-fn-with-verb-string API. Backwards-compat — neue
26
- // Apps nehmen die verb-spezifischen Wrapper oben. Existierende
27
- // Caller (Integration-Tests, alte bundled-features) bleiben so
28
- // unverändert lauffähig.
25
+ // Legacy single-fn-with-verb-string API. Backwards compatibility — new
26
+ // apps use the verb-specific wrappers above. Existing callers (integration
27
+ // tests, old bundled-features) remain runnable without changes.
29
28
  defineEntityQueryHandler, defineEntityRestoreHandler, defineEntityUpdateHandler, defineEntityWriteHandler, defineProjectionQueryHandler, entityListSchema, MAX_LIST_LIMIT, } from "./entity-handlers.js";
30
29
  export { declareEscapeHatch } from "./escape-hatch-declaration.js";
31
30
  export { createEventDef } from "./event-def.js";
@@ -81,4 +80,5 @@ export { findOverDeepTransferChain, resolveTransferAdjacency as resolveEntityTra
81
80
  export { isSystemTenant, isUuid, parseTenantId, SYSTEM_TENANT_ID } from "./types/identifiers.js";
82
81
  export { DEFAULT_CURRENCIES, DEFAULT_LOCALES, DELIVERY_FAILURE_CODES, DELIVERY_SKIP_REASONS, HookPhases, HTTP_ERROR_CODE_PATTERN, isAgentVisibleScreen, isDeliveryErrorCode, isOpenToAllGranted, resolveAgentExposure, } from "./types/index.js";
83
82
  export { runValidation } from "./validation.js";
83
+ export { walkStepInstances } from "./walk-step-instances.js";
84
84
  export { formDerivationsOfSchema, withFormDerivations } from "./with-form-derivations.js";
@@ -1,6 +1,7 @@
1
1
  export declare function splitNamedDefinition<T extends {
2
2
  readonly name: string;
3
3
  }>(definition: T): [string, Omit<T, "name">];
4
+ export declare function requireArgument<T>(value: T | undefined, featureName: string, method: string, argument: string): T;
4
5
  export declare function unwrapArrayForm<K extends string>(args: readonly [Record<K, readonly string[]>] | readonly string[], key: K): readonly string[];
5
6
  export declare function isObjectArgument(value: unknown): boolean;
6
7
  export declare function throwObjectArgumentRequired(featureName: string, method: string): never;
@@ -4,14 +4,24 @@ export function splitNamedDefinition(definition) {
4
4
  const { name, ...rest } = definition;
5
5
  return [name, rest];
6
6
  }
7
+ // The `(name, options)` overloads make `options` required for typed callers;
8
+ // the implementation signature has to stay optional, so untyped callers are caught here.
9
+ export function requireArgument(value, featureName, method, argument) {
10
+ if (value === undefined) {
11
+ throw new Error(`[Feature ${featureName}] r.${method}() needs its ${argument} argument`);
12
+ }
13
+ return value;
14
+ }
15
+ function isStringList(args) {
16
+ return args.every((arg) => typeof arg === "string");
17
+ }
7
18
  // `optionalRequires` and `readConfig` still accept variadic strings next to a
8
19
  // single object wrapping the same string array under `key`.
9
20
  export function unwrapArrayForm(args, key) {
21
+ if (isStringList(args))
22
+ return args;
10
23
  const [first] = args;
11
- if (typeof first === "object" && first !== null && key in first) {
12
- return first[key];
13
- }
14
- return args;
24
+ return first[key];
15
25
  }
16
26
  export function isObjectArgument(value) {
17
27
  return typeof value === "object" && value !== null;
@@ -72,7 +72,7 @@ export function matchesRule(rule, user, row) {
72
72
  // `where` rules produce SQL for the DB-side filter and have no in-memory
73
73
  // evaluator — the feature author owns the semantics. Only the read path
74
74
  // reaches SQL (buildOwnershipClause); on write maps the boot-validator
75
- // rejects them outright (fw#2626), and the in-memory write helpers below
75
+ // rejects them outright, and the in-memory write helpers below
76
76
  // fail closed rather than calling in here.
77
77
  throw new Error("where-rules can only be evaluated at the SQL layer (buildOwnershipClause); they are rejected on access.write at boot.");
78
78
  }
@@ -219,7 +219,7 @@ export function shiftParams(fragment, shift) {
219
219
  // is a boot-time misconfiguration; at request time we treat it as empty
220
220
  // (safe default) rather than passing silently.
221
221
  //
222
- // Caller obligations (fw#1700) — this function returns ONLY the ownership
222
+ // Caller obligations — this function returns ONLY the ownership
223
223
  // fragment, not the full row-access contract. A raw-SQL caller (not going
224
224
  // through `ctx.db`, which already applies all three) must additionally:
225
225
  // 1. Pass `paramStart` as `params.length + 1` for its own already-bound
@@ -309,7 +309,7 @@ function ruleToFragment(rule, user, table, paramStart) {
309
309
  });
310
310
  // Runtime backstop for the boot-time probe (boot-validator/ownership.ts):
311
311
  // an unqualified column in a correlated subquery silently turns into a
312
- // fail-open tautology (fw#2639) — throw instead of shipping it.
312
+ // fail-open tautology — throw instead of shipping it.
313
313
  assertQualifiedWhereFragment(frag.sqlText, cachedTableColumnSqlNames(table), `ownership where-rule on "${tableNameOf(table)}"`);
314
314
  return { kind: "sql", sqlText: frag.sqlText, params: frag.params };
315
315
  }
@@ -1,12 +1,10 @@
1
- // Tier 2.7e Cross-Feature: ReferenceFieldDef.entity-String-Parser.
2
- //
3
- // Akzeptiert beide Formen:
1
+ // Parser for the ReferenceFieldDef.entity string. Accepts both forms:
4
2
  // - "user" → same-feature ref (featureName = currentFeature)
5
- // - "users:user" → cross-feature ref (qualifiziert)
3
+ // - "users:user" → cross-feature ref (qualified)
6
4
  //
7
- // Lebt im framework-Package damit Server-Validator + Renderer (über
8
- // Re-Export aus @cosmicdrift/kumiko-renderer/headless) denselben Parser nutzen — die
9
- // Convention darf nicht zweimal implementiert werden.
5
+ // Kept in the framework package so the server validator and renderer (via
6
+ // re-export from @cosmicdrift/kumiko-renderer/headless) use the same parser;
7
+ // the convention must not be implemented twice.
10
8
  export function parseRefTarget(raw, currentFeature) {
11
9
  const idx = raw.indexOf(":");
12
10
  if (idx < 0) {