[agent] Filed by the scheduled architecture audit routine (ecosystems and formats). Register: discussion #560 register.
Kind: bug (the structural fix is one shared predicate). Source: review Part 4.4 ("Is a bun lock present"), register E17.
Problem
socket-patch asks "is the text bun.lock the live lock?" in seven places. They give three different answers for the same tree:
Bun itself follows the link. With Bun 1.3.14, I created a project with only a bun.lockb (saveTextLockfile = false), added bun.lock -> missing-target, removed node_modules and ran bun install --frozen-lockfile: it succeeded from bun.lockb and installed is-number@7.0.0.
socket-patch on the same tree (a unit probe at 045d7ec, run twice, not committed):
inventory_project_diagnosed entries=[] unsupported=[]
bun_lock::binary_lock_drives = true (vendored writes bun.lockb)
hosted::engine::bun_lock_present = false (hosted rewrites bun.lockb)
lock_inventory::bun_text_lock_present = true
Control: with the symlink removed, the inventory returns [("is-number", "7.0.0")].
So the inventory silently loses every package of the lock that Bun actually installs from, with no unsupported diagnostic. Vendored and hosted mode still wire bun.lockb. The consumers that use the inventory then disagree with the writers: scan's lockfile supplement, the in-memory hosted engine's purl set, and VEX ledger liveness. The GC probe vendored_entry_in_use reads the dangling bun.lock, gets None and keeps the entry, which is safe but never resolves.
Symptoms
None filed. The review flagged this as drift (Part 4.4).
Impact
Low likelihood: a dangling bun.lock symlink is rare. But this is the systemic shape the review describes. Each new Bun-aware path picks its own presence predicate, and every one of them is a single exists() call that looks right on its own.
Proposed change
- Add one predicate,
formats::npm::bun_text_lock_drives(view: &ProjectView) -> bool, with Bun's semantics: a bun.lock that resolves to a readable file. Document what a dangling link or a directory means.
- Route all seven sites through it.
- Delete
bun_text_lock_present{,_in}, hosted::engine::bun_lock_present and bun_lock::binary_lock_drives, and replace the inline join("bun.lock").exists() calls in bun_workspace.rs and repair.rs.
- VEX discovery may keep lstat for diagnostics, but it must pick the lock through the shared predicate.
Size and scope
About 7 files and under 150 changed production lines. Out of scope: the bun.lockb support tier (E47) and the vlt/bun sibling-lock rules in redirect/vlt.rs.
Acceptance criteria
Dependencies
None. Doesn't overlap open #722 (bun inventory of hosted pins) beyond lock_inventory/bun.rs.
[agent] Filed by the scheduled architecture audit routine (ecosystems and formats). Register: discussion #560 register.
Kind: bug (the structural fix is one shared predicate). Source: review Part 4.4 ("Is a bun lock present"), register
E17.Problem
socket-patch asks "is the text
bun.lockthe live lock?" in seven places. They give three different answers for the same tree:bun.locksymlink isexists_no_follow)lock_inventory/bun.rs#L30-L41,used by [`npm_family.rs#L153-L159`](https://github.com/SocketDev/socket-patch/blob/045d7ec783d788bf3c5a1310724b51e09fb6505d/crates/socket-patch-core/src/vendor/lock_inventory/npm_family.rs#L153-L159)`` and the GC in-use probenpm_flavor.rs#L608-L611; VEX discoveryvex/discover/bun.rs#L103viaDiscoverCtx::existsPath::exists(follows links)hosted/engine.rs#L255-L263; vendored routingbun_lock.rs#L635-L641;bun_workspace.rs#L17-L23;CLI repair / orphan sweep [`repair.rs#L44`](https://github.com/SocketDev/socket-patch/blob/045d7ec783d788bf3c5a1310724b51e09fb6505d/crates/socket-patch-cli/src/commands/vendored_backend/repair.rs#L44``)is_file(follows, regular files only)pkg_managers.rs#L139Bun itself follows the link. With Bun 1.3.14, I created a project with only a
bun.lockb(saveTextLockfile = false), addedbun.lock -> missing-target, removednode_modulesand ranbun install --frozen-lockfile: it succeeded frombun.lockband installedis-number@7.0.0.socket-patch on the same tree (a unit probe at
045d7ec, run twice, not committed):Control: with the symlink removed, the inventory returns
[("is-number", "7.0.0")].So the inventory silently loses every package of the lock that Bun actually installs from, with no
unsupporteddiagnostic. Vendored and hosted mode still wirebun.lockb. The consumers that use the inventory then disagree with the writers: scan's lockfile supplement, the in-memory hosted engine's purl set, and VEX ledger liveness. The GC probevendored_entry_in_usereads the danglingbun.lock, getsNoneand keeps the entry, which is safe but never resolves.Symptoms
None filed. The review flagged this as drift (Part 4.4).
Impact
Low likelihood: a dangling
bun.locksymlink is rare. But this is the systemic shape the review describes. Each new Bun-aware path picks its own presence predicate, and every one of them is a singleexists()call that looks right on its own.Proposed change
formats::npm::bun_text_lock_drives(view: &ProjectView) -> bool, with Bun's semantics: abun.lockthat resolves to a readable file. Document what a dangling link or a directory means.bun_text_lock_present{,_in},hosted::engine::bun_lock_presentandbun_lock::binary_lock_drives, and replace the inlinejoin("bun.lock").exists()calls inbun_workspace.rsandrepair.rs.Size and scope
About 7 files and under 150 changed production lines. Out of scope: the
bun.lockbsupport tier (E47) and the vlt/bun sibling-lock rules inredirect/vlt.rs.Acceptance criteria
grep -rn 'join("bun.lock").exists()\|join(BUN_LOCK).exists()' crates/*/srcfinds no production hit.bun.locksymlink beside a validbun.lockb, where the inventory, the hosted engine, vendored routing and the GC probe all choosebun.lockb.bun.lockthat is a directory is treated the same way.lock_inventory,npm_flavor,bun_lock,bun_binaryandhosted::enginetests stay green.Dependencies
None. Doesn't overlap open #722 (bun inventory of hosted pins) beyond
lock_inventory/bun.rs.