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