Repository navigation
Tracking: dispatch vendored backends through one per-ecosystem table instead of string matches in core and the CLI #959
Description
[agent] Filed by the scheduled architecture audit routine (ecosystems and formats). Register: discussion #560 register.
Kind: tracking. Source: review §2.1, Part 5.2 and 5.8; register E21.
Problem (verified on 9c43dfc)
Vendored mode has no backend abstraction. The eight vendor ecosystems are a string vocabulary that is re-matched wherever a per-ecosystem decision is made, and backends are uniform only by naming convention (service_preflight, vendor_*, revert_*_opts, vendored_entry_in_use). Production sites that enumerate the ecosystems include:
- Core:
path.rsECOSYSTEM_DIRS;leaf_to_purl(8 arms);vendor::service_preflight(7 arms; gem alone takessource_path, pypi two caches);redownload.rsandlock_inventory/recover.rs;``prestage.rsPRESTAGED_ECOSYSTEMS;ledger_snapshots.rsWHOLE_FILE_KINDS;- the format registry's per-ecosystem roles and
VENDORED_WRITES_UNMARKED; hosted/memory/types.rsECOSYSTEMS.
- CLI (
commands/vendor.rs#L130-L297):- the
SERVICE_ECOSYSTEMSlist (its refusal can never fire, Replace the single-variant vendor PackageSource with &Path and delete the scaffolding it props up #800); - the forward dispatch through the
vend!/vend_installed!macros; - the revert dispatch;
- the in-use dispatch.
- the
Ecosystem identity has an alias. JVM entries are re-tagged "jvm" (maven_repo.rs#L1344). That forces "maven" | "jvm" handling at the CLI revert match, in path.rs#L92 and in redownload.rs#L71-L88.
The lists drift. #832 (NuGet and Maven) and #958 (Hatch) are both a per-ecosystem fact ("which files carry my references") kept in a table apart from the backend that writes those files.
Target design
One per-ecosystem dispatch point in core, keyed by the existing Ecosystem enum (minus Deno). The CLI and the core helpers ask it instead of matching strings:
// vendor/backend.rs
pub enum VendorBackend { Npm, Pypi, Gem, Cargo, Golang, Composer, Nuget, Maven }
impl VendorBackend {
pub const ALL: [Self; 8];
pub fn dir(self) -> &'static str; // ECOSYSTEM_DIRS derives from ALL
pub fn of_entry(e: &VendorEntry) -> Option<Self>; // owns the "jvm" alias
pub async fn revert(self, e, root, opts) -> RevertOutcome;
pub async fn in_use(self, e, root) -> Option<bool>;
pub async fn preflight(self, ..) -> Option<PlannedDownload>;
pub async fn vendor(self, ..) -> VendorOutcome;
pub fn leaf_to_purl(self, leaf) -> Option<String>;
pub fn wiring_files(self) -> &'static [&'static str];
}This is an enum with match arms that call the existing backend functions, not a trait object: async dispatch stays static, and each step is mechanical. Part 5.8's batched plan/materialize trait is the later step, after the revert engine (E24) and the batched planners (E27).
Checklist (one PR each, in order)
- Route vendored revert and in-use dispatch through one core VendorBackend enum instead of CLI string matches #960:
VendorBackendenum withALL/dir/of_entry, and route revert and in-use dispatch through it. This deletes the two CLI matches and the CLI's"maven" | "jvm"arm. - Forward dispatch and
service_preflightthroughVendorBackend::{vendor, preflight}, normalizing the backend signatures (oneVendorCallargument struct). Deletevend!,vend_installed!and thevendor::service_preflightmatch. Blocked by Replace the single-variant vendor PackageSource with &Path and delete the scaffolding it props up #800, which removesPackageSource,vend_installed!andSERVICE_ECOSYSTEMS. - Per-ecosystem helpers become methods:
leaf_to_purl, theredownloadandrecoverarms,PRESTAGED_ECOSYSTEMS.ECOSYSTEM_DIRSand the in-memoryECOSYSTEMSderive fromALL. -
wiring_files()per backend replaces the registry'sVENDORED/VENDORED_WRITES_UNMARKEDsplit for the reference scan (after Vendored-reference scan never sees NuGet or Maven wiring, so the orphan sweep deletes a still-wired unit #832 and Vendored-reference scan never reads hatch.toml, so the orphan sweep deletes a wheel that a Hatch environment still installs #958). - Stop re-tagging JVM entries as
"jvm"for new ledgers, and keep reading the alias at load (of_entry), with legacy-ledger fixtures.
Acceptance (for the tracking issue)
- No production
matchon a vendor ecosystem string outsidevendor/backend.rsand the ledger-load adapter. Enforce it with a source-scan architecture test likecrawlers::architecture_tests. - The
legacy-ledgersfixtures and thee2e_vendor_*suites stay green at every step.
Dependencies
- Child 2 is blocked by Replace the single-variant vendor PackageSource with &Path and delete the scaffolding it props up #800.
- Related: Tracking: drive the seven npm-family vendor backends through one generic driver #920 (the npm-family driver inside the
Npmarm), Share the single-lock wire and revert envelope across the Poetry, PDM and Pipenv vendored backends #937 (the PyPI envelope inside thePypiarm), Stage prebuilt service archives through one shared helper instead of four per-backend service-copy pipelines #906 (the service-copy pipeline), Move canonicalize_pypi_name and the PEP 508 name scanner out of crawlers and vendor into one PyPI name module #883 and --ecosystems rejectsNPMandnpm, pypi, which socket.yml patches.ecosystems accepts: the ecosystem name parser is written three times #773 (whereEcosystemlives).
Consolidated work — backlog review, 2026-10-08
The following standalone issues are now tracked here. Their closure consolidates scheduling; it does not mean their implementation is complete. Original reports and discussion remain linked below.
#960: Route vendored revert and in-use dispatch through one core VendorBackend enum instead of CLI string matches
- Track the remaining scope from Route vendored revert and in-use dispatch through one core VendorBackend enum instead of CLI string matches #960 in this issue. Explicit backend-dispatch child of the shared vendor-backend tracker; fold the implementation step into its parent.
Preserved scope and acceptance criteria from #960
Proposed change
- Add
crates/socket-patch-core/src/vendor/backend.rswithpub enum VendorBackend { Npm, Pypi, Gem, Cargo, Golang, Composer, Nuget, Maven }and:ALL;dir(), the persisted dir name;from_dir(&str);of_entry(&VendorEntry), which accepts the"jvm"alias;async fn revert(self, &VendorEntry, &Path, RevertOpts) -> RevertOutcome, whose arms call today'srevert_*_optsfunctions unchanged;async fn in_use(self, &VendorEntry, &Path) -> Option<bool>, which is npm, cargo and pypi; the rest areNone.
ECOSYSTEM_DIRSbecomesVendorBackend::ALL.map(dir), or a const asserted equal to it by a test, keeping its order.dispatch_revert_one_optskeeps its symlink pre-check, then callsVendorBackend::of_entry(entry);Nonekeeps today's exact failure message.dispatch_in_use_onebecomes a one-liner. Delete both CLI matches and the CLI"maven" | "jvm"arm.- Out of scope: the forward
vend!dispatch andservice_preflight(child 2, blocked by Replace the single-variant vendor PackageSource with &Path and delete the scaffolding it props up #800),leaf_to_purl/redownload/recover(child 3), and the"jvm"re-tag itself (child 5).
Size and scope
- One new core file (~120 lines with tests) and about −40 lines in
cli/commands/vendor.rs. - No backend function signature changes and no behavior change.
Acceptance criteria
- No
match entry.ecosystem.as_str()remains incli/commands/vendor.rs. - Unit tests in
backend.rs:from_dir(dir())round-trips forALL;of_entrymaps"jvm"toMaven;- an unknown ecosystem is
None; ALLequalsECOSYSTEM_DIRSin order.
- The "this build has no vendor backend for ecosystem
x" message is unchanged (existing test or a new one). -
cargo test -p socket-patch-core --lib,cargo test -p socket-patch-cli, thelegacy-ledgersfixture tests, and thee2e_vendor_*suites stay green.
#1012: Move the bounded archive extractors out of vendor::registry_fetch into utils::archive
- Track the remaining scope from Move the bounded archive extractors out of vendor::registry_fetch into utils::archive #1012 in this issue. The archive-extractor file move is supporting layering work. Link this proposal from the backend/formats trackers and close the separate scheduling item.
Coordinate this single archive-extractor move between #959 (vendor backend) and #833 (neutral formats/types); the cross-reference does not require two implementations.
Preserved scope and acceptance criteria from #1012
Proposed change (this issue: the first two families only)
- Create
crates/socket-patch-core/src/utils/archive.rs(orutils/archive/{mod,go_module}.rs). Move the archive extraction and Go module zip families into it verbatim, with the tests that cover them. - Keep the
vendor::registry_fetchpaths for these items through apub(crate) use crate::utils::archive::*for one release of the code, or update the ~16 import sites directly. Either is fine; update the imports if the diff stays reviewable. - Delete the stale
read_zip_memberscomment.
Out of scope, as follow-ups recorded on register row E29:
- integrity, which waits for
LockIntegrityto move intoformats(Move LockfileEntry, LockIntegrity, SourceKind and http_url from vendor::lock_inventory into formats::entry #834); - registry bases and transport, where
download/build_registry_clientare being changed by PR Bound registry downloads by ApiTimeouts instead of a 60 s total deadline (#872) #876 for Registry downloads give up after 60 s even while the body is still arriving #872.
Size and scope
- Files:
vendor/registry_fetch.rs, the newutils/archive.rs,utils/mod.rs, and the importers (vendor/{cargo,composer_lock,gem,golang,maven_repo,npm_dir,nuget_feed,redownload,service_fetch}.rs,patch/jvm_jar.rs,patch/redirect/upstream/client.rs,api/vendor_prefetch.rs). - About 1,000 moved production lines plus their tests. The import edits are about 20 lines. There is no behavior change.
Acceptance criteria
-
git diff --color-movedshows the two families as moved blocks only. - No file outside
vendor/imports archive or Go-module-zip items fromcrate::vendor::registry_fetch. - Every archive refusal message and cap value is unchanged; the moved tests (zip, tgz, gem, module-zip and the
Sink::ValidatevsSink::Writeparity tests) pass unchanged. -
cargo test -p socket-patch-coreandcargo test -p socket-patch-clipass;cargo clippy --all-targetsis clean.
Activity
audit-ecosystems: E21 tracking (#959, #960), new E67 (#958)
mikolalysenko commentedon Oct 6, 2026
[agent] Triaged as priority:p3 (cross-cutting refactor, tracking). Not a duplicate; no open PR covers it.
Generated by Claude Code