@wardby/cli 0.2.0 → 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 (491) hide show
  1. package/.env.example +34 -4
  2. package/README.md +183 -71
  3. package/dist/claude-coding-worker/driver.d.ts +7 -1
  4. package/dist/claude-coding-worker/driver.js +31 -2
  5. package/dist/claude-coding-worker/main.js +5 -2
  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 +99 -3
  21. package/dist/coding/protocol.js +155 -4
  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 +18 -0
  60. package/dist/coding-worker/artifact.js +48 -6
  61. package/dist/coding-worker/debug-trace.d.ts +37 -0
  62. package/dist/coding-worker/debug-trace.js +117 -0
  63. package/dist/coding-worker/driver.d.ts +14 -1
  64. package/dist/coding-worker/driver.js +106 -14
  65. package/dist/coding-worker/errors.d.ts +7 -0
  66. package/dist/coding-worker/errors.js +26 -0
  67. package/dist/coding-worker/main.js +13 -4
  68. package/dist/coding-worker/sdk.d.ts +2 -2
  69. package/dist/coding-worker/sdk.js +7 -2
  70. package/dist/coding-worker/types.d.ts +3 -0
  71. package/dist/config/providers.d.ts +41 -0
  72. package/dist/config/providers.js +66 -0
  73. package/dist/core/budget-groups.d.ts +109 -6
  74. package/dist/core/budget-groups.js +125 -19
  75. package/dist/core/budget-wording.d.ts +22 -0
  76. package/dist/core/budget-wording.js +55 -0
  77. package/dist/core/coding-queue.d.ts +1 -1
  78. package/dist/core/datastores.d.ts +1 -1
  79. package/dist/core/datastores.js +18 -2
  80. package/dist/core/db.d.ts +40 -2
  81. package/dist/core/db.js +75 -2
  82. package/dist/core/dispatch.d.ts +49 -7
  83. package/dist/core/dispatch.js +290 -45
  84. package/dist/core/engine-native.js +27 -10
  85. package/dist/core/grants.d.ts +86 -0
  86. package/dist/core/grants.js +126 -0
  87. package/dist/core/host-events.d.ts +49 -0
  88. package/dist/core/host-events.js +257 -0
  89. package/dist/core/host-identity-links.d.ts +54 -0
  90. package/dist/core/host-identity-links.js +189 -0
  91. package/dist/core/host-status.d.ts +61 -0
  92. package/dist/core/host-status.js +211 -0
  93. package/dist/core/http-runtime.js +3 -4
  94. package/dist/core/in-flight-runs.d.ts +8 -0
  95. package/dist/core/in-flight-runs.js +56 -0
  96. package/dist/core/lease.d.ts +1 -1
  97. package/dist/core/private-directory.d.ts +20 -0
  98. package/dist/core/private-directory.js +36 -0
  99. package/dist/core/provider-wording.d.ts +12 -0
  100. package/dist/core/provider-wording.js +44 -0
  101. package/dist/core/reconciler.d.ts +39 -3
  102. package/dist/core/reconciler.js +86 -2
  103. package/dist/core/repo-access.d.ts +99 -0
  104. package/dist/core/repo-access.js +136 -0
  105. package/dist/core/review-host-checks.d.ts +11 -0
  106. package/dist/core/review-host-checks.js +41 -0
  107. package/dist/core/review-host-tools.d.ts +47 -0
  108. package/dist/core/review-host-tools.js +347 -0
  109. package/dist/core/run-heartbeat.d.ts +27 -0
  110. package/dist/core/run-heartbeat.js +54 -0
  111. package/dist/core/runner.d.ts +31 -9
  112. package/dist/core/runner.js +237 -40
  113. package/dist/core/scheduler.d.ts +1 -1
  114. package/dist/core/secrets.d.ts +12 -3
  115. package/dist/core/secrets.js +25 -6
  116. package/dist/core/subagent-memory-tools.d.ts +2 -2
  117. package/dist/core/subagent-memory-tools.js +16 -3
  118. package/dist/core/tool-admin.d.ts +81 -0
  119. package/dist/core/tool-admin.js +129 -0
  120. package/dist/core/tool-names.d.ts +42 -0
  121. package/dist/core/tool-names.js +64 -0
  122. package/dist/core/untrusted-content.d.ts +32 -0
  123. package/dist/core/untrusted-content.js +72 -0
  124. package/dist/core/webhooks.d.ts +9 -2
  125. package/dist/core/webhooks.js +23 -2
  126. package/dist/generated/prisma/browser.d.ts +328 -0
  127. package/dist/generated/prisma/browser.js +17 -0
  128. package/dist/generated/prisma/client.d.ts +347 -0
  129. package/dist/generated/prisma/client.js +34 -0
  130. package/dist/generated/prisma/commonInputTypes.d.ts +839 -0
  131. package/dist/generated/prisma/commonInputTypes.js +10 -0
  132. package/dist/generated/prisma/enums.d.ts +59 -0
  133. package/dist/generated/prisma/enums.js +59 -0
  134. package/dist/generated/prisma/internal/class.d.ts +605 -0
  135. package/dist/generated/prisma/internal/class.js +49 -0
  136. package/dist/generated/prisma/internal/prismaNamespace.d.ts +4494 -0
  137. package/dist/generated/prisma/internal/prismaNamespace.js +628 -0
  138. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +635 -0
  139. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +599 -0
  140. package/dist/generated/prisma/models/Agent.d.ts +3560 -0
  141. package/dist/generated/prisma/models/Agent.js +1 -0
  142. package/dist/generated/prisma/models/AgentDatastore.d.ts +1210 -0
  143. package/dist/generated/prisma/models/AgentDatastore.js +1 -0
  144. package/dist/generated/prisma/models/AgentMemory.d.ts +986 -0
  145. package/dist/generated/prisma/models/AgentMemory.js +1 -0
  146. package/dist/generated/prisma/models/AgentRepository.d.ts +1425 -0
  147. package/dist/generated/prisma/models/AgentRepository.js +1 -0
  148. package/dist/generated/prisma/models/AgentSecret.d.ts +1212 -0
  149. package/dist/generated/prisma/models/AgentSecret.js +1 -0
  150. package/dist/generated/prisma/models/AgentSubAgent.d.ts +1217 -0
  151. package/dist/generated/prisma/models/AgentSubAgent.js +1 -0
  152. package/dist/generated/prisma/models/AgentTool.d.ts +1403 -0
  153. package/dist/generated/prisma/models/AgentTool.js +1 -0
  154. package/dist/generated/prisma/models/AuthFormChallenge.d.ts +1298 -0
  155. package/dist/generated/prisma/models/AuthFormChallenge.js +1 -0
  156. package/dist/generated/prisma/models/AuthLoginKey.d.ts +1245 -0
  157. package/dist/generated/prisma/models/AuthLoginKey.js +1 -0
  158. package/dist/generated/prisma/models/AuthRateLimit.d.ts +979 -0
  159. package/dist/generated/prisma/models/AuthRateLimit.js +1 -0
  160. package/dist/generated/prisma/models/AuthSession.d.ts +1376 -0
  161. package/dist/generated/prisma/models/AuthSession.js +1 -0
  162. package/dist/generated/prisma/models/AuthUser.d.ts +1700 -0
  163. package/dist/generated/prisma/models/AuthUser.js +1 -0
  164. package/dist/generated/prisma/models/BudgetGroup.d.ts +1548 -0
  165. package/dist/generated/prisma/models/BudgetGroup.js +1 -0
  166. package/dist/generated/prisma/models/CodingAgentProfile.d.ts +1681 -0
  167. package/dist/generated/prisma/models/CodingAgentProfile.js +1 -0
  168. package/dist/generated/prisma/models/CodingProxyRequest.d.ts +1749 -0
  169. package/dist/generated/prisma/models/CodingProxyRequest.js +1 -0
  170. package/dist/generated/prisma/models/CodingProxySession.d.ts +1626 -0
  171. package/dist/generated/prisma/models/CodingProxySession.js +1 -0
  172. package/dist/generated/prisma/models/CodingRun.d.ts +3583 -0
  173. package/dist/generated/prisma/models/CodingRun.js +1 -0
  174. package/dist/generated/prisma/models/CodingService.d.ts +1348 -0
  175. package/dist/generated/prisma/models/CodingService.js +1 -0
  176. package/dist/generated/prisma/models/Datastore.d.ts +1434 -0
  177. package/dist/generated/prisma/models/Datastore.js +1 -0
  178. package/dist/generated/prisma/models/DatastoreEntry.d.ts +1314 -0
  179. package/dist/generated/prisma/models/DatastoreEntry.js +1 -0
  180. package/dist/generated/prisma/models/HostEventDelivery.d.ts +946 -0
  181. package/dist/generated/prisma/models/HostEventDelivery.js +1 -0
  182. package/dist/generated/prisma/models/HostIdentity.d.ts +1232 -0
  183. package/dist/generated/prisma/models/HostIdentity.js +1 -0
  184. package/dist/generated/prisma/models/HostIdentityLinkRequest.d.ts +1473 -0
  185. package/dist/generated/prisma/models/HostIdentityLinkRequest.js +1 -0
  186. package/dist/generated/prisma/models/OAuthAuthorizationCode.d.ts +1526 -0
  187. package/dist/generated/prisma/models/OAuthAuthorizationCode.js +1 -0
  188. package/dist/generated/prisma/models/OAuthAuthorizationRequest.d.ts +1350 -0
  189. package/dist/generated/prisma/models/OAuthAuthorizationRequest.js +1 -0
  190. package/dist/generated/prisma/models/OAuthClient.d.ts +1289 -0
  191. package/dist/generated/prisma/models/OAuthClient.js +1 -0
  192. package/dist/generated/prisma/models/OAuthFamily.d.ts +1540 -0
  193. package/dist/generated/prisma/models/OAuthFamily.js +1 -0
  194. package/dist/generated/prisma/models/OAuthGrant.d.ts +1280 -0
  195. package/dist/generated/prisma/models/OAuthGrant.js +1 -0
  196. package/dist/generated/prisma/models/Principal.d.ts +2245 -0
  197. package/dist/generated/prisma/models/Principal.js +1 -0
  198. package/dist/generated/prisma/models/RegistryAllowance.d.ts +1148 -0
  199. package/dist/generated/prisma/models/RegistryAllowance.js +1 -0
  200. package/dist/generated/prisma/models/RegistryApprovedVersion.d.ts +1219 -0
  201. package/dist/generated/prisma/models/RegistryApprovedVersion.js +1 -0
  202. package/dist/generated/prisma/models/RegistryFetch.d.ts +1428 -0
  203. package/dist/generated/prisma/models/RegistryFetch.js +1 -0
  204. package/dist/generated/prisma/models/RegistryPlanRefusal.d.ts +1294 -0
  205. package/dist/generated/prisma/models/RegistryPlanRefusal.js +1 -0
  206. package/dist/generated/prisma/models/RegistryVersionFact.d.ts +1085 -0
  207. package/dist/generated/prisma/models/RegistryVersionFact.js +1 -0
  208. package/dist/generated/prisma/models/ResourceGrant.d.ts +1437 -0
  209. package/dist/generated/prisma/models/ResourceGrant.js +1 -0
  210. package/dist/generated/prisma/models/Run.d.ts +2669 -0
  211. package/dist/generated/prisma/models/Run.js +1 -0
  212. package/dist/generated/prisma/models/RunHostCheck.d.ts +1239 -0
  213. package/dist/generated/prisma/models/RunHostCheck.js +1 -0
  214. package/dist/generated/prisma/models/RunHostStatus.d.ts +1315 -0
  215. package/dist/generated/prisma/models/RunHostStatus.js +1 -0
  216. package/dist/generated/prisma/models/SchedulerLease.d.ts +970 -0
  217. package/dist/generated/prisma/models/SchedulerLease.js +1 -0
  218. package/dist/generated/prisma/models/Secret.d.ts +1399 -0
  219. package/dist/generated/prisma/models/Secret.js +1 -0
  220. package/dist/generated/prisma/models/SecretElicitationOutcome.d.ts +973 -0
  221. package/dist/generated/prisma/models/SecretElicitationOutcome.js +1 -0
  222. package/dist/generated/prisma/models/Task.d.ts +1176 -0
  223. package/dist/generated/prisma/models/Task.js +1 -0
  224. package/dist/generated/prisma/models/Tool.d.ts +1490 -0
  225. package/dist/generated/prisma/models/Tool.js +1 -0
  226. package/dist/generated/prisma/models/Webhook.d.ts +1386 -0
  227. package/dist/generated/prisma/models/Webhook.js +1 -0
  228. package/dist/generated/prisma/models.d.ts +45 -0
  229. package/dist/generated/prisma/models.js +1 -0
  230. package/dist/help/build.d.ts +1 -0
  231. package/dist/help/build.js +9 -0
  232. package/dist/help/catalog.d.ts +24 -0
  233. package/dist/help/catalog.js +160 -0
  234. package/dist/help/cli.d.ts +2 -0
  235. package/dist/help/cli.js +65 -0
  236. package/dist/help/runtime.d.ts +3 -0
  237. package/dist/help/runtime.js +44 -0
  238. package/dist/help/search.d.ts +9 -0
  239. package/dist/help/search.js +104 -0
  240. package/dist/help-index.json +660 -0
  241. package/dist/import/cli-args.js +3 -2
  242. package/dist/import/create.d.ts +6 -1
  243. package/dist/import/create.js +68 -14
  244. package/dist/import/index.d.ts +1 -1
  245. package/dist/import/index.js +19 -9
  246. package/dist/import/neutral-schema.d.ts +11 -11
  247. package/dist/mcp/auth/access.d.ts +70 -0
  248. package/dist/mcp/auth/access.js +58 -0
  249. package/dist/mcp/auth/grants-cli.d.ts +149 -0
  250. package/dist/mcp/auth/grants-cli.js +518 -0
  251. package/dist/mcp/auth/host-account-cli.d.ts +2 -0
  252. package/dist/mcp/auth/host-account-cli.js +47 -0
  253. package/dist/mcp/auth/ownership.d.ts +37 -63
  254. package/dist/mcp/auth/ownership.js +19 -14
  255. package/dist/mcp/auth/principal.d.ts +1 -1
  256. package/dist/mcp/auth/repo-authorization.d.ts +22 -0
  257. package/dist/mcp/auth/repo-authorization.js +51 -0
  258. package/dist/mcp/auth/resource-server.d.ts +32 -3
  259. package/dist/mcp/auth/resource-server.js +84 -3
  260. package/dist/mcp/auth/self-hosted/browser.js +22 -8
  261. package/dist/mcp/auth/self-hosted/cli.d.ts +1 -1
  262. package/dist/mcp/auth/self-hosted/cli.js +47 -13
  263. package/dist/mcp/auth/self-hosted/credentials.d.ts +21 -3
  264. package/dist/mcp/auth/self-hosted/credentials.js +62 -4
  265. package/dist/mcp/auth/self-hosted/rate-limit.d.ts +1 -1
  266. package/dist/mcp/auth/self-hosted/session.d.ts +3 -2
  267. package/dist/mcp/context.d.ts +28 -2
  268. package/dist/mcp/errors.d.ts +25 -6
  269. package/dist/mcp/errors.js +98 -0
  270. package/dist/mcp/host-events/github-ingress.d.ts +36 -0
  271. package/dist/mcp/host-events/github-ingress.js +92 -0
  272. package/dist/mcp/host-events/github-user-callback.d.ts +18 -0
  273. package/dist/mcp/host-events/github-user-callback.js +50 -0
  274. package/dist/mcp/index.d.ts +8 -1
  275. package/dist/mcp/index.js +106 -16
  276. package/dist/mcp/server.d.ts +1 -1
  277. package/dist/mcp/server.js +27 -9
  278. package/dist/mcp/shared/page-style.d.ts +16 -0
  279. package/dist/mcp/shared/page-style.js +129 -0
  280. package/dist/mcp/tasks/manager.d.ts +1 -1
  281. package/dist/mcp/tools/agents.js +447 -49
  282. package/dist/mcp/tools/budget-groups.js +3 -3
  283. package/dist/mcp/tools/datastore.js +23 -16
  284. package/dist/mcp/tools/grants.d.ts +2 -0
  285. package/dist/mcp/tools/grants.js +239 -0
  286. package/dist/mcp/tools/help.d.ts +5 -0
  287. package/dist/mcp/tools/help.js +67 -0
  288. package/dist/mcp/tools/host-accounts.d.ts +2 -0
  289. package/dist/mcp/tools/host-accounts.js +114 -0
  290. package/dist/mcp/tools/memory.d.ts +7 -1
  291. package/dist/mcp/tools/memory.js +5 -5
  292. package/dist/mcp/tools/repositories.d.ts +2 -0
  293. package/dist/mcp/tools/repositories.js +181 -0
  294. package/dist/mcp/tools/runs.d.ts +6 -0
  295. package/dist/mcp/tools/runs.js +60 -6
  296. package/dist/mcp/tools/scheduling.js +4 -7
  297. package/dist/mcp/tools/secret-elicitation-form.d.ts +1 -1
  298. package/dist/mcp/tools/secret-elicitation-form.js +10 -28
  299. package/dist/mcp/tools/secret-elicitation.d.ts +1 -1
  300. package/dist/mcp/tools/secrets.js +18 -6
  301. package/dist/mcp/tools/services.d.ts +2 -0
  302. package/dist/mcp/tools/services.js +222 -0
  303. package/dist/mcp/tools/subagents.js +53 -17
  304. package/dist/mcp/tools/tools.d.ts +41 -0
  305. package/dist/mcp/tools/tools.js +202 -41
  306. package/dist/mcp/tools/trigger.js +36 -6
  307. package/dist/mcp/tools/webhooks.js +15 -4
  308. package/dist/mcp/transport/streamable-http.d.ts +11 -1
  309. package/dist/mcp/transport/streamable-http.js +32 -2
  310. package/dist/mcp/unattended-schedules.d.ts +1 -1
  311. package/dist/mcp/webhooks/ingress.d.ts +1 -1
  312. package/dist/providers/auth/delegating.d.ts +19 -0
  313. package/dist/providers/auth/delegating.js +71 -0
  314. package/dist/providers/auth/index.d.ts +3 -1
  315. package/dist/providers/auth/index.js +11 -0
  316. package/dist/providers/auth/self-hosted.d.ts +4 -3
  317. package/dist/providers/auth/self-hosted.js +16 -4
  318. package/dist/providers/auth/types.d.ts +6 -0
  319. package/dist/providers/coding-proxy/memory-ledger.d.ts +3 -0
  320. package/dist/providers/coding-proxy/memory-ledger.js +21 -2
  321. package/dist/providers/coding-proxy/metering.d.ts +4 -0
  322. package/dist/providers/coding-proxy/metering.js +28 -2
  323. package/dist/providers/coding-proxy/prisma-ledger.d.ts +18 -1
  324. package/dist/providers/coding-proxy/prisma-ledger.js +50 -5
  325. package/dist/providers/coding-proxy/proxy.d.ts +13 -0
  326. package/dist/providers/coding-proxy/proxy.js +479 -18
  327. package/dist/providers/coding-proxy/registry/audit.d.ts +131 -0
  328. package/dist/providers/coding-proxy/registry/audit.js +380 -0
  329. package/dist/providers/coding-proxy/registry/bounded-fetch.d.ts +16 -0
  330. package/dist/providers/coding-proxy/registry/bounded-fetch.js +77 -0
  331. package/dist/providers/coding-proxy/registry/plan.d.ts +58 -0
  332. package/dist/providers/coding-proxy/registry/plan.js +304 -0
  333. package/dist/providers/coding-proxy/registry/prisma-store.d.ts +34 -0
  334. package/dist/providers/coding-proxy/registry/prisma-store.js +151 -0
  335. package/dist/providers/coding-proxy/registry/service.d.ts +213 -0
  336. package/dist/providers/coding-proxy/registry/service.js +1137 -0
  337. package/dist/providers/coding-proxy/registry/store.d.ts +127 -0
  338. package/dist/providers/coding-proxy/registry/store.js +70 -0
  339. package/dist/providers/coding-proxy/runtime.d.ts +18 -1
  340. package/dist/providers/coding-proxy/runtime.js +63 -0
  341. package/dist/providers/coding-proxy/secure-fetch.js +0 -1
  342. package/dist/providers/coding-proxy/server.d.ts +11 -0
  343. package/dist/providers/coding-proxy/server.js +163 -0
  344. package/dist/providers/coding-proxy/types.d.ts +15 -0
  345. package/dist/providers/datastore/postgres.d.ts +1 -1
  346. package/dist/providers/engine/types.d.ts +10 -0
  347. package/dist/providers/executor/build.d.ts +3 -3
  348. package/dist/providers/executor/composition.d.ts +4 -1
  349. package/dist/providers/executor/composition.js +19 -0
  350. package/dist/providers/executor/container.d.ts +106 -4
  351. package/dist/providers/executor/container.js +305 -22
  352. package/dist/providers/executor/dbos.d.ts +3 -4
  353. package/dist/providers/executor/in-process.d.ts +2 -3
  354. package/dist/providers/executor/routing.d.ts +7 -0
  355. package/dist/providers/executor/routing.js +13 -0
  356. package/dist/providers/executor/types.d.ts +22 -0
  357. package/dist/providers/jobs/claude-tool-setup.d.ts +23 -0
  358. package/dist/providers/jobs/claude-tool-setup.js +50 -0
  359. package/dist/providers/jobs/collect-prune.d.ts +7 -0
  360. package/dist/providers/jobs/collect-prune.js +27 -0
  361. package/dist/providers/jobs/docker-isolation.d.ts +48 -3
  362. package/dist/providers/jobs/docker-isolation.js +213 -22
  363. package/dist/providers/jobs/docker-services.d.ts +35 -0
  364. package/dist/providers/jobs/docker-services.js +191 -0
  365. package/dist/providers/jobs/docker.d.ts +60 -2
  366. package/dist/providers/jobs/docker.js +313 -50
  367. package/dist/providers/jobs/fake-kubernetes-api.d.ts +1 -0
  368. package/dist/providers/jobs/fake-kubernetes-api.js +12 -3
  369. package/dist/providers/jobs/kubernetes-isolation.d.ts +17 -2
  370. package/dist/providers/jobs/kubernetes-isolation.js +238 -57
  371. package/dist/providers/jobs/kubernetes-platform.d.ts +10 -4
  372. package/dist/providers/jobs/kubernetes-platform.js +11 -5
  373. package/dist/providers/jobs/kubernetes-preflight.js +3 -0
  374. package/dist/providers/jobs/kubernetes.d.ts +21 -2
  375. package/dist/providers/jobs/kubernetes.js +117 -31
  376. package/dist/providers/jobs/types.d.ts +20 -0
  377. package/dist/providers/llm/anthropic.js +6 -2
  378. package/dist/providers/llm/bedrock.js +2 -1
  379. package/dist/providers/llm/claude-messages.d.ts +5 -1
  380. package/dist/providers/llm/claude-messages.js +1 -0
  381. package/dist/providers/llm/claude-provider.d.ts +3 -1
  382. package/dist/providers/llm/claude-provider.js +5 -1
  383. package/dist/providers/llm/index.d.ts +1 -1
  384. package/dist/providers/llm/index.js +1 -1
  385. package/dist/providers/llm/pricing-anthropic.d.ts +10 -0
  386. package/dist/providers/llm/pricing-anthropic.js +32 -14
  387. package/dist/providers/llm/pricing-bedrock-claude.d.ts +8 -0
  388. package/dist/providers/llm/pricing-bedrock-claude.js +9 -0
  389. package/dist/providers/llm/routing.d.ts +10 -1
  390. package/dist/providers/llm/routing.js +18 -0
  391. package/dist/providers/llm/types.d.ts +12 -0
  392. package/dist/providers/llm/types.js +8 -1
  393. package/dist/providers/memory/postgres.d.ts +1 -1
  394. package/dist/providers/review-host/diff-lines.d.ts +16 -0
  395. package/dist/providers/review-host/diff-lines.js +59 -0
  396. package/dist/providers/review-host/github-events.d.ts +6 -0
  397. package/dist/providers/review-host/github-events.js +180 -0
  398. package/dist/providers/review-host/github-user-auth.d.ts +38 -0
  399. package/dist/providers/review-host/github-user-auth.js +128 -0
  400. package/dist/providers/review-host/github.d.ts +57 -0
  401. package/dist/providers/review-host/github.js +567 -0
  402. package/dist/providers/review-host/index.d.ts +12 -0
  403. package/dist/providers/review-host/index.js +30 -0
  404. package/dist/providers/review-host/review-format.d.ts +19 -0
  405. package/dist/providers/review-host/review-format.js +59 -0
  406. package/dist/providers/review-host/types.d.ts +284 -0
  407. package/dist/providers/review-host/types.js +24 -0
  408. package/dist/providers/vcs/git.d.ts +16 -6
  409. package/dist/providers/vcs/git.js +79 -14
  410. package/dist/providers/vcs/github.d.ts +74 -3
  411. package/dist/providers/vcs/github.js +195 -15
  412. package/dist/providers/vcs/types.d.ts +42 -4
  413. package/dist/quickstart/index.js +34 -32
  414. package/dist/quickstart/migrate.d.ts +23 -0
  415. package/dist/quickstart/migrate.js +125 -0
  416. package/dist/sandbox/fetch-policy.d.ts +20 -2
  417. package/dist/sandbox/fetch-policy.js +64 -3
  418. package/dist/sandbox/host-functions.d.ts +9 -1
  419. package/dist/sandbox/host-functions.js +12 -5
  420. package/dist/serve.js +1 -1
  421. package/dist/wardby-bin.js +6 -0
  422. package/docs/README.md +30 -0
  423. package/docs/architecture-runtime.md +90 -0
  424. package/docs/assets/brand/wardby-icon-512.png +0 -0
  425. package/docs/assets/brand/wardby-icon.svg +16 -0
  426. package/docs/assets/brand/wardby-mascot-profile-512.png +0 -0
  427. package/docs/assets/brand/wardby-mascot.png +0 -0
  428. package/docs/assets/brand/wardby-mascot.svg +5 -0
  429. package/docs/assets/wardby-workflow.svg +106 -0
  430. package/docs/code-review-agents.md +481 -0
  431. package/docs/coding-agent-setup.md +172 -0
  432. package/docs/coding-packages.md +455 -0
  433. package/docs/coding-services.md +300 -0
  434. package/docs/coding-worker-byo-images.md +98 -0
  435. package/docs/coding-worker-isolation.md +1061 -0
  436. package/docs/getting-started-gke.md +615 -0
  437. package/docs/getting-started-identity-provider.md +308 -0
  438. package/docs/getting-started.md +133 -0
  439. package/docs/observability.md +53 -0
  440. package/docs/release-verification.md +66 -0
  441. package/docs/security-deployment.md +657 -0
  442. package/help/code-review-agents.md +31 -0
  443. package/help/coding-packages.md +31 -0
  444. package/help/coding-services.md +71 -0
  445. package/help/creating-agents.md +68 -0
  446. package/help/deploy-gke.md +39 -0
  447. package/help/deployment-targets.md +39 -0
  448. package/help/errors/budget-group-exhausted.md +25 -0
  449. package/help/errors/docker-isolation-unsupported.md +26 -0
  450. package/help/errors/protected-path.md +45 -0
  451. package/help/errors/repo-access.md +27 -0
  452. package/help/errors/service-declaration-invalid.md +27 -0
  453. package/help/errors/service-declaration-unavailable.md +25 -0
  454. package/help/errors/service-launcher-unsupported.md +30 -0
  455. package/help/errors/service-not-allowed.md +27 -0
  456. package/help/errors/service-unknown.md +23 -0
  457. package/help/errors/service-unready.md +49 -0
  458. package/help/getting-started.md +31 -0
  459. package/help/github.md +30 -0
  460. package/help/identity-and-access.md +39 -0
  461. package/help/mcp.md +30 -0
  462. package/help/native-capabilities.md +30 -0
  463. package/help/observability.md +41 -0
  464. package/help/operating-agents.md +30 -0
  465. package/help/security.md +27 -0
  466. package/help/troubleshooting/budgets.md +26 -0
  467. package/help/troubleshooting/coding-workers.md +47 -0
  468. package/help/troubleshooting/repository-access.md +27 -0
  469. package/package.json +30 -11
  470. package/prisma/migrate.config.mjs +20 -0
  471. package/prisma/migrations/20260925010000_coding_collect_exclude/migration.sql +8 -0
  472. package/prisma/migrations/20260925015000_allowed_egress_default/migration.sql +8 -0
  473. package/prisma/migrations/20260925020000_coding_package_registry/migration.sql +55 -0
  474. package/prisma/migrations/20260925030000_tool_name_per_owner/migration.sql +11 -0
  475. package/prisma/migrations/20260926010000_run_trigger_host_event/migration.sql +7 -0
  476. package/prisma/migrations/20260926020000_code_review_hosts/migration.sql +53 -0
  477. package/prisma/migrations/20260926030000_auth_user_roles/migration.sql +9 -0
  478. package/prisma/migrations/20260926040000_agent_effort/migration.sql +6 -0
  479. package/prisma/migrations/20260926050000_registry_lockfile_plan/migration.sql +32 -0
  480. package/prisma/migrations/20260926050000_repo_access_authorization/migration.sql +82 -0
  481. package/prisma/migrations/20260926060000_registry_plan_refusal/migration.sql +19 -0
  482. package/prisma/migrations/20260926100000_registry_plan_refusal_published_at/migration.sql +5 -0
  483. package/prisma/migrations/20260926190000_run_host_status/migration.sql +18 -0
  484. package/prisma/migrations/20260926210000_run_host_status_at_dispatch/migration.sql +5 -0
  485. package/prisma/migrations/20260927010000_resource_grants/migration.sql +72 -0
  486. package/prisma/migrations/20260927020000_proxy_session_budget_exhausted/migration.sql +3 -0
  487. package/prisma/migrations/20260927030000_coding_debug_trace/migration.sql +4 -0
  488. package/prisma/migrations/20260927040000_proxy_session_upstream_failure/migration.sql +3 -0
  489. package/prisma/migrations/20260927050000_coding_run_services/migration.sql +74 -0
  490. package/prisma/migrations/20260928000000_coding_run_tool_image/migration.sql +5 -0
  491. package/prisma/schema.prisma +417 -23
