@cosmicdrift/kumiko-framework 0.385.0 → 0.387.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (298) hide show
  1. package/LICENSE +21 -57
  2. package/README.md +2 -2
  3. package/agents/widgets.md +39 -39
  4. package/dist/api/api-constants.js +2 -2
  5. package/dist/api/auth-middleware.js +7 -7
  6. package/dist/api/auth-rate-limiters.js +7 -3
  7. package/dist/api/auth-routes-mfa.js +1 -1
  8. package/dist/api/auth-routes-session.js +1 -1
  9. package/dist/api/auth-routes-shared.js +7 -3
  10. package/dist/api/auth-routes-signup.js +1 -1
  11. package/dist/api/client-ip.js +3 -1
  12. package/dist/api/extra-route.js +1 -1
  13. package/dist/api/pii-leak-guard.js +4 -2
  14. package/dist/api/readiness.js +5 -5
  15. package/dist/api/redis-sse-broker.js +8 -6
  16. package/dist/api/request-context.d.ts +2 -0
  17. package/dist/api/request-context.js +16 -7
  18. package/dist/api/routes.js +2 -2
  19. package/dist/api/server.js +34 -33
  20. package/dist/api/sse-broker.js +1 -1
  21. package/dist/api/sse-route.d.ts +13 -13
  22. package/dist/api/sse-route.js +15 -15
  23. package/dist/auth/index.js +4 -4
  24. package/dist/auth/roles.d.ts +4 -4
  25. package/dist/auth/roles.js +24 -24
  26. package/dist/compliance/duration-spec.js +18 -20
  27. package/dist/compliance/index.js +2 -3
  28. package/dist/compliance/override-schema.d.ts +4 -4
  29. package/dist/compliance/override-schema.js +19 -22
  30. package/dist/compliance/profiles.d.ts +26 -29
  31. package/dist/compliance/profiles.js +62 -69
  32. package/dist/compliance/sub-processors.d.ts +28 -30
  33. package/dist/compliance/sub-processors.js +17 -21
  34. package/dist/consumer-cli.js +4 -4
  35. package/dist/crypto/blind-index.js +18 -17
  36. package/dist/crypto/ciphertext-pattern.js +1 -1
  37. package/dist/crypto/event-pii.js +3 -3
  38. package/dist/crypto/kek-source.d.ts +1 -1
  39. package/dist/crypto/kek-source.js +3 -2
  40. package/dist/crypto/kms-adapter.d.ts +1 -1
  41. package/dist/crypto/kms-adapter.js +4 -6
  42. package/dist/crypto/kms-wiring.d.ts +1 -2
  43. package/dist/crypto/kms-wiring.js +2 -2
  44. package/dist/crypto/pg-kms-adapter.js +2 -6
  45. package/dist/crypto/pii-field-encryption.js +4 -4
  46. package/dist/crypto/subject-resolver.d.ts +1 -1
  47. package/dist/crypto/subject-resolver.js +2 -2
  48. package/dist/db/apply-entity-event.d.ts +6 -6
  49. package/dist/db/apply-entity-event.js +55 -55
  50. package/dist/db/blind-index-cleanup.js +7 -7
  51. package/dist/db/collect-table-metas.js +10 -11
  52. package/dist/db/compound-types.d.ts +1 -1
  53. package/dist/db/compound-types.js +12 -12
  54. package/dist/db/cross-tenant-reads.d.ts +8 -0
  55. package/dist/db/cross-tenant-reads.js +23 -0
  56. package/dist/db/cursor.js +3 -4
  57. package/dist/db/dialect.d.ts +2 -2
  58. package/dist/db/dialect.js +2 -2
  59. package/dist/db/eagerload.d.ts +3 -3
  60. package/dist/db/eagerload.js +21 -12
  61. package/dist/db/entity-table-meta.d.ts +2 -2
  62. package/dist/db/entity-table-meta.js +4 -4
  63. package/dist/db/event-store-executor-context.d.ts +1 -1
  64. package/dist/db/event-store-executor-context.js +13 -13
  65. package/dist/db/event-store-executor-read.js +27 -28
  66. package/dist/db/event-store-executor-write.js +65 -70
  67. package/dist/db/located-timestamp.d.ts +11 -11
  68. package/dist/db/located-timestamp.js +29 -30
  69. package/dist/db/migrate-generator.d.ts +1 -1
  70. package/dist/db/migrate-generator.js +26 -19
  71. package/dist/db/migrate-runner.js +25 -25
  72. package/dist/db/money.d.ts +6 -6
  73. package/dist/db/money.js +29 -27
  74. package/dist/db/queries/backfill-pii.js +5 -5
  75. package/dist/db/queries/ddl.js +1 -1
  76. package/dist/db/queries/es-ops.d.ts +1 -1
  77. package/dist/db/queries/es-ops.js +1 -1
  78. package/dist/db/queries/event-consumer.js +3 -3
  79. package/dist/db/queries/event-store.js +4 -2
  80. package/dist/db/queries/projection-rebuild.js +3 -3
  81. package/dist/db/queries/raw-sql.d.ts +2 -2
  82. package/dist/db/queries/raw-sql.js +2 -2
  83. package/dist/db/queries/shadow-swap.js +19 -19
  84. package/dist/db/queries/test-stack.d.ts +4 -1
  85. package/dist/db/queries/test-stack.js +15 -2
  86. package/dist/db/query.d.ts +1 -1
  87. package/dist/db/query.js +6 -6
  88. package/dist/db/rebuild-marker.js +20 -21
  89. package/dist/db/replay-migration-sql.js +2 -2
  90. package/dist/db/schema-inspection.js +4 -5
  91. package/dist/db/table-builder.d.ts +1 -2
  92. package/dist/db/table-builder.js +82 -82
  93. package/dist/db/tenant-db.d.ts +2 -2
  94. package/dist/db/tenant-db.js +9 -2
  95. package/dist/derivatives/delete-stored-file.js +5 -2
  96. package/dist/derivatives/derivatives-context.js +1 -1
  97. package/dist/engine/boot-validator/access-roles.js +4 -3
  98. package/dist/engine/boot-validator/api-ext.js +14 -14
  99. package/dist/engine/boot-validator/config-deps.js +7 -6
  100. package/dist/engine/boot-validator/detail-screens.js +3 -3
  101. package/dist/engine/boot-validator/entity-handler.js +60 -61
  102. package/dist/engine/boot-validator/gdpr-storage.js +4 -4
  103. package/dist/engine/boot-validator/i18n-keys.js +1 -1
  104. package/dist/engine/boot-validator/index.d.ts +2 -2
  105. package/dist/engine/boot-validator/index.js +20 -20
  106. package/dist/engine/boot-validator/nav.js +5 -5
  107. package/dist/engine/boot-validator/ownership.js +12 -9
  108. package/dist/engine/boot-validator/parent-ref.js +1 -1
  109. package/dist/engine/boot-validator/pii-retention.js +50 -57
  110. package/dist/engine/boot-validator/projection-list-screens.js +7 -7
  111. package/dist/engine/boot-validator/query-refs.js +1 -1
  112. package/dist/engine/boot-validator/screens-actions.js +15 -16
  113. package/dist/engine/boot-validator/screens-forms.js +10 -11
  114. package/dist/engine/boot-validator/screens-lists.js +14 -15
  115. package/dist/engine/boot-validator/screens.js +34 -40
  116. package/dist/engine/boot-validator/security-baseline.js +3 -2
  117. package/dist/engine/boot-validator/transfer-graph.js +2 -2
  118. package/dist/engine/boot-validator/workspaces.js +3 -3
  119. package/dist/engine/bridge-stub.js +3 -4
  120. package/dist/engine/build-app-schema.js +75 -75
  121. package/dist/engine/build-config-feature-schema.js +50 -50
  122. package/dist/engine/build-target.js +20 -23
  123. package/dist/engine/codemod/pipeline-codemod.js +20 -13
  124. package/dist/engine/config-helpers.js +4 -4
  125. package/dist/engine/constants.js +2 -2
  126. package/dist/engine/create-app.js +2 -2
  127. package/dist/engine/define-feature.js +12 -11
  128. package/dist/engine/define-handler.js +12 -11
  129. package/dist/engine/define-step.js +1 -1
  130. package/dist/engine/define-workflow.d.ts +5 -5
  131. package/dist/engine/define-workflow.js +6 -6
  132. package/dist/engine/entity-handlers.js +13 -14
  133. package/dist/engine/event-def.js +2 -2
  134. package/dist/engine/extension-names.d.ts +16 -17
  135. package/dist/engine/extension-names.js +37 -38
  136. package/dist/engine/extensions/escape-hatch-usage.js +1 -1
  137. package/dist/engine/extensions/tenant-data.js +2 -2
  138. package/dist/engine/extensions/user-data.d.ts +23 -24
  139. package/dist/engine/factories.d.ts +14 -13
  140. package/dist/engine/factories.js +38 -37
  141. package/dist/engine/feature-ast/entity-field-types.js +1 -1
  142. package/dist/engine/feature-ast/extractors/handlers.js +1 -1
  143. package/dist/engine/feature-ast/extractors/hooks.js +1 -1
  144. package/dist/engine/feature-ast/extractors/shared.d.ts +2 -3
  145. package/dist/engine/feature-ast/extractors/shared.js +2 -3
  146. package/dist/engine/feature-ast/parse.js +13 -13
  147. package/dist/engine/feature-ast/patch.js +1 -1
  148. package/dist/engine/feature-ast/pattern-change-schema.d.ts +8 -0
  149. package/dist/engine/feature-ast/pattern-change-schema.js +1 -1
  150. package/dist/engine/feature-ast/patterns.d.ts +2 -2
  151. package/dist/engine/feature-ast/render.js +2 -2
  152. package/dist/engine/feature-config-events-jobs.js +15 -12
  153. package/dist/engine/feature-entity-handlers.js +9 -5
  154. package/dist/engine/feature-manifest.d.ts +9 -9
  155. package/dist/engine/feature-manifest.js +6 -7
  156. package/dist/engine/feature-ui-extensions.d.ts +3 -3
  157. package/dist/engine/feature-ui-extensions.js +30 -22
  158. package/dist/engine/field-access.js +5 -6
  159. package/dist/engine/index.d.ts +1 -0
  160. package/dist/engine/index.js +4 -4
  161. package/dist/engine/object-form.d.ts +1 -0
  162. package/dist/engine/object-form.js +14 -4
  163. package/dist/engine/ownership.js +3 -3
  164. package/dist/engine/parse-ref-target.js +5 -7
  165. package/dist/engine/pattern-library/index.js +2 -2
  166. package/dist/engine/pattern-library/library.js +2 -2
  167. package/dist/engine/pattern-library/types.js +3 -3
  168. package/dist/engine/pipeline.js +2 -2
  169. package/dist/engine/project-app-schema-for-roles.js +1 -1
  170. package/dist/engine/projection-helpers.js +9 -9
  171. package/dist/engine/qualified-name.js +2 -2
  172. package/dist/engine/registry-facade.js +1 -1
  173. package/dist/engine/registry-ingest.js +12 -12
  174. package/dist/engine/registry-state.js +17 -19
  175. package/dist/engine/registry-validate.js +12 -12
  176. package/dist/engine/run-in.js +2 -2
  177. package/dist/engine/run-pipeline.js +7 -0
  178. package/dist/engine/schema-builder.js +40 -38
  179. package/dist/engine/screen-filter-ops.js +17 -20
  180. package/dist/engine/screen-helpers.js +4 -3
  181. package/dist/engine/state-machine.d.ts +12 -12
  182. package/dist/engine/state-machine.js +7 -7
  183. package/dist/engine/steps/_resolver-utils.js +4 -3
  184. package/dist/engine/steps/for-each.js +4 -4
  185. package/dist/engine/steps/unsafe-projection-delete.js +1 -1
  186. package/dist/engine/steps/unsafe-projection-upsert.js +4 -4
  187. package/dist/engine/steps/wait-for-event.js +4 -4
  188. package/dist/engine/steps/workflow-system-events.js +3 -3
  189. package/dist/engine/tier-resolver-extension.d.ts +29 -27
  190. package/dist/engine/tier-resolver-extension.js +26 -26
  191. package/dist/engine/transfer-adjacency.js +2 -2
  192. package/dist/engine/validate-projection-allowlist.js +7 -27
  193. package/dist/engine/walk-step-instances.d.ts +2 -0
  194. package/dist/engine/walk-step-instances.js +38 -0
  195. package/dist/engine/where-rule-lint.js +1 -1
  196. package/dist/engine/write-handler-qn-extract.js +1 -1
  197. package/dist/entrypoint/index.js +3 -3
  198. package/dist/env/index.d.ts +7 -6
  199. package/dist/env/index.js +10 -9
  200. package/dist/errors/transition-details.js +7 -7
  201. package/dist/errors/write-error-info.d.ts +6 -6
  202. package/dist/errors/write-error-info.js +6 -6
  203. package/dist/es-ops/context.d.ts +9 -9
  204. package/dist/es-ops/context.js +28 -28
  205. package/dist/es-ops/index.js +7 -9
  206. package/dist/es-ops/operations-schema.js +4 -10
  207. package/dist/es-ops/runner.d.ts +11 -11
  208. package/dist/es-ops/runner.js +49 -53
  209. package/dist/es-ops/types.d.ts +55 -55
  210. package/dist/es-ops/types.js +11 -11
  211. package/dist/event-store/admin-api.js +6 -7
  212. package/dist/event-store/errors.js +1 -1
  213. package/dist/event-store/event-store.js +2 -2
  214. package/dist/event-store/rebuild-dead-letter.js +1 -1
  215. package/dist/files/feature.js +9 -10
  216. package/dist/files/file-handle.js +5 -6
  217. package/dist/files/file-ref-entity.js +15 -15
  218. package/dist/files/file-ref-table.js +7 -8
  219. package/dist/files/file-routes.d.ts +7 -0
  220. package/dist/files/file-routes.js +11 -5
  221. package/dist/files/in-memory-provider.js +8 -9
  222. package/dist/files/local-provider.js +18 -25
  223. package/dist/files/provider-resolver.d.ts +7 -7
  224. package/dist/files/storage-tracking.js +2 -2
  225. package/dist/files/zip-stream.d.ts +12 -19
  226. package/dist/files/zip-stream.js +44 -63
  227. package/dist/i18n/required-surface-keys.d.ts +1 -1
  228. package/dist/i18n/required-surface-keys.js +8 -8
  229. package/dist/internal/stack-support.d.ts +1 -1
  230. package/dist/internal/stack-support.js +1 -1
  231. package/dist/jobs/job-runner-helpers.d.ts +1 -2
  232. package/dist/jobs/job-runner-helpers.js +8 -18
  233. package/dist/jobs/job-runner-schedulers.d.ts +1 -1
  234. package/dist/jobs/job-runner-schedulers.js +3 -4
  235. package/dist/jobs/job-runner.js +17 -18
  236. package/dist/lifecycle/lifecycle.js +3 -5
  237. package/dist/lifecycle/signal-handlers.js +5 -4
  238. package/dist/logging/utils.d.ts +2 -1
  239. package/dist/logging/utils.js +38 -10
  240. package/dist/migrations/index.js +1 -1
  241. package/dist/migrations/kumiko-drift.js +14 -13
  242. package/dist/migrations/pending-rebuilds.d.ts +21 -20
  243. package/dist/migrations/pending-rebuilds.js +27 -27
  244. package/dist/migrations/projection-table-index.d.ts +3 -3
  245. package/dist/migrations/projection-table-index.js +8 -8
  246. package/dist/observability/metric-validator.js +1 -1
  247. package/dist/observability/metrics-handle.js +3 -3
  248. package/dist/pipeline/append-event-core.js +11 -2
  249. package/dist/pipeline/cascade-handler.d.ts +1 -1
  250. package/dist/pipeline/dispatch-batch.js +2 -2
  251. package/dist/pipeline/dispatch-query.js +10 -10
  252. package/dist/pipeline/dispatch-shared.d.ts +1 -0
  253. package/dist/pipeline/dispatch-shared.js +42 -19
  254. package/dist/pipeline/dispatch-stream.js +1 -1
  255. package/dist/pipeline/dispatch-write.js +3 -3
  256. package/dist/pipeline/dispatcher-utils.d.ts +1 -1
  257. package/dist/pipeline/dispatcher-utils.js +1 -1
  258. package/dist/pipeline/event-consumer-state.js +2 -2
  259. package/dist/pipeline/event-dispatcher-delivery.js +1 -1
  260. package/dist/pipeline/event-dispatcher.js +17 -28
  261. package/dist/pipeline/irreversible-operation-gate.js +1 -1
  262. package/dist/pipeline/lifecycle-pipeline.js +9 -9
  263. package/dist/pipeline/member-read-only-transaction.js +1 -3
  264. package/dist/pipeline/msp-rebuild.js +1 -1
  265. package/dist/pipeline/projection-rebuild.js +13 -12
  266. package/dist/pipeline/system-hooks.js +39 -41
  267. package/dist/pipeline/system-identity-switch.js +1 -3
  268. package/dist/pipeline/tenant-timezone-cache.js +1 -1
  269. package/dist/random/generate.d.ts +29 -29
  270. package/dist/random/generate.js +30 -30
  271. package/dist/random/words.js +20 -20
  272. package/dist/rate-limit/middleware.js +9 -5
  273. package/dist/redis/client.js +2 -2
  274. package/dist/redis/pubsub-signal.js +14 -17
  275. package/dist/schema-cli.js +116 -22
  276. package/dist/search/index.js +4 -4
  277. package/dist/search/purge-subject.js +4 -5
  278. package/dist/search/reindex-entity.js +6 -9
  279. package/dist/time/boot-tz-warning.d.ts +2 -2
  280. package/dist/time/boot-tz-warning.js +8 -8
  281. package/dist/time/iana.js +10 -10
  282. package/dist/time/index.js +9 -9
  283. package/dist/time/legacy-date.d.ts +2 -2
  284. package/dist/time/legacy-date.js +6 -8
  285. package/dist/time/polyfill.js +1 -1
  286. package/dist/time/tz-context.d.ts +1 -1
  287. package/dist/time/tz-context.js +1 -1
  288. package/dist/ui-types/app-schema.js +7 -7
  289. package/dist/ui-types/list-row-meta.js +4 -4
  290. package/dist/upgrade-cli.js +10 -9
  291. package/dist/utils/compare.js +1 -1
  292. package/package.json +4 -4
  293. package/src/changes.json +54 -0
  294. package/src/scripts/codemod/crypto-shredding-testing-move.ts +2 -2
  295. package/src/scripts/codemod/migrate-cross-tenant.ts +1 -1
  296. package/src/scripts/codemod/migrate-open-to-all.ts +1 -1
  297. package/src/scripts/codemod/pii-personal-migration.ts +17 -8
  298. package/src/scripts/codemod/unprocessable-error-details-reason.ts +2 -2
