deepline 0.3.88 → 0.3.90
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/contracts/install-commands.json +29 -0
- package/dist/bundling-sources/sdk/src/client.ts +652 -39
- package/dist/bundling-sources/sdk/src/config.ts +77 -0
- package/dist/bundling-sources/sdk/src/http.ts +76 -30
- package/dist/bundling-sources/sdk/src/index.ts +10 -0
- package/dist/bundling-sources/sdk/src/monitor-fleet-contract.ts +1 -1
- package/dist/bundling-sources/sdk/src/pagination.ts +25 -0
- package/dist/bundling-sources/sdk/src/play.ts +15 -1
- package/dist/bundling-sources/sdk/src/plays/bundle-play-file.ts +3 -3
- package/dist/bundling-sources/sdk/src/release.ts +16 -9
- package/dist/bundling-sources/sdk/src/types.ts +106 -4
- package/dist/bundling-sources/shared_libs/integration-contracts/AGENTS.md +9 -0
- package/dist/bundling-sources/shared_libs/integration-contracts/README.md +3 -0
- package/dist/bundling-sources/shared_libs/integration-contracts/index.ts +2 -0
- package/dist/bundling-sources/shared_libs/monitors/analytics-install-guides.ts +77 -0
- package/dist/bundling-sources/shared_libs/monitors/analytics-public.ts +10 -0
- package/dist/bundling-sources/shared_libs/monitors/billing/charges.ts +125 -0
- package/dist/bundling-sources/shared_libs/monitors/billing/policy-builders.ts +686 -0
- package/dist/bundling-sources/shared_libs/monitors/billing/pricing.ts +54 -0
- package/dist/bundling-sources/shared_libs/monitors/billing/public-pricing.ts +72 -0
- package/dist/bundling-sources/shared_libs/monitors/billing/session.ts +45 -0
- package/dist/bundling-sources/shared_libs/monitors/billing/state.ts +454 -0
- package/dist/bundling-sources/shared_libs/monitors/billing/tamradar.ts +260 -0
- package/dist/bundling-sources/shared_libs/monitors/billing/types.ts +215 -0
- package/dist/bundling-sources/shared_libs/monitors/callback-security.ts +333 -0
- package/dist/bundling-sources/shared_libs/monitors/cursor-page.ts +42 -0
- package/dist/bundling-sources/shared_libs/monitors/deactivation-reasons.ts +16 -0
- package/dist/bundling-sources/shared_libs/monitors/framework-managed-payload-fields.ts +71 -0
- package/dist/bundling-sources/shared_libs/monitors/generated/provider-monitor-callback-admission-targets.ts +16 -0
- package/dist/bundling-sources/shared_libs/monitors/http-error-response.ts +270 -0
- package/dist/bundling-sources/shared_libs/monitors/inbound-publish-key.ts +39 -0
- package/dist/bundling-sources/shared_libs/monitors/inbound-queue.ts +82 -0
- package/dist/bundling-sources/shared_libs/monitors/inventory-reconciliation.ts +59 -0
- package/dist/bundling-sources/shared_libs/monitors/lifecycle-notifications.ts +471 -0
- package/dist/bundling-sources/shared_libs/monitors/org-monitor-limits.ts +1 -1
- package/dist/bundling-sources/shared_libs/monitors/package.json +36 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-callback-verification.ts +1125 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-batch-operation.ts +305 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-capability.ts +964 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-consumer-health.ts +56 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-errors.ts +131 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-identity-claims.ts +535 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-legacy-aliases.ts +16 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-priority-execution.ts +56 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-public-aliases.ts +28 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-runtime-read-eligibility.ts +38 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-state.ts +122 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-status.ts +10 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-upstream-identity.ts +112 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-webhook-monitor-runtime.ts +300 -0
- package/dist/bundling-sources/shared_libs/monitors/quickstart-prompts.ts +22 -0
- package/dist/bundling-sources/shared_libs/monitors/signal-radars/inbound-dead-letter.ts +245 -0
- package/dist/bundling-sources/shared_libs/monitors/signal-radars/inbound-message.ts +38 -0
- package/dist/bundling-sources/shared_libs/monitors/signal-radars/inbound-queue-ack-probe.ts +94 -0
- package/dist/bundling-sources/shared_libs/monitors/signal-radars/inbound-queue-delivery.ts +60 -0
- package/dist/bundling-sources/shared_libs/monitors/signal-radars/inbound-queue.ts +60 -0
- package/dist/bundling-sources/shared_libs/monitors/snitcher-install-validation.ts +93 -0
- package/dist/bundling-sources/shared_libs/monitors/snitcher-ip2company.ts +332 -0
- package/dist/bundling-sources/shared_libs/monitors/snitcher-radar.ts +221 -0
- package/dist/bundling-sources/shared_libs/monitors/starter-templates.ts +105 -0
- package/dist/bundling-sources/shared_libs/monitors/tracking-provider-guidance.ts +73 -0
- package/dist/bundling-sources/shared_libs/monitors/tracking-provider-integrations.ts +81 -0
- package/dist/bundling-sources/shared_libs/monitors/tsconfig.json +10 -0
- package/dist/bundling-sources/shared_libs/observability/build-context.ts +13 -0
- package/dist/bundling-sources/shared_libs/observability/log-metadata.ts +160 -0
- package/dist/bundling-sources/shared_libs/observability/request-context.ts +68 -0
- package/dist/bundling-sources/shared_libs/observability/scheduled-jobs.json +654 -0
- package/dist/bundling-sources/shared_libs/observability/scheduled-work-manual.ts +35 -0
- package/dist/bundling-sources/shared_libs/observability/scheduled-work.ts +5 -0
- package/dist/bundling-sources/shared_libs/play-data-plane/neon-constants.ts +9 -0
- package/dist/bundling-sources/shared_libs/play-data-plane/r2.ts +272 -0
- package/dist/bundling-sources/shared_libs/play-runtime/activity-observation.ts +6 -11
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +135 -12
- package/dist/bundling-sources/shared_libs/play-runtime/async-operation.ts +66 -1
- package/dist/bundling-sources/shared_libs/play-runtime/async-play-invocation.ts +77 -0
- package/dist/bundling-sources/shared_libs/play-runtime/bounceban-batching.ts +137 -0
- package/dist/bundling-sources/shared_libs/play-runtime/checkpoint-boundaries.ts +70 -0
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +1159 -166
- package/dist/bundling-sources/shared_libs/play-runtime/coordinator-headers.ts +1 -32
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +103 -0
- package/dist/bundling-sources/shared_libs/play-runtime/daytona-payload-contract.ts +68 -0
- package/dist/bundling-sources/shared_libs/play-runtime/daytona-sandbox-janitor.ts +103 -0
- package/dist/bundling-sources/shared_libs/play-runtime/daytona-sandbox-reaper.ts +725 -0
- package/dist/bundling-sources/shared_libs/play-runtime/default-batch-strategies.ts +20 -1
- package/dist/bundling-sources/shared_libs/play-runtime/durable-call-cache.ts +29 -0
- package/dist/bundling-sources/shared_libs/play-runtime/execution-capabilities.ts +29 -0
- package/dist/bundling-sources/shared_libs/play-runtime/execution-gateway-invocation-fence.ts +442 -0
- package/dist/bundling-sources/shared_libs/play-runtime/execution-protocol.ts +7 -1
- package/dist/bundling-sources/shared_libs/play-runtime/fixture-behavior.ts +18 -0
- package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +9 -0
- package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/governor/rate-state-backend.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/contracts.ts +66 -0
- package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/job.ts +145 -0
- package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/ports.ts +21 -0
- package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/postgres-store.ts +170 -0
- package/dist/bundling-sources/shared_libs/play-runtime/internal-bootstrap-token.ts +1322 -0
- package/dist/bundling-sources/shared_libs/play-runtime/internal-step-ids.ts +2 -2
- package/dist/bundling-sources/shared_libs/play-runtime/lane-retirement-gate.ts +189 -0
- package/dist/bundling-sources/shared_libs/play-runtime/launch-source-policy.ts +112 -0
- package/dist/bundling-sources/shared_libs/play-runtime/map-execution-frame.ts +8 -0
- package/dist/bundling-sources/shared_libs/play-runtime/perf-trace.ts +127 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-artifact-runtime-contract.ts +5 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-run-recovery-policy.ts +24 -1
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/admission/contracts.ts +56 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/admission/index.ts +13 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/admission/job.ts +41 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/admission/ports.ts +17 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/admission/postgres-adapter.ts +54 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/contracts.ts +114 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/engine-suspension.ts +108 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/index.ts +36 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/job.ts +97 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/ports.ts +104 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/postgres-fence-adapter.ts +263 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/worker-terminal-projection.ts +64 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/contracts.ts +29 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/gateway-adapter.ts +56 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/index.ts +15 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/job.ts +65 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/ports.ts +17 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/sandbox-cleanup/contracts.ts +42 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/sandbox-cleanup/index.ts +17 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/sandbox-cleanup/job.ts +142 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/sandbox-cleanup/ports.ts +42 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/sandbox-cleanup/postgres-adapter.ts +102 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/contracts.ts +60 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/index.ts +16 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/job.ts +78 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/ports.ts +54 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/postgres-adapter.ts +71 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-batching-registry.ts +4 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-semantics.ts +28 -0
- package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +4 -0
- package/dist/bundling-sources/shared_libs/play-runtime/receipt-status.ts +5 -8
- package/dist/bundling-sources/shared_libs/play-runtime/run-failure.ts +23 -2
- package/dist/bundling-sources/shared_libs/play-runtime/run-identity.ts +168 -0
- package/dist/bundling-sources/shared_libs/play-runtime/run-snapshot-stream.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +90 -13
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +6 -12
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-start-admission.ts +68 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +21 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/bundle.ts +0 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/runtime-sandbox-reconciliation.ts +14 -43
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +67 -213
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-authority.ts +201 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-capacity-policy.ts +20 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-contract.ts +6 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-env.ts +12 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-operation-contract.ts +67 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-release-environment.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-sandbox-contract.ts +68 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-scheduler-topology.ts +38 -2
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-telemetry.ts +108 -0
- package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/contract.ts +4 -0
- package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/runner-backend-adapter.ts +3 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +37 -1
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-deadline-reconciliation.ts +88 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-play-deploy-task.ts +124 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-play-run-task.ts +68 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-pool.ts +193 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-queue-lifecycle.ts +269 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-queue-suffix.ts +73 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +6642 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-shared.ts +897 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-worker-metrics.ts +120 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-worker-required-env.ts +76 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd.ts +476 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/gateway-heavy-request-admission.ts +47 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/gateway-progress-registry.ts +147 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/index.ts +27 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/lazy.ts +50 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/play-run-recovery.ts +98 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-budget-state.ts +260 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-pool.ts +1416 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +1255 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-state.ts +942 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +14636 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-gateway.ts +888 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-ledger-outbox-recovery.ts +355 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-terminal-transition.ts +46 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runner-push.ts +60 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-api-origin.ts +33 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-receipt-gateway-health.ts +64 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-sandbox-capacity.ts +835 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-sandbox-ledger-reconciliation.ts +1218 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-sandbox-operation.ts +207 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-scheduler-schema.ts +10 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/scheduler-capacity.ts +123 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/scheduler-launch-read-observability.ts +78 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/submit-admission.ts +169 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/terminal-contract.ts +20 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/worker-queue-metrics.ts +34 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/worker-resource-metrics.ts +249 -0
- package/dist/bundling-sources/shared_libs/play-runtime/sheet-attempt-sql.ts +7 -0
- package/dist/bundling-sources/shared_libs/play-runtime/step-progress.ts +296 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/native-batch.ts +3 -3
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/runtime-step-receipts-adapter.ts +20 -1
- package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +60 -15
- package/dist/bundling-sources/shared_libs/play-runtime/tool-execution-outcome.ts +20 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-http-errors.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/transient-service-error.ts +12 -0
- package/dist/bundling-sources/shared_libs/play-runtime/vercel-protection.ts +14 -1
- package/dist/bundling-sources/shared_libs/play-runtime/work-receipt-storage-contract.ts +165 -0
- package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +15 -23
- package/dist/bundling-sources/shared_libs/play-runtime/workflow-executor-auth.ts +169 -0
- package/dist/bundling-sources/shared_libs/play-runtime/zerobounce-batching.ts +122 -0
- package/dist/bundling-sources/shared_libs/plays/artifact-types.ts +8 -3
- package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +454 -17
- package/dist/bundling-sources/shared_libs/plays/bundling/index.ts +19 -4
- package/dist/bundling-sources/shared_libs/plays/contracts.ts +8 -14
- package/dist/bundling-sources/shared_libs/plays/core.ts +11 -6
- package/dist/bundling-sources/shared_libs/plays/deployment-status.ts +1 -1
- package/dist/bundling-sources/shared_libs/plays/docflow-binding-owner.ts +16 -11
- package/dist/bundling-sources/shared_libs/plays/docflow.ts +1 -1
- package/dist/bundling-sources/shared_libs/plays/email-status.ts +1 -1
- package/dist/bundling-sources/shared_libs/plays/play-grep-search.ts +39 -0
- package/dist/bundling-sources/shared_libs/plays/resolve-static-pipeline.ts +1 -1
- package/dist/bundling-sources/shared_libs/plays/share/copy.ts +271 -0
- package/dist/bundling-sources/shared_libs/plays/share/metrics.ts +140 -0
- package/dist/bundling-sources/shared_libs/plays/share/public-read-model.ts +86 -0
- package/dist/bundling-sources/shared_libs/plays/share/seo-quality.ts +181 -0
- package/dist/bundling-sources/shared_libs/plays/share/static-pipeline-summary.ts +658 -0
- package/dist/bundling-sources/shared_libs/plays/share/types.ts +197 -0
- package/dist/bundling-sources/shared_libs/plays/slack-hitl-timeout-error.ts +37 -0
- package/dist/bundling-sources/shared_libs/plays/slug.ts +115 -0
- package/dist/bundling-sources/shared_libs/plays/static-pipeline.ts +50 -15
- package/dist/bundling-sources/shared_libs/plays/tool-category-descriptions.ts +2 -2
- package/dist/bundling-sources/shared_libs/plays/tool-result-types.ts +1 -1
- package/dist/bundling-sources/shared_libs/product-notifications/catalog.ts +877 -0
- package/dist/bundling-sources/shared_libs/product-notifications/configuration-draft.ts +456 -0
- package/dist/bundling-sources/shared_libs/product-notifications/configuration.ts +566 -0
- package/dist/bundling-sources/shared_libs/product-notifications/content.ts +28 -0
- package/dist/bundling-sources/shared_libs/product-notifications/default-policy.ts +60 -0
- package/dist/bundling-sources/shared_libs/product-notifications/email.ts +246 -0
- package/dist/bundling-sources/shared_libs/product-notifications/evaluation.ts +280 -0
- package/dist/bundling-sources/shared_libs/product-notifications/events.ts +433 -0
- package/dist/bundling-sources/shared_libs/product-notifications/legacy-slack.ts +80 -0
- package/dist/bundling-sources/shared_libs/product-notifications/overflow.ts +18 -0
- package/dist/bundling-sources/shared_libs/product-notifications/rolling-limit.ts +24 -0
- package/dist/bundling-sources/shared_libs/product-notifications/slack-delivery.ts +624 -0
- package/dist/bundling-sources/shared_libs/product-notifications/slack-payload.ts +140 -0
- package/dist/bundling-sources/shared_libs/product-notifications/slack-transport.ts +75 -0
- package/dist/bundling-sources/shared_libs/product-notifications/workspace.ts +38 -0
- package/dist/bundling-sources/shared_libs/runtime-env.ts +12 -0
- package/dist/bundling-sources/shared_libs/security/email-blacklist.ts +42 -0
- package/dist/bundling-sources/shared_libs/security/header-safe-env.ts +35 -0
- package/dist/bundling-sources/shared_libs/security/internal-actor.ts +50 -0
- package/dist/bundling-sources/shared_libs/security/secret-redaction.ts +267 -0
- package/dist/bundling-sources/shared_libs/security/slack-sanitize.ts +133 -0
- package/dist/cli/getting-started.d.mts +52 -0
- package/dist/cli/getting-started.d.ts +52 -0
- package/dist/cli/getting-started.js +65 -0
- package/dist/cli/getting-started.mjs +36 -0
- package/dist/cli/index.d.mts +9 -0
- package/dist/cli/index.d.ts +9 -0
- package/dist/cli/index.js +1611 -453
- package/dist/cli/index.mjs +1596 -447
- package/dist/{compiler-manifest-Dbov16F7.d.mts → compiler-manifest-BaJfE-5H.d.mts} +136 -10
- package/dist/{compiler-manifest-Dbov16F7.d.ts → compiler-manifest-BaJfE-5H.d.ts} +136 -10
- package/dist/index.d.mts +910 -155
- package/dist/index.d.ts +910 -155
- package/dist/index.js +550 -64
- package/dist/index.mjs +550 -64
- package/dist/install-integrity.json +193 -4
- package/dist/monitor-fleet-contract.d.mts +138 -0
- package/dist/monitor-fleet-contract.d.ts +138 -0
- package/dist/monitor-fleet-contract.js +404 -0
- package/dist/monitor-fleet-contract.mjs +367 -0
- package/dist/plays/bundle-play-file.d.mts +8 -3
- package/dist/plays/bundle-play-file.d.ts +8 -3
- package/dist/plays/bundle-play-file.mjs +100 -20
- package/dist/release.d.mts +255 -0
- package/dist/release.d.ts +255 -0
- package/dist/release.js +215 -0
- package/dist/release.mjs +188 -0
- package/package.json +23 -4
- /package/dist/bundling-sources/shared_libs/{integrations/bettercontact-execution-policy.ts → integration-contracts/bettercontact/execution-policy.ts} +0 -0
- /package/dist/bundling-sources/shared_libs/{integrations/theirstack-execution-policy.ts → integration-contracts/theirstack/execution-policy.ts} +0 -0
|
@@ -0,0 +1,1322 @@
|
|
|
1
|
+
import { WORKFLOW_EXECUTOR_TOKEN_TTL_SECONDS } from './/runtime-constants';
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_RUNTIME_EXECUTION_CAPABILITIES,
|
|
4
|
+
isRuntimeExecutionCapability,
|
|
5
|
+
normalizeRuntimeExecutionCapabilities,
|
|
6
|
+
type RuntimeExecutionCapability,
|
|
7
|
+
} from './/execution-capabilities';
|
|
8
|
+
import {
|
|
9
|
+
validateFixtureBehavior,
|
|
10
|
+
type FixtureBehavior,
|
|
11
|
+
} from './/fixture-behavior';
|
|
12
|
+
import { normalizeRuntimeSchedulerSchema } from './/runtime-scheduler-topology';
|
|
13
|
+
import { SignJWT, exportJWK, importJWK, jwtVerify, type JWK } from 'jose';
|
|
14
|
+
import {
|
|
15
|
+
directCompletedTerminalContract,
|
|
16
|
+
type DirectCompletedTerminalContract,
|
|
17
|
+
} from './/scheduler-backends/terminal-contract';
|
|
18
|
+
|
|
19
|
+
// Frozen at 1: the content-addressed `kid` is the real rotation mechanism, so
|
|
20
|
+
// this claim never moves and is not a place to encode a token change.
|
|
21
|
+
const INTERNAL_BOOTSTRAP_TOKEN_VERSION = 1 as const;
|
|
22
|
+
const INTERNAL_BOOTSTRAP_TOKEN_TTL_SECONDS = 60 * 5;
|
|
23
|
+
// Workflow executor token TTL.
|
|
24
|
+
//
|
|
25
|
+
// HISTORY: This used to be 7 days, with a comment that said "HITL workflow
|
|
26
|
+
// steps can legitimately wait days for a human response." That comment was
|
|
27
|
+
// describing an earlier design where a single activity blocked while waiting
|
|
28
|
+
// on a human. It is no longer true.
|
|
29
|
+
//
|
|
30
|
+
// CURRENT DESIGN: HITL pauses happen at the *workflow* level via
|
|
31
|
+
// `workflowSleep` + signal handlers.
|
|
32
|
+
// When a signal fires (or a timer elapses), the workflow schedules a NEW
|
|
33
|
+
// `executePlay` activity, and that activity mints a fresh executor token at
|
|
34
|
+
// start. So the token only needs to outlive a single activity invocation,
|
|
35
|
+
// which is bounded by `PLAY_ACTIVITY_TIMEOUT_SECONDS`. Matching the two
|
|
36
|
+
// constants makes that contract explicit: an executor token cannot outlive
|
|
37
|
+
// the activity it was minted for.
|
|
38
|
+
//
|
|
39
|
+
// CHANGING THIS: If you ever revert to a model where one activity blocks on
|
|
40
|
+
// a long-running external wait, this TTL must grow accordingly — and the
|
|
41
|
+
// activity startToCloseTimeout must grow with it. Do not bump one without
|
|
42
|
+
// the other; the runtime API depends on the token being valid for the entire
|
|
43
|
+
// lifetime of the activity that minted it.
|
|
44
|
+
const INTERNAL_WORKFLOW_TOKEN_TTL_SECONDS = WORKFLOW_EXECUTOR_TOKEN_TTL_SECONDS;
|
|
45
|
+
const INTERNAL_BOOTSTRAP_TOKEN_AUDIENCE =
|
|
46
|
+
'deepline.ingestion.provider_sync_bootstrap' as const;
|
|
47
|
+
const INTERNAL_BOOTSTRAP_TOKEN_ISSUER = 'deepline.internal' as const;
|
|
48
|
+
const INTERNAL_BOOTSTRAP_BOOTSTRAP_PURPOSE =
|
|
49
|
+
'ingestion_provider_sync_bootstrap' as const;
|
|
50
|
+
const INTERNAL_BOOTSTRAP_EXECUTION_PURPOSE =
|
|
51
|
+
'ingestion_subscription_execution' as const;
|
|
52
|
+
const INTERNAL_WORKFLOW_EXECUTION_AUDIENCE =
|
|
53
|
+
'deepline.workflow.execution' as const;
|
|
54
|
+
const INTERNAL_WORKFLOW_EXECUTION_PURPOSE = 'workflow_run_execution' as const;
|
|
55
|
+
const RUNTIME_RATE_SCOPE_AUDIENCE = 'deepline.runtime.rate_scope' as const;
|
|
56
|
+
const RUNTIME_RATE_SCOPE_PURPOSE = 'runtime_rate_scope' as const;
|
|
57
|
+
// The runner resolves a rate scope once per tool and retains it for the
|
|
58
|
+
// executor activity. Keep this narrower, run-scoped capability valid for the
|
|
59
|
+
// same bounded activity as the executor token that is required to use it.
|
|
60
|
+
const RUNTIME_RATE_SCOPE_TOKEN_TTL_SECONDS =
|
|
61
|
+
WORKFLOW_EXECUTOR_TOKEN_TTL_SECONDS;
|
|
62
|
+
const PLAY_DEPLOY_AUDIENCE = 'deepline.play.deploy' as const;
|
|
63
|
+
const PLAY_DEPLOY_PURPOSE = 'play_deploy' as const;
|
|
64
|
+
// A deploy token covers one GitHub deploy-on-push build task: tarball fetch,
|
|
65
|
+
// bundling, register, promote, and status reporting. Deliberately its own
|
|
66
|
+
// constant — the workflow executor TTL is contractually tied to a play
|
|
67
|
+
// activity's runtime limits and must not be reused here.
|
|
68
|
+
//
|
|
69
|
+
// Two hours, not build-length: the token is minted at webhook intake and the
|
|
70
|
+
// task may sit behind a queue backlog plus five exponential retries before
|
|
71
|
+
// it runs, and an expired token would leave the deployment stuck in a
|
|
72
|
+
// non-terminal state (the worker cannot even report failure without it).
|
|
73
|
+
// Real revocation is the live-state check in requirePlayDeployAuth
|
|
74
|
+
// (superseded/terminal deployment, disabled project), not this expiry.
|
|
75
|
+
const PLAY_DEPLOY_TOKEN_TTL_SECONDS = 2 * 60 * 60;
|
|
76
|
+
const INTERNAL_OAUTH_REFRESH_AUDIENCE = 'deepline.oauth.token_refresh' as const;
|
|
77
|
+
const INTERNAL_OAUTH_REFRESH_PURPOSE = 'oauth_token_refresh' as const;
|
|
78
|
+
const PRODUCT_NOTIFICATION_DELIVERY_AUDIENCE =
|
|
79
|
+
'deepline.product_notification.delivery' as const;
|
|
80
|
+
const PRODUCT_NOTIFICATION_DELIVERY_PURPOSE =
|
|
81
|
+
'product_notification_delivery' as const;
|
|
82
|
+
const PRODUCT_NOTIFICATION_UNSUBSCRIBE_AUDIENCE =
|
|
83
|
+
'deepline.product_notification.unsubscribe' as const;
|
|
84
|
+
const PRODUCT_NOTIFICATION_UNSUBSCRIBE_PURPOSE =
|
|
85
|
+
'product_notification_unsubscribe' as const;
|
|
86
|
+
const MONITOR_FLEET_DISPATCH_AUDIENCE =
|
|
87
|
+
'deepline.monitor_fleet.dispatch' as const;
|
|
88
|
+
const MONITOR_FLEET_DISPATCH_PURPOSE = 'monitor_fleet_dispatch' as const;
|
|
89
|
+
const INTERNAL_BOOTSTRAP_TOKEN_DERIVATION_LABEL =
|
|
90
|
+
'deepline:internal-bootstrap-token:v1';
|
|
91
|
+
const WORKFLOW_EXECUTOR_KEY_DERIVATION_LABEL =
|
|
92
|
+
'deepline:workflow-executor-ed25519:v1';
|
|
93
|
+
const ED25519_PKCS8_SEED_PREFIX = Uint8Array.from([
|
|
94
|
+
0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x04,
|
|
95
|
+
0x22, 0x04, 0x20,
|
|
96
|
+
]);
|
|
97
|
+
|
|
98
|
+
type IngestionInternalTokenBaseClaims = {
|
|
99
|
+
aud: typeof INTERNAL_BOOTSTRAP_TOKEN_AUDIENCE;
|
|
100
|
+
exp: number;
|
|
101
|
+
iat: number;
|
|
102
|
+
iss: typeof INTERNAL_BOOTSTRAP_TOKEN_ISSUER;
|
|
103
|
+
org_id: string;
|
|
104
|
+
provider: string;
|
|
105
|
+
sub: string;
|
|
106
|
+
v: typeof INTERNAL_BOOTSTRAP_TOKEN_VERSION;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export type IngestionProviderSyncBootstrapTokenClaims =
|
|
110
|
+
IngestionInternalTokenBaseClaims & {
|
|
111
|
+
purpose: typeof INTERNAL_BOOTSTRAP_BOOTSTRAP_PURPOSE;
|
|
112
|
+
request_token: string;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export type IngestionSubscriptionExecutionTokenClaims =
|
|
116
|
+
IngestionInternalTokenBaseClaims & {
|
|
117
|
+
purpose: typeof INTERNAL_BOOTSTRAP_EXECUTION_PURPOSE;
|
|
118
|
+
subscription_id: string;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export type IngestionInternalTokenClaims =
|
|
122
|
+
| IngestionProviderSyncBootstrapTokenClaims
|
|
123
|
+
| IngestionSubscriptionExecutionTokenClaims;
|
|
124
|
+
|
|
125
|
+
export type OAuthTokenRefreshTokenClaims = {
|
|
126
|
+
aud: typeof INTERNAL_OAUTH_REFRESH_AUDIENCE;
|
|
127
|
+
exp: number;
|
|
128
|
+
iat: number;
|
|
129
|
+
iss: typeof INTERNAL_BOOTSTRAP_TOKEN_ISSUER;
|
|
130
|
+
org_id: string;
|
|
131
|
+
provider: string;
|
|
132
|
+
purpose: typeof INTERNAL_OAUTH_REFRESH_PURPOSE;
|
|
133
|
+
sub: string;
|
|
134
|
+
v: typeof INTERNAL_BOOTSTRAP_TOKEN_VERSION;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
export type ProductNotificationDeliveryTokenClaims = {
|
|
138
|
+
aud: typeof PRODUCT_NOTIFICATION_DELIVERY_AUDIENCE;
|
|
139
|
+
delivery_id: string;
|
|
140
|
+
exp: number;
|
|
141
|
+
iat: number;
|
|
142
|
+
iss: typeof INTERNAL_BOOTSTRAP_TOKEN_ISSUER;
|
|
143
|
+
org_id: string;
|
|
144
|
+
purpose: typeof PRODUCT_NOTIFICATION_DELIVERY_PURPOSE;
|
|
145
|
+
sub: 'product_notification_dispatcher';
|
|
146
|
+
v: typeof INTERNAL_BOOTSTRAP_TOKEN_VERSION;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
export type ProductNotificationUnsubscribeTokenClaims = {
|
|
150
|
+
aud: typeof PRODUCT_NOTIFICATION_UNSUBSCRIBE_AUDIENCE;
|
|
151
|
+
group_key: string;
|
|
152
|
+
iat: number;
|
|
153
|
+
iss: typeof INTERNAL_BOOTSTRAP_TOKEN_ISSUER;
|
|
154
|
+
member_id: string;
|
|
155
|
+
org_id: string;
|
|
156
|
+
purpose: typeof PRODUCT_NOTIFICATION_UNSUBSCRIBE_PURPOSE;
|
|
157
|
+
sub: 'product_notification_recipient';
|
|
158
|
+
v: typeof INTERNAL_BOOTSTRAP_TOKEN_VERSION;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
/** Capability scoped to one durable Monitor Fleet control-plane operation. */
|
|
162
|
+
export type MonitorFleetDispatchTokenClaims = {
|
|
163
|
+
aud: typeof MONITOR_FLEET_DISPATCH_AUDIENCE;
|
|
164
|
+
exp: number;
|
|
165
|
+
iat: number;
|
|
166
|
+
iss: typeof INTERNAL_BOOTSTRAP_TOKEN_ISSUER;
|
|
167
|
+
org_id: string;
|
|
168
|
+
purpose: typeof MONITOR_FLEET_DISPATCH_PURPOSE;
|
|
169
|
+
fleet_id?: string;
|
|
170
|
+
operation: 'dispatch' | 'daily_sync';
|
|
171
|
+
sub: 'monitor_fleet_dispatcher';
|
|
172
|
+
v: typeof INTERNAL_BOOTSTRAP_TOKEN_VERSION;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
export type PlayDeployTokenClaims = {
|
|
176
|
+
aud: typeof PLAY_DEPLOY_AUDIENCE;
|
|
177
|
+
deployment_id: string;
|
|
178
|
+
exp: number;
|
|
179
|
+
granted_by_user_id: string;
|
|
180
|
+
iat: number;
|
|
181
|
+
iss: typeof INTERNAL_BOOTSTRAP_TOKEN_ISSUER;
|
|
182
|
+
org_id: string;
|
|
183
|
+
play_project_id: string;
|
|
184
|
+
purpose: typeof PLAY_DEPLOY_PURPOSE;
|
|
185
|
+
sub: 'play_deploy_worker';
|
|
186
|
+
v: typeof INTERNAL_BOOTSTRAP_TOKEN_VERSION;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
type WorkflowInternalTokenBaseClaims = {
|
|
190
|
+
aud: typeof INTERNAL_WORKFLOW_EXECUTION_AUDIENCE;
|
|
191
|
+
exp: number;
|
|
192
|
+
iat: number;
|
|
193
|
+
iss: typeof INTERNAL_BOOTSTRAP_TOKEN_ISSUER;
|
|
194
|
+
org_id: string;
|
|
195
|
+
sub: string;
|
|
196
|
+
v: typeof INTERNAL_BOOTSTRAP_TOKEN_VERSION;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
export type WorkflowRunExecutionTokenClaims =
|
|
200
|
+
WorkflowInternalTokenBaseClaims & {
|
|
201
|
+
purpose: typeof INTERNAL_WORKFLOW_EXECUTION_PURPOSE;
|
|
202
|
+
workflow_id: string;
|
|
203
|
+
run_id: string;
|
|
204
|
+
// Billing owner for this execution. Defaults to run_id on older tokens and
|
|
205
|
+
// root/top-level run id for child play executor tokens.
|
|
206
|
+
billing_run_id?: string;
|
|
207
|
+
// Required by the runtime storage endpoint. Do not make this optional:
|
|
208
|
+
// play/table names are caller-provided in runtime API requests, so the
|
|
209
|
+
// signed executor token must carry the authoritative play scope.
|
|
210
|
+
play_name: string;
|
|
211
|
+
allowed_secrets?: string[];
|
|
212
|
+
max_credits_per_run?: number;
|
|
213
|
+
integration_mode?: 'live' | 'eval_stub' | 'fixture';
|
|
214
|
+
fixture_behavior?: FixtureBehavior;
|
|
215
|
+
// Marks a run launched by an automated test harness (set in non-prod only).
|
|
216
|
+
// Carried on the signed token so the terminal runtime callback — which only
|
|
217
|
+
// sees executor-token auth, not the original CLI request — can suppress SDK
|
|
218
|
+
// CLI error alerts for intentionally failing test plays. Absent on real runs.
|
|
219
|
+
synthetic?: true;
|
|
220
|
+
// Identity of the actor that launched the run, carried on the signed token
|
|
221
|
+
// for the same reason as `synthetic`: the terminal runtime callback only
|
|
222
|
+
// sees executor-token auth, so without these claims it cannot attribute the
|
|
223
|
+
// run and emits "(unknown actor)" alerts. Optional — trigger/cron/system
|
|
224
|
+
// launches have no human actor, and older tokens predate the claims.
|
|
225
|
+
actor_user_id?: string;
|
|
226
|
+
actor_email?: string;
|
|
227
|
+
/** Durable scheduler topology attested by the launch-time app. */
|
|
228
|
+
scheduler_schema?: string;
|
|
229
|
+
/** Scheduler generation that owns durable writes for this execution leg. */
|
|
230
|
+
run_attempt?: number;
|
|
231
|
+
/** Opaque scheduler lease nonce bound to this exact execution leg. */
|
|
232
|
+
run_lease_token?: string;
|
|
233
|
+
/**
|
|
234
|
+
* Terminal transition contract declared by the worker release that asked
|
|
235
|
+
* for this token. Typed as a string, not a union: a token minted by an
|
|
236
|
+
* older or newer build may carry a value this build does not know, and
|
|
237
|
+
* that must degrade to the compatibility wake path rather than make an
|
|
238
|
+
* otherwise valid execution token fail verification.
|
|
239
|
+
*/
|
|
240
|
+
terminal_contract?: string;
|
|
241
|
+
/** Explicit allow-list. Tokens without capabilities fail validation. */
|
|
242
|
+
capabilities: RuntimeExecutionCapability[];
|
|
243
|
+
/** Trusted deployment origin that issued this run token. */
|
|
244
|
+
runtime_origin?: string;
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
export type RuntimeRateScopeTokenClaims = {
|
|
248
|
+
aud: typeof RUNTIME_RATE_SCOPE_AUDIENCE;
|
|
249
|
+
exp: number;
|
|
250
|
+
iat: number;
|
|
251
|
+
iss: typeof INTERNAL_BOOTSTRAP_TOKEN_ISSUER;
|
|
252
|
+
org_id: string;
|
|
253
|
+
run_id: string;
|
|
254
|
+
provider: string;
|
|
255
|
+
bucket_id: string;
|
|
256
|
+
scope_kind: 'fleet_managed' | 'customer_credential' | 'org';
|
|
257
|
+
purpose: typeof RUNTIME_RATE_SCOPE_PURPOSE;
|
|
258
|
+
sub: 'runtime_rate_scope';
|
|
259
|
+
v: typeof INTERNAL_BOOTSTRAP_TOKEN_VERSION;
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
function isValidWorkflowIntegrationMode(
|
|
263
|
+
value: unknown,
|
|
264
|
+
): value is NonNullable<WorkflowRunExecutionTokenClaims['integration_mode']> {
|
|
265
|
+
return value === 'live' || value === 'eval_stub' || value === 'fixture';
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function getRootSecret(): string {
|
|
269
|
+
const secretFromEnv = process.env.BETTER_AUTH_SECRET;
|
|
270
|
+
if (!secretFromEnv) {
|
|
271
|
+
throw new Error('BETTER_AUTH_SECRET is not set.');
|
|
272
|
+
}
|
|
273
|
+
return `${secretFromEnv}:${INTERNAL_BOOTSTRAP_TOKEN_DERIVATION_LABEL}`;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
let workflowKeyCache: {
|
|
277
|
+
secret: string;
|
|
278
|
+
privateKey: CryptoKey;
|
|
279
|
+
publicJwk: JWK & { kid: string };
|
|
280
|
+
} | null = null;
|
|
281
|
+
|
|
282
|
+
async function deriveWorkflowExecutorSeed(secret: string): Promise<Uint8Array> {
|
|
283
|
+
// Convex WebCrypto does not implement HKDF. Implement the RFC 5869
|
|
284
|
+
// extract/expand operations with HMAC-SHA-256 instead, so this stays
|
|
285
|
+
// byte-for-byte compatible with the prior HKDF(seed, zero salt, label).
|
|
286
|
+
const toArrayBuffer = (bytes: Uint8Array): ArrayBuffer => {
|
|
287
|
+
const copy = new Uint8Array(bytes.byteLength);
|
|
288
|
+
copy.set(bytes);
|
|
289
|
+
return copy.buffer;
|
|
290
|
+
};
|
|
291
|
+
const hmac = async (
|
|
292
|
+
keyMaterial: Uint8Array,
|
|
293
|
+
message: Uint8Array,
|
|
294
|
+
): Promise<Uint8Array> => {
|
|
295
|
+
const key = await crypto.subtle.importKey(
|
|
296
|
+
'raw',
|
|
297
|
+
toArrayBuffer(keyMaterial),
|
|
298
|
+
{ name: 'HMAC', hash: 'SHA-256' },
|
|
299
|
+
false,
|
|
300
|
+
['sign'],
|
|
301
|
+
);
|
|
302
|
+
return new Uint8Array(
|
|
303
|
+
await crypto.subtle.sign('HMAC', key, toArrayBuffer(message)),
|
|
304
|
+
);
|
|
305
|
+
};
|
|
306
|
+
const info = new TextEncoder().encode(WORKFLOW_EXECUTOR_KEY_DERIVATION_LABEL);
|
|
307
|
+
const prk = await hmac(new Uint8Array(32), new TextEncoder().encode(secret));
|
|
308
|
+
const expandInput = new Uint8Array(info.length + 1);
|
|
309
|
+
expandInput.set(info);
|
|
310
|
+
expandInput[info.length] = 1;
|
|
311
|
+
return await hmac(prk, expandInput);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async function workflowExecutorKeyPair(): Promise<{
|
|
315
|
+
privateKey: CryptoKey;
|
|
316
|
+
publicJwk: JWK & { kid: string };
|
|
317
|
+
}> {
|
|
318
|
+
const secret = process.env.BETTER_AUTH_SECRET?.trim();
|
|
319
|
+
if (!secret) throw new Error('BETTER_AUTH_SECRET is not set.');
|
|
320
|
+
if (workflowKeyCache?.secret === secret) return workflowKeyCache;
|
|
321
|
+
const seed = await deriveWorkflowExecutorSeed(secret);
|
|
322
|
+
const pkcs8 = new Uint8Array(ED25519_PKCS8_SEED_PREFIX.length + seed.length);
|
|
323
|
+
pkcs8.set(ED25519_PKCS8_SEED_PREFIX);
|
|
324
|
+
pkcs8.set(seed, ED25519_PKCS8_SEED_PREFIX.length);
|
|
325
|
+
const privateKey = await crypto.subtle.importKey(
|
|
326
|
+
'pkcs8',
|
|
327
|
+
pkcs8,
|
|
328
|
+
{ name: 'Ed25519' },
|
|
329
|
+
true,
|
|
330
|
+
['sign'],
|
|
331
|
+
);
|
|
332
|
+
const exported = await exportJWK(privateKey);
|
|
333
|
+
const publicJwk = {
|
|
334
|
+
kty: exported.kty,
|
|
335
|
+
crv: exported.crv,
|
|
336
|
+
x: exported.x,
|
|
337
|
+
use: 'sig',
|
|
338
|
+
alg: 'EdDSA',
|
|
339
|
+
kid: `runtime-${String(exported.x).slice(0, 16)}`,
|
|
340
|
+
} satisfies JWK & { kid: string };
|
|
341
|
+
workflowKeyCache = { secret, privateKey, publicJwk };
|
|
342
|
+
return workflowKeyCache;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export async function workflowExecutorPublicJwks(): Promise<{
|
|
346
|
+
keys: Array<JWK & { kid: string }>;
|
|
347
|
+
}> {
|
|
348
|
+
const { publicJwk } = await workflowExecutorKeyPair();
|
|
349
|
+
return { keys: [publicJwk] };
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function bytesToBase64(bytes: Uint8Array): string {
|
|
353
|
+
if (typeof Buffer !== 'undefined') {
|
|
354
|
+
return Buffer.from(bytes).toString('base64');
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
let binary = '';
|
|
358
|
+
for (const byte of bytes) {
|
|
359
|
+
binary += String.fromCharCode(byte);
|
|
360
|
+
}
|
|
361
|
+
return btoa(binary);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function base64ToBytes(value: string): Uint8Array {
|
|
365
|
+
if (typeof Buffer !== 'undefined') {
|
|
366
|
+
return new Uint8Array(Buffer.from(value, 'base64'));
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const binary = atob(value);
|
|
370
|
+
const bytes = new Uint8Array(binary.length);
|
|
371
|
+
for (let index = 0; index < binary.length; index += 1) {
|
|
372
|
+
bytes[index] = binary.charCodeAt(index);
|
|
373
|
+
}
|
|
374
|
+
return bytes;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function toBase64Url(bytes: Uint8Array): string {
|
|
378
|
+
return bytesToBase64(bytes)
|
|
379
|
+
.replace(/\+/g, '-')
|
|
380
|
+
.replace(/\//g, '_')
|
|
381
|
+
.replace(/=+$/g, '');
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function fromBase64Url(value: string): Uint8Array {
|
|
385
|
+
const padding =
|
|
386
|
+
value.length % 4 === 0 ? '' : '='.repeat(4 - (value.length % 4));
|
|
387
|
+
const normalized = value.replace(/-/g, '+').replace(/_/g, '/') + padding;
|
|
388
|
+
return base64ToBytes(normalized);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function encodeJson(value: unknown): string {
|
|
392
|
+
return toBase64Url(new TextEncoder().encode(JSON.stringify(value)));
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function decodeJson<T>(value: string): T {
|
|
396
|
+
const decoded = new TextDecoder().decode(fromBase64Url(value));
|
|
397
|
+
return JSON.parse(decoded) as T;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
async function signTokenMaterial(value: string): Promise<string> {
|
|
401
|
+
const key = await crypto.subtle.importKey(
|
|
402
|
+
'raw',
|
|
403
|
+
new TextEncoder().encode(getRootSecret()),
|
|
404
|
+
{
|
|
405
|
+
name: 'HMAC',
|
|
406
|
+
hash: 'SHA-256',
|
|
407
|
+
},
|
|
408
|
+
false,
|
|
409
|
+
['sign'],
|
|
410
|
+
);
|
|
411
|
+
const signature = await crypto.subtle.sign(
|
|
412
|
+
'HMAC',
|
|
413
|
+
key,
|
|
414
|
+
new TextEncoder().encode(value),
|
|
415
|
+
);
|
|
416
|
+
return toBase64Url(new Uint8Array(signature));
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function timingSafeEqual(left: Uint8Array, right: Uint8Array): boolean {
|
|
420
|
+
if (left.length !== right.length) {
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
let mismatch = 0;
|
|
425
|
+
for (let index = 0; index < left.length; index += 1) {
|
|
426
|
+
mismatch |= left[index] ^ right[index];
|
|
427
|
+
}
|
|
428
|
+
return mismatch === 0;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function isValidClaimsShape(
|
|
432
|
+
claims: Record<string, unknown>,
|
|
433
|
+
): claims is IngestionInternalTokenClaims {
|
|
434
|
+
const hasBaseShape =
|
|
435
|
+
claims.v === INTERNAL_BOOTSTRAP_TOKEN_VERSION &&
|
|
436
|
+
claims.iss === INTERNAL_BOOTSTRAP_TOKEN_ISSUER &&
|
|
437
|
+
claims.aud === INTERNAL_BOOTSTRAP_TOKEN_AUDIENCE &&
|
|
438
|
+
typeof claims.org_id === 'string' &&
|
|
439
|
+
claims.org_id.length > 0 &&
|
|
440
|
+
typeof claims.provider === 'string' &&
|
|
441
|
+
claims.provider.length > 0 &&
|
|
442
|
+
typeof claims.sub === 'string' &&
|
|
443
|
+
claims.sub.length > 0 &&
|
|
444
|
+
typeof claims.iat === 'number' &&
|
|
445
|
+
Number.isFinite(claims.iat) &&
|
|
446
|
+
typeof claims.exp === 'number' &&
|
|
447
|
+
Number.isFinite(claims.exp);
|
|
448
|
+
|
|
449
|
+
if (!hasBaseShape) {
|
|
450
|
+
return false;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (claims.purpose === INTERNAL_BOOTSTRAP_BOOTSTRAP_PURPOSE) {
|
|
454
|
+
return (
|
|
455
|
+
typeof claims.request_token === 'string' &&
|
|
456
|
+
claims.request_token.length > 0
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if (claims.purpose === INTERNAL_BOOTSTRAP_EXECUTION_PURPOSE) {
|
|
461
|
+
return (
|
|
462
|
+
typeof claims.subscription_id === 'string' &&
|
|
463
|
+
claims.subscription_id.length > 0
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
return false;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function isValidOAuthRefreshClaimsShape(
|
|
471
|
+
claims: Record<string, unknown>,
|
|
472
|
+
): claims is OAuthTokenRefreshTokenClaims {
|
|
473
|
+
return (
|
|
474
|
+
claims.v === INTERNAL_BOOTSTRAP_TOKEN_VERSION &&
|
|
475
|
+
claims.iss === INTERNAL_BOOTSTRAP_TOKEN_ISSUER &&
|
|
476
|
+
claims.aud === INTERNAL_OAUTH_REFRESH_AUDIENCE &&
|
|
477
|
+
claims.purpose === INTERNAL_OAUTH_REFRESH_PURPOSE &&
|
|
478
|
+
typeof claims.org_id === 'string' &&
|
|
479
|
+
claims.org_id.length > 0 &&
|
|
480
|
+
typeof claims.provider === 'string' &&
|
|
481
|
+
claims.provider.length > 0 &&
|
|
482
|
+
typeof claims.sub === 'string' &&
|
|
483
|
+
claims.sub.length > 0 &&
|
|
484
|
+
typeof claims.iat === 'number' &&
|
|
485
|
+
Number.isFinite(claims.iat) &&
|
|
486
|
+
typeof claims.exp === 'number' &&
|
|
487
|
+
Number.isFinite(claims.exp)
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function isValidRuntimeOrigin(value: unknown): value is string {
|
|
492
|
+
if (typeof value !== 'string') return false;
|
|
493
|
+
try {
|
|
494
|
+
const url = new URL(value);
|
|
495
|
+
if (url.origin !== value || url.username || url.password) return false;
|
|
496
|
+
if (url.protocol === 'https:') return true;
|
|
497
|
+
return (
|
|
498
|
+
url.protocol === 'http:' &&
|
|
499
|
+
(url.hostname === 'localhost' ||
|
|
500
|
+
url.hostname === '127.0.0.1' ||
|
|
501
|
+
url.hostname === '[::1]')
|
|
502
|
+
);
|
|
503
|
+
} catch {
|
|
504
|
+
return false;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function isValidWorkflowClaimsShape(
|
|
509
|
+
claims: Record<string, unknown>,
|
|
510
|
+
): claims is WorkflowRunExecutionTokenClaims {
|
|
511
|
+
return (
|
|
512
|
+
claims.v === INTERNAL_BOOTSTRAP_TOKEN_VERSION &&
|
|
513
|
+
claims.iss === INTERNAL_BOOTSTRAP_TOKEN_ISSUER &&
|
|
514
|
+
claims.aud === INTERNAL_WORKFLOW_EXECUTION_AUDIENCE &&
|
|
515
|
+
claims.purpose === INTERNAL_WORKFLOW_EXECUTION_PURPOSE &&
|
|
516
|
+
typeof claims.org_id === 'string' &&
|
|
517
|
+
claims.org_id.length > 0 &&
|
|
518
|
+
typeof claims.workflow_id === 'string' &&
|
|
519
|
+
claims.workflow_id.length > 0 &&
|
|
520
|
+
typeof claims.run_id === 'string' &&
|
|
521
|
+
claims.run_id.length > 0 &&
|
|
522
|
+
(claims.billing_run_id === undefined ||
|
|
523
|
+
(typeof claims.billing_run_id === 'string' &&
|
|
524
|
+
claims.billing_run_id.length > 0)) &&
|
|
525
|
+
// Hard cutover invariant: old workflow tokens without play_name are not
|
|
526
|
+
// accepted. If this is loosened, runtime storage minting can no longer
|
|
527
|
+
// prove that the requested playName came from the signed run contract.
|
|
528
|
+
typeof claims.play_name === 'string' &&
|
|
529
|
+
claims.play_name.length > 0 &&
|
|
530
|
+
(claims.max_credits_per_run === undefined ||
|
|
531
|
+
(typeof claims.max_credits_per_run === 'number' &&
|
|
532
|
+
Number.isFinite(claims.max_credits_per_run) &&
|
|
533
|
+
claims.max_credits_per_run > 0)) &&
|
|
534
|
+
(claims.integration_mode === undefined ||
|
|
535
|
+
isValidWorkflowIntegrationMode(claims.integration_mode)) &&
|
|
536
|
+
(claims.fixture_behavior === undefined ||
|
|
537
|
+
(claims.integration_mode === 'fixture' &&
|
|
538
|
+
validateFixtureBehavior(claims.fixture_behavior).ok)) &&
|
|
539
|
+
(claims.synthetic === undefined || claims.synthetic === true) &&
|
|
540
|
+
(claims.actor_user_id === undefined ||
|
|
541
|
+
typeof claims.actor_user_id === 'string') &&
|
|
542
|
+
(claims.actor_email === undefined ||
|
|
543
|
+
typeof claims.actor_email === 'string') &&
|
|
544
|
+
(claims.scheduler_schema === undefined ||
|
|
545
|
+
normalizeRuntimeSchedulerSchema(claims.scheduler_schema) !== null) &&
|
|
546
|
+
(claims.run_attempt === undefined ||
|
|
547
|
+
(typeof claims.run_attempt === 'number' &&
|
|
548
|
+
Number.isSafeInteger(claims.run_attempt) &&
|
|
549
|
+
claims.run_attempt >= 0)) &&
|
|
550
|
+
(claims.run_lease_token === undefined ||
|
|
551
|
+
(typeof claims.run_lease_token === 'string' &&
|
|
552
|
+
claims.run_lease_token.length > 0)) &&
|
|
553
|
+
(claims.terminal_contract === undefined ||
|
|
554
|
+
typeof claims.terminal_contract === 'string') &&
|
|
555
|
+
(claims.runtime_origin === undefined ||
|
|
556
|
+
isValidRuntimeOrigin(claims.runtime_origin)) &&
|
|
557
|
+
Array.isArray(claims.capabilities) &&
|
|
558
|
+
claims.capabilities.length > 0 &&
|
|
559
|
+
claims.capabilities.every(isRuntimeExecutionCapability) &&
|
|
560
|
+
typeof claims.sub === 'string' &&
|
|
561
|
+
claims.sub.length > 0 &&
|
|
562
|
+
typeof claims.iat === 'number' &&
|
|
563
|
+
Number.isFinite(claims.iat) &&
|
|
564
|
+
typeof claims.exp === 'number' &&
|
|
565
|
+
Number.isFinite(claims.exp)
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
export async function mintIngestionProviderSyncBootstrapToken(input: {
|
|
570
|
+
orgId: string;
|
|
571
|
+
provider: string;
|
|
572
|
+
requestToken: string;
|
|
573
|
+
requestedByUserId: string | null;
|
|
574
|
+
nowMs?: number;
|
|
575
|
+
}): Promise<string> {
|
|
576
|
+
const issuedAt = Math.floor((input.nowMs ?? Date.now()) / 1000);
|
|
577
|
+
const claims: IngestionProviderSyncBootstrapTokenClaims = {
|
|
578
|
+
v: INTERNAL_BOOTSTRAP_TOKEN_VERSION,
|
|
579
|
+
iss: INTERNAL_BOOTSTRAP_TOKEN_ISSUER,
|
|
580
|
+
aud: INTERNAL_BOOTSTRAP_TOKEN_AUDIENCE,
|
|
581
|
+
purpose: INTERNAL_BOOTSTRAP_BOOTSTRAP_PURPOSE,
|
|
582
|
+
org_id: input.orgId,
|
|
583
|
+
provider: input.provider,
|
|
584
|
+
request_token: input.requestToken,
|
|
585
|
+
sub: input.requestedByUserId ?? 'system',
|
|
586
|
+
iat: issuedAt,
|
|
587
|
+
exp: issuedAt + INTERNAL_BOOTSTRAP_TOKEN_TTL_SECONDS,
|
|
588
|
+
};
|
|
589
|
+
const header = {
|
|
590
|
+
alg: 'HS256',
|
|
591
|
+
typ: 'JWT',
|
|
592
|
+
};
|
|
593
|
+
const encodedHeader = encodeJson(header);
|
|
594
|
+
const encodedClaims = encodeJson(claims);
|
|
595
|
+
const signingInput = `${encodedHeader}.${encodedClaims}`;
|
|
596
|
+
const signature = await signTokenMaterial(signingInput);
|
|
597
|
+
return `${signingInput}.${signature}`;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
export async function mintIngestionSubscriptionExecutionToken(input: {
|
|
601
|
+
orgId: string;
|
|
602
|
+
provider: string;
|
|
603
|
+
subscriptionId: string;
|
|
604
|
+
subject?: string | null;
|
|
605
|
+
nowMs?: number;
|
|
606
|
+
}): Promise<string> {
|
|
607
|
+
const issuedAt = Math.floor((input.nowMs ?? Date.now()) / 1000);
|
|
608
|
+
const claims: IngestionSubscriptionExecutionTokenClaims = {
|
|
609
|
+
v: INTERNAL_BOOTSTRAP_TOKEN_VERSION,
|
|
610
|
+
iss: INTERNAL_BOOTSTRAP_TOKEN_ISSUER,
|
|
611
|
+
aud: INTERNAL_BOOTSTRAP_TOKEN_AUDIENCE,
|
|
612
|
+
purpose: INTERNAL_BOOTSTRAP_EXECUTION_PURPOSE,
|
|
613
|
+
org_id: input.orgId,
|
|
614
|
+
provider: input.provider,
|
|
615
|
+
subscription_id: input.subscriptionId,
|
|
616
|
+
sub: input.subject ?? 'ingestion_executor',
|
|
617
|
+
iat: issuedAt,
|
|
618
|
+
exp: issuedAt + INTERNAL_BOOTSTRAP_TOKEN_TTL_SECONDS,
|
|
619
|
+
};
|
|
620
|
+
const header = {
|
|
621
|
+
alg: 'HS256',
|
|
622
|
+
typ: 'JWT',
|
|
623
|
+
};
|
|
624
|
+
const encodedHeader = encodeJson(header);
|
|
625
|
+
const encodedClaims = encodeJson(claims);
|
|
626
|
+
const signingInput = `${encodedHeader}.${encodedClaims}`;
|
|
627
|
+
const signature = await signTokenMaterial(signingInput);
|
|
628
|
+
return `${signingInput}.${signature}`;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
export async function mintWorkflowRunExecutionToken(input: {
|
|
632
|
+
orgId: string;
|
|
633
|
+
workflowId: string;
|
|
634
|
+
runId: string;
|
|
635
|
+
billingRunId?: string | null;
|
|
636
|
+
playName: string;
|
|
637
|
+
allowedSecrets?: readonly string[] | null;
|
|
638
|
+
maxCreditsPerRun?: number | null;
|
|
639
|
+
integrationMode?: 'live' | 'eval_stub' | 'fixture' | null;
|
|
640
|
+
fixtureBehavior?: FixtureBehavior | null;
|
|
641
|
+
synthetic?: boolean | null;
|
|
642
|
+
actorUserId?: string | null;
|
|
643
|
+
actorEmail?: string | null;
|
|
644
|
+
schedulerSchema?: string | null;
|
|
645
|
+
runAttempt?: number | null;
|
|
646
|
+
runLeaseToken?: string | null;
|
|
647
|
+
terminalContract?: DirectCompletedTerminalContract | null;
|
|
648
|
+
capabilities?: readonly RuntimeExecutionCapability[];
|
|
649
|
+
runtimeOrigin?: string | null;
|
|
650
|
+
subject?: string | null;
|
|
651
|
+
nowMs?: number;
|
|
652
|
+
}): Promise<string> {
|
|
653
|
+
const fixtureBehavior = validateFixtureBehavior(input.fixtureBehavior);
|
|
654
|
+
if (!fixtureBehavior.ok) throw new Error(fixtureBehavior.error);
|
|
655
|
+
if (fixtureBehavior.behavior && input.integrationMode !== 'fixture') {
|
|
656
|
+
throw new Error(
|
|
657
|
+
'fixtureBehavior requires workflow integration mode "fixture".',
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
const actorUserId = input.actorUserId?.trim() || null;
|
|
661
|
+
const actorEmail = input.actorEmail?.trim() || null;
|
|
662
|
+
const schedulerSchema = normalizeRuntimeSchedulerSchema(
|
|
663
|
+
input.schedulerSchema,
|
|
664
|
+
);
|
|
665
|
+
const issuedAt = Math.floor((input.nowMs ?? Date.now()) / 1000);
|
|
666
|
+
// A retired contract value mints no claim; the verifier then selects the wake path.
|
|
667
|
+
const mintedTerminalContract = directCompletedTerminalContract(
|
|
668
|
+
input.terminalContract,
|
|
669
|
+
);
|
|
670
|
+
const claims: WorkflowRunExecutionTokenClaims = {
|
|
671
|
+
v: INTERNAL_BOOTSTRAP_TOKEN_VERSION,
|
|
672
|
+
iss: INTERNAL_BOOTSTRAP_TOKEN_ISSUER,
|
|
673
|
+
aud: INTERNAL_WORKFLOW_EXECUTION_AUDIENCE,
|
|
674
|
+
purpose: INTERNAL_WORKFLOW_EXECUTION_PURPOSE,
|
|
675
|
+
org_id: input.orgId,
|
|
676
|
+
workflow_id: input.workflowId,
|
|
677
|
+
run_id: input.runId,
|
|
678
|
+
...(input.billingRunId?.trim()
|
|
679
|
+
? { billing_run_id: input.billingRunId.trim() }
|
|
680
|
+
: {}),
|
|
681
|
+
play_name: input.playName,
|
|
682
|
+
...(input.allowedSecrets?.length
|
|
683
|
+
? {
|
|
684
|
+
allowed_secrets: [
|
|
685
|
+
...new Set(
|
|
686
|
+
input.allowedSecrets
|
|
687
|
+
.filter((name): name is string => typeof name === 'string')
|
|
688
|
+
.map((name) => name.trim().toUpperCase())
|
|
689
|
+
.filter(Boolean),
|
|
690
|
+
),
|
|
691
|
+
],
|
|
692
|
+
}
|
|
693
|
+
: {}),
|
|
694
|
+
...(typeof input.maxCreditsPerRun === 'number' &&
|
|
695
|
+
Number.isFinite(input.maxCreditsPerRun) &&
|
|
696
|
+
input.maxCreditsPerRun > 0
|
|
697
|
+
? { max_credits_per_run: input.maxCreditsPerRun }
|
|
698
|
+
: {}),
|
|
699
|
+
...(isValidWorkflowIntegrationMode(input.integrationMode)
|
|
700
|
+
? { integration_mode: input.integrationMode }
|
|
701
|
+
: {}),
|
|
702
|
+
...(fixtureBehavior.behavior
|
|
703
|
+
? { fixture_behavior: fixtureBehavior.behavior }
|
|
704
|
+
: {}),
|
|
705
|
+
...(input.synthetic ? { synthetic: true as const } : {}),
|
|
706
|
+
...(actorUserId ? { actor_user_id: actorUserId } : {}),
|
|
707
|
+
...(actorEmail ? { actor_email: actorEmail } : {}),
|
|
708
|
+
...(schedulerSchema ? { scheduler_schema: schedulerSchema } : {}),
|
|
709
|
+
...(typeof input.runAttempt === 'number' &&
|
|
710
|
+
Number.isSafeInteger(input.runAttempt) &&
|
|
711
|
+
input.runAttempt >= 0
|
|
712
|
+
? { run_attempt: input.runAttempt }
|
|
713
|
+
: {}),
|
|
714
|
+
...(input.runLeaseToken?.trim()
|
|
715
|
+
? { run_lease_token: input.runLeaseToken.trim() }
|
|
716
|
+
: {}),
|
|
717
|
+
...(mintedTerminalContract
|
|
718
|
+
? { terminal_contract: mintedTerminalContract }
|
|
719
|
+
: {}),
|
|
720
|
+
capabilities: normalizeRuntimeExecutionCapabilities(
|
|
721
|
+
input.capabilities ?? DEFAULT_RUNTIME_EXECUTION_CAPABILITIES,
|
|
722
|
+
),
|
|
723
|
+
...(input.runtimeOrigin?.trim()
|
|
724
|
+
? { runtime_origin: new URL(input.runtimeOrigin).origin }
|
|
725
|
+
: {}),
|
|
726
|
+
sub: input.subject ?? 'workflow_executor',
|
|
727
|
+
iat: issuedAt,
|
|
728
|
+
exp: issuedAt + INTERNAL_WORKFLOW_TOKEN_TTL_SECONDS,
|
|
729
|
+
};
|
|
730
|
+
const { privateKey, publicJwk } = await workflowExecutorKeyPair();
|
|
731
|
+
return await new SignJWT(claims)
|
|
732
|
+
.setProtectedHeader({ alg: 'EdDSA', typ: 'JWT', kid: publicJwk.kid })
|
|
733
|
+
.sign(privateKey);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
export async function mintRuntimeRateScopeToken(input: {
|
|
737
|
+
orgId: string;
|
|
738
|
+
runId: string;
|
|
739
|
+
provider: string;
|
|
740
|
+
bucketId: string;
|
|
741
|
+
scopeKind: RuntimeRateScopeTokenClaims['scope_kind'];
|
|
742
|
+
nowMs?: number;
|
|
743
|
+
}): Promise<string> {
|
|
744
|
+
const issuedAt = Math.floor((input.nowMs ?? Date.now()) / 1000);
|
|
745
|
+
const claims: RuntimeRateScopeTokenClaims = {
|
|
746
|
+
v: INTERNAL_BOOTSTRAP_TOKEN_VERSION,
|
|
747
|
+
iss: INTERNAL_BOOTSTRAP_TOKEN_ISSUER,
|
|
748
|
+
aud: RUNTIME_RATE_SCOPE_AUDIENCE,
|
|
749
|
+
purpose: RUNTIME_RATE_SCOPE_PURPOSE,
|
|
750
|
+
org_id: input.orgId,
|
|
751
|
+
run_id: input.runId,
|
|
752
|
+
provider: input.provider,
|
|
753
|
+
bucket_id: input.bucketId,
|
|
754
|
+
scope_kind: input.scopeKind,
|
|
755
|
+
sub: 'runtime_rate_scope',
|
|
756
|
+
iat: issuedAt,
|
|
757
|
+
exp: issuedAt + RUNTIME_RATE_SCOPE_TOKEN_TTL_SECONDS,
|
|
758
|
+
};
|
|
759
|
+
const { privateKey, publicJwk } = await workflowExecutorKeyPair();
|
|
760
|
+
return await new SignJWT(claims)
|
|
761
|
+
.setProtectedHeader({ alg: 'EdDSA', typ: 'JWT', kid: publicJwk.kid })
|
|
762
|
+
.sign(privateKey);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
export async function verifyRuntimeRateScopeTokenWithJwks(
|
|
766
|
+
token: string,
|
|
767
|
+
jwks: { keys: JWK[] },
|
|
768
|
+
nowMs = Date.now(),
|
|
769
|
+
): Promise<RuntimeRateScopeTokenClaims> {
|
|
770
|
+
const headerKid = decodeJson<Record<string, unknown>>(
|
|
771
|
+
token.split('.')[0] ?? '',
|
|
772
|
+
).kid;
|
|
773
|
+
const jwk = jwks.keys.find((candidate) => candidate.kid === headerKid);
|
|
774
|
+
if (!jwk) throw new Error('Invalid runtime rate-scope token key.');
|
|
775
|
+
const key = await importJWK(jwk, 'EdDSA');
|
|
776
|
+
const verified = await jwtVerify(token, key, {
|
|
777
|
+
algorithms: ['EdDSA'],
|
|
778
|
+
issuer: INTERNAL_BOOTSTRAP_TOKEN_ISSUER,
|
|
779
|
+
audience: RUNTIME_RATE_SCOPE_AUDIENCE,
|
|
780
|
+
currentDate: new Date(nowMs),
|
|
781
|
+
typ: 'JWT',
|
|
782
|
+
});
|
|
783
|
+
const claims = verified.payload as Record<string, unknown>;
|
|
784
|
+
if (
|
|
785
|
+
claims.v !== INTERNAL_BOOTSTRAP_TOKEN_VERSION ||
|
|
786
|
+
claims.purpose !== RUNTIME_RATE_SCOPE_PURPOSE ||
|
|
787
|
+
claims.sub !== 'runtime_rate_scope' ||
|
|
788
|
+
typeof claims.org_id !== 'string' ||
|
|
789
|
+
typeof claims.run_id !== 'string' ||
|
|
790
|
+
typeof claims.provider !== 'string' ||
|
|
791
|
+
typeof claims.bucket_id !== 'string' ||
|
|
792
|
+
!['fleet_managed', 'customer_credential', 'org'].includes(
|
|
793
|
+
String(claims.scope_kind),
|
|
794
|
+
)
|
|
795
|
+
) {
|
|
796
|
+
throw new Error('Invalid runtime rate-scope token.');
|
|
797
|
+
}
|
|
798
|
+
return claims as RuntimeRateScopeTokenClaims;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
export async function verifyRuntimeRateScopeToken(
|
|
802
|
+
token: string,
|
|
803
|
+
nowMs = Date.now(),
|
|
804
|
+
): Promise<RuntimeRateScopeTokenClaims> {
|
|
805
|
+
const { publicJwk } = await workflowExecutorKeyPair();
|
|
806
|
+
return await verifyRuntimeRateScopeTokenWithJwks(
|
|
807
|
+
token,
|
|
808
|
+
{ keys: [publicJwk] },
|
|
809
|
+
nowMs,
|
|
810
|
+
);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
/**
|
|
814
|
+
* Deploy tokens authorize exactly one GitHub deploy-on-push deployment. The
|
|
815
|
+
* control plane mints one at webhook-intake time (the deploy worker holds no
|
|
816
|
+
* signing material) and only the /api/v2/deployments/* endpoints accept it.
|
|
817
|
+
* The granting principal is the play project's binding admin; the grant at
|
|
818
|
+
* bind time is the authorization event.
|
|
819
|
+
*/
|
|
820
|
+
export async function mintPlayDeployToken(input: {
|
|
821
|
+
orgId: string;
|
|
822
|
+
playProjectId: string;
|
|
823
|
+
deploymentId: string;
|
|
824
|
+
grantedByUserId: string;
|
|
825
|
+
nowMs?: number;
|
|
826
|
+
}): Promise<string> {
|
|
827
|
+
const issuedAt = Math.floor((input.nowMs ?? Date.now()) / 1000);
|
|
828
|
+
const claims: PlayDeployTokenClaims = {
|
|
829
|
+
v: INTERNAL_BOOTSTRAP_TOKEN_VERSION,
|
|
830
|
+
iss: INTERNAL_BOOTSTRAP_TOKEN_ISSUER,
|
|
831
|
+
aud: PLAY_DEPLOY_AUDIENCE,
|
|
832
|
+
purpose: PLAY_DEPLOY_PURPOSE,
|
|
833
|
+
org_id: input.orgId,
|
|
834
|
+
play_project_id: input.playProjectId,
|
|
835
|
+
deployment_id: input.deploymentId,
|
|
836
|
+
granted_by_user_id: input.grantedByUserId,
|
|
837
|
+
sub: 'play_deploy_worker',
|
|
838
|
+
iat: issuedAt,
|
|
839
|
+
exp: issuedAt + PLAY_DEPLOY_TOKEN_TTL_SECONDS,
|
|
840
|
+
};
|
|
841
|
+
const { privateKey, publicJwk } = await workflowExecutorKeyPair();
|
|
842
|
+
return await new SignJWT(claims)
|
|
843
|
+
.setProtectedHeader({ alg: 'EdDSA', typ: 'JWT', kid: publicJwk.kid })
|
|
844
|
+
.sign(privateKey);
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
/**
|
|
848
|
+
* Cheap pre-verification discriminator: does this bearer token claim to be a
|
|
849
|
+
* play deploy token? Decodes without verifying — callers must still verify.
|
|
850
|
+
*/
|
|
851
|
+
export function isPlayDeployTokenCandidate(token: string): boolean {
|
|
852
|
+
const segments = token.split('.');
|
|
853
|
+
if (segments.length !== 3) return false;
|
|
854
|
+
try {
|
|
855
|
+
const claims = decodeJson<Record<string, unknown>>(segments[1] ?? '');
|
|
856
|
+
return claims.aud === PLAY_DEPLOY_AUDIENCE;
|
|
857
|
+
} catch {
|
|
858
|
+
return false;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
export async function verifyPlayDeployToken(
|
|
863
|
+
token: string,
|
|
864
|
+
nowMs = Date.now(),
|
|
865
|
+
): Promise<PlayDeployTokenClaims> {
|
|
866
|
+
const { publicJwk } = await workflowExecutorKeyPair();
|
|
867
|
+
const headerKid = decodeJson<Record<string, unknown>>(
|
|
868
|
+
token.split('.')[0] ?? '',
|
|
869
|
+
).kid;
|
|
870
|
+
if (headerKid !== publicJwk.kid) {
|
|
871
|
+
throw new Error('Invalid play deploy token key.');
|
|
872
|
+
}
|
|
873
|
+
const key = await importJWK(publicJwk, 'EdDSA');
|
|
874
|
+
const verified = await jwtVerify(token, key, {
|
|
875
|
+
algorithms: ['EdDSA'],
|
|
876
|
+
issuer: INTERNAL_BOOTSTRAP_TOKEN_ISSUER,
|
|
877
|
+
audience: PLAY_DEPLOY_AUDIENCE,
|
|
878
|
+
currentDate: new Date(nowMs),
|
|
879
|
+
typ: 'JWT',
|
|
880
|
+
});
|
|
881
|
+
const claims = verified.payload as Record<string, unknown>;
|
|
882
|
+
if (
|
|
883
|
+
claims.v !== INTERNAL_BOOTSTRAP_TOKEN_VERSION ||
|
|
884
|
+
claims.purpose !== PLAY_DEPLOY_PURPOSE ||
|
|
885
|
+
claims.sub !== 'play_deploy_worker' ||
|
|
886
|
+
typeof claims.org_id !== 'string' ||
|
|
887
|
+
!claims.org_id ||
|
|
888
|
+
typeof claims.play_project_id !== 'string' ||
|
|
889
|
+
!claims.play_project_id ||
|
|
890
|
+
typeof claims.deployment_id !== 'string' ||
|
|
891
|
+
!claims.deployment_id ||
|
|
892
|
+
typeof claims.granted_by_user_id !== 'string' ||
|
|
893
|
+
!claims.granted_by_user_id
|
|
894
|
+
) {
|
|
895
|
+
throw new Error('Invalid play deploy token.');
|
|
896
|
+
}
|
|
897
|
+
return claims as PlayDeployTokenClaims;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
// Server-only authorization for runtime OAuth token-refresh writes. Minted by
|
|
901
|
+
// the server during integration execution (which holds the signing secret) and
|
|
902
|
+
// verified by the Convex refresh mutation, so a member API key can never
|
|
903
|
+
// authorize a connection write — it can only execute, which triggers the
|
|
904
|
+
// server-side refresh that mints this token.
|
|
905
|
+
export async function mintOAuthTokenRefreshToken(input: {
|
|
906
|
+
orgId: string;
|
|
907
|
+
provider: string;
|
|
908
|
+
subject?: string | null;
|
|
909
|
+
nowMs?: number;
|
|
910
|
+
}): Promise<string> {
|
|
911
|
+
const issuedAt = Math.floor((input.nowMs ?? Date.now()) / 1000);
|
|
912
|
+
const claims: OAuthTokenRefreshTokenClaims = {
|
|
913
|
+
v: INTERNAL_BOOTSTRAP_TOKEN_VERSION,
|
|
914
|
+
iss: INTERNAL_BOOTSTRAP_TOKEN_ISSUER,
|
|
915
|
+
aud: INTERNAL_OAUTH_REFRESH_AUDIENCE,
|
|
916
|
+
purpose: INTERNAL_OAUTH_REFRESH_PURPOSE,
|
|
917
|
+
org_id: input.orgId,
|
|
918
|
+
provider: input.provider,
|
|
919
|
+
sub: input.subject ?? 'oauth_token_refresh',
|
|
920
|
+
iat: issuedAt,
|
|
921
|
+
exp: issuedAt + INTERNAL_BOOTSTRAP_TOKEN_TTL_SECONDS,
|
|
922
|
+
};
|
|
923
|
+
const header = {
|
|
924
|
+
alg: 'HS256',
|
|
925
|
+
typ: 'JWT',
|
|
926
|
+
};
|
|
927
|
+
const encodedHeader = encodeJson(header);
|
|
928
|
+
const encodedClaims = encodeJson(claims);
|
|
929
|
+
const signingInput = `${encodedHeader}.${encodedClaims}`;
|
|
930
|
+
const signature = await signTokenMaterial(signingInput);
|
|
931
|
+
return `${signingInput}.${signature}`;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
export async function verifyOAuthTokenRefreshToken(
|
|
935
|
+
token: string,
|
|
936
|
+
nowMs = Date.now(),
|
|
937
|
+
): Promise<OAuthTokenRefreshTokenClaims> {
|
|
938
|
+
const segments = token.split('.');
|
|
939
|
+
if (segments.length !== 3) {
|
|
940
|
+
throw new Error('Invalid OAuth refresh token.');
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
const [encodedHeader, encodedClaims, signature] = segments;
|
|
944
|
+
const header = decodeJson<Record<string, unknown>>(encodedHeader);
|
|
945
|
+
if (header.alg !== 'HS256' || header.typ !== 'JWT') {
|
|
946
|
+
throw new Error('Invalid OAuth refresh token.');
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
const expectedSignature = await signTokenMaterial(
|
|
950
|
+
`${encodedHeader}.${encodedClaims}`,
|
|
951
|
+
);
|
|
952
|
+
if (
|
|
953
|
+
!timingSafeEqual(fromBase64Url(signature), fromBase64Url(expectedSignature))
|
|
954
|
+
) {
|
|
955
|
+
throw new Error('Invalid OAuth refresh token.');
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
const claims = decodeJson<Record<string, unknown>>(encodedClaims);
|
|
959
|
+
if (!isValidOAuthRefreshClaimsShape(claims)) {
|
|
960
|
+
throw new Error('Invalid OAuth refresh token.');
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
const nowSeconds = Math.floor(nowMs / 1000);
|
|
964
|
+
if (claims.exp <= nowSeconds || claims.iat > nowSeconds + 30) {
|
|
965
|
+
throw new Error('OAuth refresh token expired.');
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
return claims;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
/**
|
|
972
|
+
* Mints a short-lived capability for one product-notification delivery. The
|
|
973
|
+
* delivery id is part of the signed scope so the receiving route cannot use a
|
|
974
|
+
* valid token to read another org delivery or OAuth connection.
|
|
975
|
+
*/
|
|
976
|
+
export async function mintProductNotificationDeliveryToken(input: {
|
|
977
|
+
orgId: string;
|
|
978
|
+
deliveryId: string;
|
|
979
|
+
nowMs?: number;
|
|
980
|
+
}): Promise<string> {
|
|
981
|
+
const issuedAt = Math.floor((input.nowMs ?? Date.now()) / 1000);
|
|
982
|
+
const claims: ProductNotificationDeliveryTokenClaims = {
|
|
983
|
+
v: INTERNAL_BOOTSTRAP_TOKEN_VERSION,
|
|
984
|
+
iss: INTERNAL_BOOTSTRAP_TOKEN_ISSUER,
|
|
985
|
+
aud: PRODUCT_NOTIFICATION_DELIVERY_AUDIENCE,
|
|
986
|
+
purpose: PRODUCT_NOTIFICATION_DELIVERY_PURPOSE,
|
|
987
|
+
org_id: input.orgId,
|
|
988
|
+
delivery_id: input.deliveryId,
|
|
989
|
+
sub: 'product_notification_dispatcher',
|
|
990
|
+
iat: issuedAt,
|
|
991
|
+
exp: issuedAt + INTERNAL_BOOTSTRAP_TOKEN_TTL_SECONDS,
|
|
992
|
+
};
|
|
993
|
+
const encodedHeader = encodeJson({ alg: 'HS256', typ: 'JWT' });
|
|
994
|
+
const encodedClaims = encodeJson(claims);
|
|
995
|
+
const signingInput = `${encodedHeader}.${encodedClaims}`;
|
|
996
|
+
const signature = await signTokenMaterial(signingInput);
|
|
997
|
+
return `${signingInput}.${signature}`;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
export async function verifyProductNotificationDeliveryToken(
|
|
1001
|
+
token: string,
|
|
1002
|
+
nowMs = Date.now(),
|
|
1003
|
+
): Promise<ProductNotificationDeliveryTokenClaims> {
|
|
1004
|
+
const segments = token.split('.');
|
|
1005
|
+
if (segments.length !== 3) {
|
|
1006
|
+
throw new Error('Invalid product notification delivery token.');
|
|
1007
|
+
}
|
|
1008
|
+
const [encodedHeader, encodedClaims, signature] = segments;
|
|
1009
|
+
const header = decodeJson<Record<string, unknown>>(encodedHeader);
|
|
1010
|
+
if (header.alg !== 'HS256' || header.typ !== 'JWT') {
|
|
1011
|
+
throw new Error('Invalid product notification delivery token.');
|
|
1012
|
+
}
|
|
1013
|
+
const expectedSignature = await signTokenMaterial(
|
|
1014
|
+
`${encodedHeader}.${encodedClaims}`,
|
|
1015
|
+
);
|
|
1016
|
+
if (
|
|
1017
|
+
!timingSafeEqual(fromBase64Url(signature), fromBase64Url(expectedSignature))
|
|
1018
|
+
) {
|
|
1019
|
+
throw new Error('Invalid product notification delivery token.');
|
|
1020
|
+
}
|
|
1021
|
+
const claims = decodeJson<Record<string, unknown>>(encodedClaims);
|
|
1022
|
+
if (
|
|
1023
|
+
claims.v !== INTERNAL_BOOTSTRAP_TOKEN_VERSION ||
|
|
1024
|
+
claims.iss !== INTERNAL_BOOTSTRAP_TOKEN_ISSUER ||
|
|
1025
|
+
claims.aud !== PRODUCT_NOTIFICATION_DELIVERY_AUDIENCE ||
|
|
1026
|
+
claims.purpose !== PRODUCT_NOTIFICATION_DELIVERY_PURPOSE ||
|
|
1027
|
+
claims.sub !== 'product_notification_dispatcher' ||
|
|
1028
|
+
typeof claims.org_id !== 'string' ||
|
|
1029
|
+
claims.org_id.length === 0 ||
|
|
1030
|
+
typeof claims.delivery_id !== 'string' ||
|
|
1031
|
+
claims.delivery_id.length === 0 ||
|
|
1032
|
+
typeof claims.iat !== 'number' ||
|
|
1033
|
+
!Number.isFinite(claims.iat) ||
|
|
1034
|
+
typeof claims.exp !== 'number' ||
|
|
1035
|
+
!Number.isFinite(claims.exp)
|
|
1036
|
+
) {
|
|
1037
|
+
throw new Error('Invalid product notification delivery token.');
|
|
1038
|
+
}
|
|
1039
|
+
const nowSeconds = Math.floor(nowMs / 1000);
|
|
1040
|
+
if (claims.exp <= nowSeconds || claims.iat > nowSeconds + 30) {
|
|
1041
|
+
throw new Error('Product notification delivery token expired.');
|
|
1042
|
+
}
|
|
1043
|
+
return claims as ProductNotificationDeliveryTokenClaims;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
/**
|
|
1047
|
+
* Mints a durable, narrowly scoped opt-out command. It never expires because
|
|
1048
|
+
* an old notification must remain usable to stop future email; rotating the
|
|
1049
|
+
* application secret revokes every outstanding link.
|
|
1050
|
+
*/
|
|
1051
|
+
export async function mintProductNotificationUnsubscribeToken(input: {
|
|
1052
|
+
orgId: string;
|
|
1053
|
+
groupKey: string;
|
|
1054
|
+
memberId: string;
|
|
1055
|
+
nowMs?: number;
|
|
1056
|
+
}): Promise<string> {
|
|
1057
|
+
const claims: ProductNotificationUnsubscribeTokenClaims = {
|
|
1058
|
+
v: INTERNAL_BOOTSTRAP_TOKEN_VERSION,
|
|
1059
|
+
iss: INTERNAL_BOOTSTRAP_TOKEN_ISSUER,
|
|
1060
|
+
aud: PRODUCT_NOTIFICATION_UNSUBSCRIBE_AUDIENCE,
|
|
1061
|
+
purpose: PRODUCT_NOTIFICATION_UNSUBSCRIBE_PURPOSE,
|
|
1062
|
+
org_id: input.orgId,
|
|
1063
|
+
group_key: input.groupKey,
|
|
1064
|
+
member_id: input.memberId,
|
|
1065
|
+
sub: 'product_notification_recipient',
|
|
1066
|
+
iat: Math.floor((input.nowMs ?? Date.now()) / 1000),
|
|
1067
|
+
};
|
|
1068
|
+
const encodedHeader = encodeJson({ alg: 'HS256', typ: 'JWT' });
|
|
1069
|
+
const encodedClaims = encodeJson(claims);
|
|
1070
|
+
const signingInput = `${encodedHeader}.${encodedClaims}`;
|
|
1071
|
+
return `${signingInput}.${await signTokenMaterial(signingInput)}`;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
export async function verifyProductNotificationUnsubscribeToken(
|
|
1075
|
+
token: string,
|
|
1076
|
+
nowMs = Date.now(),
|
|
1077
|
+
): Promise<ProductNotificationUnsubscribeTokenClaims> {
|
|
1078
|
+
const segments = token.split('.');
|
|
1079
|
+
if (segments.length !== 3) {
|
|
1080
|
+
throw new Error('Invalid product notification unsubscribe token.');
|
|
1081
|
+
}
|
|
1082
|
+
const [encodedHeader, encodedClaims, signature] = segments;
|
|
1083
|
+
const header = decodeJson<Record<string, unknown>>(encodedHeader);
|
|
1084
|
+
if (header.alg !== 'HS256' || header.typ !== 'JWT') {
|
|
1085
|
+
throw new Error('Invalid product notification unsubscribe token.');
|
|
1086
|
+
}
|
|
1087
|
+
const expectedSignature = await signTokenMaterial(
|
|
1088
|
+
`${encodedHeader}.${encodedClaims}`,
|
|
1089
|
+
);
|
|
1090
|
+
if (
|
|
1091
|
+
!timingSafeEqual(fromBase64Url(signature), fromBase64Url(expectedSignature))
|
|
1092
|
+
) {
|
|
1093
|
+
throw new Error('Invalid product notification unsubscribe token.');
|
|
1094
|
+
}
|
|
1095
|
+
const claims = decodeJson<Record<string, unknown>>(encodedClaims);
|
|
1096
|
+
if (
|
|
1097
|
+
claims.v !== INTERNAL_BOOTSTRAP_TOKEN_VERSION ||
|
|
1098
|
+
claims.iss !== INTERNAL_BOOTSTRAP_TOKEN_ISSUER ||
|
|
1099
|
+
claims.aud !== PRODUCT_NOTIFICATION_UNSUBSCRIBE_AUDIENCE ||
|
|
1100
|
+
claims.purpose !== PRODUCT_NOTIFICATION_UNSUBSCRIBE_PURPOSE ||
|
|
1101
|
+
claims.sub !== 'product_notification_recipient' ||
|
|
1102
|
+
typeof claims.org_id !== 'string' ||
|
|
1103
|
+
!claims.org_id ||
|
|
1104
|
+
typeof claims.group_key !== 'string' ||
|
|
1105
|
+
!claims.group_key ||
|
|
1106
|
+
typeof claims.member_id !== 'string' ||
|
|
1107
|
+
!claims.member_id ||
|
|
1108
|
+
typeof claims.iat !== 'number' ||
|
|
1109
|
+
!Number.isFinite(claims.iat) ||
|
|
1110
|
+
claims.iat > Math.floor(nowMs / 1000) + 30
|
|
1111
|
+
) {
|
|
1112
|
+
throw new Error('Invalid product notification unsubscribe token.');
|
|
1113
|
+
}
|
|
1114
|
+
return claims as ProductNotificationUnsubscribeTokenClaims;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
export async function mintMonitorFleetDispatchToken(input: {
|
|
1118
|
+
orgId: string;
|
|
1119
|
+
operation: 'dispatch' | 'daily_sync';
|
|
1120
|
+
fleetId?: string;
|
|
1121
|
+
nowMs?: number;
|
|
1122
|
+
}): Promise<string> {
|
|
1123
|
+
const issuedAt = Math.floor((input.nowMs ?? Date.now()) / 1000);
|
|
1124
|
+
const claims: MonitorFleetDispatchTokenClaims = {
|
|
1125
|
+
v: INTERNAL_BOOTSTRAP_TOKEN_VERSION,
|
|
1126
|
+
iss: INTERNAL_BOOTSTRAP_TOKEN_ISSUER,
|
|
1127
|
+
aud: MONITOR_FLEET_DISPATCH_AUDIENCE,
|
|
1128
|
+
purpose: MONITOR_FLEET_DISPATCH_PURPOSE,
|
|
1129
|
+
org_id: input.orgId,
|
|
1130
|
+
operation: input.operation,
|
|
1131
|
+
...(input.fleetId ? { fleet_id: input.fleetId } : {}),
|
|
1132
|
+
sub: 'monitor_fleet_dispatcher',
|
|
1133
|
+
iat: issuedAt,
|
|
1134
|
+
exp: issuedAt + INTERNAL_BOOTSTRAP_TOKEN_TTL_SECONDS,
|
|
1135
|
+
};
|
|
1136
|
+
const encodedHeader = encodeJson({ alg: 'HS256', typ: 'JWT' });
|
|
1137
|
+
const encodedClaims = encodeJson(claims);
|
|
1138
|
+
const signingInput = `${encodedHeader}.${encodedClaims}`;
|
|
1139
|
+
return `${signingInput}.${await signTokenMaterial(signingInput)}`;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
export async function verifyMonitorFleetDispatchToken(
|
|
1143
|
+
token: string,
|
|
1144
|
+
nowMs = Date.now(),
|
|
1145
|
+
): Promise<MonitorFleetDispatchTokenClaims> {
|
|
1146
|
+
const segments = token.split('.');
|
|
1147
|
+
if (segments.length !== 3)
|
|
1148
|
+
throw new Error('Invalid monitor fleet dispatch token.');
|
|
1149
|
+
const [encodedHeader, encodedClaims, signature] = segments;
|
|
1150
|
+
const header = decodeJson<Record<string, unknown>>(encodedHeader);
|
|
1151
|
+
if (header.alg !== 'HS256' || header.typ !== 'JWT') {
|
|
1152
|
+
throw new Error('Invalid monitor fleet dispatch token.');
|
|
1153
|
+
}
|
|
1154
|
+
const expectedSignature = await signTokenMaterial(
|
|
1155
|
+
`${encodedHeader}.${encodedClaims}`,
|
|
1156
|
+
);
|
|
1157
|
+
if (
|
|
1158
|
+
!timingSafeEqual(fromBase64Url(signature), fromBase64Url(expectedSignature))
|
|
1159
|
+
) {
|
|
1160
|
+
throw new Error('Invalid monitor fleet dispatch token.');
|
|
1161
|
+
}
|
|
1162
|
+
const claims = decodeJson<Record<string, unknown>>(encodedClaims);
|
|
1163
|
+
if (
|
|
1164
|
+
claims.v !== INTERNAL_BOOTSTRAP_TOKEN_VERSION ||
|
|
1165
|
+
claims.iss !== INTERNAL_BOOTSTRAP_TOKEN_ISSUER ||
|
|
1166
|
+
claims.aud !== MONITOR_FLEET_DISPATCH_AUDIENCE ||
|
|
1167
|
+
claims.purpose !== MONITOR_FLEET_DISPATCH_PURPOSE ||
|
|
1168
|
+
claims.sub !== 'monitor_fleet_dispatcher' ||
|
|
1169
|
+
typeof claims.org_id !== 'string' ||
|
|
1170
|
+
!claims.org_id ||
|
|
1171
|
+
!['dispatch', 'daily_sync'].includes(String(claims.operation)) ||
|
|
1172
|
+
(claims.fleet_id !== undefined && typeof claims.fleet_id !== 'string') ||
|
|
1173
|
+
typeof claims.iat !== 'number' ||
|
|
1174
|
+
!Number.isFinite(claims.iat) ||
|
|
1175
|
+
typeof claims.exp !== 'number' ||
|
|
1176
|
+
!Number.isFinite(claims.exp)
|
|
1177
|
+
) {
|
|
1178
|
+
throw new Error('Invalid monitor fleet dispatch token.');
|
|
1179
|
+
}
|
|
1180
|
+
const nowSeconds = Math.floor(nowMs / 1000);
|
|
1181
|
+
if (claims.exp <= nowSeconds || claims.iat > nowSeconds + 30) {
|
|
1182
|
+
throw new Error('Monitor fleet dispatch token expired.');
|
|
1183
|
+
}
|
|
1184
|
+
return claims as MonitorFleetDispatchTokenClaims;
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
export async function verifyIngestionInternalToken(
|
|
1188
|
+
token: string,
|
|
1189
|
+
nowMs = Date.now(),
|
|
1190
|
+
): Promise<IngestionInternalTokenClaims> {
|
|
1191
|
+
const segments = token.split('.');
|
|
1192
|
+
if (segments.length !== 3) {
|
|
1193
|
+
throw new Error('Invalid ingestion token.');
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
const [encodedHeader, encodedClaims, signature] = segments;
|
|
1197
|
+
const header = decodeJson<Record<string, unknown>>(encodedHeader);
|
|
1198
|
+
if (header.alg !== 'HS256' || header.typ !== 'JWT') {
|
|
1199
|
+
throw new Error('Invalid ingestion token.');
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
const expectedSignature = await signTokenMaterial(
|
|
1203
|
+
`${encodedHeader}.${encodedClaims}`,
|
|
1204
|
+
);
|
|
1205
|
+
if (
|
|
1206
|
+
!timingSafeEqual(fromBase64Url(signature), fromBase64Url(expectedSignature))
|
|
1207
|
+
) {
|
|
1208
|
+
throw new Error('Invalid ingestion token.');
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
const claims = decodeJson<Record<string, unknown>>(encodedClaims);
|
|
1212
|
+
if (!isValidClaimsShape(claims)) {
|
|
1213
|
+
throw new Error('Invalid ingestion token.');
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
if (claims.exp <= Math.floor(nowMs / 1000)) {
|
|
1217
|
+
throw new Error('Ingestion token expired.');
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
return claims;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
export async function verifyWorkflowInternalToken(
|
|
1224
|
+
token: string,
|
|
1225
|
+
nowMs = Date.now(),
|
|
1226
|
+
): Promise<WorkflowRunExecutionTokenClaims> {
|
|
1227
|
+
const { publicJwk } = await workflowExecutorKeyPair();
|
|
1228
|
+
return await verifyWorkflowInternalTokenWithJwks(
|
|
1229
|
+
token,
|
|
1230
|
+
{ keys: [publicJwk] },
|
|
1231
|
+
nowMs,
|
|
1232
|
+
);
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
export async function verifyWorkflowInternalTokenWithJwks(
|
|
1236
|
+
token: string,
|
|
1237
|
+
jwks: { keys: JWK[] },
|
|
1238
|
+
nowMs = Date.now(),
|
|
1239
|
+
): Promise<WorkflowRunExecutionTokenClaims> {
|
|
1240
|
+
const headerKid = decodeJson<Record<string, unknown>>(
|
|
1241
|
+
token.split('.')[0] ?? '',
|
|
1242
|
+
).kid;
|
|
1243
|
+
const jwk = jwks.keys.find((candidate) => candidate.kid === headerKid);
|
|
1244
|
+
if (!jwk) throw new Error('Invalid workflow token key.');
|
|
1245
|
+
const key = await importJWK(jwk, 'EdDSA');
|
|
1246
|
+
let claims: Record<string, unknown>;
|
|
1247
|
+
try {
|
|
1248
|
+
const verified = await jwtVerify(token, key, {
|
|
1249
|
+
algorithms: ['EdDSA'],
|
|
1250
|
+
issuer: INTERNAL_BOOTSTRAP_TOKEN_ISSUER,
|
|
1251
|
+
audience: INTERNAL_WORKFLOW_EXECUTION_AUDIENCE,
|
|
1252
|
+
currentDate: new Date(nowMs),
|
|
1253
|
+
typ: 'JWT',
|
|
1254
|
+
});
|
|
1255
|
+
claims = verified.payload as Record<string, unknown>;
|
|
1256
|
+
} catch (error) {
|
|
1257
|
+
if (
|
|
1258
|
+
error &&
|
|
1259
|
+
typeof error === 'object' &&
|
|
1260
|
+
'code' in error &&
|
|
1261
|
+
(error as { code?: unknown }).code === 'ERR_JWT_EXPIRED'
|
|
1262
|
+
) {
|
|
1263
|
+
throw new Error('Workflow token expired.');
|
|
1264
|
+
}
|
|
1265
|
+
throw error;
|
|
1266
|
+
}
|
|
1267
|
+
if (!isValidWorkflowClaimsShape(claims)) {
|
|
1268
|
+
throw new Error('Invalid workflow token.');
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
const nowSeconds = Math.floor(nowMs / 1000);
|
|
1272
|
+
if (claims.exp <= nowSeconds || claims.iat > nowSeconds + 30) {
|
|
1273
|
+
throw new Error('Workflow token expired.');
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
return claims;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
export async function verifyIngestionProviderSyncBootstrapToken(
|
|
1280
|
+
token: string,
|
|
1281
|
+
nowMs = Date.now(),
|
|
1282
|
+
): Promise<IngestionProviderSyncBootstrapTokenClaims> {
|
|
1283
|
+
const claims = await verifyIngestionInternalToken(token, nowMs);
|
|
1284
|
+
if (claims.purpose !== INTERNAL_BOOTSTRAP_BOOTSTRAP_PURPOSE) {
|
|
1285
|
+
throw new Error('Invalid bootstrap token.');
|
|
1286
|
+
}
|
|
1287
|
+
return claims;
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
export async function verifyIngestionSubscriptionExecutionToken(
|
|
1291
|
+
token: string,
|
|
1292
|
+
nowMs = Date.now(),
|
|
1293
|
+
): Promise<IngestionSubscriptionExecutionTokenClaims> {
|
|
1294
|
+
const claims = await verifyIngestionInternalToken(token, nowMs);
|
|
1295
|
+
if (claims.purpose !== INTERNAL_BOOTSTRAP_EXECUTION_PURPOSE) {
|
|
1296
|
+
throw new Error('Invalid execution token.');
|
|
1297
|
+
}
|
|
1298
|
+
return claims;
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
export async function verifyWorkflowRunExecutionToken(
|
|
1302
|
+
token: string,
|
|
1303
|
+
nowMs = Date.now(),
|
|
1304
|
+
): Promise<WorkflowRunExecutionTokenClaims> {
|
|
1305
|
+
const claims = await verifyWorkflowInternalToken(token, nowMs);
|
|
1306
|
+
if (claims.purpose !== INTERNAL_WORKFLOW_EXECUTION_PURPOSE) {
|
|
1307
|
+
throw new Error('Invalid workflow execution token.');
|
|
1308
|
+
}
|
|
1309
|
+
return claims;
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
export async function verifyWorkflowRunExecutionTokenWithJwks(
|
|
1313
|
+
token: string,
|
|
1314
|
+
jwks: { keys: JWK[] },
|
|
1315
|
+
nowMs = Date.now(),
|
|
1316
|
+
): Promise<WorkflowRunExecutionTokenClaims> {
|
|
1317
|
+
const claims = await verifyWorkflowInternalTokenWithJwks(token, jwks, nowMs);
|
|
1318
|
+
if (claims.purpose !== INTERNAL_WORKFLOW_EXECUTION_PURPOSE) {
|
|
1319
|
+
throw new Error('Invalid workflow execution token.');
|
|
1320
|
+
}
|
|
1321
|
+
return claims;
|
|
1322
|
+
}
|