Fix Bun patch compatibility and annotations - #245
Conversation
Support Bun text lock version 0 and reject workspace layouts whose tarball paths cannot survive native reinstalls. Refuse incompatible vendored downloads before recording manifest patch intent. Add native release/configuration checks for hosted, vendored and detached installs, patched bytes, integrity and rollback. Assisted-by: Codex:gpt-6-astra
Use the regular-file reader for Bun preflight and vendoring. Verify FIFO inputs refuse promptly, and snapshot the CLI for native runs so concurrent builds cannot change the binary under test. Assisted-by: Codex:gpt-6-astra
Apply Bun compatibility preflight to get by UUID as well as search. Exercise both entry points across the native release matrix. Assisted-by: Codex:gpt-6-astra
…over revert
`revert_npm_redirect_purl` hard-refused any `redirect_bun_lock_package`
edit whose fragments mentioned the package ("cannot replay yet"), so on a
bun project every hosted->vendored conversion (`scan --mode vendored`,
`get --mode vendored`, `vendor`) exited 1 with `redirect_revert_failed`,
and a scoped `rollback <purl>` / `remove <purl>` holding a second hosted
record did the same — although the whole-ledger replay already inverted
the edit kind. The refusal text prescribed `bun install` (a no-op: bun
keeps a URL 3-tuple byte-identically) and hand-editing the ledger.
The bun rewriter records the whole packages-entry line as `original` /
`new` and keys the edit by the lock MAP key (`minimist`, a nested
`other/minimist`, an install alias), never `name@version`. Ownership is
therefore read from the recorded line's spec, exactly the field the
rewriter matched on: a registry spec equal to `<name>@<version>`, or a
hosted http(s) URL spec for `<name>` whose last path segment is
`<bare>-<version>.tgz` (the leaf `tgz_rel_leaf` / `is_prior_hosted_bun_spec`
agree on for scoped names). Sibling versions are foreign (never claimed,
never a refusal); an edit that mentions the package but parses as no bun
entry line refuses with the WORKING remedy (an unscoped `rollback`).
Claimed edits replay through the same whole-fragment
`replacen(new, original, 1)` path as the yarn/pnpm text kinds, with the
same drift refusal, so a CRLF lock round-trips byte-exactly.
Tests: the fail-closed refusal test becomes a success round-trip through
the real rewriter; added sibling-version non-claim, scoped re-redirect
chain claimed by spec+leaf (same-leaf `@other/pkg` and bare `pkg`
untouched), drift refusal, CRLF round-trip, two-records-revert-one,
dry-run, undecidable-edit remedy text, alias-keyed instance, and
hand-restored no-op. rollback.rs: the `defer_bun` comment no longer
describes a refusal.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ng it The dry-run arm of the cross-mode gate emitted `vendor_would_revert_redirect` for every purl the redirect ledger claimed without ever asking the per-purl revert whether it would succeed, so `vendor --dry-run` promised a takeover the wet run could refuse (`redirect_revert_failed` on drift or a corrupt edit). It now runs `revert_redirect_purl(.., dry_run = true)` on a throwaway ledger clone — write-free, same inverses and drift checks — and surfaces a refusal with the SAME code and detail the wet run emits. On success, when the probe would rewrite bun.lock the preview stops after the advisory (which already states the whole plan: revert, then vendor): the bun backend reads the lock from disk, where the hosted URL 3-tuple has replaced the `name@version` spec it keys on, so previewing over it would emit a `vendor_lock_entry_not_found` the wet run never sees. Flavors whose hosted rewrite keeps the entry identity (yarn, pnpm, package-lock) preview exactly as before. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ind CLI suite
Drives the built binary against a wiremock patch API over a real bun 1.4.2
lockfileVersion-2 lock (matrix-capture grammar; no bun binary needed):
1. scan --mode hosted -> scan --mode vendored succeeds with
`vendor_takeover_reverted_redirect`, the redirect ledger record is
dropped, bun.lock carries the `.socket/vendor/npm/<uuid>/` 3-tuple and
no hosted URL, state.json records the PRISTINE registry line as the
wiring original, a re-run is `already_vendored`, and `vendor --revert`
restores the pristine bytes.
2. vendor --dry-run over the live hosted redirect previews the takeover
(`vendor_would_revert_redirect`, no `vendor_lock_entry_not_found`, no
writes); the wet vendor completes it.
3./4. Two hosted records: scoped `rollback <purl>` and `remove <purl>`
(per-purl path, replay not eligible) unwind only the targeted line and
record; the sibling stays hosted.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…EXT + cmd.exe shims; pipenv uses it Lift resolve_on_path / is_executable / is_batch_shim / the cmd.exe /C launcher out of utils/pipenv.rs into utils::process as resolve_tool / resolve_tool_with / command_for / tool_command, so every tool the CLI spawns inside a scanned project (bun, pipenv) skips relative PATH entries (a repo-planted binary) and finds .cmd/.bat shims on Windows. pipenv.rs keeps its thin wrapper; behaviour and its tests are unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Inverse::BunLockbMigrated now decodes the pre-migration bytes (standard base64 in FileEdit.original) and writes bun.lockb back through the crate's atomic writer, leaving the migrated bun.lock in place, with the informational redirect_bun_lockb_restored warning. Without captured bytes the honest redirect_bun_lockb_unrestorable fires only when bun.lockb is actually absent; a present-but-different lock is never clobbered. The migration record now obeys the ledger path-safety rule because the replay writes its path. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… resolved bun spawn - After a successful migration a bun.lockb that bun 1.1.43-1.1.45 kept is removed by the CLI so the ledger's removed record is always true; the pre-migration bytes are captured as standard base64 in the FileEdit original (raw cap 8 MiB) so rollback can restore the binary lock. The zero-redirect unwind keys off an in-memory flag + bytes, not the ledger payload. - exit 0 with no bun.lock (bun 1.1.39) is redirect_bun_lockb_manual_migration naming bun install --save-text-lockfile; spawn failure / non-zero exit stays redirect_bun_lockb_unsupported and now carries bun's output tail. - bun is resolved via utils::process::tool_command (absolute PATH entries, PATHEXT, cmd.exe shims) and the resolved path is spawned, never the bare name. - The spurious redirect_npm_no_lockfile on bun.lockb-only projects is dropped by the driver. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…s bun.cmd twins, real 3-tuple fixtures Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ed-warnings.json redirect_golden.rs asserted only the changed-file set and the edits ledger, so every refusal fixture passed on ANY early return: a renamed code, a refusal firing for the wrong reason, or an entry that silently failed to match all produced the same "no files, edits == []". A case may now ship `expected-warnings.json` (JSON array of codes, order-sensitive) and the harness asserts `result.warnings[].code` equals it. The file is optional so the maven cases that legitimately rewrite AND warn keep passing unchanged; positive cases may pin `[]`. The four bun refusal fixtures now pin their codes (redirect_bun_workspace_unsupported, redirect_bun_lock_unsupported, redirect_bun_lockb_unsupported, redirect_bun_missing_sha512). Mutation- checked: renaming the workspace code at its emit site fails lock-v0-workspace-refusal with "warning codes mismatch". The depscan TS twin (golden.test.ts) consumes the same fixture tree and must gain the same optional file for the cross-language contract to hold. Findings: test-quality:golden-harness-never-asserts-warnings, hosted-engine:v0-workspace-refusal-has-no-code-asserting-test, docs-contract:hosted-workspace-refusal-code-unasserted. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…nd vendored check_lock_version accepts 0, 1 and 2 and parses a u64, so the only reachable `Some(v)` refusal is v >= 3: a lock written by a Bun NEWER than this release tests. Both the vendored gate and the hosted rewriter told the user to "re-lock with bun >= 1.4", which reproduces the same head. The `Some(v)` arm now says the lock is newer than this socket-patch release supports and to update socket-patch (or re-lock with a Bun that writes 0-2); only the `None` arm (no integer head) keeps a re-lock remedy, now "Bun >= 1.2 (`bun install`)", the first release whose default lock is text. rewrite_bun_lock pushes the gate's Err text as the redirect_bun_lock_unsupported detail instead of its own fixed string, so hosted and vendored share exactly one message and cannot drift. Unit tests in both modules pin the per-arm remedies and the equality. Finding: docs-contract:future-lockfileversion-remedy-incoherent. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…n grammar
redirect_bun_workspace_unsupported told the user to "upgrade Bun and
regenerate the text lockfile" without saying how. Verified with real Bun on
a 1.1.45-written lockfileVersion-0 workspace lock: a plain `bun install`
with EVERY release from 1.2.0 on (1.2.0, 1.2.23, 1.3.0, 1.3.13, 1.3.14,
1.4.0, 1.4.1, 1.4.2) rewrites it in place as lockfileVersion 1 (the root
workspace dep spelling changes from a bare path to `workspace:*`, forcing
the save), while 1.1.45 keeps it at 0; a v0 lock WITHOUT workspaces is kept
at 0 by 1.2.x and only bumped by >= 1.3.14. The detail now names that
remedy: re-lock with Bun >= 1.2 (a plain `bun install` rewrites the lock as
lockfileVersion 1, which hosted mode accepts) or delete bun.lock and re-run
`bun install`. End-to-end with this CLI: real 1.1.45 v0 workspace lock ->
refused, bytes untouched; `bun install` with 1.2.0 -> v1 -> re-scan
redirected=1, frozen install rc=0, rollback rc=0.
The code was asserted by no test and no test fed the rewriter a
lockfileVersion 1/2 lock containing a `workspace:` entry, so widening the
gate to every workspace lock passed everything. New unit tests: the real
1.1.39-1.1.45 2-tuple `["consumer@workspace:packages/consumer",
{ "dependencies": {...} }]` at v0 -> files empty, exactly one warning with
this code and the remedy text; the SAME entries at v1 and v2 -> rewritten
with the workspace line byte-identical and no warnings (plus the real v1
1-tuple spelling); a v0 lock whose only workspace is the root "" ->
rewritten. Mutation-checked: `lock_version(content).is_some()` fails the
unit test and the lock-v1-workspace golden case.
Fixtures now carry the grammar bun actually writes (captured from bun
1.1.45 / 1.3.14 / 1.4.2 on real workspace projects): the
lock-v0-workspace-refusal input is the verbatim 1.1.45 shape (no
configVersion, bare-path root workspace dep, 2-tuple member entry with its
deps object, blank line between entries) and is still refused; new
lock-v1-workspace (1-tuple member, root-declared dep rewritten, warnings
[]) and lock-v2-workspace-nested (root `left-pad` and nested
`consumer/left-pad` at the same version both rewritten, nested
`other/left-pad` at another version untouched).
Findings: test-quality:redirect-workspace-gate-code-unasserted-no-negative-twin,
hosted-engine:v0-workspace-refusal-has-no-code-asserting-test,
vendored-engine:v0-fixtures-not-real-bun-grammar (golden half),
test-quality:new-unit-tests-weak-oracles-and-v0-fixture-arity (fixture half).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
rewrite_bun_lock splits on '\n' and rebuilt the matched entry from its parsed parts, so on a CRLF bun.lock (Windows core.autocrlf checkout) exactly the rewritten line lost its trailing '\r': the file became mixed-EOL and the ledger `new` fragment no longer matched the on-disk bytes the way `original` did (replay matches fragments exactly, so after an autocrlf commit/checkout round-trip a revert would leave '\r\r\n'). The vendored engine (vendor/bun_lock.rs) already re-emits the '\r'; the hosted rewriter now does the same. Verified with real Bun against the production minimist@1.2.2 patch on a CRLF-converted lock: PR-head CLI -> 14 CRLF / 1 LF-only lines and ledger `new` without '\r'; this CLI -> 15/15 CRLF, `original` and `new` both carry '\r', `bun install --frozen-lockfile` rc=0 on 1.4.2 and 1.3.14, rollback restores the CRLF original byte-exact. Adds the bun_crlf_lock_keeps_crlf_on_rewritten_line unit test (modelled on the yarn classic CRLF test: every line keeps CRLF, output == LF rewrite with '\n' -> '\r\n', both ledger fragments end in '\r') and the lock-v2-crlf golden fixture (real bun 1.4.2 grammar, CRLF input + expected; .gitattributes already keeps the fixture tree -text). Mutation-checked: dropping the '\r' re-emit fails both. Findings: windows-macos:hosted-bun-rewrite-drops-cr-mixed-eol, hosted-engine:hosted-bun-rewrite-drops-cr-on-rewritten-line. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…pelling re-redirect-stale-url: the input already carries a hosted URL 3-tuple from an EARLIER redirect (older token + patch uuid + integrity). The registry `name@version` spec is gone, so ownership is origin + `<name>-<version>.tgz` leaf (is_prior_hosted_bun_spec); the entry is re-pinned to the current URL and sha, and the ledger `original` is the stale URL line. Until now this arm was covered by one unit test only, so the TS<->Rust byte-parity contract never saw it. NOTE for depscan: bun.ts has no prior-URL arm, so this case needs a TS port (or a TS_LAGGING entry) in lockstep. alias: bun's spelling for `"alias": "npm:left-pad@1.3.0"` (captured from bun 1.3.14 and 1.4.2) keys the packages entry by the ALIAS while the tuple spec is the real `left-pad@1.3.0`. The rewriter matches on the spec and re-emits the key verbatim, so the alias is rewritten (as the live 1.4.2 alias-hosted matrix cell showed) and the ledger key is the alias; this pins it. Both pin `expected-warnings.json` = []. Finding: hosted-engine:re-redirect-path-has-no-golden-or-cli-test (golden half). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…nce and make its remedy converge The workspace gate added by the PR ran on the raw bun.lock before the target instances were classified, so a project vendored on a lockfileVersion 0/1 lock that later grew a workspace member was refused every maintenance verb (`vendor`, `scan --mode vendored`, `repair`) with vendor_bun_workspace_unsupported, and `repair` left the lock pointing at a tarball it declined to rebuild (cold `bun install --frozen-lockfile` then fails). The gate now runs after classification and refuses only a run that would WRITE a new local-tarball tuple (a Registry instance); in-sync re-runs and repair rebuilds (every instance already Ours) go through. It stays ahead of staging, so refusals still precede writes. The remedy could not converge: Bun 1.4.x never bumps an existing v1 lock to 2 in place (install, --save-text-lockfile, --force, add, update all keep it), so "upgrade to Bun >= 1.4 and run `bun install`" looped forever. The message now names the lock's version, says to delete bun.lock and re-lock with Bun >= 1.4, notes that an in-place install keeps the version, and offers `--mode hosted`. The gate itself is kept as a documented over-approximation (root-only declarations would work on v1, but the lock cannot cheaply prove who declares an entry). vendor_bun_lockb_unsupported had two emitters with different remedies; the preflight one dropped the contract's `bun install --save-text-lockfile` pointer and said "upgrade Bun". Both now share one const carrying the flag and its 1.1.39 floor. Module doc: integrity is enforced fail-closed by Bun >= 1.3.10 (registry tuples from >= 1.2.0); earlier releases install a tampered tarball with exit 0 (the PR's docs said 1.3.14). Tests use the real per-version workspace grammar (v0: no configVersion, bare-path root dep, 2-tuple member entry with deps; v1/v2: 1-tuple), byte-exact BN3 oracles for the v0 and v2 arms including revert, message assertions, and three new cases: in-sync re-run, rebuild-on-missing, and fresh-vendor-still-refuses on v0/v1 workspace locks. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…d of a silent empty inventory
A bun project whose only lockfile is the legacy binary bun.lockb (bun
<= 1.1.38 always; 1.1.39-1.1.45 without --save-text-lockfile) with no
node_modules scanned as `status: success / scannedPackages: 0` with no
warnings in every mode (54 lockfile-only matrix cells passed as clean),
because the lock inventory mapped the probe's vendor_bun_lockb_unsupported
to the calm Ok(None) reserved for "no lockfile". The inventory now
returns an UnsupportedNpmLayout with the stable code bun_lockb_unsupported
and an inventory-phrased remedy, which rides scan's additive run-level
warnings[] (and the human `Warning (code): detail` line) exactly like the
PnP refusals; exit code and status are unchanged.
Hosted mode drops that warning only on the NON-empty path, where the
hosted driver runs and owns the bun.lockb story (it migrates the lock
when a bun candidate exists, or reports its own redirect_bun_lockb_*
outcome); the zero-package hosted envelope keeps it, because the driver
never runs there and the run would otherwise be the exact silent no-op
this closes.
Inventory tests now loop lockfileVersion {0, 1, 2} with the real v0
2-tuple workspace entry asserted skipped, and pin the lockb-only
diagnosis (plus bun.lock-beside-bun.lockb inventorying normally).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…2} x workspace shapes No test layer ran `repair` on a workspace-bearing or lockfileVersion-0 bun.lock, which is how the workspace-gate repair regression slipped past 1,809 green matrix rows. The bun arm of repair_rebuilds_deleted_* / repair_rebuilds_corrupt_* now covers six shapes; v0/v1 workspace shapes are reached the way real projects reach them (vendored first, member added afterwards, since a fresh vendor into such a lock is refused by design) with the real per-version workspace entry grammar, asserting a byte-identical rebuild, unchanged lock bytes and exit 0. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…le under --silent `scan --mode vendored --detached` skipped the Bun preflight: it fetched the patch view first and, for alias-installed packages on a bun.lockb project, the vendor step then misreported `package_not_installed`. The preflight now runs once on every path that feeds the vendor engine (manifest-tracked and detached download phases, `get <uuid>` / `get <purl> --mode vendored`, and the `--dry-run` previews), BEFORE any `/patches/view/` fetch, through one shared `BunVendorRefusal` helper. - `--silent` is "errors only": the purl-path `[error]` line prints whenever not JSON and is code-tagged (`[error] <purl> (<code>): <detail>`); the uuid path's `Error (<code>): <detail>` drops its `!silent` gate. - Already-vendored exemption: a purl the vendor ledger wires at the SAME uuid the run selected is never refused (in-sync re-runs and the pre-gate upgrade path reach the engine's already_vendored skip); an unreadable ledger exempts nothing (fail closed). - uuid-path envelope parity: the failed record gains `error` and the envelope gains `skipped: 0`; the refusal fires `patch_vendor_failed` telemetry. The search path and both scan arms report run-outcome telemetry (`has_errors`, download refusals included) instead of a success event on an exit-1 run. - `--dry-run` previews emit the additive `would_refuse` action (+ `errorCode` / `error`) for npm purls the wet run would refuse; status and exit code are unchanged; the human dry-run names them as `[would-refuse]` lines. Unit tests: download_patch_records refuses lockb / v1 workspace before any fetch, skips non-npm purls, exempts the in-sync ledger entry; the preview classifies would_refuse / already_vendored / lockb. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
New tests/in_process_vendor_bun.rs (wiremock, real bun lock grammar: v1/v2
1-tuple workspace locks, the bun 1.1.45 v0 2-tuple lock, bun.lockb-only,
malformed lockfileVersion 3, a Unix FIFO): exit codes, exact envelopes
(uuid path status:error + error{code,message} + record errorCode+error;
scan / purl paths partial_failure), zero view fetches on refusal,
byte-identical bun.lock, no .socket/vendor, a seeded manifest record
preserved (Value equality), --silent stderr carries the code with an empty
stdout, --dry-run reports would_refuse on every entry point, --save-only
agent runs bypass the preflight, positive controls (v2 workspace vendors;
v0 direct lock vendors via get and rollback restores bytes), --detached
refuses pre-fetch, the already-vendored download-phase exemption, and the
workspace-member --cwd behaviour pinned as it is today. The full
already_vendored re-run is #[ignore]d pending lane B1's engine-side
ordering fix (verified to fail today on vendor_bun_workspace_unsupported).
get_modes_e2e.rs: silent visibility (both identifiers), dry-run
would_refuse, save-only exemption. scan_vendor_e2e.rs: download-phase
refusal, detached twin, silent human arm.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ry, rollback + plain-install legs in the hosted real-bun suite The hosted real-bun capstone soft-skipped whenever `bun` was missing, so it passed vacuously in every CI job; its tampered leg hard-asserted a frozen-install failure that bun < 1.3.10 never produces; its forced-v2 leg proved nothing distinct (native lock on >= 1.4, unreadable below); and it had no rollback leg and no plain-install lock-stability check. - `SOCKET_PATCH_BUN_E2E_REQUIRED` (set AND non-empty — CI passes an empty string for non-bun legs) turns every SKIP (no bun, fixture install failed, no text lock, unparsable version, pre-1.1.39 bun) into a hard assert; `SOCKET_PATCH_BUN_E2E_VERSION` must equal `bun --version` so a leg cannot pass on the wrong bun. - `bun --version` is parsed once; `--save-text-lockfile` is passed only for bun < 1.2.0 (the opt-in era); the fixture ASSERTS the emitted lockfileVersion matches the era table (1.1.39–1.1.x → 0, 1.2–1.3 → 1, >= 1.4 → 2) and every later assertion is version-independent. - Tampered leg gated on `TARBALL_INTEGRITY_ENFORCED_FROM = (1,3,10)`: >= 1.3.10 must fail on the integrity check; below it a DIFFERENT valid tarball must install with exit 0 and the installed bytes must be the tampered bytes (PARTIAL). Verified on 1.3.9 (accepts) vs 1.3.10 (rejects). - Forced-v2 leg replaced by the one distinct cross-version proof a single binary can give: on bun >= 1.4 the native v2 lock is relabelled to lockfileVersion 1 (configVersion kept — dropping it makes bun add `"configVersion": 0` on a plain install), the rewrite must keep the version line, and frozen + plain installs must succeed without a bump. - New rollback leg: `rollback --yes --json` restores bun.lock byte-for- byte, deletes the redirect ledger, and a fresh frozen install lands the ORIGINAL bytes. - Every install proof now also runs a plain `bun install` (node_modules removed, empty cache) and asserts the lock stays byte-identical — frozen mode never writes the lock, so only this observes re-serialization drift. - Tarballs are built with the tar crate (no system `tar`; Windows-ready), all bun installs pass `--ignore-scripts`, every CLI run passes `--no-telemetry`, and the stale lockb header comment now points at the in-process shim tests and the bun-compatibility native matrix. Verified with SOCKET_PATCH_BUN_E2E_REQUIRED=1 on real bun 1.1.39, 1.1.45, 1.2.23, 1.3.9, 1.3.10, 1.3.13, 1.3.14 and 1.4.2 (10/10 each); no bun → soft-skip, REQUIRED + no bun → loud failure. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…repair + plain-install legs in the vendored real-bun suite The vendored real-bun capstone hard-asserted the fixture lock was lockfileVersion 1 or 2, so on bun 1.1.39–1.1.45 (the releases whose `--save-text-lockfile` writes 0 — the very ones the PR adds support for) both tests panicked at fixture setup; it soft-skipped without bun (vacuous CI pass); it had no tampered twin, no repair leg, and its only lock-stability check ran after `--frozen-lockfile`, which never writes. - Same REQUIRED / VERSION gates and version-aware fixture as the hosted suite: `bun --version` parsed once, `--save-text-lockfile` only for bun < 1.2.0, the emitted lockfileVersion ASSERTED against the era table (0 / 1 / 2) and recorded on the fixture; the rewrite must keep the version line; the registry 4-tuple spelling is identical across eras so every downstream assertion is version-independent. - New tampered twin: the vendored `.tgz` is swapped for a DIFFERENT valid tarball; from 1.3.10 the fresh frozen install must fail on the integrity check, below it must exit 0 and install the tampered bytes (PARTIAL). Verified on 1.3.9 (accepts) vs 1.3.10 (rejects). - New repair leg inside the capstone: `.socket/vendor/npm/<uuid>/` is deleted, `repair --offline --yes` must rebuild the tarball byte- identically without touching bun.lock, and a cold fresh checkout must frozen-install the marker bytes from the rebuilt artifact. - Every install proof now also runs a plain `bun install` (node_modules removed, empty cache) and asserts bun.lock stays byte-identical and the marker lands again. - Tests are `#[serial]` like the hosted suite, all bun installs pass `--ignore-scripts`, every CLI run passes `--no-telemetry`, and the replacement tarball is built with the tar crate (Windows-ready). Verified with SOCKET_PATCH_BUN_E2E_REQUIRED=1 on real bun 1.1.39, 1.1.45, 1.2.23, 1.3.9, 1.3.10, 1.3.13, 1.3.14 and 1.4.2 (8/8 each); no bun → soft-skip, REQUIRED + no bun → loud failure. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…in} meta must survive hosted and vendored rewrites
Every real-bun proof so far patched left-pad, whose lock meta is `{}`,
so the engines' deps-preserving branches (redirect/mod.rs deps_verbatim,
vendor/bun_lock.rs) and scoped `@scope/name` keys were exercised only by
parser-level goldens and unit tests. A meta-dropping regression would be
SILENT under those legs: bun installs a `{}`-meta tarball tuple with
exit 0, patched bytes and a stable lock — and no deps, no bin.
Both suites gain a `Target` (LeftPad | ScopedWithDeps) and one leg each
for `@scope/pkg@1.0.0`: a scoped package with `dependencies: {left-pad}`
and a `bin`, served by a wiremock npm registry through bun's
`[install.scopes]` (bunfig.toml — a committable file that travels with
the fresh checkouts). Bun records it as `["@scope/pkg@1.0.0", "<tarball
url>", { "dependencies": {…}, "bin": {…} }, "sha512-…"]`; the legs
assert that exact pre-rewrite spelling, that the rewrite produces the
3-tuple with the meta object byte-identical (hosted URL / local path
`.socket/vendor/npm/<uuid>/@scope/pkg-1.0.0.tgz`), that left-pad's own
registry entry stays byte-identical, and that the fresh frozen AND plain
installs land the patched bytes, install left-pad and link the bin
(`node_modules/.bin/scope-pkg*`, Windows shims included).
Tarballs built from the installed tree now keep file modes (the bin
script stays executable); Windows falls back to 0755 under `bin/`.
Verified with SOCKET_PATCH_BUN_E2E_REQUIRED=1 on real bun 1.1.39,
1.1.45, 1.2.23, 1.3.9, 1.3.10, 1.3.13, 1.3.14 and 1.4.2: redirect suite
11/11, vendor suite 9/9 on every version.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
e2e_redirect_bun_build and e2e_vendor_bun_build have never executed a bun command in CI: the `test` job runs them on runners that ship no bun, so both soft-skip and report "ok" in 0.00s on all three OS, and the `e2e` matrix had no bun leg. Add legs for both suites plus the new mode_migration_bun suite on ubuntu/macos/windows with bun 1.4.2 (lockfileVersion 2), and ubuntu lock-era legs with 1.1.45 (v0 opt-in text lock) and 1.2.23 (v1 default; 1.3.14 for mode_migration_bun), installed by SHA-pinned oven-sh/setup-bun v2.2.0. `test_filter: --include-ignored` is mandatory on every bun leg: the suites carry no #[ignore] tests, so the job default `-- --ignored` would select nothing and pass vacuously (the e2e_composer trap). The run step exports SOCKET_PATCH_BUN_E2E_REQUIRED=1 and SOCKET_PATCH_BUN_E2E_VERSION=<pin> on bun legs (empty string elsewhere) so the suites hard-fail instead of skipping when bun is missing or the wrong release. The rust-cache key gains the bun release so several legs of one suite on one OS no longer collide. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…spatch inputs The native bun matrix ran only on path-filtered pull_request events, so post-merge main was never exercised and its rust-cache (save-if main) was never written: every build restored nothing and compiled cold. Add a path-filtered `push: branches: [main]` (pdm shape) and gate cancel-in-progress on non-main so the seeding run is never cancelled mid-save. Widen the pull_request filter to the code the backtest actually drives (rollback/vendor/repair_vendor/remove commands, npm crawler and pkg_managers detection, constants, utils/process, bun_lock_text, the bun redirect fixtures, the doc and Cargo.lock). scripts/backtest-bun.py fetches each release with one un-retried urlretrieve before any case runs; a transient GitHub 500 killed a whole cell on the workflow's first run. Add a step that pre-populates the exact `tools/<version>/<asset>/bun[.exe]` layout install_tool() looks up with a 5-attempt backoff loop, verifies the archive against the release's SHASUMS256.txt before extracting (fail closed) and checks `bun --version`, then pass `--tools native-bun/tools` so the script only sees a verified, cached binary. Also: add 1.1.43 (first `--lockfile-only`), 1.3.9 and 1.3.10 (URL/local tarball sha512 enforcement boundary) to the matrix — all three ship a Windows asset, so the exclude list is unchanged; add workflow_dispatch inputs versions/shapes/modes wired like pdm-compatibility.yml; record provenance as both `--cli-revision` (branch-resolvable head SHA, via env) and CLI_BUILD_SHA (the SHA actions/checkout actually built); add the `# vX.Y.Z` comments on every SHA pin, name every step, add setup-python + `python3` and `chmod || true` per the sibling workflows, and a header comment pointing at docs/testing/bun-compatibility.md. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…te is instance-scoped Lane B2 wrote this scenario against the pre-fix engine, where vendor_bun applied the workspace gate before classifying the in-sync tuple, and parked it behind #[ignore]. With the gate now evaluated per classified instance the re-run reports the documented `skipped`/`already_vendored` event; assert that shape (action `skipped`, errorCode `already_vendored`) instead of a bare `already_vendored` action that the CLI never emits. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… dry-run parity, scoped unwind, rollback)
Adds crates/socket-patch-cli/tests/mode_migration_bun.rs, the bun twin of
mode_migration_npm.rs (yarn) and mode_migration_cargo.rs: a two-dep project
(left-pad@1.3.0 patched, is-number@7.0.0 bystander / second record) installed
by REAL `bun install --ignore-scripts` (`--save-text-lockfile` below 1.2.0),
private BUN_INSTALL + cache per project, wiremock patch API, patched tarballs
built from the installed bytes. The native lockfileVersion is asserted
against the era table (0 for 1.1.39-1.1.x, 1 for 1.2-1.3, 2 for 1.4+), and
every terminal state ends with a fresh checkout's `bun install
--frozen-lockfile` from an EMPTY cache proving the bytes the lock claims.
1. vendored -> hosted: `redirect_takeover_reverted_vendored`, vendored
ledger entry + artifact gone, URL 3-tuple line, redirect-ledger
`original` == the PRISTINE registry line, marker bytes installed;
`rollback` -> pristine bytes, original bytes installed.
2. hosted -> vendored via BOTH `vendor --offline` and `scan --mode
vendored` (copies of one hosted project): `vendor_takeover_reverted_redirect`,
redirect record + edit dropped, local `.socket/vendor/npm/<uuid>/`
3-tuple, vendor-ledger `original` == pristine line, marker bytes
installed, re-run `already_vendored`; `vendor --revert` -> pristine.
3. dry-run parity: `vendor --dry-run` previews `vendor_would_revert_redirect`
(no `vendor_lock_entry_not_found`, no `redirect_revert_failed`),
`scan --mode vendored --dry-run` classifies `would_vendor` (never
`would_refuse`), `scan --mode hosted --dry-run` over a vendored state
previews `redirect_would_revert_vendored`; a whole-tree snapshot proves
none of them writes a byte; the wet runs land the previewed takeovers.
4. two hosted records in one scan; scoped `rollback <purl>` and `remove
<purl>` (per-purl path) unwind only that line/record/edit, the sibling
stays hosted, a fresh install lands a's original + b's marker bytes,
then the unscoped rollback restores pristine.
5. unscoped `rollback` from each mixed state restores pristine bytes and
leaves no vendor artifacts or ledgers.
Gates mirror the two bun capstones: soft-skip without bun unless
SOCKET_PATCH_BUN_E2E_REQUIRED is set and non-empty (then hard failure), and
SOCKET_PATCH_BUN_E2E_VERSION must equal `bun --version`. Verified green
(10/10 each) against real bun 1.4.2 (v2), 1.3.14 (v1) and 1.1.45 (v0); the
CI legs for this suite were added by the ci.yml e2e matrix already.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…pe assertions, conversion + legacy-lockb + CRLF + workspace shapes, verified retried downloads The runner classified cells from the CLI's own refusal codes, so a CLI regression that refused a supported configuration (or "supported" a refused one) passed as an unsupported cell. Every cell is now judged against `expected_outcome(version, shape, mode)`, which encodes the measured Bun boundaries: bun.lockb-only releases (<= 1.1.38 by default, 1.1.39-1.1.42 for the CLI's migration recipe -> redirect_bun_lockb_manual_migration, 1.1.43+ migrate), the version-0 workspace hosted refusal, the pre-v2 workspace vendored refusal, the vendored bun.lockb refusal (scan adds the bun_lockb_unsupported diagnosis), the 0.8.1/1.0.0 peer/override upstream limitation, everything else supported. Refusal codes must match the expectation EXACTLY after an explicit informational allowlist; regression codes (migration reverted / entry not found / revert failed) fail a supported cell. Every CLI invocation records its exit code (main, repeat, rollback, conversion, repair): supported -> 0, hosted refusals -> 0 with redirected 0, vendored / detached / get refusals -> non-zero with no download and no stray manifest record. The repeat run must be the documented no-op (hosted: redirected 1, no warnings; vendored: applied 0 / skipped 1 / one already_vendored event). Rollback must exit 0 and satisfy the lockfile presence rules (text projects: bun.lock back, no bun.lockb; migrated projects: bun.lockb restored from the ledger with redirect_bun_lockb_restored, bun.lock kept). Digest boundary: TARBALL_INTEGRITY_ENFORCED_FROM = 1.3.10 (1.3.9 installs a tampered tarball, 1.3.10 refuses); below it the observation is recorded, not asserted. A new registryDigestEnforced probe proves the registry tuple IS verified on every text-lock release, documenting the downgrade the rewrite introduces below 1.3.10. Bun 1.3.9/1.3.10 print the integrity error and never exit on a workspace project; the tamper installs tolerate that hang. New shapes: hosted-then-vendored / vendored-then-hosted (takeover round trips, ledger and manifest contracts pinned), legacy-lockb (bun.lockb written by 1.1.38, the matrix release migrates it; rollback restores the binary lock byte-identically), crlf-lock (every line stays CRLF through rewrite, repeat and rollback), text-workspace (a REAL version-0 workspace lock), workspace-root, workspace-get-uuid / -search, already-vendored-workspace (re-run over a grown workspace lock, then `repair` rebuilds a deleted artifact), preexisting-manifest (a foreign manifest record with its blob survives a refused vendored run). custom-registry now injects bun's full-URL registry slot and asserts the rewrite drops it; the text gate is >= 1.1.39 and asserts the text lock was written; the alias/package_not_installed carve-out is gone. install_tool downloads with backoff (5xx/429/connection/stall/truncated zip), verifies the zip against the release's SHASUMS256.txt (fail closed), records bunSha256 / bunArchiveSha256 per row, and a tool-install failure still writes summary.json for the artifact upload. Flags and the tools layout stay compatible with bun-compatibility.yml. Verified on macOS against the production patch service with real Bun 1.1.38 1.1.39 1.1.43 1.1.45 1.2.23 1.3.9 1.3.10 1.3.14 1.4.2 over 11 shapes x 3 modes. The only failing cells are already-vendored-workspace on Bun < 1.3.10: those releases re-save URL/local tarball tuples WITHOUT their sha512 (a 2-tuple) whenever bun.lock changes, after which the CLI no longer recognizes its own wiring (redirect_bun_entry_not_found / vendor_lock_entry_not_found) and rollback refuses on drift — a CLI gap the oracle deliberately keeps visible. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Claude (@claude) review |
…un < 1.3.10 Every text-lock Bun below 1.3.10 (1.1.39–1.3.9; measured on 1.1.45, 1.2.23 and 1.3.9) re-saves a hosted URL or vendored local-tarball 3-tuple WITHOUT its sha512 whenever bun.lock is re-saved for another reason (`bun add`, `bun install` after a package.json / workspace change). The 2-tuple keeps the spec Bun installs from, but the CLI no longer recognised its own wiring: a repeat hosted scan warned redirect_bun_entry_not_found beside redirected: 1, `rollback` refused partial_failure, the hosted→vendored takeover (and scoped rollback / remove) refused as drift, and the vendored re-run / repair / revert refused vendor_lock_entry_not_found / _drifted. - bun_lock_text: same_wiring_modulo_integrity + restore_digestless_line — a live line is the recorded wiring iff byte-equal (modulo trailing \r) or the same key/spec/meta with only the trailing "sha512-…" dropped. - hosted rewriter: a 2-tuple at the current URL is healed back to the 3-tuple (the edit records the 2-tuple as original); a stale URL is re-pinned from either spelling; no entry_not_found for either. - replay + takeover: when neither `new` nor `original` is present, the unique digest-less spelling of `new` is replaced by `original` (redirect_bun_lock_package only); duplicates and anything else refuse. - vendored engine: classify accepts the 2-tuple as Ours; an in-sync digest-less line is healed on disk without a wiring record when the committed artifact still holds the bytes the lock was written from, otherwise re-pinned like any stale tuple (repair's rebuild returns a fresh entry whose original carry_forward_wiring refills); revert claims the 2-tuple by its uuid path. - fix the v0-bump comment (first bumping release lies in (1.3.0, 1.3.9]). - tests: unit tests in all five modules, goldens digestless-hosted-already-wired + digestless-hosted-stale-url-repin, in_process_redirect / in_process_vendor_bun / in_process_vendor_bun_takeover, and real-bun legs in both e2e suites (network-free `file:`-dep re-save, the era's spelling asserted from both sides); backtest already-vendored-workspace now expects the digest-less spelling below 1.3.10 (digestDroppedOnResave, resaveKeepsDigest). - docs: CLI_CONTRACT bun clauses, bun-compatibility guide, CHANGELOG. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…overy diagnosis The npm flavor router checks bun.lockb before pnpm/yarn/npm locks, so a project that migrated away from bun and left a stale bun.lockb committed loses lockfile-only discovery of its live lock. The `bun_lockb_unsupported` detail told that project to run `bun install --save-text-lockfile`, which would create a bun.lock for a non-bun project and never named the shadowed lock or the real remedy (delete the debris). The bun.lockb arm of `inventory_npm_lock` now probes for a sibling pnpm-lock.yaml / yarn.lock / npm-shrinkwrap.json / package-lock.json (router precedence) and, when one exists, phrases the detail as "shadows <sibling> in lockfile discovery; delete the stale bun.lockb if <sibling>'s installer is in use, or run `bun install --save-text-lockfile` (Bun >= 1.1.39) if bun is". Code and fail-closed no-inventory posture unchanged; the lockb-only text is unchanged. Unit test beside the stale-lockb tests covers all four sibling kinds and the precedence. Finding: VC-4 (PR #245 final review). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…add wired_instances_all_ours `check_workspace_compatibility` always ended its remedy with "or use `--mode hosted`, which accepts version-1 workspace locks" - including for the lockfileVersion-0 lock it had just named. Hosted mode refuses every v0 workspace lock (`redirect_bun_workspace_unsupported`), so a Bun 1.1.39-1.1.45 user following the alternative hit a second refusal with a different remedy. The tail is now version-specific: v1 keeps the hosted pointer; v0 says "or delete bun.lock, re-lock with Bun >= 1.2 (which writes lockfileVersion 1) and use `--mode hosted`" (an in-place `bun install` does not reliably bump a v0 workspace lock). `assert_workspace_remedy` asserts the exact tail per version. New `pub async fn wired_instances_all_ours(project_root, purl)`: whether bun.lock already wires EVERY packages entry resolving the purl's `name@version` to one of our `.socket/vendor/npm/` tuples (any uuid, 3-tuple or the digest-less 2-tuple Bun < 1.3.10 re-saves). This is the engine's own criterion for skipping the workspace gate, exposed so the CLI's pre-download preflight can exempt exactly what the engine would let through (a superseding-uuid patch update, a wiped ledger) instead of refusing with a remedy Bun 1.2/1.3 teams cannot follow. `preflight_vendor`'s doc comment now states the real exemption rule (ledger same-uuid OR lock all-ours). Findings: VC-3, VC-2 core half (PR #245 final review). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…keep bun.lockb warning on hosted scans Bun vendored preflight moved out of get.rs into a shared `commands/bun_preflight.rs` used by get.rs, scan/vendor_flow.rs AND vendor.rs, with three behaviour changes: * takeover-1 / DC-1 / takeover-2 (P1 regression): `vendor_records` ran the hosted->vendored takeover (wet `revert_redirect_purl` + `persist_redirect_state`) BEFORE `dispatch_vendor_one`, and the plain `vendor` command never ran the Bun preflight. Over a hosted-wired lockfileVersion-0/1 workspace bun.lock (hosted accepts it, the vendored backend refuses it) that stripped the live hosted redirect, deleted the ledger record, then failed `vendor_bun_workspace_unsupported` - unpatched in both modes, with a remedy pointing at the mode it had just destroyed. The dry run promised the takeover (`vendor_would_revert_redirect`, status success). The preflight now runs once per run before the dispatch loop and refuses per candidate BEFORE the takeover block, dry and wet alike: same `failed` event/code/detail the engine would emit, hosted wiring, ledger and lock byte-untouched, exit 1 on both. * VC-2 (P2): the already-vendored exemption was ledger+same-uuid only, so a superseding patch uuid on a project vendored before it grew a workspace member (or the same project with a wiped state.json) was refused at download while the engine would re-vendor in place. A purl is now exempt when EITHER the vendor ledger wires it at the selected uuid OR `wired_instances_all_ours` says every lock instance is already ours. * GCP-2 (P3): all three `load_state` sites (uuid-path preflight, detached download, dry-run preview) flattened an unreadable ledger into an empty one and reported a Bun lock remedy. They now hand the load outcome to the preflight as a Result; an Err yields `vendor_state_unreadable` with the io/parse detail (fail-closed, nothing exempt). Scan changes in the same file set: * VC-1 (P2 fail-open): scan/mod.rs dropped the discovery `bun_lockb_unsupported` warning on every non-empty hosted run, but the hosted driver only speaks about bun.lockb when an npm override is granted (decided inside `run_redirect`, which owns the envelope from there). The retain is gone: the warning stays in EVERY mode on both paths; nothing is deduplicated. * GCP-1 (P3): `print_dry_run_refusals` moved next to `preview_vendor_json` in vendor_flow.rs as pub(crate); scan's interactive `--mode vendored --dry-run` arm now prints the same `[would-refuse] <purl> (<code>): <detail>` lines as get, under the same `!silent` gate. Tests: in_process_vendor_bun_takeover.rs scenario 5 (hosted v1 workspace -> vendor dry+wet refuse before un-hosting; v2 twin still takes over); in_process_vendor_bun.rs superseding-uuid re-vendor, wiped-ledger not-refused, corrupt-ledger -> vendor_state_unreadable on uuid/human/dry-run/ detached; covgap_commands_scan_mod.rs hosted arm flipped to keep the warning + human scan dry-run [would-refuse] line; bun_preflight.rs unit tests. Verified end to end with real Bun 1.3.14 and the production minimist patch: scan --mode hosted -> get <uuid> -> vendor exits 1, lock still hosted, cold-cache frozen install installs the patched bytes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ling lock before spawning bun Two fail-closed gates now run in the hosted driver's bun.lockb migration BEFORE any `bun install` spawn: * A `bun.lockb` that is not a regular file (a FIFO, socket or directory squatting the path) passes the `exists()` gate but wedged the plain `std::fs::read` capture of the pre-migration bytes forever — and bun's own open of the lock blocks on the same FIFO, so guarding the read alone would only move the hang into the child. The capture now goes through a new FIFO-safe `utils::fs::read_regular_to_bytes_sync` (non-blocking open + fstat regular-file check, the bytes twin of `read_regular_to_string_sync`); `InvalidInput` refuses with `redirect_bun_lockb_unsupported` "bun.lockb is not a regular file; refusing to migrate it" and bun is never spawned. Dry-run predicts the same refusal (stat, never open). Other read errors keep today's contract (migrate, record without restorable bytes). * A `bun.lockb` beside a live `package-lock.json`, `npm-shrinkwrap.json`, `yarn.lock` or `pnpm-lock.yaml` (and no `bun.lock`) is most likely debris of a migration AWAY from bun; migrating it converted an npm / yarn / pnpm project into a bun.lock project (verified with bun 1.4.2: bun.lockb deleted, lockfileVersion-2 bun.lock created and redirected beside the redirected package-lock.json). The driver now leaves it alone with the new stable warning `redirect_bun_lockb_sibling_lock` naming the sibling(s) and both remedies; the redirect follows the sibling lock as before. Dry-run reports the same code instead of `redirect_bun_lockb_would_migrate`. Tests: core `read_regular_to_bytes_sync` (binary verbatim, error kinds, FIFO fails fast), hosted unit tests for the sibling probe and details, and three covgap subprocess tests with a marker `bun` shim proving no spawn: FIFO bun.lockb (deadline-guarded, dry-run + live), stale lockb beside package-lock.json (dry-run, live, human stderr) and beside pnpm-lock.yaml. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…m correctly
`command_for` launched a Windows batch shim as `cmd.exe /C <shim>`. std
quotes that path as an ordinary argument, and cmd's `/c` rule keeps the
quotes only when the quoted string has no `& < > ( ) @ ^ |`, so a shim
under a directory with a space AND a metacharacter (`C:\Program Files
(x86)\...\bun.cmd`, `C:\Users\Jane (Work)\...`) was stripped to
`C:\Program` and failed with "is not recognized" — bun degraded to
`redirect_bun_lockb_unsupported`, pipenv's installed major to None —
although the shim works in the user's shell. Rust std >= 1.77.2 (the
toolchain pins 1.93.1) already detects `.bat`/`.cmd` on the resolved
program and spawns `%SystemRoot%\System32\cmd.exe /e:ON /v:OFF /d /c
""<script>" <escaped args>"` with an outer quote pair and per-argument
escaping, so the wrapper was redundant and strictly less robust.
`command_for` now returns `Command::new(<resolved path>)` unconditionally
(the PATHEXT-aware resolver stays: std does not search PATHEXT);
`is_batch_shim` is gone with its last callers. The cfg(windows) tests
assert the shim is the program with no wrapper args and spawn it — once
from a plain dir, once from `Program Files (x86)` — expecting the shim's
output; the pipenv Windows test spawns its `.bat` and parses the banner.
Doc comments corrected ("CreateProcess refuses them" was false).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e bump conditional `redirect_bun_workspace_unsupported` claimed a plain in-place `bun install` with any Bun >= 1.2 rewrites a version-0 workspace lock as lockfileVersion 1. That was measured only for the backtest's `workspace` shapes, whose root depends on the member: Bun >= 1.2 re-saves that dependency's bare-path spelling as `workspace:*`, which forces the save. For a v0 workspace lock with no inter-workspace dependency (a root that only lists `workspaces`), Bun 1.2.0 exits 0 and keeps 0, and 1.2.23 to 1.4.2 exit 1 with `<pkg>@<ver> failed to resolve` and keep 0 — hosted mode kept refusing and the stated remedy never converged. The detail now leads with the remedy that converges on every release (delete bun.lock and re-run `bun install` with Bun >= 1.2, which writes lockfileVersion 1) and states the in-place bump as conditional on a workspace depending on another workspace. The gate comment and the unit test (now an exact-string pin) follow. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e bun suites `scrub_socket_env` in the redirect and vendor capstones removed only `SOCKET_*`, `VIRTUAL_ENV` and `BUN_INSTALL_CACHE_DIR`, and `cache_env::isolate` pins no registry variable, so an ambient `BUN_CONFIG_REGISTRY`, `npm_config_registry` or `NPM_CONFIG_REGISTRY` pointing at a URL-rewriting mirror reached the fixture `bun install`; bun then recorded the mirror tarball URL in the 4-tuple's registry slot instead of `""` and 5/11 redirect and 3/9 vendor legs failed as false negatives, while mode_migration_bun (which scrubbed `BUN_*` and case-insensitive `npm_config_*`) stayed green. The scrub now lives once in `tests/common/cache_env.rs` (`is_ambient_bun_var` + `scrub_ambient_bun_env`) with self-tests on the covered names, and all three bun suites call it, so they cannot drift. Verified: both capstones pass on bun 1.2.23 and 1.4.2 with `BUN_CONFIG_REGISTRY=https://registry.npmmirror.com/` exported. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… skip Step 6 of `bun_vendor_fresh_checkout_frozen_install_and_revert` asserted only `failed == 0` and bun.lock byte-identity, so a regression that re-classifies the in-sync local-path tuple as needing a rewrite (applied == 1, tarball re-packed, wiring re-recorded, lock bytes unchanged) passed it — and step 7's revert still restored the lock via the ledger's carried-forward original. Mirror the pnpm capstone: `applied == 0`, `skipped == 1`, a `skipped`/`already_vendored` event for the target purl and no `applied` event. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The golden harness asserts warning codes only when a case ships `expected-warnings.json`, so a positive case without one (rerun-noop, basic, lock-v0, lock-v2, nested-entry, scoped-package, custom-registry) could degrade into a warning-emitting non-match that changes no file and still pass. Each now pins `[]`. No harness enforcement is added: ten non-bun no-`expected/` fixtures legitimately warn today and are shared with depscan's TS twin, which ignores the extra file. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…an, records cliBuildSha, reports empty narrowings
Every `scan` on a bun.lockb-only project now carries the run-level
`bun_lockb_unsupported` layout warning in every mode, hosted included
(the discovery pass never read the binary lock; the hosted driver's own
`redirect_bun_lockb_*` outcome rides beside it on the run that migrates,
nothing deduplicated). The oracle encodes that as a `layout` set folded
into every bun.lockb band: the 0.8.1 / 1.0.0 `transitive` cells (mkdirp
still installs, so a bun.lockb is left — `peer` leaves no lock and stays
empty), the manual-migration and version-0-workspace refusals, the
supported migrating runs and the vendored refusals; `get` shapes run no
inventory pass and keep the driver's codes alone. Verified 104/104 on
0.8.1 + 1.0.0 (all shapes x 3 modes) and 12/12 on 1.1.39 + 1.4.2 against
the base CLI with the pre-F1 rule, then 28/28 across every band against
a base + lane-F1 build; the base binary fails exactly the two hosted
lockb cells the new rule adds.
`--cli-build-sha` (default `$CLI_BUILD_SHA`, else null) is recorded as
`cliBuildSha` beside `cliRevision`, so PR rows carry the refs/pull/N/merge
commit actions/checkout actually built. A `--versions/--shapes/--modes`
narrowing that leaves no applicable cell prints a `::notice::`, writes a
single `{"noCells": true, "passed": false, "error": …}` summary row and
exits 0 instead of a red "0/0 passed" — the default shape list always
holds `direct`, so an un-narrowed run can never go vacuous. The Bun
1.1.38 legacy baseline is fetched only when a `legacy-lockb` cell applies
to a requested release.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…the listing first, pass --cli-build-sha, fix the path filters Download step: pre-populate Bun 1.1.38 (the `legacy-lockb` baseline the script installs with on every other release) beside the matrix release or the dispatch override — de-duplicated, only when that shape is in play — so the script's in-job install_tool() fetch, and the GitHub-outage exposure it carried on 42 of 45 cells, is gone; check the SHASUMS256.txt listing BEFORE fetching the archive (a release without this OS's asset fails in one request instead of five 404 rounds); skip override releases before 1.1.0 on Windows with a `::notice::` (no Windows build exists) and export the staged releases through `steps.bun.outputs.versions`. Run step: consume that output, report `noCells` and pass when every requested release was skipped, and pass `--cli-build-sha "$CLI_BUILD_SHA"` so the provenance the comment promised is actually recorded. Path filters: the `crates/socket-patch-core/src/patch/bun_lock_text.rs` entries named a file that has not existed since #150 — now `vendor/bun_lock_text.rs`; the push list gains `Cargo.lock` (what rust-cache keys on), `vendor/**`, `commands/scan/**`, `vendor.rs`, `repair_vendor.rs` and `remove.rs`. The step logic was exercised locally with stubbed curl/python3 across six scenarios; yaml parse, pin-check grep and `zizmor --offline --min-severity medium` are clean. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Contract rows and prose for the code landing in lanes F1/F2: `vendor` and the vendor step run the Bun vendored preflight BEFORE a hosted -> vendored takeover's revert (a refused lock leaves the hosted wiring, ledger and bun.lock untouched and reports `failed <code>`; `vendor --dry-run` previews the same code instead of `vendor_would_revert_redirect`); the already-vendored exemption is ledger-at-selected-uuid OR every matching bun.lock instance already a `.socket/vendor/npm/` tuple; a corrupt vendor ledger at the preflight is `vendor_state_unreadable` (added to the `failed`/`would_refuse` enumerations); `bun_lockb_unsupported` is kept in every mode, hosted included, and names a shadowed sibling lock; new hosted warning `redirect_bun_lockb_sibling_lock`; a bun.lockb that is not a regular file is refused before any spawn; the version-specific `vendor_bun_workspace_unsupported` remedy tail; Windows `.cmd`/`.bat` shims are spawned directly (std quotes batch paths); scan's human dry-run prints `[would-refuse]` lines like get's. The version-0 workspace remedy at all six sites (contract x2, ecosystems, guide row + measured bullet, changelog) is now "delete bun.lock and re-lock with Bun >= 1.2 (writes lockfileVersion 1)"; the in-place `bun install` bump is conditional on an inter-workspace dependency (root -> member, the only shape measured) — otherwise Bun 1.2.0 keeps 0 and 1.2.23+ fail to resolve. The older CHANGELOG "Hosted redirect unwind" bullet no longer calls the bun.lockb migration unrestorable by design (bytes ride the ledger; `redirect_bun_lockb_restored`; `redirect_bun_lockb_unrestorable` names git history only when bytes are absent or a different bun.lockb is present). The guide gains a "depscan TS parity" section (the TS_LAGGING entries the submodule bump needs, the expected-warnings.json harness requirement, the bun.ts porting items), a provenance note for the synthetic same-version nested entry in `lock-v2-workspace-nested`, a softened byte-identity sentence, the 0.8.1/1.0.0 transitive/peer oracle rows, the `cliBuildSha` provenance key, the noCells / Windows-skip dispatch behaviour and the widened push filter. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
Review + fix campaign: bun e2e coverage, hosted⇄vendored conversion, all Bun eras, 3 OSA multi-agent review of this PR (10 lenses, two adversarial verifiers per finding, then a completeness critic and a second full-diff review) confirmed 88 + 23 findings. Every confirmed finding is fixed in the 41 commits pushed on top of d475268 (89 files). Summary by area: Broken or missing end-to-end tests
Hosted⇄vendored conversion (the code changes)
DocsCLI_CONTRACT.md, docs/ecosystems.md, docs/testing/bun-compatibility.md and the CHANGELOG describe the merged behaviour (lockfileVersion 0/1/2, all new codes, envelopes, boundaries, CI wiring, and a depscan TS-parity handoff list). Verification on the pushed head
Follow-ups not in this PR
🤖 Generated with Claude Code |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
GitHub's default CodeQL setup raised eight rust/cleartext-logging alerts on the new bun tests: its name-based heuristic treats any value flowing from a `uuid`-named binding (a loop variable, a tuple holding `dep.uuid_h`, the `other_uuid` fixture) as sensitive when it reaches a panic/assert message. The values are patch identifiers in test fixtures, not secrets, but the repo keeps the check green on PRs, so the messages now describe the failed condition without interpolating those values and the uuid is bound apart from the vendor envelope it was paired with. No assertion got weaker: each still checks the same condition and prints the same envelope/lock context. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Claude (@claude) review |
|
BugBot review |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
Bugbot Autofix is ON. A cloud agent has been kicked off to fix the reported issue.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit b25aebf. Configure here.

