@wardby/cli 0.2.1 → 0.4.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 +53 -4
- package/README.md +52 -13
- package/dist/claude-coding-worker/driver.d.ts +6 -1
- package/dist/claude-coding-worker/driver.js +27 -1
- package/dist/claude-coding-worker/main.js +2 -0
- package/dist/claude-coding-worker/tool-socket.d.ts +9 -0
- package/dist/claude-coding-worker/tool-socket.js +26 -0
- package/dist/cli-help.d.ts +1 -1
- package/dist/cli-help.js +14 -4
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +284 -36
- package/dist/coding/base-commit.d.ts +6 -0
- package/dist/coding/base-commit.js +12 -0
- package/dist/coding/collect-exclude.d.ts +25 -0
- package/dist/coding/collect-exclude.js +76 -0
- package/dist/coding/profile.d.ts +67 -22
- package/dist/coding/profile.js +58 -25
- package/dist/coding/protected-path-wording.d.ts +24 -0
- package/dist/coding/protected-path-wording.js +55 -0
- package/dist/coding/protected-paths.d.ts +31 -0
- package/dist/coding/protected-paths.js +48 -0
- package/dist/coding/protocol.d.ts +91 -4
- package/dist/coding/protocol.js +123 -12
- package/dist/coding/provider.d.ts +6 -1
- package/dist/coding/provider.js +16 -9
- package/dist/coding/registry/adapters.d.ts +2 -0
- package/dist/coding/registry/adapters.js +9 -0
- package/dist/coding/registry/allowlist.d.ts +9 -0
- package/dist/coding/registry/allowlist.js +28 -0
- package/dist/coding/registry/json-scan.d.ts +55 -0
- package/dist/coding/registry/json-scan.js +191 -0
- package/dist/coding/registry/lockfiles.d.ts +6 -0
- package/dist/coding/registry/lockfiles.js +89 -0
- package/dist/coding/registry/npm-lockfile.d.ts +9 -0
- package/dist/coding/registry/npm-lockfile.js +73 -0
- package/dist/coding/registry/npm-plan.d.ts +29 -0
- package/dist/coding/registry/npm-plan.js +289 -0
- package/dist/coding/registry/npm.d.ts +7 -0
- package/dist/coding/registry/npm.js +252 -0
- package/dist/coding/registry/pypi.d.ts +4 -0
- package/dist/coding/registry/pypi.js +210 -0
- package/dist/coding/registry/report.d.ts +37 -0
- package/dist/coding/registry/report.js +31 -0
- package/dist/coding/registry/token.d.ts +4 -0
- package/dist/coding/registry/token.js +7 -0
- package/dist/coding/registry/types.d.ts +286 -0
- package/dist/coding/registry/types.js +19 -0
- package/dist/coding/registry/worker-config.d.ts +14 -0
- package/dist/coding/registry/worker-config.js +31 -0
- package/dist/coding/services/builtins.d.ts +22 -0
- package/dist/coding/services/builtins.js +85 -0
- package/dist/coding/services/catalog.d.ts +443 -0
- package/dist/coding/services/catalog.js +159 -0
- package/dist/coding/services/declaration.d.ts +11 -0
- package/dist/coding/services/declaration.js +114 -0
- package/dist/coding/services/note.d.ts +8 -0
- package/dist/coding/services/note.js +15 -0
- package/dist/coding/services/resolve.d.ts +25 -0
- package/dist/coding/services/resolve.js +50 -0
- package/dist/coding/services/wording.d.ts +25 -0
- package/dist/coding/services/wording.js +70 -0
- package/dist/coding-proxy/main.js +24 -4
- package/dist/coding-worker/artifact.d.ts +6 -0
- package/dist/coding-worker/debug-trace.d.ts +37 -0
- package/dist/coding-worker/debug-trace.js +117 -0
- package/dist/coding-worker/driver.d.ts +13 -1
- package/dist/coding-worker/driver.js +102 -13
- package/dist/coding-worker/errors.js +8 -0
- package/dist/coding-worker/main.js +10 -2
- package/dist/coding-worker/sdk.d.ts +2 -2
- package/dist/coding-worker/sdk.js +7 -2
- package/dist/coding-worker/types.d.ts +3 -0
- package/dist/config/providers.d.ts +66 -0
- package/dist/config/providers.js +137 -0
- package/dist/core/attribution.d.ts +101 -0
- package/dist/core/attribution.js +208 -0
- package/dist/core/budget-groups.d.ts +120 -10
- package/dist/core/budget-groups.js +133 -24
- package/dist/core/budget-wording.d.ts +22 -0
- package/dist/core/budget-wording.js +55 -0
- package/dist/core/coding-queue.d.ts +3 -0
- package/dist/core/coding-queue.js +6 -2
- package/dist/core/coding-service-status.d.ts +11 -0
- package/dist/core/coding-service-status.js +17 -0
- package/dist/core/cost-report.d.ts +88 -0
- package/dist/core/cost-report.js +248 -0
- package/dist/core/datastores.js +18 -2
- package/dist/core/db.d.ts +5 -1
- package/dist/core/db.js +8 -2
- package/dist/core/dispatch.d.ts +74 -7
- package/dist/core/dispatch.js +382 -47
- package/dist/core/engine-native.js +44 -14
- package/dist/core/glob.d.ts +10 -0
- package/dist/core/glob.js +33 -0
- package/dist/core/grants.d.ts +86 -0
- package/dist/core/grants.js +126 -0
- package/dist/core/host-events.d.ts +72 -0
- package/dist/core/host-events.js +597 -0
- package/dist/core/host-identity-links.d.ts +54 -0
- package/dist/core/host-identity-links.js +189 -0
- package/dist/core/host-status.d.ts +78 -0
- package/dist/core/host-status.js +228 -0
- package/dist/core/in-flight-runs.d.ts +8 -0
- package/dist/core/in-flight-runs.js +56 -0
- package/dist/core/issue-bridge.d.ts +60 -0
- package/dist/core/issue-bridge.js +189 -0
- package/dist/core/issue-dedupe.d.ts +70 -0
- package/dist/core/issue-dedupe.js +255 -0
- package/dist/core/issue-events.d.ts +42 -0
- package/dist/core/issue-events.js +155 -0
- package/dist/core/issue-status.d.ts +29 -0
- package/dist/core/issue-status.js +241 -0
- package/dist/core/issue-tracker-tools.d.ts +64 -0
- package/dist/core/issue-tracker-tools.js +850 -0
- package/dist/core/model-usage.d.ts +10 -0
- package/dist/core/model-usage.js +24 -0
- package/dist/core/provider-wording.d.ts +12 -0
- package/dist/core/provider-wording.js +44 -0
- package/dist/core/reconciler.d.ts +42 -2
- package/dist/core/reconciler.js +97 -2
- package/dist/core/repo-access.d.ts +99 -0
- package/dist/core/repo-access.js +136 -0
- package/dist/core/review-host-checks.d.ts +11 -0
- package/dist/core/review-host-checks.js +41 -0
- package/dist/core/review-host-tools.d.ts +47 -0
- package/dist/core/review-host-tools.js +354 -0
- package/dist/core/run-heartbeat.d.ts +27 -0
- package/dist/core/run-heartbeat.js +54 -0
- package/dist/core/run-pricing.d.ts +61 -0
- package/dist/core/run-pricing.js +56 -0
- package/dist/core/runner.d.ts +33 -8
- package/dist/core/runner.js +410 -62
- package/dist/core/scheduler.d.ts +4 -1
- package/dist/core/scheduler.js +3 -2
- package/dist/core/secrets.d.ts +11 -2
- package/dist/core/secrets.js +25 -6
- package/dist/core/self-defects.d.ts +80 -0
- package/dist/core/self-defects.js +180 -0
- package/dist/core/subagent-memory-tools.d.ts +1 -1
- package/dist/core/subagent-memory-tools.js +16 -3
- package/dist/core/tool-admin.d.ts +81 -0
- package/dist/core/tool-admin.js +129 -0
- package/dist/core/tool-names.d.ts +42 -0
- package/dist/core/tool-names.js +67 -0
- package/dist/core/untrusted-content.d.ts +32 -0
- package/dist/core/untrusted-content.js +72 -0
- package/dist/core/webhooks.d.ts +17 -2
- package/dist/core/webhooks.js +42 -3
- package/dist/generated/prisma/browser.d.ts +166 -0
- package/dist/generated/prisma/client.d.ts +166 -0
- package/dist/generated/prisma/commonInputTypes.d.ts +152 -52
- package/dist/generated/prisma/enums.d.ts +13 -0
- package/dist/generated/prisma/enums.js +12 -1
- package/dist/generated/prisma/internal/class.d.ts +242 -0
- package/dist/generated/prisma/internal/class.js +4 -4
- package/dist/generated/prisma/internal/prismaNamespace.d.ts +2571 -577
- package/dist/generated/prisma/internal/prismaNamespace.js +312 -6
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +328 -0
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +312 -6
- package/dist/generated/prisma/models/Agent.d.ts +682 -1
- package/dist/generated/prisma/models/AgentIssueProject.d.ts +1838 -0
- package/dist/generated/prisma/models/AgentIssueProject.js +1 -0
- package/dist/generated/prisma/models/AgentRepository.d.ts +1425 -0
- package/dist/generated/prisma/models/AgentRepository.js +1 -0
- package/dist/generated/prisma/models/AgentTool.d.ts +95 -1
- package/dist/generated/prisma/models/AuthUser.d.ts +56 -1
- package/dist/generated/prisma/models/CodingAgentProfile.d.ts +258 -7
- package/dist/generated/prisma/models/CodingProxySession.d.ts +195 -2
- package/dist/generated/prisma/models/CodingRun.d.ts +1405 -95
- package/dist/generated/prisma/models/CodingRunServiceStatus.d.ts +1404 -0
- package/dist/generated/prisma/models/CodingRunServiceStatus.js +1 -0
- package/dist/generated/prisma/models/CodingService.d.ts +1348 -0
- package/dist/generated/prisma/models/CodingService.js +1 -0
- package/dist/generated/prisma/models/HostEventDelivery.d.ts +946 -0
- package/dist/generated/prisma/models/HostEventDelivery.js +1 -0
- package/dist/generated/prisma/models/HostIdentity.d.ts +1232 -0
- package/dist/generated/prisma/models/HostIdentity.js +1 -0
- package/dist/generated/prisma/models/HostIdentityLinkRequest.d.ts +1473 -0
- package/dist/generated/prisma/models/HostIdentityLinkRequest.js +1 -0
- package/dist/generated/prisma/models/IssueFingerprint.d.ts +1183 -0
- package/dist/generated/prisma/models/IssueFingerprint.js +1 -0
- package/dist/generated/prisma/models/IssuePullRequest.d.ts +1255 -0
- package/dist/generated/prisma/models/IssuePullRequest.js +1 -0
- package/dist/generated/prisma/models/ModelCatalogEntry.d.ts +1322 -0
- package/dist/generated/prisma/models/ModelCatalogEntry.js +1 -0
- package/dist/generated/prisma/models/Principal.d.ts +455 -0
- package/dist/generated/prisma/models/RegistryAllowance.d.ts +1148 -0
- package/dist/generated/prisma/models/RegistryAllowance.js +1 -0
- package/dist/generated/prisma/models/RegistryApprovedVersion.d.ts +1219 -0
- package/dist/generated/prisma/models/RegistryApprovedVersion.js +1 -0
- package/dist/generated/prisma/models/RegistryFetch.d.ts +1428 -0
- package/dist/generated/prisma/models/RegistryFetch.js +1 -0
- package/dist/generated/prisma/models/RegistryPlanRefusal.d.ts +1294 -0
- package/dist/generated/prisma/models/RegistryPlanRefusal.js +1 -0
- package/dist/generated/prisma/models/RegistryVersionFact.d.ts +1085 -0
- package/dist/generated/prisma/models/RegistryVersionFact.js +1 -0
- package/dist/generated/prisma/models/ResourceGrant.d.ts +1437 -0
- package/dist/generated/prisma/models/ResourceGrant.js +1 -0
- package/dist/generated/prisma/models/Run.d.ts +1402 -82
- package/dist/generated/prisma/models/RunAttribution.d.ts +1259 -0
- package/dist/generated/prisma/models/RunAttribution.js +1 -0
- package/dist/generated/prisma/models/RunHostCheck.d.ts +1239 -0
- package/dist/generated/prisma/models/RunHostCheck.js +1 -0
- package/dist/generated/prisma/models/RunHostStatus.d.ts +1315 -0
- package/dist/generated/prisma/models/RunHostStatus.js +1 -0
- package/dist/generated/prisma/models/RunIssueStatus.d.ts +1199 -0
- package/dist/generated/prisma/models/RunIssueStatus.js +1 -0
- package/dist/generated/prisma/models/RunModelUsage.d.ts +1316 -0
- package/dist/generated/prisma/models/RunModelUsage.js +1 -0
- package/dist/generated/prisma/models/Tool.d.ts +15 -3
- package/dist/generated/prisma/models/WorkItem.d.ts +1408 -0
- package/dist/generated/prisma/models/WorkItem.js +1 -0
- package/dist/generated/prisma/models.d.ts +22 -0
- package/dist/help/build.d.ts +1 -0
- package/dist/help/build.js +9 -0
- package/dist/help/catalog.d.ts +24 -0
- package/dist/help/catalog.js +160 -0
- package/dist/help/cli.d.ts +2 -0
- package/dist/help/cli.js +65 -0
- package/dist/help/runtime.d.ts +3 -0
- package/dist/help/runtime.js +44 -0
- package/dist/help/search.d.ts +9 -0
- package/dist/help/search.js +104 -0
- package/dist/help-index.json +999 -0
- package/dist/import/cli-args.js +3 -2
- package/dist/import/create.d.ts +5 -0
- package/dist/import/create.js +67 -13
- package/dist/import/index.js +19 -9
- package/dist/import/neutral-schema.d.ts +18 -18
- package/dist/knowledge/check.d.ts +13 -0
- package/dist/knowledge/check.js +69 -0
- package/dist/knowledge/cli.d.ts +14 -0
- package/dist/knowledge/cli.js +67 -0
- package/dist/knowledge/concept.d.ts +54 -0
- package/dist/knowledge/concept.js +78 -0
- package/dist/knowledge/note.d.ts +11 -0
- package/dist/knowledge/note.js +39 -0
- package/dist/knowledge/relevance.d.ts +11 -0
- package/dist/knowledge/relevance.js +14 -0
- package/dist/knowledge/span-hash.d.ts +3 -0
- package/dist/knowledge/span-hash.js +16 -0
- package/dist/mcp/auth/access.d.ts +72 -0
- package/dist/mcp/auth/access.js +58 -0
- package/dist/mcp/auth/grants-cli.d.ts +149 -0
- package/dist/mcp/auth/grants-cli.js +518 -0
- package/dist/mcp/auth/host-account-cli.d.ts +2 -0
- package/dist/mcp/auth/host-account-cli.js +47 -0
- package/dist/mcp/auth/ownership.d.ts +25 -51
- package/dist/mcp/auth/ownership.js +19 -14
- package/dist/mcp/auth/repo-authorization.d.ts +22 -0
- package/dist/mcp/auth/repo-authorization.js +51 -0
- package/dist/mcp/auth/resource-server.d.ts +33 -2
- package/dist/mcp/auth/resource-server.js +99 -3
- package/dist/mcp/auth/self-hosted/browser.js +2 -2
- package/dist/mcp/auth/self-hosted/cli.js +47 -13
- package/dist/mcp/auth/self-hosted/credentials.d.ts +23 -5
- package/dist/mcp/auth/self-hosted/credentials.js +62 -4
- package/dist/mcp/auth/self-hosted/session.d.ts +7 -6
- package/dist/mcp/context.d.ts +30 -1
- package/dist/mcp/errors.d.ts +25 -6
- package/dist/mcp/errors.js +98 -0
- package/dist/mcp/host-events/deliveries.d.ts +9 -0
- package/dist/mcp/host-events/deliveries.js +17 -0
- package/dist/mcp/host-events/github-ingress.d.ts +38 -0
- package/dist/mcp/host-events/github-ingress.js +83 -0
- package/dist/mcp/host-events/github-user-callback.d.ts +18 -0
- package/dist/mcp/host-events/github-user-callback.js +50 -0
- package/dist/mcp/host-events/jira-ingress.d.ts +29 -0
- package/dist/mcp/host-events/jira-ingress.js +92 -0
- package/dist/mcp/index.d.ts +10 -1
- package/dist/mcp/index.js +187 -19
- package/dist/mcp/server.js +32 -11
- package/dist/mcp/tools/agents.js +519 -50
- package/dist/mcp/tools/budget-groups.js +3 -3
- package/dist/mcp/tools/cost-report.d.ts +8 -0
- package/dist/mcp/tools/cost-report.js +60 -0
- package/dist/mcp/tools/datastore.js +22 -15
- package/dist/mcp/tools/grants.d.ts +2 -0
- package/dist/mcp/tools/grants.js +239 -0
- package/dist/mcp/tools/help.d.ts +5 -0
- package/dist/mcp/tools/help.js +67 -0
- package/dist/mcp/tools/host-accounts.d.ts +2 -0
- package/dist/mcp/tools/host-accounts.js +114 -0
- package/dist/mcp/tools/issue-projects.d.ts +2 -0
- package/dist/mcp/tools/issue-projects.js +238 -0
- package/dist/mcp/tools/memory.d.ts +7 -1
- package/dist/mcp/tools/memory.js +5 -5
- package/dist/mcp/tools/model-catalog.d.ts +22 -0
- package/dist/mcp/tools/model-catalog.js +423 -0
- package/dist/mcp/tools/repositories.d.ts +2 -0
- package/dist/mcp/tools/repositories.js +182 -0
- package/dist/mcp/tools/runs.d.ts +6 -0
- package/dist/mcp/tools/runs.js +60 -6
- package/dist/mcp/tools/scheduling.js +3 -6
- package/dist/mcp/tools/secrets.js +18 -6
- package/dist/mcp/tools/services.d.ts +2 -0
- package/dist/mcp/tools/services.js +222 -0
- package/dist/mcp/tools/subagents.js +52 -16
- package/dist/mcp/tools/tools.d.ts +41 -0
- package/dist/mcp/tools/tools.js +201 -40
- package/dist/mcp/tools/trigger.js +65 -7
- package/dist/mcp/tools/webhooks.js +15 -4
- package/dist/mcp/transport/streamable-http.d.ts +15 -0
- package/dist/mcp/transport/streamable-http.js +55 -3
- package/dist/mcp/webhooks/ingress.d.ts +2 -1
- package/dist/mcp/webhooks/ingress.js +9 -2
- package/dist/providers/auth/delegating.d.ts +19 -0
- package/dist/providers/auth/delegating.js +71 -0
- package/dist/providers/auth/index.d.ts +2 -0
- package/dist/providers/auth/index.js +11 -0
- package/dist/providers/auth/self-hosted.d.ts +10 -2
- package/dist/providers/auth/self-hosted.js +55 -6
- package/dist/providers/auth/types.d.ts +6 -0
- package/dist/providers/coding-proxy/memory-ledger.d.ts +4 -1
- package/dist/providers/coding-proxy/memory-ledger.js +31 -3
- package/dist/providers/coding-proxy/metering.d.ts +6 -1
- package/dist/providers/coding-proxy/metering.js +41 -4
- package/dist/providers/coding-proxy/prisma-ledger.d.ts +17 -0
- package/dist/providers/coding-proxy/prisma-ledger.js +106 -8
- package/dist/providers/coding-proxy/proxy.d.ts +25 -2
- package/dist/providers/coding-proxy/proxy.js +561 -38
- package/dist/providers/coding-proxy/registry/audit.d.ts +131 -0
- package/dist/providers/coding-proxy/registry/audit.js +380 -0
- package/dist/providers/coding-proxy/registry/bounded-fetch.d.ts +16 -0
- package/dist/providers/coding-proxy/registry/bounded-fetch.js +77 -0
- package/dist/providers/coding-proxy/registry/plan.d.ts +58 -0
- package/dist/providers/coding-proxy/registry/plan.js +304 -0
- package/dist/providers/coding-proxy/registry/prisma-store.d.ts +34 -0
- package/dist/providers/coding-proxy/registry/prisma-store.js +151 -0
- package/dist/providers/coding-proxy/registry/service.d.ts +213 -0
- package/dist/providers/coding-proxy/registry/service.js +1137 -0
- package/dist/providers/coding-proxy/registry/store.d.ts +127 -0
- package/dist/providers/coding-proxy/registry/store.js +70 -0
- package/dist/providers/coding-proxy/runtime.d.ts +17 -0
- package/dist/providers/coding-proxy/runtime.js +63 -0
- package/dist/providers/coding-proxy/secure-fetch.js +0 -1
- package/dist/providers/coding-proxy/server.d.ts +11 -0
- package/dist/providers/coding-proxy/server.js +163 -0
- package/dist/providers/coding-proxy/types.d.ts +33 -1
- package/dist/providers/coding-proxy/types.js +12 -1
- package/dist/providers/engine/types.d.ts +29 -0
- package/dist/providers/executor/build.d.ts +2 -2
- package/dist/providers/executor/composition.d.ts +3 -0
- package/dist/providers/executor/composition.js +28 -1
- package/dist/providers/executor/container.d.ts +134 -4
- package/dist/providers/executor/container.js +394 -37
- package/dist/providers/executor/dbos.d.ts +4 -3
- package/dist/providers/executor/dbos.js +7 -5
- package/dist/providers/executor/in-process.d.ts +2 -3
- package/dist/providers/executor/routing.d.ts +13 -0
- package/dist/providers/executor/routing.js +18 -0
- package/dist/providers/executor/types.d.ts +34 -0
- package/dist/providers/issue-tracker/adf.d.ts +31 -0
- package/dist/providers/issue-tracker/adf.js +181 -0
- package/dist/providers/issue-tracker/index.d.ts +5 -0
- package/dist/providers/issue-tracker/index.js +12 -0
- package/dist/providers/issue-tracker/jira-client.d.ts +41 -0
- package/dist/providers/issue-tracker/jira-client.js +151 -0
- package/dist/providers/issue-tracker/jira-events.d.ts +3 -0
- package/dist/providers/issue-tracker/jira-events.js +98 -0
- package/dist/providers/issue-tracker/jira.d.ts +116 -0
- package/dist/providers/issue-tracker/jira.js +502 -0
- package/dist/providers/issue-tracker/types.d.ts +269 -0
- package/dist/providers/issue-tracker/types.js +16 -0
- package/dist/providers/jobs/claude-tool-setup.d.ts +23 -0
- package/dist/providers/jobs/claude-tool-setup.js +50 -0
- package/dist/providers/jobs/collect-prune.d.ts +7 -0
- package/dist/providers/jobs/collect-prune.js +27 -0
- package/dist/providers/jobs/docker-isolation.d.ts +48 -3
- package/dist/providers/jobs/docker-isolation.js +213 -22
- package/dist/providers/jobs/docker-services.d.ts +35 -0
- package/dist/providers/jobs/docker-services.js +191 -0
- package/dist/providers/jobs/docker.d.ts +53 -2
- package/dist/providers/jobs/docker.js +307 -32
- package/dist/providers/jobs/fake-kubernetes-api.d.ts +1 -0
- package/dist/providers/jobs/fake-kubernetes-api.js +12 -3
- package/dist/providers/jobs/kubernetes-isolation.d.ts +17 -2
- package/dist/providers/jobs/kubernetes-isolation.js +238 -57
- package/dist/providers/jobs/kubernetes-platform.d.ts +10 -4
- package/dist/providers/jobs/kubernetes-platform.js +11 -5
- package/dist/providers/jobs/kubernetes-preflight.js +3 -0
- package/dist/providers/jobs/kubernetes.d.ts +24 -2
- package/dist/providers/jobs/kubernetes.js +153 -22
- package/dist/providers/jobs/service-state.d.ts +22 -0
- package/dist/providers/jobs/service-state.js +17 -0
- package/dist/providers/jobs/types.d.ts +18 -0
- package/dist/providers/llm/anthropic.d.ts +3 -3
- package/dist/providers/llm/anthropic.js +3 -5
- package/dist/providers/llm/bedrock.d.ts +3 -3
- package/dist/providers/llm/bedrock.js +3 -8
- package/dist/providers/llm/catalog-lookup.d.ts +10 -0
- package/dist/providers/llm/catalog-lookup.js +15 -0
- package/dist/providers/llm/catalog-shipped.d.ts +18 -0
- package/dist/providers/llm/catalog-shipped.js +197 -0
- package/dist/providers/llm/catalog-store.d.ts +58 -0
- package/dist/providers/llm/catalog-store.js +138 -0
- package/dist/providers/llm/catalog-types.d.ts +66 -0
- package/dist/providers/llm/catalog-types.js +64 -0
- package/dist/providers/llm/catalog.d.ts +61 -0
- package/dist/providers/llm/catalog.js +147 -0
- package/dist/providers/llm/claude-messages.d.ts +5 -1
- package/dist/providers/llm/claude-messages.js +1 -0
- package/dist/providers/llm/claude-provider.d.ts +10 -12
- package/dist/providers/llm/claude-provider.js +15 -6
- package/dist/providers/llm/index.d.ts +9 -6
- package/dist/providers/llm/index.js +8 -5
- package/dist/providers/llm/openai.d.ts +14 -5
- package/dist/providers/llm/openai.js +24 -14
- package/dist/providers/llm/pricing-core.d.ts +5 -3
- package/dist/providers/llm/registration.js +8 -12
- package/dist/providers/llm/routing.d.ts +21 -11
- package/dist/providers/llm/routing.js +47 -13
- package/dist/providers/llm/types.d.ts +12 -0
- package/dist/providers/llm/types.js +8 -1
- package/dist/providers/review-host/diff-lines.d.ts +16 -0
- package/dist/providers/review-host/diff-lines.js +59 -0
- package/dist/providers/review-host/github-events.d.ts +6 -0
- package/dist/providers/review-host/github-events.js +226 -0
- package/dist/providers/review-host/github-user-auth.d.ts +38 -0
- package/dist/providers/review-host/github-user-auth.js +128 -0
- package/dist/providers/review-host/github.d.ts +57 -0
- package/dist/providers/review-host/github.js +568 -0
- package/dist/providers/review-host/index.d.ts +12 -0
- package/dist/providers/review-host/index.js +30 -0
- package/dist/providers/review-host/review-format.d.ts +19 -0
- package/dist/providers/review-host/review-format.js +59 -0
- package/dist/providers/review-host/types.d.ts +309 -0
- package/dist/providers/review-host/types.js +24 -0
- package/dist/providers/vcs/git.d.ts +16 -6
- package/dist/providers/vcs/git.js +78 -34
- package/dist/providers/vcs/github.d.ts +94 -3
- package/dist/providers/vcs/github.js +223 -17
- package/dist/providers/vcs/types.d.ts +48 -4
- package/dist/quickstart/index.d.ts +2 -0
- package/dist/quickstart/index.js +36 -8
- package/dist/sandbox/fetch-policy.d.ts +20 -2
- package/dist/sandbox/fetch-policy.js +64 -3
- package/dist/sandbox/host-functions.d.ts +9 -1
- package/dist/sandbox/host-functions.js +12 -5
- package/dist/serve.js +8 -2
- package/dist/viewer/api-schema.d.ts +2757 -0
- package/dist/viewer/api-schema.js +165 -0
- package/dist/viewer/build-schemas.d.ts +2 -0
- package/dist/viewer/build-schemas.js +18 -0
- package/dist/viewer/event-bus.d.ts +38 -0
- package/dist/viewer/event-bus.js +232 -0
- package/dist/viewer/graph.d.ts +40 -0
- package/dist/viewer/graph.js +243 -0
- package/dist/viewer/http.d.ts +30 -0
- package/dist/viewer/http.js +133 -0
- package/dist/viewer/run-detail.d.ts +4 -0
- package/dist/viewer/run-detail.js +61 -0
- package/dist/wardby-bin.js +11 -0
- package/docs/README.md +40 -0
- package/docs/agent-recipes.md +383 -0
- package/docs/architecture-runtime.md +90 -0
- package/docs/assets/brand/wardby-icon-512.png +0 -0
- package/docs/assets/brand/wardby-icon.svg +16 -0
- package/docs/assets/brand/wardby-mascot-profile-512.png +0 -0
- package/docs/assets/brand/wardby-mascot.png +0 -0
- package/docs/assets/brand/wardby-mascot.svg +5 -0
- package/docs/assets/wardby-workflow.svg +106 -0
- package/docs/code-review-agents.md +508 -0
- package/docs/coding-agent-setup.md +175 -0
- package/docs/coding-packages.md +455 -0
- package/docs/coding-services.md +300 -0
- package/docs/coding-worker-byo-images.md +98 -0
- package/docs/coding-worker-isolation.md +1068 -0
- package/docs/getting-started-gke.md +632 -0
- package/docs/getting-started-identity-provider.md +319 -0
- package/docs/getting-started.md +147 -0
- package/docs/jira-agents.md +649 -0
- package/docs/knowledge.md +387 -0
- package/docs/models.md +221 -0
- package/docs/observability.md +53 -0
- package/docs/release-verification.md +66 -0
- package/docs/security-deployment.md +667 -0
- package/docs/viewer-api.md +142 -0
- package/help/admin-viewer.md +39 -0
- package/help/agent-recipes.md +173 -0
- package/help/architecture-agent.md +189 -0
- package/help/builder-agent.md +80 -0
- package/help/code-review-agents.md +37 -0
- package/help/coding-packages.md +31 -0
- package/help/coding-services.md +71 -0
- package/help/cost-attribution.md +67 -0
- package/help/creating-agents.md +90 -0
- package/help/deploy-gke.md +45 -0
- package/help/deployment-targets.md +39 -0
- package/help/errors/budget-group-exhausted.md +25 -0
- package/help/errors/docker-isolation-unsupported.md +26 -0
- package/help/errors/model-unavailable.md +63 -0
- package/help/errors/protected-path.md +45 -0
- package/help/errors/repo-access.md +27 -0
- package/help/errors/service-declaration-invalid.md +27 -0
- package/help/errors/service-declaration-unavailable.md +25 -0
- package/help/errors/service-launcher-unsupported.md +30 -0
- package/help/errors/service-not-allowed.md +27 -0
- package/help/errors/service-unknown.md +23 -0
- package/help/errors/service-unready.md +49 -0
- package/help/getting-started.md +32 -0
- package/help/github.md +48 -0
- package/help/identity-and-access.md +44 -0
- package/help/jira.md +135 -0
- package/help/knowledge.md +47 -0
- package/help/mcp.md +30 -0
- package/help/models.md +90 -0
- package/help/native-capabilities.md +30 -0
- package/help/observability.md +41 -0
- package/help/operating-agents.md +36 -0
- package/help/security.md +27 -0
- package/help/troubleshooting/budgets.md +32 -0
- package/help/troubleshooting/coding-workers.md +47 -0
- package/help/troubleshooting/repository-access.md +27 -0
- package/package.json +14 -3
- package/prisma/migrations/20260925010000_coding_collect_exclude/migration.sql +8 -0
- package/prisma/migrations/20260925015000_allowed_egress_default/migration.sql +8 -0
- package/prisma/migrations/20260925020000_coding_package_registry/migration.sql +55 -0
- package/prisma/migrations/20260925030000_tool_name_per_owner/migration.sql +11 -0
- package/prisma/migrations/20260926010000_run_trigger_host_event/migration.sql +7 -0
- package/prisma/migrations/20260926020000_code_review_hosts/migration.sql +53 -0
- package/prisma/migrations/20260926030000_auth_user_roles/migration.sql +9 -0
- package/prisma/migrations/20260926040000_agent_effort/migration.sql +6 -0
- package/prisma/migrations/20260926050000_registry_lockfile_plan/migration.sql +32 -0
- package/prisma/migrations/20260926050000_repo_access_authorization/migration.sql +82 -0
- package/prisma/migrations/20260926060000_registry_plan_refusal/migration.sql +19 -0
- package/prisma/migrations/20260926100000_registry_plan_refusal_published_at/migration.sql +5 -0
- package/prisma/migrations/20260926190000_run_host_status/migration.sql +18 -0
- package/prisma/migrations/20260926210000_run_host_status_at_dispatch/migration.sql +5 -0
- package/prisma/migrations/20260927010000_resource_grants/migration.sql +72 -0
- package/prisma/migrations/20260927020000_proxy_session_budget_exhausted/migration.sql +3 -0
- package/prisma/migrations/20260927030000_coding_debug_trace/migration.sql +4 -0
- package/prisma/migrations/20260927040000_proxy_session_upstream_failure/migration.sql +3 -0
- package/prisma/migrations/20260927050000_coding_run_services/migration.sql +74 -0
- package/prisma/migrations/20260928000000_coding_run_tool_image/migration.sql +5 -0
- package/prisma/migrations/20260930000000_jira_issue_projects/migration.sql +34 -0
- package/prisma/migrations/20261001000000_jira_phase2_allowlists/migration.sql +3 -0
- package/prisma/migrations/20261001010000_jira_link_types_allowlist/migration.sql +2 -0
- package/prisma/migrations/20261002000000_jira_coding_bridge/migration.sql +28 -0
- package/prisma/migrations/20261002010000_jira_issue_creation/migration.sql +25 -0
- package/prisma/migrations/20261003000000_issue_cost_attribution/migration.sql +56 -0
- package/prisma/migrations/20261003010000_coding_run_service_status/migration.sql +23 -0
- package/prisma/migrations/20261003020000_viewer_notify/migration.sql +54 -0
- package/prisma/migrations/20261003030000_viewer_notify_fixes/migration.sql +47 -0
- package/prisma/migrations/20261003040000_viewer_indexes/migration.sql +12 -0
- package/prisma/migrations/20261004000000_model_catalog/migration.sql +26 -0
- package/prisma/schema.prisma +663 -21
- package/dist/mcp/tools/models.d.ts +0 -8
- package/dist/mcp/tools/models.js +0 -15
- package/dist/providers/llm/pricing-anthropic.d.ts +0 -4
- package/dist/providers/llm/pricing-anthropic.js +0 -30
- package/dist/providers/llm/pricing-bedrock-claude.d.ts +0 -12
- package/dist/providers/llm/pricing-bedrock-claude.js +0 -37
- package/dist/providers/llm/pricing.d.ts +0 -30
- package/dist/providers/llm/pricing.js +0 -74
|
@@ -3,8 +3,23 @@ export const CODING_WORKER_UID = 10001;
|
|
|
3
3
|
export const CODING_WORKER_GID = 10001;
|
|
4
4
|
// The keeper needs headroom for runc's short-lived exec process plus Node's threads.
|
|
5
5
|
const KEEPER_PIDS_LIMIT = 32;
|
|
6
|
+
/**
|
|
7
|
+
* The network keeper (a run with services only): owns the run's network namespace so each
|
|
8
|
+
* service and the worker can share it with `--network container:`, as a pod's containers do.
|
|
9
|
+
*/
|
|
10
|
+
export const NETWORK_KEEPER_PIDS_LIMIT = 32;
|
|
11
|
+
export const NETWORK_KEEPER_MEMORY_MB = 64;
|
|
12
|
+
/** `--cpus 0.1`, as Docker reports it. */
|
|
13
|
+
const NETWORK_KEEPER_NANO_CPUS = 100_000_000;
|
|
14
|
+
/** Idles until stopped, on the worker image's own `node` (every worker image ships it for the storage keeper). */
|
|
15
|
+
export const NETWORK_KEEPER_SCRIPT = "process.once('SIGTERM', () => process.exit(0)); setInterval(() => {}, 2147483647);";
|
|
6
16
|
export const CODING_PROXY_ALIAS = "wardby-proxy";
|
|
7
17
|
export const CODING_PROXY_PORT = 8787;
|
|
18
|
+
/**
|
|
19
|
+
* The one variable Claude's tool runner receives: its package-registry and service settings as JSON
|
|
20
|
+
* (claude-tool-setup.ts). Never the run capability.
|
|
21
|
+
*/
|
|
22
|
+
export const CLAUDE_TOOL_SETUP_ENV = "WARDBY_TOOL_SETUP";
|
|
8
23
|
/**
|
|
9
24
|
* The proxy's deny port. Nothing is served here (see coding-proxy/deny-port.ts);
|
|
10
25
|
* it is the enforcement witness — the one destination a run's NetworkPolicy must
|
|
@@ -50,6 +65,14 @@ function assertIntegerRange(value, min, max) {
|
|
|
50
65
|
if (!Number.isSafeInteger(value) || value < min || value > max)
|
|
51
66
|
throw isolationError();
|
|
52
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Which runs the Docker launcher starts services for: Codex and Claude Code. The container that runs
|
|
70
|
+
* the run's commands (Codex's worker, Claude Code's tool runner) joins the network keeper's
|
|
71
|
+
* namespace and reaches the services on 127.0.0.1; Claude Code's agent stays on the run network.
|
|
72
|
+
*/
|
|
73
|
+
export function dockerSupportsServicesFor(provider) {
|
|
74
|
+
return provider === "codex" || provider === "claude-code";
|
|
75
|
+
}
|
|
53
76
|
function validateSpec(spec) {
|
|
54
77
|
if (spec.kind !== "coding-agent" || !/^[a-zA-Z0-9][a-zA-Z0-9_.:-]{0,199}$/.test(spec.runId)) {
|
|
55
78
|
throw isolationError();
|
|
@@ -61,12 +84,18 @@ function validateSpec(spec) {
|
|
|
61
84
|
if (spec.provider === "claude-code") {
|
|
62
85
|
if (!spec.toolImage || !isImmutableDockerImage(spec.toolImage))
|
|
63
86
|
throw isolationError();
|
|
64
|
-
if (spec.limits.cpus < 0.35 || spec.limits.memoryMb < 256 || spec.limits.pids <
|
|
87
|
+
if (spec.limits.cpus < 0.35 || spec.limits.memoryMb < 256 || spec.limits.pids < CLAUDE_MIN_PIDS)
|
|
65
88
|
throw isolationError();
|
|
66
89
|
}
|
|
67
90
|
else if (spec.toolImage !== undefined) {
|
|
68
91
|
throw isolationError();
|
|
69
92
|
}
|
|
93
|
+
if (spec.services !== undefined) {
|
|
94
|
+
// docker-services.ts re-validates every entry against the catalog schema. Both providers' shells
|
|
95
|
+
// share the network keeper's namespace: Codex's worker, and Claude Code's tool runner.
|
|
96
|
+
if (spec.services.length === 0)
|
|
97
|
+
throw isolationError();
|
|
98
|
+
}
|
|
70
99
|
assertFiniteRange(spec.limits.cpus, 0.1, 32);
|
|
71
100
|
assertIntegerRange(spec.limits.memoryMb, 128, 65_536);
|
|
72
101
|
assertIntegerRange(spec.limits.pids, 16, 4_096);
|
|
@@ -85,21 +114,33 @@ function validateDockerObject(value) {
|
|
|
85
114
|
if (!DOCKER_OBJECT.test(value))
|
|
86
115
|
throw isolationError();
|
|
87
116
|
}
|
|
88
|
-
|
|
117
|
+
/** The opaque per-run token in every Docker object name; never the run ID itself. */
|
|
118
|
+
export function isolationToken(runId) {
|
|
89
119
|
if (!/^[a-zA-Z0-9][a-zA-Z0-9_.:-]{0,199}$/.test(runId))
|
|
90
120
|
throw isolationError();
|
|
91
|
-
|
|
121
|
+
return runHash(runId).slice(0, 20);
|
|
122
|
+
}
|
|
123
|
+
export function isolationNames(runId) {
|
|
124
|
+
const token = isolationToken(runId);
|
|
92
125
|
return {
|
|
93
126
|
network: `wardby-net-${token}`,
|
|
94
127
|
storageVolume: `wardby-storage-${token}`,
|
|
95
128
|
keeperContainer: `wardby-keeper-${token}`,
|
|
96
129
|
workerContainer: `wardby-worker-${token}`,
|
|
97
130
|
toolContainer: `wardby-tools-${token}`,
|
|
131
|
+
networkKeeperContainer: `wardby-netns-${token}`,
|
|
98
132
|
};
|
|
99
133
|
}
|
|
100
134
|
function labels(runId) {
|
|
101
135
|
return ["--label", LABEL_MANAGED, "--label", LABEL_COMPONENT, "--label", `io.wardby.run-sha256=${runHash(runId)}`];
|
|
102
136
|
}
|
|
137
|
+
/** The fixed isolation labels every run object carries (docker-services.ts builds with them). */
|
|
138
|
+
export function isolationLabelArgs(runId) {
|
|
139
|
+
return labels(runId);
|
|
140
|
+
}
|
|
141
|
+
export function hasIsolationLabels(labels, runId) {
|
|
142
|
+
return hasResourceLabels(labels, runId);
|
|
143
|
+
}
|
|
103
144
|
function storageMountOptions(spec) {
|
|
104
145
|
const inodes = Math.max(4_096, Math.min(262_144, spec.limits.diskMb * 256));
|
|
105
146
|
return `size=${spec.limits.diskMb}m,nr_inodes=${inodes},uid=${CODING_WORKER_UID},gid=${CODING_WORKER_GID},mode=0700,nosuid,nodev`;
|
|
@@ -192,6 +233,68 @@ export function buildKeeperCreateArgs(spec) {
|
|
|
192
233
|
"/opt/wardby/coding-worker/keeper.js",
|
|
193
234
|
];
|
|
194
235
|
}
|
|
236
|
+
export function buildNetworkKeeperCreateArgs(spec) {
|
|
237
|
+
validateSpec(spec);
|
|
238
|
+
if (spec.services === undefined)
|
|
239
|
+
throw isolationError();
|
|
240
|
+
const names = isolationNames(spec.runId);
|
|
241
|
+
return [
|
|
242
|
+
"container",
|
|
243
|
+
"create",
|
|
244
|
+
"--name",
|
|
245
|
+
names.networkKeeperContainer,
|
|
246
|
+
"--pull",
|
|
247
|
+
"never",
|
|
248
|
+
"--user",
|
|
249
|
+
`${CODING_WORKER_UID}:${CODING_WORKER_GID}`,
|
|
250
|
+
"--network",
|
|
251
|
+
names.network,
|
|
252
|
+
"--read-only",
|
|
253
|
+
"--cap-drop",
|
|
254
|
+
"ALL",
|
|
255
|
+
"--security-opt",
|
|
256
|
+
"no-new-privileges=true",
|
|
257
|
+
"--security-opt",
|
|
258
|
+
"seccomp=builtin",
|
|
259
|
+
"--init",
|
|
260
|
+
"--cgroupns",
|
|
261
|
+
"private",
|
|
262
|
+
"--ipc",
|
|
263
|
+
"none",
|
|
264
|
+
"--cpus",
|
|
265
|
+
"0.1",
|
|
266
|
+
"--memory",
|
|
267
|
+
`${NETWORK_KEEPER_MEMORY_MB}m`,
|
|
268
|
+
"--memory-swap",
|
|
269
|
+
`${NETWORK_KEEPER_MEMORY_MB}m`,
|
|
270
|
+
"--memory-swappiness",
|
|
271
|
+
"0",
|
|
272
|
+
"--pids-limit",
|
|
273
|
+
String(NETWORK_KEEPER_PIDS_LIMIT),
|
|
274
|
+
"--restart",
|
|
275
|
+
"no",
|
|
276
|
+
"--log-driver",
|
|
277
|
+
"local",
|
|
278
|
+
"--log-opt",
|
|
279
|
+
"max-size=1m",
|
|
280
|
+
"--log-opt",
|
|
281
|
+
"max-file=2",
|
|
282
|
+
...labels(spec.runId),
|
|
283
|
+
"--entrypoint",
|
|
284
|
+
"node",
|
|
285
|
+
spec.image,
|
|
286
|
+
"-e",
|
|
287
|
+
NETWORK_KEEPER_SCRIPT,
|
|
288
|
+
];
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* The network of the container that runs the run's commands (Codex's worker, Claude Code's tool
|
|
292
|
+
* runner): the run network, or the network keeper's namespace for a run with services.
|
|
293
|
+
*/
|
|
294
|
+
function workerNetworkMode(spec) {
|
|
295
|
+
const names = isolationNames(spec.runId);
|
|
296
|
+
return spec.services === undefined ? names.network : `container:${names.networkKeeperContainer}`;
|
|
297
|
+
}
|
|
195
298
|
export function buildWorkerCreateArgs(spec, proxyPort = CODING_PROXY_PORT) {
|
|
196
299
|
validateSpec(spec);
|
|
197
300
|
assertIntegerRange(proxyPort, 1, 65_535);
|
|
@@ -207,7 +310,7 @@ export function buildWorkerCreateArgs(spec, proxyPort = CODING_PROXY_PORT) {
|
|
|
207
310
|
"--user",
|
|
208
311
|
`${CODING_WORKER_UID}:${CODING_WORKER_GID}`,
|
|
209
312
|
"--network",
|
|
210
|
-
|
|
313
|
+
workerNetworkMode(spec),
|
|
211
314
|
"--read-only",
|
|
212
315
|
"--cap-drop",
|
|
213
316
|
"ALL",
|
|
@@ -262,11 +365,15 @@ export function buildWorkerCreateArgs(spec, proxyPort = CODING_PROXY_PORT) {
|
|
|
262
365
|
spec.image,
|
|
263
366
|
];
|
|
264
367
|
}
|
|
265
|
-
|
|
368
|
+
/** The tool runner's shell + npm shim (two Node processes) + npm peak near 30 PIDs; 64 leaves headroom. */
|
|
369
|
+
const CLAUDE_TOOL_PIDS = 64;
|
|
370
|
+
/** A Claude run must leave its agent at least 32 PIDs after the tool runner's share. */
|
|
371
|
+
const CLAUDE_MIN_PIDS = CLAUDE_TOOL_PIDS + 32;
|
|
372
|
+
export function claudeToolLimits(spec) {
|
|
266
373
|
return {
|
|
267
374
|
cpus: 0.25,
|
|
268
375
|
memoryMb: Math.min(512, Math.max(128, Math.floor(spec.limits.memoryMb / 3))),
|
|
269
|
-
pids:
|
|
376
|
+
pids: CLAUDE_TOOL_PIDS,
|
|
270
377
|
};
|
|
271
378
|
}
|
|
272
379
|
function claudeAgentLimits(spec) {
|
|
@@ -351,7 +458,14 @@ export function buildClaudeAgentCreateArgs(spec, proxyPort = CODING_PROXY_PORT)
|
|
|
351
458
|
spec.image,
|
|
352
459
|
];
|
|
353
460
|
}
|
|
354
|
-
/**
|
|
461
|
+
/**
|
|
462
|
+
* The tool runner receives only the checkout and its own storage subpath; it shares the run's
|
|
463
|
+
* proxy network with the agent (one room) but holds no capability, so the proxy grants it only
|
|
464
|
+
* registry access via its own WARDBY_TOOL_SETUP value. For a run with services it joins the network
|
|
465
|
+
* keeper's namespace instead, which is on that same run network: it reaches the proxy by its alias
|
|
466
|
+
* and the services on 127.0.0.1, and nothing else. It talks to the agent over the Unix socket in
|
|
467
|
+
* their shared `tool` storage subpath, which does not depend on either container's network.
|
|
468
|
+
*/
|
|
355
469
|
export function buildClaudeToolRunnerCreateArgs(spec) {
|
|
356
470
|
validateSpec(spec);
|
|
357
471
|
if (spec.provider !== "claude-code" || !spec.toolImage)
|
|
@@ -368,8 +482,11 @@ export function buildClaudeToolRunnerCreateArgs(spec) {
|
|
|
368
482
|
"never",
|
|
369
483
|
"--user",
|
|
370
484
|
`${CODING_WORKER_UID}:${CODING_WORKER_GID}`,
|
|
485
|
+
// One room (docs/coding-worker-isolation.md): the run's proxy network, like the agent, so npm and
|
|
486
|
+
// pip reach the registry. It holds no capability, so the proxy refuses it anything else. With
|
|
487
|
+
// services: the network keeper's namespace on that network, so the services answer on 127.0.0.1.
|
|
371
488
|
"--network",
|
|
372
|
-
|
|
489
|
+
workerNetworkMode(spec),
|
|
373
490
|
"--read-only",
|
|
374
491
|
"--cap-drop",
|
|
375
492
|
"ALL",
|
|
@@ -402,6 +519,9 @@ export function buildClaudeToolRunnerCreateArgs(spec) {
|
|
|
402
519
|
`type=volume,src=${names.storageVolume},dst=${WORKER_PATHS.workspace},volume-subpath=workspace,volume-nocopy`,
|
|
403
520
|
"--mount",
|
|
404
521
|
`type=volume,src=${names.storageVolume},dst=${WORKER_PATHS.tool},volume-subpath=tool,volume-nocopy`,
|
|
522
|
+
// By name only: the value (claude-tool-setup.ts) is in the docker CLI's environment, never argv.
|
|
523
|
+
"--env",
|
|
524
|
+
CLAUDE_TOOL_SETUP_ENV,
|
|
405
525
|
"--restart",
|
|
406
526
|
"no",
|
|
407
527
|
"--stop-signal",
|
|
@@ -434,6 +554,7 @@ export function buildDockerIsolationPlan(spec, proxyContainer) {
|
|
|
434
554
|
keeperCreateArgs: buildKeeperCreateArgs(spec),
|
|
435
555
|
workerCreateArgs: spec.provider === "claude-code" ? buildClaudeAgentCreateArgs(spec) : buildWorkerCreateArgs(spec),
|
|
436
556
|
...(spec.provider === "claude-code" ? { toolCreateArgs: buildClaudeToolRunnerCreateArgs(spec) } : {}),
|
|
557
|
+
...(spec.services !== undefined ? { networkKeeperCreateArgs: buildNetworkKeeperCreateArgs(spec) } : {}),
|
|
437
558
|
proxyNetworkConnectArgs: buildProxyNetworkConnectArgs(spec.runId, proxyContainer),
|
|
438
559
|
};
|
|
439
560
|
}
|
|
@@ -520,6 +641,77 @@ export function assertKeeperContainerInspection(container, spec) {
|
|
|
520
641
|
throw isolationError();
|
|
521
642
|
}
|
|
522
643
|
}
|
|
644
|
+
export function assertNetworkKeeperContainerInspection(container, spec) {
|
|
645
|
+
validateSpec(spec);
|
|
646
|
+
if (spec.services === undefined)
|
|
647
|
+
throw isolationError();
|
|
648
|
+
const names = isolationNames(spec.runId);
|
|
649
|
+
const host = container.HostConfig;
|
|
650
|
+
const security = host?.SecurityOpt ?? [];
|
|
651
|
+
if (container.Config?.User !== `${CODING_WORKER_UID}:${CODING_WORKER_GID}` ||
|
|
652
|
+
container.Config?.Image !== spec.image ||
|
|
653
|
+
!hasResourceLabels(container.Config.Labels, spec.runId) ||
|
|
654
|
+
host?.NetworkMode !== names.network ||
|
|
655
|
+
host.ReadonlyRootfs !== true ||
|
|
656
|
+
host.Privileged !== false ||
|
|
657
|
+
(host.Binds?.length ?? 0) !== 0 ||
|
|
658
|
+
(host.CapAdd?.length ?? 0) !== 0 ||
|
|
659
|
+
!host.CapDrop?.includes("ALL") ||
|
|
660
|
+
!security.includes("no-new-privileges=true") ||
|
|
661
|
+
!security.includes("seccomp=builtin") ||
|
|
662
|
+
host.PidsLimit !== NETWORK_KEEPER_PIDS_LIMIT ||
|
|
663
|
+
host.Memory !== NETWORK_KEEPER_MEMORY_MB * 1024 * 1024 ||
|
|
664
|
+
host.MemorySwap !== host.Memory ||
|
|
665
|
+
// cgroup v2 hosts may report this as null after accepting the explicit
|
|
666
|
+
// no-swappiness request; MemorySwap still attests that swap is disabled.
|
|
667
|
+
(host.MemorySwappiness !== 0 && host.MemorySwappiness !== null) ||
|
|
668
|
+
host.NanoCpus !== NETWORK_KEEPER_NANO_CPUS ||
|
|
669
|
+
host.CgroupnsMode !== "private" ||
|
|
670
|
+
host.IpcMode !== "none" ||
|
|
671
|
+
host.PidMode !== "" ||
|
|
672
|
+
host.Init !== true ||
|
|
673
|
+
host.RestartPolicy?.Name !== "no" ||
|
|
674
|
+
host.LogConfig?.Type !== "local" ||
|
|
675
|
+
host.LogConfig.Config?.["max-size"] !== "1m" ||
|
|
676
|
+
host.LogConfig.Config?.["max-file"] !== "2" ||
|
|
677
|
+
(host.Devices?.length ?? 0) !== 0 ||
|
|
678
|
+
(host.DeviceRequests?.length ?? 0) !== 0 ||
|
|
679
|
+
(host.GroupAdd?.length ?? 0) !== 0 ||
|
|
680
|
+
// The worker and services take their DNS and port view from this container.
|
|
681
|
+
(host.Dns?.length ?? 0) !== 0 ||
|
|
682
|
+
(host.DnsOptions?.length ?? 0) !== 0 ||
|
|
683
|
+
(host.DnsSearch?.length ?? 0) !== 0 ||
|
|
684
|
+
(host.ExtraHosts?.length ?? 0) !== 0 ||
|
|
685
|
+
Object.keys(host.PortBindings ?? {}).length !== 0 ||
|
|
686
|
+
host.PublishAllPorts !== false ||
|
|
687
|
+
Object.keys(container.NetworkSettings?.Ports ?? {}).length !== 0 ||
|
|
688
|
+
(host.Mounts?.length ?? 0) !== 0 ||
|
|
689
|
+
(container.Mounts?.length ?? 0) !== 0 ||
|
|
690
|
+
Object.keys(container.NetworkSettings?.Networks ?? {}).join(",") !== names.network ||
|
|
691
|
+
// Exactly the idle script buildNetworkKeeperCreateArgs runs, never the image's own entrypoint.
|
|
692
|
+
JSON.stringify(container.Config.Entrypoint ?? null) !== JSON.stringify(["node"]) ||
|
|
693
|
+
JSON.stringify(container.Config.Cmd ?? null) !== JSON.stringify(["-e", NETWORK_KEEPER_SCRIPT])) {
|
|
694
|
+
throw isolationError();
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* The worker (Codex) or tool runner (Claude Code) of a run without services is on the run network
|
|
699
|
+
* only. With services it is in the network keeper's namespace: Docker reports `container:<name>`
|
|
700
|
+
* before start and `container:<id>` after, and lists no networks of its own.
|
|
701
|
+
*/
|
|
702
|
+
function workerNetworkAttested(container, spec, networkKeeperId) {
|
|
703
|
+
const names = isolationNames(spec.runId);
|
|
704
|
+
const mode = container.HostConfig?.NetworkMode;
|
|
705
|
+
const networks = Object.keys(container.NetworkSettings?.Networks ?? {});
|
|
706
|
+
if (spec.services === undefined) {
|
|
707
|
+
return mode === names.network && networks.length === 1 && networks[0] === names.network;
|
|
708
|
+
}
|
|
709
|
+
const shared = [
|
|
710
|
+
`container:${names.networkKeeperContainer}`,
|
|
711
|
+
...(networkKeeperId ? [`container:${networkKeeperId}`] : []),
|
|
712
|
+
];
|
|
713
|
+
return mode !== undefined && shared.includes(mode) && networks.length === 0;
|
|
714
|
+
}
|
|
523
715
|
export function assertProxyContainerInspection(container, runId) {
|
|
524
716
|
const internalNetwork = isolationNames(runId).network;
|
|
525
717
|
const networks = container.NetworkSettings?.Networks ?? {};
|
|
@@ -571,11 +763,10 @@ function assertExactWorkerMounts(container, names) {
|
|
|
571
763
|
}
|
|
572
764
|
}
|
|
573
765
|
}
|
|
574
|
-
export function assertWorkerContainerInspection(container, spec, expectedCapability) {
|
|
766
|
+
export function assertWorkerContainerInspection(container, spec, expectedCapability, networkKeeperId) {
|
|
575
767
|
validateSpec(spec);
|
|
576
768
|
const names = isolationNames(spec.runId);
|
|
577
769
|
const host = container.HostConfig;
|
|
578
|
-
const networks = Object.keys(container.NetworkSettings?.Networks ?? {});
|
|
579
770
|
const security = host?.SecurityOpt ?? [];
|
|
580
771
|
const wardbyEnvironment = (container.Config?.Env ?? []).filter((value) => value.startsWith("WARDBY_"));
|
|
581
772
|
if (container.Config?.User !== `${CODING_WORKER_UID}:${CODING_WORKER_GID}` ||
|
|
@@ -594,7 +785,7 @@ export function assertWorkerContainerInspection(container, spec, expectedCapabil
|
|
|
594
785
|
host.PidsLimit !== spec.limits.pids ||
|
|
595
786
|
host.NanoCpus !== Math.round(spec.limits.cpus * 1_000_000_000) ||
|
|
596
787
|
host.ShmSize !== 16 * 1024 * 1024 ||
|
|
597
|
-
|
|
788
|
+
!workerNetworkAttested(container, spec, networkKeeperId) ||
|
|
598
789
|
host.PidMode !== "" ||
|
|
599
790
|
host.RestartPolicy?.Name !== "no" ||
|
|
600
791
|
host.LogConfig?.Type !== "local" ||
|
|
@@ -611,9 +802,7 @@ export function assertWorkerContainerInspection(container, spec, expectedCapabil
|
|
|
611
802
|
(host.GroupAdd?.length ?? 0) !== 0 ||
|
|
612
803
|
Object.keys(host.PortBindings ?? {}).length !== 0 ||
|
|
613
804
|
host.PublishAllPorts !== false ||
|
|
614
|
-
Object.keys(container.NetworkSettings?.Ports ?? {}).length !== 0
|
|
615
|
-
networks.length !== 1 ||
|
|
616
|
-
networks[0] !== names.network) {
|
|
805
|
+
Object.keys(container.NetworkSettings?.Ports ?? {}).length !== 0) {
|
|
617
806
|
throw isolationError();
|
|
618
807
|
}
|
|
619
808
|
if (wardbyEnvironment.length !== 2 ||
|
|
@@ -645,7 +834,7 @@ function assertExactMountSet(container, names, expected) {
|
|
|
645
834
|
throw isolationError();
|
|
646
835
|
}
|
|
647
836
|
}
|
|
648
|
-
function assertClaudeContainerBaseline(container, image,
|
|
837
|
+
function assertClaudeContainerBaseline(container, image, spec, limits) {
|
|
649
838
|
const host = container.HostConfig;
|
|
650
839
|
const security = host?.SecurityOpt ?? [];
|
|
651
840
|
if (container.Config?.User !== `${CODING_WORKER_UID}:${CODING_WORKER_GID}` ||
|
|
@@ -667,7 +856,6 @@ function assertClaudeContainerBaseline(container, image, network, spec, limits)
|
|
|
667
856
|
host.PidsLimit !== limits.pids ||
|
|
668
857
|
host.NanoCpus !== Math.round(limits.cpus * 1_000_000_000) ||
|
|
669
858
|
host.ShmSize !== 16 * 1024 * 1024 ||
|
|
670
|
-
host.NetworkMode !== network ||
|
|
671
859
|
host.PidMode !== "" ||
|
|
672
860
|
host.RestartPolicy?.Name !== "no" ||
|
|
673
861
|
host.LogConfig?.Type !== "local" ||
|
|
@@ -693,7 +881,7 @@ export function assertClaudeAgentContainerInspection(container, spec, expectedCa
|
|
|
693
881
|
if (spec.provider !== "claude-code")
|
|
694
882
|
throw isolationError();
|
|
695
883
|
const names = isolationNames(spec.runId);
|
|
696
|
-
assertClaudeContainerBaseline(container, spec.image,
|
|
884
|
+
assertClaudeContainerBaseline(container, spec.image, spec, claudeAgentLimits(spec));
|
|
697
885
|
const environment = (container.Config?.Env ?? []).filter((value) => value.startsWith("WARDBY_"));
|
|
698
886
|
if (environment.length !== 2 ||
|
|
699
887
|
!environment.includes(`WARDBY_PROXY_URL=http://${CODING_PROXY_ALIAS}:${CODING_PROXY_PORT}`) ||
|
|
@@ -704,18 +892,21 @@ export function assertClaudeAgentContainerInspection(container, spec, expectedCa
|
|
|
704
892
|
{ path: WORKER_PATHS.output, writable: true, subpath: "output" },
|
|
705
893
|
{ path: WORKER_PATHS.tool, writable: true, subpath: "tool" },
|
|
706
894
|
]);
|
|
707
|
-
|
|
895
|
+
// The agent holds the capability: always on the run network only, never in the keeper's namespace.
|
|
896
|
+
if (container.HostConfig?.NetworkMode !== names.network ||
|
|
897
|
+
Object.keys(container.NetworkSettings?.Networks ?? {}).join(",") !== names.network)
|
|
708
898
|
throw isolationError();
|
|
709
899
|
}
|
|
710
|
-
export function assertClaudeToolRunnerContainerInspection(container, spec) {
|
|
900
|
+
export function assertClaudeToolRunnerContainerInspection(container, spec, expectedSetup, networkKeeperId) {
|
|
711
901
|
validateSpec(spec);
|
|
712
902
|
if (spec.provider !== "claude-code" || !spec.toolImage)
|
|
713
903
|
throw isolationError();
|
|
714
904
|
const names = isolationNames(spec.runId);
|
|
715
|
-
assertClaudeContainerBaseline(container, spec.toolImage,
|
|
716
|
-
|
|
905
|
+
assertClaudeContainerBaseline(container, spec.toolImage, spec, claudeToolLimits(spec));
|
|
906
|
+
const environment = (container.Config?.Env ?? []).filter((value) => value.startsWith("WARDBY_"));
|
|
907
|
+
if (environment.length !== 1 || environment[0] !== `${CLAUDE_TOOL_SETUP_ENV}=${expectedSetup}`)
|
|
717
908
|
throw isolationError();
|
|
718
|
-
if (
|
|
909
|
+
if (!workerNetworkAttested(container, spec, networkKeeperId))
|
|
719
910
|
throw isolationError();
|
|
720
911
|
assertExactMountSet(container, names, [
|
|
721
912
|
{ path: WORKER_PATHS.workspace, writable: true, subpath: "workspace" },
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type ResolvedCodingService } from "../../coding/services/catalog.js";
|
|
2
|
+
import { type DockerContainerInspection } from "./docker-isolation.js";
|
|
3
|
+
import type { JobSpec } from "./types.js";
|
|
4
|
+
/** Every Docker run container is PID-bounded; the catalog has no PID field. */
|
|
5
|
+
export declare const SERVICE_PIDS_LIMIT = 512;
|
|
6
|
+
/** /dev/shm for a service (PostgreSQL's dynamic shared memory): the Kubernetes default size. */
|
|
7
|
+
export declare const SERVICE_SHM_MIB = 64;
|
|
8
|
+
export interface DockerServiceContainer {
|
|
9
|
+
service: ResolvedCodingService;
|
|
10
|
+
container: string;
|
|
11
|
+
createArgs: string[];
|
|
12
|
+
}
|
|
13
|
+
export declare function dockerServiceContainerName(runId: string, serviceName: string): string;
|
|
14
|
+
/** The container names of a spec's services, for stop and cleanup: names only, never launch input. */
|
|
15
|
+
export declare function dockerServiceContainerNames(spec: JobSpec): string[];
|
|
16
|
+
/**
|
|
17
|
+
* Re-validated here, not trusted from dispatch, as the Kubernetes launcher does. Returns the
|
|
18
|
+
* schema's parsed output (defaults such as `serviceEnv`/`writablePaths` filled in), never the raw
|
|
19
|
+
* spec, so a caller never has to guard against a missing default field, and any shape the schema
|
|
20
|
+
* rejects throws the isolation error rather than surfacing as a TypeError further down.
|
|
21
|
+
*/
|
|
22
|
+
export declare function validateDockerServices(spec: JobSpec): ResolvedCodingService[];
|
|
23
|
+
/** Its data path at its disk size, then SERVICE_SCRATCH_MIB for each writable path: the Kubernetes sizes. */
|
|
24
|
+
export declare function serviceTmpfsOptions(service: ResolvedCodingService): Record<string, string>;
|
|
25
|
+
/** tmpfs pages count against the container's memory cgroup: catalog memory, its disk, and /dev/shm. */
|
|
26
|
+
export declare function serviceMemoryMib(service: ResolvedCodingService): number;
|
|
27
|
+
export declare function buildServiceCreateArgs(spec: JobSpec, service: ResolvedCodingService): string[];
|
|
28
|
+
export declare function buildDockerServicePlan(spec: JobSpec): DockerServiceContainer[];
|
|
29
|
+
/**
|
|
30
|
+
* Before a service starts: exactly the controls buildServiceCreateArgs asked for. No mounts at all,
|
|
31
|
+
* so an image VOLUME outside the entry's dataPath/writablePaths (an anonymous, unbounded host
|
|
32
|
+
* volume) fails here rather than run. Re-validates the spec and requires `service` to be one of its
|
|
33
|
+
* validated entries (by name and version), rather than trusting the caller's pairing of the two.
|
|
34
|
+
*/
|
|
35
|
+
export declare function assertServiceContainerInspection(container: DockerContainerInspection, spec: JobSpec, service: ResolvedCodingService, networkKeeperId?: string): void;
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coding-run services on the Docker launcher (docs/coding-services.md): one hardened container per
|
|
3
|
+
* resolved catalog entry, in the run's network keeper's namespace, so it answers on 127.0.0.1
|
|
4
|
+
* exactly like a Kubernetes sidecar. Pure builders and attestation only: DockerJobLauncher runs
|
|
5
|
+
* and checks them. The image and environment come from the admin-managed catalog snapshot on the
|
|
6
|
+
* spec, never from repository content, and are re-validated here rather than trusted from dispatch.
|
|
7
|
+
*/
|
|
8
|
+
import { CODING_SERVICE_NAME } from "../../coding/protocol.js";
|
|
9
|
+
import { StoredCodingServicesSchema } from "../../coding/services/catalog.js";
|
|
10
|
+
import { CODING_WORKER_GID, CODING_WORKER_UID, DOCKER_ISOLATION_ERROR, hasIsolationLabels, isolationLabelArgs, isolationNames, isolationToken, } from "./docker-isolation.js";
|
|
11
|
+
import { SERVICE_SCRATCH_MIB, serviceEphemeralStorageMib } from "./kubernetes-isolation.js";
|
|
12
|
+
/** Every Docker run container is PID-bounded; the catalog has no PID field. */
|
|
13
|
+
export const SERVICE_PIDS_LIMIT = 512;
|
|
14
|
+
/** /dev/shm for a service (PostgreSQL's dynamic shared memory): the Kubernetes default size. */
|
|
15
|
+
export const SERVICE_SHM_MIB = 64;
|
|
16
|
+
function isolationError() {
|
|
17
|
+
return new Error(DOCKER_ISOLATION_ERROR);
|
|
18
|
+
}
|
|
19
|
+
export function dockerServiceContainerName(runId, serviceName) {
|
|
20
|
+
if (!CODING_SERVICE_NAME.test(serviceName))
|
|
21
|
+
throw isolationError();
|
|
22
|
+
return `wardby-svc-${isolationToken(runId)}-${serviceName}`;
|
|
23
|
+
}
|
|
24
|
+
/** The container names of a spec's services, for stop and cleanup: names only, never launch input. */
|
|
25
|
+
export function dockerServiceContainerNames(spec) {
|
|
26
|
+
return (spec.services ?? [])
|
|
27
|
+
.filter((service) => CODING_SERVICE_NAME.test(service.name))
|
|
28
|
+
.map((service) => dockerServiceContainerName(spec.runId, service.name));
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Re-validated here, not trusted from dispatch, as the Kubernetes launcher does. Returns the
|
|
32
|
+
* schema's parsed output (defaults such as `serviceEnv`/`writablePaths` filled in), never the raw
|
|
33
|
+
* spec, so a caller never has to guard against a missing default field, and any shape the schema
|
|
34
|
+
* rejects throws the isolation error rather than surfacing as a TypeError further down.
|
|
35
|
+
*/
|
|
36
|
+
export function validateDockerServices(spec) {
|
|
37
|
+
if (spec.services === undefined)
|
|
38
|
+
return [];
|
|
39
|
+
const names = spec.services.map((service) => service.name);
|
|
40
|
+
if (spec.services.length === 0 || new Set(names).size !== names.length) {
|
|
41
|
+
throw isolationError();
|
|
42
|
+
}
|
|
43
|
+
const parsed = StoredCodingServicesSchema.safeParse(spec.services);
|
|
44
|
+
if (!parsed.success)
|
|
45
|
+
throw isolationError();
|
|
46
|
+
return parsed.data;
|
|
47
|
+
}
|
|
48
|
+
/** Its data path at its disk size, then SERVICE_SCRATCH_MIB for each writable path: the Kubernetes sizes. */
|
|
49
|
+
export function serviceTmpfsOptions(service) {
|
|
50
|
+
const mounts = [
|
|
51
|
+
{ path: service.dataPath, sizeMib: service.resources.diskMib },
|
|
52
|
+
...service.writablePaths.map((path) => ({ path, sizeMib: SERVICE_SCRATCH_MIB })),
|
|
53
|
+
];
|
|
54
|
+
return Object.fromEntries(mounts.map(({ path, sizeMib }) => [
|
|
55
|
+
path,
|
|
56
|
+
`rw,nosuid,nodev,size=${sizeMib}m,uid=${CODING_WORKER_UID},gid=${CODING_WORKER_GID},mode=0700`,
|
|
57
|
+
]));
|
|
58
|
+
}
|
|
59
|
+
/** tmpfs pages count against the container's memory cgroup: catalog memory, its disk, and /dev/shm. */
|
|
60
|
+
export function serviceMemoryMib(service) {
|
|
61
|
+
return service.resources.memoryMib + serviceEphemeralStorageMib(service) + SERVICE_SHM_MIB;
|
|
62
|
+
}
|
|
63
|
+
export function buildServiceCreateArgs(spec, service) {
|
|
64
|
+
const names = isolationNames(spec.runId);
|
|
65
|
+
const memoryMib = serviceMemoryMib(service);
|
|
66
|
+
const env = Object.entries(service.serviceEnv)
|
|
67
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
68
|
+
.flatMap(([name, value]) => ["--env", `${name}=${value}`]);
|
|
69
|
+
return [
|
|
70
|
+
"container",
|
|
71
|
+
"create",
|
|
72
|
+
"--name",
|
|
73
|
+
dockerServiceContainerName(spec.runId, service.name),
|
|
74
|
+
"--pull",
|
|
75
|
+
"never",
|
|
76
|
+
"--user",
|
|
77
|
+
`${CODING_WORKER_UID}:${CODING_WORKER_GID}`,
|
|
78
|
+
"--network",
|
|
79
|
+
`container:${names.networkKeeperContainer}`,
|
|
80
|
+
"--read-only",
|
|
81
|
+
"--cap-drop",
|
|
82
|
+
"ALL",
|
|
83
|
+
"--security-opt",
|
|
84
|
+
"no-new-privileges=true",
|
|
85
|
+
"--security-opt",
|
|
86
|
+
"seccomp=builtin",
|
|
87
|
+
"--cgroupns",
|
|
88
|
+
"private",
|
|
89
|
+
"--ipc",
|
|
90
|
+
"private",
|
|
91
|
+
"--shm-size",
|
|
92
|
+
`${SERVICE_SHM_MIB}m`,
|
|
93
|
+
"--cpus",
|
|
94
|
+
String(service.resources.cpuMillicores / 1000),
|
|
95
|
+
"--memory",
|
|
96
|
+
`${memoryMib}m`,
|
|
97
|
+
"--memory-swap",
|
|
98
|
+
`${memoryMib}m`,
|
|
99
|
+
"--memory-swappiness",
|
|
100
|
+
"0",
|
|
101
|
+
"--pids-limit",
|
|
102
|
+
String(SERVICE_PIDS_LIMIT),
|
|
103
|
+
...Object.entries(serviceTmpfsOptions(service)).flatMap(([path, options]) => ["--tmpfs", `${path}:${options}`]),
|
|
104
|
+
...env,
|
|
105
|
+
"--restart",
|
|
106
|
+
"no",
|
|
107
|
+
"--log-driver",
|
|
108
|
+
"local",
|
|
109
|
+
"--log-opt",
|
|
110
|
+
"max-size=1m",
|
|
111
|
+
"--log-opt",
|
|
112
|
+
"max-file=2",
|
|
113
|
+
...isolationLabelArgs(spec.runId),
|
|
114
|
+
service.image,
|
|
115
|
+
];
|
|
116
|
+
}
|
|
117
|
+
export function buildDockerServicePlan(spec) {
|
|
118
|
+
return validateDockerServices(spec).map((service) => ({
|
|
119
|
+
service,
|
|
120
|
+
container: dockerServiceContainerName(spec.runId, service.name),
|
|
121
|
+
createArgs: buildServiceCreateArgs(spec, service),
|
|
122
|
+
}));
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Before a service starts: exactly the controls buildServiceCreateArgs asked for. No mounts at all,
|
|
126
|
+
* so an image VOLUME outside the entry's dataPath/writablePaths (an anonymous, unbounded host
|
|
127
|
+
* volume) fails here rather than run. Re-validates the spec and requires `service` to be one of its
|
|
128
|
+
* validated entries (by name and version), rather than trusting the caller's pairing of the two.
|
|
129
|
+
*/
|
|
130
|
+
export function assertServiceContainerInspection(container, spec, service, networkKeeperId) {
|
|
131
|
+
const validated = validateDockerServices(spec);
|
|
132
|
+
if (!validated.some((entry) => entry.name === service.name && entry.version === service.version)) {
|
|
133
|
+
throw isolationError();
|
|
134
|
+
}
|
|
135
|
+
const names = isolationNames(spec.runId);
|
|
136
|
+
const host = container.HostConfig;
|
|
137
|
+
const security = host?.SecurityOpt ?? [];
|
|
138
|
+
const env = container.Config?.Env ?? [];
|
|
139
|
+
const memory = serviceMemoryMib(service) * 1024 * 1024;
|
|
140
|
+
const networkModes = [
|
|
141
|
+
`container:${names.networkKeeperContainer}`,
|
|
142
|
+
...(networkKeeperId ? [`container:${networkKeeperId}`] : []),
|
|
143
|
+
];
|
|
144
|
+
const expectedTmpfs = serviceTmpfsOptions(service);
|
|
145
|
+
const actualTmpfs = host?.Tmpfs ?? {};
|
|
146
|
+
if (container.Config?.User !== `${CODING_WORKER_UID}:${CODING_WORKER_GID}` ||
|
|
147
|
+
container.Config.Image !== service.image ||
|
|
148
|
+
!hasIsolationLabels(container.Config.Labels, spec.runId) ||
|
|
149
|
+
host?.ReadonlyRootfs !== true ||
|
|
150
|
+
host.Privileged !== false ||
|
|
151
|
+
(host.Binds?.length ?? 0) !== 0 ||
|
|
152
|
+
(host.CapAdd?.length ?? 0) !== 0 ||
|
|
153
|
+
!host.CapDrop?.includes("ALL") ||
|
|
154
|
+
host.CgroupnsMode !== "private" ||
|
|
155
|
+
host.IpcMode !== "private" ||
|
|
156
|
+
host.ShmSize !== SERVICE_SHM_MIB * 1024 * 1024 ||
|
|
157
|
+
host.Memory !== memory ||
|
|
158
|
+
host.MemorySwap !== memory ||
|
|
159
|
+
// cgroup v2 hosts may report this as null after accepting the explicit
|
|
160
|
+
// no-swappiness request; MemorySwap still attests that swap is disabled.
|
|
161
|
+
(host.MemorySwappiness !== 0 && host.MemorySwappiness !== null) ||
|
|
162
|
+
host.PidsLimit !== SERVICE_PIDS_LIMIT ||
|
|
163
|
+
host.NanoCpus !== service.resources.cpuMillicores * 1_000_000 ||
|
|
164
|
+
!networkModes.includes(host.NetworkMode ?? "") ||
|
|
165
|
+
host.PidMode !== "" ||
|
|
166
|
+
host.RestartPolicy?.Name !== "no" ||
|
|
167
|
+
host.LogConfig?.Type !== "local" ||
|
|
168
|
+
host.LogConfig.Config?.["max-size"] !== "1m" ||
|
|
169
|
+
host.LogConfig.Config?.["max-file"] !== "2" ||
|
|
170
|
+
!security.includes("no-new-privileges=true") ||
|
|
171
|
+
!security.includes("seccomp=builtin") ||
|
|
172
|
+
(host.Devices?.length ?? 0) !== 0 ||
|
|
173
|
+
(host.DeviceRequests?.length ?? 0) !== 0 ||
|
|
174
|
+
(host.Dns?.length ?? 0) !== 0 ||
|
|
175
|
+
(host.DnsOptions?.length ?? 0) !== 0 ||
|
|
176
|
+
(host.DnsSearch?.length ?? 0) !== 0 ||
|
|
177
|
+
(host.ExtraHosts?.length ?? 0) !== 0 ||
|
|
178
|
+
(host.GroupAdd?.length ?? 0) !== 0 ||
|
|
179
|
+
Object.keys(host.PortBindings ?? {}).length !== 0 ||
|
|
180
|
+
host.PublishAllPorts !== false ||
|
|
181
|
+
(host.Mounts?.length ?? 0) !== 0 ||
|
|
182
|
+
(container.Mounts?.length ?? 0) !== 0 ||
|
|
183
|
+
Object.keys(container.NetworkSettings?.Networks ?? {}).length !== 0 ||
|
|
184
|
+
Object.keys(container.NetworkSettings?.Ports ?? {}).length !== 0 ||
|
|
185
|
+
Object.keys(actualTmpfs).length !== Object.keys(expectedTmpfs).length ||
|
|
186
|
+
Object.entries(expectedTmpfs).some(([path, options]) => actualTmpfs[path] !== options) ||
|
|
187
|
+
env.some((value) => value.startsWith("WARDBY_")) ||
|
|
188
|
+
Object.entries(service.serviceEnv).some(([name, value]) => !env.includes(`${name}=${value}`))) {
|
|
189
|
+
throw isolationError();
|
|
190
|
+
}
|
|
191
|
+
}
|