@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
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/// <reference types="temporal-polyfill/global" preserve="true" />
|
|
2
|
+
import { crossTenantReadsGrantFor, escapeHatchFor } from "@cosmicdrift/kumiko-types/handlers";
|
|
3
|
+
import { createCrossTenantReads } from "../db/cross-tenant-reads.js";
|
|
4
|
+
import { createTenantDb, createUncheckedSystemDb } from "../db/tenant-db.js";
|
|
5
|
+
import { createDerivativesContext } from "../derivatives/derivatives-context.js";
|
|
6
|
+
import { createSystemUser } from "../engine/system-user.js";
|
|
7
|
+
import { InternalError } from "../errors/index.js";
|
|
8
|
+
import { createTenantFileContext } from "../files/file-handle.js";
|
|
9
|
+
import { createEscapeHatchReporter, createMetricsForFactory } from "../observability/index.js";
|
|
10
|
+
import { configAccessorFactoryOf } from "../pipeline/config-accessor-factory.js";
|
|
11
|
+
import { createGatedIdentitySwitch } from "../pipeline/system-identity-switch.js";
|
|
12
|
+
import { buildPersonalDataGate, parseWriteOrigin } from "../pipeline/write-origin.js";
|
|
13
|
+
import { attemptNumberOf, createJobLogger, handlerPayloadOf, isFinalAttempt, isJobTriggerEvent, jobSubjectKey, missingDbProxy, tenantIdOfJobData, } from "./job-runner-helpers.js";
|
|
14
|
+
// Tenant-visible failure record (JobDefinition.tenantVisibleFailure). The
|
|
15
|
+
// subject is read from the handler payload, so it is resolved where the
|
|
16
|
+
// payload is built, not in the callbacks.
|
|
17
|
+
// A bad subject config must surface as a failed run, so it is only recorded
|
|
18
|
+
// here and thrown from inside the span, not before any run record exists.
|
|
19
|
+
function resolveTenantVisibleSubjects(jobName, jobDef, payload) {
|
|
20
|
+
let failureSubject = null;
|
|
21
|
+
let runSubject = null;
|
|
22
|
+
let configError;
|
|
23
|
+
const runDecl = jobDef.tenantVisibleRun;
|
|
24
|
+
const failureDecl = jobDef.tenantVisibleFailure;
|
|
25
|
+
if (runDecl) {
|
|
26
|
+
try {
|
|
27
|
+
runSubject = jobSubjectKey(jobName, payload, runDecl.subjectFields, "tenantVisibleRun");
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
configError = err instanceof Error ? err : new Error(String(err));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (failureDecl && !configError) {
|
|
34
|
+
try {
|
|
35
|
+
failureSubject = jobSubjectKey(jobName, payload, failureDecl.subjectFields, "tenantVisibleFailure");
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
configError = err instanceof Error ? err : new Error(String(err));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return { failureSubject, runSubject, configError };
|
|
42
|
+
}
|
|
43
|
+
// Absent = legacy or ungated root; present but invalid fails the run closed.
|
|
44
|
+
function parseJobWriteOrigin(rawData, jobName) {
|
|
45
|
+
const rawWriteOrigin = rawData["_writeOrigin"]; // @cast-boundary dynamic-key
|
|
46
|
+
if (rawWriteOrigin === undefined)
|
|
47
|
+
return { writeOrigin: undefined, writeOriginInvalid: false };
|
|
48
|
+
const parsed = parseWriteOrigin(rawWriteOrigin);
|
|
49
|
+
if (!parsed)
|
|
50
|
+
return { writeOrigin: undefined, writeOriginInvalid: true };
|
|
51
|
+
return { writeOrigin: { ...parsed, viaJob: jobName }, writeOriginInvalid: false };
|
|
52
|
+
}
|
|
53
|
+
function createJobEscapeHatchReporter(scope, input, systemUser) {
|
|
54
|
+
const { jobName, jobDef, tenantId, triggeredById } = input;
|
|
55
|
+
// One reporter for ctx.systemDb and ctx.db.unsafeRaw() so both dedupe in the same window.
|
|
56
|
+
const isSystemCronRun = triggeredById === null && jobDef.escapeHatch !== undefined && "cron" in jobDef.trigger;
|
|
57
|
+
return createEscapeHatchReporter({
|
|
58
|
+
handler: jobName,
|
|
59
|
+
tenantId,
|
|
60
|
+
actor: systemUser.id,
|
|
61
|
+
sink: scope.context._escapeHatchAuditSink,
|
|
62
|
+
log: scope.errorLogger,
|
|
63
|
+
window: scope.escapeHatchReportWindow,
|
|
64
|
+
...(triggeredById !== null && { caller: triggeredById }),
|
|
65
|
+
...(isSystemCronRun && { processDedup: scope.systemCronEscapeHatchDedup }),
|
|
66
|
+
...(scope.context.meter && { meter: scope.context.meter }),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function buildJobDbFacets(scope, facetInput) {
|
|
70
|
+
const { context, registry } = scope;
|
|
71
|
+
const { input, isSystemJob, reportEscapeHatch } = facetInput;
|
|
72
|
+
const { jobDef, tenantId, writeOrigin } = input;
|
|
73
|
+
const personalDataGate = writeOrigin ? buildPersonalDataGate(registry, writeOrigin) : undefined;
|
|
74
|
+
const configDb = context.db; // @cast-boundary db-operator
|
|
75
|
+
// Shared by the config accessor and ctx.derivatives below — both need the
|
|
76
|
+
// same tenant-scoped db, and building it twice would let the two calls
|
|
77
|
+
// drift apart. Always "system" mode regardless of the job's own
|
|
78
|
+
// systemScope() status (pre-existing, not something this change alters)
|
|
79
|
+
// — isSystemJob below is what actually keeps ctx.systemDb off a
|
|
80
|
+
// non-system job; it is the only thing standing between this db and an
|
|
81
|
+
// unchecked cross-tenant escape hatch for such a job. Gated like jobDb so
|
|
82
|
+
// ctx.systemDb, built from it, is gated too.
|
|
83
|
+
const tenantScopedDb = configDb
|
|
84
|
+
? createTenantDb({
|
|
85
|
+
db: configDb,
|
|
86
|
+
tenantId,
|
|
87
|
+
mode: "system",
|
|
88
|
+
grants: {
|
|
89
|
+
projectionRegistry: registry,
|
|
90
|
+
...(personalDataGate && { personalDataGate }),
|
|
91
|
+
},
|
|
92
|
+
})
|
|
93
|
+
: undefined;
|
|
94
|
+
const systemDb = isSystemJob && tenantScopedDb
|
|
95
|
+
? createUncheckedSystemDb(tenantScopedDb, undefined, reportEscapeHatch)
|
|
96
|
+
: undefined;
|
|
97
|
+
const jobDb = configDb
|
|
98
|
+
? createTenantDb({
|
|
99
|
+
db: configDb,
|
|
100
|
+
tenantId,
|
|
101
|
+
mode: "tenant",
|
|
102
|
+
tracer: context.tracer,
|
|
103
|
+
meter: context.meter,
|
|
104
|
+
grants: {
|
|
105
|
+
unsafeRaw: escapeHatchFor(jobDef.escapeHatch, "unsafeRaw"),
|
|
106
|
+
globalWrites: escapeHatchFor(jobDef.escapeHatch, "globalWrites"),
|
|
107
|
+
report: reportEscapeHatch,
|
|
108
|
+
projectionRegistry: registry,
|
|
109
|
+
...(personalDataGate && { personalDataGate }),
|
|
110
|
+
},
|
|
111
|
+
})
|
|
112
|
+
: undefined;
|
|
113
|
+
const crossTenantReadsReason = crossTenantReadsGrantFor(jobDef.escapeHatch)?.reason;
|
|
114
|
+
const crossTenantReads = tenantScopedDb && crossTenantReadsReason !== undefined
|
|
115
|
+
? createCrossTenantReads(tenantScopedDb, crossTenantReadsReason, reportEscapeHatch)
|
|
116
|
+
: undefined;
|
|
117
|
+
return { tenantScopedDb, jobDb, systemDb, crossTenantReads };
|
|
118
|
+
}
|
|
119
|
+
function buildJobServiceFacets(scope, facetInput, tenantScopedDb) {
|
|
120
|
+
const { context, registry } = scope;
|
|
121
|
+
const { input, systemUser } = facetInput;
|
|
122
|
+
const { tenantId } = input;
|
|
123
|
+
// Mirror dispatch-shared.ts buildHandlerContext: ctx.files must resolve
|
|
124
|
+
// through the same _fileProviderResolver for jobs as for write-handlers,
|
|
125
|
+
// otherwise event-triggered jobs silently get an unresolved ctx.files.
|
|
126
|
+
const fileResolver = context._fileProviderResolver;
|
|
127
|
+
const files = fileResolver ? createTenantFileContext(fileResolver, tenantId) : context.files;
|
|
128
|
+
// Same buildHandlerContext parity as ctx.files above: ctx.notify/ctx.config
|
|
129
|
+
// must resolve for jobs the same way they do for write-handlers, or an
|
|
130
|
+
// app-author job calling ctx.notify(...)/ctx.config(...) hits a TypeError
|
|
131
|
+
// at runtime the write-handler path never would.
|
|
132
|
+
const notify = context._notifyFactory?.(systemUser, tenantId, scope.getSelfRunner(), undefined, context._fileProviderResolver);
|
|
133
|
+
const configFactory = configAccessorFactoryOf(context);
|
|
134
|
+
const config = configFactory && tenantScopedDb
|
|
135
|
+
? configFactory({
|
|
136
|
+
user: { id: systemUser.id, tenantId },
|
|
137
|
+
db: tenantScopedDb,
|
|
138
|
+
secrets: context.secrets,
|
|
139
|
+
})
|
|
140
|
+
: undefined;
|
|
141
|
+
// Mirror dispatch-shared.ts: ctx.derivatives needs files+db, same
|
|
142
|
+
// tenant-scoped db the config accessor above uses.
|
|
143
|
+
const derivatives = files && tenantScopedDb
|
|
144
|
+
? createDerivativesContext({ files, registry, db: tenantScopedDb, tenantId })
|
|
145
|
+
: context.derivatives;
|
|
146
|
+
return { files, notify, config, derivatives };
|
|
147
|
+
}
|
|
148
|
+
function requireDispatchRef(scope, method) {
|
|
149
|
+
const ref = scope.getDispatchWriteRef();
|
|
150
|
+
if (!ref) {
|
|
151
|
+
throw new Error(`JobContext.${method} called before dispatcher attached — call attachDispatcher() first`);
|
|
152
|
+
}
|
|
153
|
+
return ref;
|
|
154
|
+
}
|
|
155
|
+
function buildJobDispatchFacets(scope, facetInput) {
|
|
156
|
+
const { input, systemUser, isSystemJob, reportEscapeHatch } = facetInput;
|
|
157
|
+
const { jobName, jobDef, tenantId, writeOrigin } = input;
|
|
158
|
+
// Per-job-run reader, created lazily on queryAsMember's first call and
|
|
159
|
+
// cached for the rest of this run.
|
|
160
|
+
let memberReader;
|
|
161
|
+
const identitySwitchHatch = escapeHatchFor(jobDef.escapeHatch, "systemIdentity");
|
|
162
|
+
const identitySwitchGrantReason = identitySwitchHatch?.reason ??
|
|
163
|
+
(isSystemJob ? `r.systemScope() feature of job "${jobName}"` : undefined);
|
|
164
|
+
const gatedIdentitySwitch = createGatedIdentitySwitch(`job "${jobName}"`, systemUser, isSystemJob || identitySwitchHatch !== undefined, {
|
|
165
|
+
queryAs: (user, qn, payload) => requireDispatchRef(scope, "queryAs").queryAs(user, qn, payload, writeOrigin),
|
|
166
|
+
writeAs: async (user, qn, payload) => requireDispatchRef(scope, "writeAs").write(user, qn, payload, writeOrigin),
|
|
167
|
+
}, { reason: identitySwitchGrantReason, report: reportEscapeHatch });
|
|
168
|
+
return {
|
|
169
|
+
write: (qn, payload) => requireDispatchRef(scope, "write").write(systemUser, qn, payload, writeOrigin),
|
|
170
|
+
writeAs: gatedIdentitySwitch.writeAs,
|
|
171
|
+
queryAs: gatedIdentitySwitch.queryAs,
|
|
172
|
+
queryAsMember: (userId, qn, payload) => {
|
|
173
|
+
const ref = requireDispatchRef(scope, "queryAsMember");
|
|
174
|
+
// Lazy per-run reader, mirroring the handler-context reader.
|
|
175
|
+
memberReader ??= ref.createMemberReader(tenantId);
|
|
176
|
+
return memberReader(userId, qn, payload);
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function createJobDefer(jobName, bullJob, token) {
|
|
181
|
+
return (delayMs) => {
|
|
182
|
+
if (!Number.isInteger(delayMs) || delayMs <= 0) {
|
|
183
|
+
throw new RangeError(`Job "${jobName}": ctx.defer needs a positive integer delay in ms (got ${delayMs})`);
|
|
184
|
+
}
|
|
185
|
+
if (token === undefined || !bullJob.moveToDelayed) {
|
|
186
|
+
throw new InternalError({
|
|
187
|
+
message: `Job "${jobName}": ctx.defer needs a queued run; it is not available inline (boot gate).`,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
return { kind: "job-deferral", delayMs };
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
function buildJobContext(scope, input) {
|
|
194
|
+
const { jobName, bullJob, token, rawData, tenantId, triggeredById, attempt, finalAttempt } = input;
|
|
195
|
+
const systemUser = createSystemUser(tenantId);
|
|
196
|
+
const facetInput = {
|
|
197
|
+
input,
|
|
198
|
+
systemUser,
|
|
199
|
+
isSystemJob: scope.registry.isJobSystemScoped(jobName),
|
|
200
|
+
reportEscapeHatch: createJobEscapeHatchReporter(scope, input, systemUser),
|
|
201
|
+
};
|
|
202
|
+
const { tenantScopedDb, jobDb, systemDb, crossTenantReads } = buildJobDbFacets(scope, facetInput);
|
|
203
|
+
const { files, notify, config, derivatives } = buildJobServiceFacets(scope, facetInput, tenantScopedDb);
|
|
204
|
+
const dispatchFacets = buildJobDispatchFacets(scope, facetInput);
|
|
205
|
+
const selfRunner = scope.getSelfRunner();
|
|
206
|
+
// Carry `_triggerName` from rawData when set — handleEvent injects it on
|
|
207
|
+
// multi-trigger dispatch; exposed as jobContext.triggerName so handlers
|
|
208
|
+
// don't dig through the raw payload themselves.
|
|
209
|
+
const triggerName = rawData["_triggerName"]; // @cast-boundary dynamic-key
|
|
210
|
+
// Same carry-through for `_triggerEvent` — handleEvent injects it when
|
|
211
|
+
// the job-trigger event consumer supplied the stored event that fired
|
|
212
|
+
// this run (see createJobTriggerEventConsumer).
|
|
213
|
+
const rawTriggerEvent = rawData["_triggerEvent"];
|
|
214
|
+
const triggerEvent = isJobTriggerEvent(rawTriggerEvent) ? rawTriggerEvent : undefined;
|
|
215
|
+
const jobContext = {
|
|
216
|
+
defer: createJobDefer(jobName, bullJob, token),
|
|
217
|
+
...scope.context,
|
|
218
|
+
db: jobDb ?? missingDbProxy(jobName),
|
|
219
|
+
files,
|
|
220
|
+
derivatives,
|
|
221
|
+
...(notify !== undefined && { notify }),
|
|
222
|
+
...(config !== undefined && { config }),
|
|
223
|
+
...(systemDb && { systemDb }),
|
|
224
|
+
...(crossTenantReads && { crossTenantReads }),
|
|
225
|
+
// The runner owns the registry it resolved this job from — expose it so
|
|
226
|
+
// workers can reach projections/jobs without the app author duplicating
|
|
227
|
+
// it into `context` (the JobContext contract guarantees `registry`).
|
|
228
|
+
registry: scope.registry,
|
|
229
|
+
// Expose the runner so handlers can chain a follow-up job. Symmetric with
|
|
230
|
+
// how the command-dispatcher hands write-handlers their jobRunner.
|
|
231
|
+
...(selfRunner !== undefined && { jobRunner: selfRunner }),
|
|
232
|
+
systemUser,
|
|
233
|
+
triggeredBy: triggeredById !== null ? { id: triggeredById, tenantId } : null,
|
|
234
|
+
attempt,
|
|
235
|
+
finalAttempt,
|
|
236
|
+
log: createJobLogger(input.logs),
|
|
237
|
+
metricsFor: createMetricsForFactory(scope.context.meter),
|
|
238
|
+
...(triggerName !== undefined && { triggerName }),
|
|
239
|
+
...(triggerEvent !== undefined && { triggerEvent }),
|
|
240
|
+
...dispatchFacets,
|
|
241
|
+
};
|
|
242
|
+
return { jobContext, config };
|
|
243
|
+
}
|
|
244
|
+
export function prepareJobRun(scope, jobDef, bullJob, token) {
|
|
245
|
+
const jobName = bullJob.name;
|
|
246
|
+
const jobId = bullJob.id ?? "unknown";
|
|
247
|
+
const startTime = Date.now();
|
|
248
|
+
const logs = [];
|
|
249
|
+
// Extract meta from job data. `attempt` is BullMQ's own counter
|
|
250
|
+
// (1-based on the first run, incremented on each retry) — threading
|
|
251
|
+
// it through lets the logger tag the run-started event with the
|
|
252
|
+
// retry number, so audit queries distinguish fresh from retry runs
|
|
253
|
+
// without peeking at BullMQ internals.
|
|
254
|
+
const rawData = bullJob.data;
|
|
255
|
+
const attempt = attemptNumberOf(bullJob);
|
|
256
|
+
// Build handler payload (without internal meta fields)
|
|
257
|
+
const payload = handlerPayloadOf(rawData);
|
|
258
|
+
// Determine tenantId and triggeredBy from meta
|
|
259
|
+
const tenantId = tenantIdOfJobData(rawData);
|
|
260
|
+
const triggeredById = rawData["_triggeredById"] ?? null; // @cast-boundary dynamic-key
|
|
261
|
+
const subjects = resolveTenantVisibleSubjects(jobName, jobDef, payload);
|
|
262
|
+
const finalAttempt = isFinalAttempt(bullJob, jobDef);
|
|
263
|
+
const { writeOrigin, writeOriginInvalid } = parseJobWriteOrigin(rawData, jobName);
|
|
264
|
+
const meta = {
|
|
265
|
+
triggeredById: rawData["_triggeredById"], // @cast-boundary dynamic-key
|
|
266
|
+
payload: rawData["_payload"], // @cast-boundary dynamic-key
|
|
267
|
+
attempt,
|
|
268
|
+
...(jobDef.tenantVisibleRun &&
|
|
269
|
+
!subjects.configError && {
|
|
270
|
+
tenantVisibleRun: { tenantId, subject: subjects.runSubject },
|
|
271
|
+
}),
|
|
272
|
+
};
|
|
273
|
+
const { jobContext, config } = buildJobContext(scope, {
|
|
274
|
+
jobName,
|
|
275
|
+
jobDef,
|
|
276
|
+
bullJob,
|
|
277
|
+
token,
|
|
278
|
+
rawData,
|
|
279
|
+
tenantId,
|
|
280
|
+
triggeredById,
|
|
281
|
+
writeOrigin,
|
|
282
|
+
attempt,
|
|
283
|
+
finalAttempt,
|
|
284
|
+
logs,
|
|
285
|
+
});
|
|
286
|
+
return {
|
|
287
|
+
jobName,
|
|
288
|
+
jobDef,
|
|
289
|
+
jobId,
|
|
290
|
+
startTime,
|
|
291
|
+
logs,
|
|
292
|
+
rawData,
|
|
293
|
+
attempt,
|
|
294
|
+
finalAttempt,
|
|
295
|
+
meta,
|
|
296
|
+
payload,
|
|
297
|
+
tenantId,
|
|
298
|
+
triggeredById,
|
|
299
|
+
subjects,
|
|
300
|
+
writeOrigin,
|
|
301
|
+
writeOriginInvalid,
|
|
302
|
+
config,
|
|
303
|
+
jobContext,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/// <reference types="temporal-polyfill/global" preserve="true" />
|
|
2
|
+
import type { JobDeferral } from "../engine/types/index.ts";
|
|
3
|
+
import type { JobRun } from "./job-runner-context.ts";
|
|
4
|
+
import { type JobRunnerScope } from "./job-runner-scope.ts";
|
|
5
|
+
import type { JobInvocation } from "./job-runner-types.ts";
|
|
6
|
+
export declare function executeJobInSpan(scope: JobRunnerScope, run: JobRun): Promise<JobDeferral | undefined>;
|
|
7
|
+
export declare function applyHandlerDeferral(scope: JobRunnerScope, run: JobRun, bullJob: JobInvocation, deferral: JobDeferral | undefined, token: string | undefined): Promise<"deferred" | "not-deferred">;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/// <reference types="temporal-polyfill/global" preserve="true" />
|
|
2
|
+
import { UnrecoverableError } from "bullmq";
|
|
3
|
+
import { requestContext } from "../api/request-context.js";
|
|
4
|
+
import { qnScope } from "../engine/qualified-name.js";
|
|
5
|
+
import { InternalError } from "../errors/index.js";
|
|
6
|
+
import { scrubLogText } from "../logging/index.js";
|
|
7
|
+
import { emitJobLastSuccess, withEscapeHatchAuditScope } from "../observability/index.js";
|
|
8
|
+
import { DEFAULT_MAX_DEFERS, HANDLER_DEFER_COUNT_KEY, isJobDeferral, priorDeferCountOf, readTraceContext, resolveJobTz, tenantFailureMessageKey, } from "./job-runner-helpers.js";
|
|
9
|
+
import { laneForJob, logFailedAttempt } from "./job-runner-scope.js";
|
|
10
|
+
function buildOutcomeMeta(run, messageKey, unrecoverable = false) {
|
|
11
|
+
const { jobDef, subjects } = run;
|
|
12
|
+
return {
|
|
13
|
+
tenantId: run.tenantId,
|
|
14
|
+
// BullMQ never retries an UnrecoverableError, whatever attempts are left.
|
|
15
|
+
finalAttempt: run.finalAttempt || unrecoverable,
|
|
16
|
+
...(jobDef.tenantVisibleFailure &&
|
|
17
|
+
!subjects.configError && {
|
|
18
|
+
tenantVisible: { subject: subjects.failureSubject, messageKey },
|
|
19
|
+
}),
|
|
20
|
+
...(jobDef.tenantVisibleRun &&
|
|
21
|
+
!subjects.configError && { tenantVisibleRun: { subject: subjects.runSubject } }),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
async function runJobHandler(scope, run, correlation) {
|
|
25
|
+
if (run.subjects.configError) {
|
|
26
|
+
// Retrying cannot fix a declaration error.
|
|
27
|
+
throw new UnrecoverableError(run.subjects.configError.message);
|
|
28
|
+
}
|
|
29
|
+
if (run.writeOriginInvalid) {
|
|
30
|
+
throw new InternalError({
|
|
31
|
+
message: `Job "${run.jobName}" received an unparseable _writeOrigin — refusing to run without ` +
|
|
32
|
+
"a trustworthy anonymous-root gate.",
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return requestContext.run({
|
|
36
|
+
requestId: correlation.requestId,
|
|
37
|
+
correlationId: correlation.correlationId,
|
|
38
|
+
// Events a job writes carry the job as their origin.
|
|
39
|
+
handler: run.jobName,
|
|
40
|
+
feature: scope.registry.getJobFeature(run.jobName) ?? qnScope(run.jobName),
|
|
41
|
+
writeOrigin: run.writeOrigin,
|
|
42
|
+
tenantId: run.tenantId,
|
|
43
|
+
...(run.triggeredById !== null && { userId: run.triggeredById }),
|
|
44
|
+
}, async () => run.jobDef.handler(run.payload, {
|
|
45
|
+
...run.jobContext,
|
|
46
|
+
tz: await resolveJobTz(run.config, scope.context, scope.errorLogger),
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
function assertDeferBudget(run) {
|
|
50
|
+
const maxDefers = run.jobDef.maxDefers ?? DEFAULT_MAX_DEFERS;
|
|
51
|
+
if (priorDeferCountOf(run.rawData, HANDLER_DEFER_COUNT_KEY) >= maxDefers) {
|
|
52
|
+
// The counter never resets, so a retry would hit the cap again.
|
|
53
|
+
throw new UnrecoverableError(`Job "${run.jobName}" was deferred ${maxDefers} times (maxDefers); giving up.`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async function recordJobSuccess(scope, run) {
|
|
57
|
+
// Stamped before the observer hook: a throwing onJobComplete must not
|
|
58
|
+
// make a run that actually succeeded look dead to the liveness alert.
|
|
59
|
+
if (scope.context.meter) {
|
|
60
|
+
try {
|
|
61
|
+
emitJobLastSuccess(scope.context.meter, run.jobName);
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
scope.errorLogger.warn("job last-success metric failed", {
|
|
65
|
+
job: run.jobName,
|
|
66
|
+
error: err instanceof Error ? err.message : String(err),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const duration = Date.now() - run.startTime;
|
|
71
|
+
await scope.options.onJobComplete?.(run.jobName, run.jobId, duration, run.logs, buildOutcomeMeta(run, null));
|
|
72
|
+
}
|
|
73
|
+
async function recordJobFailure(scope, run, err) {
|
|
74
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
75
|
+
run.logs.push({ level: "error", message: errorMsg, timestamp: Temporal.Now.instant() });
|
|
76
|
+
logFailedAttempt(scope, {
|
|
77
|
+
job: run.jobName,
|
|
78
|
+
jobId: run.jobId,
|
|
79
|
+
attempt: run.attempt,
|
|
80
|
+
finalAttempt: run.finalAttempt,
|
|
81
|
+
err,
|
|
82
|
+
});
|
|
83
|
+
await scope.options.onJobFailed?.(run.jobName, run.jobId, errorMsg, run.logs, buildOutcomeMeta(run, tenantFailureMessageKey(err, run.jobDef.tenantVisibleFailure), err instanceof UnrecoverableError));
|
|
84
|
+
}
|
|
85
|
+
async function runAndRecordJob(scope, run, correlation) {
|
|
86
|
+
try {
|
|
87
|
+
const handlerResult = await runJobHandler(scope, run, correlation);
|
|
88
|
+
if (isJobDeferral(handlerResult)) {
|
|
89
|
+
assertDeferBudget(run);
|
|
90
|
+
// skip: the deferral is applied after the span closes
|
|
91
|
+
return handlerResult;
|
|
92
|
+
}
|
|
93
|
+
await recordJobSuccess(scope, run);
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
await recordJobFailure(scope, run, err);
|
|
98
|
+
throw err;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
export async function executeJobInSpan(scope, run) {
|
|
102
|
+
// Cross-process trace continuation: if the enqueuing code captured a
|
|
103
|
+
// parent span, start the job.execute span as its child. Works for event
|
|
104
|
+
// and manual triggers; cron jobs start a fresh root span.
|
|
105
|
+
const parentContext = readTraceContext(run.rawData);
|
|
106
|
+
// Correlation propagation: the scheduling request's correlationId was
|
|
107
|
+
// packed into _correlationId at dispatch time. Re-enter requestContext.run
|
|
108
|
+
// so event writes during this job stamp the same correlation as the
|
|
109
|
+
// request that scheduled it. Cron/boot jobs (no scheduler) start fresh
|
|
110
|
+
// — correlationId = new requestId, no parent causation.
|
|
111
|
+
const inheritedCorrelationId = run.rawData["_correlationId"] ?? undefined; // @cast-boundary dynamic-key
|
|
112
|
+
const requestId = requestContext.generateId();
|
|
113
|
+
const correlationId = inheritedCorrelationId ?? requestId;
|
|
114
|
+
// Unified span creation: withSpan handles start/end + status/exception
|
|
115
|
+
// recording identically for both parent-context and no-parent paths.
|
|
116
|
+
// When parentContext is set, the new parent-aware StartSpanOptions
|
|
117
|
+
// plumbs it through to startSpan — no manual try/finally needed.
|
|
118
|
+
return withEscapeHatchAuditScope(() => scope.tracer.withSpan("job.execute", {
|
|
119
|
+
attributes: {
|
|
120
|
+
"job.name": run.jobName,
|
|
121
|
+
"job.id": run.jobId,
|
|
122
|
+
"job.attempt": run.attempt,
|
|
123
|
+
"kumiko.tenant_id": run.tenantId,
|
|
124
|
+
// Lane-routing attributes. `run_in` is the job's
|
|
125
|
+
// declared lane (explicit or default-"worker"); `consumer_lane`
|
|
126
|
+
// is which runner actually executed it. They diverge in
|
|
127
|
+
// all-in-one (both lanes live in one process) but must match
|
|
128
|
+
// in split deploys — a mismatch in prod logs signals a
|
|
129
|
+
// misrouted job that slipped past the boot-validator.
|
|
130
|
+
"kumiko.job.run_in": laneForJob(run.jobDef),
|
|
131
|
+
// Omit attribute entirely when no consumer (enqueuer-only runner) —
|
|
132
|
+
// SpanAttributeValue doesn't accept undefined.
|
|
133
|
+
...(scope.consumerLane !== undefined
|
|
134
|
+
? { "kumiko.job.consumer_lane": scope.consumerLane }
|
|
135
|
+
: {}),
|
|
136
|
+
},
|
|
137
|
+
...(parentContext ? { parent: parentContext } : {}),
|
|
138
|
+
}, () => runAndRecordJob(scope, run, { requestId, correlationId })));
|
|
139
|
+
}
|
|
140
|
+
export async function applyHandlerDeferral(scope, run, bullJob, deferral, token) {
|
|
141
|
+
if (!deferral || token === undefined || !bullJob.moveToDelayed)
|
|
142
|
+
return "not-deferred";
|
|
143
|
+
// Run-log bookkeeping must not turn a defer into a failure.
|
|
144
|
+
try {
|
|
145
|
+
await scope.options.onJobDeferred?.(run.jobName, run.jobId, run.meta);
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
scope.errorLogger.warn("job deferred hook failed", {
|
|
149
|
+
job: run.jobName,
|
|
150
|
+
jobId: run.jobId,
|
|
151
|
+
error: scrubLogText(err instanceof Error ? err.message : String(err)),
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
await bullJob.updateData?.({
|
|
155
|
+
...bullJob.data,
|
|
156
|
+
[HANDLER_DEFER_COUNT_KEY]: priorDeferCountOf(bullJob.data, HANDLER_DEFER_COUNT_KEY) + 1,
|
|
157
|
+
});
|
|
158
|
+
await bullJob.moveToDelayed(Date.now() + deferral.delayMs, token);
|
|
159
|
+
return "deferred";
|
|
160
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/// <reference types="temporal-polyfill/global" preserve="true" />
|
|
2
|
+
import type { JobDefinition } from "../engine/types/index.ts";
|
|
3
|
+
import { type JobRun } from "./job-runner-context.ts";
|
|
4
|
+
import { type JobRunnerScope } from "./job-runner-scope.ts";
|
|
5
|
+
import type { JobInvocation } from "./job-runner-types.ts";
|
|
6
|
+
export declare const PER_TENANT_PREFIX = "_perTenant:";
|
|
7
|
+
export declare function perTenantTargetOf(jobName: string): string | undefined;
|
|
8
|
+
export declare function fanOutPerTenantJob(scope: JobRunnerScope, bullJob: JobInvocation, actualName: string): Promise<void>;
|
|
9
|
+
export declare function startJobRun(scope: JobRunnerScope, jobDef: JobDefinition, bullJob: JobInvocation, token: string | undefined): Promise<JobRun>;
|
|
10
|
+
export declare function resolveJobDefinitionOrFail(scope: JobRunnerScope, bullJob: JobInvocation): Promise<JobDefinition>;
|
|
11
|
+
export declare function deferOverTenantLimit(scope: JobRunnerScope, jobDef: JobDefinition, bullJob: JobInvocation, token: string | undefined): Promise<"deferred" | "run">;
|
|
12
|
+
export type SequentialSlot = {
|
|
13
|
+
readonly kind: "run";
|
|
14
|
+
readonly lockToken: string | null;
|
|
15
|
+
} | {
|
|
16
|
+
readonly kind: "requeued";
|
|
17
|
+
};
|
|
18
|
+
export declare function claimSequentialSlot(scope: JobRunnerScope, jobDef: JobDefinition, bullJob: JobInvocation): Promise<SequentialSlot>;
|
|
19
|
+
export declare function releaseSequentialSlot(scope: JobRunnerScope, jobName: string, lockToken: string | null): Promise<void>;
|