@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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type Readable } from "node:stream";
|
|
2
2
|
import type { KubernetesJobConfig } from "../../config/providers.js";
|
|
3
|
+
import { type Power } from "../../core/power.js";
|
|
3
4
|
import { type KubernetesApi } from "./kubernetes-api.js";
|
|
4
5
|
import { type ServiceStateReporter } from "./service-state.js";
|
|
5
6
|
import type { CodingProvider } from "../../coding/provider.js";
|
|
@@ -19,20 +20,29 @@ export interface KubernetesJobLauncherOptions {
|
|
|
19
20
|
* the gate probes the address `provision` re-reads for that launch, not this memoized one.
|
|
20
21
|
*/
|
|
21
22
|
preflight?: () => Promise<KubernetesClusterInfo | void>;
|
|
23
|
+
/**
|
|
24
|
+
* Low-power mode: `launch()` wakes the (possibly scaled-to-zero) coding proxy before its preflight,
|
|
25
|
+
* and `warmUp()` defers the preflight while the proxy is asleep. Default `DISABLED_POWER` (no-op).
|
|
26
|
+
*/
|
|
27
|
+
power?: Power;
|
|
22
28
|
now?: () => number;
|
|
23
29
|
sleep?: (milliseconds: number) => Promise<void>;
|
|
24
30
|
readyTimeoutMs?: number;
|
|
25
31
|
/**
|
|
26
32
|
* How long to wait for the run's NetworkPolicy to be enforced before seeding. Default 30_000,
|
|
27
|
-
* but never *less* than
|
|
28
|
-
*
|
|
29
|
-
*
|
|
33
|
+
* but never *less* than one full streak exec's timeout
|
|
34
|
+
* (`enforcementExecTimeoutMs * ENFORCEMENT_BLOCKED_STREAK + ENFORCEMENT_POLL_MS * (ENFORCEMENT_BLOCKED_STREAK - 1)`,
|
|
35
|
+
* 31_000 at the default exec timeout): see the constructor. An explicit override here is a floor
|
|
36
|
+
* raise, not an escape hatch — the gate still needs room for a full streak of probes at whatever
|
|
37
|
+
* the effective per-probe budget is.
|
|
30
38
|
*/
|
|
31
39
|
enforcementTimeoutMs?: number;
|
|
32
40
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
41
|
+
* Time budget for a single enforcement probe. Default 10_000 (DEFAULT_ENFORCEMENT_EXEC_TIMEOUT_MS).
|
|
42
|
+
* A streak's probes share one exec, whose timeout is this times ENFORCEMENT_BLOCKED_STREAK plus
|
|
43
|
+
* the poll gaps between them. Raise this on a resource-constrained cluster (e.g. `kind` on a
|
|
44
|
+
* laptop) where a healthy probe can legitimately take longer than 10 s to run inside the
|
|
45
|
+
* keeper's CPU/memory limits.
|
|
36
46
|
*/
|
|
37
47
|
enforcementExecTimeoutMs?: number;
|
|
38
48
|
createArchive?: (directory: string) => {
|
|
@@ -60,7 +70,10 @@ export declare class KubernetesJobLauncher implements WorkspaceJobLauncher {
|
|
|
60
70
|
private readonly warn;
|
|
61
71
|
private readonly enforcementTimeoutMs;
|
|
62
72
|
private readonly enforcementExecTimeoutMs;
|
|
73
|
+
/** Timeout of one streak exec: ENFORCEMENT_BLOCKED_STREAK probes plus the gaps between them. */
|
|
74
|
+
private readonly streakExecTimeoutMs;
|
|
63
75
|
private preflightResult?;
|
|
76
|
+
private readonly power;
|
|
64
77
|
constructor(options: KubernetesJobLauncherOptions);
|
|
65
78
|
/**
|
|
66
79
|
* Services run as native sidecars in the run pod (kubernetes-isolation.ts), for either provider:
|
|
@@ -88,7 +101,35 @@ export declare class KubernetesJobLauncher implements WorkspaceJobLauncher {
|
|
|
88
101
|
materializeWorkspace(handle: JobHandle, destination: string): Promise<void>;
|
|
89
102
|
stop(handle: JobHandle, _reason?: string): Promise<void>;
|
|
90
103
|
remove(handle: JobHandle): Promise<void>;
|
|
91
|
-
/**
|
|
104
|
+
/**
|
|
105
|
+
* Low-power mode: whether the proxy is scaled to zero. A failed check is logged by code only (the
|
|
106
|
+
* leading reason code, else the error's class name -- never the raw message, which could carry
|
|
107
|
+
* cluster detail) and counts as awake, so warm-up runs the preflight as it would without the mode.
|
|
108
|
+
*/
|
|
109
|
+
private proxyAsleep;
|
|
110
|
+
/**
|
|
111
|
+
* Starts the memoized cluster preflight (`runPreflight`) right away, instead of waiting for the
|
|
112
|
+
* first `launch()`, so a long-running server process's own start-up pays the preflight's cost (a
|
|
113
|
+
* canary pod on `kind`: ~20s) rather than that process's first coding run. Never throws: it only
|
|
114
|
+
* reports the outcome (an info line on success, a warning naming the failure code otherwise) so
|
|
115
|
+
* an operator sees a misconfigured cluster at boot. Any `launch()` already awaiting this same
|
|
116
|
+
* attempt (concurrent with this call) still fails exactly as it does today when the preflight
|
|
117
|
+
* failed — it shares the same rejected promise. But unlike a preflight `launch()` starts on its
|
|
118
|
+
* own, a start-up failure here is **not** left stuck memoized: a transient cluster problem at
|
|
119
|
+
* process start must not fail every coding run until restart, so the failed attempt is cleared
|
|
120
|
+
* (only if nothing has already started a newer one — compared by identity, never by state) and
|
|
121
|
+
* the *next* `launch()` runs the preflight fresh. A successful warm-up stays memoized as always.
|
|
122
|
+
* In low-power mode, while the proxy is asleep, warm-up is skipped (the memo stays unset) rather
|
|
123
|
+
* than waking the proxy only for the preflight; the first `launch()` wakes it and runs the preflight.
|
|
124
|
+
*/
|
|
125
|
+
warmUp(): Promise<void>;
|
|
126
|
+
/**
|
|
127
|
+
* Memoized: the preflight (or, without one, a single proxy-witness read) runs once per launcher —
|
|
128
|
+
* including its failure, which stays memoized and fails every subsequent `launch()` immediately,
|
|
129
|
+
* for a preflight `launch()` itself started (there was no `warmUp()`, or `warmUp()`'s own failed
|
|
130
|
+
* attempt was already cleared). `warmUp()` is the one caller that clears a failed attempt instead
|
|
131
|
+
* of leaving it stuck; see its own doc comment.
|
|
132
|
+
*/
|
|
92
133
|
private runPreflight;
|
|
93
134
|
/**
|
|
94
135
|
* Waits until the run's NetworkPolicy is enforced on this pod: the keeper (same network namespace
|
|
@@ -96,6 +137,14 @@ export declare class KubernetesJobLauncher implements WorkspaceJobLauncher {
|
|
|
96
137
|
* proxy on CODING_PROXY_PORT and fail to reach it on CODING_PROXY_DENY_PORT. Anything else resets
|
|
97
138
|
* the streak; the wall-clock bound still applies.
|
|
98
139
|
*
|
|
140
|
+
* The whole streak runs in ONE keeper exec (`enforcementStreakScript`), so exec and interpreter
|
|
141
|
+
* start-up is paid once per streak rather than once per probe. That exec exits 0 only after the
|
|
142
|
+
* full streak was proven; otherwise it stops at the first non-proven probe and exits with that
|
|
143
|
+
* probe's code. Any non-zero exit is a broken streak: if the bound has been reached it is the
|
|
144
|
+
* verdict, else the launcher waits ENFORCEMENT_POLL_MS and starts a fresh streak from zero. The
|
|
145
|
+
* exec is bounded by `streakExecTimeoutMs` (the per-probe budget times the streak, plus the gaps);
|
|
146
|
+
* an exec that times out or fails throws, failing the launch exactly as a failed probe exec did.
|
|
147
|
+
*
|
|
99
148
|
* "Fail to reach" means the connect *timed out* — the packet was dropped. A refusal (RST) is not
|
|
100
149
|
* a denial: it proves the SYN reached the destination host, so the deny port is merely unserved
|
|
101
150
|
* and witnesses nothing. That is its own verdict, below.
|
|
@@ -29,11 +29,12 @@ import { finished } from "node:stream/promises";
|
|
|
29
29
|
import { createHash } from "node:crypto";
|
|
30
30
|
import { isIP } from "node:net";
|
|
31
31
|
import { logger } from "../../core/logger.js";
|
|
32
|
-
import {
|
|
32
|
+
import { DISABLED_POWER, wakeTimeoutMs } from "../../core/power.js";
|
|
33
|
+
import { MAX_CODING_ARTIFACT_BYTES, MAX_CODING_INPUT_BYTES, MAX_DEBUG_TRACE_LINE_BYTES, parseCodingAgentOutputJson, } from "../../coding/protocol.js";
|
|
33
34
|
import { CODING_PROXY_DENY_PORT, CODING_PROXY_PORT } from "./docker-isolation.js";
|
|
34
35
|
import { parseWorkerDiagnosticLine } from "./docker.js";
|
|
35
36
|
import { KubernetesAlreadyExistsError, KubernetesConflictError } from "./kubernetes-api.js";
|
|
36
|
-
import { ENFORCEMENT_PROBE_DENY_REACHABLE, ENFORCEMENT_PROBE_PROVEN, ENFORCEMENT_PROBE_DENY_REFUSED, ENFORCEMENT_PROBE_PROXY_UNREACHABLE, KEEPER_CONTAINER, KEEPER_SEEDED_MARKER, KUBERNETES_ISOLATION_ERROR, STORAGE_ROOT, TOOL_RUNNER_CONTAINER, WORKER_CONTAINER, assertRunNetworkPolicyMatches, assertRunPodMatches, buildCapabilitySecret, buildRunNetworkPolicy, buildRunPod,
|
|
37
|
+
import { ENFORCEMENT_PROBE_DENY_REACHABLE, ENFORCEMENT_PROBE_PROVEN, ENFORCEMENT_PROBE_DENY_REFUSED, ENFORCEMENT_PROBE_PROXY_UNREACHABLE, KEEPER_CONTAINER, KEEPER_SEEDED_MARKER, KUBERNETES_ISOLATION_ERROR, STORAGE_ROOT, TOOL_RUNNER_CONTAINER, WORKER_CONTAINER, assertRunNetworkPolicyMatches, assertRunPodMatches, buildCapabilitySecret, buildRunNetworkPolicy, buildRunPod, enforcementStreakScript, kubernetesRunNames, kubernetesRunNamesForToken, runLabels, serviceContainerName, validateKubernetesSpec, } from "./kubernetes-isolation.js";
|
|
37
38
|
import { claudeToolSetup } from "./claude-tool-setup.js";
|
|
38
39
|
import { podQuotaUsage, quotaShortfall } from "./kubernetes-quota.js";
|
|
39
40
|
import { readProxyWitness } from "./kubernetes-witness.js";
|
|
@@ -60,14 +61,17 @@ const ENFORCEMENT_POLL_MS = 500;
|
|
|
60
61
|
/** Consecutive proven probes required (8787 reachable, 8788 blocked): one dropped SYN must not open the gate. */
|
|
61
62
|
const ENFORCEMENT_BLOCKED_STREAK = 3;
|
|
62
63
|
/**
|
|
63
|
-
* Default
|
|
64
|
+
* Default time budget for ONE enforcement probe (the probe makes two sequential connects, so it
|
|
64
65
|
* gives up after at most 6 s of its own connect timeouts). Overridable per KubernetesJobConfig's
|
|
65
66
|
* `enforcementExecTimeoutMs` (KUBERNETES_ENFORCEMENT_EXEC_TIMEOUT_MS) — a resource-constrained
|
|
66
67
|
* keeper (e.g. a laptop `kind` cluster's default 250m CPU / 128Mi limit) can take noticeably
|
|
67
|
-
* longer than this to run even a healthy probe to completion.
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* timeout is
|
|
68
|
+
* longer than this to run even a healthy probe to completion.
|
|
69
|
+
*
|
|
70
|
+
* The probes of one streak run inside a single exec (`enforcementStreakScript`), so the exec's own
|
|
71
|
+
* timeout is derived from this per-probe budget: ENFORCEMENT_BLOCKED_STREAK probes plus the
|
|
72
|
+
* ENFORCEMENT_POLL_MS gaps between them (`streakExecTimeoutMs`; 31_000 at this default). The
|
|
73
|
+
* constructor then floors the overall wall-clock bound at that one-streak-exec value, so a raised
|
|
74
|
+
* per-probe budget can never leave the bound shorter than a single streak.
|
|
71
75
|
*/
|
|
72
76
|
const DEFAULT_ENFORCEMENT_EXEC_TIMEOUT_MS = 10_000;
|
|
73
77
|
/**
|
|
@@ -81,12 +85,10 @@ const DEFAULT_ENFORCEMENT_EXEC_TIMEOUT_MS = 10_000;
|
|
|
81
85
|
* and sending an operator to the CNI over a dead exec wastes the one clue they were given.
|
|
82
86
|
*/
|
|
83
87
|
const ENFORCEMENT_VERDICTS = {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
detail: `the last probe was proven, but never ${ENFORCEMENT_BLOCKED_STREAK} consecutive times within the bound — enforcement is flapping rather than absent`,
|
|
89
|
-
},
|
|
88
|
+
// No ENFORCEMENT_PROBE_PROVEN entry, deliberately: the whole streak runs in one exec
|
|
89
|
+
// (enforcementStreakScript), which exits PROVEN only after ENFORCEMENT_BLOCKED_STREAK consecutive
|
|
90
|
+
// proven probes — and that opens the gate before the bound is ever checked. A streak broken by
|
|
91
|
+
// flapping exits with the breaking probe's own code, so the verdict is always one of these.
|
|
90
92
|
[ENFORCEMENT_PROBE_DENY_REACHABLE]: {
|
|
91
93
|
code: "kubernetes_policy_not_enforced",
|
|
92
94
|
detail: `the deny port ${CODING_PROXY_DENY_PORT} accepted a connection, so no policy is blocking it (or the policy is not port-scoped) — look at the CNI`,
|
|
@@ -110,12 +112,8 @@ const ENFORCEMENT_PROBE_DID_NOT_RUN = {
|
|
|
110
112
|
};
|
|
111
113
|
/** Same shape as ENFORCEMENT_VERDICTS/ENFORCEMENT_PROBE_DID_NOT_RUN, but for the `pre_marker` stage. */
|
|
112
114
|
const ENFORCEMENT_VERDICTS_PRE_MARKER = {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
detail: `the policy was proven enforced earlier in this launch, but the re-probe run immediately before ` +
|
|
116
|
-
`opening the gate never reached ${ENFORCEMENT_BLOCKED_STREAK} consecutive proven probes within the ` +
|
|
117
|
-
"bound — enforcement held at the initial proof but is flapping now, immediately before release",
|
|
118
|
-
},
|
|
115
|
+
// No ENFORCEMENT_PROBE_PROVEN entry, for the same reason as ENFORCEMENT_VERDICTS: a proven streak
|
|
116
|
+
// exec opens the gate, so it never reaches enforcementVerdict.
|
|
119
117
|
[ENFORCEMENT_PROBE_DENY_REACHABLE]: {
|
|
120
118
|
code: "kubernetes_policy_enforcement_lost_before_marker",
|
|
121
119
|
detail: `the deny port ${CODING_PROXY_DENY_PORT} was proven blocked earlier in this launch, but the re-probe ` +
|
|
@@ -386,10 +384,14 @@ export class KubernetesJobLauncher {
|
|
|
386
384
|
warn;
|
|
387
385
|
enforcementTimeoutMs;
|
|
388
386
|
enforcementExecTimeoutMs;
|
|
387
|
+
/** Timeout of one streak exec: ENFORCEMENT_BLOCKED_STREAK probes plus the gaps between them. */
|
|
388
|
+
streakExecTimeoutMs;
|
|
389
389
|
preflightResult;
|
|
390
|
+
power;
|
|
390
391
|
constructor(options) {
|
|
391
392
|
this.options = options;
|
|
392
393
|
this.api = options.api;
|
|
394
|
+
this.power = options.power ?? DISABLED_POWER;
|
|
393
395
|
this.namespace = options.config.namespace;
|
|
394
396
|
this.workspaceRoot = resolve(options.workspaceRoot);
|
|
395
397
|
if (this.workspaceRoot === resolve("/"))
|
|
@@ -398,13 +400,18 @@ export class KubernetesJobLauncher {
|
|
|
398
400
|
this.sleep = options.sleep ?? ((ms) => new Promise((resolvePromise) => setTimeout(resolvePromise, ms)));
|
|
399
401
|
this.readyTimeoutMs = options.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS;
|
|
400
402
|
this.enforcementExecTimeoutMs = options.enforcementExecTimeoutMs ?? DEFAULT_ENFORCEMENT_EXEC_TIMEOUT_MS;
|
|
401
|
-
//
|
|
402
|
-
//
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
//
|
|
407
|
-
this
|
|
403
|
+
// One exec runs a whole streak: ENFORCEMENT_BLOCKED_STREAK probes, each within its own
|
|
404
|
+
// per-probe budget, with ENFORCEMENT_POLL_MS between consecutive probes.
|
|
405
|
+
this.streakExecTimeoutMs =
|
|
406
|
+
this.enforcementExecTimeoutMs * ENFORCEMENT_BLOCKED_STREAK +
|
|
407
|
+
ENFORCEMENT_POLL_MS * (ENFORCEMENT_BLOCKED_STREAK - 1);
|
|
408
|
+
// Never less than one full streak exec's worst case: a longer configured per-probe budget that
|
|
409
|
+
// left this unchanged could make the wall-clock bound expire inside the first slow streak exec,
|
|
410
|
+
// leaving no room to retry a streak broken while the policy was still being programmed — which
|
|
411
|
+
// would turn "the keeper is just slow" into an indistinguishable "the policy is not enforced"
|
|
412
|
+
// false negative. At the defaults this raises DEFAULT_ENFORCEMENT_TIMEOUT_MS (30_000) to the
|
|
413
|
+
// default streak exec timeout (10_000 * 3 + 500 * 2 = 31_000).
|
|
414
|
+
this.enforcementTimeoutMs = Math.max(options.enforcementTimeoutMs ?? DEFAULT_ENFORCEMENT_TIMEOUT_MS, this.streakExecTimeoutMs);
|
|
408
415
|
this.createArchive = options.createArchive ?? hostTarArchive;
|
|
409
416
|
this.warn = options.onWarning ?? ((message) => kubernetesLog.warn(message));
|
|
410
417
|
}
|
|
@@ -491,6 +498,11 @@ export class KubernetesJobLauncher {
|
|
|
491
498
|
this.warn(`kubernetes_spec_rejected: ${error.message}`);
|
|
492
499
|
throw error;
|
|
493
500
|
}
|
|
501
|
+
// Low-power mode: the proxy may be scaled to zero; wake it before the preflight reads it. Guarded
|
|
502
|
+
// (not just a no-op await) so mode off keeps launch() joining an in-flight warm-up synchronously.
|
|
503
|
+
if (this.power.enabled) {
|
|
504
|
+
await this.power.ensureAwake("proxy", { timeoutMs: wakeTimeoutMs(this.readyTimeoutMs), runId: spec.runId });
|
|
505
|
+
}
|
|
494
506
|
await this.runPreflight();
|
|
495
507
|
const names = kubernetesRunNames(spec.runId);
|
|
496
508
|
const handle = { backend: BACKEND, id: `${this.namespace}/${names.token}` };
|
|
@@ -614,7 +626,69 @@ export class KubernetesJobLauncher {
|
|
|
614
626
|
}
|
|
615
627
|
// -------------------------------------------------------------------------
|
|
616
628
|
// Launch
|
|
617
|
-
/**
|
|
629
|
+
/**
|
|
630
|
+
* Low-power mode: whether the proxy is scaled to zero. A failed check is logged by code only (the
|
|
631
|
+
* leading reason code, else the error's class name -- never the raw message, which could carry
|
|
632
|
+
* cluster detail) and counts as awake, so warm-up runs the preflight as it would without the mode.
|
|
633
|
+
*/
|
|
634
|
+
async proxyAsleep() {
|
|
635
|
+
try {
|
|
636
|
+
return await this.power.isAsleep("proxy");
|
|
637
|
+
}
|
|
638
|
+
catch (error) {
|
|
639
|
+
const code = (error instanceof Error && /^[a-z][a-z0-9_]*(?=:)/.exec(error.message)?.[0]) ||
|
|
640
|
+
(error instanceof Error ? error.name : "unknown");
|
|
641
|
+
this.warn(`kubernetes_preflight_power_check_failed: ${code}`);
|
|
642
|
+
return false;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Starts the memoized cluster preflight (`runPreflight`) right away, instead of waiting for the
|
|
647
|
+
* first `launch()`, so a long-running server process's own start-up pays the preflight's cost (a
|
|
648
|
+
* canary pod on `kind`: ~20s) rather than that process's first coding run. Never throws: it only
|
|
649
|
+
* reports the outcome (an info line on success, a warning naming the failure code otherwise) so
|
|
650
|
+
* an operator sees a misconfigured cluster at boot. Any `launch()` already awaiting this same
|
|
651
|
+
* attempt (concurrent with this call) still fails exactly as it does today when the preflight
|
|
652
|
+
* failed — it shares the same rejected promise. But unlike a preflight `launch()` starts on its
|
|
653
|
+
* own, a start-up failure here is **not** left stuck memoized: a transient cluster problem at
|
|
654
|
+
* process start must not fail every coding run until restart, so the failed attempt is cleared
|
|
655
|
+
* (only if nothing has already started a newer one — compared by identity, never by state) and
|
|
656
|
+
* the *next* `launch()` runs the preflight fresh. A successful warm-up stays memoized as always.
|
|
657
|
+
* In low-power mode, while the proxy is asleep, warm-up is skipped (the memo stays unset) rather
|
|
658
|
+
* than waking the proxy only for the preflight; the first `launch()` wakes it and runs the preflight.
|
|
659
|
+
*/
|
|
660
|
+
async warmUp() {
|
|
661
|
+
if (this.power.enabled && (await this.proxyAsleep())) {
|
|
662
|
+
kubernetesLog.info("kubernetes_preflight_deferred: proxy asleep (low-power mode); the first coding run runs the preflight");
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
const attempt = this.runPreflight();
|
|
666
|
+
try {
|
|
667
|
+
await attempt;
|
|
668
|
+
kubernetesLog.info("kubernetes_preflight_warm_up_succeeded: cluster preflight passed at start-up");
|
|
669
|
+
}
|
|
670
|
+
catch (error) {
|
|
671
|
+
// Only clear the memo if it still holds this exact attempt: a concurrent launch() or warmUp()
|
|
672
|
+
// may have already seen this same rejection and started a fresh one (which must survive), and
|
|
673
|
+
// nothing else could have replaced this attempt with anything but a newer one, since
|
|
674
|
+
// runPreflight() never overwrites an already-set, still-pending-or-resolved promise.
|
|
675
|
+
if (this.preflightResult === attempt)
|
|
676
|
+
this.preflightResult = undefined;
|
|
677
|
+
// Same code-only convention as every other failure this launcher logs (e.g. launch()'s
|
|
678
|
+
// kubernetes_spec_rejected above): the error's own message is the fixed reason code set by
|
|
679
|
+
// runPreflight's errorWithCode (KUBERNETES_ISOLATION_ERROR) — never the raw cause, which could
|
|
680
|
+
// carry cluster detail.
|
|
681
|
+
const code = error instanceof Error ? error.message : String(error);
|
|
682
|
+
this.warn(`kubernetes_preflight_warm_up_failed: ${code}`);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Memoized: the preflight (or, without one, a single proxy-witness read) runs once per launcher —
|
|
687
|
+
* including its failure, which stays memoized and fails every subsequent `launch()` immediately,
|
|
688
|
+
* for a preflight `launch()` itself started (there was no `warmUp()`, or `warmUp()`'s own failed
|
|
689
|
+
* attempt was already cleared). `warmUp()` is the one caller that clears a failed attempt instead
|
|
690
|
+
* of leaving it stuck; see its own doc comment.
|
|
691
|
+
*/
|
|
618
692
|
runPreflight() {
|
|
619
693
|
this.preflightResult ??= (async () => {
|
|
620
694
|
try {
|
|
@@ -639,6 +713,14 @@ export class KubernetesJobLauncher {
|
|
|
639
713
|
* proxy on CODING_PROXY_PORT and fail to reach it on CODING_PROXY_DENY_PORT. Anything else resets
|
|
640
714
|
* the streak; the wall-clock bound still applies.
|
|
641
715
|
*
|
|
716
|
+
* The whole streak runs in ONE keeper exec (`enforcementStreakScript`), so exec and interpreter
|
|
717
|
+
* start-up is paid once per streak rather than once per probe. That exec exits 0 only after the
|
|
718
|
+
* full streak was proven; otherwise it stops at the first non-proven probe and exits with that
|
|
719
|
+
* probe's code. Any non-zero exit is a broken streak: if the bound has been reached it is the
|
|
720
|
+
* verdict, else the launcher waits ENFORCEMENT_POLL_MS and starts a fresh streak from zero. The
|
|
721
|
+
* exec is bounded by `streakExecTimeoutMs` (the per-probe budget times the streak, plus the gaps);
|
|
722
|
+
* an exec that times out or fails throws, failing the launch exactly as a failed probe exec did.
|
|
723
|
+
*
|
|
642
724
|
* "Fail to reach" means the connect *timed out* — the packet was dropped. A refusal (RST) is not
|
|
643
725
|
* a denial: it proves the SYN reached the destination host, so the deny port is merely unserved
|
|
644
726
|
* and witnesses nothing. That is its own verdict, below.
|
|
@@ -659,17 +741,16 @@ export class KubernetesJobLauncher {
|
|
|
659
741
|
* must not open the gate at the initial proof either.
|
|
660
742
|
*/
|
|
661
743
|
async waitForPolicyEnforcement(names, proxyIp, stage = "initial") {
|
|
662
|
-
const command = ["node", "-e",
|
|
744
|
+
const command = ["node", "-e", enforcementStreakScript(proxyIp, ENFORCEMENT_BLOCKED_STREAK, ENFORCEMENT_POLL_MS)];
|
|
663
745
|
const started = this.now();
|
|
664
|
-
let blocked = 0;
|
|
665
746
|
for (;;) {
|
|
666
|
-
// `exitCode` is this
|
|
667
|
-
//
|
|
747
|
+
// `exitCode` is this attempt's streak, which stops at (and exits with) its first non-proven
|
|
748
|
+
// probe, and the bound below is only ever reached from here — so the verdict is always the
|
|
749
|
+
// *last* probe's, never a remembered earlier one.
|
|
668
750
|
const exitCode = await this.api.exec(this.namespace, names.pod, KEEPER_CONTAINER, command, {
|
|
669
|
-
timeoutMs: this.
|
|
751
|
+
timeoutMs: this.streakExecTimeoutMs,
|
|
670
752
|
});
|
|
671
|
-
|
|
672
|
-
if (blocked >= ENFORCEMENT_BLOCKED_STREAK)
|
|
753
|
+
if (exitCode === ENFORCEMENT_PROBE_PROVEN)
|
|
673
754
|
return;
|
|
674
755
|
if (this.now() - started >= this.enforcementTimeoutMs) {
|
|
675
756
|
throw enforcementVerdict(exitCode, proxyIp, stage);
|
|
@@ -851,14 +932,14 @@ export class KubernetesJobLauncher {
|
|
|
851
932
|
const staging = await mkdtemp(join(tmpdir(), "wardby-k8s-input-"));
|
|
852
933
|
try {
|
|
853
934
|
const source = await lstat(inputArtifact);
|
|
854
|
-
if (!source.isFile() || source.size >
|
|
935
|
+
if (!source.isFile() || source.size > MAX_CODING_INPUT_BYTES) {
|
|
855
936
|
throw new Error("kubernetes_input_artifact_invalid");
|
|
856
937
|
}
|
|
857
938
|
const copy = join(staging, "input.json");
|
|
858
939
|
await copyFile(inputArtifact, copy);
|
|
859
940
|
// Re-check the copy: the source could have changed between lstat and copy.
|
|
860
941
|
const copied = await lstat(copy);
|
|
861
|
-
if (!copied.isFile() || copied.size >
|
|
942
|
+
if (!copied.isFile() || copied.size > MAX_CODING_INPUT_BYTES) {
|
|
862
943
|
throw new Error("kubernetes_input_artifact_invalid");
|
|
863
944
|
}
|
|
864
945
|
await this.seedDirectory(names, staging, INPUT_STORAGE, timeoutMs);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloud metadata endpoints a coding or native worker pod must never reach: they hand out node or
|
|
3
|
+
* workload credentials. The instance metadata service (GCE, EC2 IPv4), the EKS Pod Identity agent (IPv4
|
|
4
|
+
* and IPv6), and EC2's IPv6 IMDS endpoint. Probed on port 80, where every one of them serves. A pod without
|
|
5
|
+
* IPv6 fails the last connect immediately, which counts as unreachable — correct either way.
|
|
6
|
+
* src/sandbox/fetch-policy.ts blocks the same addresses for in-process fetches.
|
|
7
|
+
*/
|
|
8
|
+
export declare const CLOUD_METADATA_PROBES: readonly {
|
|
9
|
+
host: string;
|
|
10
|
+
port: number;
|
|
11
|
+
}[];
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloud metadata endpoints a coding or native worker pod must never reach: they hand out node or
|
|
3
|
+
* workload credentials. The instance metadata service (GCE, EC2 IPv4), the EKS Pod Identity agent (IPv4
|
|
4
|
+
* and IPv6), and EC2's IPv6 IMDS endpoint. Probed on port 80, where every one of them serves. A pod without
|
|
5
|
+
* IPv6 fails the last connect immediately, which counts as unreachable — correct either way.
|
|
6
|
+
* src/sandbox/fetch-policy.ts blocks the same addresses for in-process fetches.
|
|
7
|
+
*/
|
|
8
|
+
export const CLOUD_METADATA_PROBES = Object.freeze([
|
|
9
|
+
Object.freeze({ host: "169.254.169.254", port: 80 }),
|
|
10
|
+
Object.freeze({ host: "169.254.170.23", port: 80 }),
|
|
11
|
+
Object.freeze({ host: "fd00:ec2::254", port: 80 }),
|
|
12
|
+
Object.freeze({ host: "fd00:ec2::23", port: 80 }),
|
|
13
|
+
]);
|
|
@@ -88,6 +88,15 @@ export interface JobLauncher {
|
|
|
88
88
|
* itself stays the authority.
|
|
89
89
|
*/
|
|
90
90
|
hasCapacityFor?: (spec: JobSpec) => Promise<boolean>;
|
|
91
|
+
/**
|
|
92
|
+
* Starts any one-time start-up check (e.g. Kubernetes's memoized cluster preflight) right away,
|
|
93
|
+
* instead of waiting for the first `launch()`. Never throws — a failure is only logged — and
|
|
94
|
+
* `launch()` still runs and reports the same check itself, memoized exactly as if this had never
|
|
95
|
+
* been called. Composition roots call this, fire-and-forget, only from a long-running server
|
|
96
|
+
* process's own start-up (`wardby serve`, the MCP server, `wardby scheduler`); never from a
|
|
97
|
+
* one-shot CLI command. Absent on a launcher with nothing to warm up (e.g. Docker).
|
|
98
|
+
*/
|
|
99
|
+
warmUp?: () => Promise<void>;
|
|
91
100
|
launch: (spec: JobSpec) => Promise<JobHandle>;
|
|
92
101
|
status: (handle: JobHandle) => Promise<JobStatus>;
|
|
93
102
|
collect: (handle: JobHandle) => Promise<JobResult>;
|
|
@@ -153,7 +153,6 @@ export class OpenAiLlmProvider {
|
|
|
153
153
|
store: false,
|
|
154
154
|
...(req.tools ? { tools: toOpenAiTools(req.tools) } : {}),
|
|
155
155
|
...(req.maxTokens !== undefined ? { max_output_tokens: Math.max(req.maxTokens, MIN_MAX_OUTPUT_TOKENS) } : {}),
|
|
156
|
-
// The SDK's ReasoningEffort type predates xhigh/max; the API accepts them.
|
|
157
156
|
...(sendEffort ? { reasoning: { effort: req.effort } } : {}),
|
|
158
157
|
...(sendTemperature ? { temperature: req.temperature } : {}),
|
|
159
158
|
};
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* and revokes it afterwards. See docs/private/2026-09-25-code-review-host-design.md §5.1.
|
|
6
6
|
*/
|
|
7
7
|
import { type GitHubAppClient } from "../vcs/github.js";
|
|
8
|
-
import { ReviewHostError, type CiView, type CodeReviewHost, type CommentInput, type CommentRef, type CompleteCheckInput, type EditCommentInput, type FileListView, type FileReadResult, type HostPermission, type HostUser, type PublishReviewInput, type PublishReviewResult, type PullRequestHead, type PullRequestOrigin, type PullRequestView, type StartCheckInput } from "./types.js";
|
|
8
|
+
import { ReviewHostError, type CiView, type CodeReviewHost, type CommentInput, type CommentRef, type CompleteCheckInput, type EditCommentInput, type FileListView, type FileReadResult, type HostPermission, type HostUser, type PublishReviewInput, type PublishReviewResult, type PullRequestHead, type PullRequestOrigin, type PullRequestView, type StartCheckInput, type UpsertNamedCheckInput } from "./types.js";
|
|
9
9
|
/** listFiles leaves out vendored trees and lockfiles; every host applies the same filter. */
|
|
10
10
|
export declare function skippedInListing(path: string): boolean;
|
|
11
11
|
type Json = Record<string, unknown>;
|
|
@@ -16,6 +16,7 @@ export declare class GitHubReviewHost implements CodeReviewHost {
|
|
|
16
16
|
private readonly client;
|
|
17
17
|
readonly provider: "github";
|
|
18
18
|
constructor(client: GitHubAppClient);
|
|
19
|
+
appOwnerLogin(): Promise<string>;
|
|
19
20
|
/** Runs `action` with a token for exactly `permissions`, translating failures to ReviewHostError. */
|
|
20
21
|
protected withToken<T>(repository: string, permissions: Record<string, "read" | "write">, action: (get: (path: string, init?: RequestInit, expected?: number[]) => Promise<Response>, graphql: GraphQl) => Promise<T>): Promise<T>;
|
|
21
22
|
repositoryPermission(repository: string, user: HostUser): Promise<{
|
|
@@ -70,5 +71,14 @@ export declare class GitHubReviewHost implements CodeReviewHost {
|
|
|
70
71
|
checkId: string;
|
|
71
72
|
}>;
|
|
72
73
|
completeCheck(repository: string, input: CompleteCheckInput): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Creates or updates, by fixed name, a check run on `input.headSha` that
|
|
76
|
+
* this App owns: lists the commit's check runs filtered to that name and
|
|
77
|
+
* patches the newest one `fromApp` owns, else creates one. Writes nothing
|
|
78
|
+
* when that run already carries the same status, conclusion, and output.
|
|
79
|
+
* A completed run is never moved back to in_progress (GitHub does not
|
|
80
|
+
* allow reopening a completed check run): a fresh one is created instead.
|
|
81
|
+
*/
|
|
82
|
+
upsertNamedCheck(repository: string, input: UpsertNamedCheckInput): Promise<void>;
|
|
73
83
|
}
|
|
74
84
|
export {};
|
|
@@ -22,6 +22,12 @@ export function skippedInListing(path) {
|
|
|
22
22
|
return VENDORED.test(path) || path.endsWith("package-lock.json");
|
|
23
23
|
}
|
|
24
24
|
const CHECK_TEXT_LIMIT = 65_535;
|
|
25
|
+
/**
|
|
26
|
+
* Cap on a named check's output.title. Not a documented GitHub limit (none
|
|
27
|
+
* is published for the title); a conservative length that keeps the title
|
|
28
|
+
* a one-line heading in the checks UI.
|
|
29
|
+
*/
|
|
30
|
+
const CHECK_TITLE_LIMIT = 255;
|
|
25
31
|
const COMMENT_WRITE = { issues: "write", pull_requests: "write" };
|
|
26
32
|
const REVIEW_WRITE = { pull_requests: "write", checks: "write" };
|
|
27
33
|
/** Always implicitly granted to an installation token; enough for the collaborator-permission endpoint. */
|
|
@@ -201,6 +207,9 @@ export class GitHubReviewHost {
|
|
|
201
207
|
constructor(client) {
|
|
202
208
|
this.client = client;
|
|
203
209
|
}
|
|
210
|
+
async appOwnerLogin() {
|
|
211
|
+
return (await this.client.appIdentity()).ownerLogin;
|
|
212
|
+
}
|
|
204
213
|
/** Runs `action` with a token for exactly `permissions`, translating failures to ReviewHostError. */
|
|
205
214
|
async withToken(repository, permissions, action) {
|
|
206
215
|
try {
|
|
@@ -708,4 +717,45 @@ export class GitHubReviewHost {
|
|
|
708
717
|
const base = repoPath(repository);
|
|
709
718
|
await this.withToken(repository, { checks: "write" }, (get) => this.patchCheck(get, base, input));
|
|
710
719
|
}
|
|
720
|
+
/**
|
|
721
|
+
* Creates or updates, by fixed name, a check run on `input.headSha` that
|
|
722
|
+
* this App owns: lists the commit's check runs filtered to that name and
|
|
723
|
+
* patches the newest one `fromApp` owns, else creates one. Writes nothing
|
|
724
|
+
* when that run already carries the same status, conclusion, and output.
|
|
725
|
+
* A completed run is never moved back to in_progress (GitHub does not
|
|
726
|
+
* allow reopening a completed check run): a fresh one is created instead.
|
|
727
|
+
*/
|
|
728
|
+
async upsertNamedCheck(repository, input) {
|
|
729
|
+
// A caller's bad input, like path_invalid: never interpolated into the URL.
|
|
730
|
+
if (!SAFE_SHA.test(input.headSha))
|
|
731
|
+
throw new ReviewHostError("host_api_error", "head_sha_invalid");
|
|
732
|
+
const base = repoPath(repository);
|
|
733
|
+
const output = {
|
|
734
|
+
title: input.title.slice(0, CHECK_TITLE_LIMIT),
|
|
735
|
+
summary: input.summary.slice(0, CHECK_TEXT_LIMIT),
|
|
736
|
+
};
|
|
737
|
+
const conclusion = input.status === "completed" ? input.conclusion : undefined;
|
|
738
|
+
await this.withToken(repository, { checks: "write" }, async (get) => {
|
|
739
|
+
const { id: appId } = await this.client.appIdentity();
|
|
740
|
+
const payload = record(await (await get(`${base}/commits/${input.headSha}/check-runs?check_name=${encodeURIComponent(input.name)}`)).json());
|
|
741
|
+
const existing = list(payload.check_runs ?? [])
|
|
742
|
+
.filter((r) => fromApp(r, appId))
|
|
743
|
+
.sort((a, b) => num(b.id) - num(a.id))[0];
|
|
744
|
+
const body = { status: input.status, ...(conclusion ? { conclusion } : {}), output };
|
|
745
|
+
if (existing) {
|
|
746
|
+
const current = record(existing.output ?? {});
|
|
747
|
+
const unchanged = existing.status === input.status &&
|
|
748
|
+
(existing.conclusion ?? undefined) === conclusion &&
|
|
749
|
+
current.title === output.title &&
|
|
750
|
+
current.summary === output.summary;
|
|
751
|
+
if (unchanged)
|
|
752
|
+
return;
|
|
753
|
+
if (!(existing.status === "completed" && input.status !== "completed")) {
|
|
754
|
+
await get(`${base}/check-runs/${encodeURIComponent(String(num(existing.id)))}`, { method: "PATCH", body: JSON.stringify(body) }, [200]);
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
await get(`${base}/check-runs`, { method: "POST", body: JSON.stringify({ name: input.name, head_sha: input.headSha, ...body }) }, [201]);
|
|
759
|
+
});
|
|
760
|
+
}
|
|
711
761
|
}
|
|
@@ -207,6 +207,18 @@ export interface CompleteCheckInput {
|
|
|
207
207
|
text?: string;
|
|
208
208
|
detailsUrl?: string;
|
|
209
209
|
}
|
|
210
|
+
export type UpsertNamedCheckInput = {
|
|
211
|
+
headSha: string;
|
|
212
|
+
/** The check's fixed name, e.g. MERGE_ORDER_CHECK_NAME ("wardby merge order"); identifies which run to update. */
|
|
213
|
+
name: string;
|
|
214
|
+
title: string;
|
|
215
|
+
summary: string;
|
|
216
|
+
} & ({
|
|
217
|
+
status: "in_progress";
|
|
218
|
+
} | {
|
|
219
|
+
status: "completed";
|
|
220
|
+
conclusion: "success" | "failure";
|
|
221
|
+
});
|
|
210
222
|
/**
|
|
211
223
|
* A user's effective permission on one repository, host-neutral and ranked
|
|
212
224
|
* none < read < triage < write < maintain < admin. GitHub maps its
|
|
@@ -222,6 +234,8 @@ export interface HostUser {
|
|
|
222
234
|
}
|
|
223
235
|
export interface CodeReviewHost {
|
|
224
236
|
readonly provider: ReviewHostProvider;
|
|
237
|
+
/** The GitHub App's owner account (always served; see WARDBY_GITHUB_ALLOWED_ACCOUNTS). Hosts without an App omit it. */
|
|
238
|
+
appOwnerLogin?(): Promise<string>;
|
|
225
239
|
/**
|
|
226
240
|
* The user's effective permission on `repository`, asked with the App's own
|
|
227
241
|
* installation credential (never a user token). Answers only for the user
|
|
@@ -274,6 +288,15 @@ export interface CodeReviewHost {
|
|
|
274
288
|
checkId: string;
|
|
275
289
|
}>;
|
|
276
290
|
completeCheck(repository: string, input: CompleteCheckInput): Promise<void>;
|
|
291
|
+
/**
|
|
292
|
+
* Creates or updates, by fixed name, a check run on one commit that isn't
|
|
293
|
+
* tied to a run's own startCheck/completeCheck lifecycle (e.g. the
|
|
294
|
+
* `wardby merge order` check, recomputed fresh and reposted on demand).
|
|
295
|
+
* Finds the one this App owns among the commit's check runs with that
|
|
296
|
+
* name and patches it; creates one when none exists. Hosts that can't
|
|
297
|
+
* post checks (e.g. a local repository) leave this out.
|
|
298
|
+
*/
|
|
299
|
+
upsertNamedCheck?(repository: string, input: UpsertNamedCheckInput): Promise<void>;
|
|
277
300
|
}
|
|
278
301
|
export type ReviewHostErrorCode = "host_not_installed" | "host_permission_missing" | "host_api_error" | "host_invalid_response";
|
|
279
302
|
export declare class ReviewHostError extends Error {
|
|
@@ -149,9 +149,16 @@ export interface RelatedPullRequestEntry {
|
|
|
149
149
|
state?: RelatedPullRequestState;
|
|
150
150
|
/** The pull request whose description this section is in. */
|
|
151
151
|
self?: boolean;
|
|
152
|
+
/**
|
|
153
|
+
* Step in the delegating agent's declared merge order (1-99), from
|
|
154
|
+
* CodingRun.mergeOrder; absent = the delegating agent set no order
|
|
155
|
+
* (an open entry is listed under "Not ordered:" when any other entry,
|
|
156
|
+
* open or done, has one).
|
|
157
|
+
*/
|
|
158
|
+
mergeOrder?: number;
|
|
152
159
|
}
|
|
153
160
|
export interface RelatedPullRequestsInput {
|
|
154
|
-
/** In
|
|
161
|
+
/** In merge order: by mergeOrder when set, else the order the request's coding runs were dispatched. */
|
|
155
162
|
entries: readonly RelatedPullRequestEntry[];
|
|
156
163
|
issue?: {
|
|
157
164
|
key: string;
|
|
@@ -162,9 +169,18 @@ export interface RelatedPullRequestsInput {
|
|
|
162
169
|
/**
|
|
163
170
|
* The "Related pull requests" block, markers included, built only from
|
|
164
171
|
* stored rows (never agent text). Open (and draft, and not-yet-known) pull
|
|
165
|
-
* requests form the
|
|
166
|
-
*
|
|
167
|
-
*
|
|
172
|
+
* requests form the merge order; merged and closed ones follow as context.
|
|
173
|
+
* Undefined when no other valid pull request is left after validation: a
|
|
174
|
+
* set of one is not a set.
|
|
175
|
+
*
|
|
176
|
+
* When any valid entry (open, merged or closed) carries a `mergeOrder` (set
|
|
177
|
+
* by the delegating agent), steps are counted over all of them, so a
|
|
178
|
+
* label never changes as pull requests merge: the open entries that have
|
|
179
|
+
* one are numbered under "Merge order" and labelled with their step (equal
|
|
180
|
+
* `mergeOrder` values share a step label), any open entry without one is
|
|
181
|
+
* listed after under "Not ordered:", and a merged or closed entry that has
|
|
182
|
+
* one carries its step label in the context list too. Otherwise every open
|
|
183
|
+
* entry is numbered in the unordered "Suggested merge order" list.
|
|
168
184
|
*/
|
|
169
185
|
export declare function renderRelatedSection(input: RelatedPullRequestsInput): string | undefined;
|
|
170
186
|
/**
|
|
@@ -228,11 +244,21 @@ export declare class GitHubAppClient implements GitHubRepositoryAccess {
|
|
|
228
244
|
*/
|
|
229
245
|
readFileAtRef(input: RepositoryFileInput): Promise<string | null>;
|
|
230
246
|
private identity?;
|
|
231
|
-
/**
|
|
232
|
-
|
|
247
|
+
/**
|
|
248
|
+
* The App's id, slug (its @-mention handle), and owner account, read once
|
|
249
|
+
* with the App JWT and memoized. `fresh` re-reads GET /app and replaces the
|
|
250
|
+
* memo (used before a destructive action, since the owner can change).
|
|
251
|
+
* A rejection is never cached.
|
|
252
|
+
*/
|
|
253
|
+
appIdentity(opts?: {
|
|
254
|
+
fresh?: boolean;
|
|
255
|
+
}): Promise<{
|
|
233
256
|
id: number;
|
|
234
257
|
slug: string;
|
|
258
|
+
ownerLogin: string;
|
|
235
259
|
}>;
|
|
260
|
+
/** Removes this App from an installation (an account outside the trusted set). */
|
|
261
|
+
deleteInstallation(installationId: number): Promise<void>;
|
|
236
262
|
upsertContinuationStatusComment(input: ContinuationStatusCommentInput): Promise<void>;
|
|
237
263
|
updateContinuationStatusComment(input: ContinuationStatusCommentInput): Promise<void>;
|
|
238
264
|
createContinuationCheckRun(input: ContinuationCheckRunInput): Promise<void>;
|