@wardby/cli 0.6.0 → 0.7.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 (576) hide show
  1. package/deploy/local/docker-compose.quickstart-coding.yml +13 -0
  2. package/dist/claude-coding-worker/driver.d.ts +21 -3
  3. package/dist/claude-coding-worker/driver.js +125 -17
  4. package/dist/claude-coding-worker/main.js +34 -6
  5. package/dist/claude-coding-worker/sdk.js +12 -1
  6. package/dist/claude-coding-worker/tool-socket.d.ts +27 -0
  7. package/dist/claude-coding-worker/tool-socket.js +111 -0
  8. package/dist/claude-coding-worker/transcript.d.ts +18 -0
  9. package/dist/claude-coding-worker/transcript.js +77 -0
  10. package/dist/cli-help.d.ts +1 -1
  11. package/dist/cli-help.js +1 -0
  12. package/dist/cli.js +24 -10
  13. package/dist/coding/continuation-wording.d.ts +35 -0
  14. package/dist/coding/continuation-wording.js +73 -0
  15. package/dist/coding/linked-issue-note.d.ts +23 -0
  16. package/dist/coding/linked-issue-note.js +47 -0
  17. package/dist/coding/merge-wording.d.ts +28 -0
  18. package/dist/coding/merge-wording.js +78 -0
  19. package/dist/coding/pr-description-note.d.ts +9 -0
  20. package/dist/coding/pr-description-note.js +32 -0
  21. package/dist/coding/profile.d.ts +46 -126
  22. package/dist/coding/profile.js +45 -4
  23. package/dist/coding/protected-path-wording.d.ts +8 -0
  24. package/dist/coding/protected-path-wording.js +1 -1
  25. package/dist/coding/protocol.d.ts +264 -346
  26. package/dist/coding/protocol.js +235 -12
  27. package/dist/coding/redact.d.ts +3 -0
  28. package/dist/coding/redact.js +12 -0
  29. package/dist/coding/registry/allowlist.d.ts +12 -0
  30. package/dist/coding/registry/allowlist.js +14 -1
  31. package/dist/coding/registry/audit-mode.d.ts +153 -0
  32. package/dist/coding/registry/audit-mode.js +304 -0
  33. package/dist/coding/registry/lockfile-shared.d.ts +36 -0
  34. package/dist/coding/registry/lockfile-shared.js +128 -0
  35. package/dist/coding/registry/lockfiles.js +1 -1
  36. package/dist/coding/registry/npm-lockfile-formats.d.ts +11 -0
  37. package/dist/coding/registry/npm-lockfile-formats.js +41 -0
  38. package/dist/coding/registry/npm-lockfile.d.ts +8 -4
  39. package/dist/coding/registry/npm-lockfile.js +4 -6
  40. package/dist/coding/registry/npm.js +78 -8
  41. package/dist/coding/registry/pnpm-lockfile.d.ts +17 -0
  42. package/dist/coding/registry/pnpm-lockfile.js +189 -0
  43. package/dist/coding/registry/pypi-plan.d.ts +50 -0
  44. package/dist/coding/registry/pypi-plan.js +406 -0
  45. package/dist/coding/registry/pypi-requirements.d.ts +58 -0
  46. package/dist/coding/registry/pypi-requirements.js +155 -0
  47. package/dist/coding/registry/pypi.d.ts +3 -27
  48. package/dist/coding/registry/pypi.js +40 -154
  49. package/dist/coding/registry/refusal-hints.d.ts +20 -0
  50. package/dist/coding/registry/refusal-hints.js +61 -0
  51. package/dist/coding/registry/refusal-wording.d.ts +11 -0
  52. package/dist/coding/registry/refusal-wording.js +56 -0
  53. package/dist/coding/registry/report.d.ts +70 -8
  54. package/dist/coding/registry/report.js +112 -5
  55. package/dist/coding/registry/types.d.ts +58 -8
  56. package/dist/coding/registry/yarn-lockfile.d.ts +30 -0
  57. package/dist/coding/registry/yarn-lockfile.js +367 -0
  58. package/dist/coding/services/catalog.d.ts +37 -351
  59. package/dist/coding/transcript/claude-code.d.ts +13 -0
  60. package/dist/coding/transcript/claude-code.js +273 -0
  61. package/dist/coding/transcript/download-protocol.d.ts +30 -0
  62. package/dist/coding/transcript/download-protocol.js +18 -0
  63. package/dist/coding/transcript/format.d.ts +20 -0
  64. package/dist/coding/transcript/format.js +32 -0
  65. package/dist/coding/transcript/recorder.d.ts +51 -0
  66. package/dist/coding/transcript/recorder.js +225 -0
  67. package/dist/coding/transcript/resume/checkout.d.ts +31 -0
  68. package/dist/coding/transcript/resume/checkout.js +203 -0
  69. package/dist/coding/transcript/resume/command.d.ts +17 -0
  70. package/dist/coding/transcript/resume/command.js +176 -0
  71. package/dist/coding/transcript/resume/download.d.ts +20 -0
  72. package/dist/coding/transcript/resume/download.js +153 -0
  73. package/dist/coding/transcript/resume/launch.d.ts +38 -0
  74. package/dist/coding/transcript/resume/launch.js +128 -0
  75. package/dist/coding/transcript/resume/session-file.d.ts +13 -0
  76. package/dist/coding/transcript/resume/session-file.js +79 -0
  77. package/dist/coding/transcript/resume/stand-in.d.ts +11 -0
  78. package/dist/coding/transcript/resume/stand-in.js +49 -0
  79. package/dist/coding-worker/artifact.d.ts +8 -3
  80. package/dist/coding-worker/artifact.js +3 -1
  81. package/dist/coding-worker/debug-trace.d.ts +6 -5
  82. package/dist/coding-worker/debug-trace.js +13 -18
  83. package/dist/coding-worker/dependency-cache.d.mts +229 -0
  84. package/dist/coding-worker/dependency-cache.mjs +2123 -0
  85. package/dist/coding-worker/driver.d.ts +4 -3
  86. package/dist/coding-worker/driver.js +116 -38
  87. package/dist/coding-worker/errors.d.ts +1 -0
  88. package/dist/coding-worker/errors.js +32 -0
  89. package/dist/coding-worker/task-note.d.ts +11 -0
  90. package/dist/coding-worker/task-note.js +13 -0
  91. package/dist/coding-worker/types.d.ts +11 -0
  92. package/dist/config/dependency-cache.d.ts +23 -0
  93. package/dist/config/dependency-cache.js +32 -0
  94. package/dist/config/providers.d.ts +12 -3
  95. package/dist/config/providers.js +55 -5
  96. package/dist/config/test-simulators.d.ts +82 -0
  97. package/dist/config/test-simulators.js +305 -0
  98. package/dist/core/async-context.d.ts +2 -0
  99. package/dist/core/async-context.js +12 -0
  100. package/dist/core/budget-events.d.ts +108 -0
  101. package/dist/core/budget-events.js +346 -0
  102. package/dist/core/budget-groups.d.ts +50 -1
  103. package/dist/core/budget-groups.js +236 -27
  104. package/dist/core/budget-stopped-head.d.ts +46 -0
  105. package/dist/core/budget-stopped-head.js +81 -0
  106. package/dist/core/budget-topup.d.ts +51 -0
  107. package/dist/core/budget-topup.js +156 -0
  108. package/dist/core/budget-wording.d.ts +16 -2
  109. package/dist/core/budget-wording.js +40 -2
  110. package/dist/core/coding-queue.d.ts +2 -1
  111. package/dist/core/coding-queue.js +7 -0
  112. package/dist/core/cron.d.ts +19 -1
  113. package/dist/core/cron.js +17 -0
  114. package/dist/core/datastores.js +1 -1
  115. package/dist/core/deferred-coding.d.ts +14 -0
  116. package/dist/core/deferred-coding.js +62 -0
  117. package/dist/core/delegation-child.d.ts +25 -0
  118. package/dist/core/delegation-child.js +37 -0
  119. package/dist/core/dispatch.d.ts +42 -0
  120. package/dist/core/dispatch.js +131 -56
  121. package/dist/core/engine-native.js +1 -1
  122. package/dist/core/error-code.d.ts +38 -0
  123. package/dist/core/error-code.js +168 -0
  124. package/dist/core/grants.d.ts +19 -6
  125. package/dist/core/grants.js +55 -6
  126. package/dist/core/host-events.d.ts +18 -2
  127. package/dist/core/host-events.js +284 -24
  128. package/dist/core/host-status.d.ts +85 -3
  129. package/dist/core/host-status.js +298 -20
  130. package/dist/core/issue-bridge.d.ts +50 -6
  131. package/dist/core/issue-bridge.js +124 -33
  132. package/dist/core/issue-events.d.ts +1 -1
  133. package/dist/core/issue-events.js +4 -10
  134. package/dist/core/issue-status.d.ts +1 -1
  135. package/dist/core/issue-status.js +54 -26
  136. package/dist/core/logger.d.ts +5 -0
  137. package/dist/core/logger.js +14 -1
  138. package/dist/core/lost-run-children.d.ts +6 -0
  139. package/dist/core/lost-run-children.js +32 -0
  140. package/dist/core/mark-ready.d.ts +34 -0
  141. package/dist/core/mark-ready.js +152 -0
  142. package/dist/core/markdown-code.d.ts +8 -0
  143. package/dist/core/markdown-code.js +107 -0
  144. package/dist/core/memory-tools.js +7 -0
  145. package/dist/core/merge-order-check.d.ts +2 -1
  146. package/dist/core/merge-order-check.js +8 -1
  147. package/dist/core/notification-templates.d.ts +1 -7
  148. package/dist/core/notification-templates.js +36 -2
  149. package/dist/core/pull-request-state-sync.js +2 -2
  150. package/dist/core/reconciler.d.ts +4 -3
  151. package/dist/core/reconciler.js +8 -1
  152. package/dist/core/related-pull-requests.d.ts +11 -3
  153. package/dist/core/related-pull-requests.js +27 -14
  154. package/dist/core/reply-parts.d.ts +21 -0
  155. package/dist/core/reply-parts.js +73 -0
  156. package/dist/core/review-fix.d.ts +1 -1
  157. package/dist/core/review-fix.js +2 -1
  158. package/dist/core/review-host-checks.d.ts +1 -1
  159. package/dist/core/review-host-tools.d.ts +3 -0
  160. package/dist/core/review-host-tools.js +99 -13
  161. package/dist/core/review-reads.d.ts +55 -0
  162. package/dist/core/review-reads.js +233 -0
  163. package/dist/core/run-status.d.ts +12 -0
  164. package/dist/core/run-status.js +12 -0
  165. package/dist/core/runner.d.ts +80 -34
  166. package/dist/core/runner.js +439 -144
  167. package/dist/core/schedule-start.d.ts +12 -0
  168. package/dist/core/schedule-start.js +14 -0
  169. package/dist/core/secret-broker-config.d.ts +10 -74
  170. package/dist/core/secret-guard.d.ts +46 -0
  171. package/dist/core/secret-guard.js +104 -0
  172. package/dist/core/secret-guarded-stores.d.ts +12 -0
  173. package/dist/core/secret-guarded-stores.js +41 -0
  174. package/dist/core/secret-spans.d.ts +50 -0
  175. package/dist/core/secret-spans.js +94 -0
  176. package/dist/core/stop-run.d.ts +46 -0
  177. package/dist/core/stop-run.js +61 -0
  178. package/dist/core/workflow-events.d.ts +14 -2
  179. package/dist/core/workflow-events.js +4 -0
  180. package/dist/core/workflow-run-events.d.ts +5 -3
  181. package/dist/core/workflow-run-events.js +43 -8
  182. package/dist/core/zod-json-schema.d.ts +4 -0
  183. package/dist/core/zod-json-schema.js +39 -0
  184. package/dist/core/zod3-window.d.ts +35 -0
  185. package/dist/core/zod3-window.js +124 -0
  186. package/dist/generated/prisma/browser.d.ts +34 -3
  187. package/dist/generated/prisma/client.d.ts +34 -3
  188. package/dist/generated/prisma/commonInputTypes.d.ts +220 -60
  189. package/dist/generated/prisma/enums.d.ts +10 -0
  190. package/dist/generated/prisma/enums.js +8 -0
  191. package/dist/generated/prisma/internal/class.d.ts +44 -0
  192. package/dist/generated/prisma/internal/class.js +4 -4
  193. package/dist/generated/prisma/internal/prismaNamespace.d.ts +428 -9
  194. package/dist/generated/prisma/internal/prismaNamespace.js +92 -1
  195. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +95 -0
  196. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +92 -1
  197. package/dist/generated/prisma/models/Agent.d.ts +106 -8
  198. package/dist/generated/prisma/models/AgentRepository.d.ts +42 -1
  199. package/dist/generated/prisma/models/CodingAgentProfile.d.ts +229 -1
  200. package/dist/generated/prisma/models/CodingProxyRequest.d.ts +207 -3
  201. package/dist/generated/prisma/models/CodingProxySession.d.ts +114 -1
  202. package/dist/generated/prisma/models/CodingRun.d.ts +1637 -1
  203. package/dist/generated/prisma/models/DependencySnapshot.d.ts +1668 -0
  204. package/dist/generated/prisma/models/DependencySnapshot.js +1 -0
  205. package/dist/generated/prisma/models/IssuePullRequest.d.ts +6 -2
  206. package/dist/generated/prisma/models/LocalReview.d.ts +40 -1
  207. package/dist/generated/prisma/models/NativeGatewayCall.d.ts +39 -1
  208. package/dist/generated/prisma/models/NativeGatewaySession.d.ts +35 -1
  209. package/dist/generated/prisma/models/RegistryAllowance.d.ts +41 -1
  210. package/dist/generated/prisma/models/RegistryApprovedVersion.d.ts +75 -1
  211. package/dist/generated/prisma/models/RegistryAuditRecord.d.ts +1364 -0
  212. package/dist/generated/prisma/models/RegistryAuditRecord.js +1 -0
  213. package/dist/generated/prisma/models/Run.d.ts +738 -2
  214. package/dist/generated/prisma/models/RunBudgetEvent.d.ts +1211 -0
  215. package/dist/generated/prisma/models/RunBudgetEvent.js +1 -0
  216. package/dist/generated/prisma/models/RunTranscript.d.ts +1339 -0
  217. package/dist/generated/prisma/models/RunTranscript.js +1 -0
  218. package/dist/generated/prisma/models.d.ts +4 -0
  219. package/dist/help-index.json +1320 -69
  220. package/dist/import/create.js +32 -3
  221. package/dist/import/neutral-schema.d.ts +50 -281
  222. package/dist/import/neutral-schema.js +5 -4
  223. package/dist/knowledge/check.d.ts +1 -1
  224. package/dist/knowledge/cli.d.ts +11 -1
  225. package/dist/knowledge/cli.js +37 -4
  226. package/dist/knowledge/concept.d.ts +3 -19
  227. package/dist/knowledge/location.d.ts +31 -0
  228. package/dist/knowledge/location.js +78 -0
  229. package/dist/mcp/auth/access.d.ts +10 -2
  230. package/dist/mcp/auth/access.js +15 -1
  231. package/dist/mcp/auth/ownership.d.ts +7 -7
  232. package/dist/mcp/auth/self-hosted/credentials.d.ts +3 -3
  233. package/dist/mcp/auth/self-hosted/session.d.ts +2 -2
  234. package/dist/mcp/host-events/github-ingress.d.ts +2 -0
  235. package/dist/mcp/host-events/github-ingress.js +2 -1
  236. package/dist/mcp/index.d.ts +6 -0
  237. package/dist/mcp/index.js +58 -10
  238. package/dist/mcp/server.d.ts +13 -0
  239. package/dist/mcp/server.js +33 -14
  240. package/dist/mcp/tools/agents.d.ts +4 -0
  241. package/dist/mcp/tools/agents.js +198 -16
  242. package/dist/mcp/tools/datastore.js +17 -8
  243. package/dist/mcp/tools/grants.js +9 -3
  244. package/dist/mcp/tools/memory.js +11 -1
  245. package/dist/mcp/tools/notification-channels.js +2 -2
  246. package/dist/mcp/tools/packages.d.ts +2 -0
  247. package/dist/mcp/tools/packages.js +195 -0
  248. package/dist/mcp/tools/repositories.js +18 -5
  249. package/dist/mcp/tools/run-access.d.ts +16 -0
  250. package/dist/mcp/tools/run-access.js +25 -0
  251. package/dist/mcp/tools/run-transcript-download.d.ts +30 -0
  252. package/dist/mcp/tools/run-transcript-download.js +129 -0
  253. package/dist/mcp/tools/run-transcript.d.ts +19 -0
  254. package/dist/mcp/tools/run-transcript.js +70 -0
  255. package/dist/mcp/tools/runs.js +34 -12
  256. package/dist/mcp/tools/scheduling.js +3 -1
  257. package/dist/mcp/tools/secret-elicitation-form.js +5 -0
  258. package/dist/mcp/tools/secret-elicitation-server.d.ts +6 -0
  259. package/dist/mcp/tools/secret-elicitation-server.js +10 -61
  260. package/dist/mcp/tools/secrets.js +2 -2
  261. package/dist/mcp/tools/stop-run.d.ts +2 -0
  262. package/dist/mcp/tools/stop-run.js +82 -0
  263. package/dist/mcp/tools/subagents.js +2 -2
  264. package/dist/mcp/tools/tools.d.ts +1 -1
  265. package/dist/mcp/tools/tools.js +25 -9
  266. package/dist/mcp/transport/stdio-link-host.d.ts +20 -0
  267. package/dist/mcp/transport/stdio-link-host.js +67 -0
  268. package/dist/mcp/transport/streamable-http.d.ts +13 -0
  269. package/dist/mcp/transport/streamable-http.js +41 -1
  270. package/dist/native-worker/docker-launcher.d.ts +38 -2
  271. package/dist/native-worker/docker-launcher.js +65 -5
  272. package/dist/native-worker/gateway-server.d.ts +41 -3
  273. package/dist/native-worker/gateway-server.js +124 -27
  274. package/dist/native-worker/gateway.d.ts +27 -1
  275. package/dist/native-worker/gateway.js +70 -4
  276. package/dist/native-worker/http-server.d.ts +3 -1
  277. package/dist/native-worker/http-server.js +11 -7
  278. package/dist/native-worker/http-transport.js +1 -1
  279. package/dist/native-worker/kubernetes-launcher.d.ts +7 -3
  280. package/dist/native-worker/kubernetes-launcher.js +43 -3
  281. package/dist/native-worker/launch.d.ts +18 -1
  282. package/dist/native-worker/launch.js +6 -1
  283. package/dist/native-worker/ledger.d.ts +18 -2
  284. package/dist/native-worker/ledger.js +57 -4
  285. package/dist/native-worker/loopback.js +3 -8
  286. package/dist/native-worker/main.d.ts +2 -1
  287. package/dist/native-worker/main.js +14 -3
  288. package/dist/native-worker/protocol.d.ts +129 -577
  289. package/dist/native-worker/protocol.js +47 -1
  290. package/dist/native-worker/sandbox-executor.d.ts +17 -2
  291. package/dist/native-worker/sandbox-executor.js +78 -4
  292. package/dist/native-worker/stderr-redaction.d.ts +77 -0
  293. package/dist/native-worker/stderr-redaction.js +237 -0
  294. package/dist/native-worker/stdio.d.ts +1 -1
  295. package/dist/native-worker/stdio.js +6 -5
  296. package/dist/native-worker/warm-pool.d.ts +2 -2
  297. package/dist/native-worker/warm-pool.js +4 -4
  298. package/dist/native-worker/worker.js +28 -2
  299. package/dist/observability/metrics.js +6 -1
  300. package/dist/providers/auth/self-hosted.d.ts +1 -1
  301. package/dist/providers/coding-proxy/dependency-cache/eviction.d.ts +29 -0
  302. package/dist/providers/coding-proxy/dependency-cache/eviction.js +87 -0
  303. package/dist/providers/coding-proxy/dependency-cache/key.d.ts +33 -0
  304. package/dist/providers/coding-proxy/dependency-cache/key.js +86 -0
  305. package/dist/providers/coding-proxy/dependency-cache/model-request-seal.d.ts +18 -0
  306. package/dist/providers/coding-proxy/dependency-cache/model-request-seal.js +40 -0
  307. package/dist/providers/coding-proxy/dependency-cache/prisma-store.d.ts +25 -0
  308. package/dist/providers/coding-proxy/dependency-cache/prisma-store.js +131 -0
  309. package/dist/providers/coding-proxy/dependency-cache/service.d.ts +70 -0
  310. package/dist/providers/coding-proxy/dependency-cache/service.js +409 -0
  311. package/dist/providers/coding-proxy/dependency-cache/store.d.ts +85 -0
  312. package/dist/providers/coding-proxy/dependency-cache/store.js +95 -0
  313. package/dist/providers/coding-proxy/hold.d.ts +32 -0
  314. package/dist/providers/coding-proxy/hold.js +86 -0
  315. package/dist/providers/coding-proxy/interrupted.d.ts +70 -0
  316. package/dist/providers/coding-proxy/interrupted.js +104 -0
  317. package/dist/providers/coding-proxy/memory-ledger.d.ts +15 -2
  318. package/dist/providers/coding-proxy/memory-ledger.js +84 -8
  319. package/dist/providers/coding-proxy/metering.d.ts +16 -0
  320. package/dist/providers/coding-proxy/metering.js +27 -4
  321. package/dist/providers/coding-proxy/prisma-ledger.d.ts +15 -2
  322. package/dist/providers/coding-proxy/prisma-ledger.js +179 -67
  323. package/dist/providers/coding-proxy/proxy.d.ts +14 -1
  324. package/dist/providers/coding-proxy/proxy.js +166 -14
  325. package/dist/providers/coding-proxy/registry/audit.d.ts +53 -10
  326. package/dist/providers/coding-proxy/registry/audit.js +94 -17
  327. package/dist/providers/coding-proxy/registry/lockfile-parse-worker.d.ts +36 -0
  328. package/dist/providers/coding-proxy/registry/lockfile-parse-worker.js +67 -0
  329. package/dist/providers/coding-proxy/registry/lockfile-parse.d.ts +16 -0
  330. package/dist/providers/coding-proxy/registry/lockfile-parse.js +110 -0
  331. package/dist/providers/coding-proxy/registry/plan.d.ts +9 -0
  332. package/dist/providers/coding-proxy/registry/plan.js +201 -48
  333. package/dist/providers/coding-proxy/registry/prisma-store.d.ts +11 -5
  334. package/dist/providers/coding-proxy/registry/prisma-store.js +120 -17
  335. package/dist/providers/coding-proxy/registry/service.d.ts +97 -16
  336. package/dist/providers/coding-proxy/registry/service.js +579 -112
  337. package/dist/providers/coding-proxy/registry/store.d.ts +84 -9
  338. package/dist/providers/coding-proxy/registry/store.js +54 -1
  339. package/dist/providers/coding-proxy/runtime.d.ts +4 -0
  340. package/dist/providers/coding-proxy/runtime.js +78 -16
  341. package/dist/providers/coding-proxy/server.d.ts +6 -1
  342. package/dist/providers/coding-proxy/server.js +45 -1
  343. package/dist/providers/coding-proxy/types.d.ts +65 -1
  344. package/dist/providers/datastore/types.d.ts +5 -0
  345. package/dist/providers/engine/types.d.ts +17 -2
  346. package/dist/providers/engine/types.js +17 -0
  347. package/dist/providers/executor/composition.d.ts +10 -3
  348. package/dist/providers/executor/composition.js +47 -7
  349. package/dist/providers/executor/container.d.ts +361 -8
  350. package/dist/providers/executor/container.js +1136 -91
  351. package/dist/providers/executor/dbos-status.d.ts +31 -0
  352. package/dist/providers/executor/dbos-status.js +64 -0
  353. package/dist/providers/executor/dbos.d.ts +50 -1
  354. package/dist/providers/executor/dbos.js +207 -20
  355. package/dist/providers/executor/deferred.d.ts +12 -24
  356. package/dist/providers/executor/deferred.js +10 -29
  357. package/dist/providers/executor/in-process.d.ts +9 -1
  358. package/dist/providers/executor/in-process.js +14 -4
  359. package/dist/providers/executor/linked-issue-reader.d.ts +16 -0
  360. package/dist/providers/executor/linked-issue-reader.js +30 -0
  361. package/dist/providers/executor/routing.d.ts +2 -0
  362. package/dist/providers/executor/routing.js +4 -0
  363. package/dist/providers/executor/types.d.ts +13 -0
  364. package/dist/providers/index.d.ts +2 -1
  365. package/dist/providers/issue-tracker/types.d.ts +17 -0
  366. package/dist/providers/issue-tracker/types.js +39 -1
  367. package/dist/providers/jobs/claude-tool-setup.d.ts +4 -0
  368. package/dist/providers/jobs/claude-tool-setup.js +5 -2
  369. package/dist/providers/jobs/docker-isolation.d.ts +8 -0
  370. package/dist/providers/jobs/docker-isolation.js +17 -0
  371. package/dist/providers/jobs/docker.d.ts +1 -0
  372. package/dist/providers/jobs/docker.js +27 -2
  373. package/dist/providers/jobs/fake-kubernetes-api.d.ts +2 -0
  374. package/dist/providers/jobs/fake-kubernetes-api.js +3 -0
  375. package/dist/providers/jobs/kubernetes-api.d.ts +8 -0
  376. package/dist/providers/jobs/kubernetes-api.js +10 -0
  377. package/dist/providers/jobs/kubernetes-client.js +10 -1
  378. package/dist/providers/jobs/kubernetes-isolation.d.ts +2 -0
  379. package/dist/providers/jobs/kubernetes-isolation.js +17 -1
  380. package/dist/providers/jobs/kubernetes.d.ts +6 -1
  381. package/dist/providers/jobs/kubernetes.js +105 -16
  382. package/dist/providers/jobs/types.d.ts +33 -0
  383. package/dist/providers/jobs/types.js +13 -1
  384. package/dist/providers/llm/anthropic.js +5 -1
  385. package/dist/providers/llm/bedrock.d.ts +0 -12
  386. package/dist/providers/llm/bedrock.js +4 -0
  387. package/dist/providers/llm/claude-messages.d.ts +3 -1
  388. package/dist/providers/llm/claude-messages.js +4 -1
  389. package/dist/providers/llm/claude-provider.d.ts +2 -2
  390. package/dist/providers/llm/claude-provider.js +2 -2
  391. package/dist/providers/llm/openai.d.ts +2 -2
  392. package/dist/providers/llm/openai.js +6 -2
  393. package/dist/providers/llm/routing.d.ts +2 -2
  394. package/dist/providers/llm/routing.js +2 -2
  395. package/dist/providers/llm/types.d.ts +12 -2
  396. package/dist/providers/review-host/github-events.d.ts +9 -1
  397. package/dist/providers/review-host/github-events.js +34 -12
  398. package/dist/providers/review-host/github.d.ts +29 -2
  399. package/dist/providers/review-host/github.js +224 -29
  400. package/dist/providers/review-host/local.d.ts +8 -4
  401. package/dist/providers/review-host/local.js +49 -31
  402. package/dist/providers/review-host/patch-pages.d.ts +41 -0
  403. package/dist/providers/review-host/patch-pages.js +176 -0
  404. package/dist/providers/review-host/review-format.d.ts +19 -1
  405. package/dist/providers/review-host/review-format.js +38 -7
  406. package/dist/providers/review-host/types.d.ts +134 -2
  407. package/dist/providers/review-host/types.js +8 -0
  408. package/dist/providers/storage/gcs.d.ts +63 -0
  409. package/dist/providers/storage/gcs.js +69 -0
  410. package/dist/providers/storage/index.d.ts +15 -0
  411. package/dist/providers/storage/index.js +14 -0
  412. package/dist/providers/storage/local.d.ts +24 -0
  413. package/dist/providers/storage/local.js +92 -0
  414. package/dist/providers/storage/meter.d.ts +14 -0
  415. package/dist/providers/storage/meter.js +34 -0
  416. package/dist/providers/storage/s3.d.ts +41 -0
  417. package/dist/providers/storage/s3.js +107 -0
  418. package/dist/providers/storage/types.d.ts +37 -3
  419. package/dist/providers/storage/types.js +21 -4
  420. package/dist/providers/vcs/git.d.ts +33 -2
  421. package/dist/providers/vcs/git.js +324 -22
  422. package/dist/providers/vcs/github-remote.d.ts +26 -3
  423. package/dist/providers/vcs/github-remote.js +124 -14
  424. package/dist/providers/vcs/github.d.ts +214 -6
  425. package/dist/providers/vcs/github.js +447 -45
  426. package/dist/providers/vcs/index.js +8 -1
  427. package/dist/providers/vcs/pr-description.d.ts +127 -0
  428. package/dist/providers/vcs/pr-description.js +270 -0
  429. package/dist/providers/vcs/remote.d.ts +33 -1
  430. package/dist/providers/vcs/routing.d.ts +4 -2
  431. package/dist/providers/vcs/routing.js +6 -0
  432. package/dist/providers/vcs/types.d.ts +80 -3
  433. package/dist/quickstart/coding-seed.d.ts +14 -3
  434. package/dist/quickstart/coding-seed.js +213 -4
  435. package/dist/quickstart/reviewer-prompt.d.ts +1 -1
  436. package/dist/quickstart/reviewer-prompt.js +6 -6
  437. package/dist/quickstart-images.json +1 -1
  438. package/dist/sandbox/generated/zod.bundle.js +62 -1
  439. package/dist/sandbox/generated/zod3.bundle.js +5 -0
  440. package/dist/sandbox/host-functions.d.ts +1 -0
  441. package/dist/sandbox/host-functions.js +6 -11
  442. package/dist/sandbox/secret-broker.js +3 -4
  443. package/dist/sandbox/tool-capabilities.d.ts +10 -30
  444. package/dist/sandbox/zod-params.d.ts +37 -5
  445. package/dist/sandbox/zod-params.js +181 -26
  446. package/dist/serve.js +8 -1
  447. package/dist/viewer/api-schema.d.ts +349 -2627
  448. package/dist/viewer/api-schema.js +7 -0
  449. package/dist/viewer/build-schemas.js +6 -2
  450. package/dist/viewer/openapi.js +2 -4
  451. package/dist/wardby-bin.js +10 -0
  452. package/docs/agent-recipes.md +58 -20
  453. package/docs/code-review-agents.md +551 -41
  454. package/docs/coding-agent-setup.md +489 -7
  455. package/docs/coding-packages.md +1033 -90
  456. package/docs/coding-services.md +27 -12
  457. package/docs/coding-worker-byo-images.md +20 -0
  458. package/docs/coding-worker-isolation.md +105 -30
  459. package/docs/getting-started-gke.md +124 -137
  460. package/docs/getting-started-identity-provider.md +10 -1
  461. package/docs/getting-started.md +9 -0
  462. package/docs/jira-agents.md +17 -8
  463. package/docs/knowledge/blob-not-found-semantics.md +41 -0
  464. package/docs/knowledge/bounded-file-descriptor.md +52 -0
  465. package/docs/knowledge/delegation-admission-slots.md +52 -0
  466. package/docs/knowledge/http-dispatcher-bootstrap.md +61 -0
  467. package/docs/knowledge/import-rename-target.md +52 -0
  468. package/docs/knowledge/index.md +57 -0
  469. package/docs/knowledge/log.md +9 -0
  470. package/docs/knowledge/mcp-cancelled-task-precedence.md +41 -0
  471. package/docs/knowledge/mcp-http-prompt-discovery.md +41 -0
  472. package/docs/knowledge/mcp-schema-module-boundary.md +41 -0
  473. package/docs/knowledge/nullable-tool-uniqueness.md +52 -0
  474. package/docs/knowledge/overview-cli.md +74 -0
  475. package/docs/knowledge/overview-coding-proxy.md +72 -0
  476. package/docs/knowledge/overview-coding.md +72 -0
  477. package/docs/knowledge/overview-config.md +60 -0
  478. package/docs/knowledge/overview-core.md +72 -0
  479. package/docs/knowledge/overview-deploy.md +83 -0
  480. package/docs/knowledge/overview-mcp.md +61 -0
  481. package/docs/knowledge/overview-prisma.md +72 -0
  482. package/docs/knowledge/overview-providers.md +89 -0
  483. package/docs/knowledge/overview-sandbox.md +83 -0
  484. package/docs/knowledge/overview-surfaces.md +83 -0
  485. package/docs/knowledge/private-datastore-shared-boundary.md +54 -0
  486. package/docs/knowledge/proxy-settlement-projections.md +41 -0
  487. package/docs/knowledge/run-pricing-first-writer.md +43 -0
  488. package/docs/knowledge/schedule-claim-transaction.md +41 -0
  489. package/docs/knowledge/secret-entry-replica-contract.md +52 -0
  490. package/docs/knowledge/terminal-run-first-writer.md +41 -0
  491. package/docs/knowledge/transactional-agent-authorization.md +52 -0
  492. package/docs/knowledge/viewer-notify-write-isolation.md +30 -0
  493. package/docs/knowledge/warm-input-publication.md +30 -0
  494. package/docs/knowledge/workspace-staging-failure.md +54 -0
  495. package/docs/knowledge.md +54 -14
  496. package/docs/native-sandbox.md +119 -23
  497. package/docs/observability.md +4 -0
  498. package/docs/security-deployment.md +33 -2
  499. package/docs/slack-notifications.md +35 -19
  500. package/docs/tool-secrets.md +42 -10
  501. package/docs/viewer-api.openapi.yml +1 -1
  502. package/help/admin-viewer.md +24 -2
  503. package/help/architecture-agent.md +11 -6
  504. package/help/build-worker-image.md +3 -1
  505. package/help/builder-agent.md +19 -6
  506. package/help/code-review-agents.md +177 -6
  507. package/help/coding-packages.md +156 -6
  508. package/help/cost-attribution.md +9 -5
  509. package/help/creating-agents.md +14 -1
  510. package/help/dependency-cache.md +143 -0
  511. package/help/deploy-gke.md +15 -8
  512. package/help/errors/budget-exhausted.md +187 -0
  513. package/help/errors/budget-group-exhausted.md +5 -1
  514. package/help/errors/coding-stream-quota-exceeded.md +33 -0
  515. package/help/errors/coding-workspace-disk-exceeds-limit.md +39 -0
  516. package/help/errors/continuation-closed.md +9 -12
  517. package/help/errors/delegation-conflict.md +24 -0
  518. package/help/errors/file-list-truncated.md +45 -0
  519. package/help/errors/full-read-required.md +49 -0
  520. package/help/errors/github-account-not-allowed.md +4 -0
  521. package/help/errors/graph-incomplete.md +43 -0
  522. package/help/errors/graph-limit.md +43 -0
  523. package/help/errors/merge-conflict.md +49 -0
  524. package/help/errors/native-sandbox-gateway-error.md +20 -0
  525. package/help/errors/native-sandbox-launch-abandoned.md +21 -0
  526. package/help/errors/native-sandbox-worker-exited.md +3 -2
  527. package/help/errors/package-not-allowed-pending.md +52 -0
  528. package/help/errors/package-not-allowed.md +51 -0
  529. package/help/errors/package-refusals.md +43 -0
  530. package/help/errors/patch-cursor-malformed.md +31 -0
  531. package/help/errors/run-lost.md +39 -0
  532. package/help/errors/run-transcript-download.md +44 -0
  533. package/help/errors/secret-in-value.md +69 -0
  534. package/help/errors/target-merge.md +40 -0
  535. package/help/fix-merge-conflicts.md +73 -0
  536. package/help/github-app-setup.md +132 -0
  537. package/help/github.md +22 -0
  538. package/help/identity-and-access.md +2 -0
  539. package/help/jira.md +23 -6
  540. package/help/knowledge.md +16 -7
  541. package/help/local-repositories.md +1 -1
  542. package/help/low-power-mode.md +7 -1
  543. package/help/merge-order-check.md +5 -0
  544. package/help/native-capabilities.md +29 -1
  545. package/help/native-sandbox.md +15 -1
  546. package/help/operating-agents.md +5 -0
  547. package/help/package-audit-mode.md +91 -0
  548. package/help/related-pull-requests.md +10 -7
  549. package/help/resume-runs-locally.md +131 -0
  550. package/help/review-fix-rounds.md +33 -1
  551. package/help/sharing-agents.md +59 -0
  552. package/help/slack-notifications.md +19 -5
  553. package/help/status-comments.md +31 -0
  554. package/help/stop-run.md +58 -0
  555. package/help/troubleshooting/budgets.md +28 -0
  556. package/help/troubleshooting/coding-workers.md +78 -7
  557. package/package.json +17 -10
  558. package/prisma/migrations/20261016000000_registry_approved_accepted_advisories/migration.sql +3 -0
  559. package/prisma/migrations/20261016000001_mark_ready_when_passing/migration.sql +3 -0
  560. package/prisma/migrations/20261017000000_debug_trace_max_mb/migration.sql +4 -0
  561. package/prisma/migrations/20261018000000_dependency_cache/migration.sql +53 -0
  562. package/prisma/migrations/20261019000000_claude_max_output_tokens/migration.sql +5 -0
  563. package/prisma/migrations/20261021000000_pr_description_hash/migration.sql +4 -0
  564. package/prisma/migrations/20261022000000_dependency_snapshot_validation_metadata/migration.sql +4 -0
  565. package/prisma/migrations/20261023000000_package_audit_mode/migration.sql +27 -0
  566. package/prisma/migrations/20261024000000_budget_events/migration.sql +26 -0
  567. package/prisma/migrations/20261025000000_cache_aware_holds/migration.sql +9 -0
  568. package/prisma/migrations/20261026000000_budget_overrun/migration.sql +4 -0
  569. package/prisma/migrations/20261027000000_coding_run_request/migration.sql +1 -0
  570. package/prisma/migrations/20261028000000_native_gateway_review_reads/migration.sql +6 -0
  571. package/prisma/migrations/20261029000000_lead_survives_deploy/migration.sql +6 -0
  572. package/prisma/migrations/20261030000000_local_review_covered_sha/migration.sql +2 -0
  573. package/prisma/migrations/20261031000000_coding_run_budget_limit/migration.sql +9 -0
  574. package/prisma/migrations/20261101000000_run_transcripts/migration.sql +22 -0
  575. package/prisma/schema.prisma +311 -97
  576. package/dist/sandbox/generated/zod-to-json-schema.bundle.js +0 -5