@@ -1,19 +1,19 @@
1
- // Self-Populating Settings-Hub (config-provisioning Phase 2).
1
+ // Self-Populating Settings Hub.
2
2
  //
3
- // Leitet aus den im Registry deklarierten Config-Keys automatisch die
4
- // Settings-UI ab: pro Audience (scope) einen Parent-Nav, pro (Feature ×
5
- // scope) einen configEdit-Screen + Child-Nav darunter. Kein manuelles
6
- // r.screen/r.nav am App-Author.
3
+ // Derives the Settings UI automatically from the config keys declared in the
4
+ // registry: one parent nav per audience (scope), and one configEdit screen plus
5
+ // child nav per (feature × scope). App authors do not need manual
6
+ // r.screen/r.nav declarations.
7
7
  //
8
- // Sichtbar wird nur ein Key MIT `mask` (siehe ConfigKeyDefinition): mask ist
9
- // die per-Key-Intent „user-facing Einstellung" und trägt zugleich das Label
10
- // (mask.title, ein i18n-Key). Keys ohne mask sind internes Plumbing
11
- // (ENV-provisioniert/computed) und erscheinen nicht.
8
+ // Only a key WITH `mask` is visible (see ConfigKeyDefinition): mask is the
9
+ // per-key intent for a “user-facing setting” and also supplies its label
10
+ // (mask.title, an i18n key). Keys without mask are internal plumbing
11
+ // (ENV-provisioned/computed) and are omitted.
12
12
  //