LLM Description written by Codex:gpt-6-astra
Bun patches must survive fresh installs and describe what the project actually consumes. This supports Bun's version-0 text locks and prevents incompatible vendored operations from leaving misleading patch records in
.socket/manifest.json.scan,getby PURL andgetby UUID before writing patch intent. Preserve existing explicit manifest entries and use the nonblocking regular-file reader.Validation:
The paired SBOM implementation is SocketDev/depscan#26450, stacked on SocketDev/depscan#26400, and pins this branch. The tests and fixtures are split into SocketDev/depscan#26453, stacked on the implementation PR, and reuse the existing real metadata/publication/SBOM validation framework.
See
docs/testing/bun-compatibility.mdfor reproduction commands and explicit boundaries: binary-only vendoring, old workspace layouts and absent native tarball digest enforcement in tested Bun releases before 1.3.14.Note
Medium Risk
Changes lockfile rewrite, rollback, and vendored preflight paths for Bun/npm-family projects; risk is mitigated by extensive multi-OS CI but incorrect preflight or lock grammar handling could block or mis-record patches.
Overview
Bun patch flows now treat text
bun.lockversions 0, 1, and 2 in hosted and vendored modes, heal digest-less URL tuples from older Bun re-saves, and can restorebun.lockbfrom ledger-captured bytes on hosted rollback instead of treating migration as permanently unrestorable. Hosted→vendored takeover, scoped rollback, andbun.lockbmigration behavior (sibling-lock skip, PATH-safe spawn, post-migration cleanup) are aligned with the updated CLI contract and changelog.Vendored entry points share a new
bun_preflightlayer that runspreflight_vendorbefore any patch download or manifest write onscan/get(including detached and uuid paths), surfacing stable codes such asvendor_bun_workspace_unsupportedandvendor_state_unreadablewith ledger/lock exemptions for in-sync re-runs.get --mode vendoredexits early on uuid refusal without creating.socket/; dry-run previews addwould_refuse/[would-refuse]lines.CI adds a native Bun compatibility workflow (16 releases × three OSes, SHA-verified downloads,
scripts/backtest-bun.py) and extendsci.ymle2e with real Bun (setup-bun,--include-ignored, lock-era legs on Ubuntu).base64is added to the CLI crate for lockb bytes on the hosted path.Reviewed by Cursor Bugbot for commit b25aebf. Configure here.