@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
@@ -93,6 +93,7 @@ function assertNoPayloadRateLimitsWithoutResolver(registry) {
93
93
  }
94
94
  export function buildServer(options) {
95
95
  assertSeedModeNotInProduction();
96
+ const bootLog = createFallbackLogger("kumiko:boot", options.context.log);
96
97
  // Single effective hop-count for every IP-derived rate-limit + requestMeta
97
98
  // below — the top-level option wins over the deprecated
98
99
  // `auth.trustedProxyHops` fallback.
@@ -119,11 +120,11 @@ export function buildServer(options) {
119
120
  "mount `file-foundation` + a `file-provider-*` feature (inmemory/s3/s3-env) and " +
120
121
  "select one per tenant via the 'file-foundation:config:provider' config-key.");
121
122
  }
122
- // #2051 — a screen whose search box renders (kumiko-screen.tsx gates it on
123
+ // A screen whose search box renders (kumiko-screen.tsx gates it on
123
124
  // `screen.searchable ?? entity-has-searchable-field`, the same condition
124
125
  // used below) sends `payload.search` on every query. Without a
125
126
  // context.searchAdapter that throws UnprocessableError at request time
126
- // (#2032) — surface the misconfig at boot instead of on the first search.
127
+ // — surface the misconfig at boot instead of on the first search.
127
128
  // Warn, not throw: unlike missing file-storage (uploads always fail),
128
129
  // list screens still work without search: only the search box is broken.
129
130
  // Several deployed apps (offlot-app, publicstatus, kumiko-studio,
