Skip to content

Fix vendor plan granting vlt-refused packages (#1909) - #1947

Merged
Mikola Lysenko (mikolalysenko) merged 4 commits into
mainfrom
agent/fix-vendor-plan-candidate-verdicts
Nov 2, 2026
Merged

Mikola Lysenko (mikolalysenko) merged 4 commits into
mainfrom
agent/fix-vendor-plan-candidate-verdicts

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Nov 1, 2026 •

Copy link
Copy Markdown
Collaborator

LLM Description written by Claude Code:claude-opus-5-5

Fixes #1909

Summary

vendor fetches service downloads ahead of its main loop (plan_service_downloads), and decided which packages to plan with a hand-written copy of the loop's candidate checks. That copy drifted. It never saw the vlt preflight's refusals, so in a vlt project a package the loop refuses still got a download grant. A grant can start a server-side build and counts against quota. Refused cases include a store copy with bundleDependencies or build steps, a gitignored vendor dir, a member stray lock, or a route refusal.

Root cause

The candidate walk (fanning release-variant base purls out to their qualified records, once per base) and the per-candidate gates (ledger-entry-missing refusal, installed-variant probe, superseded-install stand-in, ambiguous variants, Bun refusal, vlt refusal, linked vendor dir) were written twice. They were written once in the loop and once in the plan, and only the loop's copy got the vlt refusal and the vendor_variant_ambiguous outcome.

Change (vendor.rs only)

  • candidate_groups is the one candidate walk, used by both the plan and the loop.
  • CandidateGates::judge returns one CandidateVerdict per candidate: Skip, Ambiguous, Refuse { code, detail } or Vendor { record, superseded }. The loop renders Ambiguous/Refuse exactly as before (same codes, details and matched/fetch_failed bookkeeping). The plan keeps only Vendor verdicts.
  • The plan keeps its plan-only skips: a hosted takeover, an npm artifact the ledger anchors, and an in-sync re-run. The loop answers these without the service.
  • UNINSTALLED_REL replaces four spellings of .socket/vendor/.uninstalled.
  • The loop still judges against its live ledger copy (its per-package persists update it), so the ledger is passed per call, not stored in the gates. Ambiguous candidates are collected and joined to fetch_failed after the loop. Nothing reads fetch_failed in between, so the end result is the same.

Test evidence

Regression test plan_gate_tests::the_plan_leaves_out_a_package_the_vlt_preflight_refuses: a vlt project (vlt-lock.json + store copy) whose store package.json gains bundleDependencies. vlt_vendor_preflight_pairs refuses it with vendor_bundled_deps_unsupported and the plan must not include it.

  • Red on main (same fixture, old plan signature): panicked with the vlt-refused package is never planned (the plan returned the uuid).
  • Green on this branch.

Local runs on 539f228:

  • cargo fmt --all -- --check: clean
  • cargo clippy --workspace --all-features -- -D warnings: clean
  • cargo test -p socket-patch-cli --lib commands::vendor: 75 passed
  • cargo test --workspace --all-features --no-fail-fast: 236 targets OK. 13 failures in 4 targets, all environmental and outside this diff. 11 are permission-failure tests that cannot fail as root (the sandbox runs as uid 0): core copy_tree/vlt_heal/pypi_poetry/pypi_requirements, in_process_redirect, repair. 2 are e2e_bun_lockb tests that reach the live patch API (no network here; see Fix bun.lockb nightly e2e reaching the live blob API #1905).
  • --include-ignored e2e: e2e_vendor_npm_build 43/43, e2e_vendor_pypi_build 57/57, e2e_vendor_bun_build 40/40, e2e_vendor_vlt_build 66/66 (vlt binary absent, so the real-vlt legs self-skip), e2e_vendor_gem_build 46/46.

Per-issue checklist

/code-review high findings not fixed here

  • The dry-run takeover preview (takeover_dry_refusal) is passed pkg_source, not the superseded stand-in. This is pre-existing on main (unchanged line) and a separate behaviour question, so it's left for a follow-up.
  • The plan's three plan-only skips stay in the plan: they describe where the loop answers without the service, not a refusal, so they have no loop twin to share.
  • judge still hashes a representative file in both the plan and the loop. Main already did this, so this PR doesn't add the cost.

Related, not in scope: #1839 (one Bun + vlt refusal map across every vendored caller).

https://claude.ai/code/session_011f9aeS19dxVdN1MB6299Hy


Generated by Claude Code

Assisted-by: Claude Code:claude-opus-5-5
`vendor` fetches service downloads ahead of its main loop, and decided
which packages to fetch with its own copy of the loop's candidate
checks. That copy never saw the vlt preflight's refusals, so in a vlt
project a package the loop refuses (a store copy with
bundleDependencies or build steps, a gitignored vendor dir, a route
refusal) was still granted a download, which can start a server-side
build and counts against quota.

Both now judge each candidate through one CandidateGates::judge
(skip, ambiguous variant, Bun/vlt/linked-dir refusal, or vendor), and
the candidate walk over release-variant groups is shared too. The
`.socket/vendor/.uninstalled` stand-in path is one constant.

Fixes #1909

Assisted-by: Claude Code:claude-opus-5-5
The variant plan test builds its gates through the same helper as
the other plan tests, and the helper takes the refused set instead of
leaking an empty one.

Assisted-by: Claude Code:claude-opus-5-5
Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review November 1, 2026 19:03
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 539f228. Configure here.

@mikolalysenko Mikola Lysenko (mikolalysenko) added the Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review label Nov 1, 2026
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[burn-down agent] Ready for review at 539f228.

  • CI: 82/82 check runs on 539f228 green (success/skipped/neutral); no main-wide failures.
  • Bugbot: reviewed 539f228, no findings; no open review threads.
  • Mergeable, no conflicts with main; no CHANGELOG.md change.

Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Final review brief

What it does: Fixes #1909. vendor's download plan kept its own copy of the vendor loop's candidate checks, and that copy skipped the vlt preflight refusals. So in a vlt project, a package the loop would refuse could still get a download grant, which can start a server-side build and uses quota. The PR moves the checks into one CandidateGates::judge, which returns Skip / Ambiguous / Refuse / Vendor and is shared by the plan and the loop, so the two can't drift again.

Risk: low. This is an extraction inside one file. The checks run in the same order with the same error codes and details. The only behavior change is the intended one: the plan now leaves out vlt-refused candidates. Ambiguous candidates are now added to fetch_failed after the loop instead of during it. That is equivalent: nothing reads the set in between, and each candidate is in only one group.

Look here:

Verified:

  • Traced old against new line by line.
  • cargo test -p socket-patch-cli --lib commands::vendor: 75 passed.
  • clippy (lib/bins, test profile): clean.
  • Mutation checks:
    • Disabling the vlt gate makes the new test fail, so the test covers the fix.
    • Turning Ambiguous into Skip makes the existing variant-probe test fail.
  • CHANGELOG.md is untouched.
  • CI: ci-ok and clippy are green on the head.

Changes I made: None.

Open questions: None.

  • No lib unit test covers the loop's own vlt refusal output; that gap predates this PR, and the e2e suites likely cover it.
  • The dry-run takeover preview still gets pkg_source. This already happens on main, and the description flags it as a follow-up.

Auto-merge is armed: approving sends this straight to the merge queue.


Generated by Claude Code

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[agent] Reviewed 539f228141aa937e428b9504eb6bbaea36131bf6. Checked shared candidate gates through vendor execution and service planning, variant ambiguity and vlt refusal ordering. No major correctness issues found. Validation: source and regression-test review plus the available exact-head CI checks; no local test pass is claimed.

@mikolalysenko
Mikola Lysenko (mikolalysenko) added this pull request to the merge queue Nov 2, 2026
Merged via the queue into main with commit 9b71c50 Nov 2, 2026
82 checks passed
@mikolalysenko
Mikola Lysenko (mikolalysenko) deleted the agent/fix-vendor-plan-candidate-verdicts branch November 2, 2026 16:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

vendor's download plan re-walks the vendor loop's candidate selection, and the copy skips the vlt refusal and the ambiguous-variant check

3 participants