@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.
- package/LICENSE +21 -57
- package/README.md +2 -2
- package/agents/widgets.md +39 -39
- package/dist/api/api-constants.js +2 -2
- package/dist/api/auth-middleware.js +7 -7
- package/dist/api/auth-rate-limiters.js +7 -3
- package/dist/api/auth-routes-mfa.js +1 -1
- package/dist/api/auth-routes-session.js +1 -1
- package/dist/api/auth-routes-shared.js +7 -3
- package/dist/api/auth-routes-signup.js +1 -1
- package/dist/api/client-ip.js +3 -1
- package/dist/api/extra-route.js +1 -1
- package/dist/api/pii-leak-guard.js +4 -2
- package/dist/api/readiness.js +5 -5
- package/dist/api/redis-sse-broker.js +8 -6
- package/dist/api/routes.js +2 -2
- package/dist/api/server.js +34 -33
- package/dist/api/sse-broker.js +1 -1
- package/dist/api/sse-route.d.ts +13 -13
- package/dist/api/sse-route.js +15 -15
- package/dist/auth/index.js +4 -4
- package/dist/auth/roles.d.ts +4 -4
- package/dist/auth/roles.js +24 -24
- package/dist/compliance/duration-spec.js +18 -20
- package/dist/compliance/index.js +2 -3
- package/dist/compliance/override-schema.d.ts +4 -4
- package/dist/compliance/override-schema.js +19 -22
- package/dist/compliance/profiles.d.ts +26 -29
- package/dist/compliance/profiles.js +62 -69
- package/dist/compliance/sub-processors.d.ts +28 -30
- package/dist/compliance/sub-processors.js +17 -21
- package/dist/consumer-cli.js +4 -4
- package/dist/crypto/blind-index.js +18 -17
- package/dist/crypto/ciphertext-pattern.js +1 -1
- package/dist/crypto/event-pii.js +3 -3
- package/dist/crypto/kek-source.d.ts +1 -1
- package/dist/crypto/kek-source.js +3 -2
- package/dist/crypto/kms-adapter.d.ts +1 -1
- package/dist/crypto/kms-adapter.js +4 -6
- package/dist/crypto/kms-wiring.d.ts +1 -2
- package/dist/crypto/kms-wiring.js +2 -2
- package/dist/crypto/pg-kms-adapter.js +2 -6
- package/dist/crypto/pii-field-encryption.js +4 -4
- package/dist/crypto/subject-resolver.d.ts +1 -1
- package/dist/crypto/subject-resolver.js +2 -2
- package/dist/db/apply-entity-event.d.ts +6 -6
- package/dist/db/apply-entity-event.js +55 -55
- package/dist/db/blind-index-cleanup.js +7 -7
- package/dist/db/collect-table-metas.js +10 -11
- package/dist/db/compound-types.d.ts +1 -1
- package/dist/db/compound-types.js +12 -12
- package/dist/db/cursor.js +3 -4
- package/dist/db/dialect.d.ts +2 -2
- package/dist/db/dialect.js +2 -2
- package/dist/db/eagerload.d.ts +3 -3
- package/dist/db/eagerload.js +21 -12
- package/dist/db/entity-table-meta.d.ts +2 -2
- package/dist/db/entity-table-meta.js +4 -4
- package/dist/db/event-store-executor-context.d.ts +1 -1
- package/dist/db/event-store-executor-context.js +13 -13
- package/dist/db/event-store-executor-read.js +27 -28
- package/dist/db/event-store-executor-write.js +65 -70
- package/dist/db/located-timestamp.d.ts +11 -11
- package/dist/db/located-timestamp.js +29 -30
- package/dist/db/migrate-generator.d.ts +1 -1
- package/dist/db/migrate-generator.js +26 -19
- package/dist/db/migrate-runner.js +25 -25
- package/dist/db/money.d.ts +6 -6
- package/dist/db/money.js +29 -27
- package/dist/db/queries/backfill-pii.js +5 -5
- package/dist/db/queries/ddl.js +1 -1
- package/dist/db/queries/es-ops.d.ts +1 -1
- package/dist/db/queries/es-ops.js +1 -1
- package/dist/db/queries/event-consumer.js +3 -3
- package/dist/db/queries/event-store.js +4 -2
- package/dist/db/queries/projection-rebuild.js +3 -3
- package/dist/db/queries/raw-sql.d.ts +2 -2
- package/dist/db/queries/raw-sql.js +2 -2
- package/dist/db/queries/shadow-swap.js +19 -19
- package/dist/db/query.d.ts +1 -1
- package/dist/db/query.js +6 -6
- package/dist/db/rebuild-marker.js +20 -21
- package/dist/db/replay-migration-sql.js +2 -2
- package/dist/db/schema-inspection.js +4 -5
- package/dist/db/table-builder.d.ts +1 -2
- package/dist/db/table-builder.js +82 -82
- package/dist/db/tenant-db.d.ts +2 -2
- package/dist/db/tenant-db.js +9 -2
- package/dist/derivatives/delete-stored-file.js +5 -2
- package/dist/derivatives/derivatives-context.js +1 -1
- package/dist/engine/boot-validator/access-roles.js +4 -3
- package/dist/engine/boot-validator/api-ext.js +14 -14
- package/dist/engine/boot-validator/config-deps.js +7 -6
- package/dist/engine/boot-validator/detail-screens.js +3 -3
- package/dist/engine/boot-validator/entity-handler.js +60 -61
- package/dist/engine/boot-validator/gdpr-storage.js +4 -4
- package/dist/engine/boot-validator/i18n-keys.js +1 -1
- package/dist/engine/boot-validator/index.d.ts +2 -2
- package/dist/engine/boot-validator/index.js +20 -20
- package/dist/engine/boot-validator/nav.js +5 -5
- package/dist/engine/boot-validator/ownership.js +12 -9
- package/dist/engine/boot-validator/parent-ref.js +1 -1
- package/dist/engine/boot-validator/pii-retention.js +50 -57
- package/dist/engine/boot-validator/projection-list-screens.js +7 -7
- package/dist/engine/boot-validator/query-refs.js +1 -1
- package/dist/engine/boot-validator/screens-actions.js +15 -16
- package/dist/engine/boot-validator/screens-forms.js +10 -11
- package/dist/engine/boot-validator/screens-lists.js +14 -15
- package/dist/engine/boot-validator/screens.js +34 -40
- package/dist/engine/boot-validator/security-baseline.js +3 -2
- package/dist/engine/boot-validator/transfer-graph.js +2 -2
- package/dist/engine/boot-validator/workspaces.js +3 -3
- package/dist/engine/bridge-stub.js +3 -4
- package/dist/engine/build-app-schema.js +75 -75
- package/dist/engine/build-config-feature-schema.js +50 -50
- package/dist/engine/build-target.js +20 -23
- package/dist/engine/codemod/pipeline-codemod.js +20 -13
- package/dist/engine/config-helpers.js +4 -4
- package/dist/engine/constants.js +2 -2
- package/dist/engine/create-app.js +2 -2
- package/dist/engine/define-feature.js +12 -11
- package/dist/engine/define-handler.js +12 -11
- package/dist/engine/define-step.js +1 -1
- package/dist/engine/define-workflow.d.ts +5 -5
- package/dist/engine/define-workflow.js +6 -6
- package/dist/engine/entity-handlers.js +13 -14
- package/dist/engine/event-def.js +2 -2
- package/dist/engine/extension-names.d.ts +16 -17
- package/dist/engine/extension-names.js +37 -38
- package/dist/engine/extensions/escape-hatch-usage.js +1 -1
- package/dist/engine/extensions/tenant-data.js +2 -2
- package/dist/engine/extensions/user-data.d.ts +23 -24
- package/dist/engine/factories.d.ts +14 -13
- package/dist/engine/factories.js +38 -37
- package/dist/engine/feature-ast/entity-field-types.js +1 -1
- package/dist/engine/feature-ast/extractors/handlers.js +1 -1
- package/dist/engine/feature-ast/extractors/hooks.js +1 -1
- package/dist/engine/feature-ast/extractors/shared.d.ts +2 -3
- package/dist/engine/feature-ast/extractors/shared.js +2 -3
- package/dist/engine/feature-ast/parse.js +13 -13
- package/dist/engine/feature-ast/patch.js +1 -1
- package/dist/engine/feature-ast/pattern-change-schema.js +1 -1
- package/dist/engine/feature-ast/patterns.d.ts +2 -2
- package/dist/engine/feature-ast/render.js +2 -2
- package/dist/engine/feature-config-events-jobs.js +15 -12
- package/dist/engine/feature-entity-handlers.js +9 -5
- package/dist/engine/feature-manifest.d.ts +9 -9
- package/dist/engine/feature-manifest.js +6 -7
- package/dist/engine/feature-ui-extensions.d.ts +3 -3
- package/dist/engine/feature-ui-extensions.js +30 -22
- package/dist/engine/field-access.js +5 -6
- package/dist/engine/index.d.ts +1 -0
- package/dist/engine/index.js +4 -4
- package/dist/engine/object-form.d.ts +1 -0
- package/dist/engine/object-form.js +14 -4
- package/dist/engine/ownership.js +3 -3
- package/dist/engine/parse-ref-target.js +5 -7
- package/dist/engine/pattern-library/index.js +2 -2
- package/dist/engine/pattern-library/library.js +2 -2
- package/dist/engine/pattern-library/types.js +3 -3
- package/dist/engine/pipeline.js +2 -2
- package/dist/engine/project-app-schema-for-roles.js +1 -1
- package/dist/engine/projection-helpers.js +9 -9
- package/dist/engine/qualified-name.js +2 -2
- package/dist/engine/registry-facade.js +1 -1
- package/dist/engine/registry-ingest.js +12 -12
- package/dist/engine/registry-state.js +17 -19
- package/dist/engine/registry-validate.js +12 -12
- package/dist/engine/run-in.js +2 -2
- package/dist/engine/run-pipeline.js +7 -0
- package/dist/engine/schema-builder.js +40 -38
- package/dist/engine/screen-filter-ops.js +17 -20
- package/dist/engine/screen-helpers.js +4 -3
- package/dist/engine/state-machine.d.ts +12 -12
- package/dist/engine/state-machine.js +7 -7
- package/dist/engine/steps/_resolver-utils.js +4 -3
- package/dist/engine/steps/for-each.js +4 -4
- package/dist/engine/steps/unsafe-projection-delete.js +1 -1
- package/dist/engine/steps/unsafe-projection-upsert.js +4 -4
- package/dist/engine/steps/wait-for-event.js +4 -4
- package/dist/engine/steps/workflow-system-events.js +3 -3
- package/dist/engine/tier-resolver-extension.d.ts +29 -27
- package/dist/engine/tier-resolver-extension.js +26 -26
- package/dist/engine/transfer-adjacency.js +2 -2
- package/dist/engine/validate-projection-allowlist.js +7 -27
- package/dist/engine/walk-step-instances.d.ts +2 -0
- package/dist/engine/walk-step-instances.js +38 -0
- package/dist/engine/where-rule-lint.js +1 -1
- package/dist/engine/write-handler-qn-extract.js +1 -1
- package/dist/entrypoint/index.js +3 -3
- package/dist/env/index.d.ts +7 -6
- package/dist/env/index.js +10 -9
- package/dist/errors/transition-details.js +7 -7
- package/dist/errors/write-error-info.d.ts +6 -6
- package/dist/errors/write-error-info.js +6 -6
- package/dist/es-ops/context.d.ts +9 -9
- package/dist/es-ops/context.js +28 -28
- package/dist/es-ops/index.js +7 -9
- package/dist/es-ops/operations-schema.js +4 -10
- package/dist/es-ops/runner.d.ts +11 -11
- package/dist/es-ops/runner.js +49 -53
- package/dist/es-ops/types.d.ts +55 -55
- package/dist/es-ops/types.js +11 -11
- package/dist/event-store/admin-api.js +6 -7
- package/dist/event-store/errors.js +1 -1
- package/dist/event-store/event-store.js +2 -2
- package/dist/event-store/rebuild-dead-letter.js +1 -1
- package/dist/files/feature.js +9 -10
- package/dist/files/file-handle.js +5 -6
- package/dist/files/file-ref-entity.js +15 -15
- package/dist/files/file-ref-table.js +7 -8
- package/dist/files/file-routes.d.ts +7 -0
- package/dist/files/file-routes.js +11 -5
- package/dist/files/in-memory-provider.js +8 -9
- package/dist/files/local-provider.js +18 -25
- package/dist/files/provider-resolver.d.ts +7 -7
- package/dist/files/storage-tracking.js +2 -2
- package/dist/files/zip-stream.d.ts +12 -19
- package/dist/files/zip-stream.js +44 -63
- package/dist/i18n/required-surface-keys.d.ts +1 -1
- package/dist/i18n/required-surface-keys.js +8 -8
- package/dist/jobs/job-runner-helpers.js +8 -2
- package/dist/jobs/job-runner-schedulers.d.ts +1 -1
- package/dist/jobs/job-runner-schedulers.js +3 -4
- package/dist/jobs/job-runner.js +13 -15
- package/dist/lifecycle/lifecycle.js +3 -5
- package/dist/lifecycle/signal-handlers.js +5 -4
- package/dist/logging/utils.d.ts +2 -1
- package/dist/logging/utils.js +38 -10
- package/dist/migrations/index.js +1 -1
- package/dist/migrations/kumiko-drift.js +14 -13
- package/dist/migrations/pending-rebuilds.d.ts +21 -20
- package/dist/migrations/pending-rebuilds.js +27 -27
- package/dist/migrations/projection-table-index.d.ts +3 -3
- package/dist/migrations/projection-table-index.js +8 -8
- package/dist/observability/metric-validator.js +1 -1
- package/dist/observability/metrics-handle.js +3 -3
- package/dist/pipeline/append-event-core.js +11 -2
- package/dist/pipeline/cascade-handler.d.ts +1 -1
- package/dist/pipeline/dispatch-batch.js +2 -2
- package/dist/pipeline/dispatch-query.js +10 -10
- package/dist/pipeline/dispatch-shared.d.ts +1 -0
- package/dist/pipeline/dispatch-shared.js +15 -17
- package/dist/pipeline/dispatch-stream.js +1 -1
- package/dist/pipeline/dispatch-write.js +3 -3
- package/dist/pipeline/dispatcher-utils.d.ts +1 -1
- package/dist/pipeline/dispatcher-utils.js +1 -1
- package/dist/pipeline/event-consumer-state.js +2 -2
- package/dist/pipeline/event-dispatcher-delivery.js +1 -1
- package/dist/pipeline/event-dispatcher.js +17 -28
- package/dist/pipeline/irreversible-operation-gate.js +1 -1
- package/dist/pipeline/lifecycle-pipeline.js +9 -9
- package/dist/pipeline/member-read-only-transaction.js +1 -3
- package/dist/pipeline/msp-rebuild.js +1 -1
- package/dist/pipeline/projection-rebuild.js +13 -12
- package/dist/pipeline/system-hooks.js +39 -41
- package/dist/pipeline/system-identity-switch.js +1 -3
- package/dist/pipeline/tenant-timezone-cache.js +1 -1
- package/dist/random/generate.d.ts +29 -29
- package/dist/random/generate.js +30 -30
- package/dist/random/words.js +20 -20
- package/dist/rate-limit/middleware.js +9 -5
- package/dist/redis/client.js +2 -2
- package/dist/redis/pubsub-signal.js +14 -17
- package/dist/schema-cli.js +116 -22
- package/dist/search/index.js +4 -4
- package/dist/search/purge-subject.js +4 -5
- package/dist/search/reindex-entity.js +6 -9
- package/dist/time/boot-tz-warning.d.ts +2 -2
- package/dist/time/boot-tz-warning.js +8 -8
- package/dist/time/iana.js +10 -10
- package/dist/time/index.js +9 -9
- package/dist/time/legacy-date.d.ts +2 -2
- package/dist/time/legacy-date.js +6 -8
- package/dist/time/polyfill.js +1 -1
- package/dist/time/tz-context.d.ts +1 -1
- package/dist/time/tz-context.js +1 -1
- package/dist/ui-types/app-schema.js +7 -7
- package/dist/ui-types/list-row-meta.js +4 -4
- package/dist/upgrade-cli.js +10 -9
- package/dist/utils/compare.js +1 -1
- package/package.json +4 -4
- package/src/changes.json +48 -0
- package/src/scripts/codemod/crypto-shredding-testing-move.ts +2 -2
- package/src/scripts/codemod/migrate-cross-tenant.ts +1 -1
- package/src/scripts/codemod/migrate-open-to-all.ts +1 -1
- package/src/scripts/codemod/pii-personal-migration.ts +17 -8
- package/src/scripts/codemod/unprocessable-error-details-reason.ts +2 -2
package/dist/api/server.js
CHANGED
|
@@ -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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
|
149
|
-
//
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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
|
|
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 (
|
|
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-
|
|
760
|
-
//
|
|
761
|
-
//
|
|
762
|
-
//
|
|
763
|
-
//
|
|
764
|
-
//
|
|
765
|
-
//
|
|
766
|
-
//
|
|
767
|
-
//
|
|
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
|
|
835
|
-
//
|
|
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
|
|
890
|
-
// wins",
|
|
891
|
-
// version
|
|
892
|
-
//
|
|
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
|
|
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
|
|
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();
|
package/dist/api/sse-broker.js
CHANGED
|
@@ -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
|
|
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();
|
package/dist/api/sse-route.d.ts
CHANGED
|
@@ -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
|
|
5
|
+
* Heartbeat cadence for SSE streams.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* ERR_HTTP2_PROTOCOL_ERROR.
|
|
10
|
-
* - Bun.serve default: 10 s (
|
|
11
|
-
*
|
|
12
|
-
* - Caddy reverse_proxy:
|
|
13
|
-
* via Content-Type),
|
|
14
|
-
*
|
|
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
|
|
19
|
-
*
|
|
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
|
-
*
|
|
22
|
-
*
|
|
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;
|
package/dist/api/sse-route.js
CHANGED
|
@@ -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
|
|
12
|
+
* Heartbeat cadence for SSE streams.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* ERR_HTTP2_PROTOCOL_ERROR.
|
|
17
|
-
* - Bun.serve default: 10 s (
|
|
18
|
-
*
|
|
19
|
-
* - Caddy reverse_proxy:
|
|
20
|
-
* via Content-Type),
|
|
21
|
-
*
|
|
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
|
|
26
|
-
*
|
|
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
|
-
*
|
|
29
|
-
*
|
|
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 —
|
|
188
|
-
//
|
|
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()) {
|
package/dist/auth/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// `@cosmicdrift/kumiko-framework/auth` — Auth
|
|
2
|
-
//
|
|
3
|
-
// tenant, user)
|
|
4
|
-
// lifecycle,
|
|
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";
|
package/dist/auth/roles.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
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
|
-
*
|
|
14
|
-
*
|
|
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];
|
package/dist/auth/roles.js
CHANGED
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Central role constants for the platform.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
// secrets, ai-foundation, file-provider-s3)
|
|
5
|
-
// handler, publicstatus, platform)
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
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
|
|
11
|
-
// TenantOwner —
|
|
12
|
-
//
|
|
13
|
-
// TenantAdmin — Tenant
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
// DataProtectionOfficer — DPO;
|
|
17
|
-
//
|
|
18
|
-
// SystemAdmin —
|
|
19
|
-
// Authority
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
// Member — Standard
|
|
24
|
-
//
|
|
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
|
-
*
|
|
27
|
-
*
|
|
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
|
|
1
|
+
// Converts between DurationSpec, SQL intervals, and Temporal.Duration.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
// `{ days: number } | { hours: number }` (
|
|
5
|
-
//
|
|
6
|
-
//
|
|
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
|
-
//
|
|
10
|
-
// retention
|
|
11
|
-
//
|
|
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
|
-
//
|
|
15
|
-
//
|
|
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
|
-
//
|
|
22
|
-
// authoritative.
|
|
23
|
-
// where
|
|
24
|
-
//
|
|
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
|
-
//
|
|
27
|
-
//
|
|
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
|
-
//
|
|
32
|
-
// English-only
|
|
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"}`;
|
package/dist/compliance/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
// `@cosmicdrift/kumiko-framework/compliance` —
|
|
2
|
-
//
|
|
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
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
|
2
|
-
// Sprint 1.9 Z3.
|
|
1
|
+
// Zod schema for ComplianceProfileOverride with recursive `.strict()` validation.
|
|
3
2
|
//
|
|
4
|
-
//
|
|
5
|
-
// OVERRIDABLE_PROFILE_KEYS. Sub-
|
|
6
|
-
// `{ userRights: { weeks: 3 } }`
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// crashed
|
|
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
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
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
|
-
//
|
|
17
|
-
//
|
|
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.*
|
|
27
|
-
//
|
|
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
|
|
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
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
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({
|