@@ -131,7 +132,7 @@ export function buildServer(options) {
131
132
  if (!options.context.searchAdapter) {
132
133
  const unwiredEntities = entitiesWithSearchableScreen(options.registry);
133
134
  if (unwiredEntities.length > 0) {
134
- console.warn(`[kumiko:boot] ${unwiredEntities.length} entit${unwiredEntities.length === 1 ? "y" : "ies"} ` +
135
+ bootLog.warn(`${unwiredEntities.length} entit${unwiredEntities.length === 1 ? "y" : "ies"} ` +
135
136
  `have a searchable list screen but no SearchAdapter is wired on context.searchAdapter: ` +
136
137
  `${unwiredEntities.join(", ")}. Search requests against ${unwiredEntities.length === 1 ? "it" : "them"} will fail with a 422 (search_adapter_not_wired) at runtime. ` +
137
138
  "Wire a SearchAdapter (e.g. createMeilisearchAdapter) on context.searchAdapter, or remove `searchable: true` from the affected fields.");
@@ -145,8 +146,8 @@ export function buildServer(options) {
145
146
  // An explicit ServerOptions.sseBroker always wins (caller owns its
146
147
  // lifecycle) — createDefaultSseBroker is the single decision point every
147
148
  // other caller must funnel through instead of picking a broker on its
148
- // own, so the REDIS_URL-gated cross-replica default (fw#2625) can't be
149
- // silently bypassed the way runProdApp bypassed it before this existed.
149
+ // own, so the REDIS_URL-gated cross-replica default can't be silently
150
+ // bypassed the way runProdApp bypassed it before this existed.
150
151
  let sseBroker;
151
152
  let ownedRedisSseBroker;
152
153
  if (options.sseBroker) {
@@ -157,7 +158,7 @@ export function buildServer(options) {
157
158
  sseBroker = defaults.sseBroker;
158
159
  ownedRedisSseBroker = defaults.ownedRedisSseBroker;
159
160
  if (!ownedRedisSseBroker) {
160
- console.warn("[kumiko:boot] REDIS_URL is not set: cross-replica fanout disabled. SSE, access-invalidation and toggle-sync reach only the cursor-owning process, so with more than one replica clients on the other replicas miss events and revoked sessions stay open there.");
161
+ bootLog.warn("REDIS_URL is not set: cross-replica fanout disabled. SSE, access-invalidation and toggle-sync reach only the cursor-owning process, so with more than one replica clients on the other replicas miss events and revoked sessions stay open there.");
161
162
  }
162
163
  }
163
164
  // Same decision as the SSE broker: an explicit dispatcherOptions value
@@ -176,7 +177,7 @@ export function buildServer(options) {
176
177
  `pick any other stable string.`);
177
178
  }
178
179
  // Warn when we fell back to a random UUID. The built-in SSE consumer is
179
- // delivery="shared" (one cursor for all pods, fw#2625), so it no longer
180
+ // delivery="shared" (one cursor for all pods), so it no longer
180
181
  // writes a row per instance. The id still labels this process in the
181
182
  // app.started event and in per-instance consumers (opt-in delivery), where a
182
183
  // fresh id per boot leaves an orphaned cursor-row that pins pruneEvents
@@ -185,7 +186,7 @@ export function buildServer(options) {
185
186
  // deliberate choices (the test suite sets KUMIKO_INSTANCE_ID="test-instance").
186
187
  const instanceIdWasRandom = options.instanceId === undefined && !process.env["KUMIKO_INSTANCE_ID"];
187
188
  if (instanceIdWasRandom) {
188
- console.warn(`[kumiko:boot] No ServerOptions.instanceId / KUMIKO_INSTANCE_ID set — generated a random UUID (${resolvedInstanceId}). ` +
189
+ bootLog.warn(`No ServerOptions.instanceId / KUMIKO_INSTANCE_ID set — generated a random UUID (${resolvedInstanceId}). ` +
189
190
  `SSE no longer needs a per-instance cursor, but the id labels this process in the app.started event and in per-instance consumers; without a stable id each restart leaves an orphaned cursor-row behind for those and pins events-retention on its last cursor. ` +
190
191
  `Set KUMIKO_INSTANCE_ID to the pod name (e.g. from the Kubernetes downward API metadata.name) in production.`);
191
192
  }
@@ -252,7 +253,7 @@ export function buildServer(options) {
252
253
  Object.keys(feature.writeHandlers).length + Object.keys(feature.queryHandlers).length > 0)
253
254
  .map((feature) => feature.name);
254
255
  if (unlimitedSystemScopeFeatures.length > 0) {
255
- console.warn(`[kumiko:boot] r.systemScope() features have handlers but no RateLimitResolver is configured, ` +
256
+ bootLog.warn(`r.systemScope() features have handlers but no RateLimitResolver is configured, ` +
256
257
  `so their default rate limit is not enforced: ${unlimitedSystemScopeFeatures.join(", ")}. ` +
257
258
  "Wire a Redis client on context.redis and declare rateLimit on at least one handler (or pass " +
258
259
  "context.rateLimit) to enable the resolver.");
@@ -331,7 +332,7 @@ export function buildServer(options) {
331
332
  if (jobTriggerConsumerEnabled && jobRunnerForTriggers) {
332
333
  systemConsumers.push(createJobTriggerEventConsumer(jobRunnerForTriggers, options.registry));
333
334
  }
334
- // Default ON (#1524 global-by-default). Tests that opt out of SSE via
335
+ // Default ON (global-by-default). Tests that opt out of SSE via
335
336
  // systemConsumers.accessInvalidation=false (test-stack mirrors sse off)
336
337
  // skip the row so retention prune suites are not blocked by a lagging
337
338
  // cursor=0 consumer they never drain.
@@ -345,7 +346,7 @@ export function buildServer(options) {
345
346
  // baseDb DbRunner; tenant consistency comes from the triggering event's
346
347
  // own tenantId, not a per-event TenantDb scope.
347
348
  //
348
- // Lane-filter (Welle 2.6.b): MSPs declare `runIn` to pin them to a
349
+ // Lane-filter: MSPs declare `runIn` to pin them to a
349
350
  // deploy-lane. An MSP with `runIn: "api"` won't be wired into the
350
351
  // worker-process dispatcher (and vice versa). `runIn: "both"` (or the
351
352
  // legacy undefined default of "worker") runs wherever a dispatcher is
@@ -484,7 +485,7 @@ export function buildServer(options) {
484
485
  registerMetricsRoute(app, observability.meter, options.metrics);
485
486
  }
486
487
  else if (process.env["NODE_ENV"] === "production") {
487
- console.warn("[kumiko:boot] /metrics is off: no `metrics` option, so nothing is measured or scraped. " +
488
+ bootLog.warn("/metrics is off: no `metrics` option, so nothing is measured or scraped. " +
488
489
  "Set PROMETHEUS_METRICS_TOKEN (min 32 chars) and spread resolveObservabilityWiring(token) into the server options.");
489
490
  }
490
491
  app.use("/api/*", requestIdMiddleware({
@@ -529,7 +530,7 @@ export function buildServer(options) {
529
530
  meter: observability.meter,
530
531
  }));
531
532
  // Tripwire: a kumiko-pii: ciphertext in a JSON response is always a bug
532
- // (raw read leaked past a decrypt) — dev fails loud, prod redacts (#820).
533
+ // (raw read leaked past a decrypt) — dev fails loud, prod redacts.
533
534
  app.use("/api/*", piiCiphertextResponseGuard());
534
535
  // Auth middleware skips public paths (login, health) — those routes need
535
536
  // to be callable without a valid JWT. Every other /api/* request requires
@@ -684,7 +685,7 @@ export function buildServer(options) {
684
685
  // also gets rejected below — the schema never reaches an unauthenticated
685
686
  // caller. The FULL schema is built lazily once — the registry is
686
687
  // immutable for this server's lifetime, so it's the same on every
687
- // request — then projected per caller-role-set (fw#3314: a role only
688
+ // request — then projected per caller-role-set (a role only
688
689
  // sees the screens/navs/workspaces/content-collections it may reach,
689
690
  // not the whole app's schema) and cached per role key. Tenant is
690
691
  // deliberately NOT part of the key: the projection depends only on
@@ -756,16 +757,15 @@ export function buildServer(options) {
756
757
  ...(routeOptions.maxUploadSize ? { maxUploadSize: routeOptions.maxUploadSize } : {}),
757
758
  }));
758
759
  }
759
- // Feature-deklarierte HTTP-Routes (r.httpRoute). Mount nach /api/* damit
760
- // /api/* immer Vorrang hat — feature-Routes liegen ohnehin außerhalb
761
- // (Boot-Validator blockt /api-Prefix). deps.app ist die Outer-App, sodass
762
- // der Handler /api/query intern via app.fetch(...) nutzen kann (gleicher
763
- // Auth-Pfad wie ein echter HTTP-Call). deps.systemQuery umgeht diesen
764
- // Pfad bewusst — für Routes die einen FESTEN Tenant erzwingen müssen
765
- // (z.B. legal-pages → immer SYSTEM_TENANT_ID), statt ihn per X-Tenant-
766
- // Header vorzutäuschen (das sieht für die anonymousAccess-Auflösung
767
- // wie ein normaler Client-Header aus und wird von resolverTrust:
768
- // "authoritative"-Configs zurecht als Tenant-Override abgelehnt).
760
+ // Feature-declared HTTP routes (r.httpRoute). Mount after /api/* so /api/* always
761
+ // takes precedence; feature routes must be outside that prefix (the boot validator
762
+ // enforces this). deps.app is the outer app, so the handler can call /api/query
763
+ // internally through app.fetch(...) and use the same auth path as a real HTTP call.
764
+ // deps.systemQuery deliberately bypasses that path for routes that must enforce a
765
+ // FIXED tenant (for example, legal-pages always uses SYSTEM_TENANT_ID), rather than
766
+ // spoofing it through the X-Tenant header. That header looks like a normal client
767
+ // header to anonymousAccess resolution, so resolverTrust: "authoritative" configs
768
+ // correctly reject it as a tenant override.
769
769
  for (const feature of options.registry.features.values()) {
770
770
  for (const route of Object.values(feature.httpRoutes)) {
771
771
  // @wrapper-known semantic-alias
@@ -831,8 +831,8 @@ export function buildServer(options) {
831
831
  }));
832
832
  }
833
833
  }
834
- // extraRoutes (kumiko-framework#3050) — declarative HTTP-routes with a
835
- // Pflicht `entry` tier. Mounted after r.httpRoute for the same reason: an
834
+ // extraRoutes — declarative HTTP-routes with a
835
+ // required `entry` tier. Mounted after r.httpRoute for the same reason: an
836
836
  // extraRoute dispatching through `dispatcher` builds Hono's matcher, so
837
837
  // this must run before any seed that also dispatches (runProdApp/
838
838
  // createKumikoServer call buildServer before seeding).
@@ -886,17 +886,18 @@ export function buildServer(options) {
886
886
  mountHonoRoute(app, route.method, route.path, honoHandler, bodyLimitForRoutePath(route.path, options.maxRequestBytes ?? DEFAULT_MAX_REQUEST_BYTES));
887
887
  }
888
888
  }
889
- // /version-Default registriert NACH feature-routes — Hono "first match
890
- // wins", also gewinnt feature-deklariertes /version (z.B. App-spezifisches
891
- // version-format mit Tenant-Stats) wenn vorhanden, sonst greift der
892
- // Default-Handler aus BUILD_VERSION/BUILD_TIME-env-vars.
889
+ // The default /version route is registered AFTER feature routes — Hono uses
890
+ // "first match wins", so a feature-declared /version route (for example, an
891
+ // app-specific version format with tenant statistics) takes precedence when
892
+ // present; otherwise the default handler uses BUILD_VERSION/BUILD_TIME environment
893
+ // variables.
893
894
  registerVersionRoute(app);
894
895
  // Marks "no route matched this path" so tryHonoFirst (server-runtime)
895
896
  // can tell it apart from a matched route's OWN deliberate 404 (e.g.
896
897
  // default-deny reads like file-derivatives' public-variant route) —
897
898
  // without this, tryHonoFirst's only signal was the status code, and a
898
899
  // matched route answering 404 got silently rewritten to the SPA shell
899
- // with status 200 (kumiko-framework#2435). Must be the LAST thing wired
900
+ // with status 200. Must be the LAST thing wired
900
901
  // on `app`: this only fires when the router found no handler at all, so
901
902
  // a route's own `c.text(..., 404)` never passes through here. A handler
902
903
  // that calls `c.notFound()` instead of building its own Response DOES
@@ -1234,8 +1235,8 @@ function registryDeclaresFileFields(registry) {
1234
1235
  // === false` is excluded — the boot-validator (entity-list-screens.ts) only
1235
1236
  // allows that on the whitelisted download-attempt-list-style screens, which
1236
1237
  // never render the box. This is the config-only half of the rule; the
1237
- // client additionally gates on FeatureSchema.searchAdapterMissing (#2062,
1238
- // set from the same `!options.context.searchAdapter` check below) so the
1238
+ // client additionally gates on FeatureSchema.searchAdapterMissing
1239
+ // (set from the same `!options.context.searchAdapter` check below) so the
1239
1240
  // box is actually suppressed once this function finds a hit.
1240
1241
  function entitiesWithSearchableScreen(registry) {
1241
1242
  const entities = new Set();
@@ -37,7 +37,7 @@ export function shouldInvalidateListener(credential, scope) {
37
37
  }
38
38
  export function createSseBroker() {
39
39
  // Purely local: no cross-replica fanout. buildServer wraps this in
40
- // createRedisSseBroker (fw#2625) whenever REDIS_URL is set, which is what
40
+ // createRedisSseBroker whenever REDIS_URL is set, which is what
41
41
  // makes pushToChannel/publishAccessInvalidation reach every replica's
42
42
  // clients — this reference implementation stays single-process only.
43
43
  const channels = new Map();
@@ -2,24 +2,24 @@ import { Hono } from "hono";
2
2
  import type { AccessRule } from "../engine/types/index.ts";
3
3
  import { type SseBroker } from "./sse-broker.ts";
4
4
  /**
5
- * Heartbeat-Cadence für SSE-Streams.
5
+ * Heartbeat cadence for SSE streams.
6
6
  *
7
- * Spec: muss UNTER jedem realistischen Idle-Timeout der Hop-by-Hop-Layer
8
- * liegen, sonst killt einer davon die Connection und der Browser sieht
9
- * ERR_HTTP2_PROTOCOL_ERROR. Bekannte Limits:
10
- * - Bun.serve default: 10 s (lokal disabled via idleTimeout: 0,
11
- * aber Spec-konform auch ohne Override)
12
- * - Caddy reverse_proxy: kein default-Timeout für SSE (auto-detect
13
- * via Content-Type), aber langlebige idle Streams können von
14
- * Connection-Tracking dichtgemacht werden
7
+ * Must stay below every realistic idle timeout in the hop-by-hop layers;
8
+ * otherwise one of them terminates the connection and the browser reports
9
+ * ERR_HTTP2_PROTOCOL_ERROR. Known limits:
10
+ * - Bun.serve default: 10 s (disabled locally via idleTimeout: 0,
11
+ * but the cadence also conforms without that override)
12
+ * - Caddy reverse_proxy: no default timeout for SSE (auto-detects
13
+ * via Content-Type), but long-lived idle streams may be closed by
14
+ * connection tracking
15
15
  * - Cloudflare Edge: 100 s
16
16
  * - AWS ALB: 60 s
17
17
  *
18
- * 15 s liegt komfortabel unter allen davon. Server-side Cost ist
19
- * marginal (1 Frame pro Client alle 15 s).
18
+ * 15 s is comfortably below all of these. The server-side cost is marginal
19
+ * (1 frame per client every 15 s).
20
20
  *
21
- * Spec-Test in __tests__/sse-route-spec.test.ts pinst diesen Wert
22
- * gegen versehentliches Hochsetzen.
21
+ * The spec test in __tests__/sse-route-spec.test.ts pins this value to
22
+ * prevent accidentally increasing it.
23
23
  */
24
24
  export declare const SSE_HEARTBEAT_INTERVAL_MS = 15000;
25
25
  export declare const SSE_STALLED_CLIENT_TIMEOUT_MS = 15000;
@@ -9,24 +9,24 @@ import { clearSessionCookies, getSessionCookies } from "./session-cookies.js";
9
9
  import { accessInvalidationCredentialFor } from "./sse-broker.js";
10
10
  import { scheduleTokenExpiry } from "./token-expiry-timer.js";
11
11
  /**
12
- * Heartbeat-Cadence für SSE-Streams.
12
+ * Heartbeat cadence for SSE streams.
13
13
  *
14
- * Spec: muss UNTER jedem realistischen Idle-Timeout der Hop-by-Hop-Layer
15
- * liegen, sonst killt einer davon die Connection und der Browser sieht
16
- * ERR_HTTP2_PROTOCOL_ERROR. Bekannte Limits:
17
- * - Bun.serve default: 10 s (lokal disabled via idleTimeout: 0,
18
- * aber Spec-konform auch ohne Override)
19
- * - Caddy reverse_proxy: kein default-Timeout für SSE (auto-detect
20
- * via Content-Type), aber langlebige idle Streams können von
21
- * Connection-Tracking dichtgemacht werden
14
+ * Must stay below every realistic idle timeout in the hop-by-hop layers;
15
+ * otherwise one of them terminates the connection and the browser reports
16
+ * ERR_HTTP2_PROTOCOL_ERROR. Known limits:
17
+ * - Bun.serve default: 10 s (disabled locally via idleTimeout: 0,
18
+ * but the cadence also conforms without that override)
19
+ * - Caddy reverse_proxy: no default timeout for SSE (auto-detects
20
+ * via Content-Type), but long-lived idle streams may be closed by
21
+ * connection tracking
22
22
  * - Cloudflare Edge: 100 s
23
23
  * - AWS ALB: 60 s
24
24
  *
25
- * 15 s liegt komfortabel unter allen davon. Server-side Cost ist
26
- * marginal (1 Frame pro Client alle 15 s).
25
+ * 15 s is comfortably below all of these. The server-side cost is marginal
26
+ * (1 frame per client every 15 s).
27
27
  *
28
- * Spec-Test in __tests__/sse-route-spec.test.ts pinst diesen Wert
29
- * gegen versehentliches Hochsetzen.
28
+ * The spec test in __tests__/sse-route-spec.test.ts pins this value to
29
+ * prevent accidentally increasing it.
30
30
  */
31
31
  export const SSE_HEARTBEAT_INTERVAL_MS = 15_000;
32
32
  // Entity events carry aggregateType in data (system-hooks.ts's SSE-broadcast
@@ -184,8 +184,8 @@ export function createSseRoute(broker, options) {
184
184
  unsubscribeAccessInvalidation();
185
185
  };
186
186
  stream.onAbort(release);
187
- // Keep connection alive with heartbeat — siehe SSE_HEARTBEAT_INTERVAL_MS
188
- // header für die Layer-für-Layer-Begründung der 15s-Cadence.
187
+ // Keep the connection alive with a heartbeat — see SSE_HEARTBEAT_INTERVAL_MS
188
+ // for the layer-by-layer rationale for the 15 s cadence.
189
189
  try {
190
190
  while (!stream.closed && !stream.aborted) {
191
191
  if (isStalled()) {
@@ -1,5 +1,5 @@
1
- // `@cosmicdrift/kumiko-framework/auth` — Auth-Foundation (Roles, Token-
2
- // Helpers, Session-Types). Wird von Bundled-Features (auth-email-password,
3
- // tenant, user) und Datenschutz-Sprints (S1+ user-data-rights, S5 tenant-
4
- // lifecycle, S6 legal-hold) genutzt.
1
+ // `@cosmicdrift/kumiko-framework/auth` — Auth foundation (roles, token-
2
+ // helpers, session types). Used by bundled features (auth-email-password,
3
+ // tenant, user) and data-protection features (user-data-rights, tenant-
4
+ // lifecycle, legal-hold).
5
5
  export { ROLES } from "./roles.js";
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Plattform-weit standardisierte Role-Namen. Alle Datenschutz-Sprints
3
- * (1+) nutzen ausschliesslich diese Constants statt String-Literale.
2
+ * Platform-wide standardized role names. All privacy-related code uses
3
+ * these constants exclusively instead of string literals.
4
4
  */
5
5
  export declare const ROLES: {
6
6
  readonly TenantOwner: "TenantOwner";
@@ -10,7 +10,7 @@ export declare const ROLES: {
10
10
  readonly Member: "Member";
11
11
  };
12
12
  /**
13
- * Type-Union aller bekannten Role-Namen (Compile-Time-Check fuer
14
- * Handler-Access-Rules + Ownership-Maps).
13
+ * Union of all known role names for compile-time checks of
14
+ * handler access rules and ownership maps.
15
15
  */
16
16
  export type Role = (typeof ROLES)[keyof typeof ROLES];
@@ -1,30 +1,30 @@
1
- // Zentrale Role-Konstanten der Plattform.
1
+ // Central role constants for the platform.
2
2
  //
3
- // Hintergrund: Bundled-Features driften zwischen "Admin" (text-content,
4
- // secrets, ai-foundation, file-provider-s3) und "TenantAdmin" (tenant-
5
- // handler, publicstatus, platform). App-Builder fallen in diese Falle.
6
- // Diese Datei ist die Single Source of Truth — Bundled-Features
7
- // migrieren schrittweise auf die ROLES-Constants in den Sprint-
8
- // Touchpoints, an denen sie ohnehin angefasst werden.
3
+ // Background: Bundled features have drifted between "Admin" (text-content,
4
+ // secrets, ai-foundation, file-provider-s3) and "TenantAdmin" (tenant-
5
+ // handler, publicstatus, platform), which can trap app builders.
6
+ // This file is the single source of truth. Bundled features are migrated
7
+ // gradually to the ROLES constants at touchpoints where they are already
8
+ // being changed.
9
9
  //
10
- // Canonical-Names:
11
- // TenantOwner — Volle Tenant-Hoheit, einzige Rolle die
12
- // Tenant-Destroy triggern darf.
13
- // TenantAdmin — Tenant-Konfiguration + User-Management.
14
- // Bestehender String "Admin" wird hierauf
15
- // migriert.
16
- // DataProtectionOfficer — DPO; setzt Legal-Holds, sieht Authority-
17
- // Audit-Stream auch im silentMode (Sprint 6).
18
- // SystemAdmin — Plattform-Operator (NICHT Tenant-scoped).
19
- // Authority-Export, KMS-Recovery, Cross-
20
- // Tenant-Setup. Matched den bestehenden
21
- // "SystemAdmin"-String in text-content,
22
- // secrets, auth-email-password, etc.
23
- // Member — Standard-Mitglied eines Tenants ohne
24
- // Admin-Rechte.
10
+ // Canonical names:
11
+ // TenantOwner — Full tenant authority; the only role allowed
12
+ // to trigger tenant destruction.
13
+ // TenantAdmin — Tenant configuration and user management.
14
+ // The existing "Admin" string is migrated
15
+ // to this role.
16
+ // DataProtectionOfficer — DPO; sets legal holds and can view the
17
+ // authority audit stream even in silentMode.
18
+ // SystemAdmin — Platform operator (NOT tenant-scoped).
19
+ // Authority export, KMS recovery, cross-tenant
20
+ // setup. Matches the existing "SystemAdmin"
21
+ // string in text-content, secrets,
22
+ // auth-email-password, etc.
23
+ // Member — Standard tenant member without
24
+ // administrative rights.
25
25
  /**
26
- * Plattform-weit standardisierte Role-Namen. Alle Datenschutz-Sprints
27
- * (1+) nutzen ausschliesslich diese Constants statt String-Literale.
26
+ * Platform-wide standardized role names. All privacy-related code uses
27
+ * these constants exclusively instead of string literals.
28
28
  */
29
29
  export const ROLES = {
30
30
  TenantOwner: "TenantOwner",
@@ -1,35 +1,33 @@
1
- // DurationSpec ↔ SQL-Interval ↔ Temporal.Duration converter.
1
+ // Converts between DurationSpec, SQL intervals, and Temporal.Duration.
2
2
  //
3
- // Hintergrund: ComplianceProfile.userRights.gracePeriod ist
4
- // `{ days: number } | { hours: number }` (Discriminated Union). Caller
5
- // die das in eine Postgres-`interval`-SQL einsetzen brauchen einen
6
- // einzigen vertrauenswuerdigen Punkt, sonst springt ein
7
- // `{ hours: 6 }`-Override stillschweigend auf einen days-Default.
3
+ // `ComplianceProfile.userRights.gracePeriod` is
4
+ // `{ days: number } | { hours: number }` (a discriminated union). Callers
5
+ // inserting it into a Postgres `interval` need one trusted conversion point;
6
+ // otherwise an `{ hours: 6 }` override could silently use a days default.
8
7
  //
9
- // Single source of truth: hier. Andere Spec-Forms (months/years) im
10
- // retention-Pfad gehen ueber den breiteren `keep-for`-Parser, sind hier
11
- // bewusst nicht abgedeckt — das engt den Type ein und macht die SQL-
12
- // Renderung total.
8
+ // This is the single source of truth. Other spec forms (months/years) in the
9
+ // retention path use the broader `keep-for` parser and are deliberately not
10
+ // supported here, keeping the type narrow and SQL rendering total.
13
11
  import { getTemporal } from "../time/index.js";
14
- // Spec in Millisekunden. Source of truth fuer alle Konvertierungen
15
- // (Instant-add, optional fuer JS-Datumsrechnung).
12
+ // Millisecond representation of the spec. All conversions use this source
13
+ // of truth, including Instant addition and optional JavaScript date handling.
16
14
  export function durationSpecToMs(spec) {
17
15
  if ("days" in spec)
18
16
  return spec.days * 24 * 60 * 60 * 1000;
19
17
  return spec.hours * 60 * 60 * 1000;
20
18
  }
21
- // Frist berechnen ohne DB-now() — der App-Server-Clock ist
22
- // authoritative. Fuer Forget-Grace, Token-TTLs und Frist-Setzungen
23
- // where eine Toleranz von wenigen ms zwischen App und DB irrelevant
24
- // ist (Grace-Periods >= 6h, Tokens >= Minuten).
19
+ // Computes the deadline without DB-now(); the app server clock is
20
+ // authoritative. This is used for Forget-Grace, token TTLs, and deadline
21
+ // assignments where a difference of a few milliseconds between the app and
22
+ // database is irrelevant (grace periods >= 6h, tokens >= minutes).
25
23
  //
26
- // Schreibt direkt in `instant()`-customType-Spalten — kein interval-
27
- // SQL-Fragment, kein Codec-Bypass.
24
+ // Writes directly to `instant()` custom-type columns—no interval SQL
25
+ // fragment or codec bypass.
28
26
  export function addDurationSpec(now, spec) {
29
27
  return getTemporal().Instant.fromEpochMilliseconds(now.epochMilliseconds + durationSpecToMs(spec));
30
28
  }
31
- // Lesbare Beschreibung fuer Logs / Error-Messages. Nicht i18n —
32
- // English-only-Operator-Output.
29
+ // Human-readable descriptions for logs and error messages. Not localized;
30
+ // operator output is English-only.
33
31
  export function describeDurationSpec(spec) {
34
32
  if ("days" in spec)
35
33
  return `${spec.days} day${spec.days === 1 ? "" : "s"}`;
@@ -1,6 +1,5 @@
1
- // `@cosmicdrift/kumiko-framework/compliance` — Datenschutz/Compliance-
2
- // Foundation. Wird von Sprint-1+ Features genutzt (compliance-profiles,
3
- // data-retention, user-data-rights, ...).
1
+ // `@cosmicdrift/kumiko-framework/compliance` — Data protection and compliance foundation.
2
+ // Used by compliance-profiles, data-retention, user-data-rights, and related features.
4
3
  export { addDurationSpec, describeDurationSpec, durationSpecToMs, subtractRetentionSpec, } from "./duration-spec.js";
5
4
  export { complianceProfileOverrideSchema } from "./override-schema.js";
6
5
  export { COMPLIANCE_PROFILES, OVERRIDABLE_PROFILE_KEYS, resolveComplianceProfile, SELECTABLE_PROFILE_KEYS, } from "./profiles.js";
@@ -1,9 +1,9 @@
1
1
  import * as z from "zod";
2
2
  /**
3
- * Komplett-Schema fuer ComplianceProfileOverride. Alle Top-Level- UND
4
- * Sub-Level-Keys sind gewhitelisted via .strict() — Tippfehler werfen
5
- * sofort. Set-profile-Handler (Sprint 1.9 Z3) validiert das Override
6
- * gegen dieses Schema vor dem Persist.
3
+ * Complete schema for ComplianceProfileOverride. All top-level and
4
+ * sub-level keys are whitelisted with `.strict()`, so typos throw
5
+ * immediately. The set-profile handler validates the override against
6
+ * this schema before persisting it.
7
7
  */
8
8
  export declare const complianceProfileOverrideSchema: z.ZodObject<{
9
9
  userRights: z.ZodOptional<z.ZodObject<{
@@ -1,21 +1,18 @@
1
- // Zod-Schema fuer ComplianceProfileOverride mit .strict() rekursiv —
2
- // Sprint 1.9 Z3.
1
+ // Zod schema for ComplianceProfileOverride with recursive `.strict()` validation.
3
2
  //
4
- // Vorher: set-profile.write.ts pruefte nur Top-Level-Keys gegen
5
- // OVERRIDABLE_PROFILE_KEYS. Sub-Level-Tippfehler (z.B.
6
- // `{ userRights: { weeks: 3 } }` statt `{ userRights: { gracePeriod: { days: 30 } } }`)
7
- // kamen durch — der deepMerge spliced das nonsense ins effektive
8
- // Profile, und ein Caller der `userRights.gracePeriod.days` liest
9
- // crashed mit `undefined`.
3
+ // Previously, set-profile.write.ts checked only top-level keys against
4
+ // OVERRIDABLE_PROFILE_KEYS. Sub-level typos (for example,
5
+ // `{ userRights: { weeks: 3 } }` instead of
6
+ // `{ userRights: { gracePeriod: { days: 30 } } }`) passed through —
7
+ // deepMerge spliced the nonsense into the effective profile, and a caller
8
+ // reading `userRights.gracePeriod.days` crashed with `undefined`.
10
9
  //
11
- // Lösung: Zod-Schema das die ComplianceProfile-Struktur in DeepPartial-
12
- // Form abbildet, mit .strict() auf jedem Object damit unbekannte Keys
13
- // werfen. Single source of truth wie OVERRIDABLE_PROFILE_KEYS, plus
14
- // Sub-Level-Coverage.
10
+ // The schema maps the ComplianceProfile structure to DeepPartial form and
11
+ // uses `.strict()` on every object so unknown keys throw. It is a single
12
+ // source of truth like OVERRIDABLE_PROFILE_KEYS, with sub-level coverage.
15
13
  //
16
- // Identifikations-Felder (key, region, label, extends) sind NICHT im
17
- // Schema — wer die overriden will, würde die Profile-Identitaet
18
- // zerstören.
14
+ // Identification fields (`key`, `region`, `label`, `extends`) are excluded
15
+ // from the schema because overriding them would destroy profile identity.
19
16
  import * as z from "zod";
20
17
  // DurationSpec: { days } | { hours } — strict bedeutet beide Forms
21
18
  // muessen exakt 1 property haben (kein "{ days: 30, hours: 1 }").
@@ -23,8 +20,8 @@ const durationSpecSchema = z.union([
23
20
  z.object({ days: z.number().int().nonnegative() }).strict(),
24
21
  z.object({ hours: z.number().int().nonnegative() }).strict(),
25
22
  ]);
26
- // retention.* erlaubt zusaetzlich "months" und "years". Wieder eine
27
- // strikte Disjunktion.
23
+ // retention.* additionally allows `"months"` and `"years"`; this remains
24
+ // a strict disjunction.
28
25
  const auditRetentionSchema = z.union([
29
26
  durationSpecSchema,
30
27
  z.object({ months: z.number().int().nonnegative() }).strict(),
@@ -54,7 +51,7 @@ const userRightsOverrideSchema = z
54
51
  employeeAccessRight: z.boolean().optional(),
55
52
  explicitConsentForAutomatedDecision: z.boolean().optional(),
56
53
  doNotSellRequired: z.boolean().optional(),
57
- // Async-Export-Pipeline (S2.U3+U4) — TTL Compliance-relevant,
54
+ // Async-Export-Pipeline — TTL Compliance-relevant,
58
55
  // Stale/Cleanup Operations-Settings.
59
56
  exportDownloadTtl: durationSpecSchema.optional(),
60
57
  exportStaleTimeoutMinutes: z.number().int().nonnegative().optional(),
@@ -106,10 +103,10 @@ const forgetDiscoveryOverrideSchema = z
106
103
  })
107
104
  .strict();
108
105
  /**
109
- * Komplett-Schema fuer ComplianceProfileOverride. Alle Top-Level- UND
110
- * Sub-Level-Keys sind gewhitelisted via .strict() — Tippfehler werfen
111
- * sofort. Set-profile-Handler (Sprint 1.9 Z3) validiert das Override
112
- * gegen dieses Schema vor dem Persist.
106
+ * Complete schema for ComplianceProfileOverride. All top-level and
107
+ * sub-level keys are whitelisted with `.strict()`, so typos throw
108
+ * immediately. The set-profile handler validates the override against
109
+ * this schema before persisting it.
113
110
  */
114
111
  export const complianceProfileOverrideSchema = z
115
112
  .object({