@@ -2,29 +2,24 @@
2
2
  * PyPI registry adapter: parses PyPI allowlist syntax (PEP 503 names, PEP
3
3
  * 440 specifiers), routes proxy requests against the PEP 691 JSON Simple
4
4
  * API plus PEP 658 metadata files, restricts downloads to wheels, and
5
- * configures pip for the sandboxed worker to use the proxy as its only
6
- * index with binary-only installs.
5
+ * configures pip and uv for the sandboxed worker to use the proxy as their
6
+ * only index with binary-only installs.
7
7
  */
8
8
  import { clean as pepClean, compare as pepCompare, satisfies as pepSatisfies, validRange } from "@renovatebot/pep440";
9
9
  import { unzipSync, strFromU8 } from "fflate";
10
+ import { normalizeUvLock, pypiLockfilePlan } from "./pypi-plan.js";
11
+ import { isPypiProjectName, MAX_EXTRAS, normalizePypiName, parseExtras, requiredDists, requiresDist, } from "./pypi-requirements.js";
10
12
  import { AllowlistEntryError, RegistryError, } from "./types.js";
11
13
  const UPSTREAM = "https://pypi.org/simple/";
12
14
  const SIMPLE_JSON = "application/vnd.pypi.simple.v1+json";
13
15
  /** name, an optional `[extras]` group (checked separately), then the specifier. */
