@wardby/cli 0.2.1 → 0.3.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 (407) hide show
  1. package/.env.example +34 -4
  2. package/README.md +41 -4
  3. package/dist/claude-coding-worker/driver.d.ts +6 -1
  4. package/dist/claude-coding-worker/driver.js +27 -1
  5. package/dist/claude-coding-worker/main.js +2 -0
  6. package/dist/claude-coding-worker/tool-socket.d.ts +9 -0
  7. package/dist/claude-coding-worker/tool-socket.js +26 -0
  8. package/dist/cli-help.d.ts +1 -1
  9. package/dist/cli-help.js +13 -4
  10. package/dist/cli.d.ts +1 -1
  11. package/dist/cli.js +219 -29
  12. package/dist/coding/collect-exclude.d.ts +25 -0
  13. package/dist/coding/collect-exclude.js +76 -0
  14. package/dist/coding/profile.d.ts +67 -22
  15. package/dist/coding/profile.js +58 -25
  16. package/dist/coding/protected-path-wording.d.ts +24 -0
  17. package/dist/coding/protected-path-wording.js +55 -0
  18. package/dist/coding/protected-paths.d.ts +31 -0
  19. package/dist/coding/protected-paths.js +48 -0
  20. package/dist/coding/protocol.d.ts +75 -4
  21. package/dist/coding/protocol.js +107 -7
  22. package/dist/coding/registry/adapters.d.ts +2 -0
  23. package/dist/coding/registry/adapters.js +9 -0
  24. package/dist/coding/registry/allowlist.d.ts +9 -0
  25. package/dist/coding/registry/allowlist.js +28 -0
  26. package/dist/coding/registry/json-scan.d.ts +55 -0
  27. package/dist/coding/registry/json-scan.js +191 -0
  28. package/dist/coding/registry/lockfiles.d.ts +6 -0
  29. package/dist/coding/registry/lockfiles.js +89 -0
  30. package/dist/coding/registry/npm-lockfile.d.ts +9 -0
  31. package/dist/coding/registry/npm-lockfile.js +73 -0
  32. package/dist/coding/registry/npm-plan.d.ts +29 -0
  33. package/dist/coding/registry/npm-plan.js +289 -0
  34. package/dist/coding/registry/npm.d.ts +7 -0
  35. package/dist/coding/registry/npm.js +252 -0
  36. package/dist/coding/registry/pypi.d.ts +4 -0
  37. package/dist/coding/registry/pypi.js +210 -0
  38. package/dist/coding/registry/report.d.ts +37 -0
  39. package/dist/coding/registry/report.js +31 -0
  40. package/dist/coding/registry/token.d.ts +4 -0
  41. package/dist/coding/registry/token.js +7 -0
  42. package/dist/coding/registry/types.d.ts +286 -0
  43. package/dist/coding/registry/types.js +19 -0
  44. package/dist/coding/registry/worker-config.d.ts +14 -0
  45. package/dist/coding/registry/worker-config.js +31 -0
  46. package/dist/coding/services/builtins.d.ts +22 -0
  47. package/dist/coding/services/builtins.js +85 -0
  48. package/dist/coding/services/catalog.d.ts +443 -0
  49. package/dist/coding/services/catalog.js +159 -0
  50. package/dist/coding/services/declaration.d.ts +11 -0
  51. package/dist/coding/services/declaration.js +114 -0
  52. package/dist/coding/services/note.d.ts +8 -0
  53. package/dist/coding/services/note.js +15 -0
  54. package/dist/coding/services/resolve.d.ts +25 -0
  55. package/dist/coding/services/resolve.js +50 -0
  56. package/dist/coding/services/wording.d.ts +25 -0
  57. package/dist/coding/services/wording.js +70 -0
  58. package/dist/coding-proxy/main.js +24 -4
  59. package/dist/coding-worker/artifact.d.ts +6 -0
  60. package/dist/coding-worker/debug-trace.d.ts +37 -0
  61. package/dist/coding-worker/debug-trace.js +117 -0
  62. package/dist/coding-worker/driver.d.ts +13 -1
  63. package/dist/coding-worker/driver.js +102 -13
  64. package/dist/coding-worker/errors.js +8 -0
  65. package/dist/coding-worker/main.js +10 -2
  66. package/dist/coding-worker/sdk.d.ts +2 -2
  67. package/dist/coding-worker/sdk.js +7 -2
  68. package/dist/coding-worker/types.d.ts +3 -0
  69. package/dist/config/providers.d.ts +41 -0
  70. package/dist/config/providers.js +66 -0
  71. package/dist/core/budget-groups.d.ts +108 -5
  72. package/dist/core/budget-groups.js +125 -19
  73. package/dist/core/budget-wording.d.ts +22 -0
  74. package/dist/core/budget-wording.js +55 -0
  75. package/dist/core/datastores.js +18 -2
  76. package/dist/core/db.d.ts +5 -1
  77. package/dist/core/db.js +8 -2
  78. package/dist/core/dispatch.d.ts +33 -6
  79. package/dist/core/dispatch.js +260 -43
  80. package/dist/core/engine-native.js +27 -10
  81. package/dist/core/grants.d.ts +86 -0
  82. package/dist/core/grants.js +126 -0
  83. package/dist/core/host-events.d.ts +49 -0
  84. package/dist/core/host-events.js +257 -0
  85. package/dist/core/host-identity-links.d.ts +54 -0
  86. package/dist/core/host-identity-links.js +189 -0
  87. package/dist/core/host-status.d.ts +61 -0
  88. package/dist/core/host-status.js +211 -0
  89. package/dist/core/in-flight-runs.d.ts +8 -0
  90. package/dist/core/in-flight-runs.js +56 -0
  91. package/dist/core/provider-wording.d.ts +12 -0
  92. package/dist/core/provider-wording.js +44 -0
  93. package/dist/core/reconciler.d.ts +38 -2
  94. package/dist/core/reconciler.js +86 -2
  95. package/dist/core/repo-access.d.ts +99 -0
  96. package/dist/core/repo-access.js +136 -0
  97. package/dist/core/review-host-checks.d.ts +11 -0
  98. package/dist/core/review-host-checks.js +41 -0
  99. package/dist/core/review-host-tools.d.ts +47 -0
  100. package/dist/core/review-host-tools.js +347 -0
  101. package/dist/core/run-heartbeat.d.ts +27 -0
  102. package/dist/core/run-heartbeat.js +54 -0
  103. package/dist/core/runner.d.ts +30 -8
  104. package/dist/core/runner.js +237 -40
  105. package/dist/core/secrets.d.ts +11 -2
  106. package/dist/core/secrets.js +25 -6
  107. package/dist/core/subagent-memory-tools.d.ts +1 -1
  108. package/dist/core/subagent-memory-tools.js +16 -3
  109. package/dist/core/tool-admin.d.ts +81 -0
  110. package/dist/core/tool-admin.js +129 -0
  111. package/dist/core/tool-names.d.ts +42 -0
  112. package/dist/core/tool-names.js +64 -0
  113. package/dist/core/untrusted-content.d.ts +32 -0
  114. package/dist/core/untrusted-content.js +72 -0
  115. package/dist/core/webhooks.d.ts +8 -1
  116. package/dist/core/webhooks.js +23 -2
  117. package/dist/generated/prisma/browser.d.ts +100 -0
  118. package/dist/generated/prisma/client.d.ts +100 -0
  119. package/dist/generated/prisma/commonInputTypes.d.ts +30 -0
  120. package/dist/generated/prisma/enums.d.ts +6 -0
  121. package/dist/generated/prisma/enums.js +6 -1
  122. package/dist/generated/prisma/internal/class.d.ts +143 -0
  123. package/dist/generated/prisma/internal/class.js +4 -4
  124. package/dist/generated/prisma/internal/prismaNamespace.d.ts +1746 -577
  125. package/dist/generated/prisma/internal/prismaNamespace.js +179 -6
  126. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +186 -0
  127. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +179 -6
  128. package/dist/generated/prisma/models/Agent.d.ts +294 -1
  129. package/dist/generated/prisma/models/AgentRepository.d.ts +1425 -0
  130. package/dist/generated/prisma/models/AgentRepository.js +1 -0
  131. package/dist/generated/prisma/models/AgentTool.d.ts +95 -1
  132. package/dist/generated/prisma/models/AuthUser.d.ts +56 -1
  133. package/dist/generated/prisma/models/CodingAgentProfile.d.ts +258 -7
  134. package/dist/generated/prisma/models/CodingProxySession.d.ts +123 -2
  135. package/dist/generated/prisma/models/CodingRun.d.ts +1276 -95
  136. package/dist/generated/prisma/models/CodingService.d.ts +1348 -0
  137. package/dist/generated/prisma/models/CodingService.js +1 -0
  138. package/dist/generated/prisma/models/HostEventDelivery.d.ts +946 -0
  139. package/dist/generated/prisma/models/HostEventDelivery.js +1 -0
  140. package/dist/generated/prisma/models/HostIdentity.d.ts +1232 -0
  141. package/dist/generated/prisma/models/HostIdentity.js +1 -0
  142. package/dist/generated/prisma/models/HostIdentityLinkRequest.d.ts +1473 -0
  143. package/dist/generated/prisma/models/HostIdentityLinkRequest.js +1 -0
  144. package/dist/generated/prisma/models/Principal.d.ts +455 -0
  145. package/dist/generated/prisma/models/RegistryAllowance.d.ts +1148 -0
  146. package/dist/generated/prisma/models/RegistryAllowance.js +1 -0
  147. package/dist/generated/prisma/models/RegistryApprovedVersion.d.ts +1219 -0
  148. package/dist/generated/prisma/models/RegistryApprovedVersion.js +1 -0
  149. package/dist/generated/prisma/models/RegistryFetch.d.ts +1428 -0
  150. package/dist/generated/prisma/models/RegistryFetch.js +1 -0
  151. package/dist/generated/prisma/models/RegistryPlanRefusal.d.ts +1294 -0
  152. package/dist/generated/prisma/models/RegistryPlanRefusal.js +1 -0
  153. package/dist/generated/prisma/models/RegistryVersionFact.d.ts +1085 -0
  154. package/dist/generated/prisma/models/RegistryVersionFact.js +1 -0
  155. package/dist/generated/prisma/models/ResourceGrant.d.ts +1437 -0
  156. package/dist/generated/prisma/models/ResourceGrant.js +1 -0
  157. package/dist/generated/prisma/models/Run.d.ts +389 -1
  158. package/dist/generated/prisma/models/RunHostCheck.d.ts +1239 -0
  159. package/dist/generated/prisma/models/RunHostCheck.js +1 -0
  160. package/dist/generated/prisma/models/RunHostStatus.d.ts +1315 -0
  161. package/dist/generated/prisma/models/RunHostStatus.js +1 -0
  162. package/dist/generated/prisma/models/Tool.d.ts +15 -3
  163. package/dist/generated/prisma/models.d.ts +13 -0
  164. package/dist/help/build.d.ts +1 -0
  165. package/dist/help/build.js +9 -0
  166. package/dist/help/catalog.d.ts +24 -0
  167. package/dist/help/catalog.js +160 -0
  168. package/dist/help/cli.d.ts +2 -0
  169. package/dist/help/cli.js +65 -0
  170. package/dist/help/runtime.d.ts +3 -0
  171. package/dist/help/runtime.js +44 -0
  172. package/dist/help/search.d.ts +9 -0
  173. package/dist/help/search.js +104 -0
  174. package/dist/help-index.json +660 -0
  175. package/dist/import/cli-args.js +3 -2
  176. package/dist/import/create.d.ts +5 -0
  177. package/dist/import/create.js +67 -13
  178. package/dist/import/index.js +19 -9
  179. package/dist/import/neutral-schema.d.ts +11 -11
  180. package/dist/mcp/auth/access.d.ts +70 -0
  181. package/dist/mcp/auth/access.js +58 -0
  182. package/dist/mcp/auth/grants-cli.d.ts +149 -0
  183. package/dist/mcp/auth/grants-cli.js +518 -0
  184. package/dist/mcp/auth/host-account-cli.d.ts +2 -0
  185. package/dist/mcp/auth/host-account-cli.js +47 -0
  186. package/dist/mcp/auth/ownership.d.ts +26 -52
  187. package/dist/mcp/auth/ownership.js +19 -14
  188. package/dist/mcp/auth/repo-authorization.d.ts +22 -0
  189. package/dist/mcp/auth/repo-authorization.js +51 -0
  190. package/dist/mcp/auth/resource-server.d.ts +31 -2
  191. package/dist/mcp/auth/resource-server.js +84 -3
  192. package/dist/mcp/auth/self-hosted/browser.js +2 -2
  193. package/dist/mcp/auth/self-hosted/cli.js +47 -13
  194. package/dist/mcp/auth/self-hosted/credentials.d.ts +20 -2
  195. package/dist/mcp/auth/self-hosted/credentials.js +62 -4
  196. package/dist/mcp/auth/self-hosted/session.d.ts +2 -1
  197. package/dist/mcp/context.d.ts +27 -1
  198. package/dist/mcp/errors.d.ts +25 -6
  199. package/dist/mcp/errors.js +98 -0
  200. package/dist/mcp/host-events/github-ingress.d.ts +36 -0
  201. package/dist/mcp/host-events/github-ingress.js +92 -0
  202. package/dist/mcp/host-events/github-user-callback.d.ts +18 -0
  203. package/dist/mcp/host-events/github-user-callback.js +50 -0
  204. package/dist/mcp/index.d.ts +8 -1
  205. package/dist/mcp/index.js +106 -16
  206. package/dist/mcp/server.js +27 -9
  207. package/dist/mcp/tools/agents.js +446 -48
  208. package/dist/mcp/tools/budget-groups.js +3 -3
  209. package/dist/mcp/tools/datastore.js +22 -15
  210. package/dist/mcp/tools/grants.d.ts +2 -0
  211. package/dist/mcp/tools/grants.js +239 -0
  212. package/dist/mcp/tools/help.d.ts +5 -0
  213. package/dist/mcp/tools/help.js +67 -0
  214. package/dist/mcp/tools/host-accounts.d.ts +2 -0
  215. package/dist/mcp/tools/host-accounts.js +114 -0
  216. package/dist/mcp/tools/memory.d.ts +7 -1
  217. package/dist/mcp/tools/memory.js +5 -5
  218. package/dist/mcp/tools/repositories.d.ts +2 -0
  219. package/dist/mcp/tools/repositories.js +181 -0
  220. package/dist/mcp/tools/runs.d.ts +6 -0
  221. package/dist/mcp/tools/runs.js +60 -6
  222. package/dist/mcp/tools/scheduling.js +3 -6
  223. package/dist/mcp/tools/secrets.js +18 -6
  224. package/dist/mcp/tools/services.d.ts +2 -0
  225. package/dist/mcp/tools/services.js +222 -0
  226. package/dist/mcp/tools/subagents.js +52 -16
  227. package/dist/mcp/tools/tools.d.ts +41 -0
  228. package/dist/mcp/tools/tools.js +201 -40
  229. package/dist/mcp/tools/trigger.js +36 -6
  230. package/dist/mcp/tools/webhooks.js +15 -4
  231. package/dist/mcp/transport/streamable-http.d.ts +10 -0
  232. package/dist/mcp/transport/streamable-http.js +32 -2
  233. package/dist/providers/auth/delegating.d.ts +19 -0
  234. package/dist/providers/auth/delegating.js +71 -0
  235. package/dist/providers/auth/index.d.ts +2 -0
  236. package/dist/providers/auth/index.js +11 -0
  237. package/dist/providers/auth/self-hosted.d.ts +2 -1
  238. package/dist/providers/auth/self-hosted.js +16 -4
  239. package/dist/providers/auth/types.d.ts +6 -0
  240. package/dist/providers/coding-proxy/memory-ledger.d.ts +3 -0
  241. package/dist/providers/coding-proxy/memory-ledger.js +21 -2
  242. package/dist/providers/coding-proxy/metering.d.ts +4 -0
  243. package/dist/providers/coding-proxy/metering.js +28 -2
  244. package/dist/providers/coding-proxy/prisma-ledger.d.ts +17 -0
  245. package/dist/providers/coding-proxy/prisma-ledger.js +50 -5
  246. package/dist/providers/coding-proxy/proxy.d.ts +13 -0
  247. package/dist/providers/coding-proxy/proxy.js +479 -18
  248. package/dist/providers/coding-proxy/registry/audit.d.ts +131 -0
  249. package/dist/providers/coding-proxy/registry/audit.js +380 -0
  250. package/dist/providers/coding-proxy/registry/bounded-fetch.d.ts +16 -0
  251. package/dist/providers/coding-proxy/registry/bounded-fetch.js +77 -0
  252. package/dist/providers/coding-proxy/registry/plan.d.ts +58 -0
  253. package/dist/providers/coding-proxy/registry/plan.js +304 -0
  254. package/dist/providers/coding-proxy/registry/prisma-store.d.ts +34 -0
  255. package/dist/providers/coding-proxy/registry/prisma-store.js +151 -0
  256. package/dist/providers/coding-proxy/registry/service.d.ts +213 -0
  257. package/dist/providers/coding-proxy/registry/service.js +1137 -0
  258. package/dist/providers/coding-proxy/registry/store.d.ts +127 -0
  259. package/dist/providers/coding-proxy/registry/store.js +70 -0
  260. package/dist/providers/coding-proxy/runtime.d.ts +17 -0
  261. package/dist/providers/coding-proxy/runtime.js +63 -0
  262. package/dist/providers/coding-proxy/secure-fetch.js +0 -1
  263. package/dist/providers/coding-proxy/server.d.ts +11 -0
  264. package/dist/providers/coding-proxy/server.js +163 -0
  265. package/dist/providers/coding-proxy/types.d.ts +15 -0
  266. package/dist/providers/engine/types.d.ts +10 -0
  267. package/dist/providers/executor/build.d.ts +2 -2
  268. package/dist/providers/executor/composition.d.ts +3 -0
  269. package/dist/providers/executor/composition.js +19 -0
  270. package/dist/providers/executor/container.d.ts +105 -3
  271. package/dist/providers/executor/container.js +296 -20
  272. package/dist/providers/executor/dbos.d.ts +2 -3
  273. package/dist/providers/executor/in-process.d.ts +2 -3
  274. package/dist/providers/executor/routing.d.ts +7 -0
  275. package/dist/providers/executor/routing.js +13 -0
  276. package/dist/providers/executor/types.d.ts +22 -0
  277. package/dist/providers/jobs/claude-tool-setup.d.ts +23 -0
  278. package/dist/providers/jobs/claude-tool-setup.js +50 -0
  279. package/dist/providers/jobs/collect-prune.d.ts +7 -0
  280. package/dist/providers/jobs/collect-prune.js +27 -0
  281. package/dist/providers/jobs/docker-isolation.d.ts +48 -3
  282. package/dist/providers/jobs/docker-isolation.js +213 -22
  283. package/dist/providers/jobs/docker-services.d.ts +35 -0
  284. package/dist/providers/jobs/docker-services.js +191 -0
  285. package/dist/providers/jobs/docker.d.ts +49 -2
  286. package/dist/providers/jobs/docker.js +279 -32
  287. package/dist/providers/jobs/fake-kubernetes-api.d.ts +1 -0
  288. package/dist/providers/jobs/fake-kubernetes-api.js +12 -3
  289. package/dist/providers/jobs/kubernetes-isolation.d.ts +17 -2
  290. package/dist/providers/jobs/kubernetes-isolation.js +238 -57
  291. package/dist/providers/jobs/kubernetes-platform.d.ts +10 -4
  292. package/dist/providers/jobs/kubernetes-platform.js +11 -5
  293. package/dist/providers/jobs/kubernetes-preflight.js +3 -0
  294. package/dist/providers/jobs/kubernetes.d.ts +21 -2
  295. package/dist/providers/jobs/kubernetes.js +112 -21
  296. package/dist/providers/jobs/types.d.ts +18 -0
  297. package/dist/providers/llm/anthropic.js +6 -2
  298. package/dist/providers/llm/bedrock.js +2 -1
  299. package/dist/providers/llm/claude-messages.d.ts +5 -1
  300. package/dist/providers/llm/claude-messages.js +1 -0
  301. package/dist/providers/llm/claude-provider.d.ts +3 -1
  302. package/dist/providers/llm/claude-provider.js +5 -1
  303. package/dist/providers/llm/index.d.ts +1 -1
  304. package/dist/providers/llm/index.js +1 -1
  305. package/dist/providers/llm/pricing-anthropic.d.ts +10 -0
  306. package/dist/providers/llm/pricing-anthropic.js +32 -14
  307. package/dist/providers/llm/pricing-bedrock-claude.d.ts +8 -0
  308. package/dist/providers/llm/pricing-bedrock-claude.js +9 -0
  309. package/dist/providers/llm/routing.d.ts +10 -1
  310. package/dist/providers/llm/routing.js +18 -0
  311. package/dist/providers/llm/types.d.ts +12 -0
  312. package/dist/providers/llm/types.js +8 -1
  313. package/dist/providers/review-host/diff-lines.d.ts +16 -0
  314. package/dist/providers/review-host/diff-lines.js +59 -0
  315. package/dist/providers/review-host/github-events.d.ts +6 -0
  316. package/dist/providers/review-host/github-events.js +180 -0
  317. package/dist/providers/review-host/github-user-auth.d.ts +38 -0
  318. package/dist/providers/review-host/github-user-auth.js +128 -0
  319. package/dist/providers/review-host/github.d.ts +57 -0
  320. package/dist/providers/review-host/github.js +567 -0
  321. package/dist/providers/review-host/index.d.ts +12 -0
  322. package/dist/providers/review-host/index.js +30 -0
  323. package/dist/providers/review-host/review-format.d.ts +19 -0
  324. package/dist/providers/review-host/review-format.js +59 -0
  325. package/dist/providers/review-host/types.d.ts +284 -0
  326. package/dist/providers/review-host/types.js +24 -0
  327. package/dist/providers/vcs/git.d.ts +16 -6
  328. package/dist/providers/vcs/git.js +77 -13
  329. package/dist/providers/vcs/github.d.ts +74 -3
  330. package/dist/providers/vcs/github.js +195 -15
  331. package/dist/providers/vcs/types.d.ts +42 -4
  332. package/dist/quickstart/index.js +13 -2
  333. package/dist/sandbox/fetch-policy.d.ts +20 -2
  334. package/dist/sandbox/fetch-policy.js +64 -3
  335. package/dist/sandbox/host-functions.d.ts +9 -1
  336. package/dist/sandbox/host-functions.js +12 -5
  337. package/dist/serve.js +1 -1
  338. package/dist/wardby-bin.js +6 -0
  339. package/docs/README.md +30 -0
  340. package/docs/architecture-runtime.md +90 -0
  341. package/docs/assets/brand/wardby-icon-512.png +0 -0
  342. package/docs/assets/brand/wardby-icon.svg +16 -0
  343. package/docs/assets/brand/wardby-mascot-profile-512.png +0 -0
  344. package/docs/assets/brand/wardby-mascot.png +0 -0
  345. package/docs/assets/brand/wardby-mascot.svg +5 -0
  346. package/docs/assets/wardby-workflow.svg +106 -0
  347. package/docs/code-review-agents.md +481 -0
  348. package/docs/coding-agent-setup.md +172 -0
  349. package/docs/coding-packages.md +455 -0
  350. package/docs/coding-services.md +300 -0
  351. package/docs/coding-worker-byo-images.md +98 -0
  352. package/docs/coding-worker-isolation.md +1061 -0
  353. package/docs/getting-started-gke.md +615 -0
  354. package/docs/getting-started-identity-provider.md +308 -0
  355. package/docs/getting-started.md +133 -0
  356. package/docs/observability.md +53 -0
  357. package/docs/release-verification.md +66 -0
  358. package/docs/security-deployment.md +657 -0
  359. package/help/code-review-agents.md +31 -0
  360. package/help/coding-packages.md +31 -0
  361. package/help/coding-services.md +71 -0
  362. package/help/creating-agents.md +68 -0
  363. package/help/deploy-gke.md +39 -0
  364. package/help/deployment-targets.md +39 -0
  365. package/help/errors/budget-group-exhausted.md +25 -0
  366. package/help/errors/docker-isolation-unsupported.md +26 -0
  367. package/help/errors/protected-path.md +45 -0
  368. package/help/errors/repo-access.md +27 -0
  369. package/help/errors/service-declaration-invalid.md +27 -0
  370. package/help/errors/service-declaration-unavailable.md +25 -0
  371. package/help/errors/service-launcher-unsupported.md +30 -0
  372. package/help/errors/service-not-allowed.md +27 -0
  373. package/help/errors/service-unknown.md +23 -0
  374. package/help/errors/service-unready.md +49 -0
  375. package/help/getting-started.md +31 -0
  376. package/help/github.md +30 -0
  377. package/help/identity-and-access.md +39 -0
  378. package/help/mcp.md +30 -0
  379. package/help/native-capabilities.md +30 -0
  380. package/help/observability.md +41 -0
  381. package/help/operating-agents.md +30 -0
  382. package/help/security.md +27 -0
  383. package/help/troubleshooting/budgets.md +26 -0
  384. package/help/troubleshooting/coding-workers.md +47 -0
  385. package/help/troubleshooting/repository-access.md +27 -0
  386. package/package.json +12 -3
  387. package/prisma/migrations/20260925010000_coding_collect_exclude/migration.sql +8 -0
  388. package/prisma/migrations/20260925015000_allowed_egress_default/migration.sql +8 -0
  389. package/prisma/migrations/20260925020000_coding_package_registry/migration.sql +55 -0
  390. package/prisma/migrations/20260925030000_tool_name_per_owner/migration.sql +11 -0
  391. package/prisma/migrations/20260926010000_run_trigger_host_event/migration.sql +7 -0
  392. package/prisma/migrations/20260926020000_code_review_hosts/migration.sql +53 -0
  393. package/prisma/migrations/20260926030000_auth_user_roles/migration.sql +9 -0
  394. package/prisma/migrations/20260926040000_agent_effort/migration.sql +6 -0
  395. package/prisma/migrations/20260926050000_registry_lockfile_plan/migration.sql +32 -0
  396. package/prisma/migrations/20260926050000_repo_access_authorization/migration.sql +82 -0
  397. package/prisma/migrations/20260926060000_registry_plan_refusal/migration.sql +19 -0
  398. package/prisma/migrations/20260926100000_registry_plan_refusal_published_at/migration.sql +5 -0
  399. package/prisma/migrations/20260926190000_run_host_status/migration.sql +18 -0
  400. package/prisma/migrations/20260926210000_run_host_status_at_dispatch/migration.sql +5 -0
  401. package/prisma/migrations/20260927010000_resource_grants/migration.sql +72 -0
  402. package/prisma/migrations/20260927020000_proxy_session_budget_exhausted/migration.sql +3 -0
  403. package/prisma/migrations/20260927030000_coding_debug_trace/migration.sql +4 -0
  404. package/prisma/migrations/20260927040000_proxy_session_upstream_failure/migration.sql +3 -0
  405. package/prisma/migrations/20260927050000_coding_run_services/migration.sql +74 -0
  406. package/prisma/migrations/20260928000000_coding_run_tool_image/migration.sql +5 -0
  407. package/prisma/schema.prisma +407 -21