13
- // Die erzeugten Screens/Navs werden von buildAppSchema in die FeatureSchema
14
- // des config-Features (featureName "config") gemerged — der Renderer
15
- // qualifiziert die kurzen ids/refs mit "config". Daher hier durchweg KURZE
16
- // ids/parent/screen-Refs (buildNavRegistrySliceForApp qualifiziert selbst).
13
+ // buildAppSchema merges the generated screens/navs into the FeatureSchema of
14
+ // the config feature (featureName "config"). The renderer qualifies the short
15
+ // ids/refs with "config", so this code consistently uses SHORT ids/parent/screen
16
+ // refs (buildNavRegistrySliceForApp qualifies them itself).
17
17
  import { isOptionsQueryFieldRef } from "@cosmicdrift/kumiko-types/fields";
18
18
  import { CONFIG_EDIT_ENTITY, fieldLabelKey } from "../i18n/required-surface-keys.js";
19
19
  import { isEncryptedAtRest } from "./config-helpers.js";
@@ -22,14 +22,15 @@ import { createBooleanField, createNumberField, createSelectField, createTextFie
22
22
  import { isKebabSegment, toKebab } from "./qualified-name.js";
23
23
  import { isFieldsEditSection, sectionFieldSpecs, } from "./screen-helpers.js";
24
24
  import { isOpenToAllGranted } from "./types/handlers.js";
25
- // Namespace, unter dem buildAppSchema die generierten Screens/Navs einhängt
26
- // (find-or-create FeatureSchema). MUSS gleich CONFIG_FEATURE aus dem config
27
- // bundled-feature sein — framework kann das const nicht importieren (Richtung
28
- // bundled-features → framework), darum hier gepinnt + Pin-Test bundled-seitig.
25
+ // Namespace under which buildAppSchema attaches the generated screens/navs
26
+ // (find-or-create FeatureSchema). It MUST match CONFIG_FEATURE from the config
27
+ // bundled feature. The framework cannot import that constant because of the
28
+ // bundled-features → framework dependency direction, so it is pinned here and
29
+ // verified by a bundled-feature pin test.
29
30
  export const SETTINGS_HUB_FEATURE = "config";
30
- // Eigene Workspace nur für workspace-mode-Apps (siehe buildAppSchema): Settings
31
- // erscheinen als eigener Switcher-Eintrag, statt die kuratierten App-Workspaces
32
- // zu verschmutzen. Apps ohne Workspaces zeigen die Navs über den no-filter-Pfad.
31
+ // A dedicated workspace for workspace-mode apps (see buildAppSchema): Settings
32
+ // appear as their own switcher entry instead of cluttering the curated app
33
+ // workspaces. Apps without workspaces expose these navs through the no-filter path.
33
34
  export const SETTINGS_HUB_WORKSPACE = "settings";
34
35
  // Audience-Reihenfolge im Sidebar: Plattform vor Tenant vor Benutzer.
35
36
  const SCOPE_ORDER = { system: 10, tenant: 20, user: 30 };
@@ -54,21 +55,21 @@ const SECRETS_SECTION_DESCRIPTION_KEY = "config.secrets.description";
54
55
  const audienceNavShortId = (scope) => `audience-${scope}`;
55
56
  // Generated post-boot, never via r.nav() — the boot validator exempts exactly these QNs (an app references one to place the settings group inline).
56
57
  export const SETTINGS_HUB_AUDIENCE_NAV_QNS = SCOPES_BROAD_TO_DEEP.map((scope) => `${SETTINGS_HUB_FEATURE}:nav:${audienceNavShortId(scope)}`);
57
- // An einem Scope BREITER als der Home-Scope eines Keys darf nur eine für DIESE
58
- // Ebene privilegierte Rolle den (Cascade-)Default setzen — SystemAdmin auf
59
- // system, TenantAdmin/Admin auf tenant. Am Home-Scope gilt das volle write-Set
60
- // (unverändertes Verhalten). So liefert ein tenant-Home-Key wie SMTP zusätzlich
61
- // einen SystemAdmin-only Plattform-Screen; ein Key, dessen write-Set keine
62
- // dieser Rollen nennt, bekommt keinen breiteren Screen (write-Set = opt-in).
58
+ // At a scope broader than a key's home scope, only a role privileged for THAT
59
+ // level may set the cascade default—SystemAdmin at system, TenantAdmin/Admin at
60
+ // tenant. The home scope retains the full write set (unchanged behavior). Thus a
61
+ // tenant-home key such as SMTP also gets a SystemAdmin-only platform screen; a
62
+ // key whose write set names none of these roles gets no broader screen (the write
63
+ // set is the opt-in).
63
64
  const ELEVATED_ROLES = {
64
65
  system: ["SystemAdmin"],
65
66
  tenant: ["TenantAdmin", "Admin"],
66
67
  user: [],
67
68
  };
68
- // Der interne Maschinen-Akteur (access.system). Ein Key, den NUR diese Rolle
69
- // schreiben darf, ist provisioned-not-user-facing: er gehört nicht in den
70
- // menschlichen Hub (sonst rendert ein Feld, das der sichtbare Mensch nicht
71
- // speichern kann). `as const` für Literal-Verengung am Vergleich.
69
+ // The internal machine actor (access.system). A key writable ONLY by this role
70
+ // is provisioned-not-user-facing and must not enter the human hub; otherwise the
71
+ // UI would render a field that the visible user cannot save. `as const` narrows
72
+ // the literal for the comparison.
72
73
  const MACHINE_WRITE_ROLE = "system";
73
74
  const NO_SELECTOR_GATING = {
74
75
  pluginsByOwnerGroup: new Map(),
@@ -442,7 +443,7 @@ function buildScopeResult(scope, masked, secretsEnabled, secretsWriteHandler, de
442
443
  : NO_SELECTOR_GATING;
443
444
  const screens = [];
444
445
  const navs = [];
445
- // Audience-Parent: Gruppierungs-Knoten ohne Screen.
446
+ // Audience parent: grouping node without a screen.
446
447
  navs.push({
447
448
  id: audienceNavShortId(scope),
448
449
  label: `config.settings.${scope}`,
@@ -624,10 +625,10 @@ function effectiveWriteRoles(def, scope) {
624
625
  const elevated = ELEVATED_ROLES[scope];
625
626
  return def.access.write.filter((r) => elevated.includes(r));
626
627
  }
627
- // navMembers tragen die QUALIFIZIERTEN Nav-QNs (siehe build-app-schema.test:
628
- // admin.navMembers === ["orders:nav:list", ...]). Die generierten Navs leben
629
- // unter SETTINGS_HUB_FEATURE, also `config:nav:<shortId>`. Sortiert = stabile
630
- // Landing-Screen-Wahl (firstNavScreenId iteriert navMembers der Reihe nach).
628
+ // navMembers contain QUALIFIED nav QNs (see build-app-schema.test:
629
+ // admin.navMembers === ["orders:nav:list", ...]). The generated navs live under
630
+ // SETTINGS_HUB_FEATURE, namely `config:nav:<shortId>`. Sorting provides stable
631
+ // landing-screen selection (firstNavScreenId iterates navMembers in order).
631
632
  function buildSettingsWorkspace(navs) {
632
633
  const navMembers = navs.map((n) => `${SETTINGS_HUB_FEATURE}:nav:${n.id}`).sort();
633
634
  return {
@@ -636,11 +637,10 @@ function buildSettingsWorkspace(navs) {
636
637
  label: "config.settings.title",
637
638
  icon: "settings",
638
639
  order: 1000,
639
- // Union der Zugriffs-Regeln der bereits generierten (machine-gefilterten)
640
- // Hub-Navs — sonst sieht ein unprivilegierter User einen leeren
641
- // "Settings"-Switcher. Aus den Navs statt aus `masked`, damit die
642
- // machine-only "system"-Rolle (die in keinem Nav steht) nicht ins
643
- // Switcher-Gate leakt.
640
+ // Union of the access rules for the already generated (machine-filtered) Hub
641
+ // navs; otherwise an unprivileged user would see an empty "Settings"
642
+ // switcher. Use the navs rather than `masked` so the machine-only
643
+ // "system" role, which appears in no nav, cannot leak into the switcher gate.
644
644
  access: unionAccessRules(navs.map((n) => n.access)),
645
645
  },
646
646
  navMembers,
@@ -830,14 +830,14 @@ function maskOrder(k) {
830
830
  function minMaskOrder(keys) {
831
831
  return Math.min(...keys.map(maskOrder));
832
832
  }
833
- // Der Hub ist zum Editieren — wer mindestens einen Key der Gruppe SCHREIBEN
834
- // darf, sieht den Settings-Eintrag (write, nicht read). Das hält system-scope
835
- // (write default `["system"]`) human-hidden bis der Autor write: SystemAdmin
836
- // opt-int, und zeigt user-scope (write `all`) jedem. `all` lässt sich in
837
- // AccessRule nur als openToAll ausdrücken; der Write bleibt server-seitig
838
- // per Key gegated.
839
- // Union der Navs-Access-Regeln: ein openToAll-Nav öffnet das ganze Gate, sonst
840
- // die Vereinigung der Rollen. undefined-access-Navs tragen nichts bei.
833
+ // The hub is for editing: anyone allowed to WRITE at least one key in the group
834
+ // sees the Settings entry (write, not read). This keeps system-scope (write
835
+ // default `["system"]`) hidden from humans until the author opts in with
836
+ // write: SystemAdmin, while user-scope (write `all`) is shown to everyone.
837
+ // `all` can only be represented as openToAll in AccessRule; writes remain
838
+ // gated per key on the server.
839
+ // Access rules for navs are unioned: an openToAll nav opens the whole gate;
840
+ // otherwise roles are combined. Navs without access rules contribute nothing.
841
841
  function unionAccessRules(rules) {
842
842
  const roles = [];
843
843
  for (const rule of rules) {
@@ -852,8 +852,8 @@ function unionAccessRules(rules) {
852
852
  }
853
853
  return rolesToAccess(roles);
854
854
  }
855
- // `all` lässt sich in AccessRule nur als openToAll ausdrücken; der Write bleibt
856
- // server-seitig per Key gegated.
855
+ // `all` can only be represented as openToAll in AccessRule; writes remain gated
856
+ // per key on the server.
857
857
  function rolesToAccess(roles) {
858
858
  if (roles.includes("all"))
859
859
  return {
@@ -1,33 +1,30 @@
1
- // buildTarget — typed Builder für TargetRef.
1
+ // buildTarget — typed builder for TargetRef.
2
2
  //
3
- // **Was er macht.** Erzeugt einen TargetRef, dessen action + args
4
- // compile-time gegen die treeActions-Map des Ziel-Features validiert
5
- // sind. Magic-String-URIs werden nirgends hand-getippt; das ist die
6
- // Single-Source-of-Truth für Cross-Feature-Linking im Visual-Tree.
3
+ // **What it does.** Creates a TargetRef whose action and args are
4
+ // validated at compile time against the target feature's treeActions map.
5
+ // Magic-string URIs are never typed by hand; this is the single source
6
+ // of truth for cross-feature linking in the visual tree.
7
7
  //
8
- // **Wie er typed.** Function-Overloads splitten Actions ohne args
9
- // (NoArgsAction) und Actions mit args (WithArgsAction). TS validiert:
10
- // - action ist string-literal aus den Keys von treeActions
11
- // - bei WithArgsAction sind args required und matchen
8
+ // **How it is typed.** Function overloads separate actions without args
9
+ // (NoArgsAction) from actions with args (WithArgsAction). TypeScript validates:
10
+ // - action is a string literal from the keys of treeActions
11
+ // - for WithArgsAction, args is required and matches
12
12
  // treeActions[action]["args"]
13
- // - bei NoArgsAction ist args nicht erlaubt
13
+ // - for NoArgsAction, args is not allowed
14
14
  //
15
- // **Phase 0.** Das Feature-Handle ist hier eine generische Constraint
16
- // (FeatureWithTreeActions). In Schicht 2 wird treeActions ein
17
- // Pattern-Type im Feature-AST und defineFeature exposed eine
18
- // treeActions-Slot mit gleichem Shape — dann passen real Features
19
- // drauf, ohne Builder-Änderung.
20
- //
21
- // Siehe docs/plans/architecture/visual-tree.md A5.
22
- // TypeGuard für args: Overloads garantieren ein Plain-Object, aber die
23
- // Implementation-Signature sieht args als unknown (TArgs ist erased zur
24
- // Laufzeit). Der Guard validiert die Plain-Object-Convention runtime
25
- // statt einen `as`-Cast zu setzen — siehe coding-standards.md.
15
+ // **Feature handle.** It is currently a generic constraint
16
+ // (FeatureWithTreeActions). Once treeActions becomes a pattern type in the
17
+ // feature AST and defineFeature exposes a treeActions slot with the same
18
+ // shape, real features will fit without changing the builder.
19
+ // Type guard for args: overloads guarantee a plain object, but the
20
+ // implementation signature sees args as unknown (TArgs is erased at
21
+ // runtime). The guard validates the plain-object convention at runtime
22
+ // instead of using an `as` cast.
26
23
  function isArgsObject(v) {
27
24
  return typeof v === "object" && v !== null && !Array.isArray(v);
28
25
  }
29
- // Implementation — args ist `unknown` weil die Overloads-Generic-TArgs
30
- // zur Laufzeit erased ist. Plain-Object-Convention via TypeGuard.
26
+ // Implementation — args is `unknown` because the overloads' generic TArgs
27
+ // is erased at runtime. The plain-object convention is enforced via the type guard.
31
28
  export function buildTarget(spec) {
32
29
  if (!isArgsObject(spec.args)) {
33
30
  return Object.freeze({
@@ -1,9 +1,8 @@
1
- // biome-ignore-all lint/suspicious/noConsole: CLI tool — console.log is the output
2
1
  // biome-ignore-all lint/style/noNonNullAssertion: regex capture-group indexing — match-success implies non-null capture
3
2
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
4
3
  import { join, relative } from "node:path";
5
4
  import { Glob } from "bun";
6
- import { Project, SyntaxKind } from "ts-morph";
5
+ import { Node, Project, SyntaxKind } from "ts-morph";
7
6
  export function analyzeHandlerArrow(arrowText) {
8
7
  const text = arrowText.trim();
9
8
  let handlerBodyText;
@@ -53,24 +52,30 @@ export function analyzeHandlerArrow(arrowText) {
53
52
  l.startsWith("for ") ||
54
53
  l.startsWith("while ") ||
55
54
  l.startsWith("} else"));
56
- const createMatch = handlerBodyText.match(/const\s+(\w+)\s*=\s*await\s+(\w+)\.create\s*\(/);
55
+ const [, createVar, createExecutor] = handlerBodyText.match(/const\s+(\w+)\s*=\s*await\s+(\w+)\.create\s*\(/) ?? [];
57
56
  const updateMatch = handlerBodyText.match(/const\s+(\w+)\s*=\s*await\s+(\w+)\.update\s*\(/);
58
57
  const hasSimpleReturn = /return\s*\{[^}]*isSuccess:\s*true/.test(handlerBodyText);
59
- if (createMatch && hasSimpleReturn && !hasConditionalLogic) {
58
+ if (createVar !== undefined &&
59
+ createExecutor !== undefined &&
60
+ hasSimpleReturn &&
61
+ !hasConditionalLogic) {
60
62
  isSimpleExecutorCreate = true;
61
- executorName = createMatch[2];
62
- executorCreateVar = createMatch[1];
63
- executorCreateArgs = extractArgs(handlerBodyText, createMatch[2], "create");
63
+ executorName = createExecutor;
64
+ executorCreateVar = createVar;
65
+ executorCreateArgs = extractArgs(handlerBodyText, createExecutor, "create");
64
66
  }
65
- else if (createMatch && hasSimpleReturn && hasConditionalLogic) {
67
+ else if (createVar !== undefined &&
68
+ createExecutor !== undefined &&
69
+ hasSimpleReturn &&
70
+ hasConditionalLogic) {
66
71
  // Try to match the guard pattern: if (!<var>.isSuccess) { return fail } return success
67
- const guard = extractGuardedPattern(handlerBodyText, createMatch[1]);
72
+ const guard = extractGuardedPattern(handlerBodyText, createVar);
68
73
  if (guard) {
69
74
  isGuardedCreate = true;
70
75
  guardConfig = guard;
71
- executorName = createMatch[2];
72
- executorCreateVar = createMatch[1];
73
- executorCreateArgs = extractArgs(handlerBodyText, createMatch[2], "create");
76
+ executorName = createExecutor;
77
+ executorCreateVar = createVar;
78
+ executorCreateArgs = extractArgs(handlerBodyText, createExecutor, "create");
74
79
  }
75
80
  }
76
81
  if (updateMatch &&
@@ -312,6 +317,8 @@ function inferSchemaType(objLiteral) {
312
317
  const schemaProp = objLiteral.getProperty("schema");
313
318
  if (!schemaProp)
314
319
  return "unknown";
320
+ if (!Node.isPropertyAssignment(schemaProp))
321
+ return "unknown";
315
322
  const init = schemaProp.getInitializer();
316
323
  if (!init)
317
324
  return "unknown";
@@ -431,7 +438,7 @@ function ensurePipelineImport(content) {
431
438
  const importRegex = /import\s*\{([^}]*)\}\s*from\s*["']@cosmicdrift\/kumiko-framework\/engine["']/;
432
439
  const match = content.match(importRegex);
433
440
  if (match) {
434
- const existingImports = match[1].trim();
441
+ const existingImports = (match[1] ?? "").trim();
435
442
  const newImports = existingImports ? `${existingImports}, stepsPipeline` : "stepsPipeline";
436
443
  return content.replace(importRegex, `import { ${newImports} } from "@cosmicdrift/kumiko-framework/engine"`);
437
444
  }
@@ -10,9 +10,9 @@ export function isEncryptedAtRest(def) {
10
10
  // --- Access Presets ---
11
11
  export const access = {
12
12
  all: ["all"], // @cast-boundary schema-walk
13
- // TenantAdmin zusätzlich: bundled-features vergeben "TenantAdmin",
14
- // App-Repos historisch "Admin" — das Preset deckt beide ab, sonst
15
- // driftet die writeRole-Spalte der Feature-Reference (Manifest 243/2).
13
+ // Bundled features use "TenantAdmin", while app repositories historically use
14
+ // "Admin"; including both prevents the feature reference's writeRole from
15
+ // drifting.
16
16
  admin: ["TenantAdmin", "Admin", "SystemAdmin"], // @cast-boundary schema-walk
17
17
  systemAdmin: ["SystemAdmin"], // @cast-boundary schema-walk
18
18
  system: ["system"], // @cast-boundary schema-walk
@@ -25,7 +25,7 @@ export const access = {
25
25
  // also set via ctx.systemWriteAs (roles = [SYSTEM_ROLE]). Stays human-writable
26
26
  // because checkWriteAccess only collapses to system-only when system is the
27
27
  // SOLE writer (humanWriters empty). Composes any preset, so apps with custom
28
- // roles get the same provisioning path (issue #396).
28
+ // roles get the same provisioning path.
29
29
  withSystem: (roles) => [SYSTEM_ROLE, ...roles],
30
30
  };
31
31
  // --- Scope Defaults ---
@@ -31,7 +31,7 @@ export const LifecycleHookTypes = {
31
31
  // --- Config Scopes ---
32
32
  // Value object satisfies the canonical ConfigScope union from kumiko-types —
33
33
  // a scope added here without a matching kumiko-types member is now a
34
- // compile error instead of silent drift (#1423/#1439).
34
+ // compile error instead of silent drift.
35
35
  export const ConfigScopes = {
36
36
  system: "system",
37
37
  tenant: "tenant",
@@ -65,7 +65,7 @@ void _concurrencyModeExhaustive;
65
65
  // --- Money Currency Sources ---
66
66
  // Canonical definition lives in kumiko-types/fields (client-safe, next to
67
67
  // MoneyCurrencySource) so renderer code can import it without pulling in
68
- // runtime-only engine code (fw#2937). Re-exported here for server callers
68
+ // runtime-only engine code. Re-exported here for server callers
69
69
  // already importing framework/engine (bundled-features/src/tenant-settings/
70
70
  // constants.ts TenantSettingsConfig.currency reuses this same constant, so
71
71
  // the two stay in sync by construction).
@@ -11,7 +11,7 @@ function inlineFormFieldMaps(screen) {
11
11
  return [];
12
12
  return screen.confirm !== undefined ? [screen.fields, screen.confirm.fields] : [screen.fields];
13
13
  }
14
- // `currency: { kind: "literal", code }` (fw#2839) is only meaningful for a
14
+ // `currency: { kind: "literal", code }` is only meaningful for a
15
15
  // code the app knows — a typo would otherwise render and submit amounts in a
16
16
  // currency no formatter or rate table covers.
17
17
  function validateLiteralCurrencyCode(where, field, currencies) {
@@ -106,7 +106,7 @@ export function createApp(config) {
106
106
  }
107
107
  }
108
108
  // A money field on an entity-less form screen names its own currency
109
- // source (fw#2839) — a literal code has to be one the app actually knows,
109
+ // source — a literal code has to be one the app actually knows,
110
110
  // same rule the entity `defaultCurrency` check above applies.
111
111
  for (const [screenId, screen] of Object.entries(feature.screens ?? {})) {
112
112
  const inlineFields = inlineFormFieldMaps(screen);
@@ -52,13 +52,14 @@ export function defineFeature(name, setup, settings) {
52
52
  throwObjectArgumentRequired(name, "requires");
53
53
  state.requires.push(...resolveFeatureNamesArgs(args));
54
54
  };
55
- fn.projection = (tableName) => {
56
- state.requiredProjections.add(tableName);
57
- };
58
- fn.step = (stepKind) => {
59
- state.requiredSteps.add(stepKind);
60
- };
61
- return fn;
55
+ return Object.assign(fn, {
56
+ projection: (tableName) => {
57
+ state.requiredProjections.add(tableName);
58
+ },
59
+ step: (stepKind) => {
60
+ state.requiredSteps.add(stepKind);
61
+ },
62
+ });
62
63
  })(),
63
64
  optionalRequires(...args) {
64
65
  state.optionalRequires.push(...resolveFeatureNamesArgs(args));
@@ -106,10 +107,10 @@ export function defineFeature(name, setup, settings) {
106
107
  postDelete: state.phasedLifecycleHooks.postDelete,
107
108
  preQuery: state.lifecycleHooks["preQuery"] ?? {},
108
109
  postQuery: state.lifecycleHooks["postQuery"] ?? {},
109
- // @cast-boundary engine-bridge — die Hook-Registrierung erased die
110
- // per-Slot-Signaturen zu LifecycleHookFn (Union, s. Cast in
111
- // addLifecycleHook); die Branches dort sind die einzigen Producer und
112
- // schreiben pro Slot typrichtig.
110
+ // @cast-boundary engine-bridge — hook registration erases the per-slot
111
+ // signatures to LifecycleHookFn (a union; see the cast in
112
+ // addLifecycleHook). The branches there are the only producers and write
113
+ // the correct type for each slot.
113
114
  },
114
115
  entityHooks: {
115
116
  postSave: state.entityPostSave,
@@ -7,6 +7,10 @@ function pickPreTransactionPhases(def) {
7
7
  ...(def.beforeTransaction && { beforeTransaction: def.beforeTransaction }),
8
8
  };
9
9
  }
10
+ // `in` keeps both branches of the discriminated union readable regardless of narrowing.
11
+ function hasHandlerAndPerform(def) {
12
+ return ("handler" in def && def.handler !== undefined && "perform" in def && def.perform !== undefined);
13
+ }
10
14
  export function defineWriteHandler(def,
11
15
  // R6: a phantom rest-param. When the inferred response `TData` carries a
12
16
  // Secret<> anywhere, ContainsSecret<TData> is `true` and this resolves to a
@@ -32,13 +36,10 @@ export function defineWriteHandler(def,
32
36
  // The discriminated-union type-error
33
37
  // "Type 'PipelineDef<...>' is not assignable to type 'undefined'."
34
38
  // is functional but cryptic for less TS-experienced users; this throws
35
- // a name-and-explanation error message instead. Followup #3.
36
- // The cast is necessary because the discriminated union narrows
37
- // `handler` away once `perform` is present (and vice-versa) — at this
38
- // boundary we want to read both regardless of the narrowing.
39
- const probe = def;
40
- if (probe.handler !== undefined && probe.perform !== undefined) {
41
- throw new Error(`defineWriteHandler("${def.name}"): both \`handler\` and \`perform\` are set. ` +
39
+ // a name-and-explanation error message instead.
40
+ const handlerName = def.name;
41
+ if (hasHandlerAndPerform(def)) {
42
+ throw new Error(`defineWriteHandler("${handlerName}"): both \`handler\` and \`perform\` are set. ` +
42
43
  `Pick one — \`handler\` for the free-form async function, ` +
43
44
  `\`perform: stepsPipeline(...)\` for the step-pipeline form. ` +
44
45
  `(See step-vocabulary.md for which form fits.)`);
@@ -103,10 +104,10 @@ export function isEntityConventionQueryHandler(def) {
103
104
  }
104
105
  // A projectionList screen's query must resolve to { rows, nextCursor, total? }
105
106
  // (CursorResult<T>) — the renderer used to read rowsQuery.data?.rows and
106
- // silently show an empty list otherwise (fw#2216: session-list); the
107
- // renderer now guards against a malformed shape at runtime instead. Use this
108
- // instead of defineQueryHandler for any query wired to a projectionList
109
- // screen's `query` so the contract is documented at the definition site.
107
+ // silently show an empty list otherwise; the renderer now guards against a
108
+ // malformed shape at runtime instead. Use this instead of defineQueryHandler
109
+ // for any query wired to a projectionList screen's `query` so the contract is
110
+ // documented at the definition site.
110
111
  //
111
112
  // Deliberately does NOT merge cursor/limit/sort/search into the input
112
113
  // schema — that would change sortable/paginated derivation for existing
@@ -4,7 +4,7 @@
4
4
  // (run-pipeline.ts) looks them up by `kind` to dispatch run-calls.
5
5
  //
6
6
  // The registry is process-global; Tier-2 step opt-in via
7
- // `r.requires.step("…")` (Q9 in step-vocabulary.md) is a future pass.
7
+ // `r.requires.step("…")` is a future pass.
8
8
  const stepRegistry = new Map();
9
9
  export function defineStep(def) {
10
10
  const existing = stepRegistry.get(def.kind);
@@ -22,9 +22,9 @@ export type WorkflowDefinition<TPayload = unknown, TData = unknown, TAwaits exte
22
22
  readonly name: string;
23
23
  readonly trigger: WorkflowTrigger;
24
24
  /**
25
- * Declared expected events (D4, workflow-resume-loop.md) — the sole route
25
+ * Declared expected events (workflow-resume-loop.md) — the sole route
26
26
  * a waitForEvent step can reach an event type. Read at registration time
27
- * to build the static apply-map (Phase 3b); values are plain event-type
27
+ * to build the static apply-map; values are plain event-type
28
28
  * strings, not the branded AwaitedEventType the pipeline closure sees.
29
29
  */
30
30
  readonly awaits?: TAwaits;
@@ -70,17 +70,17 @@ export type WorkflowInput<TPayload = unknown, TData = unknown, TAwaits extends R
70
70
  */
71
71
  export declare function defineWorkflow<TPayload = unknown, TData = unknown, const TAwaits extends Record<string, string> = Record<string, string>>(input: WorkflowInput<TPayload, TData, TAwaits>): WorkflowDefinition<TPayload, TData, TAwaits>;
72
72
  /**
73
- * Q7 Snapshot-at-Start fingerprint. SHA-256 over the workflow's stable
73
+ * Snapshot-at-Start fingerprint. SHA-256 over the workflow's stable
74
74
  * identity (name + trigger + serialized pipeline-closure source). Persisted
75
75
  * in `workflow.run.started` and re-checked at every resume so a library-
76
76
  * upgrade that changes the closure source surfaces as a loud
77
77
  * `workflow-definition-changed` failure on in-flight runs instead of a
78
78
  * silent semantic drift.
79
79
  *
80
- * Limitations (will be tightened in M.5 with the Designer/AST layer):
80
+ * Limitations (will be tightened with the Designer/AST layer):
81
81
  * - `build.toString()` captures the closure source but not bindings —
82
82
  * two definitions that import different external helpers with the
83
- * same source bytes would collide. Acceptable for M.4 because the
83
+ * same source bytes would collide. Acceptable because the
84
84
  * fingerprint is a *change-detector*, not a deep semantic identity.
85
85
  * - Minifiers / source-maps will produce different fingerprints across
86
86
  * environments. Run-the-fingerprint-in-the-same-environment is the
@@ -1,7 +1,7 @@
1
1
  // defineWorkflow — define a persistent, suspendable workflow.
2
2
  // Tier-3 / Workflow-only mount-point. See step-vocabulary.md Sample 2 for
3
3
  // the full lifecycle (run.started → wait → resume → run.completed) and
4
- // Q7 (Snapshot-at-Start) for the in-flight upgrade story.
4
+ // Snapshot-at-Start for the in-flight upgrade story.
5
5
  import { createHash } from "node:crypto";
6
6
  /**
7
7
  * Define a suspendable workflow.
@@ -39,27 +39,27 @@ export function defineWorkflow(input) {
39
39
  };
40
40
  }
41
41
  /**
42
- * Q7 Snapshot-at-Start fingerprint. SHA-256 over the workflow's stable
42
+ * Snapshot-at-Start fingerprint. SHA-256 over the workflow's stable
43
43
  * identity (name + trigger + serialized pipeline-closure source). Persisted
44
44
  * in `workflow.run.started` and re-checked at every resume so a library-
45
45
  * upgrade that changes the closure source surfaces as a loud
46
46
  * `workflow-definition-changed` failure on in-flight runs instead of a
47
47
  * silent semantic drift.
48
48
  *
49
- * Limitations (will be tightened in M.5 with the Designer/AST layer):
49
+ * Limitations (will be tightened with the Designer/AST layer):
50
50
  * - `build.toString()` captures the closure source but not bindings —
51
51
  * two definitions that import different external helpers with the
52
- * same source bytes would collide. Acceptable for M.4 because the
52
+ * same source bytes would collide. Acceptable because the
53
53
  * fingerprint is a *change-detector*, not a deep semantic identity.
54
54
  * - Minifiers / source-maps will produce different fingerprints across
55
55
  * environments. Run-the-fingerprint-in-the-same-environment is the
56
56
  * contract; cross-env replay is out of scope.
57
57
  */
58
58
  export function computeDefinitionFingerprint(workflow) {
59
- // `awaits` is part of the routing contract (D4, workflow-resume-loop.md):
59
+ // `awaits` is part of the routing contract (workflow-resume-loop.md):
60
60
  // changing which events a run waits on changes the run's behavior just
61
61
  // like changing the step source does, so a changed declaration must flip
62
- // the fingerprint and trip the Q7 stale-definition check on resume.
62
+ // the fingerprint and trip the stale-definition check on resume.
63
63
  const material = JSON.stringify({
64
64
  name: workflow.name,
65
65
  trigger: workflow.trigger,
@@ -90,14 +90,14 @@ export const entityListSchema = z.object({
90
90
  .object({
91
91
  field: z.string(),
92
92
  op: z.enum(["eq", "ne", "lt", "gt", "lte", "gte", "in"]),
93
- // Value ist `unknown` zur Compile-Zeit; Server-Side prüft beim
94
- // Build der WHERE-Clause ob der Type zum Field passt. z.unknown()
95
- // lässt alles durch; Type-Check kommt im executor.list.
93
+ // `unknown` at compile time; the server checks while building the WHERE
94
+ // clause that the value's type matches the field. z.unknown() accepts
95
+ // everything, so the type check occurs in executor.list.
96
96
  value: z.unknown(),
97
97
  })
98
98
  .optional(),
99
- // User-gewählte Faceted-Filter (dynamisch). Additiv zum statischen
100
- // `filter` — executor.list verknüpft alle mit AND.
99
+ // User-selected faceted filters are dynamic and additive to the static
100
+ // `filter`; executor.list combines all of them with AND.
101
101
  filters: z
102
102
  .array(z.object({
103
103
  field: z.string(),
@@ -277,10 +277,10 @@ export function defineEntityWriteHandler(name, entity, options) {
277
277
  changes: dropEmptyWriteOnlyValues(entity, submitted.changes),
278
278
  };
279
279
  const streamTenantId = await foreignRowTenantFor(event.user, payload.id, db, ctx);
280
- // skipUnchanged (#464): API-driven updates diff against the stored
280
+ // skipUnchanged: API-driven updates diff against the stored
281
281
  // row so a resubmitted-but-identical field doesn't force a fresh
282
282
  // pii/encrypted ciphertext. Direct executor.update() callers (e.g.
283
- // KEK-rotation, the user-data-rights #494 backfill) don't go through
283
+ // KEK-rotation, the user-data-rights backfill) don't go through
284
284
  // this handler and keep today's always-re-encrypt behavior, which
285
285
  // they rely on to intentionally force a fresh event/ciphertext.
286
286
  return executor.update(payload, event.user, db, {
@@ -350,7 +350,7 @@ export function defineEntityQueryHandler(name, entity, options) {
350
350
  const executor = createEventStoreExecutor(table, entity, { entityName });
351
351
  let schema;
352
352
  let handler;
353
- // Tier 2.7e server-eagerload: when the entity has reference fields, the
353
+ // Server-side eager loading: when the entity has reference fields, the
354
354
  // handler resolves the UUIDs against the referenced entities after the
355
355
  // main query. The `_refs` property lands on every row; the renderer-side
356
356
  // useReferenceLookup stays as a fallback (for apps that write custom
@@ -384,17 +384,16 @@ export function defineEntityQueryHandler(name, entity, options) {
384
384
  case "list":
385
385
  schema = entityListSchema;
386
386
  handler = async (query, ctx) => {
387
- // Tier 2.7e Audit-Fix: SearchAdapter aus ctx durchreichen,
388
- // damit payload.search zur Laufzeit gegen Meilisearch/InMem
389
- // läuft (Remote-Combobox-Search). Der executor wird beim
390
- // Definition-Time gebaut, kennt den Adapter also nicht —
391
- // Runtime-Override holt das.
387
+ // Pass the SearchAdapter from ctx through so payload.search is executed
388
+ // against Meilisearch/InMem at runtime (remote combobox search). The
389
+ // executor is built at definition time and cannot know the adapter, so the
390
+ // runtime override supplies it.
392
391
  const listPayload = query.payload; // @cast-boundary engine-payload
393
392
  const db = dbFor(ctx);
394
393
  const result = await executor.list(listPayload, query.user, db, {
395
394
  ...(ctx.searchAdapter !== undefined && { searchAdapter: ctx.searchAdapter }),
396
395
  ...(ctx.includeDeleted === true && { includeDeleted: true }),
397
- // fw#2660 — registry access only exists at request time, so the
396
+ // Registry access only exists at request time, so the
398
397
  // searchable-reference cache + entity resolver travel through the
399
398
  // same runtime-override extension point as searchAdapter above.
400
399
  referenceSearch: {
@@ -2,7 +2,7 @@ import { normalizeEventPiiSubject } from "@cosmicdrift/kumiko-types/handlers";
2
2
  import { ZodObject } from "zod";
3
3
  import { qn, toKebab } from "./qualified-name.js";
4
4
  // A payload field that still holds null/undefined after parsing — the case
5
- // where an event-PII owner field yields no subject at append time (fw#2776).
5
+ // where an event-PII owner field yields no subject at append time.
6
6
  // A `.default(...)` accepts undefined but parses to a value, so it is not
7
7
  // absentable.
8
8
  function isAbsentable(field) {
@@ -90,7 +90,7 @@ function validateEventPiiFields(featureName, eventName, schema, piiFields) {
90
90
  */
91
91
  export function createEventDef(options) {
92
92
  const { feature, name: eventName, schema } = options;
93
- // Fail-closed PII stance gate (fw#2558) — runs before anything else so
93
+ // Fail-closed PII stance gate — runs before anything else so
94
94
  // an untyped JS consumer (no compiler to stop it) still can't register
95
95
  // an event without declaring whether its payload carries personal data.
96
96
  const missingStanceError = () => new Error(`[Feature ${feature}] defineEvent("${eventName}") must declare an explicit PII stance. Pass { piiFields: { <payloadField>: { personal: { of: "<userIdField>" } } } } for payload fields owned by a user (or { personal: "tenant" } / { personal: "self" } for tenant- or record-scoped subjects), or { piiFields: "none" } when the payload holds none.`);