@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.
Files changed (412) hide show
  1. package/.env.example +131 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +2 -0
  4. package/README.md +282 -0
  5. package/bin/wardby.js +3 -0
  6. package/deploy/local/docker-compose.yml +19 -0
  7. package/dist/claude-coding-worker/driver.d.ts +70 -0
  8. package/dist/claude-coding-worker/driver.js +128 -0
  9. package/dist/claude-coding-worker/main.d.ts +2 -0
  10. package/dist/claude-coding-worker/main.js +41 -0
  11. package/dist/claude-coding-worker/sdk.d.ts +4 -0
  12. package/dist/claude-coding-worker/sdk.js +43 -0
  13. package/dist/claude-coding-worker/tool-relay.d.ts +1 -0
  14. package/dist/claude-coding-worker/tool-relay.js +9 -0
  15. package/dist/cli-help.d.ts +1 -0
  16. package/dist/cli-help.js +20 -0
  17. package/dist/cli.d.ts +18 -0
  18. package/dist/cli.js +629 -0
  19. package/dist/coding/observability.d.ts +59 -0
  20. package/dist/coding/observability.js +77 -0
  21. package/dist/coding/profile.d.ts +88 -0
  22. package/dist/coding/profile.js +137 -0
  23. package/dist/coding/protocol.d.ts +345 -0
  24. package/dist/coding/protocol.js +445 -0
  25. package/dist/coding/provider.d.ts +4 -0
  26. package/dist/coding/provider.js +18 -0
  27. package/dist/coding-proxy/main.d.ts +1 -0
  28. package/dist/coding-proxy/main.js +51 -0
  29. package/dist/coding-worker/artifact.d.ts +16 -0
  30. package/dist/coding-worker/artifact.js +37 -0
  31. package/dist/coding-worker/driver.d.ts +46 -0
  32. package/dist/coding-worker/driver.js +158 -0
  33. package/dist/coding-worker/errors.d.ts +1 -0
  34. package/dist/coding-worker/errors.js +23 -0
  35. package/dist/coding-worker/isolation-probe.d.ts +1 -0
  36. package/dist/coding-worker/isolation-probe.js +146 -0
  37. package/dist/coding-worker/keeper.d.ts +4 -0
  38. package/dist/coding-worker/keeper.js +29 -0
  39. package/dist/coding-worker/main.d.ts +2 -0
  40. package/dist/coding-worker/main.js +43 -0
  41. package/dist/coding-worker/sdk.d.ts +26 -0
  42. package/dist/coding-worker/sdk.js +28 -0
  43. package/dist/coding-worker/types.d.ts +64 -0
  44. package/dist/coding-worker/types.js +1 -0
  45. package/dist/config/providers.d.ts +158 -0
  46. package/dist/config/providers.js +162 -0
  47. package/dist/core/budget-groups.d.ts +72 -0
  48. package/dist/core/budget-groups.js +149 -0
  49. package/dist/core/budget.d.ts +63 -0
  50. package/dist/core/budget.js +69 -0
  51. package/dist/core/coding-queue.d.ts +32 -0
  52. package/dist/core/coding-queue.js +54 -0
  53. package/dist/core/cron.d.ts +31 -0
  54. package/dist/core/cron.js +43 -0
  55. package/dist/core/datastores.d.ts +41 -0
  56. package/dist/core/datastores.js +105 -0
  57. package/dist/core/db.d.ts +2 -0
  58. package/dist/core/db.js +2 -0
  59. package/dist/core/dispatch.d.ts +81 -0
  60. package/dist/core/dispatch.js +184 -0
  61. package/dist/core/engine-native.d.ts +29 -0
  62. package/dist/core/engine-native.js +259 -0
  63. package/dist/core/http-runtime.d.ts +7 -0
  64. package/dist/core/http-runtime.js +101 -0
  65. package/dist/core/lease.d.ts +13 -0
  66. package/dist/core/lease.js +22 -0
  67. package/dist/core/logger.d.ts +13 -0
  68. package/dist/core/logger.js +45 -0
  69. package/dist/core/memory-tools.d.ts +11 -0
  70. package/dist/core/memory-tools.js +122 -0
  71. package/dist/core/reconciler.d.ts +45 -0
  72. package/dist/core/reconciler.js +152 -0
  73. package/dist/core/runner.d.ts +87 -0
  74. package/dist/core/runner.js +529 -0
  75. package/dist/core/scheduler.d.ts +47 -0
  76. package/dist/core/scheduler.js +121 -0
  77. package/dist/core/secrets.d.ts +46 -0
  78. package/dist/core/secrets.js +100 -0
  79. package/dist/core/subagent-memory-tools.d.ts +18 -0
  80. package/dist/core/subagent-memory-tools.js +121 -0
  81. package/dist/core/timing.d.ts +7 -0
  82. package/dist/core/timing.js +7 -0
  83. package/dist/core/webhooks.d.ts +23 -0
  84. package/dist/core/webhooks.js +113 -0
  85. package/dist/env.d.ts +11 -0
  86. package/dist/env.js +13 -0
  87. package/dist/import/budgets.d.ts +16 -0
  88. package/dist/import/budgets.js +21 -0
  89. package/dist/import/bundle.d.ts +16 -0
  90. package/dist/import/bundle.js +61 -0
  91. package/dist/import/cli-args.d.ts +2 -0
  92. package/dist/import/cli-args.js +44 -0
  93. package/dist/import/create.d.ts +34 -0
  94. package/dist/import/create.js +363 -0
  95. package/dist/import/index.d.ts +25 -0
  96. package/dist/import/index.js +130 -0
  97. package/dist/import/model-gate.d.ts +1 -0
  98. package/dist/import/model-gate.js +3 -0
  99. package/dist/import/neutral-schema.d.ts +369 -0
  100. package/dist/import/neutral-schema.js +109 -0
  101. package/dist/import/preflight.d.ts +41 -0
  102. package/dist/import/preflight.js +71 -0
  103. package/dist/import/report.d.ts +5 -0
  104. package/dist/import/report.js +98 -0
  105. package/dist/import/tool-scan.d.ts +8 -0
  106. package/dist/import/tool-scan.js +20 -0
  107. package/dist/mcp/auth/ownership.d.ts +113 -0
  108. package/dist/mcp/auth/ownership.js +87 -0
  109. package/dist/mcp/auth/principal.d.ts +9 -0
  110. package/dist/mcp/auth/principal.js +9 -0
  111. package/dist/mcp/auth/resource-server.d.ts +47 -0
  112. package/dist/mcp/auth/resource-server.js +82 -0
  113. package/dist/mcp/auth/self-hosted/browser.d.ts +3 -0
  114. package/dist/mcp/auth/self-hosted/browser.js +273 -0
  115. package/dist/mcp/auth/self-hosted/cli.d.ts +2 -0
  116. package/dist/mcp/auth/self-hosted/cli.js +39 -0
  117. package/dist/mcp/auth/self-hosted/credentials.d.ts +58 -0
  118. package/dist/mcp/auth/self-hosted/credentials.js +106 -0
  119. package/dist/mcp/auth/self-hosted/rate-limit.d.ts +13 -0
  120. package/dist/mcp/auth/self-hosted/rate-limit.js +20 -0
  121. package/dist/mcp/auth/self-hosted/session.d.ts +36 -0
  122. package/dist/mcp/auth/self-hosted/session.js +110 -0
  123. package/dist/mcp/capabilities.d.ts +11 -0
  124. package/dist/mcp/capabilities.js +10 -0
  125. package/dist/mcp/context.d.ts +46 -0
  126. package/dist/mcp/context.js +1 -0
  127. package/dist/mcp/errors.d.ts +13 -0
  128. package/dist/mcp/errors.js +22 -0
  129. package/dist/mcp/index.d.ts +32 -0
  130. package/dist/mcp/index.js +187 -0
  131. package/dist/mcp/jsonrpc.d.ts +8 -0
  132. package/dist/mcp/jsonrpc.js +1 -0
  133. package/dist/mcp/server.d.ts +107 -0
  134. package/dist/mcp/server.js +203 -0
  135. package/dist/mcp/tasks/manager.d.ts +63 -0
  136. package/dist/mcp/tasks/manager.js +103 -0
  137. package/dist/mcp/tools/agents.d.ts +2 -0
  138. package/dist/mcp/tools/agents.js +375 -0
  139. package/dist/mcp/tools/budget-groups.d.ts +2 -0
  140. package/dist/mcp/tools/budget-groups.js +136 -0
  141. package/dist/mcp/tools/datastore.d.ts +2 -0
  142. package/dist/mcp/tools/datastore.js +217 -0
  143. package/dist/mcp/tools/memory.d.ts +3 -0
  144. package/dist/mcp/tools/memory.js +60 -0
  145. package/dist/mcp/tools/models.d.ts +8 -0
  146. package/dist/mcp/tools/models.js +15 -0
  147. package/dist/mcp/tools/runs.d.ts +2 -0
  148. package/dist/mcp/tools/runs.js +61 -0
  149. package/dist/mcp/tools/scheduling.d.ts +2 -0
  150. package/dist/mcp/tools/scheduling.js +48 -0
  151. package/dist/mcp/tools/secret-elicitation-form.d.ts +22 -0
  152. package/dist/mcp/tools/secret-elicitation-form.js +93 -0
  153. package/dist/mcp/tools/secret-elicitation-server.d.ts +5 -0
  154. package/dist/mcp/tools/secret-elicitation-server.js +57 -0
  155. package/dist/mcp/tools/secret-elicitation.d.ts +38 -0
  156. package/dist/mcp/tools/secret-elicitation.js +52 -0
  157. package/dist/mcp/tools/secrets.d.ts +13 -0
  158. package/dist/mcp/tools/secrets.js +124 -0
  159. package/dist/mcp/tools/subagents.d.ts +2 -0
  160. package/dist/mcp/tools/subagents.js +133 -0
  161. package/dist/mcp/tools/text-result.d.ts +6 -0
  162. package/dist/mcp/tools/text-result.js +3 -0
  163. package/dist/mcp/tools/tools.d.ts +2 -0
  164. package/dist/mcp/tools/tools.js +211 -0
  165. package/dist/mcp/tools/trigger.d.ts +2 -0
  166. package/dist/mcp/tools/trigger.js +120 -0
  167. package/dist/mcp/tools/webhooks.d.ts +2 -0
  168. package/dist/mcp/tools/webhooks.js +34 -0
  169. package/dist/mcp/transport/http-limits.d.ts +17 -0
  170. package/dist/mcp/transport/http-limits.js +92 -0
  171. package/dist/mcp/transport/stdio.d.ts +23 -0
  172. package/dist/mcp/transport/stdio.js +6 -0
  173. package/dist/mcp/transport/streamable-http.d.ts +34 -0
  174. package/dist/mcp/transport/streamable-http.js +158 -0
  175. package/dist/mcp/unattended-schedules.d.ts +15 -0
  176. package/dist/mcp/unattended-schedules.js +21 -0
  177. package/dist/mcp/webhooks/ingress.d.ts +17 -0
  178. package/dist/mcp/webhooks/ingress.js +33 -0
  179. package/dist/observability/config.d.ts +6 -0
  180. package/dist/observability/config.js +20 -0
  181. package/dist/observability/metrics-server.d.ts +11 -0
  182. package/dist/observability/metrics-server.js +47 -0
  183. package/dist/observability/metrics.d.ts +36 -0
  184. package/dist/observability/metrics.js +158 -0
  185. package/dist/providers/auth/authorization-server.d.ts +41 -0
  186. package/dist/providers/auth/authorization-server.js +1 -0
  187. package/dist/providers/auth/delegating.d.ts +44 -0
  188. package/dist/providers/auth/delegating.js +105 -0
  189. package/dist/providers/auth/index.d.ts +11 -0
  190. package/dist/providers/auth/index.js +20 -0
  191. package/dist/providers/auth/release-gate.d.ts +1 -0
  192. package/dist/providers/auth/release-gate.js +4 -0
  193. package/dist/providers/auth/self-hosted.d.ts +78 -0
  194. package/dist/providers/auth/self-hosted.js +377 -0
  195. package/dist/providers/auth/subject.d.ts +1 -0
  196. package/dist/providers/auth/subject.js +6 -0
  197. package/dist/providers/auth/types.d.ts +63 -0
  198. package/dist/providers/auth/types.js +22 -0
  199. package/dist/providers/coding-proxy/deny-port.d.ts +5 -0
  200. package/dist/providers/coding-proxy/deny-port.js +45 -0
  201. package/dist/providers/coding-proxy/environment-credentials.d.ts +7 -0
  202. package/dist/providers/coding-proxy/environment-credentials.js +16 -0
  203. package/dist/providers/coding-proxy/index.d.ts +7 -0
  204. package/dist/providers/coding-proxy/index.js +7 -0
  205. package/dist/providers/coding-proxy/memory-ledger.d.ts +17 -0
  206. package/dist/providers/coding-proxy/memory-ledger.js +103 -0
  207. package/dist/providers/coding-proxy/metering.d.ts +20 -0
  208. package/dist/providers/coding-proxy/metering.js +161 -0
  209. package/dist/providers/coding-proxy/prisma-ledger.d.ts +15 -0
  210. package/dist/providers/coding-proxy/prisma-ledger.js +213 -0
  211. package/dist/providers/coding-proxy/proxy.d.ts +75 -0
  212. package/dist/providers/coding-proxy/proxy.js +721 -0
  213. package/dist/providers/coding-proxy/runtime.d.ts +18 -0
  214. package/dist/providers/coding-proxy/runtime.js +39 -0
  215. package/dist/providers/coding-proxy/secure-fetch.d.ts +9 -0
  216. package/dist/providers/coding-proxy/secure-fetch.js +72 -0
  217. package/dist/providers/coding-proxy/server.d.ts +17 -0
  218. package/dist/providers/coding-proxy/server.js +139 -0
  219. package/dist/providers/coding-proxy/types.d.ts +99 -0
  220. package/dist/providers/coding-proxy/types.js +1 -0
  221. package/dist/providers/datastore/index.d.ts +2 -0
  222. package/dist/providers/datastore/index.js +2 -0
  223. package/dist/providers/datastore/postgres.d.ts +20 -0
  224. package/dist/providers/datastore/postgres.js +130 -0
  225. package/dist/providers/datastore/scoped.d.ts +10 -0
  226. package/dist/providers/datastore/scoped.js +46 -0
  227. package/dist/providers/datastore/types.d.ts +31 -0
  228. package/dist/providers/datastore/types.js +7 -0
  229. package/dist/providers/email/types.d.ts +46 -0
  230. package/dist/providers/email/types.js +8 -0
  231. package/dist/providers/engine/index.d.ts +1 -0
  232. package/dist/providers/engine/index.js +1 -0
  233. package/dist/providers/engine/types.d.ts +67 -0
  234. package/dist/providers/engine/types.js +16 -0
  235. package/dist/providers/executor/build.d.ts +6 -0
  236. package/dist/providers/executor/build.js +15 -0
  237. package/dist/providers/executor/composition.d.ts +14 -0
  238. package/dist/providers/executor/composition.js +123 -0
  239. package/dist/providers/executor/container.d.ts +184 -0
  240. package/dist/providers/executor/container.js +936 -0
  241. package/dist/providers/executor/dbos-status.d.ts +50 -0
  242. package/dist/providers/executor/dbos-status.js +64 -0
  243. package/dist/providers/executor/dbos.d.ts +53 -0
  244. package/dist/providers/executor/dbos.js +269 -0
  245. package/dist/providers/executor/in-process.d.ts +18 -0
  246. package/dist/providers/executor/in-process.js +38 -0
  247. package/dist/providers/executor/index.d.ts +8 -0
  248. package/dist/providers/executor/index.js +8 -0
  249. package/dist/providers/executor/routing.d.ts +48 -0
  250. package/dist/providers/executor/routing.js +70 -0
  251. package/dist/providers/executor/types.d.ts +55 -0
  252. package/dist/providers/executor/types.js +1 -0
  253. package/dist/providers/index.d.ts +43 -0
  254. package/dist/providers/index.js +18 -0
  255. package/dist/providers/jobs/docker-isolation.d.ts +166 -0
  256. package/dist/providers/jobs/docker-isolation.js +724 -0
  257. package/dist/providers/jobs/docker.d.ts +127 -0
  258. package/dist/providers/jobs/docker.js +1027 -0
  259. package/dist/providers/jobs/fake-kubernetes-api.d.ts +98 -0
  260. package/dist/providers/jobs/fake-kubernetes-api.js +142 -0
  261. package/dist/providers/jobs/fake.d.ts +23 -0
  262. package/dist/providers/jobs/fake.js +132 -0
  263. package/dist/providers/jobs/kubernetes-api.d.ts +60 -0
  264. package/dist/providers/jobs/kubernetes-api.js +20 -0
  265. package/dist/providers/jobs/kubernetes-client.d.ts +36 -0
  266. package/dist/providers/jobs/kubernetes-client.js +171 -0
  267. package/dist/providers/jobs/kubernetes-dry-run-fixture.d.ts +49 -0
  268. package/dist/providers/jobs/kubernetes-dry-run-fixture.js +141 -0
  269. package/dist/providers/jobs/kubernetes-isolation.d.ts +147 -0
  270. package/dist/providers/jobs/kubernetes-isolation.js +607 -0
  271. package/dist/providers/jobs/kubernetes-platform.d.ts +177 -0
  272. package/dist/providers/jobs/kubernetes-platform.js +280 -0
  273. package/dist/providers/jobs/kubernetes-preflight.d.ts +59 -0
  274. package/dist/providers/jobs/kubernetes-preflight.js +339 -0
  275. package/dist/providers/jobs/kubernetes-witness.d.ts +10 -0
  276. package/dist/providers/jobs/kubernetes-witness.js +193 -0
  277. package/dist/providers/jobs/kubernetes.d.ts +127 -0
  278. package/dist/providers/jobs/kubernetes.js +874 -0
  279. package/dist/providers/jobs/safe-extract.d.ts +9 -0
  280. package/dist/providers/jobs/safe-extract.js +325 -0
  281. package/dist/providers/jobs/types.d.ts +78 -0
  282. package/dist/providers/jobs/types.js +6 -0
  283. package/dist/providers/jobs/workspace-swap.d.ts +2 -0
  284. package/dist/providers/jobs/workspace-swap.js +47 -0
  285. package/dist/providers/llm/anthropic.d.ts +13 -0
  286. package/dist/providers/llm/anthropic.js +21 -0
  287. package/dist/providers/llm/bedrock.d.ts +19 -0
  288. package/dist/providers/llm/bedrock.js +33 -0
  289. package/dist/providers/llm/claude-messages.d.ts +106 -0
  290. package/dist/providers/llm/claude-messages.js +157 -0
  291. package/dist/providers/llm/claude-provider.d.ts +40 -0
  292. package/dist/providers/llm/claude-provider.js +31 -0
  293. package/dist/providers/llm/index.d.ts +10 -0
  294. package/dist/providers/llm/index.js +10 -0
  295. package/dist/providers/llm/openai.d.ts +23 -0
  296. package/dist/providers/llm/openai.js +156 -0
  297. package/dist/providers/llm/pricing-anthropic.d.ts +4 -0
  298. package/dist/providers/llm/pricing-anthropic.js +30 -0
  299. package/dist/providers/llm/pricing-bedrock-claude.d.ts +12 -0
  300. package/dist/providers/llm/pricing-bedrock-claude.js +37 -0
  301. package/dist/providers/llm/pricing-core.d.ts +26 -0
  302. package/dist/providers/llm/pricing-core.js +17 -0
  303. package/dist/providers/llm/pricing.d.ts +30 -0
  304. package/dist/providers/llm/pricing.js +74 -0
  305. package/dist/providers/llm/registration.d.ts +8 -0
  306. package/dist/providers/llm/registration.js +39 -0
  307. package/dist/providers/llm/routing.d.ts +25 -0
  308. package/dist/providers/llm/routing.js +32 -0
  309. package/dist/providers/llm/types.d.ts +85 -0
  310. package/dist/providers/llm/types.js +12 -0
  311. package/dist/providers/memory/index.d.ts +2 -0
  312. package/dist/providers/memory/index.js +2 -0
  313. package/dist/providers/memory/postgres.d.ts +20 -0
  314. package/dist/providers/memory/postgres.js +72 -0
  315. package/dist/providers/memory/types.d.ts +32 -0
  316. package/dist/providers/memory/types.js +17 -0
  317. package/dist/providers/secrets/app-key.d.ts +8 -0
  318. package/dist/providers/secrets/app-key.js +50 -0
  319. package/dist/providers/secrets/index.d.ts +10 -0
  320. package/dist/providers/secrets/index.js +13 -0
  321. package/dist/providers/secrets/transfer-envelope.d.ts +12 -0
  322. package/dist/providers/secrets/transfer-envelope.js +35 -0
  323. package/dist/providers/secrets/types.d.ts +18 -0
  324. package/dist/providers/secrets/types.js +12 -0
  325. package/dist/providers/storage/types.d.ts +18 -0
  326. package/dist/providers/storage/types.js +7 -0
  327. package/dist/providers/vcs/git.d.ts +105 -0
  328. package/dist/providers/vcs/git.js +710 -0
  329. package/dist/providers/vcs/github.d.ts +105 -0
  330. package/dist/providers/vcs/github.js +394 -0
  331. package/dist/providers/vcs/index.d.ts +6 -0
  332. package/dist/providers/vcs/index.js +26 -0
  333. package/dist/providers/vcs/types.d.ts +118 -0
  334. package/dist/providers/vcs/types.js +1 -0
  335. package/dist/sandbox/bounded-json.d.ts +2 -0
  336. package/dist/sandbox/bounded-json.js +36 -0
  337. package/dist/sandbox/bridge.d.ts +15 -0
  338. package/dist/sandbox/bridge.js +78 -0
  339. package/dist/sandbox/eval-core.d.ts +37 -0
  340. package/dist/sandbox/eval-core.js +159 -0
  341. package/dist/sandbox/fetch-policy.d.ts +24 -0
  342. package/dist/sandbox/fetch-policy.js +96 -0
  343. package/dist/sandbox/generated/zod-to-json-schema.bundle.js +5 -0
  344. package/dist/sandbox/generated/zod.bundle.js +1 -0
  345. package/dist/sandbox/host-functions.d.ts +29 -0
  346. package/dist/sandbox/host-functions.js +168 -0
  347. package/dist/sandbox/limits.d.ts +39 -0
  348. package/dist/sandbox/limits.js +39 -0
  349. package/dist/sandbox/parser-worker/__fixtures__/crash.d.ts +1 -0
  350. package/dist/sandbox/parser-worker/__fixtures__/crash.js +6 -0
  351. package/dist/sandbox/parser-worker/__fixtures__/echo.d.ts +1 -0
  352. package/dist/sandbox/parser-worker/__fixtures__/echo.js +5 -0
  353. package/dist/sandbox/parser-worker/__fixtures__/reject.d.ts +1 -0
  354. package/dist/sandbox/parser-worker/__fixtures__/reject.js +5 -0
  355. package/dist/sandbox/parser-worker/__fixtures__/spin-forever.d.ts +1 -0
  356. package/dist/sandbox/parser-worker/__fixtures__/spin-forever.js +11 -0
  357. package/dist/sandbox/parser-worker/pool.d.ts +13 -0
  358. package/dist/sandbox/parser-worker/pool.js +111 -0
  359. package/dist/sandbox/parser-worker/worker.d.ts +14 -0
  360. package/dist/sandbox/parser-worker/worker.js +63 -0
  361. package/dist/sandbox/pii-redaction.d.ts +1 -0
  362. package/dist/sandbox/pii-redaction.js +29 -0
  363. package/dist/sandbox/prelude.d.ts +26 -0
  364. package/dist/sandbox/prelude.js +408 -0
  365. package/dist/sandbox/quickjs-module.d.ts +13 -0
  366. package/dist/sandbox/quickjs-module.js +19 -0
  367. package/dist/sandbox/run-in-sandbox.d.ts +34 -0
  368. package/dist/sandbox/run-in-sandbox.js +53 -0
  369. package/dist/sandbox/safe-fetch.d.ts +26 -0
  370. package/dist/sandbox/safe-fetch.js +140 -0
  371. package/dist/sandbox/tool-capabilities.d.ts +66 -0
  372. package/dist/sandbox/tool-capabilities.js +103 -0
  373. package/dist/sandbox/zod-params.d.ts +22 -0
  374. package/dist/sandbox/zod-params.js +60 -0
  375. package/dist/serve.d.ts +14 -0
  376. package/dist/serve.js +54 -0
  377. package/dist/wardby-bin.d.ts +2 -0
  378. package/dist/wardby-bin.js +20 -0
  379. package/package.json +123 -0
  380. package/prisma/migrations/20260905000000_init/migration.sql +34 -0
  381. package/prisma/migrations/20260905010000_scheduler_durability/migration.sql +31 -0
  382. package/prisma/migrations/20260905020000_tools_multiturn/migration.sql +47 -0
  383. package/prisma/migrations/20260905030000_cache_tool_json_schema/migration.sql +13 -0
  384. package/prisma/migrations/20260906000000_mcp_phase4/migration.sql +115 -0
  385. package/prisma/migrations/20260906010000_run_final_text/migration.sql +7 -0
  386. package/prisma/migrations/20260906020000_run_turns/migration.sql +6 -0
  387. package/prisma/migrations/20260906030000_task_principal_id/migration.sql +5 -0
  388. package/prisma/migrations/20260906040000_secure_self_hosted_oauth/migration.sql +67 -0
  389. package/prisma/migrations/20260906050000_phase5_coding_agents/migration.sql +52 -0
  390. package/prisma/migrations/20260906060000_tool_capability_scoping/migration.sql +22 -0
  391. package/prisma/migrations/20260906070000_datastore_pii_flag/migration.sql +6 -0
  392. package/prisma/migrations/20260906080000_secret_elicitation_outcomes/migration.sql +13 -0
  393. package/prisma/migrations/20260907010000_coding_proxy_ledger/migration.sql +57 -0
  394. package/prisma/migrations/20260907020000_budget_groups/migration.sql +24 -0
  395. package/prisma/migrations/20260907030000_coding_webhook_task_override/migration.sql +4 -0
  396. package/prisma/migrations/20260908010000_run_execution_backend/migration.sql +2 -0
  397. package/prisma/migrations/20260908020000_coding_run_audit_metadata/migration.sql +7 -0
  398. package/prisma/migrations/20260910010000_agent_secret_bound_name/migration.sql +16 -0
  399. package/prisma/migrations/20260911010000_coding_worker_toolchains/migration.sql +5 -0
  400. package/prisma/migrations/20260912010000_coding_provider_contract/migration.sql +24 -0
  401. package/prisma/migrations/20260912020000_coding_proxy_protocol/migration.sql +10 -0
  402. package/prisma/migrations/20260912030000_agent_memory/migration.sql +18 -0
  403. package/prisma/migrations/20260912040000_named_shared_datastores/migration.sql +68 -0
  404. package/prisma/migrations/20260913000000_run_trigger_webhook/migration.sql +2 -0
  405. package/prisma/migrations/20260913010000_agent_sub_agent/migration.sql +32 -0
  406. package/prisma/migrations/20260913020000_run_task_override/migration.sql +5 -0
  407. package/prisma/migrations/20260913030000_coding_run_root/migration.sql +11 -0
  408. package/prisma/migrations/20260922010000_coding_run_queued_at/migration.sql +8 -0
  409. package/prisma/migrations/20260922020000_coding_workspace_disk/migration.sql +8 -0
  410. package/prisma/migrations/migration_lock.toml +3 -0
  411. package/prisma/schema.prisma +723 -0
  412. package/scripts/git-askpass.sh +11 -0
