Repository navigation
Description
[agent] Filed by the scheduled architecture audit (audit-cli-tests). Register: register comment.
Kind: refactor. Register ID: T35.
Problem
scan/vendor_flow.rs asks "which selected purls would the wet run refuse?" through two separate functions. Each one loads the ledger and runs the whole preflight set on its own:
preview_vendorL193-L270 runsload_state, then the Bun, vlt and npm-lock preflights,hosted_pnpm_refusals, the takeover refusals andpypi_refusals, and returnsPreviewVerdict::WouldRefuserows.preflight_refused_purlsL349-L379 is the planning pass that keeps refused new patches from taking a--max-new-patchesslot. It runsload_state, the Bun, vlt and npm-lock preflights,hosted_pnpm_refusalsandgem_takeover_preview_refusals.
The two have drifted. The planning copy has no pypi_refusals (L205-L210 vs L370-L376).````` So a PyPI patch the backend refuses before downloading (pypi_no_requirements, `pypi_uv_no_sources`) still takes a rollout slot, while the preview of the same run shows it as `would_refuse`. That is my reading of the code; I have not executed it.
A dry run calls both functions back to back: scan/mod.rs calls the planning pass at L3306, then the preview at L3431 (the JSON path does the same at L772 and L790). So every preflight, the gem takeover probe and hosted_claimed_purls' lockfile discovery run two or three times.
All three callers of preview_vendor (scan/mod.rs L3425,`````` scan JSON L785 and get.rs L2287) also compute gem_takeover_preview_refusals and `patch_server_origins` themselves before passing them in.
Proposed change
- Keep one function:
preview_vendor(common: &GlobalArgs, selected) -> VendorPreview, which computes origins and takeover refusals itself. - The planning pass becomes
preview.rows.iter().filter(|r| matches!(r.verdict, WouldRefuse{..})). Deletepreflight_refused_purls. - Compute the preview once per run and reuse it for planning and rendering.
- Assert the preview unit tests on
PreviewVerdictand delete the test-onlypreview_vendor_jsonshape (L1409-L1443).
Size
scan/vendor_flow.rs, scan/mod.rs, get.rs. About −80 / +30 lines.
Acceptance criteria
- Refusal codes and preview rows are unchanged for Bun, vlt, npm-lock, pnpm and gem.
- A PyPI patch refused by
pypi_refusalsno longer takes a--max-new-patchesslot. Add a unit test for this. - The
e2e_rollout*and vendored scan suites stay green. - Overlaps T05/Run the Bun and vlt vendored preflights as one vendor_preflight with one per-purl refusal map #1839 (which merges the Bun and vlt preflights); either can land first.
Activity
audit-cli-tests: scan rollout..vendored_backend, T35-T42, file #1908-#…
mikolalysenko commentedon Nov 1, 2026
[agent] Triaged: priority:p1 (the drift skips the PyPI
pypi_refusalsin scan's vendored planning pass, so a refused PyPI patch can take a--max-new-patchesslot). Not a duplicate; no open PR touchespreflight_refused_purls. Distinct root cause from #1909 (vendor.rs download plan vs scan/vendor_flow.rs planning), so not clustered.Generated by Claude Code
mikolalysenko commentedon Nov 1, 2026
[agent] Claiming this issue (single-issue cluster; root cause: scan's vendored planning pass re-runs the preflight set by hand instead of reading the preview, and its copy dropped
pypi_refusals). Branch: agent/fix-scan-vendor-preview-planning. Claim-ID: 2026-11-01T12:21:12Z-780847Generated by Claude Code
mikolalysenko commentedon Nov 1, 2026
[agent] Draft PR: #1930
Generated by Claude Code
Fix vendored planning ignoring PyPI refusals
Tidy the shared vendored preview