@wardby/cli 0.5.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (411) hide show
  1. package/.env.example +33 -0
  2. package/README.md +25 -14
  3. package/dist/claude-coding-worker/context.d.ts +22 -0
  4. package/dist/claude-coding-worker/context.js +92 -0
  5. package/dist/claude-coding-worker/driver.d.ts +7 -1
  6. package/dist/claude-coding-worker/driver.js +38 -5
  7. package/dist/claude-coding-worker/sdk.js +13 -4
  8. package/dist/cli-help.d.ts +1 -1
  9. package/dist/cli-help.js +5 -2
  10. package/dist/cli-native-mode.d.ts +9 -0
  11. package/dist/cli-native-mode.js +20 -0
  12. package/dist/cli.d.ts +3 -1
  13. package/dist/cli.js +141 -5
  14. package/dist/coding/claude-context.d.ts +45 -0
  15. package/dist/coding/claude-context.js +434 -0
  16. package/dist/coding/local-git.js +9 -1
  17. package/dist/coding/profile.d.ts +12 -0
  18. package/dist/coding/profile.js +6 -0
  19. package/dist/coding/protocol.d.ts +104 -0
  20. package/dist/coding/protocol.js +87 -3
  21. package/dist/coding/registry/pypi.d.ts +25 -1
  22. package/dist/coding/registry/pypi.js +140 -16
  23. package/dist/coding/registry/types.d.ts +17 -3
  24. package/dist/coding-worker/artifact.d.ts +8 -0
  25. package/dist/coding-worker/artifact.js +2 -2
  26. package/dist/coding-worker/driver.js +2 -0
  27. package/dist/coding-worker/sdk.d.ts +6 -0
  28. package/dist/coding-worker/sdk.js +1 -0
  29. package/dist/coding-worker/skills.d.ts +23 -0
  30. package/dist/coding-worker/skills.js +117 -0
  31. package/dist/coding-worker/types.d.ts +2 -0
  32. package/dist/config/github-allowed-accounts.d.ts +15 -0
  33. package/dist/config/github-allowed-accounts.js +36 -0
  34. package/dist/config/low-power.d.ts +12 -0
  35. package/dist/config/low-power.js +18 -0
  36. package/dist/config/mobile.d.ts +12 -0
  37. package/dist/config/mobile.js +18 -0
  38. package/dist/config/providers.d.ts +56 -2
  39. package/dist/config/providers.js +92 -6
  40. package/dist/core/db.d.ts +16 -6
  41. package/dist/core/db.js +12 -10
  42. package/dist/core/dispatch.d.ts +11 -0
  43. package/dist/core/dispatch.js +36 -1
  44. package/dist/core/engine-native.js +9 -5
  45. package/dist/core/host-events.d.ts +49 -3
  46. package/dist/core/host-events.js +289 -15
  47. package/dist/core/http-runtime.js +13 -6
  48. package/dist/core/issue-bridge.js +42 -2
  49. package/dist/core/issue-events.js +12 -1
  50. package/dist/core/issue-status.js +14 -3
  51. package/dist/core/merge-order-check.d.ts +129 -0
  52. package/dist/core/merge-order-check.js +336 -0
  53. package/dist/core/notification-dispatcher.d.ts +70 -0
  54. package/dist/core/notification-dispatcher.js +368 -0
  55. package/dist/core/notification-templates.d.ts +25 -0
  56. package/dist/core/notification-templates.js +105 -0
  57. package/dist/core/notifications.d.ts +10 -0
  58. package/dist/core/notifications.js +38 -0
  59. package/dist/core/pg-auth.d.ts +22 -0
  60. package/dist/core/pg-auth.js +152 -0
  61. package/dist/core/power-composition.d.ts +16 -0
  62. package/dist/core/power-composition.js +37 -0
  63. package/dist/core/power-idle.d.ts +14 -0
  64. package/dist/core/power-idle.js +30 -0
  65. package/dist/core/power.d.ts +71 -0
  66. package/dist/core/power.js +202 -0
  67. package/dist/core/reconciler.d.ts +4 -2
  68. package/dist/core/reconciler.js +29 -5
  69. package/dist/core/related-pull-requests.d.ts +48 -4
  70. package/dist/core/related-pull-requests.js +113 -11
  71. package/dist/core/repo-access.d.ts +15 -0
  72. package/dist/core/repo-access.js +66 -0
  73. package/dist/core/review-fix.d.ts +37 -0
  74. package/dist/core/review-fix.js +207 -6
  75. package/dist/core/review-host-tools.d.ts +20 -0
  76. package/dist/core/review-host-tools.js +58 -2
  77. package/dist/core/runner.d.ts +336 -3
  78. package/dist/core/runner.js +894 -317
  79. package/dist/core/secret-broker-config.d.ts +103 -0
  80. package/dist/core/secret-broker-config.js +112 -0
  81. package/dist/core/secrets.d.ts +39 -4
  82. package/dist/core/secrets.js +148 -40
  83. package/dist/core/workflow-event-recorder.d.ts +14 -0
  84. package/dist/core/workflow-event-recorder.js +120 -0
  85. package/dist/core/workflow-events.d.ts +82 -0
  86. package/dist/core/workflow-events.js +44 -0
  87. package/dist/core/workflow-run-events.d.ts +16 -0
  88. package/dist/core/workflow-run-events.js +40 -0
  89. package/dist/generated/prisma/browser.d.ts +58 -0
  90. package/dist/generated/prisma/client.d.ts +58 -0
  91. package/dist/generated/prisma/commonInputTypes.d.ts +150 -0
  92. package/dist/generated/prisma/enums.d.ts +27 -0
  93. package/dist/generated/prisma/enums.js +23 -0
  94. package/dist/generated/prisma/internal/class.d.ts +88 -0
  95. package/dist/generated/prisma/internal/class.js +4 -4
  96. package/dist/generated/prisma/internal/prismaNamespace.d.ts +772 -1
  97. package/dist/generated/prisma/internal/prismaNamespace.js +135 -4
  98. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +139 -0
  99. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +135 -4
  100. package/dist/generated/prisma/models/Agent.d.ts +360 -1
  101. package/dist/generated/prisma/models/CodingAgentProfile.d.ts +73 -1
  102. package/dist/generated/prisma/models/CodingRun.d.ts +205 -1
  103. package/dist/generated/prisma/models/DeferredReview.d.ts +78 -1
  104. package/dist/generated/prisma/models/NativeGatewayCall.d.ts +1675 -0
  105. package/dist/generated/prisma/models/NativeGatewayCall.js +1 -0
  106. package/dist/generated/prisma/models/NativeGatewaySession.d.ts +1517 -0
  107. package/dist/generated/prisma/models/NativeGatewaySession.js +1 -0
  108. package/dist/generated/prisma/models/NativeWarmWorker.d.ts +1296 -0
  109. package/dist/generated/prisma/models/NativeWarmWorker.js +1 -0
  110. package/dist/generated/prisma/models/NotificationChannel.d.ts +1543 -0
  111. package/dist/generated/prisma/models/NotificationChannel.js +1 -0
  112. package/dist/generated/prisma/models/NotificationDelivery.d.ts +1502 -0
  113. package/dist/generated/prisma/models/NotificationDelivery.js +1 -0
  114. package/dist/generated/prisma/models/NotificationThread.d.ts +1090 -0
  115. package/dist/generated/prisma/models/NotificationThread.js +1 -0
  116. package/dist/generated/prisma/models/Run.d.ts +826 -1
  117. package/dist/generated/prisma/models/RunHostCheck.d.ts +36 -1
  118. package/dist/generated/prisma/models/Secret.d.ts +37 -1
  119. package/dist/generated/prisma/models/SecretBrokerChange.d.ts +1101 -0
  120. package/dist/generated/prisma/models/SecretBrokerChange.js +1 -0
  121. package/dist/generated/prisma/models/WorkflowEvent.d.ts +1422 -0
  122. package/dist/generated/prisma/models/WorkflowEvent.js +1 -0
  123. package/dist/generated/prisma/models.d.ts +8 -0
  124. package/dist/help/build.js +4 -2
  125. package/dist/help/catalog.d.ts +1 -1
  126. package/dist/help/catalog.js +31 -6
  127. package/dist/help-index.json +1492 -113
  128. package/dist/import/create.js +17 -0
  129. package/dist/import/neutral-schema.d.ts +6 -2
  130. package/dist/import/neutral-schema.js +2 -0
  131. package/dist/mcp/auth/access.d.ts +1 -0
  132. package/dist/mcp/auth/ownership.d.ts +1 -1
  133. package/dist/mcp/auth/repo-authorization.js +14 -0
  134. package/dist/mcp/auth/resource-server.d.ts +2 -1
  135. package/dist/mcp/auth/resource-server.js +7 -2
  136. package/dist/mcp/auth/self-hosted/browser.d.ts +1 -1
  137. package/dist/mcp/auth/self-hosted/browser.js +19 -6
  138. package/dist/mcp/context.d.ts +6 -0
  139. package/dist/mcp/host-events/github-ingress.d.ts +23 -1
  140. package/dist/mcp/host-events/github-ingress.js +68 -0
  141. package/dist/mcp/index.d.ts +13 -0
  142. package/dist/mcp/index.js +59 -10
  143. package/dist/mcp/tools/agents.js +83 -12
  144. package/dist/mcp/tools/notification-channels.d.ts +2 -0
  145. package/dist/mcp/tools/notification-channels.js +293 -0
  146. package/dist/mcp/tools/power.d.ts +2 -0
  147. package/dist/mcp/tools/power.js +77 -0
  148. package/dist/mcp/tools/runs.d.ts +0 -6
  149. package/dist/mcp/tools/runs.js +7 -2
  150. package/dist/mcp/tools/secret-broker-compat.d.ts +12 -0
  151. package/dist/mcp/tools/secret-broker-compat.js +23 -0
  152. package/dist/mcp/tools/secret-elicitation-form.d.ts +22 -2
  153. package/dist/mcp/tools/secret-elicitation-form.js +200 -10
  154. package/dist/mcp/tools/secret-elicitation-server.js +8 -1
  155. package/dist/mcp/tools/secret-elicitation.d.ts +38 -3
  156. package/dist/mcp/tools/secret-elicitation.js +110 -12
  157. package/dist/mcp/tools/secrets.js +184 -7
  158. package/dist/mcp/tools/tools.d.ts +1 -1
  159. package/dist/mcp/tools/tools.js +22 -2
  160. package/dist/native-worker/composition.d.ts +23 -0
  161. package/dist/native-worker/composition.js +77 -0
  162. package/dist/native-worker/docker-isolation.d.ts +65 -0
  163. package/dist/native-worker/docker-isolation.js +183 -0
  164. package/dist/native-worker/docker-launcher.d.ts +74 -0
  165. package/dist/native-worker/docker-launcher.js +237 -0
  166. package/dist/native-worker/gateway-server.d.ts +39 -0
  167. package/dist/native-worker/gateway-server.js +315 -0
  168. package/dist/native-worker/gateway.d.ts +44 -0
  169. package/dist/native-worker/gateway.js +197 -0
  170. package/dist/native-worker/http-server.d.ts +22 -0
  171. package/dist/native-worker/http-server.js +109 -0
  172. package/dist/native-worker/http-transport.d.ts +17 -0
  173. package/dist/native-worker/http-transport.js +88 -0
  174. package/dist/native-worker/kubernetes-isolation.d.ts +90 -0
  175. package/dist/native-worker/kubernetes-isolation.js +218 -0
  176. package/dist/native-worker/kubernetes-launcher.d.ts +102 -0
  177. package/dist/native-worker/kubernetes-launcher.js +335 -0
  178. package/dist/native-worker/launch.d.ts +45 -0
  179. package/dist/native-worker/launch.js +44 -0
  180. package/dist/native-worker/ledger.d.ts +109 -0
  181. package/dist/native-worker/ledger.js +192 -0
  182. package/dist/native-worker/loopback.d.ts +12 -0
  183. package/dist/native-worker/loopback.js +49 -0
  184. package/dist/native-worker/main.d.ts +7 -0
  185. package/dist/native-worker/main.js +74 -0
  186. package/dist/native-worker/protocol.d.ts +744 -0
  187. package/dist/native-worker/protocol.js +200 -0
  188. package/dist/native-worker/sandbox-executor.d.ts +68 -0
  189. package/dist/native-worker/sandbox-executor.js +210 -0
  190. package/dist/native-worker/stdio.d.ts +29 -0
  191. package/dist/native-worker/stdio.js +188 -0
  192. package/dist/native-worker/warm-delivery.d.ts +16 -0
  193. package/dist/native-worker/warm-delivery.js +33 -0
  194. package/dist/native-worker/warm-pool-ledger.d.ts +39 -0
  195. package/dist/native-worker/warm-pool-ledger.js +73 -0
  196. package/dist/native-worker/warm-pool.d.ts +107 -0
  197. package/dist/native-worker/warm-pool.js +283 -0
  198. package/dist/native-worker/worker.d.ts +17 -0
  199. package/dist/native-worker/worker.js +115 -0
  200. package/dist/providers/auth/authorization-server.d.ts +3 -0
  201. package/dist/providers/auth/index.js +2 -0
  202. package/dist/providers/auth/self-hosted.d.ts +13 -1
  203. package/dist/providers/auth/self-hosted.js +54 -4
  204. package/dist/providers/chat/fake.d.ts +46 -0
  205. package/dist/providers/chat/fake.js +39 -0
  206. package/dist/providers/chat/index.d.ts +4 -0
  207. package/dist/providers/chat/index.js +8 -0
  208. package/dist/providers/chat/slack.d.ts +37 -0
  209. package/dist/providers/chat/slack.js +105 -0
  210. package/dist/providers/chat/types.d.ts +42 -0
  211. package/dist/providers/chat/types.js +19 -0
  212. package/dist/providers/coding-proxy/mock-upstream.d.ts +33 -0
  213. package/dist/providers/coding-proxy/mock-upstream.js +116 -0
  214. package/dist/providers/coding-proxy/registry/prisma-store.d.ts +3 -0
  215. package/dist/providers/coding-proxy/registry/prisma-store.js +17 -0
  216. package/dist/providers/coding-proxy/registry/service.d.ts +4 -0
  217. package/dist/providers/coding-proxy/registry/service.js +20 -3
  218. package/dist/providers/coding-proxy/registry/store.d.ts +18 -1
  219. package/dist/providers/coding-proxy/registry/store.js +33 -0
  220. package/dist/providers/coding-proxy/runtime.js +12 -1
  221. package/dist/providers/coding-proxy/types.d.ts +2 -0
  222. package/dist/providers/engine/types.d.ts +19 -0
  223. package/dist/providers/engine/types.js +22 -0
  224. package/dist/providers/executor/composition.d.ts +24 -1
  225. package/dist/providers/executor/composition.js +39 -6
  226. package/dist/providers/executor/container.d.ts +24 -1
  227. package/dist/providers/executor/container.js +101 -11
  228. package/dist/providers/executor/dbos.d.ts +42 -0
  229. package/dist/providers/executor/dbos.js +118 -12
  230. package/dist/providers/executor/deferred.d.ts +56 -0
  231. package/dist/providers/executor/deferred.js +119 -0
  232. package/dist/providers/executor/routing.d.ts +37 -10
  233. package/dist/providers/executor/routing.js +73 -32
  234. package/dist/providers/executor/types.d.ts +12 -0
  235. package/dist/providers/jobs/docker.js +3 -3
  236. package/dist/providers/jobs/fake-kubernetes-api.d.ts +21 -2
  237. package/dist/providers/jobs/fake-kubernetes-api.js +42 -1
  238. package/dist/providers/jobs/kubernetes-api.d.ts +17 -1
  239. package/dist/providers/jobs/kubernetes-api.js +7 -0
  240. package/dist/providers/jobs/kubernetes-client.d.ts +10 -1
  241. package/dist/providers/jobs/kubernetes-client.js +52 -3
  242. package/dist/providers/jobs/kubernetes-dry-run-fixture.d.ts +6 -1
  243. package/dist/providers/jobs/kubernetes-dry-run-fixture.js +6 -3
  244. package/dist/providers/jobs/kubernetes-isolation.d.ts +16 -1
  245. package/dist/providers/jobs/kubernetes-isolation.js +58 -5
  246. package/dist/providers/jobs/kubernetes-platform.d.ts +4 -1
  247. package/dist/providers/jobs/kubernetes-platform.js +38 -1
  248. package/dist/providers/jobs/kubernetes-preflight.d.ts +2 -1
  249. package/dist/providers/jobs/kubernetes-preflight.js +4 -2
  250. package/dist/providers/jobs/kubernetes.d.ts +56 -7
  251. package/dist/providers/jobs/kubernetes.js +117 -36
  252. package/dist/providers/jobs/metadata-probes.d.ts +11 -0
  253. package/dist/providers/jobs/metadata-probes.js +13 -0
  254. package/dist/providers/jobs/types.d.ts +9 -0
  255. package/dist/providers/llm/openai.js +0 -1
  256. package/dist/providers/review-host/github.d.ts +11 -1
  257. package/dist/providers/review-host/github.js +50 -0
  258. package/dist/providers/review-host/types.d.ts +23 -0
  259. package/dist/providers/vcs/github.d.ts +32 -6
  260. package/dist/providers/vcs/github.js +74 -16
  261. package/dist/quickstart/coding-db.js +14 -2
  262. package/dist/quickstart/coding-doctor.d.ts +3 -0
  263. package/dist/quickstart/coding-doctor.js +3 -0
  264. package/dist/quickstart/coding-images.d.ts +9 -0
  265. package/dist/quickstart/coding-images.js +39 -0
  266. package/dist/quickstart/coding-seed.d.ts +20 -1
  267. package/dist/quickstart/coding-seed.js +96 -7
  268. package/dist/quickstart/coding.d.ts +2 -0
  269. package/dist/quickstart/coding.js +75 -3
  270. package/dist/quickstart/config.d.ts +2 -0
  271. package/dist/quickstart/config.js +4 -0
  272. package/dist/quickstart/images.d.ts +13 -0
  273. package/dist/quickstart/images.js +80 -12
  274. package/dist/quickstart/index.d.ts +0 -2
  275. package/dist/quickstart/index.js +10 -14
  276. package/dist/quickstart/manifests.d.ts +56 -0
  277. package/dist/quickstart/manifests.js +523 -0
  278. package/dist/quickstart/python-detect.d.ts +10 -0
  279. package/dist/quickstart/python-detect.js +32 -0
  280. package/dist/quickstart/repo-packages.d.ts +10 -0
  281. package/dist/quickstart/repo-packages.js +132 -0
  282. package/dist/quickstart/reviewer-prompt.d.ts +12 -0
  283. package/dist/quickstart/reviewer-prompt.js +84 -0
  284. package/dist/quickstart/scenarios.d.ts +19 -0
  285. package/dist/quickstart/scenarios.js +28 -0
  286. package/dist/quickstart/sync-reviewer-prompt.d.ts +1 -0
  287. package/dist/quickstart/sync-reviewer-prompt.js +10 -0
  288. package/dist/quickstart-images.json +1 -1
  289. package/dist/sandbox/brokered-fetch.d.ts +23 -0
  290. package/dist/sandbox/brokered-fetch.js +37 -0
  291. package/dist/sandbox/host-functions.d.ts +34 -0
  292. package/dist/sandbox/host-functions.js +144 -80
  293. package/dist/sandbox/prelude.js +7 -1
  294. package/dist/sandbox/run-in-sandbox.d.ts +19 -0
  295. package/dist/sandbox/run-in-sandbox.js +28 -10
  296. package/dist/sandbox/safe-fetch.d.ts +2 -0
  297. package/dist/sandbox/safe-fetch.js +3 -1
  298. package/dist/sandbox/secret-broker-sigv4.d.ts +2 -0
  299. package/dist/sandbox/secret-broker-sigv4.js +49 -0
  300. package/dist/sandbox/secret-broker.d.ts +34 -0
  301. package/dist/sandbox/secret-broker.js +162 -0
  302. package/dist/sandbox/user-tool.d.ts +14 -0
  303. package/dist/sandbox/user-tool.js +32 -0
  304. package/dist/serve.js +18 -0
  305. package/dist/viewer/api-schema.d.ts +214 -18
  306. package/dist/viewer/api-schema.js +22 -0
  307. package/dist/viewer/build-schemas.js +4 -1
  308. package/dist/viewer/event-bus.d.ts +6 -0
  309. package/dist/viewer/event-bus.js +16 -7
  310. package/dist/viewer/graph.d.ts +5 -2
  311. package/dist/viewer/graph.js +7 -2
  312. package/dist/viewer/http.d.ts +0 -1
  313. package/dist/viewer/http.js +2 -2
  314. package/dist/viewer/infra.js +25 -2
  315. package/dist/viewer/limits.d.ts +4 -0
  316. package/dist/viewer/limits.js +4 -0
  317. package/dist/viewer/openapi.d.ts +15 -0
  318. package/dist/viewer/openapi.js +172 -0
  319. package/docs/README.md +7 -0
  320. package/docs/agent-recipes.md +57 -8
  321. package/docs/architecture-runtime.md +16 -1
  322. package/docs/code-review-agents.md +139 -5
  323. package/docs/coding-agent-setup.md +127 -2
  324. package/docs/coding-packages.md +105 -5
  325. package/docs/coding-services.md +3 -1
  326. package/docs/coding-worker-byo-images.md +239 -2
  327. package/docs/coding-worker-isolation.md +88 -23
  328. package/docs/getting-started-gke.md +225 -9
  329. package/docs/getting-started-identity-provider.md +21 -10
  330. package/docs/getting-started.md +140 -11
  331. package/docs/jira-agents.md +5 -1
  332. package/docs/knowledge.md +11 -0
  333. package/docs/native-sandbox.md +635 -0
  334. package/docs/release-verification.md +9 -0
  335. package/docs/security-deployment.md +113 -12
  336. package/docs/slack-notifications.md +252 -0
  337. package/docs/tool-secrets.md +252 -0
  338. package/docs/viewer-api.md +46 -5
  339. package/docs/viewer-api.openapi.yml +1404 -0
  340. package/help/admin-viewer.md +6 -2
  341. package/help/agent-recipes.md +27 -0
  342. package/help/architecture-agent.md +136 -0
  343. package/help/brokered-secrets.md +79 -0
  344. package/help/build-claude-code-image.md +131 -0
  345. package/help/build-worker-image.md +195 -0
  346. package/help/code-review-agents.md +68 -6
  347. package/help/coding-packages.md +31 -2
  348. package/help/creating-agents.md +14 -0
  349. package/help/deploy-gke.md +37 -6
  350. package/help/deployment-targets.md +24 -13
  351. package/help/errors/coding-provider-not-configured.md +2 -2
  352. package/help/errors/database-auth-invalid.md +28 -0
  353. package/help/errors/github-account-not-allowed.md +25 -0
  354. package/help/errors/low-power-disabled.md +29 -0
  355. package/help/errors/low-power-scale-forbidden.md +35 -0
  356. package/help/errors/low-power-wake-timeout.md +30 -0
  357. package/help/errors/mobile-disabled.md +31 -0
  358. package/help/errors/native-sandbox-capacity.md +22 -0
  359. package/help/errors/native-sandbox-deadline-exceeded.md +19 -0
  360. package/help/errors/native-sandbox-docker-failed.md +20 -0
  361. package/help/errors/native-sandbox-gateway-unavailable.md +21 -0
  362. package/help/errors/native-sandbox-image-not-pinned.md +19 -0
  363. package/help/errors/native-sandbox-isolation-mismatch.md +20 -0
  364. package/help/errors/native-sandbox-network-unenforced.md +22 -0
  365. package/help/errors/native-sandbox-requires-catalog.md +31 -0
  366. package/help/errors/native-sandbox-unavailable.md +19 -0
  367. package/help/errors/native-sandbox-warm-delivery-failed.md +21 -0
  368. package/help/errors/native-sandbox-worker-exited.md +20 -0
  369. package/help/errors/native-sandbox-worker-lost.md +20 -0
  370. package/help/errors/native-sandbox-worker-unready.md +22 -0
  371. package/help/errors/secret-broker-body-invalid.md +26 -0
  372. package/help/errors/secret-broker-changed.md +20 -0
  373. package/help/errors/secret-broker-config-invalid.md +30 -0
  374. package/help/errors/secret-broker-conflict.md +27 -0
  375. package/help/errors/secret-broker-destination-denied.md +28 -0
  376. package/help/errors/secret-broker-value-invalid.md +28 -0
  377. package/help/errors/secret-brokered.md +24 -0
  378. package/help/errors/secret-not-brokered.md +25 -0
  379. package/help/errors/slack-auth-failed.md +48 -0
  380. package/help/errors/slack-channel-unreachable.md +39 -0
  381. package/help/errors/slack-not-configured.md +33 -0
  382. package/help/getting-started.md +18 -0
  383. package/help/github.md +18 -1
  384. package/help/identity-and-access.md +5 -1
  385. package/help/local-repositories.md +35 -5
  386. package/help/low-power-mode.md +125 -0
  387. package/help/merge-order-check.md +117 -0
  388. package/help/mobile-app.md +54 -0
  389. package/help/native-sandbox.md +136 -0
  390. package/help/observability.md +8 -0
  391. package/help/related-pull-requests.md +70 -9
  392. package/help/repo-instructions-and-skills.md +91 -0
  393. package/help/review-fix-rounds.md +10 -0
  394. package/help/slack-notifications.md +62 -0
  395. package/help/troubleshooting/coding-workers.md +21 -0
  396. package/package.json +13 -5
  397. package/prisma/migrations/20261009000003_native_execution_mode/migration.sql +12 -0
  398. package/prisma/migrations/20261009000004_native_gateway_ledger/migration.sql +65 -0
  399. package/prisma/migrations/20261009000005_native_gateway_call_child_agent/migration.sql +5 -0
  400. package/prisma/migrations/20261009000006_run_deferred_start_stop/migration.sql +14 -0
  401. package/prisma/migrations/20261009000007_native_gateway_network_ready/migration.sql +5 -0
  402. package/prisma/migrations/20261010000001_workflow_notifications/migration.sql +99 -0
  403. package/prisma/migrations/20261011000001_native_gateway_session_status_index/migration.sql +2 -0
  404. package/prisma/migrations/20261012000001_native_warm_worker/migration.sql +32 -0
  405. package/prisma/migrations/20261013000001_coding_run_merge_order/migration.sql +2 -0
  406. package/prisma/migrations/20261013000001_secret_broker/migration.sql +17 -0
  407. package/prisma/migrations/20261013000002_deferred_review_request/migration.sql +4 -0
  408. package/prisma/migrations/20261014000000_repo_skills/migration.sql +15 -0
  409. package/prisma/migrations/20261015000001_coding_run_merge_order_index/migration.sql +2 -0
  410. package/prisma/migrations/20261015000002_no_change_rereview/migration.sql +2 -0
  411. package/prisma/schema.prisma +362 -91