@@ -0,0 +1,724 @@
1
+ import { createHash } from "node:crypto";
2
+ export const CODING_WORKER_UID = 10001;
3
+ export const CODING_WORKER_GID = 10001;
4
+ // The keeper needs headroom for runc's short-lived exec process plus Node's threads.
5
+ const KEEPER_PIDS_LIMIT = 32;
6
+ export const CODING_PROXY_ALIAS = "wardby-proxy";
7
+ export const CODING_PROXY_PORT = 8787;
8
+ /**
9
+ * The proxy's deny port. Nothing is served here (see coding-proxy/deny-port.ts);
10
+ * it is the enforcement witness — the one destination a run's NetworkPolicy must
11
+ * refuse while permitting CODING_PROXY_PORT on the same pod.
12
+ */
13
+ export const CODING_PROXY_DENY_PORT = 8788;
14
+ export const DOCKER_ISOLATION_ERROR = "docker_isolation_unsupported";
15
+ export const WORKER_STOP_GRACE_SECONDS = 10;
16
+ export const WORKER_PATHS = {
17
+ workspace: "/workspace",
18
+ git: "/workspace/.git",
19
+ input: "/run/wardby/input",
20
+ output: "/run/wardby/output",
21
+ tool: "/run/wardby/tool",
22
+ storage: "/run/wardby/storage",
23
+ };
24
+ const LABEL_MANAGED = "io.wardby.managed=true";
25
+ const LABEL_COMPONENT = "io.wardby.component=coding-worker";
26
+ /**
27
+ * A digest-pinned repository reference. Only the first path component (the
28
+ * registry host) may carry a `:<port>`, and then a path must follow; tags
29
+ * (`repo:tag@sha256:…`) and empty components are rejected.
30
+ */
31
+ const REPOSITORY_DIGEST = /^[a-z0-9][a-z0-9._-]*(?::[0-9]{1,5}(?:\/[a-z0-9][a-z0-9._-]*)+|(?:\/[a-z0-9][a-z0-9._-]*)*)@sha256:[a-f0-9]{64}$/;
32
+ const LOCAL_IMAGE_ID = /^sha256:[a-f0-9]{64}$/;
33
+ const DOCKER_OBJECT = /^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$/;
34
+ function isolationError() {
35
+ return new Error(DOCKER_ISOLATION_ERROR);
36
+ }
37
+ function runHash(runId) {
38
+ return createHash("sha256").update(runId).digest("hex");
39
+ }
40
+ function hasResourceLabels(labels, runId) {
41
+ return (labels?.["io.wardby.managed"] === "true" &&
42
+ labels["io.wardby.component"] === "coding-worker" &&
43
+ labels["io.wardby.run-sha256"] === runHash(runId));
44
+ }
45
+ function assertFiniteRange(value, min, max) {
46
+ if (!Number.isFinite(value) || value < min || value > max)
47
+ throw isolationError();
48
+ }
49
+ function assertIntegerRange(value, min, max) {
50
+ if (!Number.isSafeInteger(value) || value < min || value > max)
51
+ throw isolationError();
52
+ }
53
+ function validateSpec(spec) {
54
+ if (spec.kind !== "coding-agent" || !/^[a-zA-Z0-9][a-zA-Z0-9_.:-]{0,199}$/.test(spec.runId)) {
55
+ throw isolationError();
56
+ }
57
+ if (!isImmutableDockerImage(spec.image))
58
+ throw isolationError();
59
+ if (spec.provider !== undefined && spec.provider !== "codex" && spec.provider !== "claude-code")
60
+ throw isolationError();
61
+ if (spec.provider === "claude-code") {
62
+ if (!spec.toolImage || !isImmutableDockerImage(spec.toolImage))
63
+ throw isolationError();
64
+ if (spec.limits.cpus < 0.35 || spec.limits.memoryMb < 256 || spec.limits.pids < 32)
65
+ throw isolationError();
66
+ }
67
+ else if (spec.toolImage !== undefined) {
68
+ throw isolationError();
69
+ }
70
+ assertFiniteRange(spec.limits.cpus, 0.1, 32);
71
+ assertIntegerRange(spec.limits.memoryMb, 128, 65_536);
72
+ assertIntegerRange(spec.limits.pids, 16, 4_096);
73
+ assertIntegerRange(spec.limits.diskMb, 64, 32_768);
74
+ assertIntegerRange(spec.timeoutSec, 1, 86_400);
75
+ }
76
+ /** A registry digest reference (`[host[:port]/]path@sha256:<64 hex>`); no local image IDs, no tags. */
77
+ export function isRepositoryDigest(image) {
78
+ return REPOSITORY_DIGEST.test(image);
79
+ }
80
+ /** Accepts a registry digest or Docker's content-addressed local image ID. */
81
+ export function isImmutableDockerImage(image) {
82
+ return LOCAL_IMAGE_ID.test(image) || REPOSITORY_DIGEST.test(image);
83
+ }
84
+ function validateDockerObject(value) {
85
+ if (!DOCKER_OBJECT.test(value))
86
+ throw isolationError();
87
+ }
88
+ export function isolationNames(runId) {
89
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9_.:-]{0,199}$/.test(runId))
90
+ throw isolationError();
91
+ const token = runHash(runId).slice(0, 20);
92
+ return {
93
+ network: `wardby-net-${token}`,
94
+ storageVolume: `wardby-storage-${token}`,
95
+ keeperContainer: `wardby-keeper-${token}`,
96
+ workerContainer: `wardby-worker-${token}`,
97
+ toolContainer: `wardby-tools-${token}`,
98
+ };
99
+ }
100
+ function labels(runId) {
101
+ return ["--label", LABEL_MANAGED, "--label", LABEL_COMPONENT, "--label", `io.wardby.run-sha256=${runHash(runId)}`];
102
+ }
103
+ function storageMountOptions(spec) {
104
+ const inodes = Math.max(4_096, Math.min(262_144, spec.limits.diskMb * 256));
105
+ return `size=${spec.limits.diskMb}m,nr_inodes=${inodes},uid=${CODING_WORKER_UID},gid=${CODING_WORKER_GID},mode=0700,nosuid,nodev`;
106
+ }
107
+ export function buildIsolationNetworkCreateArgs(runId) {
108
+ const names = isolationNames(runId);
109
+ return [
110
+ "network",
111
+ "create",
112
+ "--driver",
113
+ "bridge",
114
+ "--internal",
115
+ "--ipv6=false",
116
+ "--opt",
117
+ "com.docker.network.bridge.gateway_mode_ipv4=isolated",
118
+ "--opt",
119
+ "com.docker.network.bridge.gateway_mode_ipv6=isolated",
120
+ ...labels(runId),
121
+ names.network,
122
+ ];
123
+ }
124
+ export function buildStorageVolumeCreateArgs(spec) {
125
+ validateSpec(spec);
126
+ const names = isolationNames(spec.runId);
127
+ return [
128
+ "volume",
129
+ "create",
130
+ "--driver",
131
+ "local",
132
+ "--opt",
133
+ "type=tmpfs",
134
+ "--opt",
135
+ "device=tmpfs",
136
+ "--opt",
137
+ `o=${storageMountOptions(spec)}`,
138
+ ...labels(spec.runId),
139
+ names.storageVolume,
140
+ ];
141
+ }
142
+ export function buildKeeperCreateArgs(spec) {
143
+ validateSpec(spec);
144
+ const names = isolationNames(spec.runId);
145
+ const memoryMb = Math.min(65_536, spec.limits.diskMb + 128);
146
+ return [
147
+ "container",
148
+ "create",
149
+ "--name",
150
+ names.keeperContainer,
151
+ "--pull",
152
+ "never",
153
+ "--user",
154
+ `${CODING_WORKER_UID}:${CODING_WORKER_GID}`,
155
+ "--network",
156
+ "none",
157
+ "--read-only",
158
+ "--cap-drop",
159
+ "ALL",
160
+ "--security-opt",
161
+ "no-new-privileges=true",
162
+ "--security-opt",
163
+ "seccomp=builtin",
164
+ "--cgroupns",
165
+ "private",
166
+ "--ipc",
167
+ "none",
168
+ "--cpus",
169
+ "0.25",
170
+ "--memory",
171
+ `${memoryMb}m`,
172
+ "--memory-swap",
173
+ `${memoryMb}m`,
174
+ "--memory-swappiness",
175
+ "0",
176
+ "--pids-limit",
177
+ String(KEEPER_PIDS_LIMIT),
178
+ "--restart",
179
+ "no",
180
+ "--log-driver",
181
+ "local",
182
+ "--log-opt",
183
+ "max-size=1m",
184
+ "--log-opt",
185
+ "max-file=2",
186
+ "--mount",
187
+ `type=volume,src=${names.storageVolume},dst=${WORKER_PATHS.storage},volume-nocopy`,
188
+ ...labels(spec.runId),
189
+ "--entrypoint",
190
+ "node",
191
+ spec.image,
192
+ "/opt/wardby/coding-worker/keeper.js",
193
+ ];
194
+ }
195
+ export function buildWorkerCreateArgs(spec, proxyPort = CODING_PROXY_PORT) {
196
+ validateSpec(spec);
197
+ assertIntegerRange(proxyPort, 1, 65_535);
198
+ const names = isolationNames(spec.runId);
199
+ const scratchMb = Math.max(16, Math.min(64, Math.floor(spec.limits.memoryMb / 8)));
200
+ return [
201
+ "container",
202
+ "create",
203
+ "--name",
204
+ names.workerContainer,
205
+ "--pull",
206
+ "never",
207
+ "--user",
208
+ `${CODING_WORKER_UID}:${CODING_WORKER_GID}`,
209
+ "--network",
210
+ names.network,
211
+ "--read-only",
212
+ "--cap-drop",
213
+ "ALL",
214
+ "--security-opt",
215
+ "no-new-privileges=true",
216
+ "--security-opt",
217
+ "seccomp=builtin",
218
+ "--init",
219
+ "--cgroupns",
220
+ "private",
221
+ "--ipc",
222
+ "none",
223
+ "--cpus",
224
+ String(spec.limits.cpus),
225
+ "--memory",
226
+ `${spec.limits.memoryMb}m`,
227
+ "--memory-swap",
228
+ `${spec.limits.memoryMb}m`,
229
+ "--memory-swappiness",
230
+ "0",
231
+ "--pids-limit",
232
+ String(spec.limits.pids),
233
+ "--shm-size",
234
+ "16m",
235
+ "--tmpfs",
236
+ `/tmp:rw,noexec,nosuid,nodev,size=${scratchMb}m,mode=1777`,
237
+ "--tmpfs",
238
+ `/home/wardby:rw,noexec,nosuid,nodev,size=${scratchMb}m,uid=${CODING_WORKER_UID},gid=${CODING_WORKER_GID},mode=0700`,
239
+ "--mount",
240
+ `type=volume,src=${names.storageVolume},dst=${WORKER_PATHS.workspace},volume-subpath=workspace,volume-nocopy`,
241
+ "--mount",
242
+ `type=volume,src=${names.storageVolume},dst=${WORKER_PATHS.input},volume-subpath=input,volume-nocopy,readonly`,
243
+ "--mount",
244
+ `type=volume,src=${names.storageVolume},dst=${WORKER_PATHS.output},volume-subpath=output,volume-nocopy`,
245
+ "--env",
246
+ `WARDBY_PROXY_URL=http://${CODING_PROXY_ALIAS}:${proxyPort}`,
247
+ "--env",
248
+ "WARDBY_RUN_CAPABILITY",
249
+ "--restart",
250
+ "no",
251
+ "--stop-signal",
252
+ "SIGTERM",
253
+ "--stop-timeout",
254
+ String(WORKER_STOP_GRACE_SECONDS),
255
+ "--log-driver",
256
+ "local",
257
+ "--log-opt",
258
+ "max-size=1m",
259
+ "--log-opt",
260
+ "max-file=2",
261
+ ...labels(spec.runId),
262
+ spec.image,
263
+ ];
264
+ }
265
+ function claudeToolLimits(spec) {
266
+ return {
267
+ cpus: 0.25,
268
+ memoryMb: Math.min(512, Math.max(128, Math.floor(spec.limits.memoryMb / 3))),
269
+ pids: 16,
270
+ };
271
+ }
272
+ function claudeAgentLimits(spec) {
273
+ const tools = claudeToolLimits(spec);
274
+ return {
275
+ cpus: spec.limits.cpus - tools.cpus,
276
+ memoryMb: spec.limits.memoryMb - tools.memoryMb,
277
+ pids: spec.limits.pids - tools.pids,
278
+ };
279
+ }
280
+ /** The Claude agent sees the task/result and socket, never the repository workspace. */
281
+ export function buildClaudeAgentCreateArgs(spec, proxyPort = CODING_PROXY_PORT) {
282
+ validateSpec(spec);
283
+ if (spec.provider !== "claude-code")
284
+ throw isolationError();
285
+ assertIntegerRange(proxyPort, 1, 65_535);
286
+ const names = isolationNames(spec.runId);
287
+ const limits = claudeAgentLimits(spec);
288
+ const scratchMb = Math.max(16, Math.min(64, Math.floor(limits.memoryMb / 8)));
289
+ return [
290
+ "container",
291
+ "create",
292
+ "--name",
293
+ names.workerContainer,
294
+ "--pull",
295
+ "never",
296
+ "--user",
297
+ `${CODING_WORKER_UID}:${CODING_WORKER_GID}`,
298
+ "--network",
299
+ names.network,
300
+ "--read-only",
301
+ "--cap-drop",
302
+ "ALL",
303
+ "--security-opt",
304
+ "no-new-privileges=true",
305
+ "--security-opt",
306
+ "seccomp=builtin",
307
+ "--init",
308
+ "--cgroupns",
309
+ "private",
310
+ "--ipc",
311
+ "none",
312
+ "--cpus",
313
+ String(limits.cpus),
314
+ "--memory",
315
+ `${limits.memoryMb}m`,
316
+ "--memory-swap",
317
+ `${limits.memoryMb}m`,
318
+ "--memory-swappiness",
319
+ "0",
320
+ "--pids-limit",
321
+ String(limits.pids),
322
+ "--shm-size",
323
+ "16m",
324
+ "--tmpfs",
325
+ `/tmp:rw,noexec,nosuid,nodev,size=${scratchMb}m,mode=1777`,
326
+ "--tmpfs",
327
+ `/home/wardby:rw,noexec,nosuid,nodev,size=${scratchMb}m,uid=${CODING_WORKER_UID},gid=${CODING_WORKER_GID},mode=0700`,
328
+ "--mount",
329
+ `type=volume,src=${names.storageVolume},dst=${WORKER_PATHS.input},volume-subpath=input,volume-nocopy,readonly`,
330
+ "--mount",
331
+ `type=volume,src=${names.storageVolume},dst=${WORKER_PATHS.output},volume-subpath=output,volume-nocopy`,
332
+ "--mount",
333
+ `type=volume,src=${names.storageVolume},dst=${WORKER_PATHS.tool},volume-subpath=tool,volume-nocopy`,
334
+ "--env",
335
+ `WARDBY_PROXY_URL=http://${CODING_PROXY_ALIAS}:${proxyPort}`,
336
+ "--env",
337
+ "WARDBY_RUN_CAPABILITY",
338
+ "--restart",
339
+ "no",
340
+ "--stop-signal",
341
+ "SIGTERM",
342
+ "--stop-timeout",
343
+ String(WORKER_STOP_GRACE_SECONDS),
344
+ "--log-driver",
345
+ "local",
346
+ "--log-opt",
347
+ "max-size=1m",
348
+ "--log-opt",
349
+ "max-file=2",
350
+ ...labels(spec.runId),
351
+ spec.image,
352
+ ];
353
+ }
354
+ /** The tool runner receives only the checkout and socket; it never joins the proxy network. */
355
+ export function buildClaudeToolRunnerCreateArgs(spec) {
356
+ validateSpec(spec);
357
+ if (spec.provider !== "claude-code" || !spec.toolImage)
358
+ throw isolationError();
359
+ const names = isolationNames(spec.runId);
360
+ const limits = claudeToolLimits(spec);
361
+ const scratchMb = Math.max(16, Math.min(64, Math.floor(limits.memoryMb / 8)));
362
+ return [
363
+ "container",
364
+ "create",
365
+ "--name",
366
+ names.toolContainer,
367
+ "--pull",
368
+ "never",
369
+ "--user",
370
+ `${CODING_WORKER_UID}:${CODING_WORKER_GID}`,
371
+ "--network",
372
+ "none",
373
+ "--read-only",
374
+ "--cap-drop",
375
+ "ALL",
376
+ "--security-opt",
377
+ "no-new-privileges=true",
378
+ "--security-opt",
379
+ "seccomp=builtin",
380
+ "--init",
381
+ "--cgroupns",
382
+ "private",
383
+ "--ipc",
384
+ "none",
385
+ "--cpus",
386
+ String(limits.cpus),
387
+ "--memory",
388
+ `${limits.memoryMb}m`,
389
+ "--memory-swap",
390
+ `${limits.memoryMb}m`,
391
+ "--memory-swappiness",
392
+ "0",
393
+ "--pids-limit",
394
+ String(limits.pids),
395
+ "--shm-size",
396
+ "16m",
397
+ "--tmpfs",
398
+ `/tmp:rw,noexec,nosuid,nodev,size=${scratchMb}m,mode=1777`,
399
+ "--tmpfs",
400
+ `/home/wardby:rw,noexec,nosuid,nodev,size=${scratchMb}m,uid=${CODING_WORKER_UID},gid=${CODING_WORKER_GID},mode=0700`,
401
+ "--mount",
402
+ `type=volume,src=${names.storageVolume},dst=${WORKER_PATHS.workspace},volume-subpath=workspace,volume-nocopy`,
403
+ "--mount",
404
+ `type=volume,src=${names.storageVolume},dst=${WORKER_PATHS.tool},volume-subpath=tool,volume-nocopy`,
405
+ "--restart",
406
+ "no",
407
+ "--stop-signal",
408
+ "SIGTERM",
409
+ "--stop-timeout",
410
+ String(WORKER_STOP_GRACE_SECONDS),
411
+ "--log-driver",
412
+ "local",
413
+ "--log-opt",
414
+ "max-size=1m",
415
+ "--log-opt",
416
+ "max-file=2",
417
+ ...labels(spec.runId),
418
+ spec.toolImage,
419
+ ];
420
+ }
421
+ export function buildProxyNetworkConnectArgs(runId, proxyContainer) {
422
+ validateDockerObject(proxyContainer);
423
+ return ["network", "connect", "--alias", CODING_PROXY_ALIAS, isolationNames(runId).network, proxyContainer];
424
+ }
425
+ export function buildDockerIsolationPlan(spec, proxyContainer) {
426
+ validateSpec(spec);
427
+ const names = isolationNames(spec.runId);
428
+ return {
429
+ names,
430
+ deadlineMs: spec.timeoutSec * 1_000,
431
+ stopGraceSeconds: WORKER_STOP_GRACE_SECONDS,
432
+ networkCreateArgs: buildIsolationNetworkCreateArgs(spec.runId),
433
+ storageVolumeCreateArgs: buildStorageVolumeCreateArgs(spec),
434
+ keeperCreateArgs: buildKeeperCreateArgs(spec),
435
+ workerCreateArgs: spec.provider === "claude-code" ? buildClaudeAgentCreateArgs(spec) : buildWorkerCreateArgs(spec),
436
+ ...(spec.provider === "claude-code" ? { toolCreateArgs: buildClaudeToolRunnerCreateArgs(spec) } : {}),
437
+ proxyNetworkConnectArgs: buildProxyNetworkConnectArgs(spec.runId, proxyContainer),
438
+ };
439
+ }
440
+ export function assertDockerHostSupportsIsolation(info) {
441
+ const securityOptions = info.SecurityOptions ?? [];
442
+ if (info.OSType !== "linux" ||
443
+ info.CgroupVersion !== "2" ||
444
+ info.MemoryLimit !== true ||
445
+ info.SwapLimit !== true ||
446
+ info.CpuCfsQuota !== true ||
447
+ info.PidsLimit !== true ||
448
+ !securityOptions.some((option) => option.includes("seccomp")) ||
449
+ !(info.Plugins?.Volume ?? []).includes("local") ||
450
+ !(info.Plugins?.Network ?? []).includes("bridge")) {
451
+ throw isolationError();
452
+ }
453
+ }
454
+ export function assertIsolationNetworkInspection(network, runId) {
455
+ const names = isolationNames(runId);
456
+ if (network.Name !== names.network ||
457
+ network.Driver !== "bridge" ||
458
+ network.Internal !== true ||
459
+ network.EnableIPv6 !== false ||
460
+ network.Attachable !== false ||
461
+ network.Ingress !== false ||
462
+ !hasResourceLabels(network.Labels, runId) ||
463
+ network.Options?.["com.docker.network.bridge.gateway_mode_ipv4"] !== "isolated" ||
464
+ network.Options?.["com.docker.network.bridge.gateway_mode_ipv6"] !== "isolated") {
465
+ throw isolationError();
466
+ }
467
+ }
468
+ export function assertIsolationNetworkParticipants(network, expectedContainerNames) {
469
+ const actual = Object.values(network.Containers ?? {})
470
+ .map((container) => container.Name)
471
+ .filter((name) => Boolean(name))
472
+ .sort();
473
+ const expected = [...expectedContainerNames].sort();
474
+ if (JSON.stringify(actual) !== JSON.stringify(expected))
475
+ throw isolationError();
476
+ }
477
+ export function assertStorageVolumeInspection(volume, spec) {
478
+ validateSpec(spec);
479
+ const names = isolationNames(spec.runId);
480
+ if (volume.Name !== names.storageVolume ||
481
+ volume.Driver !== "local" ||
482
+ volume.Options?.type !== "tmpfs" ||
483
+ volume.Options?.device !== "tmpfs" ||
484
+ volume.Options?.o !== storageMountOptions(spec) ||
485
+ !hasResourceLabels(volume.Labels, spec.runId)) {
486
+ throw isolationError();
487
+ }
488
+ }
489
+ export function assertKeeperContainerInspection(container, spec) {
490
+ validateSpec(spec);
491
+ const names = isolationNames(spec.runId);
492
+ const host = container.HostConfig;
493
+ const security = host?.SecurityOpt ?? [];
494
+ const mount = container.Mounts?.[0];
495
+ const requestedMount = host?.Mounts?.[0];
496
+ if (container.Config?.User !== `${CODING_WORKER_UID}:${CODING_WORKER_GID}` ||
497
+ container.Config?.Image !== spec.image ||
498
+ !hasResourceLabels(container.Config.Labels, spec.runId) ||
499
+ host?.NetworkMode !== "none" ||
500
+ host.ReadonlyRootfs !== true ||
501
+ host.Privileged !== false ||
502
+ (host.Binds?.length ?? 0) !== 0 ||
503
+ (host.CapAdd?.length ?? 0) !== 0 ||
504
+ !host.CapDrop?.includes("ALL") ||
505
+ !security.includes("no-new-privileges=true") ||
506
+ !security.includes("seccomp=builtin") ||
507
+ host.PidsLimit !== KEEPER_PIDS_LIMIT ||
508
+ host.RestartPolicy?.Name !== "no" ||
509
+ (container.Mounts?.length ?? 0) !== 1 ||
510
+ mount?.Type !== "volume" ||
511
+ mount.Name !== names.storageVolume ||
512
+ mount.Destination !== WORKER_PATHS.storage ||
513
+ mount.RW !== true ||
514
+ (host.Mounts?.length ?? 0) !== 1 ||
515
+ requestedMount?.Type !== "volume" ||
516
+ requestedMount.Source !== names.storageVolume ||
517
+ requestedMount.Target !== WORKER_PATHS.storage ||
518
+ requestedMount.VolumeOptions?.NoCopy !== true ||
519
+ Object.keys(container.NetworkSettings?.Networks ?? {}).join(",") !== "none") {
520
+ throw isolationError();
521
+ }
522
+ }
523
+ export function assertProxyContainerInspection(container, runId) {
524
+ const internalNetwork = isolationNames(runId).network;
525
+ const networks = container.NetworkSettings?.Networks ?? {};
526
+ const internal = networks[internalNetwork];
527
+ const internalNames = [...(internal?.Aliases ?? []), ...(internal?.DNSNames ?? [])];
528
+ const external = Object.entries(networks).filter(([name]) => name !== internalNetwork);
529
+ if (!internal ||
530
+ !internalNames.includes(CODING_PROXY_ALIAS) ||
531
+ internal.Gateway !== "" ||
532
+ external.length < 1 ||
533
+ !external.some(([, network]) => Boolean(network.Gateway))) {
534
+ throw isolationError();
535
+ }
536
+ }
537
+ function assertExactWorkerMounts(container, names) {
538
+ const expected = new Map([
539
+ [WORKER_PATHS.workspace, true],
540
+ [WORKER_PATHS.input, false],
541
+ [WORKER_PATHS.output, true],
542
+ ]);
543
+ const mounts = container.Mounts ?? [];
544
+ if (mounts.length !== expected.size)
545
+ throw isolationError();
546
+ for (const mount of mounts) {
547
+ if (mount.Type !== "volume" ||
548
+ mount.Name !== names.storageVolume ||
549
+ !mount.Destination ||
550
+ expected.get(mount.Destination) !== mount.RW) {
551
+ throw isolationError();
552
+ }
553
+ }
554
+ const requested = container.HostConfig?.Mounts ?? [];
555
+ const expectedRequested = new Map([
556
+ [WORKER_PATHS.workspace, { readOnly: false, subpath: "workspace" }],
557
+ [WORKER_PATHS.input, { readOnly: true, subpath: "input" }],
558
+ [WORKER_PATHS.output, { readOnly: false, subpath: "output" }],
559
+ ]);
560
+ if (requested.length !== expectedRequested.size)
561
+ throw isolationError();
562
+ for (const mount of requested) {
563
+ const expectedMount = mount.Target ? expectedRequested.get(mount.Target) : undefined;
564
+ if (mount.Type !== "volume" ||
565
+ mount.Source !== names.storageVolume ||
566
+ !expectedMount ||
567
+ Boolean(mount.ReadOnly) !== expectedMount.readOnly ||
568
+ mount.VolumeOptions?.NoCopy !== true ||
569
+ mount.VolumeOptions.Subpath !== expectedMount.subpath) {
570
+ throw isolationError();
571
+ }
572
+ }
573
+ }
574
+ export function assertWorkerContainerInspection(container, spec, expectedCapability) {
575
+ validateSpec(spec);
576
+ const names = isolationNames(spec.runId);
577
+ const host = container.HostConfig;
578
+ const networks = Object.keys(container.NetworkSettings?.Networks ?? {});
579
+ const security = host?.SecurityOpt ?? [];
580
+ const wardbyEnvironment = (container.Config?.Env ?? []).filter((value) => value.startsWith("WARDBY_"));
581
+ if (container.Config?.User !== `${CODING_WORKER_UID}:${CODING_WORKER_GID}` ||
582
+ container.Config?.Image !== spec.image ||
583
+ !hasResourceLabels(container.Config.Labels, spec.runId) ||
584
+ host?.ReadonlyRootfs !== true ||
585
+ host.Privileged !== false ||
586
+ (host.Binds?.length ?? 0) !== 0 ||
587
+ (host.CapAdd?.length ?? 0) !== 0 ||
588
+ !host.CapDrop?.includes("ALL") ||
589
+ host.CgroupnsMode !== "private" ||
590
+ host.IpcMode !== "none" ||
591
+ host.Init !== true ||
592
+ host.Memory !== spec.limits.memoryMb * 1024 * 1024 ||
593
+ host.MemorySwap !== host.Memory ||
594
+ host.PidsLimit !== spec.limits.pids ||
595
+ host.NanoCpus !== Math.round(spec.limits.cpus * 1_000_000_000) ||
596
+ host.ShmSize !== 16 * 1024 * 1024 ||
597
+ host.NetworkMode !== names.network ||
598
+ host.PidMode !== "" ||
599
+ host.RestartPolicy?.Name !== "no" ||
600
+ host.LogConfig?.Type !== "local" ||
601
+ host.LogConfig.Config?.["max-size"] !== "1m" ||
602
+ host.LogConfig.Config?.["max-file"] !== "2" ||
603
+ !security.includes("no-new-privileges=true") ||
604
+ !security.includes("seccomp=builtin") ||
605
+ (host.Devices?.length ?? 0) !== 0 ||
606
+ (host.DeviceRequests?.length ?? 0) !== 0 ||
607
+ (host.Dns?.length ?? 0) !== 0 ||
608
+ (host.DnsOptions?.length ?? 0) !== 0 ||
609
+ (host.DnsSearch?.length ?? 0) !== 0 ||
610
+ (host.ExtraHosts?.length ?? 0) !== 0 ||
611
+ (host.GroupAdd?.length ?? 0) !== 0 ||
612
+ Object.keys(host.PortBindings ?? {}).length !== 0 ||
613
+ host.PublishAllPorts !== false ||
614
+ Object.keys(container.NetworkSettings?.Ports ?? {}).length !== 0 ||
615
+ networks.length !== 1 ||
616
+ networks[0] !== names.network) {
617
+ throw isolationError();
618
+ }
619
+ if (wardbyEnvironment.length !== 2 ||
620
+ !wardbyEnvironment.includes(`WARDBY_PROXY_URL=http://${CODING_PROXY_ALIAS}:${CODING_PROXY_PORT}`) ||
621
+ !wardbyEnvironment.includes(`WARDBY_RUN_CAPABILITY=${expectedCapability}`)) {
622
+ throw isolationError();
623
+ }
624
+ const tmpfs = host.Tmpfs ?? {};
625
+ if (!tmpfs["/tmp"]?.includes("noexec") || !tmpfs["/home/wardby"]?.includes("noexec"))
626
+ throw isolationError();
627
+ assertExactWorkerMounts(container, names);
628
+ }
629
+ function assertExactMountSet(container, names, expected) {
630
+ const mounts = container.Mounts ?? [];
631
+ const requested = container.HostConfig?.Mounts ?? [];
632
+ if (mounts.length !== expected.length || requested.length !== expected.length)
633
+ throw isolationError();
634
+ for (const item of expected) {
635
+ const mount = mounts.find((value) => value.Destination === item.path);
636
+ const request = requested.find((value) => value.Target === item.path);
637
+ if (mount?.Type !== "volume" ||
638
+ mount.Name !== names.storageVolume ||
639
+ mount.RW !== item.writable ||
640
+ request?.Type !== "volume" ||
641
+ request.Source !== names.storageVolume ||
642
+ Boolean(request.ReadOnly) === item.writable ||
643
+ request.VolumeOptions?.NoCopy !== true ||
644
+ request.VolumeOptions.Subpath !== item.subpath)
645
+ throw isolationError();
646
+ }
647
+ }
648
+ function assertClaudeContainerBaseline(container, image, network, spec, limits) {
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 !== image ||
653
+ !hasResourceLabels(container.Config.Labels, spec.runId) ||
654
+ host?.ReadonlyRootfs !== true ||
655
+ host.Privileged !== false ||
656
+ (host.Binds?.length ?? 0) !== 0 ||
657
+ (host.CapAdd?.length ?? 0) !== 0 ||
658
+ !host.CapDrop?.includes("ALL") ||
659
+ host.CgroupnsMode !== "private" ||
660
+ host.IpcMode !== "none" ||
661
+ host.Init !== true ||
662
+ host.Memory !== limits.memoryMb * 1024 * 1024 ||
663
+ host.MemorySwap !== host.Memory ||
664
+ // cgroup v2 hosts may report this as null after accepting the explicit
665
+ // no-swappiness request; MemorySwap still attests that swap is disabled.
666
+ (host.MemorySwappiness !== 0 && host.MemorySwappiness !== null) ||
667
+ host.PidsLimit !== limits.pids ||
668
+ host.NanoCpus !== Math.round(limits.cpus * 1_000_000_000) ||
669
+ host.ShmSize !== 16 * 1024 * 1024 ||
670
+ host.NetworkMode !== network ||
671
+ host.PidMode !== "" ||
672
+ host.RestartPolicy?.Name !== "no" ||
673
+ host.LogConfig?.Type !== "local" ||
674
+ host.LogConfig.Config?.["max-size"] !== "1m" ||
675
+ host.LogConfig.Config?.["max-file"] !== "2" ||
676
+ !security.includes("no-new-privileges=true") ||
677
+ !security.includes("seccomp=builtin") ||
678
+ (host.Devices?.length ?? 0) !== 0 ||
679
+ (host.DeviceRequests?.length ?? 0) !== 0 ||
680
+ (host.Dns?.length ?? 0) !== 0 ||
681
+ (host.DnsOptions?.length ?? 0) !== 0 ||
682
+ (host.DnsSearch?.length ?? 0) !== 0 ||
683
+ (host.ExtraHosts?.length ?? 0) !== 0 ||
684
+ (host.GroupAdd?.length ?? 0) !== 0 ||
685
+ Object.keys(host.PortBindings ?? {}).length !== 0 ||
686
+ host.PublishAllPorts !== false ||
687
+ !host.Tmpfs?.["/tmp"]?.includes("noexec") ||
688
+ !host.Tmpfs?.["/home/wardby"]?.includes("noexec"))
689
+ throw isolationError();
690
+ }
691
+ export function assertClaudeAgentContainerInspection(container, spec, expectedCapability) {
692
+ validateSpec(spec);
693
+ if (spec.provider !== "claude-code")
694
+ throw isolationError();
695
+ const names = isolationNames(spec.runId);
696
+ assertClaudeContainerBaseline(container, spec.image, names.network, spec, claudeAgentLimits(spec));
697
+ const environment = (container.Config?.Env ?? []).filter((value) => value.startsWith("WARDBY_"));
698
+ if (environment.length !== 2 ||
699
+ !environment.includes(`WARDBY_PROXY_URL=http://${CODING_PROXY_ALIAS}:${CODING_PROXY_PORT}`) ||
700
+ !environment.includes(`WARDBY_RUN_CAPABILITY=${expectedCapability}`))
701
+ throw isolationError();
702
+ assertExactMountSet(container, names, [
703
+ { path: WORKER_PATHS.input, writable: false, subpath: "input" },
704
+ { path: WORKER_PATHS.output, writable: true, subpath: "output" },
705
+ { path: WORKER_PATHS.tool, writable: true, subpath: "tool" },
706
+ ]);
707
+ if (Object.keys(container.NetworkSettings?.Networks ?? {}).join(",") !== names.network)
708
+ throw isolationError();
709
+ }
710
+ export function assertClaudeToolRunnerContainerInspection(container, spec) {
711
+ validateSpec(spec);
712
+ if (spec.provider !== "claude-code" || !spec.toolImage)
713
+ throw isolationError();
714
+ const names = isolationNames(spec.runId);
715
+ assertClaudeContainerBaseline(container, spec.toolImage, "none", spec, claudeToolLimits(spec));
716
+ if ((container.Config?.Env ?? []).some((value) => value.startsWith("WARDBY_")))
717
+ throw isolationError();
718
+ if (Object.keys(container.NetworkSettings?.Networks ?? {}).join(",") !== "none")
719
+ throw isolationError();
720
+ assertExactMountSet(container, names, [
721
+ { path: WORKER_PATHS.workspace, writable: true, subpath: "workspace" },
722
+ { path: WORKER_PATHS.tool, writable: true, subpath: "tool" },
723
+ ]);
724
+ }