Repository navigation
Decide vendored-entry liveness through one discovery verdict - #1050
Merged
Merged
Conversation
The prune GC and scan's ledger supplement asked a per-backend probe (npm, cargo, pypi-requirements only) whether a vendored entry is still consumed, while vex and vendor --check judge liveness by discovery. Give discovery the evidence for a tri-state in-use answer for every ecosystem: - `Discovery::read` logs every guarded read, tagged with the ecosystem whose extractor read it, so "nothing wires it" can mean "unused" only once a lockfile of that ecosystem was actually read and parsed. - `Discovery::withheld` records wiring an extractor withholds as a ref although a file still routes through the patch: npm's and bun's in-lock contests and cargo copies whose lock lags (another tag, or an untagged override). Not attested, but still wiring the GC must keep. - `vendor_entry_in_use` = live, contested or withheld -> Some(true); a read lockfile of the ecosystem and nothing wiring it -> Some(false); no lockfile, or one that cannot be read or parsed -> None. JVM entries answer from their own layout (`entry_references`). Audit B19 (§3.B "is this vendored entry live"). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…re readers The vendored Maven backend treated any occurrence of its repository id in pom.xml as wiring, and the NuGet backend any occurrence of its source key in nuget.config, so a commented-out (or profile-scoped) repository or source took the in-sync hot path and the build resolved the unpatched package. Use the same masking the forward writers already use: `find_wireable_anchor` (comments and <profiles> masked) for the pom, and `parse_config_source_keys(blank_comments(..))` for the nuget.config <packageSources>. Audit B61. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
`vendor --check` reported committed JVM trees (maven2, gradle, coursier, their indexes, the generated sbt file) as `vendor_ledger_missing` only when the whole ledger was empty, so a project that also vendors another ecosystem never noticed a lost JVM half. Fire whenever no ledger entry is a JVM entry: an empty ledger still refuses as before, otherwise the orphan is recorded as a failed artifact event beside the other checks. The path list moves to one `jvm::apply::LEDGER_OWNED_PATHS` (replacing the CLI's inline list and `coursier_tree::ORPHAN_PATHS`), and the JVM path tables spell the tree roots through their named constants. Audit B62. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…gh one in-use verdict `dispatch_in_use_one` answered "is this vendored entry still consumed" for npm, cargo and the pypi requirements flavor only and kept every other entry (gem, golang, composer, nuget, maven/jvm, uv, poetry, pdm, pipenv, hatch, pylock) forever: `scan --prune` could never reclaim them and scan's ledger supplement resurrected them as discovered packages, never reporting `vendor_ledger_entry_unwired`, while `vendor --check` and vex already called them dead. Every caller now asks `Discovery::vendor_entry_in_use`: - run_vendor_gc (pass b) takes all verdicts from one discovery of the project before it reverts anything; - vendored_ledger_supplement reads the run's ProjectContext discovery (scan and get pass their context); - vendor --check's "dependency removed" remedy. Deleted: `dispatch_in_use_one` (CLI) and the per-backend dispatchers `vendor::npm_flavor::vendored_entry_in_use`, `vendor::cargo::vendored_entry_in_use`, `vendor::pypi::vendored_entry_in_use` and `pypi_requirements::requirements_entry_in_use` (4 in-use oracles -> 1; the pnpm/vlt structural probes stay for their revert guards). Their tests now assert the discovery verdict, with lock fixtures carrying the version an install records. Audit B19. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ibutable The prune GC's in-use verdict read every vendored ref that attestation dropped as unused unless an extractor had recorded it as withheld, and only npm's in-pair contest, bun and cargo did. Every other unattributable drop (an unpatched copy in the same lock, npm's legacy `dependencies` mirror, a non-registry nested copy, vlt's other instances and bundled copies, a yarn git block, a version-less Go replace) made `scan --prune` unwire a vendored patch the package manager still installs. `vendor_entry_in_use` now keeps an entry whenever a file that mentions its uuid also carries a DIAG_REF_UNATTRIBUTABLE diagnostic. That one rule covers every extractor, so the npm and bun `withheld` pushes are gone; cargo's relock-pending record stays because cargo reports that case as DIAG_REF_INVALID. Mentions rejected as invalid (stale bun.lockb pool strings, vlt edge specs, cargo's leftover [patch] after a hosted takeover) still read as unused. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…e GC Each successful, non-dry-run vendor through the test_support wrappers now asks discovery's in-use verdict about the entry it just wrote and fails on Some(false). Every backend suite (npm flavors, pnpm, yarn, bun, vlt, cargo, composer, gem, golang, maven, nuget and every pypi flavor) thus pins the keep side of the GC against its real output. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The B62 test now checks that the vendor_ledger_missing event names a LEDGER_OWNED_PATHS member that exists, not just any maven-tagged event. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
marked this pull request as ready for review
October 7, 2026 17:06
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.
Autofix Details
Bugbot Autofix prepared fixes for both issues found in the latest run.
- ✅ Fixed: Gradle JVM prune may fail open
- Created gradle::references_checked that returns Result<bool, String> and fails when files cannot be read, ensuring fail-closed behavior consistent with other JVM backends.
- ✅ Fixed: Empty-wiring JVM entries skip layout check
- Modified is_jvm_entry to recognize empty-wiring maven-ecosystem entries with valid maven purls, preventing them from incorrectly falling through to generic maven discovery.
Or push these changes by commenting:
@cursor push 002ca36d71
Preview (002ca36d71)
diff --git a/crates/socket-patch-core/src/vendor/jvm/apply.rs b/crates/socket-patch-core/src/vendor/jvm/apply.rs
--- a/crates/socket-patch-core/src/vendor/jvm/apply.rs
+++ b/crates/socket-patch-core/src/vendor/jvm/apply.rs
@@ -36,13 +36,20 @@
};
/// Whether `entry` was written by this backend: it has wiring and every
-/// record is one of this backend's kinds.
+/// record is one of this backend's kinds, OR it's a maven-ecosystem entry
+/// with a valid maven purl (covers empty-wiring reconstructed entries).
pub fn is_jvm_entry(entry: &VendorEntry) -> bool {
- !entry.wiring.is_empty()
+ if !entry.wiring.is_empty()
&& entry
.wiring
.iter()
.all(|w| KINDS.contains(&w.kind.as_str()))
+ {
+ return true;
+ }
+ // Empty-wiring entries (e.g., from repair/reconstruction) that are
+ // maven-ecosystem with valid maven purls should still be treated as JVM entries
+ entry.ecosystem == "maven" && parse_maven_purl(&entry.base_purl).is_some()
}
/// Offline inputs have not been authenticated by independent registry checksums.
@@ -827,7 +834,7 @@
}
let (maven, gradle) = sides(&entry.wiring);
(maven && maven_reactor::wired_checked(&read, &c).unwrap_or(true))
- || (gradle && gradle::references(&read, &c))
+ || (gradle && gradle::references_checked(&read, &c).unwrap_or(true))
}
/// The liveness proof `vex` needs: every half of the entry is wired, and
diff --git a/crates/socket-patch-core/src/vendor/jvm/gradle.rs b/crates/socket-patch-core/src/vendor/jvm/gradle.rs
--- a/crates/socket-patch-core/src/vendor/jvm/gradle.rs
+++ b/crates/socket-patch-core/src/vendor/jvm/gradle.rs
@@ -1197,6 +1197,42 @@
indexed && applied
}
+/// Checked variant of [`references`] that returns an error when files
+/// cannot be read, for fail-closed GC ([`crate::vex::discover::Discovery::vendor_entry_in_use`]).
+pub fn references_checked(read: ReadFn<'_>, c: &Coords<'_>) -> Result<bool, String> {
+ let gav = format!("{}:{}:{}", c.group_id, c.artifact_id, c.version);
+ let index_bytes = read(INDEX_REL).ok_or_else(|| format!("cannot read {}", INDEX_REL))?;
+ let index_text = String::from_utf8(index_bytes)
+ .map_err(|_| format!("{} is not valid UTF-8", INDEX_REL))?;
+ let rows = index_rows(&index_text)
+ .ok_or_else(|| format!("{} could not be parsed", INDEX_REL))?;
+ let indexed = rows.iter().any(|r| {
+ let cols: Vec<&str> = r.split('\t').collect();
+ cols.first() == Some(&gav.as_str()) && cols.get(3) == Some(&c.uuid)
+ });
+
+ let wiring = WiringTarget::vendored();
+ let mut applied = false;
+ let mut settings_found = false;
+ for rel in ["settings.gradle", "settings.gradle.kts"] {
+ if let Some(bytes) = read(rel) {
+ settings_found = true;
+ let text = String::from_utf8(bytes)
+ .map_err(|_| format!("{} is not valid UTF-8", rel))?;
+ let dsl = dsl::dsl_of(rel).unwrap_or(Dsl::Groovy);
+ if has_apply_line(&text, dsl, &wiring, "") {
+ applied = true;
+ break;
+ }
+ }
+ }
+ if !settings_found {
+ return Err("cannot read settings.gradle or settings.gradle.kts".to_string());
+ }
+
+ Ok(indexed && applied)
+}
+
/// The liveness proof `vex` needs for this layout: `c` is
/// [`references`]d, the script is ours (line endings aside), and a re-plan
/// over the committed tree is refused nowhere and degraded nowhere.You can send follow-ups to the cloud agent here.
`scan --prune` reverts a JVM ledger entry when `entry_references` answers false, but the Gradle arm returned `gradle::references` as is: a malformed gradle-index.tsv, a non-UTF-8 settings file, or a file the reader could not read (permission error, link out of the checkout) all read as "not referenced", so the GC could delete a tree that is still wired. The maven/sbt/scala-cli arms already map undecidable to true. Add `gradle::references_checked`, which answers None when a file it decides by exists but cannot be parsed, and have `entry_references` treat that, and any read error the ProjectReader recorded, as still in use. `gradle::references` keeps its old answers for revert and attestation. Also pin with a test that a hand-stripped, empty-wiring `jvm` entry stays undecidable (None) for the prune GC. Co-Authored-By: Claude <noreply@anthropic.com>
Collaborator
Author
|
bugbot run Generated by Claude Code |
There was a problem hiding this comment.
✅ 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 e9bd805. Configure here.
Tanmay Singla (Tanmay182003)
approved these changes
Oct 7, 2026
Collaborator
Author
|
[agent] Ready for review at
Generated by Claude Code |
github-merge-queue
Bot
removed this pull request from the merge queue due to a conflict with the base branch
Oct 8, 2026
Conflicts were main's rustfmt-only reflows of code this branch rewrote (vendored_ledger_supplement test calls now take a ProjectContext, dispatch_in_use_one moved behind the shared liveness probe, cargo vendored_from_patches keeps the relock_pending claim); kept this branch's versions. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
github-merge-queue
Bot
removed this pull request from the merge queue due to a manual request
Oct 8, 2026
Pick up #1093 so PR CI runs without the macOS legs. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Resolve golden.rs Discovery destructure: keep this PR's read/withheld fields and main's unwired_copies (#1033). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
removed this pull request from the merge queue due to a manual request
Oct 8, 2026
This was referenced Oct 8, 2026
github-merge-queue
Bot
removed this pull request from the merge queue due to failed status checks
Oct 8, 2026
Brings in #1044 (governing lock table), #1035, #1038, #1083, #724. Drop the BUN_LOCK/BUN_LOCKB/NPM_LOCKS imports #1044 added to npm_flavor.rs: their only user, vendored_entry_in_use, is removed by this PR (liveness now comes from Discovery::vendor_entry_in_use). This unused import failed clippy in the merge group. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
github-merge-queue
Bot
removed this pull request from the merge queue due to failed status checks
Oct 8, 2026
Mikola Lysenko (mikolalysenko)
added a commit
that referenced
this pull request
Oct 8, 2026
Resolve conflicts with #1050 (vendored-entry liveness): keep its fail-closed Gradle references_checked and ledger-owned orphan check, and route both through vendor::jvm::layout (LEDGER_OWNED_PATHS replaces ORPHAN_PATHS there, re-exported from jvm::apply; settings files come from layout::GRADLE_SETTINGS_FILES). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
added a commit
that referenced
this pull request
Oct 8, 2026
maven_repo.rs: keep this PR's deletion of the legacy same-GAV <repository> backend; #1050's B61 change (comment-aware "already wired" check via find_wireable_anchor) and its test only touched that deleted backend. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
pushed a commit
that referenced
this pull request
Oct 8, 2026
Resolve the conflicts with main's move of vendored in-use detection into discovery (#1050) and its PurlKey rename (#1045): - npm_flavor.rs: drop the branch's vendored_entry_in_use, now that Discovery::vendor_entry_in_use replaces it. Bun discovery already picks the live lock through bun_text_lock_drives, so the #735 regression test now runs through the shared in_use helper with a minimist entry. - lock_inventory/tests.rs: keep both sides' new tests. - vendor.rs: key the Bun reinstall dedupe on PurlKey. Assisted-by: Claude Code:claude-opus-5-5
This was referenced Oct 8, 2026
Mikola Lysenko (mikolalysenko)
pushed a commit
that referenced
this pull request
Oct 8, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KWAYXWPgb6FHP1jeYTCPcA
Mikola Lysenko (mikolalysenko)
pushed a commit
that referenced
this pull request
Oct 8, 2026
Resolve the three conflicts by keeping both sides: - scan/policy.rs: keep the PR's dir_markers/lock_markers split (lock markers decide nested roots for #554) and re-apply main's #1032 change inside dir_markers: the JVM manifest fallback now reads vendor::jvm::layout::JVM_PROJECT_MARKERS, with main's is_file comment. - vex/discover/mod.rs: Discovery keeps the PR's `shadowed` refs (#828) alongside main's `read` and `withheld` evidence (#1050). Shadowed refs carry DIAG_REF_UNATTRIBUTABLE, so vendor_entry_in_use already keeps their entries through unattributable_mention. - vex/discover/testing/golden.rs: destructure all three fields. Co-Authored-By: Claude <noreply@anthropic.com>
This was referenced Oct 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Problem
Architecture audit §3.B asks one question: is this vendored entry still live? Four separate oracles answer it today:
vendor_entry_live,dispatch_in_use_one,scan_vendor_referencesand the JVMentry_wired. They disagree.dispatch_in_use_oneserves the prune GC, scan's ledger supplement and thevendor --checkremedy. It only had probes for npm, cargo and the pypirequirementsflavor. Every gem, golang, composer, nuget, maven/jvm, uv, poetry, pdm, pipenv and pylock entry answeredNoneand was kept forever:scan --prunecould never reclaim one;vendor_ledger_entry_unwired;vendor --checkand vex already called it dead.vendor --checkreported orphaned JVM trees (maven2, gradle, coursier and their indexes, plus the generated sbt file) only when the whole ledger was empty.<repository>or<packageSources>source took the in-sync hot path, so the build resolved the unpatched package.Change
Core: one GC / supplement in-use verdict.
Discovery::vendor_entry_in_use(root, entry) -> Option<bool>sits next tovendor_entry_liveand is derived from the same discovery:Some(true)in any of these cases:DIAG_REF_UNATTRIBUTABLE;[patch].Some(false): discovery read and parsed an install-deciding lockfile of the entry's ecosystem, and nothing above holds.None: discovery read no such lockfile, or one of its files could not be read or parsed.entry_references).An attestation drop is not a liveness verdict. Attestation fails closed by dropping a ref it cannot tie to the one copy that installs. Examples:
dependenciesmirror;The GC has to fail safe, by keeping the entry. One rule covers every extractor: keep the entry whenever a file that mentions the uuid carries an unattributable diagnostic. This is deliberately file-grained, so it errs toward keeping. Mentions rejected as
DIAG_REF_INVALIDare shapes the package manager never installs from, and they still read as unused. Examples: stalebun.lockbpool strings, a vlt edge spec, cargo's leftover[patch]after a hosted takeover.New discovery evidence:
Discovery::read: every guarded read, tagged with the reading extractor's ecosystem. A manifest alone (Cargo.toml,pyproject.toml,hatch.toml) is not a lock, so it decides nothing.Discovery::withheld: now only cargo's relock-pending[patch], which cargo reports as invalid rather than unattributable. The earlier npm and bun pushes were removed because the diagnostic rule subsumes them.Routing.
run_vendor_gcpass (b) takes every verdict from one discovery snapshot taken before it reverts anything. Before, each probe re-read the files after the previous revert. Reverting one entry never rewires another, so the snapshot only removes ordering effects.vendored_ledger_supplementreads the run'sProjectContextdiscovery (scan andgetpass their context).vendor --checkremedy uses the same verdict.B62. The JVM orphan check fires whenever no ledger entry is a JVM entry:
vendor_ledger_missingartifact event, whosepathnames the orphan.The path list is now one
jvm::apply::LEDGER_OWNED_PATHS.B61. Each backend now uses the comment-masking reader its forward writer already uses:
find_wireable_anchorfor the pom,parse_config_source_keys(blank_comments(..))for nuget.config.Duplicates deleted
The GC, supplement and
vendor --checkremedy oracles go from 5 to 1. Deleted:commands::vendor::dispatch_in_use_onevendor::npm_flavor::vendored_entry_in_usevendor::cargo::vendored_entry_in_usevendor::pypi::vendored_entry_in_usevendor::pypi_requirements::requirements_entry_in_useKept on purpose (see Deferred):
pnpm_entry_in_use,pnpm_legacy_entry_in_use,vlt_entry_in_use). Their revert guards still use them. If one disagrees with the GC verdict, the guard refuses and the entry lands in GCfailed, so nothing is reverted unsafely.entry_wired(vex,vendor --check) is strict attestation: every half is wired and the Gradle script is intact.entry_references(GC, supplement) asks whether anything still references the tree. Wired implies referenced, so the GC never reverts an entry that vex attests.scan_vendor_references, the raw-text check used by orphan sweeps, repair, rollback andvendor --check's unknown-wiring scan.JVM orphan path lists: the CLI inline list and
coursier_tree::ORPHAN_PATHSbecomeLEDGER_OWNED_PATHS, so 2 lists become 1.Behaviour changes
scan --prunewhen an install-deciding lock no longer mentions them: gem, golang, composer, nuget, legacy maven, JVM, and the uv, poetry, pdm, pipenv and pylock pypi flavors.pnpm-legacyentry, used to read asNone. Now a lock that resolves nothing for the entry reads as unused.None, and the entry is kept. This is now pinned by a test. Earlier wording that listed hatch among the B19 fixes was wrong for the lock-less case.--jsontop-levelerror(scan and get emit both a string and a {code, message} object) #704, Decide: warn on and then remove scan --apply/--vendor, and whether --vex stays embedded #966, Decide: give SOCKET_FORCE per-command names so forcing a self-update doesn't also force apply and vendor #615, Decide: where patch API calls go when a token is set but the org slug can't be resolved #648, C34, E44-E47 are untouched).Testing
Run locally on macOS. Every cargo command went through
heavy-job.shwithCARGO_INCREMENTAL=0and-j4.cargo test -p socket-patch-core --lib -- vex:: vendor::: 2987 passed, plus the new pyproject test.cargo test -p socket-patch-cli:--libvendor_jvm_clicovgap_commands_scan_modcovgap_commands_vendorscanrepairrollbackvendore2e_vex_lockfilein_process_vendorscan_vendor_e2escan_vendor_requirements_unwiredvendor_ejectAll green on head.
cargo clippy -p socket-patch-core -p socket-patch-cli --all-targets: no diagnostics in any file this PR touches. Toolchain 1.93.1 still flags pre-existing lints elsewhere (jvm_jar.rs,python_crawler.rs,redirect/mod.rs, test helpers), and those are on main too.Keep-side coverage for every newly prunable ecosystem. Each successful, non-dry-run vendor through the
vendor::test_supportwrappers now asserts that the GC verdict on the entry it just wrote is notSome(false). Invendor::, that ran against real backend output for every ecosystem and flavor, with every verdictSome(true)(count per ecosystem/flavor):Regression tests, failing first.
vendor::npm_flavor::tests::unattributable_wiring_stays_in_usecovers the v2 legacy mirror, a v3 nested git copy and a vlt other instance. With the new rule disabled it fails:left: Some(false), right: Some(true).gc_tests::vendor_gc_keeps_an_entry_whose_wiring_is_only_unattributable:run_vendor_gckeeps a legacy-mirror entry, dry and wet. It fails with the rule disabled.vex::discover::tests::lockless_pyproject_entries_stay_undecidablepinsNone.Run on the merge-base c5be5d1 (test hunks only, in a throwaway worktree), these three failed as expected:
vendor_gc_reclaims_unused_composer_entry_and_keeps_a_wired_one(B19):unused_revertedwas[].ledger_supplement_reports_a_bumped_composer_entry_unwired(B19): the entry was resurrected intopackages.check_reports_jvm_trees_without_a_jvm_entry_beside_other_entries(B62): no JVM orphan event; only the npm entry's own failure.The B62 test now also asserts that the event's
pathis an existingLEDGER_OWNED_PATHSmember.commented_out_repository_is_not_wiredandcommented_out_source_is_not_wired(B61) fail with the old substring checks restored.Linux and docker suites are left to CI.
Deferred
scan_vendor_references(orphan sweeps, repair, rollback's ledger-less gate,vendor --check's unknown-wiring scan) is still a separate raw-text scan. Scan every vendored-write file for vendored references (#832, #958) #1015 (Vendored-reference scan never sees NuGet or Maven wiring, so the orphan sweep deletes a still-wired unit #832, Vendored-reference scan never reads hatch.toml, so the orphan sweep deletes a wheel that a Hatch environment still installs #958) is extending it now. Retiring it into discovery is the follow-up once Scan every vendored-write file for vendored references (#832, #958) #1015 merges. This PR touches neitherrepair.rsnorregistry.rs.Discovery::vendor_entry_in_use. The pnpm part needs coordination with Fix 15 open pnpm issues across hosted, vendored and agent modes #1007's owner.entry_wired) from the reference check plus a layout-intact check, together with a singlejvm::layoutmodule and per-GAV JVM orphan detection (a tree dir with no matching JVM entry while other JVM entries exist).withheldand the unattributable rule match on uuid and mode. A uuid names one patch, so one package version, so matching onbase_purltoo would add nothing today.socket-patch-<uuid>key. Deciding "ours" by the source value (NuGetscan --mode vendoredover a hosted pin skips the takeover (purl case mismatch), keeps the hosted feed wired, writes no vendor ledger and still reports success #553) is not addressed here.ecosystem_in_scopedoes not map"jvm"to maven (a separate audit finding).🤖 Generated with Claude Code
Note
High Risk
Changes prune GC, vendor --check, and scan supplement behavior across all ecosystems; incorrect liveness could revert live patches or leave stale ledger entries.
Overview
Unifies vendored-ledger liveness behind
Discovery::vendor_entry_in_use, replacing separate per-ecosystem probes (dispatch_in_use_one, npm/cargo/pypivendored_entry_in_use) soscan --prune, the vendored ledger supplement, andvendor --checkshare one verdict.Discovery now records which lockfiles were read (
Discovery::read) and cargo relock-pending wiring (withheld), and treats unattributable attestation drops and contested locks as keep (fail-safe) rather than unused. The prune GC loads one discovery snapshot before reverting entries so half-pruned locks cannot skew later probes.Behavior fixes: Composer and other previously unprobed ecosystems can be reclaimed when install-deciding locks no longer wire them; JVM orphan detection runs when the ledger has no JVM entry (not only when empty); Maven/NuGet commented-out repository/source blocks no longer count as wired for the in-sync vendor path.
Vendor test helpers assert a freshly vendored entry is never
Some(false)for the GC verdict.Reviewed by Cursor Bugbot for commit e9bd805. Configure here.
Generated by Claude Code