@@ -0,0 +1,125 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { fileURLToPath } from "node:url";
3
+ // The installed npm package does not carry the Prisma CLI (a devDependency:
4
+ // its tree pulled advisories into every consumer install), so quickstart and
5
+ // doctor first look for a `prisma` executable on PATH and use it only if it
6
+ // reports exactly this version; anything else -- wrong version, not on PATH,
7
+ // a spawn error, a non-zero exit, or a timeout -- falls back to running the
8
+ // pinned version through npx, with a config file shipped in the package.
9
+ // Keep this equal to package.json's devDependencies.prisma (migrate.test.ts
10
+ // enforces it).
11
+ //
12
+ // Not supported on Windows: npx is `npx.cmd` there (and a local install would
13
+ // be `prisma.cmd`), which spawnSync can only run through a shell, and
14
+ // quickstart has no win32 handling anywhere else (Docker Compose paths,
15
+ // POSIX file modes) either.
16
+ export const PRISMA_CLI_VERSION = "7.10.0";
17
+ export const FETCH_NOTICE = `Fetching the Prisma CLI (prisma@${PRISMA_CLI_VERSION})…`;
18
+ export const REGISTRY_FAILURE_MESSAGE = `Could not download the Prisma CLI (prisma@${PRISMA_CLI_VERSION}) from the npm registry. ` +
19
+ "quickstart and doctor fetch it on demand. Check your internet connection or npm registry settings " +
20
+ "(.npmrc, proxy), then retry. You can also install it yourself " +
21
+ `(\`npm i -g prisma@${PRISMA_CLI_VERSION}\`) and rerun quickstart -- doctor benefits too.`;
22
+ export const migrateConfigFile = fileURLToPath(new URL("../../prisma/migrate.config.mjs", import.meta.url));
23
+ // A short timeout so a wedged or misbehaving local binary can't stall
24
+ // quickstart/doctor -- any hang past this falls back to npx.
25
+ const LOCAL_PRISMA_PROBE_TIMEOUT_MS = 5000;
26
+ // `prisma --version` prints a fixed-label, padded-column report, e.g.:
27
+ // prisma : 7.10.0
28
+ // @prisma/client : 7.10.0
29
+ // Operating System : darwin
30
+ // ...
31
+ // Several other lines carry their own version-shaped numbers (Node.js,
32
+ // TypeScript, the schema engine hash), so key off the "prisma" label
33
+ // specifically -- not the first x.y.z token anywhere in the output.
34
+ const LOCAL_VERSION_LINE = /^prisma\s*:\s*(\S+)$/m;
35
+ export function parseLocalPrismaVersion(stdout) {
36
+ return LOCAL_VERSION_LINE.exec(stdout)?.[1];
37
+ }
38
+ // Runs a local `prisma --version` under the given env's PATH and returns the
39
+ // version it reports, or undefined for anything that isn't a clean match:
40
+ // not on PATH, a spawn error, a non-zero exit, a timeout, or unparseable
41
+ // output. Never throws.
42
+ export function probeLocalPrismaVersion(env) {
43
+ const result = spawnSync("prisma", ["--version"], {
44
+ env,
45
+ encoding: "utf8",
46
+ stdio: "pipe",
47
+ timeout: LOCAL_PRISMA_PROBE_TIMEOUT_MS,
48
+ });
49
+ if (result.error || result.status !== 0)
50
+ return undefined;
51
+ return parseLocalPrismaVersion(result.stdout ?? "");
52
+ }
53
+ // DATABASE_URL travels in the child's environment only -- never argv, where
54
+ // any local user could read it from the process table. `probe` is injectable
55
+ // so tests never depend on the developer's real PATH.
56
+ export function prismaMigrateInvocation(subcommand, env, probe = probeLocalPrismaVersion) {
57
+ const childEnv = {
58
+ ...env,
59
+ PRISMA_HIDE_UPDATE_MESSAGE: "1",
60
+ npm_config_update_notifier: "false",
61
+ npm_config_fund: "false",
62
+ };
63
+ if (probe(childEnv) === PRISMA_CLI_VERSION) {
64
+ return {
65
+ command: "prisma",
66
+ args: ["migrate", subcommand, "--config", migrateConfigFile],
67
+ env: childEnv,
68
+ local: true,
69
+ };
70
+ }
71
+ return {
72
+ command: "npx",
73
+ args: ["--yes", `prisma@${PRISMA_CLI_VERSION}`, "migrate", subcommand, "--config", migrateConfigFile],
74
+ env: childEnv,
75
+ local: false,
76
+ };
77
+ }
78
+ // npm's own error lines when it cannot fetch a package (npm 7+ prints
79
+ // "npm error", older "npm ERR!"): network failures and registry HTTP / version
80
+ // errors. A failed migration is printed by Prisma itself and never carries
81
+ // these -- in particular a database ECONNREFUSED is Prisma's P1001, not an
82
+ // "npm error code" line.
83
+ const REGISTRY_ERROR = /^npm (?:error|ERR!) (?:code|errno) (?:ENOTFOUND|ECONNREFUSED|ECONNRESET|ETIMEDOUT|EAI_AGAIN|ETARGET|E[45]\d\d)\b|^npm (?:error|ERR!) network /m;
84
+ // `local` marks that `result`/`spawnError` came from the locally installed
85
+ // binary rather than npx: nothing was fetched, so neither an npm-shaped
86
+ // error line nor a spawn error is a registry failure there -- a spawn error
87
+ // still gets a clear message (below), just not that one.
88
+ export function classifyMigrateResult(result, spawnError, local = false) {
89
+ if (spawnError) {
90
+ if (local)
91
+ return { ...result, status: 1, stderr: spawnError.message };
92
+ return { ...result, status: 1, stderr: spawnError.message, registryFailure: true };
93
+ }
94
+ if (!local && result.status !== 0 && REGISTRY_ERROR.test(result.stderr))
95
+ return { ...result, registryFailure: true };
96
+ return result;
97
+ }
98
+ // The one-line reason for a failed run: the registry message, or Prisma's own
99
+ // first error line (URLs masked, though Prisma prints host and database only).
100
+ export function migrateFailureReason(result) {
101
+ if (result.registryFailure)
102
+ return REGISTRY_FAILURE_MESSAGE;
103
+ const lines = `${result.stderr}\n${result.stdout}`
104
+ .split("\n")
105
+ .map((line) => line.trim())
106
+ .filter(Boolean);
107
+ // A line that starts with "Error", or that carries a Prisma error code anywhere.
108
+ const line = lines.find((candidate) => candidate.startsWith("Error") || /\bP\d{4}\b/.test(candidate)) ?? lines[0] ?? "no output";
109
+ return line.replace(/postgres(ql)?:\/\/[^\s`'"]+/gi, "<database url>");
110
+ }
111
+ // Runs an already-resolved invocation (from `prismaMigrateInvocation`). Split
112
+ // out so callers can decide whether to print `FETCH_NOTICE` -- which only
113
+ // applies to the npx path -- before running it.
114
+ export function runPrismaMigrateInvocation(invocation, cwd) {
115
+ const result = spawnSync(invocation.command, invocation.args, {
116
+ cwd,
117
+ env: invocation.env,
118
+ encoding: "utf8",
119
+ stdio: "pipe",
120
+ });
121
+ return classifyMigrateResult({ status: result.status ?? 1, stdout: result.stdout ?? "", stderr: result.stderr ?? "" }, result.error, invocation.local);
122
+ }
123
+ export function runPrismaMigrate(subcommand, env, cwd, probe) {
124
+ return runPrismaMigrateInvocation(prismaMigrateInvocation(subcommand, env, probe), cwd);
125
+ }
@@ -4,15 +4,33 @@ export type ResolvedAddress = {
4
4
  };
5
5
  export type Resolver = (hostname: string) => Promise<ResolvedAddress[]>;
6
6
  export interface FetchPolicyOptions {
7
+ /**
8
+ * Egress allowlist, enforced only with `restrictToAllowedHosts`. It can only
9
+ * NARROW where fetch may go: a listed host must still resolve exclusively to
10
+ * global addresses. Tool-controlled lists belong here.
11
+ */
7
12
  allowedHosts?: string[];
8
- allowPrivateHosts?: boolean;
9
- resolve?: Resolver;
10
13
  restrictToAllowedHosts?: boolean;
14
+ /**
15
+ * Hosts that may resolve to a non-global (private/loopback/link-local)
16
+ * address. Operator-controlled ONLY (WARDBY_FETCH_ALLOWED_HOSTS) — never
17
+ * populate it from a tool's or caller's own capability list. Cloud metadata
18
+ * endpoints stay blocked even when listed here.
19
+ */
20
+ privateHostAllowlist?: string[];
21
+ resolve?: Resolver;
11
22
  }
12
23
  export declare class FetchPolicyError extends Error {
13
24
  constructor();
14
25
  }
15
26
  export declare function isGlobalAddress(address: string): boolean;
27
+ /**
28
+ * True for a cloud metadata address, including IPv6 encodings of the IPv4
29
+ * ones: IPv4-mapped/-compatible (::ffff:0:0/96, ::/96), NAT64 well-known
30
+ * (64:ff9b::/96), RFC 8215 local-use NAT64 (64:ff9b:1::/48, /96 and RFC 6052
31
+ * /48 layouts) and 6to4 (2002::/16). A zone id is ignored for the comparison.
32
+ */
33
+ export declare function isCloudMetadataAddress(address: string): boolean;
16
34
  export declare function normalizeHost(host: string): string;
17
35
  export declare function parseAllowedHosts(value: string | undefined): string[];
18
36
  export declare function resolveDestination(urlString: string, options?: FetchPolicyOptions): Promise<{
@@ -33,6 +33,9 @@ function v6(ip) {
33
33
  return groups.reduce((n, g) => (n << 16n) + BigInt("0x" + g), 0n);
34
34
  }
35
35
  export function isGlobalAddress(address) {
36
+ // Zone-scoped (fe80::1%eth0) answers are link-local by nature; never global.
37
+ if (address.includes("%"))
38
+ return false;
36
39
  if (isIP(address) === 4)
37
40
  return !blockedV4.some(([net, prefix]) => v4(address) >>> (32 - prefix) === v4(net) >>> (32 - prefix));
38
41
  if (isIP(address) !== 6)
@@ -51,9 +54,57 @@ export function isGlobalAddress(address) {
51
54
  return n >> shift === v6(net) >> shift;
52
55
  }));
53
56
  }
57
+ /**
58
+ * Cloud instance-metadata endpoints: GCP/AWS/Azure IMDS, GKE metadata proxy,
59
+ * ECS task credentials, EKS Pod Identity, Alibaba Cloud; plus the AWS, EKS and
60
+ * GCE IPv6 addresses.
61
+ */
62
+ const METADATA_V4 = new Set([
63
+ "169.254.169.254",
64
+ "169.254.169.252",
65
+ "169.254.170.2",
66
+ "169.254.170.23",
67
+ "100.100.100.200",
68
+ ]);
69
+ const METADATA_V6 = new Set(["fd00:ec2::254", "fd00:ec2::23", "fd20:ce::254"].map(v6));
70
+ const METADATA_HOSTNAMES = new Set(["metadata.google.internal", "metadata"]);
71
+ const NAT64_WELL_KNOWN = v6("64:ff9b::") >> 32n;
72
+ const NAT64_LOCAL_USE = v6("64:ff9b:1::") >> 80n;
73
+ const SIX_TO_FOUR = 0x2002n;
74
+ function v4FromBits(bits) {
75
+ const n = Number(bits & 0xffffffffn);
76
+ return [24, 16, 8, 0].map((shift) => (n >>> shift) & 0xff).join(".");
77
+ }
78
+ /**
79
+ * True for a cloud metadata address, including IPv6 encodings of the IPv4
80
+ * ones: IPv4-mapped/-compatible (::ffff:0:0/96, ::/96), NAT64 well-known
81
+ * (64:ff9b::/96), RFC 8215 local-use NAT64 (64:ff9b:1::/48, /96 and RFC 6052
82
+ * /48 layouts) and 6to4 (2002::/16). A zone id is ignored for the comparison.
83
+ */
84
+ export function isCloudMetadataAddress(address) {
85
+ const bare = address.split("%")[0];
86
+ if (isIP(bare) === 4)
87
+ return METADATA_V4.has(bare);
88
+ if (isIP(bare) !== 6)
89
+ return false;
90
+ const n = v6(new URL("http://[" + bare + "]").hostname.slice(1, -1));
91
+ if (METADATA_V6.has(n))
92
+ return true;
93
+ const embedded = [];
94
+ const high96 = n >> 32n;
95
+ if (high96 === 0n || high96 === 0xffffn || high96 === NAT64_WELL_KNOWN)
96
+ embedded.push(n);
97
+ if (n >> 80n === NAT64_LOCAL_USE)
98
+ embedded.push(n, (((n >> 64n) & 0xffffn) << 16n) | ((n >> 40n) & 0xffffn));
99
+ if (n >> 112n === SIX_TO_FOUR)
100
+ embedded.push(n >> 80n);
101
+ return embedded.some((bits) => METADATA_V4.has(v4FromBits(bits)));
102
+ }
54
103
  export function normalizeHost(host) {
55
104
  if (!host || /[\s*/@?#\\]/.test(host))
56
105
  throw new FetchPolicyError();
106
+ if (host.startsWith("[") !== host.endsWith("]"))
107
+ throw new FetchPolicyError();
57
108
  const value = host.startsWith("[") ? host.slice(1, -1) : host;
58
109
  if (isIP(value))
59
110
  return new URL(isIP(value) === 6 ? "http://[" + value + "]" : "http://" + value).hostname
@@ -61,7 +112,10 @@ export function normalizeHost(host) {
61
112
  .toLowerCase();
62
113
  if (host.includes(":"))
63
114
  throw new FetchPolicyError();
64
- return new URL("http://" + host).hostname.toLowerCase().replace(/\.$/, "");
115
+ const name = new URL("http://" + host).hostname.toLowerCase().replace(/\.+$/, "");
116
+ if (!name)
117
+ throw new FetchPolicyError();
118
+ return name;
65
119
  }
66
120
  export function parseAllowedHosts(value) {
67
121
  return value === undefined || value === "" ? [] : value.split(",").map((host) => normalizeHost(host.trim()));
@@ -77,17 +131,24 @@ export async function resolveDestination(urlString, options = {}) {
77
131
  if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || urlString.length > 8192)
78
132
  throw new FetchPolicyError();
79
133
  const hostname = normalizeHost(url.hostname);
134
+ if (METADATA_HOSTNAMES.has(hostname))
135
+ throw new FetchPolicyError();
80
136
  const allowed = (options.allowedHosts ?? []).map(normalizeHost).includes(hostname);
81
137
  if (options.restrictToAllowedHosts && !allowed)
82
138
  throw new FetchPolicyError();
139
+ // Only the operator list may open a non-global destination; a restricted
140
+ // caller additionally needs the host in its own list (checked just above).
141
+ const privateAllowed = (options.privateHostAllowlist ?? []).map(normalizeHost).includes(hostname);
83
142
  const version = isIP(hostname);
84
143
  const addresses = version
85
144
  ? [{ address: hostname, family: version }]
86
145
  : await (options.resolve ?? ((host) => lookup(host, { all: true })))(hostname);
87
146
  if (!addresses.length ||
88
- addresses.some((a) => !isIP(a.address) ||
147
+ addresses.some((a) => a.address.includes("%") ||
148
+ !isIP(a.address) ||
89
149
  isIP(a.address) !== a.family ||
90
- (!isGlobalAddress(a.address) && !(allowed && options.allowPrivateHosts !== false))))
150
+ isCloudMetadataAddress(a.address) ||
151
+ (!isGlobalAddress(a.address) && !privateAllowed)))
91
152
  throw new FetchPolicyError();
92
153
  return { url, hostname, address: addresses[0].address, family: addresses[0].family };
93
154
  }
@@ -8,8 +8,16 @@ import type { QuickJSContext, QuickJSRuntime } from "quickjs-emscripten";
8
8
  import type { Datastore } from "../providers/datastore/types.js";
9
9
  import type { SecretsAccessor } from "../core/secrets.js";
10
10
  import type { SharedDatastoreAccessor } from "../core/datastores.js";
11
+ import { type FetchPolicyOptions } from "./fetch-policy.js";
11
12
  import { type Logger } from "../core/logger.js";
12
13
  import { type ParserWorkerPool } from "./parser-worker/pool.js";
14
+ /**
15
+ * Fetch policy for one tool invocation. The tool's own host list only narrows
16
+ * egress; private destinations open solely via the operator's
17
+ * WARDBY_FETCH_ALLOWED_HOSTS (and, for a non-wildcard tool, only when the tool
18
+ * also lists the host). Cloud metadata stays blocked either way (fetch-policy.ts).
19
+ */
20
+ export declare function sandboxFetchPolicy(toolHosts: readonly string[], operatorHosts?: readonly string[]): FetchPolicyOptions;
13
21
  export interface HostFunctionOptions {
14
22
  signal?: AbortSignal;
15
23
  agentId: string;
@@ -23,7 +31,7 @@ export interface HostFunctionOptions {
23
31
  logger?: Logger;
24
32
  /** Overrides the shared default parser-worker pool — mainly for tests. */
25
33
  parserPool?: ParserWorkerPool;
26
- /** Hosts this specific tool attachment may fetch. Omitted/empty = no outbound fetch at all; a literal "*" element lifts the restriction (existing SSRF protection against private/link-local addresses still applies). */
34
+ /** Hosts this specific tool attachment may fetch. Omitted/empty = no outbound fetch at all; a literal "*" element lifts the restriction. Either way this list only narrows egress: private/link-local destinations need the operator's WARDBY_FETCH_ALLOWED_HOSTS, and cloud metadata is always blocked. */
27
35
  allowedFetchHosts?: string[];
28
36
  }
29
37
  export declare function installHostFunctions(context: QuickJSContext, runtime: QuickJSRuntime, options: HostFunctionOptions): void;
@@ -19,6 +19,17 @@ import { redactPii } from "./pii-redaction.js";
19
19
  const MIN_REDACTABLE_SECRET_LENGTH = 6;
20
20
  const FETCH_ALLOWED_HOSTS = parseAllowedHosts(process.env.WARDBY_FETCH_ALLOWED_HOSTS);
21
21
  let sharedParserPool;
22
+ /**
23
+ * Fetch policy for one tool invocation. The tool's own host list only narrows
24
+ * egress; private destinations open solely via the operator's
25
+ * WARDBY_FETCH_ALLOWED_HOSTS (and, for a non-wildcard tool, only when the tool
26
+ * also lists the host). Cloud metadata stays blocked either way (fetch-policy.ts).
27
+ */
28
+ export function sandboxFetchPolicy(toolHosts, operatorHosts = FETCH_ALLOWED_HOSTS) {
29
+ if (toolHosts.includes(FETCH_WILDCARD))
30
+ return { privateHostAllowlist: [...operatorHosts] };
31
+ return { allowedHosts: [...toolHosts], restrictToAllowedHosts: true, privateHostAllowlist: [...operatorHosts] };
32
+ }
22
33
  function args(argsJson) {
23
34
  return JSON.parse(argsJson);
24
35
  }
@@ -74,11 +85,7 @@ export function installHostFunctions(context, runtime, options) {
74
85
  });
75
86
  register("__bridge_fetch", async (argsJson) => {
76
87
  const [url, init] = args(argsJson);
77
- const hosts = allowedFetchHosts ?? [];
78
- if (hosts.includes(FETCH_WILDCARD)) {
79
- return safeFetch(url, init, { allowedHosts: FETCH_ALLOWED_HOSTS, signal });
80
- }
81
- return safeFetch(url, init, { allowedHosts: hosts, restrictToAllowedHosts: true, signal });
88
+ return safeFetch(url, init, { ...sandboxFetchPolicy(allowedFetchHosts ?? []), signal });
82
89
  });
83
90
  register("__bridge_datastoreGet", async (argsJson) => {
84
91
  const [key] = args(argsJson);
package/dist/serve.js CHANGED
@@ -33,7 +33,7 @@ export async function startServe(options = {}) {
33
33
  const concurrency = loadCodingConcurrencyConfig();
34
34
  const providers = options.providers ?? buildMcpProviders().providers;
35
35
  const mcp = await startMcp({ providers, schedulerAttached: true });
36
- const reconciler = startReconciler({ executor: providers.executor });
36
+ const reconciler = startReconciler({ executor: providers.executor, reviewHosts: providers.reviewHosts });
37
37
  const scheduler = startScheduler({
38
38
  executor: providers.executor,
39
39
  scope: options.scope,
@@ -36,6 +36,12 @@ async function main() {
36
36
  const { downCommand } = await import("./quickstart/index.js");
37
37
  await downCommand(process.argv.slice(3));
38
38
  }
39
+ else if (command === "help") {
40
+ // Help is bundled and offline: avoid loading the runtime environment,
41
+ // database client, or HTTP runtime just to search documentation.
42
+ const { helpCommand } = await import("./help/cli.js");
43
+ await helpCommand(process.argv.slice(3));
44
+ }
39
45
  else {
40
46
  await import("./cli.js");
41
47
  }
package/docs/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # Documentation
2
+
3
+ - [Getting started](getting-started.md) runs Wardby locally from npm with
4
+ containerized PostgreSQL and a budgeted sample agent.
5
+ - [Getting started on GKE](getting-started-gke.md) covers the supported Google
6
+ Cloud deployment from project setup through verification and teardown.
7
+ - [Bring your own identity provider](getting-started-identity-provider.md)
8
+ configures an existing OAuth/OIDC provider to protect remote MCP access.
9
+ - [Runtime architecture](architecture-runtime.md) explains the control-plane,
10
+ provider, persistence, and execution boundaries.
11
+ - [Coding-agent setup](coding-agent-setup.md) configures the local trusted proxy
12
+ and isolated Codex or Claude Code workers.
13
+ - [Coding-worker isolation](coding-worker-isolation.md) documents the threat
14
+ model and enforced worker boundary.
15
+ - [Bring-your-own worker images](coding-worker-byo-images.md) explains how to
16
+ extend the reviewed worker image contract.
17
+ - [Installing packages in coding runs](coding-packages.md) covers the coding
18
+ package registry: allowlists, safeguards, limits, and error codes.
19
+ - [Services for coding runs](coding-services.md) gives coding runs fresh
20
+ PostgreSQL, Redis or MySQL instances declared in `.wardby/services.yaml`:
21
+ the catalog, per-agent permission, variables, and errors.
22
+ - [Observability](observability.md) covers Prometheus metrics, Grafana, cloud
23
+ collectors, retention, and production ownership.
24
+ - [Release verification](release-verification.md) lists the automated and live
25
+ checks for a release candidate.
26
+ - [Security deployment](security-deployment.md) covers authentication, secrets,
27
+ networking, migrations, recovery, and known limitations.
28
+
29
+ Historical implementation plans, dated evidence, and internal design records
30
+ are intentionally maintained outside the public repository.
@@ -0,0 +1,90 @@
1
+ # Runtime architecture — GitHub trigger path + observability
2
+
3
+ Two independent subsystems currently run side by side on the same host and
4
+ share only Postgres and a Docker Compose network: the `wardby mcp` process
5
+ (agents/runs/webhooks/MCP tools) and the local observability stack
6
+ (coding-proxy metrics → Prometheus → Grafana). Neither depends on the other;
7
+ restarting one has no effect on the other.
8
+
9
+ ```mermaid
10
+ flowchart TB
11
+ subgraph GH["GitHub — your-org/your-repo"]
12
+ Issue["Issue or comment that\nmentions your agent"]
13
+ Action["Actions workflow that\nPOSTs the task to wardby"]
14
+ PRout["Draft PR opened"]
15
+ Issue --> Action
16
+ end
17
+
18
+ Action -->|"POST /webhooks/:id\nx-webhook-secret"| Tunnel
19
+
20
+ subgraph Public["Public HTTPS"]
21
+ Tunnel["HTTPS tunnel (e.g. ngrok)\n<your-domain> -> :8080\n(TLS terminated here)"]
22
+ end
23
+
24
+ Tunnel --> MCP
25
+
26
+ subgraph Host["Your machine"]
27
+ subgraph MCPBox["wardby mcp (Node/tsx)\nMCP_TRANSPORT=http, self-hosted auth"]
28
+ MCP["/webhooks/:id - secret auth\n/mcp - OAuth auth (list_agents, trigger_agent, get_run, ...)\n/.well-known/oauth-protected-resource"]
29
+ end
30
+
31
+ MCP -->|"creates Run, dispatches via JobLauncher"| Worker
32
+ MCP <-->|"reads/writes"| PG[("local-postgres-1\nAgent, Run, Webhook,\nBudgetGroup, AgentMemory, Datastore")]
33
+
34
+ subgraph Docker["Docker"]
35
+ Worker["ephemeral coding worker\n(Codex / Claude Code)\none per Run"]
36
+ Proxy["wardby-coding-proxy\ncredential injection +\ntoken/cost metering\n:9464/metrics (private net only)"]
37
+ Worker -->|"OpenAI/Anthropic calls,\nproxied"| Proxy
38
+ Proxy -->|"usage/cost ledger"| PG
39
+ end
40
+
41
+ Worker -->|"push branch"| PRout
42
+
43
+ Proxy -->|"scraped"| Prom["local-prometheus-1\n127.0.0.1:9090 (loopback)\n24h retention"]
44
+ Prom -->|"query"| Graf["local-grafana-1\n127.0.0.1:3000 (loopback)\ndashboards: wardby-coding-proxy,\nwardby-coding-budget"]
45
+ end
46
+ ```
47
+
48
+ ## Key boundaries
49
+
50
+ - **Only one thing is reachable from outside this machine:** the HTTPS
51
+ tunnel into `wardby mcp:8080`. Postgres, coding-proxy, Prometheus, and
52
+ Grafana are all either fully private-network-only (`coding-proxy:9464`
53
+ has no `ports:` mapping at all) or explicitly loopback-bound
54
+ (`127.0.0.1:9090`, `127.0.0.1:3000`) — Grafana/Prometheus are not exposed
55
+ through the tunnel.
56
+ - **The coding worker never sees real API credentials.** `wardby-coding-proxy`
57
+ sits between it and OpenAI/Anthropic, injecting credentials and metering
58
+ tokens/cost — this is also where `/metrics` lives.
59
+ - **Two write paths into Postgres:** `wardby mcp` owns the agent/run/webhook
60
+ config tables directly; `coding-proxy` writes its own usage/cost ledger
61
+ directly during a run, which `wardby mcp` reconciles back into the `Run`
62
+ row once the container finishes (the "cap at the boundary, reconcile
63
+ after" pattern for opaque agent runs).
64
+ - **`wardby mcp` restarts don't affect observability, and vice versa.** The
65
+ control plane and metrics stack can be deployed and restarted independently.
66
+
67
+ ## Where the worker actually runs
68
+
69
+ The diagram above shows `JOB_LAUNCHER=docker`, which is what this host uses.
70
+ The worker's execution backend is a seam (`WorkspaceJobLauncher`), and the
71
+ choice does not change any boundary above — the worker still holds no
72
+ credentials, still reaches only the proxy, and the proxy still owns the ledger.
73
+
74
+ | `JOB_LAUNCHER` | Worker runs in | Isolation |
75
+ | -------------- | -------------------------------- | ---------------------------------------------------- |
76
+ | `local` | a child process on this host | development only, no isolation |
77
+ | `docker` | a container on a per-run network | the diagram above |
78
+ | `kubernetes` | a pod in a namespace | per-run NetworkPolicy, attested pod, optional gVisor |
79
+
80
+ Under `kubernetes` the shape is the same with different nouns: the per-run
81
+ Docker network becomes a NetworkPolicy, the keeper and worker share a pod
82
+ instead of a volume, and the proxy is a Service rather than a container name.
83
+ Two things have no Docker equivalent. The pod read back from the API server is
84
+ compared field by field against the pod wardby built, and any difference fails
85
+ the run; and before the worker is released, the launcher proves the network
86
+ policy is actually being enforced, because a cluster accepts a policy whether
87
+ or not anything enforces it. See
88
+ [coding-worker-isolation.md](coding-worker-isolation.md) for the mechanics and
89
+ the [Kubernetes deployment guide](../deploy/kind-coding/README.md) for the local
90
+ and GKE Autopilot overlays.
@@ -0,0 +1,16 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" role="img" aria-labelledby="title desc">
2
+ <title id="title">Wardby icon</title>
3
+ <desc id="desc">A friendly helmeted guardian carrying a budget shield.</desc>
4
+
5
+ <circle cx="64" cy="64" r="58" fill="#071d3b"/>
6
+ <path d="M20 55C20 25 40 12 64 12s44 13 44 43v35H20Z" fill="#fffaf0"/>
7
+ <rect x="30" y="43" width="68" height="45" rx="18" fill="#0a2850"/>
8
+ <path d="M27 48Q64 18 101 48" fill="none" stroke="#c8aa70" stroke-width="10" stroke-linecap="round"/>
9
+ <path d="M64 7V53" fill="none" stroke="#c8aa70" stroke-width="11" stroke-linecap="round"/>
10
+ <path d="M44 65q7 10 14 0M70 65q7 10 14 0" fill="none" stroke="#4be6b3" stroke-width="6" stroke-linecap="round"/>
11
+
12
+ <path d="M78 76 108 86v18c0 12-10 19-30 24-20-5-30-12-30-24V86Z" fill="#071d3b" stroke="#fffaf0" stroke-width="5" stroke-linejoin="round"/>
13
+ <g transform="translate(78 105) scale(.72) translate(-78 -105)">
14
+ <path d="M89 91c-3-4-7-6-12-6-7 0-12 4-12 9 0 6 5 8 13 10 8 2 13 5 13 11 0 7-5 11-13 11-7 0-13-3-17-8M78 82v46" fill="none" stroke="#4be6b3" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
15
+ </g>
16
+ </svg>
@@ -0,0 +1,5 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1254 1254" role="img" aria-labelledby="title desc">
2
+ <title id="title">Wardby robot mascot</title>
3
+ <desc id="desc">A friendly helmeted robot carrying a shield marked with a dollar sign.</desc>
4
+ <image width="1254" height="1254" href="wardby-mascot.png" xlink:href="wardby-mascot.png" preserveAspectRatio="xMidYMid meet"/>
5
+ </svg>
@@ -0,0 +1,106 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="1280" height="430" viewBox="0 0 1280 430" role="img" aria-labelledby="title desc">
2
+ <title id="title">Wardby agent workflow</title>
3
+ <desc id="desc">Five stages show a request moving from Claude or Codex through MCP, governed execution in Wardby, and an outcome controlled by review policy.</desc>
4
+ <defs>
5
+ <linearGradient id="background" x1="0" y1="0" x2="1" y2="1">
6
+ <stop offset="0" stop-color="#f7faff"/>
7
+ <stop offset="1" stop-color="#edf8f7"/>
8
+ </linearGradient>
9
+ <linearGradient id="primary" x1="0" y1="0" x2="1" y2="1">
10
+ <stop offset="0" stop-color="#0f6bff"/>
11
+ <stop offset="1" stop-color="#0756d8"/>
12
+ </linearGradient>
13
+ <linearGradient id="success" x1="0" y1="0" x2="1" y2="1">
14
+ <stop offset="0" stop-color="#2de3a6"/>
15
+ <stop offset="1" stop-color="#11b981"/>
16
+ </linearGradient>
17
+ <filter id="shadow" x="-20%" y="-20%" width="140%" height="160%">
18
+ <feDropShadow dx="0" dy="5" stdDeviation="7" flood-color="#082850" flood-opacity="0.10"/>
19
+ </filter>
20
+ <marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
21
+ <path d="M 0 0 L 10 5 L 0 10 z" fill="#7c93b3"/>
22
+ </marker>
23
+ </defs>
24
+
25
+ <rect x="1" y="1" width="1278" height="428" rx="28" fill="url(#background)" stroke="#dbe7f5" stroke-width="2"/>
26
+
27
+ <text x="48" y="48" fill="#0f6bff" font-family="Arial, Helvetica, sans-serif" font-size="13" font-weight="700" letter-spacing="2">FULL AGENT LIFECYCLE</text>
28
+ <text x="48" y="76" fill="#082850" font-family="Arial, Helvetica, sans-serif" font-size="24" font-weight="700">From a conversation to a governed, reviewable outcome</text>
29
+
30
+ <g filter="url(#shadow)">
31
+ <rect x="45" y="112" width="210" height="172" rx="18" fill="#ffffff" stroke="#bcd4f7" stroke-width="2"/>
32
+ <circle cx="75" cy="142" r="17" fill="#e8f1ff"/>
33
+ <text x="75" y="148" text-anchor="middle" fill="#0f6bff" font-family="Arial, Helvetica, sans-serif" font-size="15" font-weight="700">1</text>
34
+ <text x="103" y="147" fill="#0f6bff" font-family="Arial, Helvetica, sans-serif" font-size="12" font-weight="700" letter-spacing="1.5">ASK</text>
35
+ <text x="70" y="190" fill="#082850" font-family="Arial, Helvetica, sans-serif" font-size="21" font-weight="700">Claude or Codex</text>
36
+ <text x="70" y="220" fill="#52677f" font-family="Arial, Helvetica, sans-serif" font-size="14">Describe the outcome</text>
37
+ <text x="70" y="243" fill="#52677f" font-family="Arial, Helvetica, sans-serif" font-size="14">in natural language</text>
38
+ </g>
39
+
40
+ <path d="M 263 198 H 284" fill="none" stroke="#7c93b3" stroke-width="3" marker-end="url(#arrow)"/>
41
+ <text x="273" y="184" text-anchor="middle" fill="#637b98" font-family="Arial, Helvetica, sans-serif" font-size="11" font-weight="700">MCP</text>
42
+
43
+ <g filter="url(#shadow)">
44
+ <rect x="295" y="112" width="210" height="172" rx="18" fill="#ffffff" stroke="#bcd4f7" stroke-width="2"/>
45
+ <circle cx="325" cy="142" r="17" fill="#e8f1ff"/>
46
+ <text x="325" y="148" text-anchor="middle" fill="#0f6bff" font-family="Arial, Helvetica, sans-serif" font-size="15" font-weight="700">2</text>
47
+ <text x="353" y="147" fill="#0f6bff" font-family="Arial, Helvetica, sans-serif" font-size="12" font-weight="700" letter-spacing="1.5">DEFINE</text>
48
+ <text x="320" y="190" fill="#082850" font-family="Arial, Helvetica, sans-serif" font-size="21" font-weight="700">Agent blueprint</text>
49
+ <text x="320" y="220" fill="#52677f" font-family="Arial, Helvetica, sans-serif" font-size="14">Model • tools • schedule</text>
50
+ <text x="320" y="243" fill="#52677f" font-family="Arial, Helvetica, sans-serif" font-size="14">Sub-agents • outcome</text>
51
+ </g>
52
+
53
+ <path d="M 513 198 H 534" fill="none" stroke="#7c93b3" stroke-width="3" marker-end="url(#arrow)"/>
54
+ <text x="523" y="184" text-anchor="middle" fill="#637b98" font-family="Arial, Helvetica, sans-serif" font-size="11" font-weight="700">POLICY</text>
55
+
56
+ <g filter="url(#shadow)">
57
+ <rect x="545" y="112" width="210" height="172" rx="18" fill="url(#primary)"/>
58
+ <circle cx="575" cy="142" r="17" fill="#ffffff" fill-opacity="0.20"/>
59
+ <text x="575" y="148" text-anchor="middle" fill="#ffffff" font-family="Arial, Helvetica, sans-serif" font-size="15" font-weight="700">3</text>
60
+ <text x="603" y="147" fill="#dceaff" font-family="Arial, Helvetica, sans-serif" font-size="12" font-weight="700" letter-spacing="1.5">GOVERN</text>
61
+ <text x="570" y="190" fill="#ffffff" font-family="Arial, Helvetica, sans-serif" font-size="21" font-weight="700">Wardby</text>
62
+ <text x="570" y="220" fill="#e7f0ff" font-family="Arial, Helvetica, sans-serif" font-size="14">Budget • secrets • owner</text>
63
+ <text x="570" y="243" fill="#e7f0ff" font-family="Arial, Helvetica, sans-serif" font-size="14">Audit • cancellation</text>
64
+ </g>
65
+
66
+ <path d="M 763 198 H 784" fill="none" stroke="#7c93b3" stroke-width="3" marker-end="url(#arrow)"/>
67
+ <text x="773" y="184" text-anchor="middle" fill="#637b98" font-family="Arial, Helvetica, sans-serif" font-size="11" font-weight="700">RUN</text>
68
+
69
+ <g filter="url(#shadow)">
70
+ <rect x="795" y="112" width="210" height="172" rx="18" fill="#ffffff" stroke="#bcd4f7" stroke-width="2"/>
71
+ <circle cx="825" cy="142" r="17" fill="#e8f1ff"/>
72
+ <text x="825" y="148" text-anchor="middle" fill="#0f6bff" font-family="Arial, Helvetica, sans-serif" font-size="15" font-weight="700">4</text>
73
+ <text x="853" y="147" fill="#0f6bff" font-family="Arial, Helvetica, sans-serif" font-size="12" font-weight="700" letter-spacing="1.5">EXECUTE</text>
74
+ <text x="820" y="190" fill="#082850" font-family="Arial, Helvetica, sans-serif" font-size="21" font-weight="700">Isolated workers</text>
75
+ <text x="820" y="220" fill="#52677f" font-family="Arial, Helvetica, sans-serif" font-size="14">Native • Codex • Claude</text>
76
+ <text x="820" y="243" fill="#52677f" font-family="Arial, Helvetica, sans-serif" font-size="14">Bounded tools and access</text>
77
+ </g>
78
+
79
+ <path d="M 1013 198 H 1034" fill="none" stroke="#7c93b3" stroke-width="3" marker-end="url(#arrow)"/>
80
+ <text x="1023" y="184" text-anchor="middle" fill="#637b98" font-family="Arial, Helvetica, sans-serif" font-size="11" font-weight="700">RESULT</text>
81
+
82
+ <g filter="url(#shadow)">
83
+ <rect x="1045" y="112" width="190" height="172" rx="18" fill="#ffffff" stroke="#22c995" stroke-width="2"/>
84
+ <circle cx="1075" cy="142" r="17" fill="url(#success)"/>
85
+ <text x="1075" y="148" text-anchor="middle" fill="#ffffff" font-family="Arial, Helvetica, sans-serif" font-size="15" font-weight="700">5</text>
86
+ <text x="1103" y="147" fill="#09845f" font-family="Arial, Helvetica, sans-serif" font-size="12" font-weight="700" letter-spacing="1.5">REVIEW</text>
87
+ <text x="1070" y="190" fill="#082850" font-family="Arial, Helvetica, sans-serif" font-size="21" font-weight="700">Review policy</text>
88
+ <text x="1070" y="220" fill="#52677f" font-family="Arial, Helvetica, sans-serif" font-size="14">Agent or human review</text>
89
+ <text x="1070" y="243" fill="#09845f" font-family="Arial, Helvetica, sans-serif" font-size="14" font-weight="700">Authority stays explicit</text>
90
+ </g>
91
+
92
+ <rect x="45" y="324" width="1190" height="70" rx="16" fill="#082850"/>
93
+ <text x="72" y="351" fill="#70edc2" font-family="Arial, Helvetica, sans-serif" font-size="12" font-weight="700" letter-spacing="1.5">RUNS IN YOUR INFRASTRUCTURE</text>
94
+ <text x="72" y="378" fill="#ffffff" font-family="Arial, Helvetica, sans-serif" font-size="17" font-weight="700">Your cloud • Your credentials • Your operating boundary</text>
95
+
96
+ <g font-family="Arial, Helvetica, sans-serif" font-size="12" font-weight="700" text-anchor="middle">
97
+ <rect x="605" y="343" width="140" height="32" rx="16" fill="#ffffff" fill-opacity="0.11"/>
98
+ <text x="675" y="364" fill="#ffffff">Budget enforced</text>
99
+ <rect x="757" y="343" width="168" height="32" rx="16" fill="#ffffff" fill-opacity="0.11"/>
100
+ <text x="841" y="364" fill="#ffffff">Credentials separated</text>
101
+ <rect x="937" y="343" width="135" height="32" rx="16" fill="#ffffff" fill-opacity="0.11"/>
102
+ <text x="1005" y="364" fill="#ffffff">Runs observable</text>
103
+ <rect x="1084" y="343" width="125" height="32" rx="16" fill="#ffffff" fill-opacity="0.11"/>
104
+ <text x="1147" y="364" fill="#ffffff">Action policy</text>
105
+ </g>
106
+ </svg>