@wardby/cli 0.1.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 +131 -0
- package/LICENSE +202 -0
- package/NOTICE +2 -0
- package/README.md +282 -0
- package/bin/wardby.js +3 -0
- package/deploy/local/docker-compose.yml +19 -0
- package/dist/claude-coding-worker/driver.d.ts +70 -0
- package/dist/claude-coding-worker/driver.js +128 -0
- package/dist/claude-coding-worker/main.d.ts +2 -0
- package/dist/claude-coding-worker/main.js +41 -0
- package/dist/claude-coding-worker/sdk.d.ts +4 -0
- package/dist/claude-coding-worker/sdk.js +43 -0
- package/dist/claude-coding-worker/tool-relay.d.ts +1 -0
- package/dist/claude-coding-worker/tool-relay.js +9 -0
- package/dist/cli-help.d.ts +1 -0
- package/dist/cli-help.js +20 -0
- package/dist/cli.d.ts +18 -0
- package/dist/cli.js +629 -0
- package/dist/coding/observability.d.ts +59 -0
- package/dist/coding/observability.js +77 -0
- package/dist/coding/profile.d.ts +88 -0
- package/dist/coding/profile.js +137 -0
- package/dist/coding/protocol.d.ts +345 -0
- package/dist/coding/protocol.js +445 -0
- package/dist/coding/provider.d.ts +4 -0
- package/dist/coding/provider.js +18 -0
- package/dist/coding-proxy/main.d.ts +1 -0
- package/dist/coding-proxy/main.js +51 -0
- package/dist/coding-worker/artifact.d.ts +16 -0
- package/dist/coding-worker/artifact.js +37 -0
- package/dist/coding-worker/driver.d.ts +46 -0
- package/dist/coding-worker/driver.js +158 -0
- package/dist/coding-worker/errors.d.ts +1 -0
- package/dist/coding-worker/errors.js +23 -0
- package/dist/coding-worker/isolation-probe.d.ts +1 -0
- package/dist/coding-worker/isolation-probe.js +146 -0
- package/dist/coding-worker/keeper.d.ts +4 -0
- package/dist/coding-worker/keeper.js +29 -0
- package/dist/coding-worker/main.d.ts +2 -0
- package/dist/coding-worker/main.js +43 -0
- package/dist/coding-worker/sdk.d.ts +26 -0
- package/dist/coding-worker/sdk.js +28 -0
- package/dist/coding-worker/types.d.ts +64 -0
- package/dist/coding-worker/types.js +1 -0
- package/dist/config/providers.d.ts +158 -0
- package/dist/config/providers.js +162 -0
- package/dist/core/budget-groups.d.ts +72 -0
- package/dist/core/budget-groups.js +149 -0
- package/dist/core/budget.d.ts +63 -0
- package/dist/core/budget.js +69 -0
- package/dist/core/coding-queue.d.ts +32 -0
- package/dist/core/coding-queue.js +54 -0
- package/dist/core/cron.d.ts +31 -0
- package/dist/core/cron.js +43 -0
- package/dist/core/datastores.d.ts +41 -0
- package/dist/core/datastores.js +105 -0
- package/dist/core/db.d.ts +2 -0
- package/dist/core/db.js +2 -0
- package/dist/core/dispatch.d.ts +81 -0
- package/dist/core/dispatch.js +184 -0
- package/dist/core/engine-native.d.ts +29 -0
- package/dist/core/engine-native.js +259 -0
- package/dist/core/http-runtime.d.ts +7 -0
- package/dist/core/http-runtime.js +101 -0
- package/dist/core/lease.d.ts +13 -0
- package/dist/core/lease.js +22 -0
- package/dist/core/logger.d.ts +13 -0
- package/dist/core/logger.js +45 -0
- package/dist/core/memory-tools.d.ts +11 -0
- package/dist/core/memory-tools.js +122 -0
- package/dist/core/reconciler.d.ts +45 -0
- package/dist/core/reconciler.js +152 -0
- package/dist/core/runner.d.ts +87 -0
- package/dist/core/runner.js +529 -0
- package/dist/core/scheduler.d.ts +47 -0
- package/dist/core/scheduler.js +121 -0
- package/dist/core/secrets.d.ts +46 -0
- package/dist/core/secrets.js +100 -0
- package/dist/core/subagent-memory-tools.d.ts +18 -0
- package/dist/core/subagent-memory-tools.js +121 -0
- package/dist/core/timing.d.ts +7 -0
- package/dist/core/timing.js +7 -0
- package/dist/core/webhooks.d.ts +23 -0
- package/dist/core/webhooks.js +113 -0
- package/dist/env.d.ts +11 -0
- package/dist/env.js +13 -0
- package/dist/import/budgets.d.ts +16 -0
- package/dist/import/budgets.js +21 -0
- package/dist/import/bundle.d.ts +16 -0
- package/dist/import/bundle.js +61 -0
- package/dist/import/cli-args.d.ts +2 -0
- package/dist/import/cli-args.js +44 -0
- package/dist/import/create.d.ts +34 -0
- package/dist/import/create.js +363 -0
- package/dist/import/index.d.ts +25 -0
- package/dist/import/index.js +130 -0
- package/dist/import/model-gate.d.ts +1 -0
- package/dist/import/model-gate.js +3 -0
- package/dist/import/neutral-schema.d.ts +369 -0
- package/dist/import/neutral-schema.js +109 -0
- package/dist/import/preflight.d.ts +41 -0
- package/dist/import/preflight.js +71 -0
- package/dist/import/report.d.ts +5 -0
- package/dist/import/report.js +98 -0
- package/dist/import/tool-scan.d.ts +8 -0
- package/dist/import/tool-scan.js +20 -0
- package/dist/mcp/auth/ownership.d.ts +113 -0
- package/dist/mcp/auth/ownership.js +87 -0
- package/dist/mcp/auth/principal.d.ts +9 -0
- package/dist/mcp/auth/principal.js +9 -0
- package/dist/mcp/auth/resource-server.d.ts +47 -0
- package/dist/mcp/auth/resource-server.js +82 -0
- package/dist/mcp/auth/self-hosted/browser.d.ts +3 -0
- package/dist/mcp/auth/self-hosted/browser.js +273 -0
- package/dist/mcp/auth/self-hosted/cli.d.ts +2 -0
- package/dist/mcp/auth/self-hosted/cli.js +39 -0
- package/dist/mcp/auth/self-hosted/credentials.d.ts +58 -0
- package/dist/mcp/auth/self-hosted/credentials.js +106 -0
- package/dist/mcp/auth/self-hosted/rate-limit.d.ts +13 -0
- package/dist/mcp/auth/self-hosted/rate-limit.js +20 -0
- package/dist/mcp/auth/self-hosted/session.d.ts +36 -0
- package/dist/mcp/auth/self-hosted/session.js +110 -0
- package/dist/mcp/capabilities.d.ts +11 -0
- package/dist/mcp/capabilities.js +10 -0
- package/dist/mcp/context.d.ts +46 -0
- package/dist/mcp/context.js +1 -0
- package/dist/mcp/errors.d.ts +13 -0
- package/dist/mcp/errors.js +22 -0
- package/dist/mcp/index.d.ts +32 -0
- package/dist/mcp/index.js +187 -0
- package/dist/mcp/jsonrpc.d.ts +8 -0
- package/dist/mcp/jsonrpc.js +1 -0
- package/dist/mcp/server.d.ts +107 -0
- package/dist/mcp/server.js +203 -0
- package/dist/mcp/tasks/manager.d.ts +63 -0
- package/dist/mcp/tasks/manager.js +103 -0
- package/dist/mcp/tools/agents.d.ts +2 -0
- package/dist/mcp/tools/agents.js +375 -0
- package/dist/mcp/tools/budget-groups.d.ts +2 -0
- package/dist/mcp/tools/budget-groups.js +136 -0
- package/dist/mcp/tools/datastore.d.ts +2 -0
- package/dist/mcp/tools/datastore.js +217 -0
- package/dist/mcp/tools/memory.d.ts +3 -0
- package/dist/mcp/tools/memory.js +60 -0
- package/dist/mcp/tools/models.d.ts +8 -0
- package/dist/mcp/tools/models.js +15 -0
- package/dist/mcp/tools/runs.d.ts +2 -0
- package/dist/mcp/tools/runs.js +61 -0
- package/dist/mcp/tools/scheduling.d.ts +2 -0
- package/dist/mcp/tools/scheduling.js +48 -0
- package/dist/mcp/tools/secret-elicitation-form.d.ts +22 -0
- package/dist/mcp/tools/secret-elicitation-form.js +93 -0
- package/dist/mcp/tools/secret-elicitation-server.d.ts +5 -0
- package/dist/mcp/tools/secret-elicitation-server.js +57 -0
- package/dist/mcp/tools/secret-elicitation.d.ts +38 -0
- package/dist/mcp/tools/secret-elicitation.js +52 -0
- package/dist/mcp/tools/secrets.d.ts +13 -0
- package/dist/mcp/tools/secrets.js +124 -0
- package/dist/mcp/tools/subagents.d.ts +2 -0
- package/dist/mcp/tools/subagents.js +133 -0
- package/dist/mcp/tools/text-result.d.ts +6 -0
- package/dist/mcp/tools/text-result.js +3 -0
- package/dist/mcp/tools/tools.d.ts +2 -0
- package/dist/mcp/tools/tools.js +211 -0
- package/dist/mcp/tools/trigger.d.ts +2 -0
- package/dist/mcp/tools/trigger.js +120 -0
- package/dist/mcp/tools/webhooks.d.ts +2 -0
- package/dist/mcp/tools/webhooks.js +34 -0
- package/dist/mcp/transport/http-limits.d.ts +17 -0
- package/dist/mcp/transport/http-limits.js +92 -0
- package/dist/mcp/transport/stdio.d.ts +23 -0
- package/dist/mcp/transport/stdio.js +6 -0
- package/dist/mcp/transport/streamable-http.d.ts +34 -0
- package/dist/mcp/transport/streamable-http.js +158 -0
- package/dist/mcp/unattended-schedules.d.ts +15 -0
- package/dist/mcp/unattended-schedules.js +21 -0
- package/dist/mcp/webhooks/ingress.d.ts +17 -0
- package/dist/mcp/webhooks/ingress.js +33 -0
- package/dist/observability/config.d.ts +6 -0
- package/dist/observability/config.js +20 -0
- package/dist/observability/metrics-server.d.ts +11 -0
- package/dist/observability/metrics-server.js +47 -0
- package/dist/observability/metrics.d.ts +36 -0
- package/dist/observability/metrics.js +158 -0
- package/dist/providers/auth/authorization-server.d.ts +41 -0
- package/dist/providers/auth/authorization-server.js +1 -0
- package/dist/providers/auth/delegating.d.ts +44 -0
- package/dist/providers/auth/delegating.js +105 -0
- package/dist/providers/auth/index.d.ts +11 -0
- package/dist/providers/auth/index.js +20 -0
- package/dist/providers/auth/release-gate.d.ts +1 -0
- package/dist/providers/auth/release-gate.js +4 -0
- package/dist/providers/auth/self-hosted.d.ts +78 -0
- package/dist/providers/auth/self-hosted.js +377 -0
- package/dist/providers/auth/subject.d.ts +1 -0
- package/dist/providers/auth/subject.js +6 -0
- package/dist/providers/auth/types.d.ts +63 -0
- package/dist/providers/auth/types.js +22 -0
- package/dist/providers/coding-proxy/deny-port.d.ts +5 -0
- package/dist/providers/coding-proxy/deny-port.js +45 -0
- package/dist/providers/coding-proxy/environment-credentials.d.ts +7 -0
- package/dist/providers/coding-proxy/environment-credentials.js +16 -0
- package/dist/providers/coding-proxy/index.d.ts +7 -0
- package/dist/providers/coding-proxy/index.js +7 -0
- package/dist/providers/coding-proxy/memory-ledger.d.ts +17 -0
- package/dist/providers/coding-proxy/memory-ledger.js +103 -0
- package/dist/providers/coding-proxy/metering.d.ts +20 -0
- package/dist/providers/coding-proxy/metering.js +161 -0
- package/dist/providers/coding-proxy/prisma-ledger.d.ts +15 -0
- package/dist/providers/coding-proxy/prisma-ledger.js +213 -0
- package/dist/providers/coding-proxy/proxy.d.ts +75 -0
- package/dist/providers/coding-proxy/proxy.js +721 -0
- package/dist/providers/coding-proxy/runtime.d.ts +18 -0
- package/dist/providers/coding-proxy/runtime.js +39 -0
- package/dist/providers/coding-proxy/secure-fetch.d.ts +9 -0
- package/dist/providers/coding-proxy/secure-fetch.js +72 -0
- package/dist/providers/coding-proxy/server.d.ts +17 -0
- package/dist/providers/coding-proxy/server.js +139 -0
- package/dist/providers/coding-proxy/types.d.ts +99 -0
- package/dist/providers/coding-proxy/types.js +1 -0
- package/dist/providers/datastore/index.d.ts +2 -0
- package/dist/providers/datastore/index.js +2 -0
- package/dist/providers/datastore/postgres.d.ts +20 -0
- package/dist/providers/datastore/postgres.js +130 -0
- package/dist/providers/datastore/scoped.d.ts +10 -0
- package/dist/providers/datastore/scoped.js +46 -0
- package/dist/providers/datastore/types.d.ts +31 -0
- package/dist/providers/datastore/types.js +7 -0
- package/dist/providers/email/types.d.ts +46 -0
- package/dist/providers/email/types.js +8 -0
- package/dist/providers/engine/index.d.ts +1 -0
- package/dist/providers/engine/index.js +1 -0
- package/dist/providers/engine/types.d.ts +67 -0
- package/dist/providers/engine/types.js +16 -0
- package/dist/providers/executor/build.d.ts +6 -0
- package/dist/providers/executor/build.js +15 -0
- package/dist/providers/executor/composition.d.ts +14 -0
- package/dist/providers/executor/composition.js +123 -0
- package/dist/providers/executor/container.d.ts +184 -0
- package/dist/providers/executor/container.js +936 -0
- package/dist/providers/executor/dbos-status.d.ts +50 -0
- package/dist/providers/executor/dbos-status.js +64 -0
- package/dist/providers/executor/dbos.d.ts +53 -0
- package/dist/providers/executor/dbos.js +269 -0
- package/dist/providers/executor/in-process.d.ts +18 -0
- package/dist/providers/executor/in-process.js +38 -0
- package/dist/providers/executor/index.d.ts +8 -0
- package/dist/providers/executor/index.js +8 -0
- package/dist/providers/executor/routing.d.ts +48 -0
- package/dist/providers/executor/routing.js +70 -0
- package/dist/providers/executor/types.d.ts +55 -0
- package/dist/providers/executor/types.js +1 -0
- package/dist/providers/index.d.ts +43 -0
- package/dist/providers/index.js +18 -0
- package/dist/providers/jobs/docker-isolation.d.ts +166 -0
- package/dist/providers/jobs/docker-isolation.js +724 -0
- package/dist/providers/jobs/docker.d.ts +127 -0
- package/dist/providers/jobs/docker.js +1027 -0
- package/dist/providers/jobs/fake-kubernetes-api.d.ts +98 -0
- package/dist/providers/jobs/fake-kubernetes-api.js +142 -0
- package/dist/providers/jobs/fake.d.ts +23 -0
- package/dist/providers/jobs/fake.js +132 -0
- package/dist/providers/jobs/kubernetes-api.d.ts +60 -0
- package/dist/providers/jobs/kubernetes-api.js +20 -0
- package/dist/providers/jobs/kubernetes-client.d.ts +36 -0
- package/dist/providers/jobs/kubernetes-client.js +171 -0
- package/dist/providers/jobs/kubernetes-dry-run-fixture.d.ts +49 -0
- package/dist/providers/jobs/kubernetes-dry-run-fixture.js +141 -0
- package/dist/providers/jobs/kubernetes-isolation.d.ts +147 -0
- package/dist/providers/jobs/kubernetes-isolation.js +607 -0
- package/dist/providers/jobs/kubernetes-platform.d.ts +177 -0
- package/dist/providers/jobs/kubernetes-platform.js +280 -0
- package/dist/providers/jobs/kubernetes-preflight.d.ts +59 -0
- package/dist/providers/jobs/kubernetes-preflight.js +339 -0
- package/dist/providers/jobs/kubernetes-witness.d.ts +10 -0
- package/dist/providers/jobs/kubernetes-witness.js +193 -0
- package/dist/providers/jobs/kubernetes.d.ts +127 -0
- package/dist/providers/jobs/kubernetes.js +874 -0
- package/dist/providers/jobs/safe-extract.d.ts +9 -0
- package/dist/providers/jobs/safe-extract.js +325 -0
- package/dist/providers/jobs/types.d.ts +78 -0
- package/dist/providers/jobs/types.js +6 -0
- package/dist/providers/jobs/workspace-swap.d.ts +2 -0
- package/dist/providers/jobs/workspace-swap.js +47 -0
- package/dist/providers/llm/anthropic.d.ts +13 -0
- package/dist/providers/llm/anthropic.js +21 -0
- package/dist/providers/llm/bedrock.d.ts +19 -0
- package/dist/providers/llm/bedrock.js +33 -0
- package/dist/providers/llm/claude-messages.d.ts +106 -0
- package/dist/providers/llm/claude-messages.js +157 -0
- package/dist/providers/llm/claude-provider.d.ts +40 -0
- package/dist/providers/llm/claude-provider.js +31 -0
- package/dist/providers/llm/index.d.ts +10 -0
- package/dist/providers/llm/index.js +10 -0
- package/dist/providers/llm/openai.d.ts +23 -0
- package/dist/providers/llm/openai.js +156 -0
- package/dist/providers/llm/pricing-anthropic.d.ts +4 -0
- package/dist/providers/llm/pricing-anthropic.js +30 -0
- package/dist/providers/llm/pricing-bedrock-claude.d.ts +12 -0
- package/dist/providers/llm/pricing-bedrock-claude.js +37 -0
- package/dist/providers/llm/pricing-core.d.ts +26 -0
- package/dist/providers/llm/pricing-core.js +17 -0
- package/dist/providers/llm/pricing.d.ts +30 -0
- package/dist/providers/llm/pricing.js +74 -0
- package/dist/providers/llm/registration.d.ts +8 -0
- package/dist/providers/llm/registration.js +39 -0
- package/dist/providers/llm/routing.d.ts +25 -0
- package/dist/providers/llm/routing.js +32 -0
- package/dist/providers/llm/types.d.ts +85 -0
- package/dist/providers/llm/types.js +12 -0
- package/dist/providers/memory/index.d.ts +2 -0
- package/dist/providers/memory/index.js +2 -0
- package/dist/providers/memory/postgres.d.ts +20 -0
- package/dist/providers/memory/postgres.js +72 -0
- package/dist/providers/memory/types.d.ts +32 -0
- package/dist/providers/memory/types.js +17 -0
- package/dist/providers/secrets/app-key.d.ts +8 -0
- package/dist/providers/secrets/app-key.js +50 -0
- package/dist/providers/secrets/index.d.ts +10 -0
- package/dist/providers/secrets/index.js +13 -0
- package/dist/providers/secrets/transfer-envelope.d.ts +12 -0
- package/dist/providers/secrets/transfer-envelope.js +35 -0
- package/dist/providers/secrets/types.d.ts +18 -0
- package/dist/providers/secrets/types.js +12 -0
- package/dist/providers/storage/types.d.ts +18 -0
- package/dist/providers/storage/types.js +7 -0
- package/dist/providers/vcs/git.d.ts +105 -0
- package/dist/providers/vcs/git.js +710 -0
- package/dist/providers/vcs/github.d.ts +105 -0
- package/dist/providers/vcs/github.js +394 -0
- package/dist/providers/vcs/index.d.ts +6 -0
- package/dist/providers/vcs/index.js +26 -0
- package/dist/providers/vcs/types.d.ts +118 -0
- package/dist/providers/vcs/types.js +1 -0
- package/dist/sandbox/bounded-json.d.ts +2 -0
- package/dist/sandbox/bounded-json.js +36 -0
- package/dist/sandbox/bridge.d.ts +15 -0
- package/dist/sandbox/bridge.js +78 -0
- package/dist/sandbox/eval-core.d.ts +37 -0
- package/dist/sandbox/eval-core.js +159 -0
- package/dist/sandbox/fetch-policy.d.ts +24 -0
- package/dist/sandbox/fetch-policy.js +96 -0
- package/dist/sandbox/generated/zod-to-json-schema.bundle.js +5 -0
- package/dist/sandbox/generated/zod.bundle.js +1 -0
- package/dist/sandbox/host-functions.d.ts +29 -0
- package/dist/sandbox/host-functions.js +168 -0
- package/dist/sandbox/limits.d.ts +39 -0
- package/dist/sandbox/limits.js +39 -0
- package/dist/sandbox/parser-worker/__fixtures__/crash.d.ts +1 -0
- package/dist/sandbox/parser-worker/__fixtures__/crash.js +6 -0
- package/dist/sandbox/parser-worker/__fixtures__/echo.d.ts +1 -0
- package/dist/sandbox/parser-worker/__fixtures__/echo.js +5 -0
- package/dist/sandbox/parser-worker/__fixtures__/reject.d.ts +1 -0
- package/dist/sandbox/parser-worker/__fixtures__/reject.js +5 -0
- package/dist/sandbox/parser-worker/__fixtures__/spin-forever.d.ts +1 -0
- package/dist/sandbox/parser-worker/__fixtures__/spin-forever.js +11 -0
- package/dist/sandbox/parser-worker/pool.d.ts +13 -0
- package/dist/sandbox/parser-worker/pool.js +111 -0
- package/dist/sandbox/parser-worker/worker.d.ts +14 -0
- package/dist/sandbox/parser-worker/worker.js +63 -0
- package/dist/sandbox/pii-redaction.d.ts +1 -0
- package/dist/sandbox/pii-redaction.js +29 -0
- package/dist/sandbox/prelude.d.ts +26 -0
- package/dist/sandbox/prelude.js +408 -0
- package/dist/sandbox/quickjs-module.d.ts +13 -0
- package/dist/sandbox/quickjs-module.js +19 -0
- package/dist/sandbox/run-in-sandbox.d.ts +34 -0
- package/dist/sandbox/run-in-sandbox.js +53 -0
- package/dist/sandbox/safe-fetch.d.ts +26 -0
- package/dist/sandbox/safe-fetch.js +140 -0
- package/dist/sandbox/tool-capabilities.d.ts +66 -0
- package/dist/sandbox/tool-capabilities.js +103 -0
- package/dist/sandbox/zod-params.d.ts +22 -0
- package/dist/sandbox/zod-params.js +60 -0
- package/dist/serve.d.ts +14 -0
- package/dist/serve.js +54 -0
- package/dist/wardby-bin.d.ts +2 -0
- package/dist/wardby-bin.js +20 -0
- package/package.json +123 -0
- package/prisma/migrations/20260905000000_init/migration.sql +34 -0
- package/prisma/migrations/20260905010000_scheduler_durability/migration.sql +31 -0
- package/prisma/migrations/20260905020000_tools_multiturn/migration.sql +47 -0
- package/prisma/migrations/20260905030000_cache_tool_json_schema/migration.sql +13 -0
- package/prisma/migrations/20260906000000_mcp_phase4/migration.sql +115 -0
- package/prisma/migrations/20260906010000_run_final_text/migration.sql +7 -0
- package/prisma/migrations/20260906020000_run_turns/migration.sql +6 -0
- package/prisma/migrations/20260906030000_task_principal_id/migration.sql +5 -0
- package/prisma/migrations/20260906040000_secure_self_hosted_oauth/migration.sql +67 -0
- package/prisma/migrations/20260906050000_phase5_coding_agents/migration.sql +52 -0
- package/prisma/migrations/20260906060000_tool_capability_scoping/migration.sql +22 -0
- package/prisma/migrations/20260906070000_datastore_pii_flag/migration.sql +6 -0
- package/prisma/migrations/20260906080000_secret_elicitation_outcomes/migration.sql +13 -0
- package/prisma/migrations/20260907010000_coding_proxy_ledger/migration.sql +57 -0
- package/prisma/migrations/20260907020000_budget_groups/migration.sql +24 -0
- package/prisma/migrations/20260907030000_coding_webhook_task_override/migration.sql +4 -0
- package/prisma/migrations/20260908010000_run_execution_backend/migration.sql +2 -0
- package/prisma/migrations/20260908020000_coding_run_audit_metadata/migration.sql +7 -0
- package/prisma/migrations/20260910010000_agent_secret_bound_name/migration.sql +16 -0
- package/prisma/migrations/20260911010000_coding_worker_toolchains/migration.sql +5 -0
- package/prisma/migrations/20260912010000_coding_provider_contract/migration.sql +24 -0
- package/prisma/migrations/20260912020000_coding_proxy_protocol/migration.sql +10 -0
- package/prisma/migrations/20260912030000_agent_memory/migration.sql +18 -0
- package/prisma/migrations/20260912040000_named_shared_datastores/migration.sql +68 -0
- package/prisma/migrations/20260913000000_run_trigger_webhook/migration.sql +2 -0
- package/prisma/migrations/20260913010000_agent_sub_agent/migration.sql +32 -0
- package/prisma/migrations/20260913020000_run_task_override/migration.sql +5 -0
- package/prisma/migrations/20260913030000_coding_run_root/migration.sql +11 -0
- package/prisma/migrations/20260922010000_coding_run_queued_at/migration.sql +8 -0
- package/prisma/migrations/20260922020000_coding_workspace_disk/migration.sql +8 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +723 -0
- package/scripts/git-askpass.sh +11 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform profiles for the Kubernetes coding launcher.
|
|
3
|
+
*
|
|
4
|
+
* A profile is pure data plus two pure functions. It answers exactly two
|
|
5
|
+
* questions, and a reviewer can read the full answer to both in this file:
|
|
6
|
+
*
|
|
7
|
+
* 1. What resource values must the builder emit so the platform's admission
|
|
8
|
+
* controller has nothing left to rewrite? (`conformResources`)
|
|
9
|
+
* 2. What does the platform stamp on the pod anyway, and is it in a named,
|
|
10
|
+
* bounded list? (`normalizePlatformMetadata`)
|
|
11
|
+
*
|
|
12
|
+
* Profiles narrow what counts as an *expected* difference. They never disable
|
|
13
|
+
* the comparison: `normalizePlatformMetadata` deletes named keys from both the
|
|
14
|
+
* submitted and the read-back pod, symmetrically, and everything else is still
|
|
15
|
+
* deep-compared by kubernetes-isolation.ts. The `generic` profile's lists are
|
|
16
|
+
* empty, so it tolerates nothing beyond what shipped before profiles existed.
|
|
17
|
+
*
|
|
18
|
+
* PROVENANCE OF THE AUTOPILOT NUMBERS. The resource band (`resources`, below)
|
|
19
|
+
* was originally DOCUMENTATION-SOURCED ONLY, from Google's
|
|
20
|
+
* autopilot-resource-requests and autopilot-security pages. It, and the
|
|
21
|
+
* `metadata` allowance, were both confirmed by a real server-side dry run
|
|
22
|
+
* against GKE Autopilot 1.35.8-gke.1036000 (wardby-phase12 cluster,
|
|
23
|
+
* 2026-09-23): a pod built with the numbers below came back with zero
|
|
24
|
+
* resource mutations, and the annotation-prefix, toleration and nodeSelector
|
|
25
|
+
* comments in `GKE_AUTOPILOT.metadata` cite exactly what that same dry run
|
|
26
|
+
* added. `fixtures/gke-autopilot-dry-run.json` now carries that capture with
|
|
27
|
+
* `provisional: false`; kubernetes-autopilot-attestation.test.ts's PENDING
|
|
28
|
+
* test names the state before a real capture existed, in case this ever
|
|
29
|
+
* regresses to a documentation-only fixture again.
|
|
30
|
+
*/
|
|
31
|
+
import type { V1PodSpec, V1Toleration } from "@kubernetes/client-node";
|
|
32
|
+
export type KubernetesPlatform = "generic" | "gke-autopilot";
|
|
33
|
+
export declare const KUBERNETES_PLATFORMS: readonly KubernetesPlatform[];
|
|
34
|
+
export declare const KUBERNETES_PLATFORM_ERROR = "kubernetes_platform_unconformable";
|
|
35
|
+
/** A request the platform cannot legally run, refused here instead of being silently adjusted by the platform. */
|
|
36
|
+
export declare class KubernetesPlatformError extends Error {
|
|
37
|
+
constructor(detail: string);
|
|
38
|
+
}
|
|
39
|
+
export declare const GVISOR_RUNTIME_CLASS = "gvisor";
|
|
40
|
+
/** Ephemeral storage the init container needs to create the three subPath roots. */
|
|
41
|
+
export declare const STORAGE_INIT_EPHEMERAL_MIB = 64;
|
|
42
|
+
/** Ephemeral storage reserved for the worker's writable layer (/tmp and /home are memory-backed). */
|
|
43
|
+
export declare const WORKER_EPHEMERAL_MIB = 1024;
|
|
44
|
+
export interface PlatformResourceRules {
|
|
45
|
+
/** Smallest CPU request the platform accepts, in millicores. */
|
|
46
|
+
cpuFloorMillicores: number;
|
|
47
|
+
/** Granularity the platform rounds CPU up to, in millicores. */
|
|
48
|
+
cpuIncrementMillicores: number;
|
|
49
|
+
/** Lower edge of the memory:CPU band, in MiB of memory per whole CPU. */
|
|
50
|
+
memoryPerCpuMinMib: number;
|
|
51
|
+
/** Upper edge of the memory:CPU band, in MiB of memory per whole CPU. */
|
|
52
|
+
memoryPerCpuMaxMib: number;
|
|
53
|
+
/** Ceiling on the pod's total ephemeral storage, in MiB; undefined means the platform imposes none. */
|
|
54
|
+
ephemeralStorageCeilingMib?: number;
|
|
55
|
+
/** Whether the builder must emit explicit ephemeral-storage requests/limits on every container. */
|
|
56
|
+
explicitEphemeralStorage: boolean;
|
|
57
|
+
}
|
|
58
|
+
export interface PlatformMetadataAllowance {
|
|
59
|
+
/** Annotation key prefixes the platform's admission controller adds. */
|
|
60
|
+
podAnnotationKeyPrefixes: readonly string[];
|
|
61
|
+
/**
|
|
62
|
+
* Label key prefixes the platform adds. Note run-pod labels are also the run
|
|
63
|
+
* NetworkPolicy's podSelector: forgiving an *added* label is safe because it
|
|
64
|
+
* cannot remove wardby's own labels, which still match the policy. A changed
|
|
65
|
+
* or missing wardby label is a mismatch and still fails.
|
|
66
|
+
*/
|
|
67
|
+
podLabelKeyPrefixes: readonly string[];
|
|
68
|
+
/** Tolerations the platform's admission controller adds, matched field for field. */
|
|
69
|
+
tolerations: readonly V1Toleration[];
|
|
70
|
+
/** nodeSelector entries the platform adds, matched key and value. */
|
|
71
|
+
nodeSelector: Readonly<Record<string, string>>;
|
|
72
|
+
/** True when the platform strips the pod-level seccompProfile (sandboxed pods are exempt from its default). */
|
|
73
|
+
dropsPodSeccompProfile: boolean;
|
|
74
|
+
}
|
|
75
|
+
export interface KubernetesPlatformProfile {
|
|
76
|
+
name: KubernetesPlatform;
|
|
77
|
+
resources: PlatformResourceRules;
|
|
78
|
+
metadata: PlatformMetadataAllowance;
|
|
79
|
+
/** gVisor is mandatory: an unset or different runtime class is refused at startup, not warned about. */
|
|
80
|
+
requiresGvisor: boolean;
|
|
81
|
+
}
|
|
82
|
+
export declare function platformProfile(name: KubernetesPlatform): KubernetesPlatformProfile;
|
|
83
|
+
export interface ContainerResourceRequest {
|
|
84
|
+
cpuMillicores: number;
|
|
85
|
+
memoryMib: number;
|
|
86
|
+
/** Required when the profile sets explicitEphemeralStorage. */
|
|
87
|
+
ephemeralStorageMib?: number;
|
|
88
|
+
}
|
|
89
|
+
export interface ContainerResources {
|
|
90
|
+
requests: Record<string, string>;
|
|
91
|
+
limits: Record<string, string>;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The resource block to emit for one container: the smallest values that both
|
|
95
|
+
* satisfy the request and are already legal on the platform, so nothing is
|
|
96
|
+
* rewritten after submission. Limits always equal requests (Autopilot sets them
|
|
97
|
+
* equal anyway, and every wardby container has always been emitted that way).
|
|
98
|
+
*
|
|
99
|
+
* Conformance is monotone in one pass: CPU only ever rises (to the floor, to the
|
|
100
|
+
* increment, and to whatever the memory:CPU ceiling demands), and raising CPU
|
|
101
|
+
* only ever raises the ceiling and the floor it must satisfy.
|
|
102
|
+
*
|
|
103
|
+
* Per-container conformance is enough for the pod as a whole: multiples of the
|
|
104
|
+
* CPU increment sum to a multiple of the increment, and the sum of several
|
|
105
|
+
* ratios inside a band stays inside that band.
|
|
106
|
+
*
|
|
107
|
+
* Every dimension asked for must be a finite positive number; a garbage value is
|
|
108
|
+
* refused here rather than stringified into a quantity the API server would
|
|
109
|
+
* reject (or, worse, accept). What can be range-checked for one container is:
|
|
110
|
+
* its ephemeral storage cannot alone exceed the platform's whole-pod ceiling.
|
|
111
|
+
* The *summed* pod total against that ceiling is the caller's check, since only
|
|
112
|
+
* the caller sees every container.
|
|
113
|
+
*
|
|
114
|
+
* Note `generic` deliberately DROPS a supplied `ephemeralStorageMib`: it sets
|
|
115
|
+
* `explicitEphemeralStorage: false`, and emitting an ephemeral-storage request
|
|
116
|
+
* a generic cluster never asked for would change the pod that ships today. The
|
|
117
|
+
* value is still validated before it is discarded.
|
|
118
|
+
*/
|
|
119
|
+
export declare function conformResources(profile: KubernetesPlatformProfile, request: ContainerResourceRequest): ContainerResources;
|
|
120
|
+
/**
|
|
121
|
+
* The pod's total ephemeral storage: max(init containers) + sum(regular containers).
|
|
122
|
+
* storage-init's 64 MiB never exceeds keeper + worker, so the total is the keeper's
|
|
123
|
+
* storage volume plus the worker's reservation.
|
|
124
|
+
*/
|
|
125
|
+
export declare function podEphemeralStorageMib(diskMb: number): number;
|
|
126
|
+
/**
|
|
127
|
+
* Renders a MiB figure with its GiB equivalent, e.g. `10240 MiB (10 GiB)`. Both
|
|
128
|
+
* places that report the ephemeral-storage ceiling use this rather than writing
|
|
129
|
+
* the GiB figure out by hand, so a profile with a different ceiling cannot end
|
|
130
|
+
* up described by a parenthetical that was only ever true for Autopilot's.
|
|
131
|
+
*/
|
|
132
|
+
export declare function describeMib(mib: number): string;
|
|
133
|
+
/**
|
|
134
|
+
* Exported for src/tools/capture-fixture.ts: the fingerprint check that gates a capture needs to
|
|
135
|
+
* recognize the same "platform added this toleration" match the attestation comparison uses, so
|
|
136
|
+
* the two decisions cannot silently diverge.
|
|
137
|
+
*/
|
|
138
|
+
export declare function tolerationMatches(actual: V1Toleration, allowed: V1Toleration): boolean;
|
|
139
|
+
/**
|
|
140
|
+
* Deletes exactly the metadata this platform is known to add. Called on BOTH
|
|
141
|
+
* sides of the comparison: the submitted pod never carries any of it, so the
|
|
142
|
+
* deletion is a no-op there, and the read-back pod loses only what is named
|
|
143
|
+
* here. Nothing else is touched, so any other difference still fails the run.
|
|
144
|
+
*
|
|
145
|
+
* Every branch is gated on the profile naming something, so a profile with an
|
|
146
|
+
* empty allowance — `generic` — provably leaves the view's contents alone.
|
|
147
|
+
*
|
|
148
|
+
* SYMMETRY CAVEAT — deleting from both sides forgives an *addition* only while
|
|
149
|
+
* the builder never emits an allowed key itself. If buildRunPod ever emitted,
|
|
150
|
+
* say, the gVisor nodeSelector, the deletion would land on the submitted side
|
|
151
|
+
* too and the platform *removing* it would become equally invisible. Anything
|
|
152
|
+
* named in an allowance list must therefore stay something wardby never sets.
|
|
153
|
+
*
|
|
154
|
+
* OWNERSHIP — this matters at the call site. `view.labels` and `view.annotations`
|
|
155
|
+
* are REPLACED with copies before anything is deleted, so a caller that built the
|
|
156
|
+
* view straight out of a live `V1Pod` (`normalizePod` structuredClones only the
|
|
157
|
+
* spec and hands back the pod's own `metadata.labels`/`metadata.annotations`
|
|
158
|
+
* references) does not have keys deleted out from under its real pod objects.
|
|
159
|
+
* Read the normalized bags back off `view` after the call, never off the pod.
|
|
160
|
+
* `view.spec`, by contrast, is mutated in place and the caller MUST pass a spec
|
|
161
|
+
* it already owns — `normalizePod`'s structuredClone is what makes that true.
|
|
162
|
+
*/
|
|
163
|
+
export declare function normalizePlatformMetadata(profile: KubernetesPlatformProfile, view: {
|
|
164
|
+
labels: Record<string, string>;
|
|
165
|
+
annotations: Record<string, string>;
|
|
166
|
+
spec: V1PodSpec;
|
|
167
|
+
}): void;
|
|
168
|
+
export interface PlatformConfigCheck {
|
|
169
|
+
runtimeClassName?: string;
|
|
170
|
+
/** The effective CODING_MAX_DISK_MB: the largest workspace an agents:write caller may request. */
|
|
171
|
+
maxDiskMb: number;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Refuses a configuration that cannot work on this platform, at startup rather
|
|
175
|
+
* than at the first coding run. Each message names the setting and the limit.
|
|
176
|
+
*/
|
|
177
|
+
export declare function assertPlatformConfig(profile: KubernetesPlatformProfile, config: PlatformConfigCheck): void;
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
export const KUBERNETES_PLATFORMS = Object.freeze(["generic", "gke-autopilot"]);
|
|
2
|
+
export const KUBERNETES_PLATFORM_ERROR = "kubernetes_platform_unconformable";
|
|
3
|
+
/** A request the platform cannot legally run, refused here instead of being silently adjusted by the platform. */
|
|
4
|
+
export class KubernetesPlatformError extends Error {
|
|
5
|
+
constructor(detail) {
|
|
6
|
+
super(`${KUBERNETES_PLATFORM_ERROR}: ${detail}`);
|
|
7
|
+
this.name = "KubernetesPlatformError";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export const GVISOR_RUNTIME_CLASS = "gvisor";
|
|
11
|
+
/** Ephemeral storage the init container needs to create the three subPath roots. */
|
|
12
|
+
export const STORAGE_INIT_EPHEMERAL_MIB = 64;
|
|
13
|
+
/** Ephemeral storage reserved for the worker's writable layer (/tmp and /home are memory-backed). */
|
|
14
|
+
export const WORKER_EPHEMERAL_MIB = 1024;
|
|
15
|
+
/**
|
|
16
|
+
* Deep-freezes a profile. An allowance list is the set of differences attestation
|
|
17
|
+
* will forgive: nothing at runtime — a later module, a test helper, a mistaken
|
|
18
|
+
* `push` — may widen it after this module is loaded. Frozen in place rather than
|
|
19
|
+
* copied so `platformProfile` keeps returning the one canonical object.
|
|
20
|
+
*/
|
|
21
|
+
function freezeProfile(profile) {
|
|
22
|
+
Object.freeze(profile.resources);
|
|
23
|
+
Object.freeze(profile.metadata.podAnnotationKeyPrefixes);
|
|
24
|
+
Object.freeze(profile.metadata.podLabelKeyPrefixes);
|
|
25
|
+
for (const toleration of profile.metadata.tolerations)
|
|
26
|
+
Object.freeze(toleration);
|
|
27
|
+
Object.freeze(profile.metadata.tolerations);
|
|
28
|
+
Object.freeze(profile.metadata.nodeSelector);
|
|
29
|
+
Object.freeze(profile.metadata);
|
|
30
|
+
return Object.freeze(profile);
|
|
31
|
+
}
|
|
32
|
+
const NO_METADATA = {
|
|
33
|
+
podAnnotationKeyPrefixes: [],
|
|
34
|
+
podLabelKeyPrefixes: [],
|
|
35
|
+
tolerations: [],
|
|
36
|
+
nodeSelector: {},
|
|
37
|
+
dropsPodSeccompProfile: false,
|
|
38
|
+
};
|
|
39
|
+
const GENERIC = {
|
|
40
|
+
name: "generic",
|
|
41
|
+
resources: {
|
|
42
|
+
cpuFloorMillicores: 0,
|
|
43
|
+
cpuIncrementMillicores: 1,
|
|
44
|
+
memoryPerCpuMinMib: 0,
|
|
45
|
+
memoryPerCpuMaxMib: Number.POSITIVE_INFINITY,
|
|
46
|
+
explicitEphemeralStorage: false,
|
|
47
|
+
},
|
|
48
|
+
metadata: NO_METADATA,
|
|
49
|
+
requiresGvisor: false,
|
|
50
|
+
};
|
|
51
|
+
const GKE_AUTOPILOT = {
|
|
52
|
+
name: "gke-autopilot",
|
|
53
|
+
resources: {
|
|
54
|
+
// General-purpose compute class: 0.25 vCPU minimum, 0.25 vCPU increments,
|
|
55
|
+
// memory between 1 GiB and 6.5 GiB per vCPU, 10 GiB of ephemeral storage per Pod.
|
|
56
|
+
// These are Autopilot's NON-bursting numbers, chosen deliberately: they are the
|
|
57
|
+
// stricter set, so a pod conformed to them is legal on a bursting cluster too,
|
|
58
|
+
// while the reverse would not hold.
|
|
59
|
+
cpuFloorMillicores: 250,
|
|
60
|
+
cpuIncrementMillicores: 250,
|
|
61
|
+
memoryPerCpuMinMib: 1024,
|
|
62
|
+
memoryPerCpuMaxMib: 6656,
|
|
63
|
+
ephemeralStorageCeilingMib: 10 * 1024,
|
|
64
|
+
explicitEphemeralStorage: true,
|
|
65
|
+
},
|
|
66
|
+
metadata: {
|
|
67
|
+
// autopilot.gke.io/* carries the resource-adjustment record and the warden version — that is
|
|
68
|
+
// Google's documented signal, but a real server-side dry run against GKE Autopilot
|
|
69
|
+
// 1.35.8-gke.1036000 (measured 2026-09-23, wardby-phase12 cluster) came back with NO
|
|
70
|
+
// autopilot.gke.io/* annotation of any kind on an already-conforming sandboxed pod. Kept here
|
|
71
|
+
// anyway (harmless if it never matches) in case a different Autopilot version reinstates it.
|
|
72
|
+
// dev.gvisor.* is what that same dry run actually added: one
|
|
73
|
+
// `dev.gvisor.internal.seccomp.<container>` annotation per container (recording gVisor's own
|
|
74
|
+
// RuntimeDefault seccomp default, since containerSecurity() only sets seccompProfile at the pod
|
|
75
|
+
// level) and one `dev.gvisor.spec.mount.<volume>.*` triple per Memory-medium emptyDir volume
|
|
76
|
+
// (recording gVisor's internal tmpfs translation of the "tmp"/"home" scratch volumes). Both are
|
|
77
|
+
// gVisor's own bookkeeping of what wardby already declared, not an admission rewrite.
|
|
78
|
+
podAnnotationKeyPrefixes: ["autopilot.gke.io/", "dev.gvisor."],
|
|
79
|
+
// topology.kubernetes.io/{region,zone} are stamped onto the pod by GKE *after binding*, from the
|
|
80
|
+
// node it landed on. That timing is the point: the dry-run capture tool never schedules anything,
|
|
81
|
+
// so no fixture can ever list this mutation, and the first thing that saw it was a real launch on
|
|
82
|
+
// Autopilot failing attestation with a bare kubernetes_isolation_unsupported (measured 2026-09-23,
|
|
83
|
+
// wardby-phase12). Treat the fixture as a lower bound on what a platform mutates, never a complete
|
|
84
|
+
// one. Forgiving an added label is safe for the reason in PlatformMetadataAllowance — wardby's own
|
|
85
|
+
// labels must still be present and unchanged, so the run NetworkPolicy still selects this pod —
|
|
86
|
+
// and an added label that made the pod match some *other*, more permissive policy would still not
|
|
87
|
+
// open the gate: the enforcement witness probes the real dataplane, and re-probes it immediately
|
|
88
|
+
// before the marker write.
|
|
89
|
+
podLabelKeyPrefixes: ["autopilot.gke.io/", "topology.kubernetes.io/"],
|
|
90
|
+
// GKE adds the gVisor toleration itself for a pod with runtimeClassName: gvisor, plus
|
|
91
|
+
// kubernetes.io/arch (also measured on the same real dry run, 2026-09-23): every Autopilot node
|
|
92
|
+
// pool tolerates architecture, so it stamps this on any pod without an explicit arch selector.
|
|
93
|
+
// Hardcoded to amd64 because that is what the current worker images and node pools are; if
|
|
94
|
+
// wardby ever runs on arm64 Autopilot nodes this needs a second capture and a second entry.
|
|
95
|
+
tolerations: [
|
|
96
|
+
{ key: "sandbox.gke.io/runtime", operator: "Equal", value: GVISOR_RUNTIME_CLASS, effect: "NoSchedule" },
|
|
97
|
+
{ key: "kubernetes.io/arch", operator: "Equal", value: "amd64", effect: "NoSchedule" },
|
|
98
|
+
],
|
|
99
|
+
nodeSelector: { "sandbox.gke.io/runtime": GVISOR_RUNTIME_CLASS },
|
|
100
|
+
// We set seccompProfile: RuntimeDefault ourselves, and the sandbox exemption only means Autopilot
|
|
101
|
+
// does not *add* one — so there is nothing to forgive. If a capture ever shows the field stripped,
|
|
102
|
+
// flip this to true rather than widening anything else.
|
|
103
|
+
dropsPodSeccompProfile: false,
|
|
104
|
+
},
|
|
105
|
+
requiresGvisor: true,
|
|
106
|
+
};
|
|
107
|
+
const PROFILES = Object.freeze({
|
|
108
|
+
generic: freezeProfile(GENERIC),
|
|
109
|
+
"gke-autopilot": freezeProfile(GKE_AUTOPILOT),
|
|
110
|
+
});
|
|
111
|
+
export function platformProfile(name) {
|
|
112
|
+
const profile = PROFILES[name];
|
|
113
|
+
if (!profile)
|
|
114
|
+
throw new KubernetesPlatformError(`unknown platform ${String(name)}`);
|
|
115
|
+
return profile;
|
|
116
|
+
}
|
|
117
|
+
/** Refuses NaN, Infinity, 0 and negatives before they are stringified into a Kubernetes quantity. */
|
|
118
|
+
function requirePositiveMib(profile, field, value) {
|
|
119
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
120
|
+
throw new KubernetesPlatformError(`platform ${profile.name} was asked for ${field}=${String(value)}; it must be a finite positive number`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The resource block to emit for one container: the smallest values that both
|
|
125
|
+
* satisfy the request and are already legal on the platform, so nothing is
|
|
126
|
+
* rewritten after submission. Limits always equal requests (Autopilot sets them
|
|
127
|
+
* equal anyway, and every wardby container has always been emitted that way).
|
|
128
|
+
*
|
|
129
|
+
* Conformance is monotone in one pass: CPU only ever rises (to the floor, to the
|
|
130
|
+
* increment, and to whatever the memory:CPU ceiling demands), and raising CPU
|
|
131
|
+
* only ever raises the ceiling and the floor it must satisfy.
|
|
132
|
+
*
|
|
133
|
+
* Per-container conformance is enough for the pod as a whole: multiples of the
|
|
134
|
+
* CPU increment sum to a multiple of the increment, and the sum of several
|
|
135
|
+
* ratios inside a band stays inside that band.
|
|
136
|
+
*
|
|
137
|
+
* Every dimension asked for must be a finite positive number; a garbage value is
|
|
138
|
+
* refused here rather than stringified into a quantity the API server would
|
|
139
|
+
* reject (or, worse, accept). What can be range-checked for one container is:
|
|
140
|
+
* its ephemeral storage cannot alone exceed the platform's whole-pod ceiling.
|
|
141
|
+
* The *summed* pod total against that ceiling is the caller's check, since only
|
|
142
|
+
* the caller sees every container.
|
|
143
|
+
*
|
|
144
|
+
* Note `generic` deliberately DROPS a supplied `ephemeralStorageMib`: it sets
|
|
145
|
+
* `explicitEphemeralStorage: false`, and emitting an ephemeral-storage request
|
|
146
|
+
* a generic cluster never asked for would change the pod that ships today. The
|
|
147
|
+
* value is still validated before it is discarded.
|
|
148
|
+
*/
|
|
149
|
+
export function conformResources(profile, request) {
|
|
150
|
+
const rules = profile.resources;
|
|
151
|
+
requirePositiveMib(profile, "cpuMillicores", request.cpuMillicores);
|
|
152
|
+
requirePositiveMib(profile, "memoryMib", request.memoryMib);
|
|
153
|
+
if (request.ephemeralStorageMib !== undefined) {
|
|
154
|
+
requirePositiveMib(profile, "ephemeralStorageMib", request.ephemeralStorageMib);
|
|
155
|
+
const ceiling = rules.ephemeralStorageCeilingMib;
|
|
156
|
+
if (ceiling !== undefined && request.ephemeralStorageMib > ceiling) {
|
|
157
|
+
throw new KubernetesPlatformError(`platform ${profile.name} was asked for a container ephemeral-storage request of ${request.ephemeralStorageMib} MiB, over the ${ceiling} MiB ceiling for the whole pod`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const increment = Math.max(1, rules.cpuIncrementMillicores);
|
|
161
|
+
const cpuForMemory = Number.isFinite(rules.memoryPerCpuMaxMib)
|
|
162
|
+
? Math.ceil((request.memoryMib / rules.memoryPerCpuMaxMib) * 1000)
|
|
163
|
+
: 0;
|
|
164
|
+
const wanted = Math.max(request.cpuMillicores, cpuForMemory, rules.cpuFloorMillicores);
|
|
165
|
+
const cpuMillicores = Math.ceil(wanted / increment) * increment;
|
|
166
|
+
const memoryMib = Math.max(request.memoryMib, Math.ceil((cpuMillicores / 1000) * rules.memoryPerCpuMinMib));
|
|
167
|
+
const requests = { cpu: `${cpuMillicores}m`, memory: `${memoryMib}Mi` };
|
|
168
|
+
if (rules.explicitEphemeralStorage) {
|
|
169
|
+
if (request.ephemeralStorageMib === undefined) {
|
|
170
|
+
throw new KubernetesPlatformError(`platform ${profile.name} requires an explicit ephemeral-storage request on every container`);
|
|
171
|
+
}
|
|
172
|
+
requests["ephemeral-storage"] = `${request.ephemeralStorageMib}Mi`;
|
|
173
|
+
}
|
|
174
|
+
return { requests, limits: { ...requests } };
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* The pod's total ephemeral storage: max(init containers) + sum(regular containers).
|
|
178
|
+
* storage-init's 64 MiB never exceeds keeper + worker, so the total is the keeper's
|
|
179
|
+
* storage volume plus the worker's reservation.
|
|
180
|
+
*/
|
|
181
|
+
export function podEphemeralStorageMib(diskMb) {
|
|
182
|
+
return diskMb + WORKER_EPHEMERAL_MIB;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Renders a MiB figure with its GiB equivalent, e.g. `10240 MiB (10 GiB)`. Both
|
|
186
|
+
* places that report the ephemeral-storage ceiling use this rather than writing
|
|
187
|
+
* the GiB figure out by hand, so a profile with a different ceiling cannot end
|
|
188
|
+
* up described by a parenthetical that was only ever true for Autopilot's.
|
|
189
|
+
*/
|
|
190
|
+
export function describeMib(mib) {
|
|
191
|
+
const gib = mib / 1024;
|
|
192
|
+
// 3 decimals, not 2: a ceiling of 1025 MiB must not print as "(1 GiB)".
|
|
193
|
+
const rendered = Number.isInteger(gib) ? String(gib) : gib.toFixed(3).replace(/0+$/, "").replace(/\.$/, "");
|
|
194
|
+
return `${mib} MiB (${rendered} GiB)`;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Exported for src/tools/capture-fixture.ts: the fingerprint check that gates a capture needs to
|
|
198
|
+
* recognize the same "platform added this toleration" match the attestation comparison uses, so
|
|
199
|
+
* the two decisions cannot silently diverge.
|
|
200
|
+
*/
|
|
201
|
+
export function tolerationMatches(actual, allowed) {
|
|
202
|
+
return (actual.key === allowed.key &&
|
|
203
|
+
actual.operator === allowed.operator &&
|
|
204
|
+
actual.value === allowed.value &&
|
|
205
|
+
actual.effect === allowed.effect &&
|
|
206
|
+
actual.tolerationSeconds === allowed.tolerationSeconds);
|
|
207
|
+
}
|
|
208
|
+
function stripPrefixed(bag, prefixes) {
|
|
209
|
+
if (prefixes.length === 0)
|
|
210
|
+
return;
|
|
211
|
+
for (const key of Object.keys(bag)) {
|
|
212
|
+
if (prefixes.some((prefix) => key.startsWith(prefix)))
|
|
213
|
+
delete bag[key];
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Deletes exactly the metadata this platform is known to add. Called on BOTH
|
|
218
|
+
* sides of the comparison: the submitted pod never carries any of it, so the
|
|
219
|
+
* deletion is a no-op there, and the read-back pod loses only what is named
|
|
220
|
+
* here. Nothing else is touched, so any other difference still fails the run.
|
|
221
|
+
*
|
|
222
|
+
* Every branch is gated on the profile naming something, so a profile with an
|
|
223
|
+
* empty allowance — `generic` — provably leaves the view's contents alone.
|
|
224
|
+
*
|
|
225
|
+
* SYMMETRY CAVEAT — deleting from both sides forgives an *addition* only while
|
|
226
|
+
* the builder never emits an allowed key itself. If buildRunPod ever emitted,
|
|
227
|
+
* say, the gVisor nodeSelector, the deletion would land on the submitted side
|
|
228
|
+
* too and the platform *removing* it would become equally invisible. Anything
|
|
229
|
+
* named in an allowance list must therefore stay something wardby never sets.
|
|
230
|
+
*
|
|
231
|
+
* OWNERSHIP — this matters at the call site. `view.labels` and `view.annotations`
|
|
232
|
+
* are REPLACED with copies before anything is deleted, so a caller that built the
|
|
233
|
+
* view straight out of a live `V1Pod` (`normalizePod` structuredClones only the
|
|
234
|
+
* spec and hands back the pod's own `metadata.labels`/`metadata.annotations`
|
|
235
|
+
* references) does not have keys deleted out from under its real pod objects.
|
|
236
|
+
* Read the normalized bags back off `view` after the call, never off the pod.
|
|
237
|
+
* `view.spec`, by contrast, is mutated in place and the caller MUST pass a spec
|
|
238
|
+
* it already owns — `normalizePod`'s structuredClone is what makes that true.
|
|
239
|
+
*/
|
|
240
|
+
export function normalizePlatformMetadata(profile, view) {
|
|
241
|
+
const { metadata } = profile;
|
|
242
|
+
view.labels = { ...view.labels };
|
|
243
|
+
view.annotations = { ...view.annotations };
|
|
244
|
+
stripPrefixed(view.annotations, metadata.podAnnotationKeyPrefixes);
|
|
245
|
+
stripPrefixed(view.labels, metadata.podLabelKeyPrefixes);
|
|
246
|
+
if (metadata.tolerations.length > 0 && view.spec.tolerations) {
|
|
247
|
+
const remaining = view.spec.tolerations.filter((toleration) => !metadata.tolerations.some((allowed) => tolerationMatches(toleration, allowed)));
|
|
248
|
+
if (remaining.length === 0)
|
|
249
|
+
delete view.spec.tolerations;
|
|
250
|
+
else
|
|
251
|
+
view.spec.tolerations = remaining;
|
|
252
|
+
}
|
|
253
|
+
const selector = view.spec.nodeSelector;
|
|
254
|
+
if (selector && Object.keys(metadata.nodeSelector).length > 0) {
|
|
255
|
+
for (const [key, value] of Object.entries(metadata.nodeSelector)) {
|
|
256
|
+
if (selector[key] === value)
|
|
257
|
+
delete selector[key];
|
|
258
|
+
}
|
|
259
|
+
if (Object.keys(selector).length === 0)
|
|
260
|
+
delete view.spec.nodeSelector;
|
|
261
|
+
}
|
|
262
|
+
if (metadata.dropsPodSeccompProfile && view.spec.securityContext)
|
|
263
|
+
delete view.spec.securityContext.seccompProfile;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Refuses a configuration that cannot work on this platform, at startup rather
|
|
267
|
+
* than at the first coding run. Each message names the setting and the limit.
|
|
268
|
+
*/
|
|
269
|
+
export function assertPlatformConfig(profile, config) {
|
|
270
|
+
if (profile.requiresGvisor && config.runtimeClassName !== GVISOR_RUNTIME_CLASS) {
|
|
271
|
+
throw new KubernetesPlatformError(`KUBERNETES_PLATFORM=${profile.name} requires KUBERNETES_RUNTIME_CLASS=${GVISOR_RUNTIME_CLASS} (found ${config.runtimeClassName ?? "unset"})`);
|
|
272
|
+
}
|
|
273
|
+
const ceiling = profile.resources.ephemeralStorageCeilingMib;
|
|
274
|
+
if (ceiling !== undefined) {
|
|
275
|
+
const total = podEphemeralStorageMib(config.maxDiskMb);
|
|
276
|
+
if (total > ceiling) {
|
|
277
|
+
throw new KubernetesPlatformError(`CODING_MAX_DISK_MB=${config.maxDiskMb} needs ${total} MiB of pod ephemeral storage, over the ${describeMib(ceiling)} ceiling of KUBERNETES_PLATFORM=${profile.name}; the worker container reserves ${WORKER_EPHEMERAL_MIB} MiB of that, so the largest workspace this platform can run is ${ceiling - WORKER_EPHEMERAL_MIB} MiB`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { KubernetesJobConfig } from "../../config/providers.js";
|
|
2
|
+
import type { KubernetesApi } from "./kubernetes-api.js";
|
|
3
|
+
export interface KubernetesPreflightOptions {
|
|
4
|
+
api: KubernetesApi;
|
|
5
|
+
config: KubernetesJobConfig;
|
|
6
|
+
workerImage: string;
|
|
7
|
+
/** The effective CODING_MAX_DISK_MB; the platform check refuses a value the platform cannot run. */
|
|
8
|
+
maxDiskMb: number;
|
|
9
|
+
now?: () => number;
|
|
10
|
+
sleep?: (milliseconds: number) => Promise<void>;
|
|
11
|
+
timeoutMs?: number;
|
|
12
|
+
cleanupTimeoutMs?: number;
|
|
13
|
+
}
|
|
14
|
+
export interface CanaryResult {
|
|
15
|
+
dns: boolean;
|
|
16
|
+
/** The proxy's deny port: reachable means the run policy is not being enforced. */
|
|
17
|
+
proxyDeny: boolean;
|
|
18
|
+
internet: boolean;
|
|
19
|
+
metadata: boolean;
|
|
20
|
+
proxy: boolean;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Runs in the worker image under the run policy; prints exactly one `{"wardbyCanary":{...}}` line.
|
|
24
|
+
* `dns` checks in-pod resolution; `proxyDeny` checks the policy itself — a TCP connect to the *same*
|
|
25
|
+
* proxy pod on a port no run policy permits, which a namespace-wide allow-all policy would reopen.
|
|
26
|
+
* Probing one destination on two ports is what makes the result decisive: `proxy` true with
|
|
27
|
+
* `proxyDeny` false can only mean a policy is enforced and port-scoped.
|
|
28
|
+
*
|
|
29
|
+
* `proxyDeny` is false for exactly one socket outcome: a **timeout**, i.e. the packet was dropped.
|
|
30
|
+
* A refusal (RST) is not a denial — it proves the SYN reached the destination host, so the deny
|
|
31
|
+
* port is merely unserved — and a connect obviously is not either. This mirrors the launch gate's
|
|
32
|
+
* probe (`enforcementProbeScript`), which had to make the same distinction after a live cluster
|
|
33
|
+
* with no NetworkPolicy at all passed a boolean version of this check; preflight and gate must
|
|
34
|
+
* agree on what counts as evidence, or one of them is vacuous. `internet`, `metadata` and `proxy`
|
|
35
|
+
* stay plain reachability, where "connected" is the only thing that matters.
|
|
36
|
+
*
|
|
37
|
+
* CNIs program a new pod's policy a few seconds after it starts, so the script first waits (up to
|
|
38
|
+
* 20 s) for the deny-port connect to be *dropped*; if it never is, `proxyDeny` reports true.
|
|
39
|
+
*/
|
|
40
|
+
export declare const CANARY_SCRIPT: string;
|
|
41
|
+
export interface KubernetesPreflightResult {
|
|
42
|
+
checks: string[];
|
|
43
|
+
/** The validated proxy Service ClusterIP; the launcher probes both of its ports to wait for each run's policy. */
|
|
44
|
+
proxyIp: string;
|
|
45
|
+
}
|
|
46
|
+
/** Throws kubernetes_isolation_unsupported:<check> on the first failed check; returns the checks that passed. */
|
|
47
|
+
export declare function kubernetesPreflight(options: KubernetesPreflightOptions): Promise<string[]>;
|
|
48
|
+
/**
|
|
49
|
+
* Throws kubernetes_isolation_unsupported:<check> on the first failed check
|
|
50
|
+
* (`:timeout` when the whole preflight exceeds `timeoutMs`); returns the checks that passed and
|
|
51
|
+
* the proxy ClusterIP it validated.
|
|
52
|
+
*/
|
|
53
|
+
export declare function runKubernetesPreflight(options: KubernetesPreflightOptions): Promise<KubernetesPreflightResult>;
|
|
54
|
+
/**
|
|
55
|
+
* One operator-facing line for a failed preflight: the check code, plus the
|
|
56
|
+
* short message of a cause only where the preflight attached one (API errors;
|
|
57
|
+
* never canary output). Anything else (e.g. a kubeconfig load error) is shown briefly.
|
|
58
|
+
*/
|
|
59
|
+
export declare function describePreflightFailure(error: unknown): string;
|