14
16
  const ENTRY = /^([A-Za-z0-9][A-Za-z0-9._-]*)\s*(?:\[([^\]]*)\])?\s*(.*)$/;
15
- /** The most extras one allowlist entry or dependency line may name. */
16
- export const MAX_EXTRAS = 32;
17
17
  const WHEEL_METADATA_LIMIT = 64 * 1024 * 1024;
18
+ export { isPypiProjectName, MAX_EXTRAS, normalizePypiName, parseExtras, requiredDists, requiresDist };
18
19
  function uploadTime(file) {
19
20
  const time = file["upload-time"] ? Date.parse(file["upload-time"]) : NaN;
20
21
  return Number.isNaN(time) ? null : new Date(time);
21
22
  }
22
- /** A valid PyPI project name (PEP 508): ASCII letters and digits, with
23
- * `.`, `_` and `-` allowed only between them. */
24
- const PROJECT_NAME = /^(?:[A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9._-]*[A-Za-z0-9])$/;
25
- export function isPypiProjectName(name) {
26
- return PROJECT_NAME.test(name);
27
- }
28
23
  function decoded(segment) {
29
24
  try {
30
25
  return decodeURIComponent(segment);
@@ -35,12 +30,12 @@ function decoded(segment) {
35
30
  }
36
31
  function projectName(segment) {
37
32
  const name = decoded(segment);
38
- if (!PROJECT_NAME.test(name))
33
+ if (!isPypiProjectName(name))
39
34
  throw new RegistryError(400, "wardby_bad_request", "not a valid Python project name");
40
35
  return normalizePypiName(name);
41
36
  }
42
- export function normalizePypiName(name) {
43
- return name.toLowerCase().replace(/[-_.]+/g, "-");
37
+ function isSdist(filename) {
38
+ return /\.(?:tar\.gz|zip)$/.test(filename);
44
39
  }
45
40
  /** Version from a wheel ("name-1.0-py3-none-any.whl") or sdist ("name-1.0.tar.gz") filename. */
46
41
  function versionOf(filename) {
@@ -49,139 +44,15 @@ function versionOf(filename) {
49
44
  const sdist = filename.match(/^.+?-(\d[^-]*)\.(?:tar\.gz|zip)$/);
50
45
  return sdist ? sdist[1] : null;
51
46
  }
52
- /** Normalized (PEP 685), sorted, de-duplicated extras from the text inside
53
- * `[...]`, or null when any is not a valid extra name (PEP 508: the same
54
- * shape as a project name), the list is empty, or it names too many. */
55
- export function parseExtras(list) {
56
- const items = list.split(",").map((item) => item.trim());
57
- if (items.length > MAX_EXTRAS || items.some((item) => !PROJECT_NAME.test(item)))
58
- return null;
59
- return [...new Set(items.map(normalizePypiName))].sort();
60
- }
61
- /** The string literals a marker compares `extra` to with `==` or `===`
62
- * (either operand order), normalized; null when the marker never mentions
63
- * `extra`. Tokenized, so a quoted value never counts as a clause. Fails
64
- * closed: when `extra` appears in any shape this does not fully recognise
65
- * (`extra in "a b"`, `extra.x`, a bare `extra`, an unbalanced quote), the
66
- * line is gated on no extra at all, so it is never followed. */
67
- function markerExtras(marker) {
68
- if (!/\bextra\b/.test(marker))
69
- return null;
70
- const tokens = marker.match(/"[^"]*"|'[^']*'|===|==|!=|<=|>=|~=|[A-Za-z_][A-Za-z0-9_.]*|\S/g) ?? [];
71
- const isLiteral = (token) => token !== undefined && token.length >= 2 && (token[0] === '"' || token[0] === "'");
72
- const isEquals = (token) => token === "==" || token === "===";
73
- // A lone quote (unterminated string) means the marker cannot be read.
74
- if (tokens.some((token) => token === '"' || token === "'"))
75
- return [];
76
- const found = [];
77
- let recognised = 0;
78
- for (let i = 0; i < tokens.length; i++) {
79
- if (tokens[i] !== "extra")
80
- continue;
81
- let value = null;
82
- if (isEquals(tokens[i + 1]) && isLiteral(tokens[i + 2]))
83
- value = tokens[i + 2].slice(1, -1);
84
- else if (isEquals(tokens[i - 1]) && isLiteral(tokens[i - 2]))
85
- value = tokens[i - 2].slice(1, -1);
86
- if (value === null)
87
- return [];
88
- recognised++;
89
- if (PROJECT_NAME.test(value))
90
- found.push(normalizePypiName(value));
91
- }
92
- // `extra` only inside quoted values: not a clause, but not one this reads either.
93
- return recognised === 0 ? [] : found;
94
- }
95
- const REQUIREMENT = /^([A-Za-z0-9][A-Za-z0-9._-]*)\s*(?:\[([^\]]*)\])?/;
96
- // Splits each Requires-Dist value only at its first `;` (the marker), which is
97
- // all wheel METADATA needs: no `name @ url` with a `;` in the URL, no comments
98
- // or line continuations as in a requirements file.
99
- function requirementLines(metadataText) {
100
- let self = null;
101
- const lines = [];
102
- for (const line of metadataText.split(/\r?\n/)) {
103
- // The headers end at the first truly empty line; the description
104
- // follows. A whitespace-only line is a folded header's continuation.
105
- if (line === "")
106
- break;
107
- if (line.startsWith("Name:")) {
108
- const name = line.slice("Name:".length).trim();
109
- if (self === null && PROJECT_NAME.test(name))
110
- self = normalizePypiName(name);
111
- continue;
112
- }
113
- if (!line.startsWith("Requires-Dist:"))
114
- continue;
115
- const value = line.slice("Requires-Dist:".length).trim();
116
- const match = value.match(REQUIREMENT);
117
- if (!match || !PROJECT_NAME.test(match[1]))
118
- continue;
119
- const semicolon = value.indexOf(";", match[0].length);
120
- lines.push({
121
- name: normalizePypiName(match[1]),
122
- // A malformed extras list still installs the package, just none of its extras.
123
- extras: match[2] === undefined ? [] : (parseExtras(match[2]) ?? []),
124
- gate: semicolon === -1 ? null : markerExtras(value.slice(semicolon + 1)),
125
- });
126
- }
127
- return { self, lines };
128
- }
129
- /**
130
- * The dependencies a wheel's METADATA declares for an install of the package
131
- * with `extras`: every line without an `extra == "…"` marker clause, plus the
132
- * lines whose marker names one of `extras` (other marker clauses are ignored,
133
- * so a requested extra's lines are included on every platform). A line gated
134
- * on any other extra is skipped. Each dependency carries the extras its line
135
- * asks of it (`uvicorn[standard]`); a line naming the package itself
136
- * (`celery[redis]; extra == "all"`) adds those extras to this install
137
- * instead. Names and extras are validated and normalized.
138
- *
139
- * Policy, not pip: a plain install (no `extras`) follows no extras at all,
140
- * neither its own (a self-referencing line) nor its dependencies' — each
141
- * dependency comes back bare, so extras are followed only along a chain that
142
- * starts at an allowlist entry naming extras.
143
- */
144
- export function requiredDists(metadataText, extras = []) {
145
- const { self, lines } = requirementLines(metadataText);
146
- const requested = new Set(extras.map(normalizePypiName));
147
- const plain = requested.size === 0;
148
- const included = (line) => line.gate === null || line.gate.some((extra) => requested.has(extra));
149
- for (let grew = !plain; grew;) {
150
- grew = false;
151
- for (const line of lines) {
152
- if (line.name !== self || !included(line))
153
- continue;
154
- for (const extra of line.extras) {
155
- if (requested.size >= MAX_EXTRAS || requested.has(extra))
156
- continue;
157
- requested.add(extra);
158
- grew = true;
159
- }
160
- }
161
- }
162
- const out = new Map();
163
- for (const line of lines) {
164
- if (line.name === self || !included(line))
165
- continue;
166
- const merged = out.get(line.name) ?? new Set();
167
- if (!plain)
168
- for (const extra of line.extras)
169
- if (merged.size < MAX_EXTRAS)
170
- merged.add(extra);
171
- out.set(line.name, merged);
172
- }
173
- return [...out].map(([name, merged]) => ({ name, extras: [...merged].sort() }));
174
- }
175
- /** Dependency names a METADATA declares for a plain install (no extras). */
176
- export function requiresDist(metadataText) {
177
- return requiredDists(metadataText).map((dependency) => dependency.name);
178
- }
179
47
  export const pypiAdapter = {
180
48
  id: "pypi",
181
49
  osvEcosystem: "PyPI",
182
50
  upstreamHosts: ["pypi.org", "files.pythonhosted.org"],
183
51
  collectExclude: [".venv", "venv", "__pycache__"],
184
52
  dependenciesInMetadata: false,
53
+ // uv records the proxy's URLs in a uv.lock it writes through it.
54
+ lockfiles: { names: ["uv.lock"], normalize: (content, registryUrl) => normalizeUvLock(content, registryUrl) },
55
+ lockfilePlan: pypiLockfilePlan,
185
56
  parseAllowlistEntry(raw) {
186
57
  const match = raw.trim().match(ENTRY);
187
58
  if (!match)
@@ -251,29 +122,37 @@ export const pypiAdapter = {
251
122
  });
252
123
  versions.set(version, entry);
253
124
  }
254
- // Keep only what renderMetadata and resolveFileMetadata read: wheels,
255
- // with the PEP 691 file fields pip uses.
125
+ // Keep only what renderMetadata and resolveFileMetadata read: wheels and
126
+ // sdists, with the PEP 691 file fields pip and uv use. An sdist is listed
127
+ // (so a uv.lock written through the proxy keeps it, as one written
128
+ // against PyPI does) but never served: downloads are wheels only, and
129
+ // PIP_ONLY_BINARY / UV_NO_BUILD keep both clients off it. Its metadata
130
+ // file is never offered.
256
131
  const raw = {
257
132
  name: index.name,
258
133
  files: index.files
259
- .filter((file) => file.filename.endsWith(".whl") && !file.yanked)
260
- .map((file) => ({
261
- filename: file.filename,
262
- url: file.url,
263
- ...(file.hashes ? { hashes: file.hashes } : {}),
264
- ...(file["upload-time"] ? { "upload-time": file["upload-time"] } : {}),
265
- ...(file.size !== undefined ? { size: file.size } : {}),
266
- ...(file["core-metadata"] !== undefined ? { "core-metadata": file["core-metadata"] } : {}),
267
- ...(file["dist-info-metadata"] !== undefined ? { "dist-info-metadata": file["dist-info-metadata"] } : {}),
268
- ...(file["requires-python"] !== undefined ? { "requires-python": file["requires-python"] } : {}),
269
- })),
134
+ .filter((file) => (file.filename.endsWith(".whl") || isSdist(file.filename)) && !file.yanked)
135
+ .map((file) => {
136
+ const wheel = file.filename.endsWith(".whl");
137
+ return {
138
+ filename: file.filename,
139
+ url: file.url,
140
+ ...(file.hashes ? { hashes: file.hashes } : {}),
141
+ ...(file["upload-time"] ? { "upload-time": file["upload-time"] } : {}),
142
+ ...(file.size !== undefined ? { size: file.size } : {}),
143
+ ...(wheel && file["core-metadata"] !== undefined ? { "core-metadata": file["core-metadata"] } : {}),
144
+ ...(wheel && file["dist-info-metadata"] !== undefined
145
+ ? { "dist-info-metadata": file["dist-info-metadata"] }
146
+ : {}),
147
+ ...(file["requires-python"] !== undefined ? { "requires-python": file["requires-python"] } : {}),
148
+ };
149
+ }),
270
150
  };
271
151
  return { name: normalizePypiName(index.name), versions, raw };
272
152
  },
273
153
  renderMetadata(meta, keep, keptFiles, proxyBase) {
274
154
  const index = meta.raw;
275
155
  const files = index.files
276
- .filter((file) => file.filename.endsWith(".whl"))
277
156
  .filter((file) => keep.has(versionOf(file.filename) ?? "") && keptFiles.has(file.filename))
278
157
  .map((file) => ({
279
158
  ...file,
@@ -327,6 +206,13 @@ export const pypiAdapter = {
327
206
  PIP_CACHE_DIR: cacheDir,
328
207
  PIP_DISABLE_PIP_VERSION_CHECK: "1",
329
208
  PIP_NO_INPUT: "1",
209
+ // uv reads none of pip's settings: without these it goes straight to pypi.org,
210
+ // which the sandbox can't reach. UV_NO_BUILD refuses sdists from the index (the
211
+ // wheels-only rule) and still builds the project itself.
212
+ UV_DEFAULT_INDEX: index.toString(),
213
+ UV_NO_BUILD: "1",
214
+ UV_CACHE_DIR: `${cacheDir}/uv`,
215
+ UV_PYTHON_DOWNLOADS: "never",
330
216
  },
331
217
  files: [],
332
218
  };
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Shared wording for package refusals served by the registry proxy. Pure (no provider imports) so the
3
+ * proxy, the run report, and the PR banner all read one table.
4
+ */
5
+ export declare const PACKAGE_NOT_ALLOWED = "wardby_package_not_allowed";
6
+ export declare const PACKAGE_NOT_ALLOWED_PENDING = "wardby_package_not_allowed_pending";
7
+ export declare const GRAPH_LIMIT = "wardby_graph_limit";
8
+ export declare const GRAPH_INCOMPLETE = "wardby_graph_incomplete";
9
+ export interface WalkProgress {
10
+ walked: number;
11
+ queued: number;
12
+ }
13
+ export declare function notAllowedMessage(name: string, ecosystem: string, hasAllowlist: boolean): string;
14
+ export declare function notAllowedPendingMessage(name: string, ecosystem: string, progress: WalkProgress): string;
15
+ export declare function graphLimitMessage(name: string, ecosystem: string, limit: number): string;
16
+ export declare function graphIncompleteMessage(name: string, ecosystem: string, progress: WalkProgress): string;
17
+ /** Static guidance for a stored refusal code (get_run `hint`); undefined for unknown codes. */
18
+ export declare function refusalHint(code: string): string | undefined;
19
+ /** Short parenthetical for the PR banner; undefined for unknown codes. */
20
+ export declare function refusalMeaning(code: string): string | undefined;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Shared wording for package refusals served by the registry proxy. Pure (no provider imports) so the
3
+ * proxy, the run report, and the PR banner all read one table.
4
+ */
5
+ export const PACKAGE_NOT_ALLOWED = "wardby_package_not_allowed";
6
+ export const PACKAGE_NOT_ALLOWED_PENDING = "wardby_package_not_allowed_pending";
7
+ export const GRAPH_LIMIT = "wardby_graph_limit";
8
+ export const GRAPH_INCOMPLETE = "wardby_graph_incomplete";
9
+ function packages(count) {
10
+ return `${count.toLocaleString("en-US")} package${count === 1 ? "" : "s"}`;
11
+ }
12
+ export function notAllowedMessage(name, ecosystem, hasAllowlist) {
13
+ const why = hasAllowlist
14
+ ? `${name} (${ecosystem}) is not on this agent's allowlist, and no allowlisted package this run resolved depends on it (a package added to the project directly must itself be allowlisted)`
15
+ : `${name} (${ecosystem}) is not allowed: this agent has no allowlist for ${ecosystem}`;
16
+ return `${why}. The agent's owner must allowlist it; retrying will not help.`;
17
+ }
18
+ export function notAllowedPendingMessage(name, ecosystem, progress) {
19
+ return (`${name} (${ecosystem}) is not on this agent's allowlist, and was not found among the allowlisted packages' dependencies after ` +
20
+ `${packages(progress.walked)}, at least ${progress.queued.toLocaleString("en-US")} more queued. ` +
21
+ `It may still be reachable, but the walk was cut short; retrying rarely helps. ` +
22
+ `The agent's owner must allowlist ${name} directly.`);
23
+ }
24
+ export function graphLimitMessage(name, ecosystem, limit) {
25
+ return (`${name} (${ecosystem}) wasn't reached before this run's dependency graph walk hit its limit of ${packages(limit)} ` +
26
+ `(REGISTRY_MAX_GRAPH_PACKAGES); retrying will not help. ` +
27
+ `The agent's owner must allowlist ${name} directly, or the operator can raise the limit.`);
28
+ }
29
+ export function graphIncompleteMessage(name, ecosystem, progress) {
30
+ return (`${name} (${ecosystem}) is still being checked against the allowed packages' dependency graph after ${packages(progress.walked)}, ` +
31
+ `${progress.queued.toLocaleString("en-US")} still queued; the walk timed out before checking it (usually a slow upstream for that package, or another request's walk running out of time). Please try again shortly.`);
32
+ }
33
+ const OWNER = "The agent's owner must allowlist the package directly; retrying will not help.";
34
+ const HINTS = {
35
+ [PACKAGE_NOT_ALLOWED]: `The package is not on the agent's allowlist and no allowlisted package the run resolved depends on it. ${OWNER}`,
36
+ [PACKAGE_NOT_ALLOWED_PENDING]: `The dependency walk ended before reaching the package. Retrying rarely helps. ${OWNER}`,
37
+ [GRAPH_LIMIT]: `The run's dependency graph walk hit REGISTRY_MAX_GRAPH_PACKAGES before reaching the package, so it may not be in the graph at all. Retrying will not help. The agent's owner must allowlist the package directly, or the operator can raise the limit.`,
38
+ [GRAPH_INCOMPLETE]: "The walk reached the package but timed out before checking its versions, usually a slow upstream. Try again shortly.",
39
+ wardby_version_filtered: "Every version the agent may use is newer than the release-age limit, outside the allowlisted range, or has a blocking advisory. Pin an older version, or the agent's owner can change the allowlist or policy.",
40
+ wardby_file_not_allowed: "The registry serves wheels only, and the allowed versions of this package publish only source distributions. Use a version or package that ships a wheel, or ask the agent's owner.",
41
+ };
42
+ /** What each refusal code means to someone reading the PR, without the registry's internals. */
43
+ const MEANINGS = {
44
+ [PACKAGE_NOT_ALLOWED]: "not on the allowlist, or only reachable through a refused package; the owner must allowlist it",
45
+ [PACKAGE_NOT_ALLOWED_PENDING]: "not reached before the dependency walk stopped; the owner must allowlist it",
46
+ [GRAPH_LIMIT]: "not reached before the run's dependency graph walk hit its package limit; the owner must allowlist it directly",
47
+ [GRAPH_INCOMPLETE]: "still being checked against the allowed packages' dependency graph; retry shortly",
48
+ wardby_version_filtered: "withheld: newer than the release-age limit, or a high-severity advisory",
49
+ wardby_file_not_allowed: "no wheel to install: only source distributions, which the registry does not serve",
50
+ wardby_lockfile_integrity_mismatch: "the lock file's integrity does not match the registry's",
51
+ wardby_upstream_error: "the upstream registry failed (transient)",
52
+ wardby_audit_unavailable: "the advisory check was unavailable (transient)",
53
+ };
54
+ /** Static guidance for a stored refusal code (get_run `hint`); undefined for unknown codes. */
55
+ export function refusalHint(code) {
56
+ return Object.hasOwn(HINTS, code) ? HINTS[code] : undefined;
57
+ }
58
+ /** Short parenthetical for the PR banner; undefined for unknown codes. */
59
+ export function refusalMeaning(code) {
60
+ return Object.hasOwn(MEANINGS, code) ? MEANINGS[code] : undefined;
61
+ }
@@ -0,0 +1,11 @@
1
+ import type { RegistryRefusalSummary } from "./report.js";
2
+ /** Untrusted names stay bounded code spans with mentions and markup defused. */
3
+ export declare function codeSpan(value: string): string;
4
+ /**
5
+ * The ⛔ blocked status (or, with `blocked: false`, the ⚠️ warning) for a run's refusals, as GitHub/Jira
6
+ * Markdown or, with `slack`, as Slack mrkdwn (already escaped).
7
+ */
8
+ export declare function packageRefusalWording(refusals: readonly RegistryRefusalSummary[], opts?: {
9
+ blocked?: boolean;
10
+ slack?: boolean;
11
+ }): string;
@@ -0,0 +1,56 @@
1
+ /** Fixed operator guidance from structured registry codes, never registry free text. */
2
+ import { GRAPH_INCOMPLETE, GRAPH_LIMIT, PACKAGE_NOT_ALLOWED, PACKAGE_NOT_ALLOWED_PENDING } from "./refusal-hints.js";
3
+ const HELP_URL = "https://github.com/wardby/wardby/blob/main/help/coding-packages.md";
4
+ const HELP_LABEL = "Help: package allowlist";
5
+ const ALLOWLIST = new Set([PACKAGE_NOT_ALLOWED, PACKAGE_NOT_ALLOWED_PENDING, GRAPH_LIMIT]);
6
+ /** Upstream or check failures that usually pass: a later run may succeed unchanged. */
7
+ const TRANSIENT = new Set([
8
+ GRAPH_INCOMPLETE,
9
+ "wardby_upstream_error",
10
+ "wardby_upstream_unavailable",
11
+ "wardby_audit_unavailable",
12
+ "wardby_download_idle",
13
+ ]);
14
+ /** Untrusted names stay bounded code spans with mentions and markup defused. */
15
+ export function codeSpan(value) {
16
+ // eslint-disable-next-line no-control-regex -- stripping untrusted control characters.
17
+ const withoutControls = value.replace(/[`\u0000-\u001f\u007f<>]/g, " ");
18
+ const safe = withoutControls.replace(/\s+/g, " ").replace(/@/g, "@​").slice(0, 120);
19
+ return `\`${safe}\``;
20
+ }
21
+ /**
22
+ * The ⛔ blocked status (or, with `blocked: false`, the ⚠️ warning) for a run's refusals, as GitHub/Jira
23
+ * Markdown or, with `slack`, as Slack mrkdwn (already escaped).
24
+ */
25
+ export function packageRefusalWording(refusals, opts = {}) {
26
+ const blocked = opts.blocked ?? true;
27
+ const bold = (text) => (opts.slack ? `*${text}*` : `**${text}**`);
28
+ const distinct = [...new Map(refusals.map((r) => [JSON.stringify([r.ecosystem, r.name, r.reason]), r])).values()];
29
+ const lines = distinct.slice(0, 10).map((r) => {
30
+ const code = /^wardby_[a-z_]+$/.exec(r.reason)?.[0] === r.reason ? ` (\`${r.reason}\`)` : "";
31
+ const guidance = ALLOWLIST.has(r.reason)
32
+ ? "not on this agent's allowlist; add it to `codingProfile.packageAllowlist`"
33
+ : r.reason === "wardby_version_filtered"
34
+ ? "no version passed the release-age, advisory or malware checks; wait or adjust `packagePolicy.minReleaseAgeDays`, accept an advisory in `packagePolicy.acceptedAdvisories`, or upgrade. Never bypass malware checks"
35
+ : r.reason === "wardby_lockfile_entry_unsupported"
36
+ ? "the lock file installs it from git or a URL, which the registry cannot verify; depend on a registry version instead"
37
+ : TRANSIENT.has(r.reason)
38
+ ? "the registry could not finish checking it (a slow or failing upstream); mention `@wardby` again to retry"
39
+ : "refused by the package registry; review the package policy and registry error before retrying";
40
+ return `- ${codeSpan(r.ecosystem)} ${codeSpan(r.name)}: ${guidance}${code}`;
41
+ });
42
+ if (distinct.length > 10)
43
+ lines.push(`- ${distinct.length - 10} more refused packages.`);
44
+ const text = [
45
+ blocked
46
+ ? `⛔ ${bold("Blocked by the package registry: no changes were made.")} Coding sub-runs couldn't install:`
47
+ : `⚠️ ${bold("Dependency install incomplete.")} Coding sub-runs couldn't install:`,
48
+ ...lines,
49
+ "",
50
+ "To unblock: use `update_agent` to update `codingProfile.packageAllowlist` or `codingProfile.packagePolicy` as appropriate (needs `packages:approve` or `agents:admin`), then mention `@wardby` again. ",
51
+ ].join("\n");
52
+ // Slack's three control characters; the names cannot hold < or > (codeSpan), but may hold &.
53
+ return opts.slack
54
+ ? `${text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")}<${HELP_URL}|${HELP_LABEL}>`
55
+ : `${text}[${HELP_LABEL}](${HELP_URL})`;
56
+ }
@@ -1,9 +1,3 @@
1
- /**
2
- * The one summary of a run's RegistryFetch rows, shared by get_run and the
3
- * coding executor's PR-body report so both deduplicate identically: served
4
- * packages collapse on ecosystem + name + version (a retried download is one
5
- * package), refusals on ecosystem + name + reason.
6
- */
7
1
  export interface RegistryFetchRow {
8
2
  ecosystem: string;
9
3
  name: string;
@@ -13,24 +7,92 @@ export interface RegistryFetchRow {
13
7
  sizeBytes: number | null;
14
8
  /** `-/plan` for a refusal a lockfile plan recorded (see PLAN_FETCH_FILENAME). */
15
9
  filename?: string | null;
10
+ /** The coding run that recorded it; needed when rows of several runs are summarized together. */
11
+ runId?: string;
16
12
  }
17
13
  /** The `filename` a lockfile plan records its refusals under. */
18
14
  export declare const PLAN_REFUSAL_FILENAME = "-/plan";
15
+ /**
16
+ * The `filename` of a refusal recorded only from an index listing: the
17
+ * registry answered a metadata request whose kept versions have no file the
18
+ * client may download (a PyPI project published only as an sdist, or whose
19
+ * every wheel is too new). The installer may only have looked at the name
20
+ * (a resolver exploring candidates, a dependency under a marker that does not
21
+ * apply), so such a refusal is weaker evidence than a refused download.
22
+ *
23
+ * The rule, the same for every ecosystem: a listing-only refusal is cleared
24
+ * like any refusal of a whole name, by any version of that name served after
25
+ * it in the same run (summarizeRegistryFetches). One left standing (nothing
26
+ * of that name was served) is marked `listingOnly`. It is reported to the
27
+ * agent (`wardby-packages`, GET /registry/-/refusals) and in get_run, and:
28
+ * - a coding sub-run failed: it counts toward ⛔ "Blocked by the package
29
+ * registry" and Slack `run_blocked` like any refusal (blockingRefusals in
30
+ * core/host-status.ts);
31
+ * - the run made no changes (and none failed): it is a ⚠️ line on the status,
32
+ * never ⛔ or `run_blocked` (a package whose only release has nothing but
33
+ * too-new wheels can be why);
34
+ * - a pull request resulted: it adds nothing, neither to the description's
35
+ * install warning and package section nor to the status (warningRefusals).
36
+ */
37
+ export declare const LISTING_REFUSAL_FILENAME = "-/listing";
38
+ /** The refusals a pull request description or a not-blocked status warns
39
+ * about: all but listing-only ones (see LISTING_REFUSAL_FILENAME). */
40
+ export declare function warningRefusals<T extends {
41
+ listingOnly?: boolean;
42
+ }>(refusals: readonly T[]): T[];
19
43
  /** Distinct refusals lockfile plans recorded (ecosystem + name + version +
20
44
  * reason), however often the lockfile was planned. */
21
45
  export declare function countPlanRefusals(rows: readonly RegistryFetchRow[]): number;
46
+ /** A served row's `reason` when serving it waived blocking advisories the
47
+ * run's packagePolicy.acceptedAdvisories lists: this prefix, then the
48
+ * waived advisory ids joined by ", ". */
49
+ export declare const ACCEPTED_ADVISORIES_PREFIX = "accepted advisories: ";
50
+ /** The served-row reason recording these waived advisory ids, or undefined
51
+ * when none were waived (the row then has no reason, as before). */
52
+ export declare function acceptedAdvisoriesReason(ids: readonly string[]): string | undefined;
53
+ /** The waived advisory ids a served row's reason records (empty for any
54
+ * other reason, or none). */
55
+ export declare function parseAcceptedAdvisories(reason: string | null | undefined): string[];
22
56
  export interface RegistryPackageSummary {
23
57
  ecosystem: string;
24
58
  name: string;
25
59
  version: string;
26
60
  /** Bytes served (RegistryFetch.sizeBytes); null when not recorded. */
27
61
  size: number | null;
62
+ /** Blocking advisories waived to serve it (the run's accepted set);
63
+ * absent when none were. */
64
+ acceptedAdvisories?: string[];
28
65
  }
29
- export interface RegistryRefusalSummary {
66
+ export type RegistryRefusalSummary = {
30
67
  ecosystem: string;
31
68
  name: string;
32
69
  reason: string;
33
- }
70
+ /** Static guidance for the code (shared with the proxy message and the PR banner); absent for unknown codes. */
71
+ hint?: string;
72
+ /** Every row behind it was an index listing's (LISTING_REFUSAL_FILENAME); absent otherwise. */
73
+ listingOnly?: true;
74
+ };
75
+ /**
76
+ * Rows must be in the order they were recorded (createdAt ascending): a
77
+ * refusal followed by a served row for the same package in the same run
78
+ * (`runId`; rows without one count as one run) no longer counts, since that
79
+ * run's install went on to get it (npm retrying a temporary
80
+ * `wardby_upstream_error`, an audit that was briefly unavailable). A package
81
+ * served to a sibling sub-run never clears another run's refusal. A refusal
82
+ * of one version is cleared only by that version being served; a refusal of
83
+ * the whole name (no version) by any version of it. A refusal recorded after
84
+ * the package was served still counts. Cleared refusals are left out of every
85
+ * surface (get_run, the PR warning, the status comments, Slack), because each
86
+ * of them tells the user the install did not finish, which is then untrue.
87
+ *
88
+ * Known limits: order is by record time, and a served row is recorded when
89
+ * its download finishes (no start time is stored), so a download that started
90
+ * before a concurrent refusal of the same version in the same run, and
91
+ * finished after it, clears it; rows in the same millisecond have no
92
+ * tiebreaker. Matching is per version, not per file, so serving any file of
93
+ * a PyPI release clears a refused file of that release (another platform's
94
+ * too-new wheel).
95
+ */
34
96
  export declare function summarizeRegistryFetches(rows: readonly RegistryFetchRow[]): {
35
97
  packages: RegistryPackageSummary[];
36
98
  packageRefusals: RegistryRefusalSummary[];
@@ -1,5 +1,41 @@
1
+ /**
2
+ * The one summary of a run's RegistryFetch rows, shared by get_run and the
3
+ * coding executor's PR-body report so both deduplicate identically: served
4
+ * packages collapse on ecosystem + name + version (a retried download is one
5
+ * package), refusals on ecosystem + name + reason.
6
+ */
7
+ import { ACCEPTED_ADVISORY_ID } from "./allowlist.js";
8
+ import { refusalHint } from "./refusal-hints.js";
1
9
  /** The `filename` a lockfile plan records its refusals under. */
2
10
  export const PLAN_REFUSAL_FILENAME = "-/plan";
11
+ /**
12
+ * The `filename` of a refusal recorded only from an index listing: the
13
+ * registry answered a metadata request whose kept versions have no file the
14
+ * client may download (a PyPI project published only as an sdist, or whose
15
+ * every wheel is too new). The installer may only have looked at the name
16
+ * (a resolver exploring candidates, a dependency under a marker that does not
17
+ * apply), so such a refusal is weaker evidence than a refused download.
18
+ *
19
+ * The rule, the same for every ecosystem: a listing-only refusal is cleared
20
+ * like any refusal of a whole name, by any version of that name served after
21
+ * it in the same run (summarizeRegistryFetches). One left standing (nothing
22
+ * of that name was served) is marked `listingOnly`. It is reported to the
23
+ * agent (`wardby-packages`, GET /registry/-/refusals) and in get_run, and:
24
+ * - a coding sub-run failed: it counts toward ⛔ "Blocked by the package
25
+ * registry" and Slack `run_blocked` like any refusal (blockingRefusals in
26
+ * core/host-status.ts);
27
+ * - the run made no changes (and none failed): it is a ⚠️ line on the status,
28
+ * never ⛔ or `run_blocked` (a package whose only release has nothing but
29
+ * too-new wheels can be why);
30
+ * - a pull request resulted: it adds nothing, neither to the description's
31
+ * install warning and package section nor to the status (warningRefusals).
32
+ */
33
+ export const LISTING_REFUSAL_FILENAME = "-/listing";
34
+ /** The refusals a pull request description or a not-blocked status warns
35
+ * about: all but listing-only ones (see LISTING_REFUSAL_FILENAME). */
36
+ export function warningRefusals(refusals) {
37
+ return refusals.filter((refusal) => refusal.listingOnly !== true);
38
+ }
3
39
  /** Distinct refusals lockfile plans recorded (ecosystem + name + version +
4
40
  * reason), however often the lockfile was planned. */
5
41
  export function countPlanRefusals(rows) {
@@ -11,21 +47,92 @@ export function countPlanRefusals(rows) {
11
47
  }
12
48
  return keys.size;
13
49
  }
50
+ /** A served row's `reason` when serving it waived blocking advisories the
51
+ * run's packagePolicy.acceptedAdvisories lists: this prefix, then the
52
+ * waived advisory ids joined by ", ". */
53
+ export const ACCEPTED_ADVISORIES_PREFIX = "accepted advisories: ";
54
+ /** The served-row reason recording these waived advisory ids, or undefined
55
+ * when none were waived (the row then has no reason, as before). */
56
+ export function acceptedAdvisoriesReason(ids) {
57
+ return ids.length > 0 ? `${ACCEPTED_ADVISORIES_PREFIX}${ids.join(", ")}` : undefined;
58
+ }
59
+ /** The waived advisory ids a served row's reason records (empty for any
60
+ * other reason, or none). */
61
+ export function parseAcceptedAdvisories(reason) {
62
+ if (!reason?.startsWith(ACCEPTED_ADVISORIES_PREFIX))
63
+ return [];
64
+ return reason
65
+ .slice(ACCEPTED_ADVISORIES_PREFIX.length)
66
+ .split(",")
67
+ .map((id) => id.trim())
68
+ .filter((id) => ACCEPTED_ADVISORY_ID.test(id));
69
+ }
70
+ /**
71
+ * Rows must be in the order they were recorded (createdAt ascending): a
72
+ * refusal followed by a served row for the same package in the same run
73
+ * (`runId`; rows without one count as one run) no longer counts, since that
74
+ * run's install went on to get it (npm retrying a temporary
75
+ * `wardby_upstream_error`, an audit that was briefly unavailable). A package
76
+ * served to a sibling sub-run never clears another run's refusal. A refusal
77
+ * of one version is cleared only by that version being served; a refusal of
78
+ * the whole name (no version) by any version of it. A refusal recorded after
79
+ * the package was served still counts. Cleared refusals are left out of every
80
+ * surface (get_run, the PR warning, the status comments, Slack), because each
81
+ * of them tells the user the install did not finish, which is then untrue.
82
+ *
83
+ * Known limits: order is by record time, and a served row is recorded when
84
+ * its download finishes (no start time is stored), so a download that started
85
+ * before a concurrent refusal of the same version in the same run, and
86
+ * finished after it, clears it; rows in the same millisecond have no
87
+ * tiebreaker. Matching is per version, not per file, so serving any file of
88
+ * a PyPI release clears a refused file of that release (another platform's
89
+ * too-new wheel).
90
+ */
14
91
  export function summarizeRegistryFetches(rows) {
15
92
  const packages = new Map();
93
+ // Each refusal with the run + version pairs it still stands for (version "" = the whole name).
16
94
  const refusals = new Map();
17
95
  for (const row of rows) {
18
96
  if (row.outcome === "served" && row.version) {
19
97
  const key = `${row.ecosystem}\0${row.name}\0${row.version}`;
20
- if (!packages.has(key))
21
- packages.set(key, { ecosystem: row.ecosystem, name: row.name, version: row.version, size: row.sizeBytes });
98
+ let summary = packages.get(key);
99
+ if (!summary) {
100
+ summary = { ecosystem: row.ecosystem, name: row.name, version: row.version, size: row.sizeBytes };
101
+ packages.set(key, summary);
102
+ }
103
+ // Every file of the version (a retry, a wheel and its metadata) may
104
+ // record the waiver; the package lists each advisory once.
105
+ const accepted = parseAcceptedAdvisories(row.reason);
106
+ if (accepted.length > 0)
107
+ summary.acceptedAdvisories = [...new Set([...(summary.acceptedAdvisories ?? []), ...accepted])];
108
+ for (const [refusalKey, refusal] of refusals) {
109
+ if (refusal.summary.ecosystem !== row.ecosystem || refusal.summary.name !== row.name)
110
+ continue;
111
+ refusal.versions.delete(`${row.runId ?? ""}\0`);
112
+ refusal.versions.delete(`${row.runId ?? ""}\0${row.version}`);
113
+ if (refusal.versions.size === 0)
114
+ refusals.delete(refusalKey);
115
+ }
22
116
  }
23
117
  else if (row.outcome === "refused") {
24
118
  const reason = row.reason ?? "refused";
25
119
  const key = `${row.ecosystem}\0${row.name}\0${reason}`;
26
- if (!refusals.has(key))
27
- refusals.set(key, { ecosystem: row.ecosystem, name: row.name, reason });
120
+ let refusal = refusals.get(key);
121
+ if (!refusal) {
122
+ const hint = refusalHint(reason);
123
+ refusal = {
124
+ summary: { ecosystem: row.ecosystem, name: row.name, reason, ...(hint ? { hint } : {}) },
125
+ versions: new Set(),
126
+ listing: true,
127
+ };
128
+ refusals.set(key, refusal);
129
+ }
130
+ refusal.versions.add(`${row.runId ?? ""}\0${row.version ?? ""}`);
131
+ refusal.listing &&= row.filename === LISTING_REFUSAL_FILENAME;
28
132
  }
29
133
  }
30
- return { packages: [...packages.values()], packageRefusals: [...refusals.values()] };
134
+ return {
135
+ packages: [...packages.values()],
136
+ packageRefusals: [...refusals.values()].map(({ summary, listing }) => listing ? { ...summary, listingOnly: true } : summary),
137
+ };
31
138
  }