@cosmicdrift/kumiko-framework 0.386.0 → 0.388.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/AGENTS.md +6 -1
- package/LICENSE +21 -57
- package/README.md +2 -2
- package/agents/examples/anonymous-access.md +1 -1
- package/agents/examples/apex-landing.md +204 -0
- package/agents/examples/api-worker-split.md +64 -0
- package/agents/examples/app-config.md +14 -0
- package/agents/examples/auth-mfa.md +115 -0
- package/agents/examples/env-composition.md +28 -1
- package/agents/examples/legal-pages.md +1 -1
- package/agents/examples/prod-boot-options.md +16 -0
- package/agents/examples/rate-limiting.md +48 -0
- package/agents/examples/record-detail-layout.md +692 -0
- package/agents/examples/seed-migration.md +212 -0
- package/agents/examples/wizard-form.md +941 -0
- package/agents/examples/writeform-section.md +548 -0
- package/agents/features.md +19 -2
- package/agents/widgets.md +40 -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-error-route.js +8 -2
- package/dist/api/client-ip.js +3 -1
- package/dist/api/extra-route.js +1 -1
- package/dist/api/index.d.ts +3 -1
- package/dist/api/index.js +3 -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/request-context.d.ts +1 -1
- package/dist/api/request-context.js +1 -1
- package/dist/api/routes.js +2 -2
- package/dist/api/server-boot-checks.d.ts +10 -0
- package/dist/api/server-boot-checks.js +157 -0
- package/dist/api/server-event-dispatcher.d.ts +26 -0
- package/dist/api/server-event-dispatcher.js +208 -0
- package/dist/api/server-extra-routes.d.ts +11 -0
- package/dist/api/server-extra-routes.js +203 -0
- package/dist/api/server-middleware.d.ts +20 -0
- package/dist/api/server-middleware.js +211 -0
- package/dist/api/server-routes.d.ts +19 -0
- package/dist/api/server-routes.js +293 -0
- package/dist/api/server-websocket-routes.d.ts +4 -0
- package/dist/api/server-websocket-routes.js +108 -0
- package/dist/api/server-wiring.d.ts +32 -0
- package/dist/api/server-wiring.js +236 -0
- package/dist/api/server.d.ts +10 -15
- package/dist/api/server.js +48 -1250
- package/dist/api/session-cookies.js +4 -4
- package/dist/api/sse-broker.js +1 -1
- package/dist/api/sse-route.d.ts +13 -13
- package/dist/api/sse-route.js +31 -27
- 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/owner-transition-rekey.d.ts +5 -1
- package/dist/crypto/owner-transition-rekey.js +1 -1
- package/dist/crypto/pg-kms-adapter.js +2 -6
- package/dist/crypto/pii-field-encryption.d.ts +5 -1
- package/dist/crypto/pii-field-encryption.js +7 -5
- package/dist/crypto/subject-resolver.d.ts +1 -1
- package/dist/crypto/subject-resolver.js +2 -2
- package/dist/db/aggregate-sql.d.ts +21 -0
- package/dist/db/aggregate-sql.js +155 -0
- 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/closed-connection.d.ts +9 -0
- package/dist/db/closed-connection.js +95 -0
- 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 +3 -3
- package/dist/db/distinct-on.d.ts +1 -1
- package/dist/db/driver-row-coercion.d.ts +2 -0
- package/dist/db/driver-row-coercion.js +186 -0
- package/dist/db/driver-value-preparation.d.ts +16 -0
- package/dist/db/driver-value-preparation.js +99 -0
- package/dist/db/eagerload.d.ts +3 -3
- package/dist/db/eagerload.js +21 -12
- package/dist/db/entity-field-encryption.d.ts +1 -0
- package/dist/db/entity-field-encryption.js +1 -1
- package/dist/db/entity-table-meta.d.ts +2 -2
- package/dist/db/entity-table-meta.js +105 -112
- package/dist/db/event-store-executor-context.d.ts +3 -2
- package/dist/db/event-store-executor-context.js +13 -13
- package/dist/db/event-store-executor-read-aggregate.d.ts +8 -0
- package/dist/db/event-store-executor-read-aggregate.js +112 -0
- package/dist/db/event-store-executor-read-filter.d.ts +31 -0
- package/dist/db/event-store-executor-read-filter.js +203 -0
- package/dist/db/event-store-executor-read-paging.d.ts +14 -0
- package/dist/db/event-store-executor-read-paging.js +99 -0
- package/dist/db/event-store-executor-read-rows.d.ts +6 -0
- package/dist/db/event-store-executor-read-rows.js +32 -0
- package/dist/db/event-store-executor-read-search.d.ts +28 -0
- package/dist/db/event-store-executor-read-search.js +221 -0
- package/dist/db/event-store-executor-read-sort.d.ts +41 -0
- package/dist/db/event-store-executor-read-sort.js +130 -0
- package/dist/db/event-store-executor-read-where.d.ts +24 -0
- package/dist/db/event-store-executor-read-where.js +81 -0
- package/dist/db/event-store-executor-read.d.ts +2 -9
- package/dist/db/event-store-executor-read.js +50 -704
- package/dist/db/event-store-executor-write-steps.d.ts +20 -0
- package/dist/db/event-store-executor-write-steps.js +100 -0
- package/dist/db/event-store-executor-write.js +401 -510
- package/dist/db/index.d.ts +6 -2
- package/dist/db/index.js +5 -1
- package/dist/db/list-aggregate-labels.d.ts +14 -0
- package/dist/db/list-aggregate-labels.js +26 -0
- 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 +97 -105
- package/dist/db/migrate-runner.js +29 -26
- 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 +3 -16
- package/dist/db/query.js +8 -761
- package/dist/db/rebuild-marker.js +20 -21
- package/dist/db/reference-data.js +6 -1
- 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 +137 -190
- package/dist/db/table-info.d.ts +10 -0
- package/dist/db/table-info.js +45 -0
- package/dist/db/temporal-value-guards.d.ts +6 -0
- package/dist/db/temporal-value-guards.js +15 -0
- package/dist/db/tenant-db-runner.d.ts +1 -0
- package/dist/db/tenant-db-runner.js +3 -0
- package/dist/db/tenant-db.d.ts +2 -2
- package/dist/db/tenant-db.js +48 -11
- package/dist/db/where-clause.d.ts +6 -0
- package/dist/db/where-clause.js +236 -0
- package/dist/derivatives/delete-stored-file.js +6 -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/boot-check.js +1 -1
- package/dist/engine/boot-validator/config-deps.js +7 -6
- package/dist/engine/boot-validator/detail-screens.js +4 -4
- package/dist/engine/boot-validator/entity-handler.d.ts +1 -0
- package/dist/engine/boot-validator/entity-handler.js +77 -61
- package/dist/engine/boot-validator/form-derivations.js +35 -1
- package/dist/engine/boot-validator/gdpr-storage.js +4 -4
- package/dist/engine/boot-validator/i18n-keys.js +6 -4
- package/dist/engine/boot-validator/index.d.ts +2 -2
- package/dist/engine/boot-validator/index.js +22 -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 +258 -187
- 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.d.ts +1 -0
- package/dist/engine/boot-validator/screens-actions.js +23 -17
- package/dist/engine/boot-validator/screens-dashboard.js +2 -5
- package/dist/engine/boot-validator/screens-forms.js +12 -17
- package/dist/engine/boot-validator/screens-kind-config-edit.d.ts +3 -0
- package/dist/engine/boot-validator/screens-kind-config-edit.js +88 -0
- package/dist/engine/boot-validator/screens-kind-entity-edit.d.ts +3 -0
- package/dist/engine/boot-validator/screens-kind-entity-edit.js +55 -0
- package/dist/engine/boot-validator/screens-kind-entity-list.d.ts +3 -0
- package/dist/engine/boot-validator/screens-kind-entity-list.js +228 -0
- package/dist/engine/boot-validator/screens-kind-projection-detail.d.ts +3 -0
- package/dist/engine/boot-validator/screens-kind-projection-detail.js +235 -0
- package/dist/engine/boot-validator/screens-kind-projection-list.d.ts +3 -0
- package/dist/engine/boot-validator/screens-kind-projection-list.js +52 -0
- package/dist/engine/boot-validator/screens-kind-secret-mint.d.ts +3 -0
- package/dist/engine/boot-validator/screens-kind-secret-mint.js +28 -0
- package/dist/engine/boot-validator/screens-kind-shared.d.ts +37 -0
- package/dist/engine/boot-validator/screens-kind-shared.js +142 -0
- package/dist/engine/boot-validator/screens-lists.d.ts +2 -1
- package/dist/engine/boot-validator/screens-lists.js +26 -24
- package/dist/engine/boot-validator/screens.d.ts +0 -2
- package/dist/engine/boot-validator/screens.js +102 -869
- 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 +130 -170
- 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 +21 -13
- package/dist/engine/define-handler.js +13 -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.d.ts +13 -0
- package/dist/engine/entity-handlers.js +22 -34
- package/dist/engine/entity-list-runtime-options.d.ts +8 -0
- package/dist/engine/entity-list-runtime-options.js +18 -0
- 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 +2 -1
- package/dist/engine/feature-ast/extractors/hooks.js +1 -1
- package/dist/engine/feature-ast/extractors/round2.js +25 -6
- 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 +4 -182
- package/dist/engine/feature-ast/pattern-change-schema.d.ts +3 -0
- package/dist/engine/feature-ast/pattern-change-schema.js +2 -1
- package/dist/engine/feature-ast/pattern-id-matchers.d.ts +7 -0
- package/dist/engine/feature-ast/pattern-id-matchers.js +210 -0
- package/dist/engine/feature-ast/patterns.d.ts +11 -2
- package/dist/engine/feature-ast/patterns.js +43 -51
- package/dist/engine/feature-ast/render.js +50 -89
- package/dist/engine/feature-config-events-jobs.js +15 -12
- package/dist/engine/feature-entity-handlers.js +12 -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/form-derivations.js +18 -7
- package/dist/engine/handler-field-references.js +1 -1
- 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.d.ts +2 -0
- package/dist/engine/project-app-schema-for-roles.js +7 -5
- 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 +13 -13
- 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 +251 -233
- package/dist/engine/screen-filter-ops.js +17 -20
- package/dist/engine/screen-helpers.js +4 -3
- package/dist/engine/screen-kind.d.ts +7 -0
- package/dist/engine/screen-kind.js +1 -0
- 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/strip-disabled-features-from-app-schema.js +51 -14
- 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 +9 -28
- 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.d.ts +4 -1
- package/dist/entrypoint/index.js +10 -4
- package/dist/env/index.d.ts +7 -6
- package/dist/env/index.js +10 -9
- package/dist/errors/docs-url.d.ts +1 -1
- package/dist/errors/docs-url.js +22 -2
- 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 +29 -29
- 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 +7 -8
- package/dist/event-store/errors.js +1 -1
- package/dist/event-store/event-store.js +12 -6
- package/dist/event-store/rebuild-dead-letter.js +1 -1
- package/dist/files/bind-pending-file-refs.js +3 -3
- package/dist/files/create-file-ref-from-bytes.js +4 -1
- package/dist/files/feature.js +9 -10
- package/dist/files/file-handle.js +7 -9
- 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 +22 -13
- 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/resolve-content-type.js +43 -0
- package/dist/files/storage-tracking.js +2 -2
- package/dist/files/upload-ledger.js +7 -0
- 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 +188 -212
- package/dist/internal/stack-support.d.ts +2 -1
- package/dist/internal/stack-support.js +1 -1
- package/dist/jobs/job-runner-context.d.ts +32 -0
- package/dist/jobs/job-runner-context.js +305 -0
- package/dist/jobs/job-runner-execution.d.ts +7 -0
- package/dist/jobs/job-runner-execution.js +160 -0
- package/dist/jobs/job-runner-gates.d.ts +19 -0
- package/dist/jobs/job-runner-gates.js +198 -0
- package/dist/jobs/job-runner-helpers.d.ts +17 -3
- package/dist/jobs/job-runner-helpers.js +69 -4
- package/dist/jobs/job-runner-schedulers.d.ts +1 -1
- package/dist/jobs/job-runner-schedulers.js +3 -4
- package/dist/jobs/job-runner-scope.d.ts +44 -0
- package/dist/jobs/job-runner-scope.js +102 -0
- package/dist/jobs/job-runner-types.d.ts +12 -0
- package/dist/jobs/job-runner.d.ts +0 -1
- package/dist/jobs/job-runner.js +57 -669
- 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/escape-hatch-report.d.ts +3 -0
- package/dist/observability/escape-hatch-report.js +12 -1
- 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/cascade-handler.js +165 -138
- package/dist/pipeline/dispatch-batch.js +2 -2
- package/dist/pipeline/dispatch-query.js +20 -13
- package/dist/pipeline/dispatch-shared.d.ts +14 -3
- package/dist/pipeline/dispatch-shared.js +67 -721
- package/dist/pipeline/dispatch-stream.js +1 -1
- package/dist/pipeline/dispatch-write.js +297 -190
- 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-admin.js +2 -1
- package/dist/pipeline/event-dispatcher-delivery.js +4 -6
- package/dist/pipeline/event-dispatcher.js +17 -28
- package/dist/pipeline/handler-context-auth.d.ts +9 -0
- package/dist/pipeline/handler-context-auth.js +54 -0
- package/dist/pipeline/handler-context-config.d.ts +14 -0
- package/dist/pipeline/handler-context-config.js +113 -0
- package/dist/pipeline/handler-context-db.d.ts +24 -0
- package/dist/pipeline/handler-context-db.js +149 -0
- package/dist/pipeline/handler-context-dispatch.d.ts +21 -0
- package/dist/pipeline/handler-context-dispatch.js +89 -0
- package/dist/pipeline/handler-context-events.d.ts +43 -0
- package/dist/pipeline/handler-context-events.js +237 -0
- package/dist/pipeline/handler-context-notify.d.ts +12 -0
- package/dist/pipeline/handler-context-notify.js +55 -0
- package/dist/pipeline/handler-context-observability.d.ts +7 -0
- package/dist/pipeline/handler-context-observability.js +21 -0
- package/dist/pipeline/handler-context-scope.d.ts +48 -0
- package/dist/pipeline/handler-context-scope.js +88 -0
- 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/notify-scope.js +12 -1
- package/dist/pipeline/pre-handler-gates.js +20 -4
- package/dist/pipeline/pre-transaction-reservations.d.ts +1 -0
- package/dist/pipeline/pre-transaction-reservations.js +9 -3
- package/dist/pipeline/projection-rebuild.js +15 -13
- 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/apply-command.d.ts +2 -0
- package/dist/schema-cli/apply-command.js +97 -0
- package/dist/schema-cli/baseline-command.d.ts +2 -0
- package/dist/schema-cli/baseline-command.js +33 -0
- package/dist/schema-cli/generate-command.d.ts +2 -0
- package/dist/schema-cli/generate-command.js +90 -0
- package/dist/schema-cli/shared.d.ts +39 -0
- package/dist/schema-cli/shared.js +95 -0
- package/dist/schema-cli/status-command.d.ts +2 -0
- package/dist/schema-cli/status-command.js +73 -0
- package/dist/schema-cli/validate-boot-input.d.ts +5 -0
- package/dist/schema-cli/validate-boot-input.js +130 -0
- package/dist/schema-cli/validate-command.d.ts +2 -0
- package/dist/schema-cli/validate-command.js +130 -0
- package/dist/schema-cli.d.ts +2 -18
- package/dist/schema-cli.js +47 -510
- package/dist/search/index.js +4 -4
- package/dist/search/meilisearch-adapter.js +2 -1
- 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 +159 -130
- package/dist/utils/compare.js +1 -1
- package/package.json +5 -5
- package/src/changes.json +358 -0
- package/src/scripts/codemod/crypto-shredding-testing-move.ts +2 -2
- package/src/scripts/codemod/feature-layout.ts +10 -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/object-form.ts +41 -4
- package/src/scripts/codemod/package-merge.ts +58 -8
- package/src/scripts/codemod/pii-personal-migration.ts +192 -153
- package/src/scripts/codemod/testing-injection.ts +122 -42
- package/src/scripts/codemod/testing-move.ts +29 -7
- package/src/scripts/codemod/unprocessable-error-details-reason.ts +2 -2
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Type-safe transition graph. Wraps the underlying Map so callers don't
|
|
3
3
|
* accidentally fall into the `transitions[from]?.includes(to)` footgun
|
|
4
|
-
* (
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* (indexing a Map like an object returns undefined and silently breaks the
|
|
5
|
+
* entire check). The API therefore enforces explicit methods, with no Map
|
|
6
|
+
* shape exposed externally.
|
|
7
7
|
*
|
|
8
8
|
* - `canTransition(from, to)` → boolean
|
|
9
|
-
* - `allowedFrom(from)` → readonly string[] (
|
|
10
|
-
*
|
|
11
|
-
* - `assertTransition(from, to)` →
|
|
9
|
+
* - `allowedFrom(from)` → readonly string[] (empty when `from` is unknown
|
|
10
|
+
* or terminal)
|
|
11
|
+
* - `assertTransition(from, to)` → throws UnprocessableError(invalid_transition)
|
|
12
12
|
*/
|
|
13
13
|
export type TransitionGraph<TStates extends string = string> = {
|
|
14
14
|
readonly canTransition: (from: TStates, to: TStates) => boolean;
|
|
@@ -17,8 +17,8 @@ export type TransitionGraph<TStates extends string = string> = {
|
|
|
17
17
|
};
|
|
18
18
|
/**
|
|
19
19
|
* Defines allowed state transitions. Returns a typed graph (see
|
|
20
|
-
* TransitionGraph)
|
|
21
|
-
* type
|
|
20
|
+
* TransitionGraph), not a Map. This makes `transitions[x]` access a
|
|
21
|
+
* type error instead of silently returning undefined.
|
|
22
22
|
*/
|
|
23
23
|
export declare function defineTransitions<const TMap extends Record<string, readonly string[]>>(map: TMap): TransitionGraph<keyof TMap & string>;
|
|
24
24
|
/**
|
|
@@ -26,9 +26,9 @@ export declare function defineTransitions<const TMap extends Record<string, read
|
|
|
26
26
|
* reason="invalid_transition" if not — the 422 status lets the client
|
|
27
27
|
* distinguish a logical rejection from a validation or auth failure.
|
|
28
28
|
*
|
|
29
|
-
* Convenience
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
29
|
+
* Convenience wrapper around `transitions.assertTransition(from, to)` —
|
|
30
|
+
* it exists because existing callers use the `guardTransition(graph, ...)`
|
|
31
|
+
* form. Both are supported; the graph method is the idiomatic API for new
|
|
32
|
+
* code.
|
|
33
33
|
*/
|
|
34
34
|
export declare function guardTransition<TStates extends string>(transitions: TransitionGraph<TStates>, from: TStates, to: TStates): void;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { buildInvalidTransitionDetails, FrameworkReasons, UnprocessableError, } from "../errors/index.js";
|
|
2
2
|
/**
|
|
3
3
|
* Defines allowed state transitions. Returns a typed graph (see
|
|
4
|
-
* TransitionGraph)
|
|
5
|
-
* type
|
|
4
|
+
* TransitionGraph), not a Map. This makes `transitions[x]` access a
|
|
5
|
+
* type error instead of silently returning undefined.
|
|
6
6
|
*/
|
|
7
7
|
export function defineTransitions(map) {
|
|
8
8
|
const internal = new Map();
|
|
@@ -17,7 +17,7 @@ export function defineTransitions(map) {
|
|
|
17
17
|
},
|
|
18
18
|
assertTransition: (from, to) => {
|
|
19
19
|
const set = internal.get(from);
|
|
20
|
-
// skip:
|
|
20
|
+
// skip: allowed transition — no throw; fall through.
|
|
21
21
|
if (set?.has(to) === true)
|
|
22
22
|
return;
|
|
23
23
|
const allowed = set ? [...set] : [];
|
|
@@ -33,10 +33,10 @@ export function defineTransitions(map) {
|
|
|
33
33
|
* reason="invalid_transition" if not — the 422 status lets the client
|
|
34
34
|
* distinguish a logical rejection from a validation or auth failure.
|
|
35
35
|
*
|
|
36
|
-
* Convenience
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
36
|
+
* Convenience wrapper around `transitions.assertTransition(from, to)` —
|
|
37
|
+
* it exists because existing callers use the `guardTransition(graph, ...)`
|
|
38
|
+
* form. Both are supported; the graph method is the idiomatic API for new
|
|
39
|
+
* code.
|
|
40
40
|
*/
|
|
41
41
|
// @wrapper-known semantic-alias
|
|
42
42
|
export function guardTransition(transitions, from, to) {
|
|
@@ -13,14 +13,15 @@
|
|
|
13
13
|
// was needed to satisfy TS narrowing on a property-access; passing the
|
|
14
14
|
// arg through a function-call achieves the same narrowing trivially via
|
|
15
15
|
// the function-parameter binding.
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
function isResolverFunction(arg) {
|
|
17
|
+
return typeof arg === "function";
|
|
18
|
+
}
|
|
18
19
|
/**
|
|
19
20
|
* Resolve a required StepResolver — either a static value or a function.
|
|
20
21
|
* Throws if `arg` is undefined; caller must guarantee presence.
|
|
21
22
|
*/
|
|
22
23
|
export function resolveRequired(arg, ctx) {
|
|
23
|
-
if (
|
|
24
|
+
if (isResolverFunction(arg)) {
|
|
24
25
|
return arg(ctx);
|
|
25
26
|
}
|
|
26
27
|
return arg;
|
|
@@ -24,12 +24,12 @@
|
|
|
24
24
|
// ```
|
|
25
25
|
//
|
|
26
26
|
// M.1.6 supports `concurrency: 1` only (sequential). Concurrent
|
|
27
|
-
// execution (Promise.all-style)
|
|
27
|
+
// execution (Promise.all-style) requires its own audit cycle: TX-sharing,
|
|
28
28
|
// AbortSignal-cancellation, and lifecycle-hook ordering each warrant
|
|
29
|
-
//
|
|
29
|
+
// separate review.
|
|
30
30
|
//
|
|
31
|
-
//
|
|
32
|
-
// from the sub-pipeline's resolvers.
|
|
31
|
+
// `as` is required — without it, the current item is unreachable
|
|
32
|
+
// from the sub-pipeline's resolvers. `r.step.return` inside `do` is
|
|
33
33
|
// rejected at build time (would trigger the discriminated-union
|
|
34
34
|
// TData-Inference trap).
|
|
35
35
|
import { defineStep } from "../define-step.js";
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// r.requires.projection allowlist, must NOT be a registered
|
|
7
7
|
// r.entity-aggregate-table. Skips the same set of framework-protections
|
|
8
8
|
// (lifecycle hooks, field-access, audit-trail, etc.) — see
|
|
9
|
-
// step-vocabulary.md "
|
|
9
|
+
// step-vocabulary.md "What unsafeProjection.* skips".
|
|
10
10
|
//
|
|
11
11
|
// Convention (not enforced): most legitimate read-side deletions are
|
|
12
12
|
// downstream of an aggregate event (e.g. delete-user → cascading
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Idempotent on the supplied conflict-key columns (typically the
|
|
4
4
|
// natural key + tenantId). Skips lifecycle hooks, field-access,
|
|
5
|
-
// crypto-shredding, schema-versioning, audit-trail, read-access-log.
|
|
6
|
-
// See "
|
|
5
|
+
// crypto-shredding, schema-versioning, audit-trail, and read-access-log.
|
|
6
|
+
// See "What unsafeProjection.* skips" in
|
|
7
7
|
// kumiko-platform/docs/archive/plans/architecture/intern/step-vocabulary.md.
|
|
8
8
|
//
|
|
9
9
|
// Use only on tables explicitly declared via r.requires.projection in
|
|
10
|
-
// the owning feature. Aggregate
|
|
11
|
-
// rejected by boot
|
|
10
|
+
// the owning feature. Aggregate tables (registered via r.entity) are
|
|
11
|
+
// rejected by boot validation — domain mutation MUST go through
|
|
12
12
|
// r.step.aggregate.*.
|
|
13
13
|
import { extractTableName } from "../../db/index.js";
|
|
14
14
|
import { executeRawQuery } from "../../db/queries/raw-sql.js";
|
|
@@ -16,10 +16,10 @@
|
|
|
16
16
|
// resultKey is `args.event` itself (the branded event-type string) rather
|
|
17
17
|
// than a new `name` argument — a workflow only ever has one `awaits` entry
|
|
18
18
|
// per event type, so `ctx.steps[awaits.someKey]` is already a unique,
|
|
19
|
-
// intention-revealing handle. resume-run.write.ts
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
19
|
+
// intention-revealing handle. resume-run.write.ts is the only producer of
|
|
20
|
+
// this key's value: the step itself always suspends (never returns a real
|
|
21
|
+
// result), so a subsequent step only ever sees the matched event's payload
|
|
22
|
+
// after a resume seeds it in.
|
|
23
23
|
import { defineStep } from "../define-step.js";
|
|
24
24
|
import { addDuration } from "./_duration-utils.js";
|
|
25
25
|
import { resolveOptional, resolveRequired } from "./_resolver-utils.js";
|
|
@@ -15,7 +15,7 @@ const triggerEventRefSchema = z.object({
|
|
|
15
15
|
aggregateId: z.string(),
|
|
16
16
|
version: z.number().int(),
|
|
17
17
|
});
|
|
18
|
-
// Optional on every step event: runs started before the
|
|
18
|
+
// Optional on every step event: runs started before the fingerprint existed
|
|
19
19
|
// have none.
|
|
20
20
|
const stepContext = {
|
|
21
21
|
workflowName: z.string(),
|
|
@@ -29,8 +29,8 @@ const WORKFLOW_SYSTEM_EVENT_SCHEMAS = [
|
|
|
29
29
|
z.object({
|
|
30
30
|
workflowName: z.string(),
|
|
31
31
|
triggerEventType: z.string(),
|
|
32
|
-
// Current streams reference the trigger event; streams
|
|
33
|
-
//
|
|
32
|
+
// Current streams reference the trigger event; older streams carry a copy of
|
|
33
|
+
// its payload instead.
|
|
34
34
|
triggerEventRef: triggerEventRefSchema.optional(),
|
|
35
35
|
triggerPayload: z.unknown().optional(),
|
|
36
36
|
definitionFingerprint: z.string().optional(),
|
|
@@ -11,42 +11,79 @@
|
|
|
11
11
|
// gone it also drops every action, redirect, nav and workspace member that
|
|
12
12
|
// pointed at them. The one reference it does not resolve is a nav `parent`
|
|
13
13
|
// pointing at a missing nav; that is pruned here.
|
|
14
|
+
import { createFallbackLogger, scrubLogText } from "../logging/index.js";
|
|
14
15
|
import { resolveScreenTargetQn } from "./boot-validator/screens.js";
|
|
15
16
|
import { isToggleableFeature } from "./effective-features.js";
|
|
17
|
+
import { isPureGroupingNav } from "./project-app-schema-for-roles.js";
|
|
16
18
|
import { isValidQn, qualifyEntityName } from "./qualified-name.js";
|
|
19
|
+
const schemaLogger = createFallbackLogger("app-schema");
|
|
17
20
|
// Sorted, so the result doubles as a stable cache key. The trial gate is
|
|
18
21
|
// consulted like the dispatcher's feature gate does, but only for toggleable
|
|
19
22
|
// features: a trial can only unlock tier (toggleable) features, so a
|
|
20
|
-
// cascade-disabled always-on feature needs no gate call.
|
|
23
|
+
// cascade-disabled always-on feature needs no gate call. A failing gate counts
|
|
24
|
+
// as "not unlocked" (the handlers answer 403 then anyway), so a gate outage
|
|
25
|
+
// hides the add-on instead of taking the whole schema endpoint down.
|
|
21
26
|
export async function resolveDisabledFeatureNames(registry, effectiveFeatures, tenantId) {
|
|
22
27
|
const enabled = effectiveFeatures(tenantId);
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
const trialGate = effectiveFeatures.trialGate;
|
|
29
|
+
const disabled = [...registry.features].filter(([featureName]) => !enabled.has(featureName));
|
|
30
|
+
const unlockedByTrial = await Promise.all(disabled.map(async ([featureName, feature]) => {
|
|
31
|
+
if (trialGate === undefined || !isToggleableFeature(feature))
|
|
32
|
+
return false;
|
|
33
|
+
try {
|
|
34
|
+
return await trialGate(tenantId, featureName);
|
|
30
35
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
36
|
+
catch (error) {
|
|
37
|
+
schemaLogger.warn("trial gate failed, treating feature as not unlocked", {
|
|
38
|
+
featureName,
|
|
39
|
+
error: scrubLogText(error instanceof Error ? error.message : String(error)),
|
|
40
|
+
});
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}));
|
|
44
|
+
return disabled
|
|
45
|
+
.filter((_, index) => !unlockedByTrial[index])
|
|
46
|
+
.map(([featureName]) => featureName)
|
|
47
|
+
.sort();
|
|
34
48
|
}
|
|
35
49
|
function navParentQn(featureName, parent) {
|
|
36
50
|
return isValidQn(parent) ? parent : qualifyEntityName(featureName, "nav", parent);
|
|
37
51
|
}
|
|
38
|
-
|
|
52
|
+
function navQnsByParent(features) {
|
|
53
|
+
const childrenByParent = new Map();
|
|
54
|
+
for (const feature of features) {
|
|
55
|
+
for (const nav of feature.navs ?? []) {
|
|
56
|
+
if (nav.parent === undefined)
|
|
57
|
+
continue;
|
|
58
|
+
const parentQn = navParentQn(feature.featureName, nav.parent);
|
|
59
|
+
const children = childrenByParent.get(parentQn) ?? [];
|
|
60
|
+
children.push(qualifyEntityName(feature.featureName, "nav", nav.id));
|
|
61
|
+
childrenByParent.set(parentQn, children);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return childrenByParent;
|
|
65
|
+
}
|
|
66
|
+
// Navs of enabled features whose parent chain ends in a removed nav, plus pure grouping
|
|
67
|
+
// navs whose children are all removed (an empty section the role projection cannot drop,
|
|
68
|
+
// because it only drops groups that still have children).
|
|
39
69
|
function collectOrphanedNavQns(features, removedNavQns) {
|
|
40
70
|
const orphaned = new Set();
|
|
71
|
+
const childrenByParent = navQnsByParent(features);
|
|
41
72
|
let changed = true;
|
|
42
73
|
while (changed) {
|
|
43
74
|
changed = false;
|
|
44
75
|
for (const feature of features) {
|
|
45
76
|
for (const nav of feature.navs ?? []) {
|
|
46
77
|
const qn = qualifyEntityName(feature.featureName, "nav", nav.id);
|
|
47
|
-
if (removedNavQns.has(qn)
|
|
78
|
+
if (removedNavQns.has(qn))
|
|
48
79
|
continue;
|
|
49
|
-
|
|
80
|
+
const parentRemoved = nav.parent !== undefined &&
|
|
81
|
+
removedNavQns.has(navParentQn(feature.featureName, nav.parent));
|
|
82
|
+
const children = childrenByParent.get(qn) ?? [];
|
|
83
|
+
const emptiedGroup = isPureGroupingNav(nav) &&
|
|
84
|
+
children.length > 0 &&
|
|
85
|
+
children.every((childQn) => removedNavQns.has(childQn));
|
|
86
|
+
if (parentRemoved || emptiedGroup) {
|
|
50
87
|
removedNavQns.add(qn);
|
|
51
88
|
orphaned.add(qn);
|
|
52
89
|
changed = true;
|
|
@@ -4,30 +4,32 @@ import type { RegistrarExtensionRegistration } from "./types/config.ts";
|
|
|
4
4
|
import type { FeatureDefinition, Registry } from "./types/feature.ts";
|
|
5
5
|
import type { TenantId } from "./types/identifiers.ts";
|
|
6
6
|
/**
|
|
7
|
-
* Extension
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* Extension name under which a tier-resolver plugin must be registered in the
|
|
8
|
+
* registry. The constant keeps `r.useExtension(TENANT_TIER_RESOLVER_EXT, ...)`
|
|
9
|
+
* and the framework's `getExtensionUsages(TENANT_TIER_RESOLVER_EXT)` aligned
|
|
10
|
+
* on the same value, preventing typos.
|
|
11
11
|
*/
|
|
12
12
|
export declare const TENANT_TIER_RESOLVER_EXT = "tenantTierResolver";
|
|
13
13
|
/**
|
|
14
|
-
* Resolver
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* Resolver callback shape: synchronous (on the dispatcher's hot path), per
|
|
15
|
+
* tenant. Returns the tenant's effective feature set. Implementations should
|
|
16
|
+
* maintain an in-memory cache and invalidate it via `r.entityHook`.
|
|
17
17
|
*
|
|
18
|
-
* **System-context convention:**
|
|
19
|
-
*
|
|
18
|
+
* **System-context convention:** calls with SYSTEM_TENANT_ID must return the
|
|
19
|
+
* union of all tier features (see the DispatcherOptions.effectiveFeatures
|
|
20
|
+
* doc block).
|
|
20
21
|
*/
|
|
21
22
|
export type EffectiveFeaturesResolver = ((tenantId: TenantId) => ReadonlySet<string>) & {
|
|
22
23
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
24
|
+
* Optional live-trial gate, consulted by the dispatcher feature gate ONLY
|
|
25
|
+
* when the synchronous resolver reports a feature as disabled. Reads the
|
|
26
|
+
* tenant's signup date live (tenant.inserted_at) and returns true when the
|
|
27
|
+
* tenant is within the trial window AND the feature belongs to the trial
|
|
28
|
+
* tier. It is async and not part of the boot cache because the trial is
|
|
29
|
+
* time-dependent and can change between requests. Consulted at three
|
|
30
|
+
* locations: the two dispatch-gate call sites (checkFeatureEnabled) AND
|
|
31
|
+
* ctx.hasFeature in the handler body (as a fallback when the synchronous
|
|
32
|
+
* feature-set check is negative).
|
|
31
33
|
*/
|
|
32
34
|
readonly trialGate?: TrialGate;
|
|
33
35
|
};
|
|
@@ -36,10 +38,10 @@ export type EffectiveFeaturesResolver = ((tenantId: TenantId) => ReadonlySet<str
|
|
|
36
38
|
*/
|
|
37
39
|
export type TrialGate = (tenantId: TenantId, featureName: string) => Promise<boolean>;
|
|
38
40
|
/**
|
|
39
|
-
* Plugin
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
41
|
+
* Plugin shape for the tier-resolver extension. Plugins implement `build`
|
|
42
|
+
* as a boot-time factory: they receive `db` + `registry` (after stack setup),
|
|
43
|
+
* load the initial cache from the database, and return the synchronous
|
|
44
|
+
* resolver callback, optionally with an attached `trialGate`.
|
|
43
45
|
*/
|
|
44
46
|
export type TierResolverPlugin = {
|
|
45
47
|
readonly build: (deps: {
|
|
@@ -49,13 +51,13 @@ export type TierResolverPlugin = {
|
|
|
49
51
|
}) => Promise<EffectiveFeaturesResolver>;
|
|
50
52
|
};
|
|
51
53
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
54
|
+
* Scans a composed feature list for a `tenantTierResolver` extension usage.
|
|
55
|
+
* Only one plugin is supported because multiple plugins would be ambiguous:
|
|
56
|
+
* it would be unclear which resolver should win. No multi-merge pattern is
|
|
57
|
+
* needed until there is a real use case.
|
|
56
58
|
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
+
* Shared by runDevApp + runProdApp so the pickup path remains byte-for-byte
|
|
60
|
+
* identical and cannot drift.
|
|
59
61
|
*/
|
|
60
62
|
/** Narrows a registration's untyped `options` bag to the tier-resolver
|
|
61
63
|
* plugin shape — the only thing every implementation must provide is the
|
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Tier composition: framework extension point for per-tenant
|
|
2
2
|
// effective-feature resolution.
|
|
3
3
|
//
|
|
4
|
-
// **Pattern (
|
|
5
|
-
// declares `r.extendsRegistrar("tenantTierResolver")
|
|
6
|
-
//
|
|
4
|
+
// **Pattern (analogous to mail-foundation / file-foundation):** a feature
|
|
5
|
+
// declares `r.extendsRegistrar("tenantTierResolver")`, and plugins
|
|
6
|
+
// implement it via `r.useExtension("tenantTierResolver", "id", { build })`.
|
|
7
7
|
//
|
|
8
|
-
// **
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
8
|
+
// **Framework auto-wiring:** runDevApp + runProdApp scan the registry
|
|
9
|
+
// for this exact extension name. If a plugin is found AND the app author
|
|
10
|
+
// has not set an `effectiveFeatures` callback, `plugin.build({ db, registry })`
|
|
11
|
+
// is called once in onAfterSetup. The returned callback becomes
|
|
12
|
+
// `effectiveFeatures` in the dispatcher.
|
|
13
13
|
//
|
|
14
|
-
// **
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
14
|
+
// **Why this pattern:** the app author mounts
|
|
15
|
+
// `createTierEngineFeature(opts)` and is done—no late-bound holder in the
|
|
16
|
+
// run config and no need to wire an effectiveFeatures callback. The framework
|
|
17
|
+
// handles the late-binding internally. Tier composition is a feature, not a
|
|
18
|
+
// subsystem with app-specific wiring.
|
|
19
19
|
//
|
|
20
|
-
// **Apps
|
|
21
|
-
//
|
|
20
|
+
// **Apps without a tier engine:** if no plugin is registered, the framework
|
|
21
|
+
// does nothing—`effectiveFeatures` remains undefined, and all features are on.
|
|
22
22
|
import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
23
23
|
/**
|
|
24
|
-
* Extension
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
24
|
+
* Extension name under which a tier-resolver plugin must be registered in the
|
|
25
|
+
* registry. The constant keeps `r.useExtension(TENANT_TIER_RESOLVER_EXT, ...)`
|
|
26
|
+
* and the framework's `getExtensionUsages(TENANT_TIER_RESOLVER_EXT)` aligned
|
|
27
|
+
* on the same value, preventing typos.
|
|
28
28
|
*/
|
|
29
29
|
export const TENANT_TIER_RESOLVER_EXT = "tenantTierResolver";
|
|
30
30
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
31
|
+
* Scans a composed feature list for a `tenantTierResolver` extension usage.
|
|
32
|
+
* Only one plugin is supported because multiple plugins would be ambiguous:
|
|
33
|
+
* it would be unclear which resolver should win. No multi-merge pattern is
|
|
34
|
+
* needed until there is a real use case.
|
|
35
35
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
36
|
+
* Shared by runDevApp + runProdApp so the pickup path remains byte-for-byte
|
|
37
|
+
* identical and cannot drift.
|
|
38
38
|
*/
|
|
39
39
|
/** Narrows a registration's untyped `options` bag to the tier-resolver
|
|
40
40
|
* plugin shape — the only thing every implementation must provide is the
|
|
@@ -61,7 +61,7 @@ function parentRefEdgesFrom(entityName, entity, allEntityNames) {
|
|
|
61
61
|
//
|
|
62
62
|
// A type can legitimately be reached by more than one edge — `note.vehicleId`
|
|
63
63
|
// and `note.campaignId` are distinct, and collapsing them by entity name would
|
|
64
|
-
// leave one edge's rows behind,
|
|
64
|
+
// leave one edge's rows behind, causing a silent partial move.
|
|
65
65
|
export function resolveTransferAdjacency(entities, rootEntityType) {
|
|
66
66
|
const allEntityNames = [...entities.keys()];
|
|
67
67
|
const byParent = new Map();
|
|
@@ -96,7 +96,7 @@ export function findOverDeepTransferChain(adjacency, onPath) {
|
|
|
96
96
|
// skip: a cycle revisits a type already on this path, and schema depth
|
|
97
97
|
// cannot measure how far it actually runs — that depends on the rows, not
|
|
98
98
|
// the declaration. The mover carries this one instead, failing with
|
|
99
|
-
// `transfer_graph_too_deep` when its rounds run out
|
|
99
|
+
// `transfer_graph_too_deep` when its rounds run out.
|
|
100
100
|
if (onPath.includes(edge.entityName))
|
|
101
101
|
continue;
|
|
102
102
|
const deeper = findOverDeepTransferChain(adjacency, [...onPath, edge.entityName]);
|
|
@@ -23,35 +23,17 @@
|
|
|
23
23
|
// caught by this validator. A future lint-rule will enforce the contract
|
|
24
24
|
// statically; today it lives in this comment + the StepBuilder doc.
|
|
25
25
|
import { KUMIKO_META_SYMBOL } from "@cosmicdrift/kumiko-types/schema-table-types";
|
|
26
|
+
import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
27
|
+
import { resolveTableName } from "../db/entity-table-meta.js";
|
|
26
28
|
import { schemaTableName } from "../db/table-introspection.js";
|
|
27
29
|
import { getStep } from "./define-step.js";
|
|
28
30
|
import { buildPipelineSteps } from "./pipeline.js";
|
|
31
|
+
import { walkStepInstances } from "./walk-step-instances.js";
|
|
29
32
|
// Listed step-kinds whose `args.table` must be in the owning feature's
|
|
30
33
|
// r.requires.projection allowlist. Extend as further unsafeProjection.*
|
|
31
34
|
// steps land — don't pre-list hypothetical kinds (CLAUDE.md: don't design
|
|
32
35
|
// for scenarios that can't happen).
|
|
33
36
|
const UNSAFE_PROJECTION_KINDS = new Set(["unsafeProjectionUpsert", "unsafeProjectionDelete"]);
|
|
34
|
-
// Sub-pipeline arg-paths come from `defineStep({ subPaths: [...] })` —
|
|
35
|
-
// each builder declares its own (e.g. branch's onTrue/onFalse, forEach's
|
|
36
|
-
// do). Walking via the registry means nested unsafeProjection-* in NEW
|
|
37
|
-
// sub-step-builders is automatically caught the moment the builder
|
|
38
|
-
// registers itself; no central map to keep in sync. Followup #15.
|
|
39
|
-
function* walkAllSteps(steps) {
|
|
40
|
-
for (const step of steps) {
|
|
41
|
-
yield step;
|
|
42
|
-
const def = getStep(step.kind);
|
|
43
|
-
const subPaths = def?.subPaths;
|
|
44
|
-
if (!subPaths || subPaths.length === 0)
|
|
45
|
-
continue;
|
|
46
|
-
const args = step.args;
|
|
47
|
-
for (const path of subPaths) {
|
|
48
|
-
const subSteps = args[path];
|
|
49
|
-
if (Array.isArray(subSteps)) {
|
|
50
|
-
yield* walkAllSteps(subSteps);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
37
|
// table()/buildEntityTable spread column handles as enumerable props, so an
|
|
56
38
|
// entity field named `tableName`/`source` would shadow the matching meta key —
|
|
57
39
|
// the canonical meta under KUMIKO_META_SYMBOL is the only collision-safe source.
|
|
@@ -95,11 +77,10 @@ export function validateProjectionAllowlist(features) {
|
|
|
95
77
|
// the first feature's projections (and vice-versa). Detect and surface
|
|
96
78
|
// here so the error names BOTH owners; without this, the silent-set
|
|
97
79
|
// would point any later unsafeProjection-error at the wrong feature.
|
|
98
|
-
// Followup #8.
|
|
99
80
|
const aggregateTables = new Map();
|
|
100
81
|
for (const f of features) {
|
|
101
82
|
for (const [entityName, entity] of Object.entries(f.entities ?? {})) {
|
|
102
|
-
const tableName = entity
|
|
83
|
+
const tableName = resolveTableName(entityName, entity, undefined);
|
|
103
84
|
const existing = aggregateTables.get(tableName);
|
|
104
85
|
if (existing && existing !== f.name) {
|
|
105
86
|
throw new Error(`Aggregate-table "${tableName}" is registered by both feature "${existing}" and feature "${f.name}" via r.entity. ` +
|
|
@@ -129,8 +110,8 @@ export function validateProjectionAllowlist(features) {
|
|
|
129
110
|
`Closure body must produce the step list without reading event-payload fields ` +
|
|
130
111
|
`(those belong inside step resolvers).`);
|
|
131
112
|
}
|
|
132
|
-
for (const step of
|
|
133
|
-
// Tier-2 step-discovery
|
|
113
|
+
for (const step of walkStepInstances(steps, `[Feature ${f.name}] writeHandler "${handlerName}"`)) {
|
|
114
|
+
// Tier-2 step-discovery: require explicit opt-in via
|
|
134
115
|
// r.requires.step("<kind>") for any tier-2+ step. Tier-1 is implicit.
|
|
135
116
|
const def = getStep(step.kind);
|
|
136
117
|
if (def?.tier === 2 && !f.requiredSteps.has(step.kind)) {
|
|
@@ -140,12 +121,12 @@ export function validateProjectionAllowlist(features) {
|
|
|
140
121
|
}
|
|
141
122
|
if (!UNSAFE_PROJECTION_KINDS.has(step.kind))
|
|
142
123
|
continue;
|
|
143
|
-
const
|
|
144
|
-
if (!
|
|
124
|
+
const stepTable = isPlainObject(step.args) ? step.args["table"] : undefined;
|
|
125
|
+
if (!stepTable) {
|
|
145
126
|
throw new Error(`[Feature ${f.name}] writeHandler "${handlerName}" has a ${step.kind} step ` +
|
|
146
127
|
`without a \`table\` argument.`);
|
|
147
128
|
}
|
|
148
|
-
const tableName = resolveTableNameFromStep(
|
|
129
|
+
const tableName = resolveTableNameFromStep(stepTable);
|
|
149
130
|
const aggregateOwner = aggregateTables.get(tableName);
|
|
150
131
|
if (aggregateOwner) {
|
|
151
132
|
throw new Error(`[Feature ${f.name}] writeHandler "${handlerName}" uses ${step.kind} on table "${tableName}", ` +
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
2
|
+
import { getStep } from "./define-step.js";
|
|
3
|
+
function isStepInstance(value) {
|
|
4
|
+
return isPlainObject(value) && typeof value["kind"] === "string";
|
|
5
|
+
}
|
|
6
|
+
// Sub-pipeline arg-paths come from `defineStep({ subPaths })`, so nested steps of new
|
|
7
|
+
// sub-step builders are walked without a central map.
|
|
8
|
+
// Fail-closed: an entry the walker cannot read would hide a step from the caller's check
|
|
9
|
+
// (projection allowlist, event-trigger support), so malformed input throws instead of being skipped.
|
|
10
|
+
export function* walkStepInstances(steps, where) {
|
|
11
|
+
for (const [stepIndex, step] of steps.entries()) {
|
|
12
|
+
yield step;
|
|
13
|
+
const subPaths = getStep(step.kind)?.subPaths;
|
|
14
|
+
if (!subPaths || subPaths.length === 0)
|
|
15
|
+
continue;
|
|
16
|
+
const stepWhere = `${where} step[${stepIndex}] "${step.kind}"`;
|
|
17
|
+
if (!isPlainObject(step.args)) {
|
|
18
|
+
throw new Error(`${stepWhere} declares sub-paths (${subPaths.join(", ")}) but its args is not an object.`);
|
|
19
|
+
}
|
|
20
|
+
for (const path of subPaths) {
|
|
21
|
+
const subSteps = step.args[path];
|
|
22
|
+
// skip: optional sub-path not provided (branch.run treats onFalse: null like absent)
|
|
23
|
+
if (subSteps === undefined || subSteps === null)
|
|
24
|
+
continue;
|
|
25
|
+
if (!Array.isArray(subSteps)) {
|
|
26
|
+
throw new Error(`${stepWhere} sub-path "${path}" must be a list of steps.`);
|
|
27
|
+
}
|
|
28
|
+
const checked = [];
|
|
29
|
+
for (const [entryIndex, entry] of subSteps.entries()) {
|
|
30
|
+
if (!isStepInstance(entry)) {
|
|
31
|
+
throw new Error(`${stepWhere} sub-path "${path}"[${entryIndex}] is not a step (expected an object with a string "kind").`);
|
|
32
|
+
}
|
|
33
|
+
checked.push(entry);
|
|
34
|
+
}
|
|
35
|
+
yield* walkStepInstances(checked, `${stepWhere} ${path}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -6,7 +6,7 @@ import { toKebab } from "./qualified-name.js";
|
|
|
6
6
|
export const WRITE_HANDLER_QN_FORMAT_RE = /^[a-zA-Z][a-zA-Z0-9-]*:write:[a-zA-Z][a-zA-Z0-9-]*(:[a-zA-Z][a-zA-Z0-9-]*)*$/;
|
|
7
7
|
// Matches `dispatcher.write("qn", ...)` and `<expr>.write("qn", ...)` —
|
|
8
8
|
// same surface the CI guard scans. Dynamic QNs (variables, templates) are
|
|
9
|
-
// intentionally skipped (known limitation
|
|
9
|
+
// intentionally skipped (known limitation).
|
|
10
10
|
const DISPATCHER_WRITE_LITERAL_RE = /\.write\s*\(\s*["']([^"']+)["']/g;
|
|
11
11
|
/** Extract string-literal write-handler QNs from TS/TSX source text. */
|
|
12
12
|
export function extractDispatcherWriteQnsFromSource(source) {
|