@@ -1,4 +1,4 @@
1
- import type { V1ConfigMap, V1Endpoints, V1NetworkPolicy, V1Pod, V1ResourceQuota, V1Secret, V1Service } from "@kubernetes/client-node";
1
+ import type { V1ConfigMap, V1Deployment, V1Endpoints, V1NetworkPolicy, V1Pod, V1ResourceQuota, V1Secret, V1Service } from "@kubernetes/client-node";
2
2
  import type { Readable, Writable } from "node:stream";
3
3
  import { type KubernetesApi, type KubernetesExecOptions } from "./kubernetes-api.js";
4
4
  export interface FakeExecCall {
@@ -10,7 +10,7 @@ export interface FakeExecCall {
10
10
  stdout?: Writable;
11
11
  timeoutMs: number;
12
12
  }
13
- type Kind = "configmap" | "secret" | "pod" | "networkpolicy" | "service" | "endpoints" | "resourcequota";
13
+ type Kind = "configmap" | "secret" | "pod" | "networkpolicy" | "service" | "endpoints" | "resourcequota" | "deployment";
14
14
  type Obj = {
15
15
  metadata?: {
16
16
  name?: string;
@@ -31,6 +31,17 @@ export declare class FakeKubernetesApi implements KubernetesApi {
31
31
  onExec: (call: FakeExecCall) => Promise<number>;
32
32
  /** The admission chain a dry-run create runs through. Default: echo the pod back unchanged. */
33
33
  onDryRunCreatePod: (namespace: string, body: V1Pod) => Promise<V1Pod>;
34
+ readonly deploymentPatches: Array<{
35
+ name: string;
36
+ replicas?: number;
37
+ annotations?: Record<string, string>;
38
+ }>;
39
+ /** Deployment names whose patch throws KubernetesForbiddenError. */
40
+ forbidden: Set<string>;
41
+ /** "deployment/<name>" or "endpoints/<name>" keys whose read throws KubernetesForbiddenError. */
42
+ forbiddenReads: Set<string>;
43
+ /** "verb/resource/name" keys canI answers false for. */
44
+ accessDenied: Set<string>;
34
45
  logs: Map<string, string>;
35
46
  /** What readApiServerVersion reports; tests that care about provenance set it. */
36
47
  apiServerVersion: string;
@@ -85,6 +96,8 @@ export declare class FakeKubernetesApi implements KubernetesApi {
85
96
  */
86
97
  dryRunCreatePod(namespace: string, body: V1Pod): Promise<V1Pod>;
87
98
  readPod(namespace: string, name: string): Promise<V1Pod | undefined>;
99
+ /** Matches `key=value[,key=value]` selectors against stored pods' labels. */
100
+ listPods(namespace: string, labelSelector: string): Promise<V1Pod[]>;
88
101
  deletePod(namespace: string, name: string, gracePeriodSeconds: number): Promise<void>;
89
102
  createNetworkPolicy(namespace: string, body: V1NetworkPolicy): Promise<V1NetworkPolicy>;
90
103
  readNetworkPolicy(namespace: string, name: string): Promise<V1NetworkPolicy | undefined>;
@@ -92,6 +105,12 @@ export declare class FakeKubernetesApi implements KubernetesApi {
92
105
  readService(namespace: string, name: string): Promise<V1Service | undefined>;
93
106
  readEndpoints(namespace: string, name: string): Promise<V1Endpoints | undefined>;
94
107
  readResourceQuota(namespace: string, name: string): Promise<V1ResourceQuota | undefined>;
108
+ readDeployment(namespace: string, name: string): Promise<V1Deployment | undefined>;
109
+ patchDeployment(namespace: string, name: string, patch: {
110
+ replicas?: number;
111
+ annotations?: Record<string, string>;
112
+ }): Promise<void>;
113
+ canI(_namespace: string, verb: string, _group: string, resource: string, name: string): Promise<boolean>;
95
114
  readApiServerVersion(): Promise<string>;
96
115
  readNamespace(name: string): Promise<boolean>;
97
116
  exec(namespace: string, pod: string, container: string, command: string[], options: KubernetesExecOptions): Promise<number>;
@@ -1,4 +1,4 @@
1
- import { KubernetesAlreadyExistsError, KubernetesConflictError, KubernetesNotFoundError, } from "./kubernetes-api.js";
1
+ import { KubernetesAlreadyExistsError, KubernetesConflictError, KubernetesForbiddenError, KubernetesNotFoundError, } from "./kubernetes-api.js";
2
2
  /** In-memory KubernetesApi for unit tests. Tests drive pod status and exec behavior directly. */
3
3
  export class FakeKubernetesApi {
4
4
  objects = new Map();
@@ -13,6 +13,13 @@ export class FakeKubernetesApi {
13
13
  };
14
14
  /** The admission chain a dry-run create runs through. Default: echo the pod back unchanged. */
15
15
  onDryRunCreatePod = async (_namespace, body) => body;
16
+ deploymentPatches = [];
17
+ /** Deployment names whose patch throws KubernetesForbiddenError. */
18
+ forbidden = new Set();
19
+ /** "deployment/<name>" or "endpoints/<name>" keys whose read throws KubernetesForbiddenError. */
20
+ forbiddenReads = new Set();
21
+ /** "verb/resource/name" keys canI answers false for. */
22
+ accessDenied = new Set();
16
23
  logs = new Map();
17
24
  /** What readApiServerVersion reports; tests that care about provenance set it. */
18
25
  apiServerVersion = "v1.33.0";
@@ -95,6 +102,14 @@ export class FakeKubernetesApi {
95
102
  async readPod(namespace, name) {
96
103
  return this.read("pod", namespace, name);
97
104
  }
105
+ /** Matches `key=value[,key=value]` selectors against stored pods' labels. */
106
+ async listPods(namespace, labelSelector) {
107
+ const wanted = labelSelector.split(",").map((pair) => pair.split("="));
108
+ return [...this.objects.entries()]
109
+ .filter(([key]) => key.startsWith(`pod/${namespace}/`))
110
+ .map(([, pod]) => structuredClone(pod))
111
+ .filter((pod) => wanted.every(([k, v]) => pod.metadata?.labels?.[k] === v));
112
+ }
98
113
  async deletePod(namespace, name, gracePeriodSeconds) {
99
114
  this.deletedPods.push({ name, gracePeriodSeconds });
100
115
  this.delete("pod", namespace, name);
@@ -112,11 +127,37 @@ export class FakeKubernetesApi {
112
127
  return this.read("service", namespace, name);
113
128
  }
114
129
  async readEndpoints(namespace, name) {
130
+ if (this.forbiddenReads.has(`endpoints/${name}`))
131
+ throw new KubernetesForbiddenError(this.key("endpoints", namespace, name));
115
132
  return this.read("endpoints", namespace, name);
116
133
  }
117
134
  async readResourceQuota(namespace, name) {
118
135
  return this.read("resourcequota", namespace, name);
119
136
  }
137
+ async readDeployment(namespace, name) {
138
+ if (this.forbiddenReads.has(`deployment/${name}`))
139
+ throw new KubernetesForbiddenError(this.key("deployment", namespace, name));
140
+ return this.read("deployment", namespace, name);
141
+ }
142
+ async patchDeployment(namespace, name, patch) {
143
+ if (this.forbidden.has(name))
144
+ throw new KubernetesForbiddenError(this.key("deployment", namespace, name));
145
+ const key = this.key("deployment", namespace, name);
146
+ const current = this.objects.get(key);
147
+ if (!current)
148
+ throw new KubernetesNotFoundError(key);
149
+ const metadata = { ...current.metadata, resourceVersion: String(++this.version) };
150
+ if (patch.annotations)
151
+ metadata.annotations = { ...current.metadata?.annotations, ...patch.annotations };
152
+ const next = { ...current, metadata };
153
+ if (patch.replicas !== undefined)
154
+ next.spec = { selector: {}, template: {}, ...current.spec, replicas: patch.replicas };
155
+ this.objects.set(key, next);
156
+ this.deploymentPatches.push({ name, replicas: patch.replicas, annotations: patch.annotations });
157
+ }
158
+ async canI(_namespace, verb, _group, resource, name) {
159
+ return !this.accessDenied.has(`${verb}/${resource}/${name}`);
160
+ }
120
161
  async readApiServerVersion() {
121
162
  return this.apiServerVersion;
122
163
  }
@@ -3,11 +3,15 @@
3
3
  * so every launcher behavior is unit-tested against FakeKubernetesApi and
4
4
  * re-proven against a real cluster by kubernetes.integration.test.ts.
5
5
  */
6
- import type { V1ConfigMap, V1Endpoints, V1NetworkPolicy, V1Pod, V1ResourceQuota, V1Secret, V1Service } from "@kubernetes/client-node";
6
+ import type { V1ConfigMap, V1Deployment, V1Endpoints, V1NetworkPolicy, V1Pod, V1ResourceQuota, V1Secret, V1Service } from "@kubernetes/client-node";
7
7
  import type { Readable, Writable } from "node:stream";
8
8
  export declare class KubernetesNotFoundError extends Error {
9
9
  constructor(message?: string);
10
10
  }
11
+ /** The caller's identity lacks RBAC permission for the request (HTTP 403). */
12
+ export declare class KubernetesForbiddenError extends Error {
13
+ constructor(message?: string, options?: ErrorOptions);
14
+ }
11
15
  /** Stale resourceVersion on replace. */
12
16
  export declare class KubernetesConflictError extends Error {
13
17
  constructor(message?: string);
@@ -36,6 +40,8 @@ export interface KubernetesApi {
36
40
  */
37
41
  dryRunCreatePod(namespace: string, body: V1Pod): Promise<V1Pod>;
38
42
  readPod(namespace: string, name: string): Promise<V1Pod | undefined>;
43
+ /** Lists pods in one namespace matching a label selector (e.g. the native sandbox janitor's). */
44
+ listPods(namespace: string, labelSelector: string): Promise<V1Pod[]>;
39
45
  deletePod(namespace: string, name: string, gracePeriodSeconds: number): Promise<void>;
40
46
  createNetworkPolicy(namespace: string, body: V1NetworkPolicy): Promise<V1NetworkPolicy>;
41
47
  readNetworkPolicy(namespace: string, name: string): Promise<V1NetworkPolicy | undefined>;
@@ -44,11 +50,21 @@ export interface KubernetesApi {
44
50
  /**
45
51
  * Reads a Service's Endpoints by name. EndpointSlice is the durable successor to this core/v1
46
52
  * resource (migrating to it is a follow-up); Endpoints is read by name so RBAC stays one object wide.
53
+ * Undefined when missing; throws KubernetesForbiddenError on 403.
47
54
  */
48
55
  readEndpoints(namespace: string, name: string): Promise<V1Endpoints | undefined>;
49
56
  /** Reads one ResourceQuota by name (its status carries hard and used), for the launcher's capacity check. */
50
57
  readResourceQuota(namespace: string, name: string): Promise<V1ResourceQuota | undefined>;
51
58
  readNamespace(name: string): Promise<boolean>;
59
+ /** Undefined when missing; throws KubernetesForbiddenError on 403. */
60
+ readDeployment(namespace: string, name: string): Promise<V1Deployment | undefined>;
61
+ /** JSON merge patch of metadata.annotations and spec.replicas only. Throws KubernetesForbiddenError on 403. */
62
+ patchDeployment(namespace: string, name: string, patch: {
63
+ replicas?: number;
64
+ annotations?: Record<string, string>;
65
+ }): Promise<void>;
66
+ /** SelfSubjectAccessReview: may the current identity perform `verb` on this one named resource? */
67
+ canI(namespace: string, verb: string, group: string, resource: string, name: string): Promise<boolean>;
52
68
  /** Runs a command in a container; resolves with its exit code. Never uses a shell. */
53
69
  exec(namespace: string, pod: string, container: string, command: string[], options: KubernetesExecOptions): Promise<number>;
54
70
  /**
@@ -4,6 +4,13 @@ export class KubernetesNotFoundError extends Error {
4
4
  this.name = "KubernetesNotFoundError";
5
5
  }
6
6
  }
7
+ /** The caller's identity lacks RBAC permission for the request (HTTP 403). */
8
+ export class KubernetesForbiddenError extends Error {
9
+ constructor(message, options) {
10
+ super(message, options);
11
+ this.name = "KubernetesForbiddenError";
12
+ }
13
+ }
7
14
  /** Stale resourceVersion on replace. */
8
15
  export class KubernetesConflictError extends Error {
9
16
  constructor(message) {
@@ -3,7 +3,7 @@
3
3
  * method is one client call plus HTTP-status translation into the seam's
4
4
  * error classes. Proven against a real cluster by kubernetes.integration.test.ts.
5
5
  */
6
- import { type V1ConfigMap, type V1Endpoints, type V1ResourceQuota, type V1NetworkPolicy, type V1Pod, type V1Secret, type V1Service } from "@kubernetes/client-node";
6
+ import { type V1ConfigMap, type V1Deployment, type V1Endpoints, type V1ResourceQuota, type V1NetworkPolicy, type V1Pod, type V1Secret, type V1Service } from "@kubernetes/client-node";
7
7
  import { type KubernetesApi, type KubernetesExecOptions } from "./kubernetes-api.js";
8
8
  export interface ClientNodeKubernetesApiOptions {
9
9
  /** kubeconfig context to use; defaults to the current context (or in-cluster credentials). */
@@ -13,6 +13,8 @@ export declare class ClientNodeKubernetesApi implements KubernetesApi {
13
13
  private readonly config;
14
14
  private readonly core;
15
15
  private readonly networking;
16
+ private readonly apps;
17
+ private readonly authz;
16
18
  private readonly version;
17
19
  constructor(options?: ClientNodeKubernetesApiOptions);
18
20
  createConfigMap(namespace: string, body: V1ConfigMap): Promise<V1ConfigMap>;
@@ -23,6 +25,7 @@ export declare class ClientNodeKubernetesApi implements KubernetesApi {
23
25
  createPod(namespace: string, body: V1Pod): Promise<V1Pod>;
24
26
  dryRunCreatePod(namespace: string, body: V1Pod): Promise<V1Pod>;
25
27
  readPod(namespace: string, name: string): Promise<V1Pod | undefined>;
28
+ listPods(namespace: string, labelSelector: string): Promise<V1Pod[]>;
26
29
  deletePod(namespace: string, name: string, gracePeriodSeconds: number): Promise<void>;
27
30
  createNetworkPolicy(namespace: string, body: V1NetworkPolicy): Promise<V1NetworkPolicy>;
28
31
  readNetworkPolicy(namespace: string, name: string): Promise<V1NetworkPolicy | undefined>;
@@ -32,6 +35,12 @@ export declare class ClientNodeKubernetesApi implements KubernetesApi {
32
35
  readResourceQuota(namespace: string, name: string): Promise<V1ResourceQuota | undefined>;
33
36
  readApiServerVersion(): Promise<string>;
34
37
  readNamespace(name: string): Promise<boolean>;
38
+ readDeployment(namespace: string, name: string): Promise<V1Deployment | undefined>;
39
+ patchDeployment(namespace: string, name: string, patch: {
40
+ replicas?: number;
41
+ annotations?: Record<string, string>;
42
+ }): Promise<void>;
43
+ canI(namespace: string, verb: string, group: string, resource: string, name: string): Promise<boolean>;
35
44
  exec(namespace: string, pod: string, container: string, command: string[], options: KubernetesExecOptions): Promise<number>;
36
45
  readLogTail(namespace: string, pod: string, container: string, tailLines: number, limitBytes: number): Promise<string>;
37
46
  }
@@ -3,9 +3,9 @@
3
3
  * method is one client call plus HTTP-status translation into the seam's
4
4
  * error classes. Proven against a real cluster by kubernetes.integration.test.ts.
5
5
  */
6
- import { ApiException, CoreV1Api, Exec, KubeConfig, NetworkingV1Api, VersionApi, } from "@kubernetes/client-node";
6
+ import { ApiException, AppsV1Api, AuthorizationV1Api, CoreV1Api, Exec, KubeConfig, NetworkingV1Api, PatchStrategy, setHeaderOptions, VersionApi, } from "@kubernetes/client-node";
7
7
  import { Writable } from "node:stream";
8
- import { KubernetesAlreadyExistsError, KubernetesConflictError, KubernetesNotFoundError, } from "./kubernetes-api.js";
8
+ import { KubernetesAlreadyExistsError, KubernetesConflictError, KubernetesForbiddenError, KubernetesNotFoundError, } from "./kubernetes-api.js";
9
9
  function statusCode(error) {
10
10
  return error instanceof ApiException ? error.code : undefined;
11
11
  }
@@ -20,6 +20,19 @@ async function readOrUndefined(read) {
20
20
  throw error;
21
21
  }
22
22
  }
23
+ /** Undefined for a 404; a 403 becomes KubernetesForbiddenError (cause kept); rethrows anything else. */
24
+ async function readOrForbidden(what, read) {
25
+ try {
26
+ return await read();
27
+ }
28
+ catch (error) {
29
+ if (statusCode(error) === 404)
30
+ return undefined;
31
+ if (statusCode(error) === 403)
32
+ throw new KubernetesForbiddenError(`forbidden: get ${what}`, { cause: error });
33
+ throw error;
34
+ }
35
+ }
23
36
  /** Swallows a 404 so deletes are idempotent. */
24
37
  async function deleteIgnoringMissing(remove) {
25
38
  try {
@@ -69,6 +82,8 @@ export class ClientNodeKubernetesApi {
69
82
  config;
70
83
  core;
71
84
  networking;
85
+ apps;
86
+ authz;
72
87
  version;
73
88
  constructor(options = {}) {
74
89
  this.config = new KubeConfig();
@@ -77,6 +92,8 @@ export class ClientNodeKubernetesApi {
77
92
  this.config.setCurrentContext(options.context);
78
93
  this.core = this.config.makeApiClient(CoreV1Api);
79
94
  this.networking = this.config.makeApiClient(NetworkingV1Api);
95
+ this.apps = this.config.makeApiClient(AppsV1Api);
96
+ this.authz = this.config.makeApiClient(AuthorizationV1Api);
80
97
  this.version = this.config.makeApiClient(VersionApi);
81
98
  }
82
99
  createConfigMap(namespace, body) {
@@ -103,6 +120,9 @@ export class ClientNodeKubernetesApi {
103
120
  readPod(namespace, name) {
104
121
  return readOrUndefined(() => this.core.readNamespacedPod({ namespace, name }));
105
122
  }
123
+ async listPods(namespace, labelSelector) {
124
+ return (await this.core.listNamespacedPod({ namespace, labelSelector })).items;
125
+ }
106
126
  deletePod(namespace, name, gracePeriodSeconds) {
107
127
  return deleteIgnoringMissing(() => this.core.deleteNamespacedPod({ namespace, name, gracePeriodSeconds }));
108
128
  }
@@ -119,7 +139,7 @@ export class ClientNodeKubernetesApi {
119
139
  return readOrUndefined(() => this.core.readNamespacedService({ namespace, name }));
120
140
  }
121
141
  readEndpoints(namespace, name) {
122
- return readOrUndefined(() => this.core.readNamespacedEndpoints({ namespace, name }));
142
+ return readOrForbidden(`endpoints/${namespace}/${name}`, () => this.core.readNamespacedEndpoints({ namespace, name }));
123
143
  }
124
144
  readResourceQuota(namespace, name) {
125
145
  return readOrUndefined(() => this.core.readNamespacedResourceQuota({ namespace, name }));
@@ -130,6 +150,35 @@ export class ClientNodeKubernetesApi {
130
150
  async readNamespace(name) {
131
151
  return (await readOrUndefined(() => this.core.readNamespace({ name }))) !== undefined;
132
152
  }
153
+ readDeployment(namespace, name) {
154
+ return readOrForbidden(`deployments/${namespace}/${name}`, () => this.apps.readNamespacedDeployment({ namespace, name }));
155
+ }
156
+ async patchDeployment(namespace, name, patch) {
157
+ const body = {
158
+ ...(patch.annotations ? { metadata: { annotations: patch.annotations } } : {}),
159
+ ...(patch.replicas !== undefined ? { spec: { replicas: patch.replicas } } : {}),
160
+ };
161
+ try {
162
+ await this.apps.patchNamespacedDeployment({ namespace, name, body }, setHeaderOptions("Content-Type", PatchStrategy.MergePatch));
163
+ }
164
+ catch (error) {
165
+ if (statusCode(error) === 403)
166
+ throw new KubernetesForbiddenError(`deployments/${namespace}/${name}`, { cause: error });
167
+ if (statusCode(error) === 404)
168
+ throw new KubernetesNotFoundError(`deployments/${namespace}/${name}`);
169
+ throw error;
170
+ }
171
+ }
172
+ async canI(namespace, verb, group, resource, name) {
173
+ const review = await this.authz.createSelfSubjectAccessReview({
174
+ body: {
175
+ apiVersion: "authorization.k8s.io/v1",
176
+ kind: "SelfSubjectAccessReview",
177
+ spec: { resourceAttributes: { namespace, verb, group, resource, name } },
178
+ },
179
+ });
180
+ return review.status?.allowed === true;
181
+ }
133
182
  exec(namespace, pod, container, command, options) {
134
183
  // stderr is requested but discarded so it never reaches wardby's logs.
135
184
  const stderr = new Writable({
@@ -14,7 +14,11 @@ export interface DryRunFixture {
14
14
  notes: string[];
15
15
  mutations: PodMutation[];
16
16
  }
17
- export declare function loadDryRunFixture(): DryRunFixture;
17
+ declare const FIXTURE_URLS: {
18
+ readonly "gke-autopilot": import("url").URL;
19
+ readonly eks: import("url").URL;
20
+ };
21
+ export declare function loadDryRunFixture(platform?: keyof typeof FIXTURE_URLS): DryRunFixture;
18
22
  /**
19
23
  * Returns a copy of `document` with every mutation applied in order.
20
24
  *
@@ -47,3 +51,4 @@ export declare function applyMutations<T>(document: T, mutations: readonly PodMu
47
51
  * `/spec/Foo` and `/spec/foo` by tie-break rules that differ between hosts).
48
52
  */
49
53
  export declare function diffMutations(before: unknown, after: unknown, prefix?: string): PodMutation[];
54
+ export {};
@@ -33,9 +33,12 @@
33
33
  */
34
34
  import { readFileSync } from "node:fs";
35
35
  import { canonical } from "./kubernetes-isolation.js";
36
- const FIXTURE_URL = new URL("./fixtures/gke-autopilot-dry-run.json", import.meta.url);
37
- export function loadDryRunFixture() {
38
- return JSON.parse(readFileSync(FIXTURE_URL, "utf8"));
36
+ const FIXTURE_URLS = {
37
+ "gke-autopilot": new URL("./fixtures/gke-autopilot-dry-run.json", import.meta.url),
38
+ eks: new URL("./fixtures/eks-dry-run.json", import.meta.url),
39
+ };
40
+ export function loadDryRunFixture(platform = "gke-autopilot") {
41
+ return JSON.parse(readFileSync(FIXTURE_URLS[platform], "utf8"));
39
42
  }
40
43
  function segments(path) {
41
44
  if (path === "" || !path.startsWith("/"))
@@ -1,4 +1,4 @@
1
- import type { V1NetworkPolicy, V1Pod, V1Secret } from "@kubernetes/client-node";
1
+ import type { V1Container, V1NetworkPolicy, V1Pod, V1Secret } from "@kubernetes/client-node";
2
2
  import { type ResolvedCodingService } from "../../coding/services/catalog.js";
3
3
  import type { JobSpec } from "./types.js";
4
4
  import { type KubernetesPlatform } from "./kubernetes-platform.js";
@@ -81,6 +81,8 @@ export interface RunPodOptions {
81
81
  export declare function buildRunPod(spec: JobSpec, options: RunPodOptions): V1Pod;
82
82
  export declare function buildRunNetworkPolicy(spec: JobSpec, namespace: string): V1NetworkPolicy;
83
83
  export declare function buildCapabilitySecret(spec: JobSpec, namespace: string, capability: string, toolSetup?: string): V1Secret;
84
+ /** A copy of a container's resources with every quantity in one rendering ("1" and "1000m" alike); see normalizeResources. */
85
+ export declare function canonicalResources(r?: V1Container["resources"]): V1Container["resources"];
84
86
  /**
85
87
  * A copy of `pod` with ONLY the API server's own defaulting and aliasing undone
86
88
  * — the same `normalizeSpec` pass `assertRunPodMatches` runs, and nothing else.
@@ -167,3 +169,16 @@ export declare function assertRunNetworkPolicyMatches(actual: V1NetworkPolicy, e
167
169
  * The IP is validated and embedded as a JSON string literal.
168
170
  */
169
171
  export declare function enforcementProbeScript(proxyIp: string): string;
172
+ /**
173
+ * A whole enforcement streak in one process: up to `streak` probes, each exactly
174
+ * `enforcementProbeScript`'s measurement and classification (the body is shared), `intervalMs`
175
+ * apart. Exits ENFORCEMENT_PROBE_PROVEN only after `streak` consecutive proven probes; at the
176
+ * first probe that is not proven it exits at once with that probe's own code, so the caller's
177
+ * verdict is still the last probe's. Running the streak in one exec (rather than one exec per
178
+ * probe) pays the keeper's exec + interpreter start-up once per streak instead of once per probe;
179
+ * the evidence required is unchanged.
180
+ *
181
+ * The IP is validated and embedded as a JSON string literal; `streak` and `intervalMs` must be
182
+ * positive safe integers and are embedded as numeric literals.
183
+ */
184
+ export declare function enforcementStreakScript(proxyIp: string, streak: number, intervalMs: number): string;
@@ -561,6 +561,12 @@ function normalizeResources(r) {
561
561
  bag["ephemeral-storage"] = memoryBytes(bag["ephemeral-storage"]);
562
562
  }
563
563
  }
564
+ /** A copy of a container's resources with every quantity in one rendering ("1" and "1000m" alike); see normalizeResources. */
565
+ export function canonicalResources(r) {
566
+ const copy = r ? JSON.parse(JSON.stringify(r)) : undefined;
567
+ normalizeResources(copy);
568
+ return copy;
569
+ }
564
570
  function normalizeProbe(p) {
565
571
  if (!p)
566
572
  return;
@@ -770,9 +776,24 @@ export function assertRunNetworkPolicyMatches(actual, expected) {
770
776
  * The IP is validated and embedded as a JSON string literal.
771
777
  */
772
778
  export function enforcementProbeScript(proxyIp) {
779
+ return [...enforcementProbeBody(proxyIp), "(async () => {", " process.exit(await probe());", "})();"].join("\n");
780
+ }
781
+ /**
782
+ * The one measurement both enforcement scripts make, as script lines defining `probe()`: connect to
783
+ * CODING_PROXY_PORT, then to CODING_PROXY_DENY_PORT (both with a 3 s connect timeout, always both,
784
+ * in that order), and resolve to the exit code classifying that pair. Its first statement presets
785
+ * `process.exitCode = 1` so that a natural exit is never mistaken for PROVEN. Shared verbatim so the
786
+ * single probe and the streak can never disagree about what "proven" means.
787
+ */
788
+ function enforcementProbeBody(proxyIp) {
773
789
  if (isIP(proxyIp) === 0)
774
790
  throw isolationError();
775
791
  return [
792
+ // Preset a code no probe produces (it reads as "did not run"), so the only way to exit 0 is
793
+ // the explicit proven exit. Without it, a process that drains its event loop without ever
794
+ // calling process.exit — a probe promise that never settles, or a rejection under a
795
+ // non-throwing --unhandled-rejections mode — would exit 0 and read as PROVEN.
796
+ "process.exitCode = 1;",
776
797
  'const net = require("node:net");',
777
798
  "const tcp = (port) =>",
778
799
  " new Promise((done) => {",
@@ -781,13 +802,45 @@ export function enforcementProbeScript(proxyIp) {
781
802
  ' socket.once("timeout", () => { socket.destroy(); done("timeout"); });',
782
803
  ' socket.once("error", () => done("error"));',
783
804
  " });",
784
- "(async () => {",
805
+ "const probe = async () => {",
785
806
  ` const allowed = await tcp(${CODING_PROXY_PORT});`,
786
807
  ` const denied = await tcp(${CODING_PROXY_DENY_PORT});`,
787
- ` if (allowed !== "connect") process.exit(${ENFORCEMENT_PROBE_PROXY_UNREACHABLE});`,
788
- ` if (denied === "connect") process.exit(${ENFORCEMENT_PROBE_DENY_REACHABLE});`,
789
- ` if (denied === "error") process.exit(${ENFORCEMENT_PROBE_DENY_REFUSED});`,
790
- ` process.exit(${ENFORCEMENT_PROBE_PROVEN});`,
808
+ ` if (allowed !== "connect") return ${ENFORCEMENT_PROBE_PROXY_UNREACHABLE};`,
809
+ ` if (denied === "connect") return ${ENFORCEMENT_PROBE_DENY_REACHABLE};`,
810
+ ` if (denied === "error") return ${ENFORCEMENT_PROBE_DENY_REFUSED};`,
811
+ ` return ${ENFORCEMENT_PROBE_PROVEN};`,
812
+ "};",
813
+ ];
814
+ }
815
+ /**
816
+ * A whole enforcement streak in one process: up to `streak` probes, each exactly
817
+ * `enforcementProbeScript`'s measurement and classification (the body is shared), `intervalMs`
818
+ * apart. Exits ENFORCEMENT_PROBE_PROVEN only after `streak` consecutive proven probes; at the
819
+ * first probe that is not proven it exits at once with that probe's own code, so the caller's
820
+ * verdict is still the last probe's. Running the streak in one exec (rather than one exec per
821
+ * probe) pays the keeper's exec + interpreter start-up once per streak instead of once per probe;
822
+ * the evidence required is unchanged.
823
+ *
824
+ * The IP is validated and embedded as a JSON string literal; `streak` and `intervalMs` must be
825
+ * positive safe integers and are embedded as numeric literals.
826
+ */
827
+ export function enforcementStreakScript(proxyIp, streak, intervalMs) {
828
+ const body = enforcementProbeBody(proxyIp);
829
+ if (!isPositiveSafeInteger(streak) || !isPositiveSafeInteger(intervalMs))
830
+ throw isolationError();
831
+ return [
832
+ ...body,
833
+ "const pause = (ms) => new Promise((done) => setTimeout(done, ms));",
834
+ "(async () => {",
835
+ ` for (let index = 0; index < ${JSON.stringify(streak)}; index += 1) {`,
836
+ ` if (index > 0) await pause(${JSON.stringify(intervalMs)});`,
837
+ " const code = await probe();",
838
+ ` if (code !== ${ENFORCEMENT_PROBE_PROVEN}) return process.exit(code);`,
839
+ " }",
840
+ ` return process.exit(${ENFORCEMENT_PROBE_PROVEN});`,
791
841
  "})();",
792
842
  ].join("\n");
793
843
  }
844
+ function isPositiveSafeInteger(value) {
845
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
846
+ }
@@ -29,7 +29,7 @@
29
29
  * regresses to a documentation-only fixture again.
30
30
  */
31
31
  import type { V1PodSpec, V1Toleration } from "@kubernetes/client-node";
32
- export type KubernetesPlatform = "generic" | "gke-autopilot";
32
+ export type KubernetesPlatform = "generic" | "gke-autopilot" | "eks";
33
33
  export declare const KUBERNETES_PLATFORMS: readonly KubernetesPlatform[];
34
34
  export declare const KUBERNETES_PLATFORM_ERROR = "kubernetes_platform_unconformable";
35
35
  /** A request the platform cannot legally run, refused here instead of being silently adjusted by the platform. */
@@ -84,6 +84,9 @@ export interface KubernetesPlatformProfile {
84
84
  /** gVisor is mandatory: an unset or different runtime class is refused at startup, not warned about. */
85
85
  requiresGvisor: boolean;
86
86
  }
87
+ /** Node label and taint key of the EKS sandbox node group; deploy/aws and overlays/eks use the same strings. */
88
+ export declare const EKS_SANDBOX_NODE_LABEL = "wardby.dev/sandbox";
89
+ export declare const EKS_SANDBOX_TAINT_KEY = "sandbox";
87
90
  export declare function platformProfile(name: KubernetesPlatform): KubernetesPlatformProfile;
88
91
  export interface ContainerResourceRequest {
89
92
  cpuMillicores: number;
@@ -1,4 +1,8 @@
1
- export const KUBERNETES_PLATFORMS = Object.freeze(["generic", "gke-autopilot"]);
1
+ export const KUBERNETES_PLATFORMS = Object.freeze([
2
+ "generic",
3
+ "gke-autopilot",
4
+ "eks",
5
+ ]);
2
6
  export const KUBERNETES_PLATFORM_ERROR = "kubernetes_platform_unconformable";
3
7
  /** A request the platform cannot legally run, refused here instead of being silently adjusted by the platform. */
4
8
  export class KubernetesPlatformError extends Error {
@@ -109,9 +113,42 @@ const GKE_AUTOPILOT = {
109
113
  },
110
114
  requiresGvisor: true,
111
115
  };
116
+ /** Node label and taint key of the EKS sandbox node group; deploy/aws and overlays/eks use the same strings. */
117
+ export const EKS_SANDBOX_NODE_LABEL = "wardby.dev/sandbox";
118
+ export const EKS_SANDBOX_TAINT_KEY = "sandbox";
119
+ /**
120
+ * Amazon EKS with a dedicated gVisor node group (deploy/aws). EKS has no managed sandbox runtime, so
121
+ * the node group's launch template installs runsc and the overlay's `gvisor` RuntimeClass schedules
122
+ * onto it: the RuntimeClass admission plugin merges its `scheduling` block into the pod, which is
123
+ * the toleration and nodeSelector below — wardby never sets either itself (see SYMMETRY CAVEAT).
124
+ *
125
+ * PROVISIONAL: written from the RuntimeClass spec, not yet a server-side dry-run capture. EKS has no
126
+ * Autopilot-style resource admission, so resources are the generic rules plus explicit ephemeral
127
+ * storage (kubelet eviction accounting on a shared sandbox node). Replace the fixture with a real
128
+ * capture before relying on the allowance list (fixtures/eks-dry-run.json).
129
+ */
130
+ const EKS = {
131
+ name: "eks",
132
+ resources: {
133
+ cpuFloorMillicores: 0,
134
+ cpuIncrementMillicores: 1,
135
+ memoryPerCpuMinMib: 0,
136
+ memoryPerCpuMaxMib: Number.POSITIVE_INFINITY,
137
+ explicitEphemeralStorage: true,
138
+ },
139
+ metadata: {
140
+ podAnnotationKeyPrefixes: [],
141
+ podLabelKeyPrefixes: [],
142
+ tolerations: [{ key: EKS_SANDBOX_TAINT_KEY, operator: "Equal", value: GVISOR_RUNTIME_CLASS, effect: "NoSchedule" }],
143
+ nodeSelector: { [EKS_SANDBOX_NODE_LABEL]: GVISOR_RUNTIME_CLASS },
144
+ dropsPodSeccompProfile: false,
145
+ },
146
+ requiresGvisor: true,
147
+ };
112
148
  const PROFILES = Object.freeze({
113
149
  generic: freezeProfile(GENERIC),
114
150
  "gke-autopilot": freezeProfile(GKE_AUTOPILOT),
151
+ eks: freezeProfile(EKS),
115
152
  });
116
153
  export function platformProfile(name) {
117
154
  const profile = PROFILES[name];
@@ -40,7 +40,8 @@ export interface CanaryResult {
40
40
  * probe (`enforcementProbeScript`), which had to make the same distinction after a live cluster
41
41
  * with no NetworkPolicy at all passed a boolean version of this check; preflight and gate must
42
42
  * agree on what counts as evidence, or one of them is vacuous. `internet`, `metadata` and `proxy`
43
- * stay plain reachability, where "connected" is the only thing that matters.
43
+ * stay plain reachability, where "connected" is the only thing that matters; `metadata` is true if
44
+ * **any** cloud metadata endpoint connects.
44
45
  *
45
46
  * CNIs program a new pod's policy a few seconds after it starts, so the script first waits (up to
46
47
  * 20 s) for the deny-port connect to be *dropped*; if it never is, `proxyDeny` reports true.
@@ -11,6 +11,7 @@ import { randomBytes } from "node:crypto";
11
11
  import { CODING_PROXY_DENY_PORT, CODING_PROXY_PORT } from "./docker-isolation.js";
12
12
  import { KUBERNETES_ISOLATION_ERROR, WORKER_CONTAINER, buildRunNetworkPolicy, buildRunPod, isRegistryDigest, } from "./kubernetes-isolation.js";
13
13
  import { KubernetesPlatformError, assertPlatformConfig, platformProfile } from "./kubernetes-platform.js";
14
+ import { CLOUD_METADATA_PROBES } from "./metadata-probes.js";
14
15
  import { readProxyWitness } from "./kubernetes-witness.js";
15
16
  /**
16
17
  * The image the preflight canary runs (it only needs node): the Codex worker when configured,
@@ -40,7 +41,8 @@ const CANARY_KEYS = Object.keys(EXPECTED).sort();
40
41
  * probe (`enforcementProbeScript`), which had to make the same distinction after a live cluster
41
42
  * with no NetworkPolicy at all passed a boolean version of this check; preflight and gate must
42
43
  * agree on what counts as evidence, or one of them is vacuous. `internet`, `metadata` and `proxy`
43
- * stay plain reachability, where "connected" is the only thing that matters.
44
+ * stay plain reachability, where "connected" is the only thing that matters; `metadata` is true if
45
+ * **any** cloud metadata endpoint connects.
44
46
  *
45
47
  * CNIs program a new pod's policy a few seconds after it starts, so the script first waits (up to
46
48
  * 20 s) for the deny-port connect to be *dropped*; if it never is, `proxyDeny` reports true.
@@ -81,7 +83,7 @@ export const CANARY_SCRIPT = [
81
83
  " ),",
82
84
  ` proxyDeny: !(await dropped(process.env.WARDBY_CANARY_PROXY_IP, ${CODING_PROXY_DENY_PORT})),`,
83
85
  ' internet: await reachable("1.1.1.1", 443),',
84
- ' metadata: await reachable("169.254.169.254", 80),',
86
+ ` metadata: (await Promise.all(${JSON.stringify(CLOUD_METADATA_PROBES.map((p) => [p.host, p.port]))}.map(([host, port]) => reachable(host, port)))).some(Boolean),`,
85
87
  ` proxy: await reachable(process.env.WARDBY_CANARY_PROXY_IP, ${CODING_PROXY_PORT}),`,
86
88
  " };",
87
89
  " console.log(JSON.stringify({ wardbyCanary }));",