@wardby/cli 0.5.2 → 0.6.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/.env.example +33 -0
- package/README.md +25 -14
- package/dist/claude-coding-worker/context.d.ts +22 -0
- package/dist/claude-coding-worker/context.js +92 -0
- package/dist/claude-coding-worker/driver.d.ts +7 -1
- package/dist/claude-coding-worker/driver.js +38 -5
- package/dist/claude-coding-worker/sdk.js +13 -4
- package/dist/cli-help.d.ts +1 -1
- package/dist/cli-help.js +5 -2
- package/dist/cli-native-mode.d.ts +9 -0
- package/dist/cli-native-mode.js +20 -0
- package/dist/cli.d.ts +3 -1
- package/dist/cli.js +141 -5
- package/dist/coding/claude-context.d.ts +45 -0
- package/dist/coding/claude-context.js +434 -0
- package/dist/coding/local-git.js +9 -1
- package/dist/coding/profile.d.ts +12 -0
- package/dist/coding/profile.js +6 -0
- package/dist/coding/protocol.d.ts +104 -0
- package/dist/coding/protocol.js +87 -3
- package/dist/coding/registry/pypi.d.ts +25 -1
- package/dist/coding/registry/pypi.js +140 -16
- package/dist/coding/registry/types.d.ts +17 -3
- package/dist/coding-worker/artifact.d.ts +8 -0
- package/dist/coding-worker/artifact.js +2 -2
- package/dist/coding-worker/driver.js +2 -0
- package/dist/coding-worker/sdk.d.ts +6 -0
- package/dist/coding-worker/sdk.js +1 -0
- package/dist/coding-worker/skills.d.ts +23 -0
- package/dist/coding-worker/skills.js +117 -0
- package/dist/coding-worker/types.d.ts +2 -0
- package/dist/config/github-allowed-accounts.d.ts +15 -0
- package/dist/config/github-allowed-accounts.js +36 -0
- package/dist/config/low-power.d.ts +12 -0
- package/dist/config/low-power.js +18 -0
- package/dist/config/mobile.d.ts +12 -0
- package/dist/config/mobile.js +18 -0
- package/dist/config/providers.d.ts +56 -2
- package/dist/config/providers.js +92 -6
- package/dist/core/db.d.ts +16 -6
- package/dist/core/db.js +12 -10
- package/dist/core/dispatch.d.ts +11 -0
- package/dist/core/dispatch.js +36 -1
- package/dist/core/engine-native.js +9 -5
- package/dist/core/host-events.d.ts +49 -3
- package/dist/core/host-events.js +289 -15
- package/dist/core/http-runtime.js +13 -6
- package/dist/core/issue-bridge.js +42 -2
- package/dist/core/issue-events.js +12 -1
- package/dist/core/issue-status.js +14 -3
- package/dist/core/merge-order-check.d.ts +129 -0
- package/dist/core/merge-order-check.js +336 -0
- package/dist/core/notification-dispatcher.d.ts +70 -0
- package/dist/core/notification-dispatcher.js +368 -0
- package/dist/core/notification-templates.d.ts +25 -0
- package/dist/core/notification-templates.js +105 -0
- package/dist/core/notifications.d.ts +10 -0
- package/dist/core/notifications.js +38 -0
- package/dist/core/pg-auth.d.ts +22 -0
- package/dist/core/pg-auth.js +152 -0
- package/dist/core/power-composition.d.ts +16 -0
- package/dist/core/power-composition.js +37 -0
- package/dist/core/power-idle.d.ts +14 -0
- package/dist/core/power-idle.js +30 -0
- package/dist/core/power.d.ts +71 -0
- package/dist/core/power.js +202 -0
- package/dist/core/reconciler.d.ts +4 -2
- package/dist/core/reconciler.js +29 -5
- package/dist/core/related-pull-requests.d.ts +48 -4
- package/dist/core/related-pull-requests.js +113 -11
- package/dist/core/repo-access.d.ts +15 -0
- package/dist/core/repo-access.js +66 -0
- package/dist/core/review-fix.d.ts +37 -0
- package/dist/core/review-fix.js +207 -6
- package/dist/core/review-host-tools.d.ts +20 -0
- package/dist/core/review-host-tools.js +58 -2
- package/dist/core/runner.d.ts +336 -3
- package/dist/core/runner.js +894 -317
- package/dist/core/secret-broker-config.d.ts +103 -0
- package/dist/core/secret-broker-config.js +112 -0
- package/dist/core/secrets.d.ts +39 -4
- package/dist/core/secrets.js +148 -40
- package/dist/core/workflow-event-recorder.d.ts +14 -0
- package/dist/core/workflow-event-recorder.js +120 -0
- package/dist/core/workflow-events.d.ts +82 -0
- package/dist/core/workflow-events.js +44 -0
- package/dist/core/workflow-run-events.d.ts +16 -0
- package/dist/core/workflow-run-events.js +40 -0
- package/dist/generated/prisma/browser.d.ts +58 -0
- package/dist/generated/prisma/client.d.ts +58 -0
- package/dist/generated/prisma/commonInputTypes.d.ts +150 -0
- package/dist/generated/prisma/enums.d.ts +27 -0
- package/dist/generated/prisma/enums.js +23 -0
- package/dist/generated/prisma/internal/class.d.ts +88 -0
- package/dist/generated/prisma/internal/class.js +4 -4
- package/dist/generated/prisma/internal/prismaNamespace.d.ts +772 -1
- package/dist/generated/prisma/internal/prismaNamespace.js +135 -4
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +139 -0
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +135 -4
- package/dist/generated/prisma/models/Agent.d.ts +360 -1
- package/dist/generated/prisma/models/CodingAgentProfile.d.ts +73 -1
- package/dist/generated/prisma/models/CodingRun.d.ts +205 -1
- package/dist/generated/prisma/models/DeferredReview.d.ts +78 -1
- package/dist/generated/prisma/models/NativeGatewayCall.d.ts +1675 -0
- package/dist/generated/prisma/models/NativeGatewayCall.js +1 -0
- package/dist/generated/prisma/models/NativeGatewaySession.d.ts +1517 -0
- package/dist/generated/prisma/models/NativeGatewaySession.js +1 -0
- package/dist/generated/prisma/models/NativeWarmWorker.d.ts +1296 -0
- package/dist/generated/prisma/models/NativeWarmWorker.js +1 -0
- package/dist/generated/prisma/models/NotificationChannel.d.ts +1543 -0
- package/dist/generated/prisma/models/NotificationChannel.js +1 -0
- package/dist/generated/prisma/models/NotificationDelivery.d.ts +1502 -0
- package/dist/generated/prisma/models/NotificationDelivery.js +1 -0
- package/dist/generated/prisma/models/NotificationThread.d.ts +1090 -0
- package/dist/generated/prisma/models/NotificationThread.js +1 -0
- package/dist/generated/prisma/models/Run.d.ts +826 -1
- package/dist/generated/prisma/models/RunHostCheck.d.ts +36 -1
- package/dist/generated/prisma/models/Secret.d.ts +37 -1
- package/dist/generated/prisma/models/SecretBrokerChange.d.ts +1101 -0
- package/dist/generated/prisma/models/SecretBrokerChange.js +1 -0
- package/dist/generated/prisma/models/WorkflowEvent.d.ts +1422 -0
- package/dist/generated/prisma/models/WorkflowEvent.js +1 -0
- package/dist/generated/prisma/models.d.ts +8 -0
- package/dist/help/build.js +4 -2
- package/dist/help/catalog.d.ts +1 -1
- package/dist/help/catalog.js +31 -6
- package/dist/help-index.json +1492 -113
- package/dist/import/create.js +17 -0
- package/dist/import/neutral-schema.d.ts +6 -2
- package/dist/import/neutral-schema.js +2 -0
- package/dist/mcp/auth/access.d.ts +1 -0
- package/dist/mcp/auth/ownership.d.ts +1 -1
- package/dist/mcp/auth/repo-authorization.js +14 -0
- package/dist/mcp/auth/resource-server.d.ts +2 -1
- package/dist/mcp/auth/resource-server.js +7 -2
- package/dist/mcp/auth/self-hosted/browser.d.ts +1 -1
- package/dist/mcp/auth/self-hosted/browser.js +19 -6
- package/dist/mcp/context.d.ts +6 -0
- package/dist/mcp/host-events/github-ingress.d.ts +23 -1
- package/dist/mcp/host-events/github-ingress.js +68 -0
- package/dist/mcp/index.d.ts +13 -0
- package/dist/mcp/index.js +59 -10
- package/dist/mcp/tools/agents.js +83 -12
- package/dist/mcp/tools/notification-channels.d.ts +2 -0
- package/dist/mcp/tools/notification-channels.js +293 -0
- package/dist/mcp/tools/power.d.ts +2 -0
- package/dist/mcp/tools/power.js +77 -0
- package/dist/mcp/tools/runs.d.ts +0 -6
- package/dist/mcp/tools/runs.js +7 -2
- package/dist/mcp/tools/secret-broker-compat.d.ts +12 -0
- package/dist/mcp/tools/secret-broker-compat.js +23 -0
- package/dist/mcp/tools/secret-elicitation-form.d.ts +22 -2
- package/dist/mcp/tools/secret-elicitation-form.js +200 -10
- package/dist/mcp/tools/secret-elicitation-server.js +8 -1
- package/dist/mcp/tools/secret-elicitation.d.ts +38 -3
- package/dist/mcp/tools/secret-elicitation.js +110 -12
- package/dist/mcp/tools/secrets.js +184 -7
- package/dist/mcp/tools/tools.d.ts +1 -1
- package/dist/mcp/tools/tools.js +22 -2
- package/dist/native-worker/composition.d.ts +23 -0
- package/dist/native-worker/composition.js +77 -0
- package/dist/native-worker/docker-isolation.d.ts +65 -0
- package/dist/native-worker/docker-isolation.js +183 -0
- package/dist/native-worker/docker-launcher.d.ts +74 -0
- package/dist/native-worker/docker-launcher.js +237 -0
- package/dist/native-worker/gateway-server.d.ts +39 -0
- package/dist/native-worker/gateway-server.js +315 -0
- package/dist/native-worker/gateway.d.ts +44 -0
- package/dist/native-worker/gateway.js +197 -0
- package/dist/native-worker/http-server.d.ts +22 -0
- package/dist/native-worker/http-server.js +109 -0
- package/dist/native-worker/http-transport.d.ts +17 -0
- package/dist/native-worker/http-transport.js +88 -0
- package/dist/native-worker/kubernetes-isolation.d.ts +90 -0
- package/dist/native-worker/kubernetes-isolation.js +218 -0
- package/dist/native-worker/kubernetes-launcher.d.ts +102 -0
- package/dist/native-worker/kubernetes-launcher.js +335 -0
- package/dist/native-worker/launch.d.ts +45 -0
- package/dist/native-worker/launch.js +44 -0
- package/dist/native-worker/ledger.d.ts +109 -0
- package/dist/native-worker/ledger.js +192 -0
- package/dist/native-worker/loopback.d.ts +12 -0
- package/dist/native-worker/loopback.js +49 -0
- package/dist/native-worker/main.d.ts +7 -0
- package/dist/native-worker/main.js +74 -0
- package/dist/native-worker/protocol.d.ts +744 -0
- package/dist/native-worker/protocol.js +200 -0
- package/dist/native-worker/sandbox-executor.d.ts +68 -0
- package/dist/native-worker/sandbox-executor.js +210 -0
- package/dist/native-worker/stdio.d.ts +29 -0
- package/dist/native-worker/stdio.js +188 -0
- package/dist/native-worker/warm-delivery.d.ts +16 -0
- package/dist/native-worker/warm-delivery.js +33 -0
- package/dist/native-worker/warm-pool-ledger.d.ts +39 -0
- package/dist/native-worker/warm-pool-ledger.js +73 -0
- package/dist/native-worker/warm-pool.d.ts +107 -0
- package/dist/native-worker/warm-pool.js +283 -0
- package/dist/native-worker/worker.d.ts +17 -0
- package/dist/native-worker/worker.js +115 -0
- package/dist/providers/auth/authorization-server.d.ts +3 -0
- package/dist/providers/auth/index.js +2 -0
- package/dist/providers/auth/self-hosted.d.ts +13 -1
- package/dist/providers/auth/self-hosted.js +54 -4
- package/dist/providers/chat/fake.d.ts +46 -0
- package/dist/providers/chat/fake.js +39 -0
- package/dist/providers/chat/index.d.ts +4 -0
- package/dist/providers/chat/index.js +8 -0
- package/dist/providers/chat/slack.d.ts +37 -0
- package/dist/providers/chat/slack.js +105 -0
- package/dist/providers/chat/types.d.ts +42 -0
- package/dist/providers/chat/types.js +19 -0
- package/dist/providers/coding-proxy/mock-upstream.d.ts +33 -0
- package/dist/providers/coding-proxy/mock-upstream.js +116 -0
- package/dist/providers/coding-proxy/registry/prisma-store.d.ts +3 -0
- package/dist/providers/coding-proxy/registry/prisma-store.js +17 -0
- package/dist/providers/coding-proxy/registry/service.d.ts +4 -0
- package/dist/providers/coding-proxy/registry/service.js +20 -3
- package/dist/providers/coding-proxy/registry/store.d.ts +18 -1
- package/dist/providers/coding-proxy/registry/store.js +33 -0
- package/dist/providers/coding-proxy/runtime.js +12 -1
- package/dist/providers/coding-proxy/types.d.ts +2 -0
- package/dist/providers/engine/types.d.ts +19 -0
- package/dist/providers/engine/types.js +22 -0
- package/dist/providers/executor/composition.d.ts +24 -1
- package/dist/providers/executor/composition.js +39 -6
- package/dist/providers/executor/container.d.ts +24 -1
- package/dist/providers/executor/container.js +101 -11
- package/dist/providers/executor/dbos.d.ts +42 -0
- package/dist/providers/executor/dbos.js +118 -12
- package/dist/providers/executor/deferred.d.ts +56 -0
- package/dist/providers/executor/deferred.js +119 -0
- package/dist/providers/executor/routing.d.ts +37 -10
- package/dist/providers/executor/routing.js +73 -32
- package/dist/providers/executor/types.d.ts +12 -0
- package/dist/providers/jobs/docker.js +3 -3
- package/dist/providers/jobs/fake-kubernetes-api.d.ts +21 -2
- package/dist/providers/jobs/fake-kubernetes-api.js +42 -1
- package/dist/providers/jobs/kubernetes-api.d.ts +17 -1
- package/dist/providers/jobs/kubernetes-api.js +7 -0
- package/dist/providers/jobs/kubernetes-client.d.ts +10 -1
- package/dist/providers/jobs/kubernetes-client.js +52 -3
- package/dist/providers/jobs/kubernetes-dry-run-fixture.d.ts +6 -1
- package/dist/providers/jobs/kubernetes-dry-run-fixture.js +6 -3
- package/dist/providers/jobs/kubernetes-isolation.d.ts +16 -1
- package/dist/providers/jobs/kubernetes-isolation.js +58 -5
- package/dist/providers/jobs/kubernetes-platform.d.ts +4 -1
- package/dist/providers/jobs/kubernetes-platform.js +38 -1
- package/dist/providers/jobs/kubernetes-preflight.d.ts +2 -1
- package/dist/providers/jobs/kubernetes-preflight.js +4 -2
- package/dist/providers/jobs/kubernetes.d.ts +56 -7
- package/dist/providers/jobs/kubernetes.js +117 -36
- package/dist/providers/jobs/metadata-probes.d.ts +11 -0
- package/dist/providers/jobs/metadata-probes.js +13 -0
- package/dist/providers/jobs/types.d.ts +9 -0
- package/dist/providers/llm/openai.js +0 -1
- package/dist/providers/review-host/github.d.ts +11 -1
- package/dist/providers/review-host/github.js +50 -0
- package/dist/providers/review-host/types.d.ts +23 -0
- package/dist/providers/vcs/github.d.ts +32 -6
- package/dist/providers/vcs/github.js +74 -16
- package/dist/quickstart/coding-db.js +14 -2
- package/dist/quickstart/coding-doctor.d.ts +3 -0
- package/dist/quickstart/coding-doctor.js +3 -0
- package/dist/quickstart/coding-images.d.ts +9 -0
- package/dist/quickstart/coding-images.js +39 -0
- package/dist/quickstart/coding-seed.d.ts +20 -1
- package/dist/quickstart/coding-seed.js +96 -7
- package/dist/quickstart/coding.d.ts +2 -0
- package/dist/quickstart/coding.js +75 -3
- package/dist/quickstart/config.d.ts +2 -0
- package/dist/quickstart/config.js +4 -0
- package/dist/quickstart/images.d.ts +13 -0
- package/dist/quickstart/images.js +80 -12
- package/dist/quickstart/index.d.ts +0 -2
- package/dist/quickstart/index.js +10 -14
- package/dist/quickstart/manifests.d.ts +56 -0
- package/dist/quickstart/manifests.js +523 -0
- package/dist/quickstart/python-detect.d.ts +10 -0
- package/dist/quickstart/python-detect.js +32 -0
- package/dist/quickstart/repo-packages.d.ts +10 -0
- package/dist/quickstart/repo-packages.js +132 -0
- package/dist/quickstart/reviewer-prompt.d.ts +12 -0
- package/dist/quickstart/reviewer-prompt.js +84 -0
- package/dist/quickstart/scenarios.d.ts +19 -0
- package/dist/quickstart/scenarios.js +28 -0
- package/dist/quickstart/sync-reviewer-prompt.d.ts +1 -0
- package/dist/quickstart/sync-reviewer-prompt.js +10 -0
- package/dist/quickstart-images.json +1 -1
- package/dist/sandbox/brokered-fetch.d.ts +23 -0
- package/dist/sandbox/brokered-fetch.js +37 -0
- package/dist/sandbox/host-functions.d.ts +34 -0
- package/dist/sandbox/host-functions.js +144 -80
- package/dist/sandbox/prelude.js +7 -1
- package/dist/sandbox/run-in-sandbox.d.ts +19 -0
- package/dist/sandbox/run-in-sandbox.js +28 -10
- package/dist/sandbox/safe-fetch.d.ts +2 -0
- package/dist/sandbox/safe-fetch.js +3 -1
- package/dist/sandbox/secret-broker-sigv4.d.ts +2 -0
- package/dist/sandbox/secret-broker-sigv4.js +49 -0
- package/dist/sandbox/secret-broker.d.ts +34 -0
- package/dist/sandbox/secret-broker.js +162 -0
- package/dist/sandbox/user-tool.d.ts +14 -0
- package/dist/sandbox/user-tool.js +32 -0
- package/dist/serve.js +18 -0
- package/dist/viewer/api-schema.d.ts +214 -18
- package/dist/viewer/api-schema.js +22 -0
- package/dist/viewer/build-schemas.js +4 -1
- package/dist/viewer/event-bus.d.ts +6 -0
- package/dist/viewer/event-bus.js +16 -7
- package/dist/viewer/graph.d.ts +5 -2
- package/dist/viewer/graph.js +7 -2
- package/dist/viewer/http.d.ts +0 -1
- package/dist/viewer/http.js +2 -2
- package/dist/viewer/infra.js +25 -2
- package/dist/viewer/limits.d.ts +4 -0
- package/dist/viewer/limits.js +4 -0
- package/dist/viewer/openapi.d.ts +15 -0
- package/dist/viewer/openapi.js +172 -0
- package/docs/README.md +7 -0
- package/docs/agent-recipes.md +57 -8
- package/docs/architecture-runtime.md +16 -1
- package/docs/code-review-agents.md +139 -5
- package/docs/coding-agent-setup.md +127 -2
- package/docs/coding-packages.md +105 -5
- package/docs/coding-services.md +3 -1
- package/docs/coding-worker-byo-images.md +239 -2
- package/docs/coding-worker-isolation.md +88 -23
- package/docs/getting-started-gke.md +225 -9
- package/docs/getting-started-identity-provider.md +21 -10
- package/docs/getting-started.md +140 -11
- package/docs/jira-agents.md +5 -1
- package/docs/knowledge.md +11 -0
- package/docs/native-sandbox.md +635 -0
- package/docs/release-verification.md +9 -0
- package/docs/security-deployment.md +113 -12
- package/docs/slack-notifications.md +252 -0
- package/docs/tool-secrets.md +252 -0
- package/docs/viewer-api.md +46 -5
- package/docs/viewer-api.openapi.yml +1404 -0
- package/help/admin-viewer.md +6 -2
- package/help/agent-recipes.md +27 -0
- package/help/architecture-agent.md +136 -0
- package/help/brokered-secrets.md +79 -0
- package/help/build-claude-code-image.md +131 -0
- package/help/build-worker-image.md +195 -0
- package/help/code-review-agents.md +68 -6
- package/help/coding-packages.md +31 -2
- package/help/creating-agents.md +14 -0
- package/help/deploy-gke.md +37 -6
- package/help/deployment-targets.md +24 -13
- package/help/errors/coding-provider-not-configured.md +2 -2
- package/help/errors/database-auth-invalid.md +28 -0
- package/help/errors/github-account-not-allowed.md +25 -0
- package/help/errors/low-power-disabled.md +29 -0
- package/help/errors/low-power-scale-forbidden.md +35 -0
- package/help/errors/low-power-wake-timeout.md +30 -0
- package/help/errors/mobile-disabled.md +31 -0
- package/help/errors/native-sandbox-capacity.md +22 -0
- package/help/errors/native-sandbox-deadline-exceeded.md +19 -0
- package/help/errors/native-sandbox-docker-failed.md +20 -0
- package/help/errors/native-sandbox-gateway-unavailable.md +21 -0
- package/help/errors/native-sandbox-image-not-pinned.md +19 -0
- package/help/errors/native-sandbox-isolation-mismatch.md +20 -0
- package/help/errors/native-sandbox-network-unenforced.md +22 -0
- package/help/errors/native-sandbox-requires-catalog.md +31 -0
- package/help/errors/native-sandbox-unavailable.md +19 -0
- package/help/errors/native-sandbox-warm-delivery-failed.md +21 -0
- package/help/errors/native-sandbox-worker-exited.md +20 -0
- package/help/errors/native-sandbox-worker-lost.md +20 -0
- package/help/errors/native-sandbox-worker-unready.md +22 -0
- package/help/errors/secret-broker-body-invalid.md +26 -0
- package/help/errors/secret-broker-changed.md +20 -0
- package/help/errors/secret-broker-config-invalid.md +30 -0
- package/help/errors/secret-broker-conflict.md +27 -0
- package/help/errors/secret-broker-destination-denied.md +28 -0
- package/help/errors/secret-broker-value-invalid.md +28 -0
- package/help/errors/secret-brokered.md +24 -0
- package/help/errors/secret-not-brokered.md +25 -0
- package/help/errors/slack-auth-failed.md +48 -0
- package/help/errors/slack-channel-unreachable.md +39 -0
- package/help/errors/slack-not-configured.md +33 -0
- package/help/getting-started.md +18 -0
- package/help/github.md +18 -1
- package/help/identity-and-access.md +5 -1
- package/help/local-repositories.md +35 -5
- package/help/low-power-mode.md +125 -0
- package/help/merge-order-check.md +117 -0
- package/help/mobile-app.md +54 -0
- package/help/native-sandbox.md +136 -0
- package/help/observability.md +8 -0
- package/help/related-pull-requests.md +70 -9
- package/help/repo-instructions-and-skills.md +91 -0
- package/help/review-fix-rounds.md +10 -0
- package/help/slack-notifications.md +62 -0
- package/help/troubleshooting/coding-workers.md +21 -0
- package/package.json +13 -5
- package/prisma/migrations/20261009000003_native_execution_mode/migration.sql +12 -0
- package/prisma/migrations/20261009000004_native_gateway_ledger/migration.sql +65 -0
- package/prisma/migrations/20261009000005_native_gateway_call_child_agent/migration.sql +5 -0
- package/prisma/migrations/20261009000006_run_deferred_start_stop/migration.sql +14 -0
- package/prisma/migrations/20261009000007_native_gateway_network_ready/migration.sql +5 -0
- package/prisma/migrations/20261010000001_workflow_notifications/migration.sql +99 -0
- package/prisma/migrations/20261011000001_native_gateway_session_status_index/migration.sql +2 -0
- package/prisma/migrations/20261012000001_native_warm_worker/migration.sql +32 -0
- package/prisma/migrations/20261013000001_coding_run_merge_order/migration.sql +2 -0
- package/prisma/migrations/20261013000001_secret_broker/migration.sql +17 -0
- package/prisma/migrations/20261013000002_deferred_review_request/migration.sql +4 -0
- package/prisma/migrations/20261014000000_repo_skills/migration.sql +15 -0
- package/prisma/migrations/20261015000001_coding_run_merge_order_index/migration.sql +2 -0
- package/prisma/migrations/20261015000002_no_change_rereview/migration.sql +2 -0
- package/prisma/schema.prisma +362 -91
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const MIN_BROKERED_VALUE_LENGTH = 6;
|
|
3
|
+
export declare const SecretBrokerConfigSchema: z.ZodObject<{
|
|
4
|
+
hosts: z.ZodArray<z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>, "many">;
|
|
5
|
+
pathPrefixes: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
6
|
+
placement: z.ZodDiscriminatedUnion<"kind", [z.ZodObject<{
|
|
7
|
+
kind: z.ZodLiteral<"header">;
|
|
8
|
+
name: z.ZodEffects<z.ZodString, string, string>;
|
|
9
|
+
format: z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>;
|
|
10
|
+
}, "strict", z.ZodTypeAny, {
|
|
11
|
+
name: string;
|
|
12
|
+
kind: "header";
|
|
13
|
+
format: string;
|
|
14
|
+
}, {
|
|
15
|
+
name: string;
|
|
16
|
+
kind: "header";
|
|
17
|
+
format: string;
|
|
18
|
+
}>, z.ZodObject<{
|
|
19
|
+
kind: z.ZodLiteral<"query">;
|
|
20
|
+
name: z.ZodString;
|
|
21
|
+
}, "strict", z.ZodTypeAny, {
|
|
22
|
+
name: string;
|
|
23
|
+
kind: "query";
|
|
24
|
+
}, {
|
|
25
|
+
name: string;
|
|
26
|
+
kind: "query";
|
|
27
|
+
}>, z.ZodObject<{
|
|
28
|
+
kind: z.ZodLiteral<"body">;
|
|
29
|
+
field: z.ZodString;
|
|
30
|
+
}, "strict", z.ZodTypeAny, {
|
|
31
|
+
kind: "body";
|
|
32
|
+
field: string;
|
|
33
|
+
}, {
|
|
34
|
+
kind: "body";
|
|
35
|
+
field: string;
|
|
36
|
+
}>, z.ZodObject<{
|
|
37
|
+
kind: z.ZodLiteral<"aws-sigv4">;
|
|
38
|
+
region: z.ZodString;
|
|
39
|
+
service: z.ZodString;
|
|
40
|
+
}, "strict", z.ZodTypeAny, {
|
|
41
|
+
kind: "aws-sigv4";
|
|
42
|
+
service: string;
|
|
43
|
+
region: string;
|
|
44
|
+
}, {
|
|
45
|
+
kind: "aws-sigv4";
|
|
46
|
+
service: string;
|
|
47
|
+
region: string;
|
|
48
|
+
}>]>;
|
|
49
|
+
}, "strict", z.ZodTypeAny, {
|
|
50
|
+
hosts: string[];
|
|
51
|
+
placement: {
|
|
52
|
+
name: string;
|
|
53
|
+
kind: "header";
|
|
54
|
+
format: string;
|
|
55
|
+
} | {
|
|
56
|
+
name: string;
|
|
57
|
+
kind: "query";
|
|
58
|
+
} | {
|
|
59
|
+
kind: "body";
|
|
60
|
+
field: string;
|
|
61
|
+
} | {
|
|
62
|
+
kind: "aws-sigv4";
|
|
63
|
+
service: string;
|
|
64
|
+
region: string;
|
|
65
|
+
};
|
|
66
|
+
pathPrefixes?: string[] | undefined;
|
|
67
|
+
}, {
|
|
68
|
+
hosts: string[];
|
|
69
|
+
placement: {
|
|
70
|
+
name: string;
|
|
71
|
+
kind: "header";
|
|
72
|
+
format: string;
|
|
73
|
+
} | {
|
|
74
|
+
name: string;
|
|
75
|
+
kind: "query";
|
|
76
|
+
} | {
|
|
77
|
+
kind: "body";
|
|
78
|
+
field: string;
|
|
79
|
+
} | {
|
|
80
|
+
kind: "aws-sigv4";
|
|
81
|
+
service: string;
|
|
82
|
+
region: string;
|
|
83
|
+
};
|
|
84
|
+
pathPrefixes?: string[] | undefined;
|
|
85
|
+
}>;
|
|
86
|
+
export type SecretBrokerConfig = z.infer<typeof SecretBrokerConfigSchema>;
|
|
87
|
+
export type SecretBrokerPlacement = SecretBrokerConfig["placement"];
|
|
88
|
+
export declare function parseSecretBrokerConfig(input: unknown): SecretBrokerConfig;
|
|
89
|
+
/**
|
|
90
|
+
* A fingerprint of one broker config (sha256 hex of its parsed JSON): an unbroker
|
|
91
|
+
* link carries the hash of the config it was minted for, so it can't remove a
|
|
92
|
+
* config that has changed since.
|
|
93
|
+
*/
|
|
94
|
+
export declare function brokerConfigHash(config: SecretBrokerConfig): string;
|
|
95
|
+
export declare const SECRET_BROKER_CHANGED = "secret_broker_changed: brokering changed since this link was created; request a new link";
|
|
96
|
+
export type SigV4Credentials = {
|
|
97
|
+
accessKeyId: string;
|
|
98
|
+
secretAccessKey: string;
|
|
99
|
+
sessionToken?: string;
|
|
100
|
+
};
|
|
101
|
+
export declare function parseSigV4Value(value: string): SigV4Credentials;
|
|
102
|
+
/** Throws unless `value` can be brokered under `config` (and later scrubbed from responses). */
|
|
103
|
+
export declare function assertBrokerableValue(config: SecretBrokerConfig, value: string): void;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A brokered secret's owner-set config: where its value may be sent and how it is
|
|
3
|
+
* placed. Validated on every write; the privileged host (sandbox/secret-broker.ts)
|
|
4
|
+
* trusts it at request time.
|
|
5
|
+
*/
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
export const MIN_BROKERED_VALUE_LENGTH = 6;
|
|
9
|
+
const FORBIDDEN_HEADERS = ["host", "connection", "transfer-encoding", "content-length", "upgrade", "proxy-connection"];
|
|
10
|
+
const TOKEN = /^[!#$%&'*+.^_`|~0-9A-Za-z-]{1,128}$/;
|
|
11
|
+
const HOSTNAME = /^(?=.{1,253}$)(?!-)[a-z0-9-]{1,63}(?:\.(?!-)[a-z0-9-]{1,63})+$/;
|
|
12
|
+
const IPV4 = /^\d{1,3}(?:\.\d{1,3}){3}$/;
|
|
13
|
+
/** False for a name a resolver could read as an IPv4 address ("127.1", "0x7f.0.0.1") or with a label ending in "-". */
|
|
14
|
+
function isExactHostname(h) {
|
|
15
|
+
if (!HOSTNAME.test(h) || IPV4.test(h))
|
|
16
|
+
return false;
|
|
17
|
+
const labels = h.split(".");
|
|
18
|
+
const last = labels[labels.length - 1];
|
|
19
|
+
return !/^\d+$/.test(last) && !last.startsWith("0x") && !labels.some((l) => l.endsWith("-"));
|
|
20
|
+
}
|
|
21
|
+
const host = z
|
|
22
|
+
.string()
|
|
23
|
+
.transform((h) => h.toLowerCase())
|
|
24
|
+
.refine(isExactHostname, "hosts must be exact hostnames (no scheme, port, wildcard, or IP)");
|
|
25
|
+
const pathPrefix = z
|
|
26
|
+
.string()
|
|
27
|
+
.max(512)
|
|
28
|
+
.startsWith("/")
|
|
29
|
+
.refine(
|
|
30
|
+
// eslint-disable-next-line no-control-regex
|
|
31
|
+
(p) => !/[?#\\\x00-\x1f\x7f]/.test(p) && !/%2f|%5c/i.test(p), "path prefixes must not contain ?, #, a backslash, control characters, or an encoded / or \\");
|
|
32
|
+
const placement = z.discriminatedUnion("kind", [
|
|
33
|
+
z
|
|
34
|
+
.object({
|
|
35
|
+
kind: z.literal("header"),
|
|
36
|
+
name: z
|
|
37
|
+
.string()
|
|
38
|
+
.regex(TOKEN, "header name must be an HTTP token")
|
|
39
|
+
.refine((n) => !FORBIDDEN_HEADERS.includes(n.toLowerCase()), "that header cannot be set"),
|
|
40
|
+
format: z
|
|
41
|
+
.string()
|
|
42
|
+
.max(256)
|
|
43
|
+
.refine((f) => f.split("{value}").length === 2, 'format must contain "{value}" exactly once')
|
|
44
|
+
.refine((f) => !/[\r\n]/.test(f), "format must not contain line breaks"),
|
|
45
|
+
})
|
|
46
|
+
.strict(),
|
|
47
|
+
z.object({ kind: z.literal("query"), name: z.string().min(1).max(128) }).strict(),
|
|
48
|
+
z.object({ kind: z.literal("body"), field: z.string().min(1).max(128) }).strict(),
|
|
49
|
+
z
|
|
50
|
+
.object({
|
|
51
|
+
kind: z.literal("aws-sigv4"),
|
|
52
|
+
region: z.string().regex(/^[a-z0-9-]{1,32}$/),
|
|
53
|
+
service: z.string().regex(/^[a-z0-9-]{1,64}$/),
|
|
54
|
+
})
|
|
55
|
+
.strict(),
|
|
56
|
+
]);
|
|
57
|
+
export const SecretBrokerConfigSchema = z
|
|
58
|
+
.object({
|
|
59
|
+
hosts: z.array(host).min(1).max(20),
|
|
60
|
+
pathPrefixes: z.array(pathPrefix).max(20).optional(),
|
|
61
|
+
placement,
|
|
62
|
+
})
|
|
63
|
+
.strict();
|
|
64
|
+
export function parseSecretBrokerConfig(input) {
|
|
65
|
+
const parsed = SecretBrokerConfigSchema.safeParse(input);
|
|
66
|
+
if (!parsed.success) {
|
|
67
|
+
throw new Error(`secret_broker_config_invalid: ${parsed.error.issues.map((i) => i.message).join("; ")}`);
|
|
68
|
+
}
|
|
69
|
+
return parsed.data;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* A fingerprint of one broker config (sha256 hex of its parsed JSON): an unbroker
|
|
73
|
+
* link carries the hash of the config it was minted for, so it can't remove a
|
|
74
|
+
* config that has changed since.
|
|
75
|
+
*/
|
|
76
|
+
export function brokerConfigHash(config) {
|
|
77
|
+
return createHash("sha256")
|
|
78
|
+
.update(JSON.stringify(parseSecretBrokerConfig(config)))
|
|
79
|
+
.digest("hex");
|
|
80
|
+
}
|
|
81
|
+
export const SECRET_BROKER_CHANGED = "secret_broker_changed: brokering changed since this link was created; request a new link";
|
|
82
|
+
const SigV4ValueSchema = z
|
|
83
|
+
.object({
|
|
84
|
+
accessKeyId: z.string().min(1).max(256),
|
|
85
|
+
secretAccessKey: z.string().min(MIN_BROKERED_VALUE_LENGTH).max(1024),
|
|
86
|
+
sessionToken: z.string().min(MIN_BROKERED_VALUE_LENGTH).max(8192).optional(),
|
|
87
|
+
})
|
|
88
|
+
.strict();
|
|
89
|
+
export function parseSigV4Value(value) {
|
|
90
|
+
let json;
|
|
91
|
+
try {
|
|
92
|
+
json = JSON.parse(value);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
throw new Error("secret_broker_value_invalid: an AWS SigV4 secret's value must be JSON {accessKeyId, secretAccessKey, sessionToken?}");
|
|
96
|
+
}
|
|
97
|
+
const parsed = SigV4ValueSchema.safeParse(json);
|
|
98
|
+
if (!parsed.success) {
|
|
99
|
+
throw new Error("secret_broker_value_invalid: an AWS SigV4 secret's value must be JSON {accessKeyId, secretAccessKey, sessionToken?}");
|
|
100
|
+
}
|
|
101
|
+
return parsed.data;
|
|
102
|
+
}
|
|
103
|
+
/** Throws unless `value` can be brokered under `config` (and later scrubbed from responses). */
|
|
104
|
+
export function assertBrokerableValue(config, value) {
|
|
105
|
+
if (config.placement.kind === "aws-sigv4") {
|
|
106
|
+
parseSigV4Value(value);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (value.length < MIN_BROKERED_VALUE_LENGTH) {
|
|
110
|
+
throw new Error(`secret_broker_value_invalid: a brokered value must be at least ${MIN_BROKERED_VALUE_LENGTH} characters`);
|
|
111
|
+
}
|
|
112
|
+
}
|
package/dist/core/secrets.d.ts
CHANGED
|
@@ -6,19 +6,52 @@
|
|
|
6
6
|
* always resolve a name within one owner's (or, at get-time, one agent's)
|
|
7
7
|
* scope, never globally.
|
|
8
8
|
*/
|
|
9
|
-
import type
|
|
9
|
+
import { type PrismaClient, type Secret } from "#prisma";
|
|
10
10
|
import type { SecretCipher } from "../providers/secrets/types.js";
|
|
11
|
-
|
|
11
|
+
import { type SecretBrokerConfig } from "./secret-broker-config.js";
|
|
12
|
+
export type BrokerChangeVia = "mcp" | "browser";
|
|
13
|
+
export type SecretMetadata = Pick<Secret, "id" | "name" | "keyId" | "ownerId" | "createdAt" | "updatedAt"> & {
|
|
14
|
+
broker: SecretBrokerConfig | null;
|
|
15
|
+
};
|
|
16
|
+
export interface SecretEntry {
|
|
17
|
+
value: string;
|
|
18
|
+
broker: SecretBrokerConfig | null;
|
|
19
|
+
}
|
|
12
20
|
export interface SecretsAccessor {
|
|
21
|
+
/** Readable secrets only: a brokered secret throws `secret_brokered`. */
|
|
13
22
|
get(name: string): Promise<string | undefined>;
|
|
23
|
+
/** Host-internal: value and broker config, brokered or not. Never exposed to tool code. */
|
|
24
|
+
resolve?(name: string): Promise<SecretEntry | undefined>;
|
|
14
25
|
}
|
|
15
26
|
/**
|
|
16
27
|
* Encrypts and stores a secret value. Upserts on (ownerId, name) — calling
|
|
17
28
|
* this again for a name the owner already has rotates its value in place
|
|
18
29
|
* (same id) rather than failing on the unique constraint. The plaintext is
|
|
19
|
-
* never logged or returned.
|
|
30
|
+
* never logged or returned. With `options.broker`, the config is validated
|
|
31
|
+
* against the value, saved, and audited in the same transaction.
|
|
32
|
+
*/
|
|
33
|
+
export declare function createSecret(name: string, value: string, ownerId: string, cipher: SecretCipher, db: PrismaClient, options?: {
|
|
34
|
+
broker?: SecretBrokerConfig;
|
|
35
|
+
via?: BrokerChangeVia;
|
|
36
|
+
}): Promise<Secret>;
|
|
37
|
+
/**
|
|
38
|
+
* Sets, changes, or (broker: null) removes a secret's broker config, with one audit
|
|
39
|
+
* row per actual change. `expectedBrokerHash` (brokerConfigHash of the config the
|
|
40
|
+
* caller confirmed) makes the change conditional, checked inside the transaction:
|
|
41
|
+
* if the secret is no longer brokered with exactly that config, it throws
|
|
42
|
+
* secret_broker_changed and changes nothing.
|
|
20
43
|
*/
|
|
21
|
-
export declare function
|
|
44
|
+
export declare function setSecretBroker(db: PrismaClient, cipher: SecretCipher, input: {
|
|
45
|
+
ownerId: string;
|
|
46
|
+
name: string;
|
|
47
|
+
broker: SecretBrokerConfig | null;
|
|
48
|
+
actorId: string;
|
|
49
|
+
via: BrokerChangeVia;
|
|
50
|
+
expectedBrokerHash?: string;
|
|
51
|
+
}): Promise<{
|
|
52
|
+
before: SecretBrokerConfig | null;
|
|
53
|
+
after: SecretBrokerConfig | null;
|
|
54
|
+
}>;
|
|
22
55
|
/** Names/metadata only — never a value or ciphertext. */
|
|
23
56
|
export declare function listSecrets(ownerId: string, db: PrismaClient): Promise<SecretMetadata[]>;
|
|
24
57
|
/** Attaches one of the owner's own secrets (by canonical name) to an agent,
|
|
@@ -43,6 +76,8 @@ export declare function deleteSecret(secretId: string, db: PrismaClient): Promis
|
|
|
43
76
|
* binding written before that rule, left behind by make_owner, or on an
|
|
44
77
|
* owner-less agent behaves exactly like an unattached name, so one owner's
|
|
45
78
|
* secret never reaches another owner's agent (A2/S2-2, R2-1).
|
|
79
|
+
*
|
|
80
|
+
* A brokered secret's `get` throws `secret_brokered`; only the privileged host's `resolve` sees its value.
|
|
46
81
|
*/
|
|
47
82
|
export declare function buildSecretsAccessor(agentId: string, cipher: SecretCipher, db: Pick<PrismaClient, "agentSecret"> & Partial<Pick<PrismaClient, "$queryRaw">>): SecretsAccessor;
|
|
48
83
|
/**
|
package/dist/core/secrets.js
CHANGED
|
@@ -1,31 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secrets: encrypted-at-rest values, attached to an agent by name, readable
|
|
3
|
+
* only inside the sandbox at point-of-use (never by any read/list tool).
|
|
4
|
+
* `Secret.name` is unique per owner (`@@unique([ownerId, name])`), so two
|
|
5
|
+
* owners can each have their own "API_KEY" without collision — attach/get
|
|
6
|
+
* always resolve a name within one owner's (or, at get-time, one agent's)
|
|
7
|
+
* scope, never globally.
|
|
8
|
+
*/
|
|
9
|
+
import { Prisma } from "#prisma";
|
|
1
10
|
import { boundedString } from "../sandbox/bounded-json.js";
|
|
11
|
+
import { assertBrokerableValue, brokerConfigHash, parseSecretBrokerConfig, SECRET_BROKER_CHANGED, } from "./secret-broker-config.js";
|
|
12
|
+
function brokerOf(secret) {
|
|
13
|
+
return secret.broker === null || secret.broker === undefined ? null : parseSecretBrokerConfig(secret.broker);
|
|
14
|
+
}
|
|
15
|
+
const BROKERED_GET_MESSAGE = 'secret_brokered: this secret is brokered; send it with fetch(url, { secrets: ["NAME"] }) instead of reading it';
|
|
2
16
|
/**
|
|
3
17
|
* Encrypts and stores a secret value. Upserts on (ownerId, name) — calling
|
|
4
18
|
* this again for a name the owner already has rotates its value in place
|
|
5
19
|
* (same id) rather than failing on the unique constraint. The plaintext is
|
|
6
|
-
* never logged or returned.
|
|
20
|
+
* never logged or returned. With `options.broker`, the config is validated
|
|
21
|
+
* against the value, saved, and audited in the same transaction.
|
|
7
22
|
*/
|
|
8
|
-
export async function createSecret(name, value, ownerId, cipher, db) {
|
|
23
|
+
export async function createSecret(name, value, ownerId, cipher, db, options = {}) {
|
|
9
24
|
boundedString(name, 1024);
|
|
10
25
|
boundedString(value, 65_536);
|
|
26
|
+
const broker = options.broker ? parseSecretBrokerConfig(options.broker) : undefined;
|
|
27
|
+
if (broker)
|
|
28
|
+
assertBrokerableValue(broker, value);
|
|
11
29
|
const ciphertext = await cipher.encrypt(value);
|
|
12
30
|
const keyId = cipher.keyId();
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
31
|
+
if (!broker) {
|
|
32
|
+
// A rotation that omits `broker` keeps the stored config, so the new value must still be brokerable.
|
|
33
|
+
const existing = await db.secret.findUnique({
|
|
34
|
+
where: { ownerId_name: { ownerId, name } },
|
|
35
|
+
select: { broker: true },
|
|
36
|
+
});
|
|
37
|
+
if (existing?.broker)
|
|
38
|
+
assertBrokerableValue(brokerOf(existing), value);
|
|
39
|
+
return db.secret.upsert({
|
|
40
|
+
where: { ownerId_name: { ownerId, name } },
|
|
41
|
+
create: { name, ciphertext, keyId, ownerId },
|
|
42
|
+
update: { ciphertext, keyId },
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return db.$transaction(async (tx) => {
|
|
46
|
+
const existing = await tx.secret.findUnique({ where: { ownerId_name: { ownerId, name } } });
|
|
47
|
+
const before = existing ? brokerOf(existing) : null;
|
|
48
|
+
const secret = await tx.secret.upsert({
|
|
49
|
+
where: { ownerId_name: { ownerId, name } },
|
|
50
|
+
create: { name, ciphertext, keyId, ownerId, broker },
|
|
51
|
+
update: { ciphertext, keyId, broker },
|
|
52
|
+
});
|
|
53
|
+
if (JSON.stringify(before) !== JSON.stringify(broker)) {
|
|
54
|
+
await tx.secretBrokerChange.create({
|
|
55
|
+
data: {
|
|
56
|
+
secretId: secret.id,
|
|
57
|
+
ownerId,
|
|
58
|
+
secretName: name,
|
|
59
|
+
actorId: ownerId,
|
|
60
|
+
before: before ?? Prisma.JsonNull,
|
|
61
|
+
after: broker,
|
|
62
|
+
via: options.via ?? "mcp",
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return secret;
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Sets, changes, or (broker: null) removes a secret's broker config, with one audit
|
|
71
|
+
* row per actual change. `expectedBrokerHash` (brokerConfigHash of the config the
|
|
72
|
+
* caller confirmed) makes the change conditional, checked inside the transaction:
|
|
73
|
+
* if the secret is no longer brokered with exactly that config, it throws
|
|
74
|
+
* secret_broker_changed and changes nothing.
|
|
75
|
+
*/
|
|
76
|
+
export async function setSecretBroker(db, cipher, input) {
|
|
77
|
+
const after = input.broker === null ? null : parseSecretBrokerConfig(input.broker);
|
|
78
|
+
return db.$transaction(async (tx) => {
|
|
79
|
+
const secret = await tx.secret.findUnique({
|
|
80
|
+
where: { ownerId_name: { ownerId: input.ownerId, name: input.name } },
|
|
81
|
+
});
|
|
82
|
+
if (!secret)
|
|
83
|
+
throw new Error(`secret_not_found: no secret named "${input.name}"`);
|
|
84
|
+
const before = brokerOf(secret);
|
|
85
|
+
if (input.expectedBrokerHash !== undefined && (!before || brokerConfigHash(before) !== input.expectedBrokerHash)) {
|
|
86
|
+
throw new Error(SECRET_BROKER_CHANGED);
|
|
87
|
+
}
|
|
88
|
+
if (JSON.stringify(before) === JSON.stringify(after))
|
|
89
|
+
return { before, after };
|
|
90
|
+
if (after)
|
|
91
|
+
assertBrokerableValue(after, await cipher.decrypt(secret.ciphertext));
|
|
92
|
+
await tx.secret.update({ where: { id: secret.id }, data: { broker: after ?? Prisma.DbNull } });
|
|
93
|
+
await tx.secretBrokerChange.create({
|
|
94
|
+
data: {
|
|
95
|
+
secretId: secret.id,
|
|
96
|
+
ownerId: input.ownerId,
|
|
97
|
+
secretName: secret.name,
|
|
98
|
+
actorId: input.actorId,
|
|
99
|
+
before: before ?? Prisma.JsonNull,
|
|
100
|
+
after: after ?? Prisma.JsonNull,
|
|
101
|
+
via: input.via,
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
return { before, after };
|
|
17
105
|
});
|
|
18
106
|
}
|
|
19
107
|
/** Names/metadata only — never a value or ciphertext. */
|
|
20
108
|
export async function listSecrets(ownerId, db) {
|
|
21
109
|
const secrets = await db.secret.findMany({ where: { ownerId } });
|
|
22
|
-
return secrets.map((
|
|
23
|
-
id,
|
|
24
|
-
name,
|
|
25
|
-
keyId,
|
|
26
|
-
ownerId:
|
|
27
|
-
createdAt,
|
|
28
|
-
updatedAt,
|
|
110
|
+
return secrets.map((s) => ({
|
|
111
|
+
id: s.id,
|
|
112
|
+
name: s.name,
|
|
113
|
+
keyId: s.keyId,
|
|
114
|
+
ownerId: s.ownerId,
|
|
115
|
+
createdAt: s.createdAt,
|
|
116
|
+
updatedAt: s.updatedAt,
|
|
117
|
+
broker: brokerOf(s),
|
|
29
118
|
}));
|
|
30
119
|
}
|
|
31
120
|
async function findOwnedSecretByName(db, ownerId, name) {
|
|
@@ -66,37 +155,53 @@ export async function deleteSecret(secretId, db) {
|
|
|
66
155
|
* binding written before that rule, left behind by make_owner, or on an
|
|
67
156
|
* owner-less agent behaves exactly like an unattached name, so one owner's
|
|
68
157
|
* secret never reaches another owner's agent (A2/S2-2, R2-1).
|
|
158
|
+
*
|
|
159
|
+
* A brokered secret's `get` throws `secret_brokered`; only the privileged host's `resolve` sees its value.
|
|
69
160
|
*/
|
|
70
161
|
export function buildSecretsAccessor(agentId, cipher, db) {
|
|
162
|
+
async function resolve(name) {
|
|
163
|
+
boundedString(name, 1024);
|
|
164
|
+
if (db.$queryRaw) {
|
|
165
|
+
// NULL-safe: "=" is never true when either owner is NULL.
|
|
166
|
+
const rows = await db.$queryRaw `
|
|
167
|
+
SELECT CASE WHEN octet_length(s."ciphertext") <= 262144 THEN s."ciphertext" ELSE NULL END AS "ciphertext",
|
|
168
|
+
s."broker" AS "broker"
|
|
169
|
+
FROM "Secret" s
|
|
170
|
+
JOIN "AgentSecret" a ON a."secretId" = s."id"
|
|
171
|
+
JOIN "Agent" g ON g."id" = a."agentId"
|
|
172
|
+
WHERE a."agentId" = ${agentId} AND a."boundName" = ${name} AND s."ownerId" = g."ownerId" LIMIT 1`;
|
|
173
|
+
if (!rows.length)
|
|
174
|
+
return undefined;
|
|
175
|
+
if (rows[0].ciphertext === null)
|
|
176
|
+
throw new Error("secret_value_limit");
|
|
177
|
+
return {
|
|
178
|
+
value: await cipher.decrypt(rows[0].ciphertext),
|
|
179
|
+
broker: brokerOf({ broker: rows[0].broker }),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
// Lightweight provider doubles use the same post-read bound; production Prisma filters in SQL.
|
|
183
|
+
const attachment = await db.agentSecret.findFirst({
|
|
184
|
+
where: { agentId, boundName: name },
|
|
185
|
+
include: { secret: true, agent: { select: { ownerId: true } } },
|
|
186
|
+
});
|
|
187
|
+
if (!attachment)
|
|
188
|
+
return undefined;
|
|
189
|
+
const owner = attachment.agent.ownerId;
|
|
190
|
+
if (owner === null || attachment.secret.ownerId !== owner)
|
|
191
|
+
return undefined;
|
|
192
|
+
boundedString(attachment.secret.ciphertext, 256 * 1024);
|
|
193
|
+
return {
|
|
194
|
+
value: await cipher.decrypt(attachment.secret.ciphertext),
|
|
195
|
+
broker: brokerOf({ broker: attachment.secret.broker ?? null }),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
71
198
|
return {
|
|
199
|
+
resolve,
|
|
72
200
|
async get(name) {
|
|
73
|
-
|
|
74
|
-
if (
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
SELECT CASE WHEN octet_length(s."ciphertext") <= 262144 THEN s."ciphertext" ELSE NULL END AS "ciphertext"
|
|
78
|
-
FROM "Secret" s
|
|
79
|
-
JOIN "AgentSecret" a ON a."secretId" = s."id"
|
|
80
|
-
JOIN "Agent" g ON g."id" = a."agentId"
|
|
81
|
-
WHERE a."agentId" = ${agentId} AND a."boundName" = ${name} AND s."ownerId" = g."ownerId" LIMIT 1`;
|
|
82
|
-
if (!rows.length)
|
|
83
|
-
return undefined;
|
|
84
|
-
if (rows[0].ciphertext === null)
|
|
85
|
-
throw new Error("secret_value_limit");
|
|
86
|
-
return cipher.decrypt(rows[0].ciphertext);
|
|
87
|
-
}
|
|
88
|
-
// Lightweight provider doubles use the same post-read bound; production Prisma filters in SQL.
|
|
89
|
-
const attachment = await db.agentSecret.findFirst({
|
|
90
|
-
where: { agentId, boundName: name },
|
|
91
|
-
include: { secret: true, agent: { select: { ownerId: true } } },
|
|
92
|
-
});
|
|
93
|
-
if (!attachment)
|
|
94
|
-
return undefined;
|
|
95
|
-
const owner = attachment.agent.ownerId;
|
|
96
|
-
if (owner === null || attachment.secret.ownerId !== owner)
|
|
97
|
-
return undefined;
|
|
98
|
-
boundedString(attachment.secret.ciphertext, 256 * 1024);
|
|
99
|
-
return cipher.decrypt(attachment.secret.ciphertext);
|
|
201
|
+
const entry = await resolve(name);
|
|
202
|
+
if (entry?.broker)
|
|
203
|
+
throw new Error(BROKERED_GET_MESSAGE);
|
|
204
|
+
return entry?.value;
|
|
100
205
|
},
|
|
101
206
|
};
|
|
102
207
|
}
|
|
@@ -115,5 +220,8 @@ export function scopeSecretsAccessor(accessor, allowedNames) {
|
|
|
115
220
|
return undefined;
|
|
116
221
|
return accessor.get(name);
|
|
117
222
|
},
|
|
223
|
+
...(accessor.resolve && {
|
|
224
|
+
resolve: async (name) => (allowed.has(name) ? accessor.resolve(name) : undefined),
|
|
225
|
+
}),
|
|
118
226
|
};
|
|
119
227
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turns an emitted workflow event into outbox rows: resolves the event's work
|
|
3
|
+
* item (given, else the run's attribution, else the PR's issue link), finds
|
|
4
|
+
* the channel links that match (project links by the item's project, agent
|
|
5
|
+
* links by the event's agent), filters each link's event kinds, de-duplicates
|
|
6
|
+
* by channel, and writes one WorkflowEvent plus one NotificationDelivery per
|
|
7
|
+
* channel in a transaction. A dedupeKey that already exists is a no-op, so
|
|
8
|
+
* redeliveries and repeated finalizers record a fact once. Writes nothing
|
|
9
|
+
* when no link matches.
|
|
10
|
+
*/
|
|
11
|
+
import { type PrismaClient } from "#prisma";
|
|
12
|
+
import { type WorkflowEventSink } from "./workflow-events.js";
|
|
13
|
+
export type RecorderDb = Pick<PrismaClient, "notificationChannel" | "workflowEvent" | "notificationDelivery" | "runAttribution" | "issuePullRequest" | "$transaction">;
|
|
14
|
+
export declare function createWorkflowEventRecorder(db: RecorderDb, enabledProviders: readonly string[]): WorkflowEventSink;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turns an emitted workflow event into outbox rows: resolves the event's work
|
|
3
|
+
* item (given, else the run's attribution, else the PR's issue link), finds
|
|
4
|
+
* the channel links that match (project links by the item's project, agent
|
|
5
|
+
* links by the event's agent), filters each link's event kinds, de-duplicates
|
|
6
|
+
* by channel, and writes one WorkflowEvent plus one NotificationDelivery per
|
|
7
|
+
* channel in a transaction. A dedupeKey that already exists is a no-op, so
|
|
8
|
+
* redeliveries and repeated finalizers record a fact once. Writes nothing
|
|
9
|
+
* when no link matches.
|
|
10
|
+
*/
|
|
11
|
+
import { Prisma } from "#prisma";
|
|
12
|
+
import { projectOf } from "../providers/issue-tracker/types.js";
|
|
13
|
+
import { threadKeys } from "./workflow-events.js";
|
|
14
|
+
async function resolveWorkItem(db, input) {
|
|
15
|
+
if (input.workItem)
|
|
16
|
+
return input.workItem;
|
|
17
|
+
if (input.runId) {
|
|
18
|
+
const attribution = await db.runAttribution.findUnique({
|
|
19
|
+
where: { runId: input.runId },
|
|
20
|
+
select: { workItem: { select: { provider: true, key: true } } },
|
|
21
|
+
});
|
|
22
|
+
if (attribution)
|
|
23
|
+
return attribution.workItem;
|
|
24
|
+
}
|
|
25
|
+
if (input.pullRequest) {
|
|
26
|
+
const pr = await db.issuePullRequest.findFirst({
|
|
27
|
+
where: {
|
|
28
|
+
codeProvider: input.pullRequest.codeProvider,
|
|
29
|
+
repository: input.pullRequest.repository,
|
|
30
|
+
number: input.pullRequest.number,
|
|
31
|
+
},
|
|
32
|
+
orderBy: { createdAt: "asc" },
|
|
33
|
+
select: { issueProvider: true, issueKey: true },
|
|
34
|
+
});
|
|
35
|
+
if (pr)
|
|
36
|
+
return { provider: pr.issueProvider, key: pr.issueKey };
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
export function createWorkflowEventRecorder(db, enabledProviders) {
|
|
41
|
+
return async (input) => {
|
|
42
|
+
if (enabledProviders.length === 0)
|
|
43
|
+
return;
|
|
44
|
+
const existing = await db.workflowEvent.findUnique({
|
|
45
|
+
where: { dedupeKey: input.dedupeKey },
|
|
46
|
+
select: { id: true },
|
|
47
|
+
});
|
|
48
|
+
if (existing)
|
|
49
|
+
return;
|
|
50
|
+
const item = await resolveWorkItem(db, input);
|
|
51
|
+
const or = [];
|
|
52
|
+
if (item)
|
|
53
|
+
or.push({ issueProvider: item.provider, projectKey: projectOf(item.key) });
|
|
54
|
+
if (input.agentId)
|
|
55
|
+
or.push({ agentId: input.agentId });
|
|
56
|
+
if (or.length === 0)
|
|
57
|
+
return;
|
|
58
|
+
const links = await db.notificationChannel.findMany({
|
|
59
|
+
where: { provider: { in: [...enabledProviders] }, OR: or },
|
|
60
|
+
select: { provider: true, channelId: true, events: true, includeCost: true },
|
|
61
|
+
});
|
|
62
|
+
const kind = input.payload.kind;
|
|
63
|
+
const byChannel = new Map();
|
|
64
|
+
for (const link of links) {
|
|
65
|
+
if (link.events.length > 0 && !link.events.includes(kind))
|
|
66
|
+
continue;
|
|
67
|
+
const key = `${link.provider}\u0000${link.channelId}`;
|
|
68
|
+
const prev = byChannel.get(key);
|
|
69
|
+
byChannel.set(key, {
|
|
70
|
+
provider: link.provider,
|
|
71
|
+
channelId: link.channelId,
|
|
72
|
+
includeCost: (prev?.includeCost ?? false) || link.includeCost,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
if (byChannel.size === 0)
|
|
76
|
+
return;
|
|
77
|
+
const threadKey = item
|
|
78
|
+
? threadKeys.issue(item.provider, item.key)
|
|
79
|
+
: input.pullRequest
|
|
80
|
+
? threadKeys.pr(input.pullRequest.codeProvider, input.pullRequest.repository, input.pullRequest.number)
|
|
81
|
+
: input.runId
|
|
82
|
+
? threadKeys.run(input.runId)
|
|
83
|
+
: null;
|
|
84
|
+
if (!threadKey)
|
|
85
|
+
return;
|
|
86
|
+
try {
|
|
87
|
+
await db.$transaction(async (tx) => {
|
|
88
|
+
const event = await tx.workflowEvent.create({
|
|
89
|
+
data: {
|
|
90
|
+
kind,
|
|
91
|
+
dedupeKey: input.dedupeKey,
|
|
92
|
+
runId: input.runId ?? null,
|
|
93
|
+
agentId: input.agentId ?? null,
|
|
94
|
+
workItemProvider: item?.provider ?? null,
|
|
95
|
+
workItemKey: item?.key ?? null,
|
|
96
|
+
codeProvider: input.pullRequest?.codeProvider ?? null,
|
|
97
|
+
repository: input.pullRequest?.repository ?? null,
|
|
98
|
+
prNumber: input.pullRequest?.number ?? null,
|
|
99
|
+
payload: input.payload,
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
await tx.notificationDelivery.createMany({
|
|
103
|
+
data: [...byChannel.values()].map((c) => ({
|
|
104
|
+
eventId: event.id,
|
|
105
|
+
provider: c.provider,
|
|
106
|
+
channelId: c.channelId,
|
|
107
|
+
threadKey,
|
|
108
|
+
includeCost: c.includeCost,
|
|
109
|
+
})),
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
// A concurrent recording of the same fact won the dedupeKey: nothing to do.
|
|
115
|
+
if (err instanceof Prisma.PrismaClientKnownRequestError && err.code === "P2002")
|
|
116
|
+
return;
|
|
117
|
+
throw err;
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}
|