@cosmicdrift/kumiko-framework 1.0.0 → 2.0.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/README.md +9 -38
- package/package.json +21 -2
- package/src/__tests__/anonymous-access.integration.test.ts +185 -0
- package/src/__tests__/consumer-cli.integration.test.ts +172 -0
- package/src/__tests__/entity-permalink-open.integration.test.ts +94 -0
- package/src/__tests__/full-stack.integration.test.ts +1 -1
- package/src/__tests__/schema-cli-temporal-polyfill.test.ts +48 -0
- package/src/__tests__/schema-cli.integration.test.ts +50 -0
- package/src/__tests__/store-table.integration.test.ts +93 -0
- package/src/api/__tests__/api.test.ts +373 -0
- package/src/api/__tests__/auth-middleware-anonymous-access-boot.test.ts +40 -0
- package/src/api/__tests__/auth-routes-cookie.test.ts +17 -1
- package/src/api/__tests__/auth-routes-invalid-body-invite.test.ts +253 -0
- package/src/api/__tests__/auth-routes-mfa-preauth-confirm.test.ts +222 -0
- package/src/api/__tests__/auth-routes-mfa-preauth-enable-start.test.ts +249 -0
- package/src/api/__tests__/auth-routes-mfa-verify.test.ts +202 -0
- package/src/api/__tests__/auth-routes-trusted-proxy.test.ts +135 -0
- package/src/api/__tests__/batch.integration.test.ts +101 -11
- package/src/api/__tests__/csrf-constants-sync.test.ts +20 -0
- package/src/api/__tests__/dispatcher-live.integration.test.ts +74 -0
- package/src/api/__tests__/jwt.test.ts +200 -1
- package/src/api/__tests__/login-rate-limiter-sweep.test.ts +50 -0
- package/src/api/__tests__/pat-scope.test.ts +36 -0
- package/src/api/__tests__/pii-leak-guard.integration.test.ts +103 -0
- package/src/api/__tests__/redis-login-rate-limiter.integration.test.ts +138 -0
- package/src/api/__tests__/request-id-middleware.test.ts +51 -0
- package/src/api/__tests__/server-boot-guards.test.ts +71 -0
- package/src/api/__tests__/server-jwt-ttl.test.ts +58 -0
- package/src/api/__tests__/sse-broker.test.ts +57 -0
- package/src/api/__tests__/sse-route.test.ts +4 -0
- package/src/api/api-constants.ts +11 -0
- package/src/api/auth-middleware.ts +248 -40
- package/src/api/auth-routes.ts +576 -95
- package/src/api/index.ts +13 -4
- package/src/api/jwt.ts +170 -11
- package/src/api/pat-scope.ts +14 -0
- package/src/api/pii-leak-guard.ts +47 -0
- package/src/api/request-context.ts +3 -0
- package/src/api/request-id-middleware.ts +2 -0
- package/src/api/routes.ts +169 -2
- package/src/api/server.ts +112 -16
- package/src/api/sse-broker.ts +39 -0
- package/src/bun-db/__tests__/PATTERN.md +0 -1
- package/src/bun-db/__tests__/coerce-row-temporal.test.ts +41 -0
- package/src/bun-db/__tests__/select-many-retry.test.ts +79 -0
- package/src/bun-db/__tests__/write-brand.test.ts +21 -0
- package/src/bun-db/connection.ts +3 -3
- package/src/bun-db/index.ts +2 -0
- package/src/bun-db/query.ts +105 -32
- package/src/consumer-cli.ts +134 -0
- package/src/crypto/__tests__/blind-index.test.ts +130 -0
- package/src/crypto/__tests__/event-pii.test.ts +161 -0
- package/src/crypto/__tests__/kek-rotation.integration.test.ts +180 -0
- package/src/crypto/__tests__/kms-adapter-contract.ts +134 -0
- package/src/crypto/__tests__/kms-adapter.contract.test.ts +4 -0
- package/src/crypto/__tests__/pg-kms-adapter.integration.test.ts +117 -0
- package/src/crypto/__tests__/pii-field-encryption.test.ts +376 -0
- package/src/crypto/__tests__/request-kms-cache.test.ts +74 -0
- package/src/crypto/__tests__/subject-resolver.test.ts +108 -0
- package/src/crypto/blind-index.ts +118 -0
- package/src/crypto/event-pii.ts +70 -0
- package/src/crypto/in-memory-kms-adapter.ts +50 -0
- package/src/crypto/index.ts +67 -0
- package/src/crypto/kms-adapter.ts +2 -0
- package/src/crypto/pg-kms-adapter.ts +295 -0
- package/src/crypto/pii-field-encryption.ts +248 -0
- package/src/crypto/request-kms-cache.ts +38 -0
- package/src/crypto/subject-resolver.ts +91 -0
- package/src/db/__tests__/assert-no-unreachable-live-rows.integration.test.ts +191 -0
- package/src/db/__tests__/blind-index.integration.test.ts +248 -0
- package/src/db/__tests__/build-filter-where.test.ts +34 -0
- package/src/db/__tests__/collect-table-metas.test.ts +10 -10
- package/src/db/__tests__/config-seed.integration.test.ts +13 -5
- package/src/db/__tests__/dialect-instant.test.ts +1 -4
- package/src/db/__tests__/entity-field-encryption.test.ts +7 -7
- package/src/db/__tests__/entity-table-meta-source.test.ts +50 -0
- package/src/db/__tests__/event-store-executor-context.pii-roundtrip.test.ts +67 -0
- package/src/db/__tests__/event-store-executor-write-verbs.integration.test.ts +402 -0
- package/src/db/__tests__/event-store-executor.integration.test.ts +299 -29
- package/src/db/__tests__/feature-table-sources.test.ts +34 -0
- package/src/db/__tests__/implicit-projection-equivalence.integration.test.ts +118 -39
- package/src/db/__tests__/instant-to-driver-temporal.test.ts +21 -0
- package/src/db/__tests__/located-timestamp.test.ts +19 -0
- package/src/db/__tests__/migrate-generator.test.ts +21 -0
- package/src/db/__tests__/migrate-runner.test.ts +61 -0
- package/src/db/__tests__/number-field-fractional.integration.test.ts +58 -0
- package/src/db/__tests__/rebuild-marker.test.ts +13 -3
- package/src/db/__tests__/replay-migration-sql.test.ts +384 -0
- package/src/db/__tests__/schema-inspection.test.ts +7 -0
- package/src/db/__tests__/schema-migration.integration.test.ts +1 -1
- package/src/db/__tests__/table-builder-meta-lockstep.test.ts +39 -0
- package/src/db/__tests__/tenant-db-where-merge.test.ts +49 -3
- package/src/db/__tests__/tenant-db.integration.test.ts +6 -2
- package/src/db/api.ts +2 -2
- package/src/db/apply-entity-event.ts +18 -14
- package/src/db/blind-index-cleanup.ts +55 -0
- package/src/db/bun-provider.ts +2 -2
- package/src/db/collect-table-metas.ts +6 -7
- package/src/db/config-seed.ts +9 -9
- package/src/db/connection.ts +7 -12
- package/src/db/cursor.ts +1 -18
- package/src/db/dialect.ts +20 -26
- package/src/db/entity-field-encryption.ts +81 -24
- package/src/db/entity-table-meta-types.ts +2 -0
- package/src/db/entity-table-meta.ts +63 -90
- package/src/db/event-store-executor-context.ts +404 -0
- package/src/db/event-store-executor-read.ts +275 -0
- package/src/db/event-store-executor-write.ts +644 -0
- package/src/db/event-store-executor.ts +28 -1155
- package/src/db/feature-table-sources.ts +6 -5
- package/src/db/index.ts +20 -3
- package/src/db/located-timestamp.ts +4 -0
- package/src/db/migrate-generator.ts +39 -6
- package/src/db/migrate-runner.ts +107 -11
- package/src/db/pg-error.ts +9 -1
- package/src/db/postgres-provider.ts +2 -2
- package/src/db/queries/__tests__/event-store-idempotency-index.integration.test.ts +80 -0
- package/src/db/queries/backfill-pii.ts +277 -0
- package/src/db/queries/ddl.ts +45 -0
- package/src/db/queries/event-consumer.ts +35 -2
- package/src/db/queries/event-store.ts +126 -19
- package/src/db/queries/projection-rebuild.ts +22 -8
- package/src/db/queries/shadow-swap.ts +183 -0
- package/src/db/queries/test-stack.ts +4 -30
- package/src/db/query-api.ts +1 -0
- package/src/db/query.ts +1 -0
- package/src/db/rebuild-marker.ts +18 -2
- package/src/db/reference-data.ts +2 -3
- package/src/db/replay-migration-sql.ts +257 -0
- package/src/db/schema-inspection.ts +1 -1
- package/src/db/table-builder.ts +102 -71
- package/src/db/tenant-db.ts +15 -53
- package/src/engine/__tests__/boot-validator-action-wiring.test.ts +242 -0
- package/src/engine/__tests__/boot-validator-boot-check.test.ts +99 -0
- package/src/engine/__tests__/boot-validator-dashboard.test.ts +237 -0
- package/src/engine/__tests__/boot-validator-entity-list.test.ts +104 -0
- package/src/engine/__tests__/boot-validator-gdpr-storage.test.ts +7 -69
- package/src/engine/__tests__/boot-validator-i18n-keys.test.ts +26 -5
- package/src/engine/__tests__/boot-validator-located-timestamps.test.ts +3 -19
- package/src/engine/__tests__/boot-validator-pii-retention.test.ts +246 -4
- package/src/engine/__tests__/boot-validator.test.ts +219 -15
- package/src/engine/__tests__/build-app-schema.test.ts +82 -0
- package/src/engine/__tests__/build-config-feature-schema.test.ts +125 -0
- package/src/engine/__tests__/build-target.test.ts +3 -11
- package/src/engine/__tests__/codemod-pipeline.test.ts +152 -21
- package/src/engine/__tests__/config-helpers.test.ts +16 -0
- package/src/engine/__tests__/define-feature-entity-mapping.test.ts +6 -0
- package/src/engine/__tests__/define-roles.test.ts +21 -0
- package/src/engine/__tests__/engine.test.ts +163 -1
- package/src/engine/__tests__/entity-handlers.test.ts +80 -0
- package/src/engine/__tests__/event-migration-declarative.test.ts +65 -0
- package/src/engine/__tests__/event-type-map-augmentation.test.ts +24 -0
- package/src/engine/__tests__/extend-entity-projection.test.ts +123 -0
- package/src/engine/__tests__/factories-time.test.ts +2 -66
- package/src/engine/__tests__/feature-crud-shorthand.test.ts +28 -0
- package/src/engine/__tests__/feature-manifest.test.ts +31 -1
- package/src/engine/__tests__/field-access.test.ts +23 -1
- package/src/engine/__tests__/hook-phases.test.ts +5 -5
- package/src/engine/__tests__/membership-roles.test.ts +4 -10
- package/src/engine/__tests__/nav.test.ts +86 -1
- package/src/engine/__tests__/pipeline-engine.test.ts +9 -9
- package/src/engine/__tests__/pipeline-handler.integration.test.ts +18 -18
- package/src/engine/__tests__/pipeline-observability.integration.test.ts +2 -2
- package/src/engine/__tests__/pipeline-performance.integration.test.ts +3 -3
- package/src/engine/__tests__/pipeline-sub-pipelines.test.ts +9 -9
- package/src/engine/__tests__/post-query-hook.test.ts +7 -7
- package/src/engine/__tests__/registrar-object-form.test.ts +141 -0
- package/src/engine/__tests__/registry.test.ts +144 -0
- package/src/engine/__tests__/schema-builder.test.ts +18 -0
- package/src/engine/__tests__/screen.test.ts +147 -1
- package/src/engine/__tests__/soft-delete-cleanup.test.ts +3 -0
- package/src/engine/__tests__/store-table.test.ts +229 -0
- package/src/engine/__tests__/tier-resolver-extension.test.ts +19 -1
- package/src/engine/__tests__/{visual-tree-patterns.test.ts → tree-actions-patterns.test.ts} +7 -95
- package/src/engine/__tests__/validate-projection-allowlist.test.ts +15 -15
- package/src/engine/boot-validator/__tests__/config-deps.test.ts +92 -0
- package/src/engine/boot-validator/action-wiring.ts +149 -0
- package/src/engine/boot-validator/boot-check.ts +21 -0
- package/src/engine/boot-validator/config-deps.ts +40 -4
- package/src/engine/boot-validator/entity-handler.ts +20 -21
- package/src/engine/boot-validator/entity-list-screens.ts +88 -0
- package/src/engine/boot-validator/gdpr-storage.ts +0 -20
- package/src/engine/boot-validator/i18n-keys.ts +58 -4
- package/src/engine/boot-validator/index.ts +33 -12
- package/src/engine/boot-validator/nav.ts +125 -0
- package/src/engine/boot-validator/pii-retention.ts +95 -10
- package/src/engine/boot-validator/{screens-nav.ts → screens.ts} +214 -191
- package/src/engine/boot-validator/workspaces.ts +68 -0
- package/src/engine/build-app-schema.ts +16 -0
- package/src/engine/build-config-feature-schema.ts +44 -7
- package/src/engine/codemod/pipeline-codemod.ts +5 -5
- package/src/engine/config-helpers.ts +15 -0
- package/src/engine/constants.ts +32 -6
- package/src/engine/create-app.ts +11 -0
- package/src/engine/define-feature.ts +95 -947
- package/src/engine/define-handler.ts +28 -94
- package/src/engine/define-workflow.ts +1 -1
- package/src/engine/effective-features.ts +12 -2
- package/src/engine/entity-handlers.ts +76 -11
- package/src/engine/extensions/tenant-data.ts +19 -0
- package/src/engine/extensions/user-data.ts +29 -2
- package/src/engine/factories.ts +8 -49
- package/src/engine/feature-ast/__tests__/canonical-form.test.ts +26 -29
- package/src/engine/feature-ast/__tests__/fixtures/cross-file-registrar/feature.ts +9 -0
- package/src/engine/feature-ast/__tests__/fixtures/cross-file-registrar/screens.ts +4 -0
- package/src/engine/feature-ast/__tests__/parse-happy-path.test.ts +1 -2
- package/src/engine/feature-ast/__tests__/parse-real-features.test.ts +18 -8
- package/src/engine/feature-ast/__tests__/parse.test.ts +1640 -160
- package/src/engine/feature-ast/__tests__/patch.test.ts +206 -12
- package/src/engine/feature-ast/__tests__/patcher.test.ts +20 -23
- package/src/engine/feature-ast/__tests__/render-roundtrip.test.ts +380 -5
- package/src/engine/feature-ast/extractors/events.ts +322 -0
- package/src/engine/feature-ast/extractors/handlers.ts +234 -0
- package/src/engine/feature-ast/extractors/hooks.ts +243 -0
- package/src/engine/feature-ast/extractors/index.ts +34 -31
- package/src/engine/feature-ast/extractors/jobs-routes.ts +221 -0
- package/src/engine/feature-ast/extractors/projections-screens.ts +269 -0
- package/src/engine/feature-ast/extractors/round1.ts +3 -3
- package/src/engine/feature-ast/extractors/round5.ts +3 -3
- package/src/engine/feature-ast/extractors/round6.ts +2 -36
- package/src/engine/feature-ast/extractors/shared.ts +64 -6
- package/src/engine/feature-ast/index.ts +2 -4
- package/src/engine/feature-ast/parse.ts +130 -23
- package/src/engine/feature-ast/patch.ts +39 -50
- package/src/engine/feature-ast/patcher.ts +37 -38
- package/src/engine/feature-ast/patterns.ts +50 -62
- package/src/engine/feature-ast/render.ts +67 -44
- package/src/engine/feature-builder-state.ts +168 -0
- package/src/engine/feature-config-events-jobs.ts +403 -0
- package/src/engine/feature-entity-handlers.ts +208 -0
- package/src/engine/feature-manifest.ts +2 -1
- package/src/engine/feature-ui-extensions.ts +500 -0
- package/src/engine/field-access.ts +13 -2
- package/src/engine/field-helpers.ts +31 -0
- package/src/engine/handler-helpers.ts +26 -0
- package/src/engine/hook-helpers.ts +16 -0
- package/src/engine/index.ts +23 -7
- package/src/engine/membership-roles.ts +13 -0
- package/src/engine/object-form.ts +27 -0
- package/src/engine/ownership.ts +26 -79
- package/src/engine/pattern-library/__tests__/library.test.ts +7 -20
- package/src/engine/pattern-library/library.ts +44 -1152
- package/src/engine/pattern-library/mixed-schemas.ts +450 -0
- package/src/engine/pattern-library/opaque-schemas.ts +124 -0
- package/src/engine/pattern-library/shared-fields.ts +75 -0
- package/src/engine/pattern-library/static-schemas.ts +456 -0
- package/src/engine/pipeline.ts +6 -11
- package/src/engine/registry-facade.ts +362 -0
- package/src/engine/registry-ingest.ts +478 -0
- package/src/engine/registry-state.ts +388 -0
- package/src/engine/registry-validate.ts +642 -0
- package/src/engine/registry.ts +78 -1658
- package/src/engine/run-pipeline.ts +1 -1
- package/src/engine/schema-builder.ts +1 -0
- package/src/engine/screen-helpers.ts +54 -0
- package/src/engine/soft-delete-cleanup.ts +6 -2
- package/src/engine/steps/__tests__/duration-utils.test.ts +20 -0
- package/src/engine/steps/_duration-utils.ts +2 -0
- package/src/engine/steps/unsafe-projection-upsert.ts +1 -4
- package/src/engine/tier-resolver-extension.ts +3 -2
- package/src/engine/types/config.ts +2 -482
- package/src/engine/types/define-handler.ts +2 -0
- package/src/engine/types/entity-handlers.ts +2 -0
- package/src/engine/types/event-type-map.ts +1 -37
- package/src/engine/types/feature.ts +2 -972
- package/src/engine/types/fields.ts +2 -675
- package/src/engine/types/handlers.ts +2 -774
- package/src/engine/types/hooks.ts +2 -184
- package/src/engine/types/http-route.ts +1 -54
- package/src/engine/types/identifiers.ts +1 -47
- package/src/engine/types/index.ts +86 -40
- package/src/engine/types/nav.ts +2 -63
- package/src/engine/types/ownership.ts +2 -0
- package/src/engine/types/projection.ts +2 -138
- package/src/engine/types/relations.ts +1 -51
- package/src/engine/types/screen.ts +2 -574
- package/src/engine/types/step.ts +2 -334
- package/src/engine/types/target-ref.ts +1 -21
- package/src/engine/types/tree-node.ts +1 -132
- package/src/engine/types/workspace.ts +2 -49
- package/src/engine/validate-projection-allowlist.ts +6 -6
- package/src/entrypoint/__tests__/entrypoint-job-wiring.integration.test.ts +120 -1
- package/src/entrypoint/index.ts +49 -6
- package/src/errors/classes.ts +22 -1
- package/src/errors/field-issue.ts +0 -3
- package/src/errors/index.ts +1 -1
- package/src/errors/write-error-info.ts +10 -22
- package/src/es-ops/README.md +1 -1
- package/src/es-ops/__tests__/runner.integration.test.ts +74 -0
- package/src/es-ops/context.ts +4 -2
- package/src/es-ops/types.ts +8 -1
- package/src/event-store/__tests__/admin-api.integration.test.ts +27 -1
- package/src/event-store/__tests__/backfill-pii.integration.test.ts +279 -0
- package/src/event-store/__tests__/event-store.integration.test.ts +201 -0
- package/src/event-store/__tests__/row-to-stored-event.test.ts +2 -2
- package/src/event-store/__tests__/snapshot.integration.test.ts +86 -0
- package/src/event-store/__tests__/unscoped-stream-primitives.guard.test.ts +58 -0
- package/src/event-store/__tests__/upcaster.integration.test.ts +77 -45
- package/src/event-store/admin-api.ts +11 -4
- package/src/event-store/errors.ts +2 -35
- package/src/event-store/event-store.ts +64 -78
- package/src/event-store/events-schema.ts +11 -13
- package/src/event-store/index.ts +18 -3
- package/src/event-store/rebuild-dead-letter.ts +111 -0
- package/src/event-store/snapshot.ts +63 -40
- package/src/event-store/types.ts +2 -0
- package/src/files/__tests__/build-storage-key.test.ts +28 -0
- package/src/files/__tests__/file-ref-entity.test.ts +8 -0
- package/src/files/__tests__/files.integration.test.ts +24 -0
- package/src/files/__tests__/in-memory-provider.contract.test.ts +4 -0
- package/src/files/__tests__/local-provider.test.ts +31 -0
- package/src/files/__tests__/provider-resolver.test.ts +70 -0
- package/src/files/__tests__/write-stream.test.ts +13 -0
- package/src/files/file-handle.ts +2 -19
- package/src/files/file-ref-entity.ts +2 -2
- package/src/files/file-routes.ts +13 -0
- package/src/files/index.ts +1 -1
- package/src/files/local-provider.ts +27 -8
- package/src/files/provider-resolver.ts +31 -20
- package/src/files/types.ts +13 -55
- package/src/i18n/__tests__/required-surface-keys.test.ts +17 -0
- package/src/i18n/required-surface-keys.ts +120 -1
- package/src/jobs/__tests__/job-queue-depth.integration.test.ts +82 -0
- package/src/jobs/__tests__/jobs.integration.test.ts +329 -3
- package/src/jobs/job-runner.ts +82 -12
- package/src/logging/types.ts +1 -7
- package/src/migrations/pending-rebuilds.ts +1 -1
- package/src/observability/__tests__/observability.integration.test.ts +4 -1
- package/src/observability/__tests__/recording-tracer.test.ts +6 -4
- package/src/observability/index.ts +2 -0
- package/src/observability/noop-provider.ts +0 -9
- package/src/observability/recording-tracer.ts +0 -12
- package/src/observability/standard-metrics.ts +64 -2
- package/src/observability/types/index.ts +1 -29
- package/src/observability/types/metric.ts +1 -56
- package/src/observability/types/provider.ts +1 -32
- package/src/observability/types/span.ts +1 -64
- package/src/pipeline/__tests__/ctx-bridge.integration.test.ts +2 -2
- package/src/pipeline/__tests__/dispatcher.test.ts +366 -1
- package/src/pipeline/__tests__/event-consumer-state.integration.test.ts +31 -0
- package/src/pipeline/__tests__/event-dispatcher-delivery-max-attempts.test.ts +126 -0
- package/src/pipeline/__tests__/event-dispatcher-rearm.integration.test.ts +263 -0
- package/src/pipeline/__tests__/event-dispatcher.integration.test.ts +10 -0
- package/src/pipeline/__tests__/job-trigger-consumer.integration.test.ts +106 -0
- package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +308 -77
- package/src/pipeline/__tests__/load-aggregate-query.integration.test.ts +16 -8
- package/src/pipeline/__tests__/post-query-hook.integration.test.ts +3 -3
- package/src/pipeline/__tests__/projection-rebuild.integration.test.ts +80 -2
- package/src/pipeline/__tests__/rebuild-poison-quarantine.integration.test.ts +274 -0
- package/src/pipeline/__tests__/try-append-event.integration.test.ts +166 -0
- package/src/pipeline/dispatch-batch.ts +187 -0
- package/src/pipeline/dispatch-query.ts +165 -0
- package/src/pipeline/dispatch-shared.ts +826 -0
- package/src/pipeline/dispatch-stream.ts +90 -0
- package/src/pipeline/dispatch-write.ts +451 -0
- package/src/pipeline/dispatcher-utils.ts +1 -1
- package/src/pipeline/dispatcher.ts +44 -1372
- package/src/pipeline/entity-cache.ts +2 -33
- package/src/pipeline/event-consumer-state.ts +30 -2
- package/src/pipeline/event-dispatcher-admin.ts +293 -0
- package/src/pipeline/event-dispatcher-delivery.ts +305 -0
- package/src/pipeline/event-dispatcher.ts +85 -542
- package/src/pipeline/index.ts +2 -0
- package/src/pipeline/msp-rebuild.ts +42 -3
- package/src/pipeline/multi-stream-apply-context.ts +4 -42
- package/src/pipeline/projection-rebuild.ts +105 -3
- package/src/pipeline/system-hooks.ts +144 -1
- package/src/rate-limit/__tests__/resolver.integration.test.ts +18 -0
- package/src/rate-limit/resolver.ts +16 -32
- package/src/schema-cli.ts +76 -16
- package/src/search/__tests__/meilisearch-adapter.integration.test.ts +207 -185
- package/src/search/__tests__/meilisearch-ids.test.ts +30 -0
- package/src/search/__tests__/reindex-entity.integration.test.ts +144 -0
- package/src/search/index.ts +6 -0
- package/src/search/meilisearch-adapter.ts +13 -12
- package/src/search/reindex-entity.ts +177 -0
- package/src/search/types.ts +1 -39
- package/src/secrets/__tests__/contains-secret.test.ts +34 -0
- package/src/secrets/__tests__/envelope-cipher.test.ts +59 -0
- package/src/secrets/__tests__/envelope.test.ts +1 -1
- package/src/secrets/dek-cache.ts +26 -5
- package/src/secrets/envelope-cipher.ts +53 -0
- package/src/secrets/envelope.ts +5 -3
- package/src/secrets/index.ts +13 -1
- package/src/secrets/stored-envelope.ts +46 -0
- package/src/secrets/types.ts +2 -162
- package/src/stack/__tests__/event-collector.test.ts +42 -0
- package/src/stack/__tests__/setup-test-stack-jobs.integration.test.ts +125 -0
- package/src/stack/db.ts +2 -1
- package/src/stack/push-entity-projection-tables.ts +4 -3
- package/src/stack/redis.ts +8 -0
- package/src/stack/request-helper.ts +38 -2
- package/src/stack/table-helpers.ts +6 -4
- package/src/stack/test-stack.ts +249 -131
- package/src/testing/__tests__/late-bound.test.ts +32 -0
- package/src/testing/__tests__/wait-for.test.ts +59 -0
- package/src/testing/boot-validator-fixture.ts +121 -0
- package/src/testing/e2e-generator.ts +8 -0
- package/src/testing/file-provider-contract.ts +104 -0
- package/src/testing/handler-context.ts +3 -1
- package/src/testing/index.ts +5 -0
- package/src/testing/late-bound.ts +5 -3
- package/src/testing/mutable-master-key-provider.ts +29 -3
- package/src/testing/wait-for.ts +3 -0
- package/src/testing/without-ambient-temporal.ts +14 -0
- package/src/time/__tests__/tz-dateline.test.ts +8 -10
- package/src/time/geo-tz.ts +1 -32
- package/src/time/index.ts +1 -0
- package/src/time/legacy-date.ts +18 -0
- package/src/time/polyfill.ts +21 -38
- package/src/time/tz-context.ts +44 -85
- package/src/ui-types/app-schema.ts +12 -0
- package/src/ui-types/index.ts +20 -7
- package/src/utils/__tests__/safe-json-temporal.test.ts +18 -0
- package/src/utils/safe-json.ts +13 -1
- package/src/__tests__/raw-table.integration.test.ts +0 -116
- package/src/bun-db/__tests__/bun-test-stack.ts +0 -6
- package/src/db/__tests__/encryption.test.ts +0 -39
- package/src/db/encryption.ts +0 -39
- package/src/db/row-helpers.ts +0 -4
- package/src/engine/__tests__/raw-table.test.ts +0 -150
- package/src/engine/__tests__/unmanaged-table.test.ts +0 -127
- package/src/engine/feature-ast/__tests__/visual-tree-parse.test.ts +0 -184
- package/src/engine/feature-ast/extractors/round4.ts +0 -1366
|
@@ -117,6 +117,55 @@ describe("validateBoot — PII annotations", () => {
|
|
|
117
117
|
expect(() => validateBoot([feature])).toThrow(/multiple subject-key annotations/);
|
|
118
118
|
});
|
|
119
119
|
|
|
120
|
+
test("naked sensitive (no pii/encrypted) throws — event log needs ciphertext-at-rest (#967)", () => {
|
|
121
|
+
const feature = defineFeature("test", (r) => {
|
|
122
|
+
r.entity(
|
|
123
|
+
"vault",
|
|
124
|
+
createEntity({
|
|
125
|
+
fields: {
|
|
126
|
+
apiToken: createTextField({ sensitive: true }),
|
|
127
|
+
},
|
|
128
|
+
}),
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
expect(() => validateBoot([feature])).toThrow(/sensitive: true.*without ciphertext-at-rest/);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("sensitive with pii subject annotation passes", () => {
|
|
135
|
+
const feature = defineFeature("test", (r) => {
|
|
136
|
+
r.entity(
|
|
137
|
+
"vault",
|
|
138
|
+
createEntity({
|
|
139
|
+
fields: {
|
|
140
|
+
apiToken: createTextField({ sensitive: true, pii: true }),
|
|
141
|
+
},
|
|
142
|
+
}),
|
|
143
|
+
);
|
|
144
|
+
});
|
|
145
|
+
expect(() => validateBoot([feature])).not.toThrow();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("sensitive with encrypted passes", () => {
|
|
149
|
+
process.env["KUMIKO_SECRETS_MASTER_KEY_V1"] = Buffer.from(
|
|
150
|
+
"0123456789abcdef0123456789abcdef",
|
|
151
|
+
).toString("base64");
|
|
152
|
+
try {
|
|
153
|
+
const feature = defineFeature("test", (r) => {
|
|
154
|
+
r.entity(
|
|
155
|
+
"vault",
|
|
156
|
+
createEntity({
|
|
157
|
+
fields: {
|
|
158
|
+
apiToken: createTextField({ sensitive: true, encrypted: true }),
|
|
159
|
+
},
|
|
160
|
+
}),
|
|
161
|
+
);
|
|
162
|
+
});
|
|
163
|
+
expect(() => validateBoot([feature])).not.toThrow();
|
|
164
|
+
} finally {
|
|
165
|
+
delete process.env["KUMIKO_SECRETS_MASTER_KEY_V1"];
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
120
169
|
test("userOwned.ownerField pointing to non-existent field throws", () => {
|
|
121
170
|
const feature = defineFeature("test", (r) => {
|
|
122
171
|
r.entity(
|
|
@@ -133,19 +182,36 @@ describe("validateBoot — PII annotations", () => {
|
|
|
133
182
|
);
|
|
134
183
|
});
|
|
135
184
|
|
|
136
|
-
test("userOwned.ownerField
|
|
185
|
+
test("userOwned.ownerField on a text field passes (ES userId-by-convention)", () => {
|
|
137
186
|
const feature = defineFeature("test", (r) => {
|
|
138
187
|
r.entity(
|
|
139
188
|
"comment",
|
|
140
189
|
createEntity({
|
|
141
190
|
fields: {
|
|
142
|
-
body: createLongTextField({ userOwned: { ownerField: "
|
|
143
|
-
|
|
191
|
+
body: createLongTextField({ userOwned: { ownerField: "authorId" } }),
|
|
192
|
+
authorId: createTextField(),
|
|
144
193
|
},
|
|
145
194
|
}),
|
|
146
195
|
);
|
|
147
196
|
});
|
|
148
|
-
expect(() => validateBoot([feature])).toThrow(
|
|
197
|
+
expect(() => validateBoot([feature])).not.toThrow();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test("userOwned.ownerField pointing to a non-id-capable field throws", () => {
|
|
201
|
+
const feature = defineFeature("test", (r) => {
|
|
202
|
+
r.entity(
|
|
203
|
+
"comment",
|
|
204
|
+
createEntity({
|
|
205
|
+
fields: {
|
|
206
|
+
body: createLongTextField({ userOwned: { ownerField: "isPublic" } }),
|
|
207
|
+
isPublic: { type: "boolean" },
|
|
208
|
+
},
|
|
209
|
+
}),
|
|
210
|
+
);
|
|
211
|
+
});
|
|
212
|
+
expect(() => validateBoot([feature])).toThrow(
|
|
213
|
+
/must be a reference or text \(userId\) field, got type "boolean"/,
|
|
214
|
+
);
|
|
149
215
|
});
|
|
150
216
|
|
|
151
217
|
test("userOwned.ownerField referencing non-user entity warns", () => {
|
|
@@ -568,3 +634,179 @@ describe("validateBoot — retention", () => {
|
|
|
568
634
|
expect(matchingWarn).toBeUndefined();
|
|
569
635
|
});
|
|
570
636
|
});
|
|
637
|
+
|
|
638
|
+
describe("validateBoot — lookupable / blind-index (#818)", () => {
|
|
639
|
+
test("lookupable on subject-annotated text field passes", () => {
|
|
640
|
+
const feature = defineFeature("test", (r) => {
|
|
641
|
+
r.entity(
|
|
642
|
+
"user",
|
|
643
|
+
createEntity({
|
|
644
|
+
fields: {
|
|
645
|
+
email: createTextField({ pii: true, lookupable: true }),
|
|
646
|
+
},
|
|
647
|
+
}),
|
|
648
|
+
);
|
|
649
|
+
});
|
|
650
|
+
expect(() => validateBoot([feature])).not.toThrow();
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
test("lookupable without subject annotation throws", () => {
|
|
654
|
+
const feature = defineFeature("test", (r) => {
|
|
655
|
+
r.entity(
|
|
656
|
+
"doc",
|
|
657
|
+
createEntity({
|
|
658
|
+
fields: {
|
|
659
|
+
slug: createTextField({ lookupable: true }),
|
|
660
|
+
},
|
|
661
|
+
}),
|
|
662
|
+
);
|
|
663
|
+
});
|
|
664
|
+
expect(() => validateBoot([feature])).toThrow(/lookupable.*without a subject annotation/);
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
test("lookupable on non-text field throws", () => {
|
|
668
|
+
const feature = defineFeature("test", (r) => {
|
|
669
|
+
r.entity(
|
|
670
|
+
"person",
|
|
671
|
+
createEntity({
|
|
672
|
+
fields: {
|
|
673
|
+
// longText carries PiiAnnotations too — lookupable must still be rejected.
|
|
674
|
+
bio: createLongTextField({ userOwned: { ownerField: "ownerId" }, lookupable: true }),
|
|
675
|
+
ownerId: createTextField({ required: true }),
|
|
676
|
+
},
|
|
677
|
+
}),
|
|
678
|
+
);
|
|
679
|
+
});
|
|
680
|
+
expect(() => validateBoot([feature])).toThrow(/only apply to text fields/);
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
test("searchable combined with a subject annotation throws", () => {
|
|
684
|
+
const feature = defineFeature("test", (r) => {
|
|
685
|
+
r.entity(
|
|
686
|
+
"user",
|
|
687
|
+
createEntity({
|
|
688
|
+
fields: {
|
|
689
|
+
displayName: createTextField({ pii: true, searchable: true }),
|
|
690
|
+
},
|
|
691
|
+
}),
|
|
692
|
+
);
|
|
693
|
+
});
|
|
694
|
+
expect(() => validateBoot([feature])).toThrow(/searchable.*cannot work/);
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
test("sortable combined with a subject annotation throws", () => {
|
|
698
|
+
const feature = defineFeature("test", (r) => {
|
|
699
|
+
r.entity(
|
|
700
|
+
"user",
|
|
701
|
+
createEntity({
|
|
702
|
+
fields: {
|
|
703
|
+
email: createTextField({ pii: true, sortable: true }),
|
|
704
|
+
},
|
|
705
|
+
}),
|
|
706
|
+
);
|
|
707
|
+
});
|
|
708
|
+
expect(() => validateBoot([feature])).toThrow(/sortable.*cannot work/);
|
|
709
|
+
});
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
describe("validateBoot — piiEncrypted (kumiko-platform#231/#456)", () => {
|
|
713
|
+
test("piiEncrypted on a plain text field passes", () => {
|
|
714
|
+
const feature = defineFeature("test", (r) => {
|
|
715
|
+
r.entity(
|
|
716
|
+
"tenant",
|
|
717
|
+
createEntity({
|
|
718
|
+
fields: {
|
|
719
|
+
iban: createTextField({
|
|
720
|
+
piiEncrypted: true,
|
|
721
|
+
tenantOwned: true,
|
|
722
|
+
access: { read: ["TenantAdmin"] },
|
|
723
|
+
}),
|
|
724
|
+
},
|
|
725
|
+
}),
|
|
726
|
+
);
|
|
727
|
+
});
|
|
728
|
+
expect(() => validateBoot([feature])).not.toThrow();
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
test("piiEncrypted on a non-text field throws", () => {
|
|
732
|
+
const feature = defineFeature("test", (r) => {
|
|
733
|
+
r.entity(
|
|
734
|
+
"tenant",
|
|
735
|
+
createEntity({
|
|
736
|
+
fields: {
|
|
737
|
+
amount: { ...createNumberField(), piiEncrypted: true } as unknown as ReturnType<
|
|
738
|
+
typeof createNumberField
|
|
739
|
+
>,
|
|
740
|
+
},
|
|
741
|
+
}),
|
|
742
|
+
);
|
|
743
|
+
});
|
|
744
|
+
expect(() => validateBoot([feature])).toThrow(/piiEncrypted.*only applies to text fields/);
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
test("piiEncrypted without a subject annotation throws (kumiko-platform#457)", () => {
|
|
748
|
+
const feature = defineFeature("test", (r) => {
|
|
749
|
+
r.entity(
|
|
750
|
+
"tenant",
|
|
751
|
+
createEntity({
|
|
752
|
+
fields: {
|
|
753
|
+
iban: createTextField({ piiEncrypted: true }),
|
|
754
|
+
},
|
|
755
|
+
}),
|
|
756
|
+
);
|
|
757
|
+
});
|
|
758
|
+
expect(() => validateBoot([feature])).toThrow(/piiEncrypted.*without a subject annotation/);
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
test("piiEncrypted combined with searchable throws", () => {
|
|
762
|
+
const feature = defineFeature("test", (r) => {
|
|
763
|
+
r.entity(
|
|
764
|
+
"tenant",
|
|
765
|
+
createEntity({
|
|
766
|
+
fields: {
|
|
767
|
+
iban: createTextField({
|
|
768
|
+
piiEncrypted: true,
|
|
769
|
+
tenantOwned: true,
|
|
770
|
+
access: { read: ["TenantAdmin"] },
|
|
771
|
+
searchable: true,
|
|
772
|
+
}),
|
|
773
|
+
},
|
|
774
|
+
}),
|
|
775
|
+
);
|
|
776
|
+
});
|
|
777
|
+
expect(() => validateBoot([feature])).toThrow(/piiEncrypted.*searchable.*cannot work/);
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
test("piiEncrypted combined with sortable throws", () => {
|
|
781
|
+
const feature = defineFeature("test", (r) => {
|
|
782
|
+
r.entity(
|
|
783
|
+
"tenant",
|
|
784
|
+
createEntity({
|
|
785
|
+
fields: {
|
|
786
|
+
iban: createTextField({
|
|
787
|
+
piiEncrypted: true,
|
|
788
|
+
tenantOwned: true,
|
|
789
|
+
access: { read: ["TenantAdmin"] },
|
|
790
|
+
sortable: true,
|
|
791
|
+
}),
|
|
792
|
+
},
|
|
793
|
+
}),
|
|
794
|
+
);
|
|
795
|
+
});
|
|
796
|
+
expect(() => validateBoot([feature])).toThrow(/piiEncrypted.*sortable.*cannot work/);
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
test("piiEncrypted without access.read throws (kumiko-platform#460)", () => {
|
|
800
|
+
const feature = defineFeature("test", (r) => {
|
|
801
|
+
r.entity(
|
|
802
|
+
"tenant",
|
|
803
|
+
createEntity({
|
|
804
|
+
fields: {
|
|
805
|
+
iban: createTextField({ piiEncrypted: true, tenantOwned: true }),
|
|
806
|
+
},
|
|
807
|
+
}),
|
|
808
|
+
);
|
|
809
|
+
});
|
|
810
|
+
expect(() => validateBoot([feature])).toThrow(/piiEncrypted.*without.*access.*read/);
|
|
811
|
+
});
|
|
812
|
+
});
|
|
@@ -2,7 +2,8 @@ import { describe, expect, test } from "bun:test";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import type { SchemaTable } from "../../db/dialect";
|
|
4
4
|
import { table, text } from "../../db/dialect";
|
|
5
|
-
import {
|
|
5
|
+
import { withBootValidatorFixture } from "../../testing/boot-validator-fixture";
|
|
6
|
+
import { validateBoot as validateBootRaw } from "../boot-validator";
|
|
6
7
|
import { createSystemConfig, createTenantConfig } from "../config-helpers";
|
|
7
8
|
import {
|
|
8
9
|
createDerivedField,
|
|
@@ -11,8 +12,13 @@ import {
|
|
|
11
12
|
createTextField,
|
|
12
13
|
defineFeature,
|
|
13
14
|
from,
|
|
15
|
+
registerEntityCrud,
|
|
14
16
|
} from "../index";
|
|
15
17
|
|
|
18
|
+
function validateBoot(features: Parameters<typeof validateBootRaw>[0]): void {
|
|
19
|
+
validateBootRaw(withBootValidatorFixture(features));
|
|
20
|
+
}
|
|
21
|
+
|
|
16
22
|
describe("boot-validator", () => {
|
|
17
23
|
test("passes for valid features with no issues", () => {
|
|
18
24
|
const features = [
|
|
@@ -106,8 +112,10 @@ describe("boot-validator", () => {
|
|
|
106
112
|
expect(() => validateBoot(features)).toThrow(/token.*cannot be both encrypted and sortable/i);
|
|
107
113
|
});
|
|
108
114
|
|
|
109
|
-
test("allows encrypted field when
|
|
110
|
-
process.env["
|
|
115
|
+
test("allows encrypted field when a master key keyring is available", () => {
|
|
116
|
+
process.env["KUMIKO_SECRETS_MASTER_KEY_V1"] = Buffer.from(
|
|
117
|
+
"0123456789abcdef0123456789abcdef",
|
|
118
|
+
).toString("base64");
|
|
111
119
|
try {
|
|
112
120
|
const features = [
|
|
113
121
|
defineFeature("a", (r) => {
|
|
@@ -124,12 +132,34 @@ describe("boot-validator", () => {
|
|
|
124
132
|
];
|
|
125
133
|
expect(() => validateBoot(features)).not.toThrow();
|
|
126
134
|
} finally {
|
|
127
|
-
delete process.env["
|
|
135
|
+
delete process.env["KUMIKO_SECRETS_MASTER_KEY_V1"];
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("throws at boot when the master key is malformed (not 32 bytes)", () => {
|
|
140
|
+
process.env["KUMIKO_SECRETS_MASTER_KEY_V1"] = Buffer.from("too-short").toString("base64");
|
|
141
|
+
try {
|
|
142
|
+
const features = [
|
|
143
|
+
defineFeature("a", (r) => {
|
|
144
|
+
r.entity(
|
|
145
|
+
"secret",
|
|
146
|
+
createEntity({
|
|
147
|
+
table: "Secrets",
|
|
148
|
+
fields: {
|
|
149
|
+
apiKey: { type: "text", encrypted: true },
|
|
150
|
+
},
|
|
151
|
+
}),
|
|
152
|
+
);
|
|
153
|
+
}),
|
|
154
|
+
];
|
|
155
|
+
expect(() => validateBoot(features)).toThrow(/master key/i);
|
|
156
|
+
} finally {
|
|
157
|
+
delete process.env["KUMIKO_SECRETS_MASTER_KEY_V1"];
|
|
128
158
|
}
|
|
129
159
|
});
|
|
130
160
|
|
|
131
|
-
test("throws when encrypted fields exist but
|
|
132
|
-
delete process.env["
|
|
161
|
+
test("throws when encrypted fields exist but no master key is available", () => {
|
|
162
|
+
delete process.env["KUMIKO_SECRETS_MASTER_KEY_V1"];
|
|
133
163
|
const features = [
|
|
134
164
|
defineFeature("a", (r) => {
|
|
135
165
|
r.entity(
|
|
@@ -143,16 +173,16 @@ describe("boot-validator", () => {
|
|
|
143
173
|
);
|
|
144
174
|
}),
|
|
145
175
|
];
|
|
146
|
-
expect(() => validateBoot(features)).toThrow(/
|
|
176
|
+
expect(() => validateBoot(features)).toThrow(/master key/i);
|
|
147
177
|
});
|
|
148
178
|
|
|
149
|
-
test("throws when longText encrypted field exists but
|
|
179
|
+
test("throws when longText encrypted field exists but no master key is available", () => {
|
|
150
180
|
// Drift-pin Sprint-5b-vorab-Audit Issue 1: validateEncryptedFields
|
|
151
181
|
// hatte `if (field.type !== "text") continue;` und ignorierte
|
|
152
182
|
// longText-encrypted-fields silently — ENCRYPTION_KEY-check wurde
|
|
153
183
|
// nie getriggert, encryption silent broken. Jetzt: beide string-
|
|
154
184
|
// typed fields werden gechecked.
|
|
155
|
-
delete process.env["
|
|
185
|
+
delete process.env["KUMIKO_SECRETS_MASTER_KEY_V1"];
|
|
156
186
|
const features = [
|
|
157
187
|
defineFeature("a", (r) => {
|
|
158
188
|
r.entity(
|
|
@@ -166,7 +196,7 @@ describe("boot-validator", () => {
|
|
|
166
196
|
);
|
|
167
197
|
}),
|
|
168
198
|
];
|
|
169
|
-
expect(() => validateBoot(features)).toThrow(/
|
|
199
|
+
expect(() => validateBoot(features)).toThrow(/master key/i);
|
|
170
200
|
});
|
|
171
201
|
|
|
172
202
|
test("rejects encrypted text field that is also filterable", () => {
|
|
@@ -523,6 +553,21 @@ describe("boot-validator", () => {
|
|
|
523
553
|
expect(() => validateBoot(features)).toThrow(/a:query:list.*missing an access rule/i);
|
|
524
554
|
});
|
|
525
555
|
|
|
556
|
+
test("registerEntityCrud without access options fails boot (no implicit openToAll)", () => {
|
|
557
|
+
const widgetEntity = createEntity({
|
|
558
|
+
table: "phase0_widgets",
|
|
559
|
+
fields: { name: createTextField({ required: true }) },
|
|
560
|
+
});
|
|
561
|
+
const features = [
|
|
562
|
+
defineFeature("phase0", (r) => {
|
|
563
|
+
registerEntityCrud(r, "widget", widgetEntity);
|
|
564
|
+
}),
|
|
565
|
+
];
|
|
566
|
+
expect(() => validateBoot(features)).toThrow(
|
|
567
|
+
/phase0:write:widget:create.*missing an access rule/i,
|
|
568
|
+
);
|
|
569
|
+
});
|
|
570
|
+
|
|
526
571
|
test("accepts role-based access rule", () => {
|
|
527
572
|
const features = [
|
|
528
573
|
defineFeature("a", (r) => {
|
|
@@ -545,6 +590,66 @@ describe("boot-validator", () => {
|
|
|
545
590
|
expect(() => validateBoot(features)).not.toThrow();
|
|
546
591
|
});
|
|
547
592
|
|
|
593
|
+
test("throws when a stream handler has no access rule", () => {
|
|
594
|
+
const features = [
|
|
595
|
+
defineFeature("a", (r) => {
|
|
596
|
+
r.streamHandler("chat:complete", z.object({}), async function* () {});
|
|
597
|
+
}),
|
|
598
|
+
];
|
|
599
|
+
expect(() => validateBoot(features)).toThrow(/a:stream:chat:complete.*missing an access rule/i);
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
test("object-form streamHandler with access rule passes boot", () => {
|
|
603
|
+
const features = [
|
|
604
|
+
defineFeature("a", (r) => {
|
|
605
|
+
r.streamHandler({
|
|
606
|
+
name: "chat:complete",
|
|
607
|
+
schema: z.object({}),
|
|
608
|
+
handler: async function* () {},
|
|
609
|
+
access: { roles: ["User"] },
|
|
610
|
+
});
|
|
611
|
+
}),
|
|
612
|
+
];
|
|
613
|
+
expect(() => validateBoot(features)).not.toThrow();
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
test("accepts openToAll access rule on a stream handler", () => {
|
|
617
|
+
const features = [
|
|
618
|
+
defineFeature("a", (r) => {
|
|
619
|
+
r.streamHandler("chat:complete", z.object({}), async function* () {}, {
|
|
620
|
+
access: { openToAll: true },
|
|
621
|
+
});
|
|
622
|
+
}),
|
|
623
|
+
];
|
|
624
|
+
expect(() => validateBoot(features)).not.toThrow();
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
test("throws when an anonymous stream handler uses a user-bucketed rate limit", () => {
|
|
628
|
+
const features = [
|
|
629
|
+
defineFeature("a", (r) => {
|
|
630
|
+
r.streamHandler("chat:complete", z.object({}), async function* () {}, {
|
|
631
|
+
access: { roles: ["anonymous"] },
|
|
632
|
+
rateLimit: { per: "user", limit: 10, windowSeconds: 60 },
|
|
633
|
+
});
|
|
634
|
+
}),
|
|
635
|
+
];
|
|
636
|
+
expect(() => validateBoot(features)).toThrow(
|
|
637
|
+
/stream handler "a:stream:chat:complete" allows anonymous callers/i,
|
|
638
|
+
);
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
test("accepts an anonymous stream handler with an ip-bucketed rate limit", () => {
|
|
642
|
+
const features = [
|
|
643
|
+
defineFeature("a", (r) => {
|
|
644
|
+
r.streamHandler("chat:complete", z.object({}), async function* () {}, {
|
|
645
|
+
access: { roles: ["anonymous"] },
|
|
646
|
+
rateLimit: { per: "ip", limit: 10, windowSeconds: 60 },
|
|
647
|
+
});
|
|
648
|
+
}),
|
|
649
|
+
];
|
|
650
|
+
expect(() => validateBoot(features)).not.toThrow();
|
|
651
|
+
});
|
|
652
|
+
|
|
548
653
|
describe("config key bounds consistency", () => {
|
|
549
654
|
test("accepts number key with consistent bounds + default", () => {
|
|
550
655
|
const features = [
|
|
@@ -1186,8 +1291,9 @@ describe("boot-validator", () => {
|
|
|
1186
1291
|
// --- entityList column-renderer form-check ---
|
|
1187
1292
|
// Validator akzeptiert die `{ react: { __component: "Name" } }`-Form
|
|
1188
1293
|
// (PlatformComponent → client-side Registry-Lookup) und prüft sie
|
|
1189
|
-
// strukturell.
|
|
1190
|
-
//
|
|
1294
|
+
// strukturell. null/undefined, native-only und andere Formen bleiben
|
|
1295
|
+
// opak. Function-Renderer werfen (action-wiring.ts validateFieldWiring) —
|
|
1296
|
+
// von JSON.stringify verworfen, sonst stiller No-op auf dem Client.
|
|
1191
1297
|
describe("entityList column renderer form", () => {
|
|
1192
1298
|
function shopFeature(renderer: unknown) {
|
|
1193
1299
|
return defineFeature("shop", (r) => {
|
|
@@ -1205,8 +1311,10 @@ describe("boot-validator", () => {
|
|
|
1205
1311
|
});
|
|
1206
1312
|
}
|
|
1207
1313
|
|
|
1208
|
-
test("function-renderer →
|
|
1209
|
-
expect(() => validateBoot([shopFeature((v: unknown) => String(v))])).
|
|
1314
|
+
test("function-renderer → Throw (Function wird von JSON.stringify verworfen)", () => {
|
|
1315
|
+
expect(() => validateBoot([shopFeature((v: unknown) => String(v))])).toThrow(
|
|
1316
|
+
/column "name" renderer is a function/,
|
|
1317
|
+
);
|
|
1210
1318
|
});
|
|
1211
1319
|
|
|
1212
1320
|
test("undefined renderer → kein Throw (Spalte ohne Renderer)", () => {
|
|
@@ -1249,6 +1357,102 @@ describe("boot-validator", () => {
|
|
|
1249
1357
|
});
|
|
1250
1358
|
});
|
|
1251
1359
|
|
|
1360
|
+
// --- function-renderer check on the non-entityEdit/entityList screen
|
|
1361
|
+
// types that share the same EditLayout/ListColumnSpec shapes
|
|
1362
|
+
// (action-wiring.ts's validateEditLayoutNoFunctions/validateColumnsNoFunctions) ---
|
|
1363
|
+
describe("function-renderer check on other EditLayout/columns screens", () => {
|
|
1364
|
+
test("actionForm field renderer as a function → Throw", () => {
|
|
1365
|
+
const features = [
|
|
1366
|
+
defineFeature("shop", (r) => {
|
|
1367
|
+
r.screen({
|
|
1368
|
+
id: "restock",
|
|
1369
|
+
type: "actionForm",
|
|
1370
|
+
handler: "shop:write:restock",
|
|
1371
|
+
fields: { qty: { type: "number" } } as never,
|
|
1372
|
+
layout: {
|
|
1373
|
+
sections: [
|
|
1374
|
+
{ fields: [{ field: "qty", renderer: (v: unknown) => String(v) }] as never },
|
|
1375
|
+
],
|
|
1376
|
+
},
|
|
1377
|
+
});
|
|
1378
|
+
}),
|
|
1379
|
+
];
|
|
1380
|
+
expect(() => validateBoot(features)).toThrow(
|
|
1381
|
+
/\(actionForm\) field "qty" renderer is a function/,
|
|
1382
|
+
);
|
|
1383
|
+
});
|
|
1384
|
+
|
|
1385
|
+
test("configEdit field renderer as a function → Throw", () => {
|
|
1386
|
+
const features = [
|
|
1387
|
+
defineFeature("shop", (r) => {
|
|
1388
|
+
r.config({
|
|
1389
|
+
keys: { "site-name": createTenantConfig("text", { default: "" }) },
|
|
1390
|
+
});
|
|
1391
|
+
r.screen({
|
|
1392
|
+
id: "settings",
|
|
1393
|
+
type: "configEdit",
|
|
1394
|
+
scope: "tenant",
|
|
1395
|
+
configKeys: { siteName: "shop:config:site-name" },
|
|
1396
|
+
fields: { siteName: { type: "text" } } as never,
|
|
1397
|
+
layout: {
|
|
1398
|
+
sections: [
|
|
1399
|
+
{
|
|
1400
|
+
fields: [{ field: "siteName", renderer: (v: unknown) => String(v) }] as never,
|
|
1401
|
+
},
|
|
1402
|
+
],
|
|
1403
|
+
},
|
|
1404
|
+
});
|
|
1405
|
+
}),
|
|
1406
|
+
];
|
|
1407
|
+
expect(() => validateBoot(features)).toThrow(
|
|
1408
|
+
/\(configEdit\) field "siteName" renderer is a function/,
|
|
1409
|
+
);
|
|
1410
|
+
});
|
|
1411
|
+
|
|
1412
|
+
test("projectionDetail field renderer as a function → Throw", () => {
|
|
1413
|
+
const features = [
|
|
1414
|
+
defineFeature("shop", (r) => {
|
|
1415
|
+
r.screen({
|
|
1416
|
+
id: "order-detail",
|
|
1417
|
+
type: "projectionDetail",
|
|
1418
|
+
query: "shop:query:order-detail",
|
|
1419
|
+
layout: {
|
|
1420
|
+
sections: [
|
|
1421
|
+
{ fields: [{ field: "total", renderer: (v: unknown) => String(v) }] as never },
|
|
1422
|
+
],
|
|
1423
|
+
},
|
|
1424
|
+
});
|
|
1425
|
+
}),
|
|
1426
|
+
];
|
|
1427
|
+
expect(() => validateBoot(features)).toThrow(
|
|
1428
|
+
/\(projectionDetail\) field "total" renderer is a function/,
|
|
1429
|
+
);
|
|
1430
|
+
});
|
|
1431
|
+
|
|
1432
|
+
test("dashboard list-panel column renderer as a function → Throw", () => {
|
|
1433
|
+
const features = [
|
|
1434
|
+
defineFeature("shop", (r) => {
|
|
1435
|
+
r.screen({
|
|
1436
|
+
id: "overview",
|
|
1437
|
+
type: "dashboard",
|
|
1438
|
+
panels: [
|
|
1439
|
+
{
|
|
1440
|
+
kind: "list",
|
|
1441
|
+
id: "recent-orders",
|
|
1442
|
+
label: "Recent Orders",
|
|
1443
|
+
query: "shop:query:recent-orders",
|
|
1444
|
+
columns: [{ field: "total", renderer: (v: unknown) => String(v) }] as never,
|
|
1445
|
+
},
|
|
1446
|
+
],
|
|
1447
|
+
});
|
|
1448
|
+
}),
|
|
1449
|
+
];
|
|
1450
|
+
expect(() => validateBoot(features)).toThrow(
|
|
1451
|
+
/\(dashboard-list\) column "total" renderer is a function/,
|
|
1452
|
+
);
|
|
1453
|
+
});
|
|
1454
|
+
});
|
|
1455
|
+
|
|
1252
1456
|
// --- entityList virtual (labeled) columns ---
|
|
1253
1457
|
// A column whose `field` is not an entity field is allowed IF it carries a
|
|
1254
1458
|
// `label` — a presentational column drawn by a columnRenderer component (e.g.
|
|
@@ -2558,7 +2762,7 @@ describe("boot-validator", () => {
|
|
|
2558
2762
|
});
|
|
2559
2763
|
|
|
2560
2764
|
test("number-Field OHNE sortable → Throw (sortable: true ist Pflicht)", () => {
|
|
2561
|
-
expect(() =>
|
|
2765
|
+
expect(() => validateBootRaw([buildFeature("rank", { rank: { type: "number" } })])).toThrow(
|
|
2562
2766
|
/is not sortable/,
|
|
2563
2767
|
);
|
|
2564
2768
|
});
|