@@ -0,0 +1,657 @@
1
+ # Security deployment and recovery
2
+
3
+ The secured self-hosted HTTP implementation is protected by database, HTTP,
4
+ browser, migration, and review gates. `npm install @wardby/cli` installs
5
+ audit-clean: the published package no longer installs the Prisma CLI, and the
6
+ repository's development tree is clean by reviewed overrides. Deployment
7
+ controls in this guide still apply.
8
+
9
+ ## Supported runtime and delegated operation
10
+
11
+ Use Node.js 24 or newer. Development pins Node 24 through `.nvmrc`, and the
12
+ production runtime image is built on a pinned Node 24 base image.
13
+ Stdio remains local and trusted, with `LOCAL_PRINCIPAL` as its owner identity.
14
+ It holds every scope and every role.
15
+ Delegated HTTP requires `AUTH_ISSUER`, `AUTH_JWKS_URI`, and `AUTH_AUDIENCE`.
16
+ `MCP_CANONICAL_URI` and `AUTH_AUDIENCE` must be identical normalized HTTPS URLs.
17
+ Only explicit loopback development may use HTTP.
18
+ Follow [Bring your own identity provider](getting-started-identity-provider.md)
19
+ for the complete audience, scope, client-registration, configuration, and
20
+ verification procedure.
21
+
22
+ Terminate TLS at the reverse proxy. Forward exactly the canonical Host,
23
+ including a non-default port, and block direct access to the application port.
24
+ The app does not trust Forwarded/X-Forwarded-* for identity, origins, or rate
25
+ limiting. Configure proxy rate/connection limits as well. Behind a proxy the
26
+ application's per-IP limiter sees the proxy's IP, so its limit is shared by
27
+ those clients; do not fix that by trusting arbitrary forwarded IP headers.
28
+
29
+ Browser origins default to the canonical origin. `MCP_ALLOWED_ORIGINS` accepts
30
+ comma-separated exact additional origins, without paths or wildcards. Login,
31
+ consent, and logout POSTs always require the canonical Origin, even when an
32
+ additional MCP browser origin is allowed. Tokens must have a nonblank subject
33
+ of at most 512 UTF-8 bytes. External subject spelling and case are preserved.
34
+
35
+ ## Self-hosted provisioning
36
+
37
+ Generate three independent random 32-byte keys, each encoded as 64 hex
38
+ characters. `openssl rand -hex 32` generates one key; run it independently
39
+ for `AUTH_SIGNING_KEY`, `AUTH_CREDENTIAL_HASH_KEY`, and `SECRET_APP_KEY`.
40
+ Supply them through the deployment secret manager. Never put them in the
41
+ image, shell history, repository, issue comments, or logs. Existing signing
42
+ key strings are not automatically compatible with the new hex encoding.
43
+
44
+ Use the same database and credential hash key for provisioning and the server:
45
+
46
+ ```sh
47
+ node dist/cli.js auth user create --subject user-identifier
48
+ node dist/cli.js auth user create --subject operator-identifier --role admin
49
+ node dist/cli.js auth user list
50
+ node dist/cli.js auth user grant --subject user-identifier --role package-approver
51
+ node dist/cli.js auth user grant --subject user-identifier --revoke-role package-approver
52
+ node dist/cli.js auth user grant --subject user-identifier --role service-manager
53
+ node dist/cli.js auth key create --subject user-identifier
54
+ node dist/cli.js auth key list --subject user-identifier
55
+ node dist/cli.js auth key revoke PUBLIC-KEY-ID
56
+ node dist/cli.js auth user disable --subject user-identifier
57
+ ```
58
+
59
+ Creation prints the complete high-entropy login key once. Distribute it out of
60
+ band through a secure channel; plaintext cannot be recovered from the database.
61
+ Login keys expire after one year. For a lost key, create a replacement and
62
+ revoke the old public key ID. Revocation also revokes the user's existing
63
+ sessions and OAuth families. Disable is terminal through the CLI; reactivation
64
+ requires a separately reviewed operator workflow.
65
+
66
+ The CLI rejects any flag a command does not take, for example
67
+ `auth key create --role admin`. It also rejects any unknown role name.
68
+ `auth user grant` changes only an existing user and never creates one. Both
69
+ `--role` and `--revoke-role` can be repeated.
70
+
71
+ ## Roles and privileged operations
72
+
73
+ Scopes and roles do different jobs:
74
+
75
+ - **Scopes delegate.** A token's scopes are what the user let a client do on
76
+ their behalf. A user may consent to any supported scope, and issuing a token
77
+ never depends on roles.
78
+ - **Roles authorize.** A user has a set of roles. With no roles, the user is a
79
+ member:
80
+
81
+ | Role | Grants |
82
+ | ------------------ | --------------------------------------------------------------- |
83
+ | `admin` | `agents:admin`, `packages:approve` and `services:manage` |
84
+ | `package-approver` | `packages:approve` |
85
+ | `service-manager` | `services:manage` |
86
+ | (none) | nothing privileged; every other scope works as the token allows |
87
+
88
+ Five operations are privileged:
89
+
90
+ - `make_owner`, which reassigns any agent's owner, including another
91
+ principal's private agent (see [Sharing agents](#sharing-agents) for what
92
+ moves with it);
93
+ - setting a BYO `workerImageRef`;
94
+ - approving coding agents' package allowlists or policy;
95
+ - approving a repository for an agent without checking GitHub access
96
+ (`adminOverride` on `link_repository`, `repositoryAdminOverride` on
97
+ `create_agent`/`update_agent`; see [Repository access](#repository-access));
98
+ - creating, updating or deleting coding-run service catalog entries
99
+ (`create_service`, `update_service`, `delete_service`; reading the catalog
100
+ is `agents:read`, see [coding-services.md](coding-services.md)).
101
+
102
+ Each needs **both** its scope on the token **and** a role that grants that
103
+ permission:
104
+
105
+ - `make_owner`, `workerImageRef`, and repository approval need `agents:admin`.
106
+ - Package approval needs `packages:approve`, or `agents:admin`.
107
+ - Service catalog changes need `services:manage`.
108
+
109
+ In practice:
110
+
111
+ - an `admin` can do all five;
112
+ - a `package-approver` can approve packages only;
113
+ - a `service-manager` can change the service catalog only;
114
+ - a member can do none of them.
115
+
116
+ Callers who fail the check get `403`:
117
+
118
+ - A caller whose token holds the scope but whose roles don't grant it gets a
119
+ "requires a role" error. Authorizing again for more scopes can't fix this;
120
+ an operator has to grant a role.
121
+ - A caller who has a role granting the permission but whose token lacks the
122
+ scope gets the usual `insufficient_scope` challenge, naming the scope to
123
+ request. For package approval, that is the alternative their role grants.
124
+
125
+ Roles are read from the database on every request and are never cached.
126
+
127
+ **Any role change signs the user out.** This applies to both `--role` and
128
+ `--revoke-role`. In the same transaction, `auth user grant` revokes all of the
129
+ user's:
130
+
131
+ - OAuth grant families and refresh tokens;
132
+ - browser sessions;
133
+ - unexchanged authorization codes.
134
+
135
+ Every client must sign in and authorize again. The user sees the scopes afresh
136
+ under their new roles. This has two consequences:
137
+
138
+ - A token minted under the old roles can never regain that reach if a role is
139
+ granted again later.
140
+ - A grant the user consented to while a privileged scope was inert (for
141
+ example, an MCP client that requested every scope) never silently gains
142
+ privileged reach through a promotion.
143
+
144
+ A change that leaves the roles as they were, such as revoking a role the user
145
+ doesn't hold, revokes nothing.
146
+
147
+ The other scopes are not privileged. Every agent tool also checks the
148
+ caller's access to that agent, its owner or an explicit grant (see
149
+ [Sharing agents](#sharing-agents)); a scope never reaches an agent the caller
150
+ has no access to.
151
+
152
+ **Upgrading:** every existing self-hosted user starts with no roles, including
153
+ the operator. After deploying, grant yourself the admin role, then reconnect
154
+ your MCP client:
155
+
156
+ ```sh
157
+ node dist/cli.js auth user grant --subject YOUR_SUBJECT --role admin
158
+ ```
159
+
160
+ In delegated mode, roles come from a signed access-token claim that you map
161
+ with `AUTH_ROLE_CLAIM` and `AUTH_ROLE_MAP`. See
162
+ [Bring your own identity provider](getting-started-identity-provider.md#wardby-roles).
163
+ If you leave them unset, nobody has a role and the privileged operations are
164
+ refused over HTTP.
165
+
166
+ The browser login uses a single-use, cookie-bound challenge. Consent displays
167
+ the client, the canonical resource, and exactly the scopes approval issues: the
168
+ supported part of the request, fixed when the authorization request is created. Session tokens are
169
+ server-side hashes, with a 12-hour idle and 30-day absolute lifetime; HTTPS uses
170
+ `__Host-` cookies with Secure, HttpOnly, Path=/, and SameSite=Lax. Loopback-only
171
+ HTTP development uses explicitly named development cookies without Secure.
172
+ Sign-in rotates the token and revokes the previous presented session.
173
+
174
+ Auth forms require JavaScript. A nonce-protected same-origin fetch submits the
175
+ form while retaining Origin under `Referrer-Policy: no-referrer`; native form
176
+ POSTs otherwise send `Origin: null`. CSP permits only that per-response nonce
177
+ and same-origin connections. Null-origin requests remain rejected. This
178
+ behavior is covered by real Chrome, rather than only a synthetic cookie jar.
179
+ See the [Fetch standard](https://fetch.spec.whatwg.org/#append-a-request-origin-header).
180
+
181
+ Clients must re-register as public clients (`token_endpoint_auth_method=none`).
182
+ Only S256 PKCE and exact registered redirect strings are accepted. Redirects
183
+ must be HTTPS or loopback HTTP, without credentials or fragments. Registration
184
+ allows 1-10 redirects and a bounded client name. `AUTH_MAX_CLIENTS` defaults to
185
+ 1000; PostgreSQL serializes capacity checks and shares rate limits across
186
+ processes. There is no confidential-client support or client secret.
187
+
188
+ Interactions expire after 10 minutes, authorization codes after 60 seconds,
189
+ access tokens after 10 minutes, and refresh families after 30 days. Code
190
+ consumption is atomic. Each refresh rotates its opaque token; reuse revokes the
191
+ whole family, including descendants. Access verification checks family and user
192
+ state, so revocation also invalidates existing access tokens. `/revoke` takes
193
+ the refresh token and public client ID. Do not retry a refresh with the same
194
+ token after an uncertain response; start a fresh authorization flow.
195
+
196
+ The server cleans expired auth rows every 15 minutes. Consumed refresh grants
197
+ remain until family expiry for reuse detection. Monitor cleanup failures.
198
+ Changing the credential hash key invalidates existing login keys, sessions,
199
+ codes, challenges, and refresh tokens. Rotate signing and credential keys
200
+ together during a planned global credential reset; provision new login keys
201
+ and reauthorize clients. Do not rotate `SECRET_APP_KEY` without a separate
202
+ encrypted-secret migration, or existing application secrets become unreadable.
203
+
204
+ ## Sharing agents
205
+
206
+ An agent is private to its owner unless the owner shares it. A grant is
207
+ `(agent, grantee, level)`; the grantee is one principal or **everyone**:
208
+
209
+ | Level | Lets the grantee |
210
+ | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
211
+ | `read` | see the agent's config (never secret values, never other owners' tool code), list its sub-agents |
212
+ | `execute` | also trigger runs, and see the runs they triggered |
213
+ | `write` | also change its name, prompt, model, budget amount, max turns, effort, memory on/off and schedule; attach tools they can use and detach tools; create webhooks |
214
+ | owner | everything, plus the owner-only operations below |
215
+
216
+ Owners manage grants with `grant_access`, `revoke_access` and `list_access`.
217
+ Granting again replaces the level. A revoke takes effect on the next check
218
+ (trigger, delegation, webhook fire); runs already in flight keep going.
219
+
220
+ **Owner-only, whatever the grants:** `delete_agent`, managing grants, the
221
+ agent's secret, datastore and repository bindings, the whole coding profile
222
+ (task, base ref, protected paths, task-override opt-in, image, packages,
223
+ toolchain, limits), changing its kind, its budget group, attaching or
224
+ detaching sub-agents, reading or writing its memory and datastore contents,
225
+ and granting capabilities to a tool attachment.
226
+
227
+ **What execute hands over.** An execute-grantee runs your agent with your
228
+ tools, secrets, datastores and repository, and every run shares the agent's
229
+ memory. A non-owner supplies no instructions of their own: a native agent
230
+ takes no task text from `trigger_agent` or from another owner's sub-agent
231
+ delegation, and a coding agent takes a `task` or `baseRef` from a non-owner
232
+ only if `codingProfile.allowWebhookTaskOverride` is on (the same opt-in
233
+ webhooks use); otherwise the run uses your `defaultTask`. Webhooks still
234
+ accept task text for native agents (the webhook creator needs `write` to make
235
+ one). Runs are visible to their triggerer and to you, never to other
236
+ grantees.
237
+
238
+ **What write hands over.** A write-grantee can rewrite what the agent's runs
239
+ do, but not add to what they can reach. The prompt can make a run use
240
+ everything the agent already reaches: the tool capabilities you granted, its
241
+ memory, and its linked repositories. On a coding agent the prompt is part of
242
+ every coding task, so write directs work done with your GitHub access; grant
243
+ write on such an agent only to someone you'd let push. What stays yours: the
244
+ four capability fields on a tool attachment (`allowedSecrets`,
245
+ `allowedDatastorePrefixes`, `allowedHosts`, `allowedSharedDatastorePrefixes`),
246
+ so a tool a write-grantee attaches runs with none until you re-run
247
+ `attach_tool` with them (you then vouch for code only its owner can read);
248
+ the coding profile, kind and budget group; and sub-agents, since an edge
249
+ would hand every run's data to another agent.
250
+
251
+ **Everyone** can be granted at most `execute`: everyone-write would let
252
+ anyone rewrite an agent that holds your tools and secrets.
253
+
254
+ **Nothing crosses owners without a grant on that exact resource**, checked
255
+ when bindings are written and again at run time, so rows written before this
256
+ rule (or left behind by `make_owner`) stop working on their own:
257
+
258
+ - a secret or datastore binding resolves only while the resource's owner is
259
+ the agent's current owner; any other binding behaves like an unattached
260
+ name;
261
+ - a tool attachment's capabilities count only while the agent's current owner
262
+ granted them;
263
+ - a sub-agent runs only if it has the parent's owner, or the parent's owner
264
+ holds `execute` on it (`attach_subagent` is the parent's owner's, with
265
+ `execute` on the child). Across owners, the edge carries no memory access,
266
+ no `grantParentMemoryKeys`, no `continuePriorRun`, no task text for a
267
+ native child (it runs its own prompt), and no coding task unless the child
268
+ allows non-owner task text. The child's owner can always detach it;
269
+ - a webhook fires only while its creator owns the agent or holds `execute`.
270
+
271
+ **The stdio operator** (`wardby mcp` over stdio) is owner of every agent for
272
+ access checks and sees every agent, but does not bypass the binding rules:
273
+ it can't bind its own secret to someone else's agent, and it is not the owner
274
+ for coding-profile changes, coding task overrides or sub-agent edges on
275
+ someone else's agent (it can adopt the agent first). An HTTP user whose
276
+ subject equals `LOCAL_PRINCIPAL` is not the operator. **Admins** get no
277
+ implicit access to others' agents: they can `list_access` any agent (incident
278
+ response) and reassign one with `make_owner`, which removes bindings the new
279
+ owner doesn't own, suspends tool capabilities the new owner never granted,
280
+ cuts sub-agent edges the new owner can't delegate, and, between two owners,
281
+ resets every grant. Adopting an owner-less agent keeps its grants but lowers
282
+ an everyone grant to `read` unless `keepEveryoneExecute` is set.
283
+ `make_owner` no longer releases an agent to owner-less.
284
+
285
+ ### Upgrading: owner-less agents
286
+
287
+ Before this release, an agent with no owner was public: anyone could read
288
+ **and change** it. The migration keeps such agents runnable by giving each an
289
+ everyone-`execute` grant; nobody but the stdio operator can edit them until
290
+ they get an owner. Until then they run without owned secrets or datastores and
291
+ without their tools' capabilities. Operator runbook:
292
+
293
+ 1. On the old version, with the new binary:
294
+ `wardby grants migration-report > before.txt`. It is read-only and lists
295
+ owner-less agents (and, per possible adopter, which secrets, datastores
296
+ and tool capabilities adopting would bring back), the bindings and
297
+ capabilities that will stop working, owner-less-tool capabilities that
298
+ stay in force on owned agents (review them if the agent ever changed owner
299
+ with `make_owner`), sub-agent edges that will be refused, webhooks that
300
+ won't fire, and the behaviour changes.
301
+ 2. `prisma migrate deploy` (`npm run prisma:migrate`), then roll out. The
302
+ migration needs PostgreSQL 13 or later.
303
+ 3. Immediately: `wardby grants adopt-public --owner <subject> --dry-run`,
304
+ then the same without `--dry-run`. Every owner-less agent gets that owner;
305
+ its everyone grant is lowered to `read`, because the agent is about to
306
+ regain the owner's secrets and capabilities (pass
307
+ `--keep-everyone-execute` to keep it runnable by everyone, knowingly;
308
+ webhooks others created stop firing otherwise). The owner's own bindings
309
+ come back to life, the others are removed and printed; attachments of the
310
+ owner's own tools regain their capabilities; every other attached tool is
311
+ printed for review, with the `attach_tool` call that re-grants any
312
+ capabilities it had. The subject must already exist.
313
+ 4. `wardby grants prune-bindings` removes, and prints, every remaining
314
+ binding of one owner's secret or datastore to another owner's agent
315
+ (already inert).
316
+ 5. `wardby grants migration-report > after.txt`.
317
+
318
+ `wardby agent create` now owns the agent by `--owner <subject>` or
319
+ `LOCAL_PRINCIPAL` (`--public` adds everyone-`execute`), `wardby tool attach`
320
+ grants capabilities in the agent owner's name, and `wardby import --public`
321
+ owns the imported agents by `--owner` or the importing operator, shared with
322
+ everyone at `execute`.
323
+
324
+ ## Repository access
325
+
326
+ The GitHub App is installed on repositories by their owners, not per wardby
327
+ user, so "the App can reach it" is not permission to use it. An agent may use
328
+ a repository — a `link_repository` link, or a coding agent's
329
+ `codingProfile.repository` — only when:
330
+
331
+ - its **current owner's** GitHub account, linked with `link_host_account`, has
332
+ enough permission on it: write for coding repositories and write links
333
+ (they push, comment, and publish checks), read for read links; or
334
+ - a wardby `admin` approved that exact repository explicitly; the approval is
335
+ recorded (who, when); or
336
+ - the authorization predates enforcement (`grandfathered`, stamped by the
337
+ migration).
338
+
339
+ Trust assumptions:
340
+
341
+ - **The owner's access decides, not the trigger's.** Whoever triggers a run (a
342
+ schedule, a webhook, a host event, the owner, an `execute`-grantee) only
343
+ supplies task text, and a non-owner supplies coding task text only when the
344
+ owner enabled `allowWebhookTaskOverride`; the owner controls the tools,
345
+ secrets and repository. A repository is a binding: only the owner (or an
346
+ admin's explicit approval) sets it, never a `write`-grantee.
347
+ - **Owner-less agents never hold a repository**, even admin-approved: there is
348
+ no owner whose GitHub access can be checked. Assign an owner first.
349
+ - **Checked where it's used.** Every coding run (before its workspace is
350
+ prepared, and again right before it pushes), every `repo_*` call, and every
351
+ host-event dispatch re-checks a `host_permission` authorization against the
352
+ current owner, with a 5-minute cache. A GitHub error refuses the use; for a
353
+ run already under way, a transient error (5xx, timeout, rate limit) is
354
+ retried once, then refused with its own category
355
+ (`repo_access_unavailable`). Set-time checks never retry.
356
+ - **Approvals stay with the owner they were granted under.** Admin and
357
+ grandfathered authorizations are not re-checked while the agent keeps its
358
+ owner; revoke them by unlinking or changing the repository. `make_owner` to
359
+ a different owner converts them into checks of the next owner's own GitHub
360
+ access; only an owner-less agent's first owner keeps them.
361
+ - **Admins approve explicitly, on any owned agent.** `adminOverride` works on
362
+ agents the admin doesn't own (the admin role can already reassign any
363
+ agent); it is always recorded with the approver. On another's agent an
364
+ admin can change only the repository.
365
+ - **Public repositories:** GitHub reports read access for everyone, so any
366
+ linked principal may create a `read` link to a public repository the App is
367
+ installed on. Only public data is exposed that way.
368
+ - **Identity is proven, not claimed.** Linking uses the App's OAuth web flow
369
+ with single-use state, S256 PKCE, and a one-time confirmation code that
370
+ only the initiating principal can submit, so a victim clicking an attacker's
371
+ link can't bind their GitHub account to the attacker. wardby keeps only the
372
+ GitHub numeric user id and login; the user token is revoked immediately.
373
+ Permission checks use the App's installation token. One GitHub account links
374
+ to one principal.
375
+ - **Mentions need push access.** An `@<app-slug>` mention runs an agent only
376
+ when its author has write access to the repository, checked live by user id.
377
+ - **Checks are bound to the dispatched PR.** A review publishes a check only on
378
+ the pull request its run was dispatched for; fork PRs are never dispatched.
379
+
380
+ Before upgrading, find owner-less agents that have a repository link or a
381
+ coding profile (`list_agents`, `list_repositories`) and give each an owner
382
+ with `make_owner` (or `wardby grants adopt-public`), or they stop running. See
383
+ [code-review-agents.md](code-review-agents.md#who-may-give-an-agent-a-repository)
384
+ for the App settings (callback URL, client ID and secret).
385
+
386
+ ## Migration and rollback
387
+
388
+ Migration `20260906040000_secure_self_hosted_oauth` deliberately drops the two
389
+ legacy OAuth tables and recreates incompatible secure state. All legacy
390
+ clients, codes, access tokens, refresh tokens, and login credentials must be
391
+ reissued. The verifier also rejects the legacy token shape, even with retained
392
+ signing material. No application principal or owned business data is deleted.
393
+
394
+ Stop issuance, take a backup of unrelated data, and use `prisma migrate deploy`
395
+ from a trusted migration job. Never use `db push` or edit an applied migration.
396
+ The schema adds users, hashed login keys, sessions, form challenges,
397
+ authorization interactions/codes, serialized refresh families/grants, and
398
+ shared throttling. Foreign keys and indexes are declared in both SQL and Prisma.
399
+
400
+ Restoring a full pre-fix backup would resurrect usable compromised credentials.
401
+ The secure recovery alternative restores unrelated tables only, with issuance
402
+ stopped. For a schema rollback, recreate empty legacy OAuth placeholders
403
+ before reapplying the security migration; never restore their contents. The
404
+ rehearsal script exercises migration, unrelated-data restoration, and
405
+ re-migration against synthetic data in an isolated container:
406
+
407
+ ```sh
408
+ node scripts/security-migration-rehearsal.mjs
409
+ ```
410
+
411
+ That script deliberately targets only container `wardby-security-20260906`,
412
+ database `wardby_security`, and freshly generated temporary schemas. Its
413
+ temporary schemas are removed after verification. It is not a production
414
+ backup or rollback command. Have a second reviewer inspect production backup,
415
+ recovery, auth identity derivation, CSRF, transactions, and SSRF before rollout.
416
+
417
+ ## Durable executor
418
+
419
+ `EXECUTOR=dbos` tables live outside Prisma's migration chain: DBOS creates and
420
+ migrates its own `dbos` schema (`DBOS_SCHEMA`) at `launch()`. On a fresh
421
+ schema that starts with `CREATE SCHEMA IF NOT EXISTS`, which Postgres checks
422
+ against `CREATE` on the _database_ even when the schema already exists, so a
423
+ server role limited to data access cannot launch DBOS on its own. Either give
424
+ the server's role that privilege, or migrate the schema out of band as a
425
+ privileged role with `npm run dbos:migrate` (`dbos schema "$DATABASE_URL"`)
426
+ and grant the server's role `USAGE` on the schema plus `SELECT`, `INSERT`,
427
+ `UPDATE` and `DELETE` on its tables, with default privileges for tables a
428
+ later SDK version adds. Once the schema is current, `launch()` changes
429
+ nothing. The GKE module does the latter (`deploy/gke/database-grants.sql`).
430
+
431
+ `DBOS_EXECUTOR_ID` must be unique per running _process_ — two processes
432
+ sharing an id each believe they own the other's in-flight runs and re-drive
433
+ them at launch. Left unset, each process generates a random one, which is the
434
+ safe default: a process that replaces a dead one does not need its id,
435
+ because the reconciler adopts any live workflow whose run's heartbeat has gone
436
+ stale, whichever executor id owns it. Set a fixed id only for a single,
437
+ long-lived process that should re-drive its own runs at launch rather than
438
+ after the heartbeat timeout.
439
+
440
+ **New data at rest.** Durable execution checkpoints every step's result into
441
+ `dbos.operation_outputs`: each turn's assistant text, every tool call's
442
+ arguments, every tool result in full, and the `load` step's pinned agent
443
+ fields plus each attached tool's source and `paramsZod`. That is the same
444
+ class of data as the `Run`/`Tool` tables — prompt content, tool output, and
445
+ anything a tool returned from a secret-bearing call — and it accumulates for
446
+ the life of every workflow record, with no retention limit of its own. Give
447
+ the `dbos` schema the same encryption-at-rest, access control, backup, and
448
+ backup-retention handling as the application schema, and prune completed
449
+ workflows periodically: `DBOS.deleteWorkflow(workflowID, deleteChildren?)`
450
+ removes one workflow and its step records (irreversible), or delete rows
451
+ directly from the schema's tables for a bulk retention job. Prune only
452
+ workflows in a finished status — deleting a PENDING record makes its run
453
+ unrecoverable, and the reconciler will reap it as `lost`.
454
+
455
+ **Version pinning across upgrades.** DBOS gates both recovery and dequeue on
456
+ `application_version`, which it computes from the registered code unless
457
+ `DBOS__APPVERSION` is set. After an SDK bump or any change to the workflow
458
+ wrapper, workflows recorded under the old version are never dequeued by the
459
+ new process: pin `DBOS__APPVERSION` per deploy, or drain in-flight runs before
460
+ upgrading. The executor no longer loops on those runs — `recover()` reports
461
+ the run `lost` as soon as it sees a version mismatch, and otherwise gives up
462
+ after three adoption attempts. That attempt counter is in memory and per
463
+ process, so it resets on restart.
464
+
465
+ **Rolling back, and rolling forward again.** Rolling back to
466
+ `EXECUTOR=in-process` is safe at any time: any DBOS run still in flight is
467
+ reconciled to `lost` once its heartbeat times out, because no executor is left
468
+ to recover it, and nothing else in the deployment depends on the `dbos`
469
+ schema. The return trip is the part to know about: those workflows are still
470
+ PENDING in the `dbos` schema, and re-enabling `EXECUTOR=dbos` with the same
471
+ executor id re-drives them at launch. (With a random per-process id, nothing
472
+ re-drives them: they stay PENDING until pruned.) They no longer re-spend — `executeRun`
473
+ short-circuits on any run whose row is already terminal, so a re-driven
474
+ workflow whose run was reaped as `lost` does no work and leaves the row
475
+ `lost` — but the workflows do wake up and run to completion in DBOS's own
476
+ records. Delete them (see pruning above) if you want the rollback to be final.
477
+
478
+ ## Resource and networking limits
479
+
480
+ | Surface | Limit |
481
+ | ------------------------------ | ------------------------------------------------- |
482
+ | MCP/webhook bodies | 1 MiB raw bytes |
483
+ | Auth bodies | 64 KiB raw bytes |
484
+ | Request completion / headers | 15 seconds / 10 seconds |
485
+ | Sandbox fetch | 8 MiB encoded and decoded, 5 redirects, 8 seconds |
486
+ | Bridge input / output | 1 MiB / 12 MiB |
487
+ | Parser input / console payload | 256 KiB / 16 KiB |
488
+ | HTML links extracted | 1000, with incremental result-byte accounting |
489
+ | Random bytes | 65,536 bytes |
490
+ | Host calls | 256 per invocation, at most 8 pending |
491
+ | Datastore value / keys listed | 1 MiB / 1000 |
492
+ | Datastore key / secret name | 1024 bytes |
493
+ | New secret plaintext | 64 KiB |
494
+
495
+ Oversized or malformed input fails explicitly. Existing oversized datastore
496
+ values/keys and secret ciphertext fail instead of entering Node allocations.
497
+ Response limits also apply after decompression. Fetch validates every redirect
498
+ and pins the validated DNS result to the connection while retaining Host and
499
+ TLS verification. Cross-origin redirects remove credentials, including custom
500
+ API-key headers; forwarding a request body across origins is rejected.
501
+
502
+ Every resolved address must be global. A tool's own `allowedHosts` only
503
+ narrows egress; it never opens a private, loopback or link-local destination.
504
+ Only the operator's `WARDBY_FETCH_ALLOWED_HOSTS` (exact normalized hosts) can,
505
+ and a non-wildcard tool must list the host too. Cloud metadata endpoints
506
+ (`169.254.169.254`, `169.254.169.252`, `169.254.170.2`, `169.254.170.23`,
507
+ `100.100.100.200`, `fd00:ec2::254`, `fd00:ec2::23`, `fd20:ce::254`, their
508
+ mapped/NAT64/6to4 IPv6 forms, `metadata.google.internal`, `metadata`) are
509
+ always blocked, even if listed.
510
+ The network layer cannot back this up for the control plane on GKE, because
511
+ Workload Identity needs the metadata server.
512
+ Sandbox code can explicitly log secrets it has been given; log size limits do
513
+ not provide automatic redaction. Keep tool-authoring privileges restricted.
514
+ Public/non-owner tool listings expose only id/name/description; owners retain
515
+ their source and schema. Private agents are indistinguishable from missing IDs.
516
+
517
+ Cancellation stops bridge delivery, sleep timers, and in-flight network work.
518
+ Prisma's query interface does not accept an AbortSignal (still true on Prisma
519
+ 7's `pg` driver adapter): an already-dispatched database operation may finish
520
+ after sandbox cancellation. Its returned data is
521
+ bounded before entering Node, but cancellation is not a transaction rollback.
522
+ Set a short database statement/lock timeout on the dedicated application role,
523
+ bound connection pools and concurrent invocations, and review a cancellable
524
+ database adapter or isolated worker if strict immediate termination is required.
525
+ Do not claim these per-invocation caps establish a whole-process memory ceiling.
526
+
527
+ ## Coding runs on Kubernetes
528
+
529
+ `JOB_LAUNCHER=kubernetes` runs each coding agent in its own pod instead of a
530
+ container. The trust model is unchanged — the worker is untrusted, holds no
531
+ provider credential, and may reach only the coding proxy — but a cluster
532
+ enforces that differently from a Docker host, and two controls exist because a
533
+ cluster's configuration cannot be taken on trust.
534
+
535
+ **The pod is attested before the worker runs.** The pod and NetworkPolicy read
536
+ back from the API server are compared field by field against what wardby built.
537
+ Any difference fails the launch closed. This is what catches a mutating
538
+ admission controller, or anyone with cluster access, weakening a pod's isolation
539
+ between creation and start. Only an explicit list of known server defaults is
540
+ normalized away; nothing else is tolerated.
541
+
542
+ **Enforcement is proven, not assumed.** A cluster accepts a NetworkPolicy
543
+ whether or not its CNI enforces one, and even where enforcement works it is
544
+ programmed seconds after a pod starts. Before releasing the worker, the launcher
545
+ connects from inside the pod to a destination that must be blocked and requires
546
+ three consecutive refusals. A cluster that does not enforce policy, or has not
547
+ yet programmed this pod's rules, never reaches the point of running agent code.
548
+ The same check runs at startup against a throwaway canary pod, which also
549
+ verifies the pod cannot reach DNS, the internet or the cloud metadata endpoint.
550
+
551
+ **RBAC.** The control plane needs a namespace Role (`pods`, `pods/exec`,
552
+ `pods/log`, `secrets`, `configmaps`, `networkpolicies`, `services`) and a
553
+ ClusterRole granting `get` on the single namespace it runs in, because the
554
+ preflight's namespace read is cluster-scoped. Workers get a dedicated
555
+ ServiceAccount with no permissions and no mounted token. Reference manifests are
556
+ in `deploy/kind-coding/manifests/`.
557
+
558
+ **Sandboxing.** The spec requires gVisor on GKE; the reference `kind` harness
559
+ has none, so that configuration is development-only and says so. Set
560
+ `KUBERNETES_RUNTIME_CLASS` to the cluster's sandboxed runtime class in
561
+ production; the launcher warns loudly when it is unset.
562
+
563
+ The `gke-autopilot` platform profile contains narrow admission allowances
564
+ captured from a real cluster and requires the `gvisor` runtime class. A dry-run
565
+ capture cannot observe labels added after pod scheduling, so live-run behavior
566
+ is pinned separately by tests. Run `wardby coding preflight` against every
567
+ target cluster before accepting work; it fails closed when admission or network
568
+ behavior differs from the reviewed profile.
569
+
570
+ Before production deployment, review the
571
+ [current Kubernetes limitations](coding-worker-isolation.md#known-limitations).
572
+ Per-run record ConfigMaps currently require operator-managed garbage collection,
573
+ and the real-cluster suite does not yet cover every Docker containment
574
+ scenario.
575
+
576
+ ## Coding package registry
577
+
578
+ Enabling a coding agent's package allowlist widens the trusted proxy's own
579
+ egress, not the worker's: the proxy (the only host any worker can reach) is
580
+ additionally permitted to reach `registry.npmjs.org`, `pypi.org`,
581
+ `files.pythonhosted.org`, and `api.osv.dev`, through the same pinned HTTPS
582
+ fetch used for everything else it calls out to (no redirects, no private or
583
+ IP-literal addresses). The worker's own network reachability is unchanged: it
584
+ still reaches only `wardby-proxy:8787`. See
585
+ [Installing packages in coding runs](coding-packages.md) for the full model,
586
+ including its per-run download limits.
587
+
588
+ Two of that page's safeguards are worth calling out for an operator: npm
589
+ install scripts are disabled only by a configuration default
590
+ (`npm_config_ignore_scripts=true`) that the proxy cannot enforce against a
591
+ downloaded tarball, so treat it as a documented limit rather than a
592
+ guarantee; and the OSV vulnerability audit fails **closed**
593
+ (`503 wardby_audit_unavailable`) when OSV can't be reached, unless the
594
+ operator explicitly sets `REGISTRY_AUDIT_FAIL_OPEN=true` to allow installs
595
+ through unaudited during an OSV outage.
596
+
597
+ ## Images and dependencies
598
+
599
+ ```sh
600
+ docker build -f deploy/Dockerfile --target runtime -t wardby-runtime .
601
+ docker build -f deploy/Dockerfile --target migration -t wardby-migration .
602
+ ```
603
+
604
+ The runtime runs as uid 1000 and contains the generated Prisma client (compiled
605
+ into `dist/generated/prisma`) with `@prisma/client`'s runtime and the `pg`
606
+ driver adapter, but no Prisma CLI, `@prisma/config`, `deepmerge-ts` or `mysql2`
607
+ (the image build asserts this). Its manifest drops development dependencies and
608
+ omits optional peers before installation: `@prisma/client` declares the CLI as
609
+ an optional peer. The migration image keeps the full build tree -- the Prisma
610
+ CLI and `prisma.config.ts`.
611
+
612
+ **Development tree (repository, CI, build and migration images).** The Prisma
613
+ CLI (a devDependency since the Prisma 7 upgrade) still carries two flagged
614
+ packages, both forced to patched versions by `overrides` in `package.json`:
615
+
616
+ ```json
617
+ "overrides": { "deepmerge-ts": "8.0.2", "mysql2": "3.24.4" }
618
+ ```
619
+
620
+ - `deepmerge-ts`: `@prisma/config` 7.10.0 still pins 7.1.5 exactly. The 8.x
621
+ override is safe because `@prisma/config` calls only `deepmerge()`; the 8.0
622
+ breaking changes are two type renames and `deepmergeInto`, which Prisma does
623
+ not call.
624
+ - `mysql2` (GHSA-3f6p-5ww8-9rcr, GHSA-rgwj-5xj2-c3m3, high): `prisma` 7.10.0
625
+ pins 3.15.3 exactly; it is used only by Prisma Studio's MySQL executor
626
+ (`createPool` from `mysql2/promise`), never by wardby, which is
627
+ PostgreSQL-only. 3.24.4 is the same major. After changing either override,
628
+ re-run `prisma generate`, `prisma validate`, the `migrate diff` drift check,
629
+ and the migration image's `migrate deploy`.
630
+
631
+ `scripts/security-audit.mjs` carries no exceptions: with the overrides the full
632
+ and production audits are clean, and if a lockfile change ever dropped one, the
633
+ advisory would reappear and the security job would fail.
634
+
635
+ Prisma's npm `latest` dist-tag currently points at an 8.0 release candidate, so
636
+ pin exact versions (the build hides the CLI's update banner, which recommends
637
+ it).
638
+
639
+ Vitest and esbuild's additional development advisories were remediated by
640
+ supported tooling updates.
641
+
642
+ ## Verification and rollout
643
+
644
+ Install the test browser with `npx playwright install chromium`; on this
645
+ machine `SECURITY_BROWSER_CHANNEL=chrome` uses installed Chrome in an isolated
646
+ headless profile. Database-backed and browser tests are not release evidence
647
+ when skipped. Use a disposable `DATABASE_URL`, then run typecheck, full tests,
648
+ contract tests, build, Prisma validation, migration replay/drift/recovery, and
649
+ `node scripts/security-audit.mjs`. The CI job runs database tests and archives a dependency tree.
650
+ After building, `node --expose-gc scripts/security-allocation-check.mjs` checks
651
+ stream abortion, read-ahead, and process-memory growth for an endless response.
652
+
653
+ Before external rollout, verify proxy TLS/Host normalization, blocked direct
654
+ ingress, egress policy, least-privilege encrypted database access, secret-manager
655
+ injection, credential-free access logs, and alerts for 401/403/413 responses,
656
+ blocked fetches, cleanup failures, and memory pressure. These deployment gates
657
+ cannot be established by local source tests alone.