GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
GeezerVison is showing github.com — feat(vex): manifest-less VEX from hosted/vendored lockfiles + npm 12 allow-remote auto-config (v5) by mikolalysenko · Pull Request #251 · SocketDev/socket-patch Conversation
|
| Format | Shared reader |
|---|---|
| package-lock / npm-shrinkwrap | lock_inventory::npm_lock_nodes |
| pnpm-lock.yaml / shrinkwrap.yaml / Rush | lock_inventory::pnpm::pnpm_packages over the hosted rewriter's pnpm grammar (every key generation, CRLF included), classify_pnpm_key, rush_lock_rels |
| yarn classic / berry | lock_inventory::yarn::{classic_entries, berry_entries}. One berry locator (parse_berry_locator), one cache-key and checksum rule, and one multi-descriptor key splitter, shared by the hosted rewriter, the vendored berry backend and discovery |
| bun.lock / bun.lockb | lock_inventory::bun::bun_text_entries over the backends' fail-closed vendor::bun_lock_text line grammar; BunLockb::parse_packages (also used by bun_lock's vendor paths and the trust anchor) |
Cargo.lock / .cargo/config* |
cargo_lock::locked_packages. cargo_config's [patch] and [registries] walks and effective-config probe, which the writers read through too |
| go.mod / go.sum | go_mod_edit's read helpers (normalize_for_read, block_structure_error, hosted_module_uuid), go_sum_edit::go_sum_lines / is_h1_dirhash |
| Gemfile.lock / Gemfile | vendor::gemfile_lock, a new model covering sections, checksums, remotes and lock names. vendor::gem::gem_declaration_any and discovery's source-block grammar, now also used for ledger liveness |
| composer.lock | lock_inventory::composer_lock_packages. The composer writer's lock walks use its array index too |
| uv / pylock / poetry / pdm / Pipfile.lock / requirements / Hatch | utils::python_lock (uv source fields, script-lock pairing), utils::poetry_lock, lock_inventory::pypi::parse_pipfile_lock, utils::hatch::dependency_specs. utils::requirements, a lexer lifted out of the vendored planner, now provides the exact-pin rule for the inventory, discovery and the planner. There is one hosted pypi URL grammar for the Pipfile.lock inventory and discovery |
| pom.xml / nuget.config / packages.lock.json | two new readers, vendor::maven_pom and vendor::nuget_config; nuget_feed::nuget_lock_entries, shared by discovery and the feed writer |
| pins and purls | utils::digest (SRI pin rule, hex digest shapes) and utils::purl's validating builders, shared by discovery, the inventory, ledger recovery and the rewriters |
Discovery's own duplicates are gone as well. DiscoverCtx::locate classifies a lock location once for every extractor (vendored path, hosted uuid, decorated leaf). There is one Wired type, one set of vendor-dir predicates and one JSON / TOML parse-and-diagnose helper.
Ledger liveness is one rule, Discovery::{wires_package, vendor_entry_live, redirect_record_live}. Each call site holds it through LedgerLiveness: the sorted redirect-ledger files, plus the lock inventory, loaded lazily and at most once.
vex attestations.scan's takeover classification, hosted_wiring_retained and redirectState.wiringLive.Cargo crates.io provenance is an explicit LockfileEntry::source_kind; it is no longer inferred from the checksum variant. The CLI shares one purl splitter, one vendor-ledger lookup and one npm identity crawl across vex, scan and vendor.
Integration round (after #248). The refactor's second pass removed the last parallel walkers that the duplication maps had found. There are 16 fixups, all folded into commits 4–9:
The same round moved the reason a patch was gated into warnings[], so --json keeps it (see "Behavior changes").
Production lines are non-blank lines with tests, #[cfg(test)] items and golden harnesses excluded. "Before" is the pre-refactor head of this branch; "after" is e3d0ca9 (commit 8 at the time, before the #249 / #250 rebases, which changed none of these files' production code).
| Area | Before | After | Code only (no comments) |
|---|---|---|---|
vex/discover/* |
8,285 | 7,349 (−936, −11%) | 6,120 → 5,223 (−897) |
vendor/lock_inventory (main: 2,262 in one file) |
2,166 | 2,696 (+530) | 1,643 → 1,961 |
CLI vex_sources.rs + vex_consumed.rs |
1,429 | 1,422 | 1,107 → 1,090 |
new shared readers (maven_pom, nuget_config, utils::digest) |
— | 566 | |
| every changed production file (74) | 64,256 | 65,074 (+818) | 48,696 → 48,922 (+226) |
The refactor is not a net line reduction. Each format now has one traversal; the duplicated walkers are gone from discovery and the scanners. lock_inventory and the writer modules gained the entry models, which yield every entry kind rather than just the registry ones, plus their docs.
Before any code moved, the refactor recorded golden snapshots on the pre-refactor tree:
vex CLI output (exit code, envelope and document) for every hermetic e2e call.Every later step had to keep them byte-identical. Old-vs-new differential copies of each consolidated function also ran during the work. Comparing the final tree against those baselines:
| Golden set | Baseline files | Byte-identical | Changed |
|---|---|---|---|
| discovery | 435 | 433 | 2, both bun (below) |
| lock inventory | 514 | 512 | 2, the same bun tests |
| ledger liveness | 435 | 433 | 2, the same bun tests |
CLI vex |
244 | 174 | 70, each ONLY by an added warnings[] array (checked by deleting every warnings key and comparing) |
The only behavioral change is intended. A hand re-indented bun.lock used to be read by discovery's own JSONC parser, while every other command refused it. It is now lockfile_unparseable, the same as everywhere else (reformatted_jsonc_lock_is_still_read became reformatted_lock_is_refused_like_every_other_reader). The CLI warnings[] additions are the new gating-reason warnings. No existing test assertion was loosened.
That scaffolding was about 460k lines of generated JSON, so it is not committed. Commit 9 keeps a compact regression pin instead: discovery output for the 182 committed fixture projects, one JSON file per fixture family (13 files, about 3.8k lines). It is regenerated with SOCKET_PATCH_UPDATE_GOLDEN=1 cargo test -p socket-patch-core --lib vex::discover::testing::golden, and a fixture without an entry (or an entry without a fixture) fails.
Left separate on purpose:
maven_repo / redirect pom regexes, the nuget_feed / redirect key harvesters, wired_vendor_integrity's line windows and vendored_entry_in_use. Moving them onto the shared readers changes writer behavior, so each needs its own commit with a reviewed golden diff (follow-up).Discovery (core, vex::discover/). It is read-only, never uses the network and never fails the run: a malformed file becomes a diagnostic. There is one extractor per PM family:
bun.lock / bun.lockb);-r, Hatch / PEP 621 direct refs);The result is Discovery { refs, diagnostics, recognized, unlocked_pins, elsewhere }. Every root file is read; there is no precedence chain, because the hosted rewriter edits every candidate it finds.
Fail-closed validation. Every value is committed, tamperable data:
https://patch.socket.dev or the configured --patch-server-url origin, with no userinfo. The uuid is the last canonical path segment, because grant tokens can be uuid-shaped..socket/vendor/<eco>/<uuid>/… whose leaf names the entry's own artifact.patched_ref_unattributable).UnlockedPin) can keep a ledger record live but never creates a ref.Record resolution (CLI, vex_sources.rs). Sources are tried in order: manifest, redirect ledger, vendor-ledger embedded record, then (online only) the patch API by uuid. Fetches run 10 at a time with get's 401/403 → public-proxy fallback.
socket-patch.vendor.json marker is never a record.Evidence.
scan --mode hosted --vex.Liveness gates. These run before hashing and are still enforced under --no-verify, which now skips only the hashing:
vendor_unwired / redirect_unwired: the ledger record's lockfile wiring is gone. Discovery is authoritative for every uuid a read file mentions, so a rejected mention keeps nothing alive.wiring_conflict: the lockfiles wire one package to several patches.record_mismatch: the record names a different package or uuid.record_unavailable: offline, 404, refused or a transport error. The run continues.Product auto-detect. Adds go.mod, composer.json, pom.xml, a single *.csproj and a single *.gemspec, after the existing probes.
.npmrc allow-remote auto-configThe decision was: fix the .npmrc and warn; respect explicit values; add an opt-out flag; make it revertible.
Trigger. A scan --mode hosted / get --mode hosted run that leaves a root package-lock.json / npm-shrinkwrap.json carrying a granted hosted artifact URL. The run then ensures allow-remote=all in the project .npmrc:
redirect_npmrc_allow_remote (created / added).redirect_npm_allow_remote with the tradeoff: all admits every url-resolved dependency tree-wide, while each entry's sha512 pin stays enforced.Explicit values are respected and named in the warning. Any explicit non-all value in:
.npmrc;@npmcli/config does;npm_config_allow_remote env var, which beats every .npmrc.A symlinked, unreadable or bare-CR .npmrc is left alone, with a manual-remedy warning.
Opt-out. --no-npm-allow-remote-config / SOCKET_NO_NPM_ALLOW_REMOTE_CONFIG.
Grammar. Follows npm's bundled ini exactly, measured on npm 12.1.0 and cross-checked against ini 6.0.0 / 7.0.0:
allow-remote counts;[section] bodies are not top-level;Revertible. Every reversal removes exactly what was added, once no package-lock entry needs it:
NpmrcAllowRemote inverse);rollback / remove / the vendored takeover ("last one out", same transaction);A created file the user has since modified keeps the user's lines (redirect_npmrc_allow_remote_modified, surfaced by rollback, remove, vendor and the reconcile). A symlinked .npmrc refuses an unwind at plan time, before anything is written.
Safety. Never written on --dry-run; the dry run previews the write, including for a vendored → hosted takeover. Mode-preserving atomic writes create the stage with the destination's permission bits, so a 0600 token-bearing .npmrc is never staged world-readable.
The human output this branch adds uses #248's mechanisms:
The omission line is Consolidated terminal UI and CLI-wide output polish #248's Warning: omitting <purl> from VEX: <phrase> (<tag>), with phrases for the new reasons (record_unavailable, record_mismatch, vendor_unwired, redirect_unwired, wiring_conflict).
Omissions are sorted by purl, so stderr and the skipped events are deterministic.
Plan notes are sorted, deduplicated and start with a capital letter.
Counts use ui::plural. Record fetches show a ui::StatusLine (Fetching patch records... (n/total)), which is silent under --json / --silent.
Manifest-less VEX lines use Consolidated terminal UI and CLI-wide output polish #248's format_vex_written / format_vex_dry_run_skip.
The npm caveat prints as Warning (redirect_npm_allow_remote): ….
Failed VEX runs carry the discovery diagnostics into warnings[] (standalone, embedded and scan JSON envelopes). The reason a patch was gated is there too:
vex_record_offline, vex_record_not_found, vex_record_fetch_failed;api_auth_fallback, the same text as get / scan;vex_wiring_conflict, vex_record_superseded and vex_claim_unwired.A failed embedded --vex adds one vex_omitted per omitted patch to the host command's warnings[], and --silent lists the omissions under the error.
Every PM is supported in both hosted and vendored modes, except as noted. "Hermetic" means the wiremock-backed <pm> module of the one e2e_vex_lockfile test binary (tests/e2e_vex_lockfile/<pm>.rs; run one with cargo test -p socket-patch-cli --test e2e_vex_lockfile <pm>::), in the default test job on all 3 OSes. "Real-PM" means the gated suites in CI's e2e matrix, the dedicated matrix jobs or the per-PM compatibility workflows; every real-PM flow ends in the manifest-less VEX matrix, with these cells:
--offline → record_unavailable with zero requests;--no-verify;legacy-manifest where vendored.| PM | Hosted | Vendored | Hermetic suite | Real-PM versions (CI) |
|---|---|---|---|---|
| npm | ✓ (npm 12: auto .npmrc; npm 6: fails closed, redirect_npm_legacy_client) |
✓ (npm 12 dual-lock: both locks rewired) | e2e_vex_lockfile::npm, redirect_npm_allow_remote | 6.14.18, 7.0.0, 7.24.2, 8.19.4, 9.0.0, 9.9.4, 10.9.9, 11.20.0, 12.0.0, 12.1.0 (npm-compatibility.yml) |
| pnpm | ✓ | ✓ | e2e_vex_lockfile::pnpm | e2e_redirect_pnpm_build (3 OS), pinned vendored matrix, pnpm-compatibility.yml, Rush sim |
| yarn classic | ✓ | ✓ | e2e_vex_lockfile::yarn_classic | 1.0.2, 1.6.0, 1.7.0, 1.9.4, 1.10.1, 1.22.22 (yarn-classic-matrix) |
| yarn berry | ✓ | ✓ (needs a resolutions mapping) |
e2e_vex_lockfile::yarn_berry | 4.0.2, 4.1.0, 4.6.0, 4.12.0, 4.18.0 (+ macOS/Windows); 2.x/3.x refused lanes (yarn-berry-e2e) |
| bun | ✓ | ✓ | e2e_vex_lockfile::bun | 1.3.14 redirect/vendor; bun.lockb 1.0.36 / 1.1.45; mode_migration 1.1.45 / 1.2.23 (bun-compatibility.yml) |
| cargo | ✓ | ✓ | e2e_vex_lockfile::cargo | toolchains 1.82.0 / 1.93.1 / stable × Cargo.lock v1–v4 (+ macOS/Windows v1) (cargo-vex-matrix) |
| golang | ✓ | ✓ (+ go-patches) | e2e_vex_lockfile::golang | 1.18.10, 1.21.13, 1.24.13, 1.26.3 (go-compatibility.yml); go.work build suite |
| uv | ✓ | ✓ | e2e_vex_lockfile::uv | 16 releases, 0.1.45 → 0.12.17; uv backtest |
| poetry | ✓ | ✓ | e2e_vex_lockfile::poetry | 1.0.10, 1.1.15, 1.8.5, 2.0.1, 2.4.3 (poetry-compatibility.yml) |
| pdm | ✓ | ✓ | e2e_vex_lockfile::pdm | 1.4.5, 1.15.5, 2.7.4, 2.8.2, 2.25.9, 2.29.2 (lock 3.1 and 4.0–4.2 refused) |
| hatch | ✓ | ✓ | e2e_vex_lockfile::hatch | 1.0.0, 1.2.1, 1.9.7, 1.14.2, 1.18.1 |
| pipenv | ✓ | ✓ | e2e_vex_lockfile::pipenv | 2022.12.19 → 2026.8.0 |
| pip (requirements) | ✓ | ✓ | e2e_vex_lockfile::pip | pip 22–26 |
| gem | ✓ | ✓ | e2e_vex_lockfile::gem | bundler 1.17.3, 2.1.4, 2.2.33, 2.5.23, 2.6.9, 2.7.2, 4.0.15, 4.0.21; docker vendored gem |
| composer | ✓ | ✓ | e2e_vex_lockfile::composer | composer 1, 2.2 LTS, 2 |
| maven | ✓ | ✓ (jar + .sha1 required) |
e2e_vex_lockfile::maven | 3.6.3, 3.8.9, 3.9.16, 4.0.0-rc-6 |
| nuget | ✓ (needs a packages.lock.json entry or an exclusive mapping) |
✓ | e2e_vex_lockfile::nuget | dotnet 6, 7, 8, 9, 10 |
| deno | — | — | e2e_vex_lockfile::deno (negative) | 1.46.3, 2.9.7 (agent mode + setup.manual only) |
Cross-cutting suites: e2e_vex_lockfile::manifestless_embedded, e2e_vex_redirect, e2e_vex_vendor, e2e_vex_lockfile::common_selftest, and the core polyglot test (a root with every PM discovers the union).
Real-PM spot checks (local macOS, all green). At this HEAD (78a9f557), after the review fixes: npm 12.1.0 hosted + vendored capstones (e2e_redirect_npm_build, e2e_vendor_npm_build) 24/24. The rest are from the final code before the whitespace-only fmt fixups and the test-only golden slimming:
| PM | Version(s) | Result |
|---|---|---|
| npm | 12.1.0 | hosted + vendored capstones (e2e_redirect_npm_build, e2e_vendor_npm_build) 24/24 |
| pnpm | 10.34.5 | pinned matrix: hosted and vendored 3/3 |
| yarn berry | 4.12.0, plus 2.4.3 / 3.8.7 refusal lanes | 103/103 cells |
| yarn classic | 1.22.22 | 65/65 cells |
| bun | 1.3.14 | hosted + vendored 26/26 |
| cargo | 1.93.1 | lock v1 10/10, lock v4 10/10 (redirect, vendor, mode migration) |
| go | 1.26.3 | 53/53 (build, hosted, workspace, vendored) |
| uv | 0.11.19 (Python 3.12) | hosted 14/14, vendored 18/18 |
| bundler | 4.0.15 | hosted + vendored 25/25 |
| maven | 3.9.16 | hosted and vendored builds 2/2 |
| dotnet | 8.0 | nuget build 2/2 |
The uv leg needs SOCKET_PATCH_UV_E2E_PYTHON set to a Python ≥ 3.9. Without it, uv picked this machine's Python 3.8 and hosted_uv_export_pylock_manifestless_vex stopped at setup (pylock.toml requires >=3.9). That is an environment issue, not a product one.
Earlier rounds (local macOS, all green): after the first refactor pass, the same eleven PMs. After the #248 rebase, npm 12.1.0 and pnpm 10.34.5. After the #247 rebase, npm 10.9.9 / 12.1.0, pnpm, both yarns, cargo, go, uv and bundler. Before the rebases, the full per-version matrices above were run per PM. At this HEAD they run in CI.
--no-verify / --vex-no-verify. Those flags now skip only the hashing..socket/vendor/state.json is vendor_ledger_corrupt for vex (exit 2; the host command fails under --vex).allow-remote=all to the project .npmrc (or respect and name an explicit value) and always warn redirect_npm_allow_remote. New flag --no-npm-allow-remote-config / SOCKET_NO_NPM_ALLOW_REMOTE_CONFIG. New ledger kind redirect_npmrc_allow_remote and warning redirect_npmrc_allow_remote_modified. Commit the .npmrc with the lock.record_unavailable, record_mismatch, vendor_unwired, redirect_unwired, wiring_conflict.lockfile_unreadable, lockfile_unparseable, patched_ref_invalid, patched_ref_unattributable, vex_record_offline, vex_record_not_found, vex_record_fetch_failed, vex_wiring_conflict, vex_record_superseded, vex_claim_unwired, vex_omitted; also api_auth_fallback on vex.Cargo.lock's [metadata] checksums now verify a crates.io fetch;requirements.txt is read as pip's logical lines;six==1.*) is not an exact version;http:// origin or a path-prefixed origin stays inventoried;pnpm-lock.yaml is inventoried;poetry.lock / pdm.lock / Cargo.lock that is not valid TOML contributes nothing.bun.lock is lockfile_unparseable.vendor_override_conflict.scan liveness: a commented-out Gemfile source … do block no longer keeps a reverted record alive.manifest_not_found (exit 2) now means no manifest AND nothing wired.apply --vex / vendor --vex without a manifest now attest what the lockfiles and ledgers wire. A project with nothing wired keeps the calm exit 0 and removes a stale document. apply --check and --dry-run never generate.--global / --global-prefix, because it gates the ledgers read from --cwd.vendor embeds the patch record in every ledger entry.source block that precedes dist; otherwise composer 1 / 2.2 silently installed the pristine commit from git. A hand-ordered source that can't be dropped warns redirect_composer_source_kept.GEM section in bundler's source order (frozen installs on bundler ≥ 4.0.19).Cargo.lock.vendor_npm_sibling_lock_unwired.redirect_npm_legacy_client.$CARGO_HOME or parent cargo configs;--offline gives record_unavailable.installed.json and COMPOSER=-renamed locks;allow-remote=all is tree-wide, which is npm's granularity; the per-entry sha512 pins stay enforced. A user-level allow-remote=none is respected, so npm 12 then refuses the hosted install until the user changes it (the warning says so).--vex on get --mode hosted|vendored;Final local gate on feat/vex-lockfile-inventory (HEAD 78a9f557, after the review fixes and the #249 / #250 rebase) (macOS arm64, CARGO_INCREMENTAL=0). The branch is based on origin/main = e0246295 and the tree is clean.
| Check | Result |
|---|---|
cargo clippy --workspace --all-features --all-targets -- -D warnings |
clean |
cargo test --workspace --all-features --profile ci-release --no-run |
builds: 249 test executables (274 before the consolidation, 240 on main), 8m32s cold |
cargo test --workspace --no-fail-fast |
exit 0, 8,682 passed, 0 failed, 135 ignored, across 251 test binaries. core: 4,744 passed (30 binaries, 1 ignored; lib 4,303 including #249 / #250's). cli: 3,938 passed (219 binaries, 133 ignored, which are the gated real-PM / network suites; lib 786). doc-tests: 0 run, 1 ignored. vexctl was not on PATH for this run, so the vexctl conformance step self-skipped (it ran in the earlier gate below and runs in CI) |
| rustfmt | the review fixes changed only their own hunks; no cargo fmt --all |
| CodeQL guard | a scan of every macro message the branch adds finds no uuid / token flow into assert! / panic! / println! / eprintln! (4 hits, all checked by hand: string literals only) |
| per-commit build | cargo check --workspace --all-features --all-targets passes on commits 2, 5 and 6, the ones the review fixups changed code in |
Earlier gate (HEAD faf9ed3, on 15a6ba6). The rows below were not re-run at this HEAD; the review fixes touch none of the scripts or dispatch files, and actionlint was re-run on the edited workflows with the same result:
| Check | Result |
|---|---|
cargo test --workspace --no-fail-fast (vexctl v0.3.0 on PATH, SOCKET_PATCH_GO_E2E_REQUIRED=1, like CI) |
exit 0, 8,503 passed, 0 failed, 135 ignored, across 276 test binaries |
| rustfmt | every new file is formatted. In the 4 changed files where this branch had added unformatted hunks, those hunks were fixed (8 hunks, folded into their commits); the other hunks in those files are main's own, untouched as the repo rules require |
| golden snapshots | see "Equivalence proof" |
scripts/release-lint.sh --sync-only |
OK (4.0.0 everywhere) |
npm dispatch tests (node --test npm/socket-patch/bin/socket-patch.test.mjs) |
4/4 |
pypi dispatch tests (python3 pypi/socket-patch/test_dispatch.py) |
9/9 |
python3 -B -m unittest discover -s scripts/tests |
15 OK, 1 skipped |
shellcheck: --shell=sh scripts/install.sh, the release scripts, and the new scripts/{uv,yarn-berry,yarn-classic}-vex-matrix.sh |
clean |
| actionlint | same 8 findings as main (pre-existing ci.yml shellcheck infos; one line number shifts) |
Not run locally, left to CI: the Linux and Windows test legs, test-release, coverage, e2e-docker, hosted-e2e (production credentials), and the full per-version matrices.
The commits are meant to be read in order, oldest first. Each one builds on its own:
e31da367 fix(core): hosted/vendored rewriter fixes found by the real-PM matrices. Seven independent rewriter bugs, each with a core regression test. It is small and has no VEX code, so it is a good warm-up.60ddaf82 feat(hosted): auto-configure npm 12 allow-remote in the project .npmrc. Start at core/src/patch/redirect/npmrc.rs (the grammar, the plan and the unwind). Then read replay.rs / takeover.rs, then cli/src/commands/scan/hosted.rs and remove.rs. Tests: tests/redirect_npm_allow_remote.rs.56d791fa refactor(core/vendor): split lock_inventory into per-format submodules. A verbatim move of main's file. Only imports, visibility, module docs and section banners change, and git show --color-moved shows everything else as moved. It exists so that the next commit's inventory changes can be read per format.52e71ee4 feat(core/vex): discover hosted and vendored patch references from lockfiles.
core/src/vex/discover/mod.rs: the types, the orchestrator, DiscoverCtx::locate, contested locks, recognized, and the liveness rule with LedgerLiveness.npm.rs and pypi_locks.rs are the largest. Also read patch::redirect::hosted_patch_uuid.vendor/lock_inventory/*, plus vendor/{gemfile_lock, maven_pom, nuget_config, cargo_config, go_mod_edit, go_sum_edit, yarn_berry_lock, yarn_classic_lock, bun_lock_text}.rs and utils/{digest, purl, requirements, python_lock, hatch}.rs.4868030a feat(vex): manifest-less VEX.
cli/src/commands/vex_sources.rs: the record view, the API fetch and the gating-reason warnings.vex_consumed.rs: hosted evidence.vex.rs: the liveness gates and the output.apply.rs / vendor.rs / scan.vendor_record_is_unowned, the product probes, and scan/mod.rs's classify_overlap_takeover / hosted_wiring_retained_purls, which now use the core liveness rule through commands::discover_wiring and LedgerLiveness.4f595bce test(cli): the suites. Mostly mechanical. Worth reading: tests/vex_e2e_common/ (the shared omission oracle) and one hermetic module, for example tests/e2e_vex_lockfile/npm.rs.75c80e88 ci: the matrices and backtest steps. Covers ci.yml, the new npm / go / poetry compatibility workflows and the scripts/*-vex-matrix.sh drivers.
tests/ target is its own optimized link in test-release, so the new suites share two multi-module binaries: tests/e2e_vex_lockfile/ (the 19 hermetic per-PM suites plus manifestless_embedded and common_selftest, a module each) and tests/e2e_vex_build/ (the Poetry / PDM / Hatch / Pipenv / pip / deno real-PM capstones; each e2e matrix leg and pdm-compatibility.yml pick their module with a test_filter such as pdm:: --ignored). The branch adds 9 test binaries instead of 34 (cargo test --workspace --no-run: 249, against 240 on main and 274 before the suites were merged).test-release goes from timeout-minutes: 30 to 40: it builds the graph twice and took ~25m on main already.main (save-if), including the PDM VEX capstone.780d76e5 docs. The CLI_CONTRACT.md sections "Manifest-less VEX (lockfile discovery)" and "Additive warnings", the README, the CHANGELOG [Unreleased] section and docs/testing/npm-compatibility.md.78a9f557 test(vex): golden snapshot of discovery over committed fixtures. The harness is core/src/vex/discover/testing/golden.rs; the JSON under core/tests/fixtures/vex-discover-golden/ is generated, so skim it.🤖 Generated with Claude Code
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Stale Bugbot comment from a previous run.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
BugBot review |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Claude (@claude) review |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
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 faf9ed3. Configure here.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
First of seven commits that land the manifest-less VEX branch (feat/vex-lockfile-inventory) on top of #247. The branch's ~165-commit history, built on the pre-#247 main 9489b18 with per-PM merge commits, was squashed onto 09956d9 and re-split by concern; the full pre-rebase history is preserved on branch backup/vex-lockfile-pre-rebase. These are the product bugs the per-PM real-toolchain matrices turned up while the manifest-less VEX suites were written. Each is independent of VEX and pinned by a core regression test: - composer (hosted): the rewriter drops the entry's `source` block when it immediately precedes `dist` (one `redirect_composer_dist` fragment edit spanning both, reverted byte-for-byte). Composer 1 and 2.2 LTS silently installed the pristine upstream commit from git whenever the hosted download failed; a hand-ordered source that cannot be dropped warns `redirect_composer_source_kept`. Golden fixture: `source-and-dist`. - gem (hosted): the patch-registry `GEM` section is inserted in bundler's source order (sorted by remote), so a frozen `bundle install` on bundler >= 4.0.19 accepts the converged lock. The `basic` golden and the exact lock expectations move the Socket section first. - cargo (hosted + vendored): a v1 `Cargo.lock` (checksums in `[metadata]`, dependents naming the crate by its full package id) is redirected and vendored correctly: the `[metadata]` line and every dependent's full-id reference follow the source, each fragment its own ledger edit, and `cargo --locked` accepts the result; the vendored detach/restore of a v1 entry is byte-identical. `plan_cargo_lock` keeps #247's multi-source twin disambiguation (`Ambiguous`) and hoisted regexes; the block end now also stops at a trailing `[metadata]` / `[[patch.unused]]`, and the checksum is inserted after a block-final `source` line too. - yarn berry (hosted + vendored): written checksums follow the lock's own spelling — yarn 4.0.0–4.0.2 spell `10c0` checksums as bare hex, so the prefixed form failed `yarn install --immutable` with YN0028. - npm (vendored): npm 12 reifies from the `package-lock.json` it creates beside a committed shrinkwrap, so `vendor` now rewires every present npm lock (siblings first, primary last; an unrewirable sibling warns `vendor_npm_sibling_lock_unwired`), and the in-use/revert probes read every npm lock before deleting an artifact. `select_lockfile` reads through #247's guarded `read_regular_to_bytes`. - npm (hosted): a lockfileVersion 1 redirect warns `redirect_npm_legacy_client` — npm 6 ignores a v1 lock's `resolved` and fails EINTEGRITY against the patched pin. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
npm >= 12 defaults to `allow-remote=none` and refuses (EALLOWREMOTE) every
lockfile entry whose tarball is not served by the configured registry —
exactly what a hosted redirect writes. When `scan --mode hosted` / `get
--mode hosted` leaves a root package-lock.json / npm-shrinkwrap.json
carrying a granted hosted artifact URL, the run now ensures
`allow-remote=all` in the project `.npmrc` (creating the file, or
appending one line with the BOM, CRLF and every other byte preserved) and
records it in the redirect ledger as `redirect_npmrc_allow_remote`
(`created` / `added`).
- core `patch::redirect::npmrc`: npm's `.npmrc` grammar as measured
against npm 12.1.0 (exact `allow-remote` key, last top-level assignment
wins, `[section]` bodies are not top-level, bare-CR line splits, case-
sensitive value), the plan (create / append / already-all / respected
user / env / outer-layer value / unsupported), and the unwinds.
- Every reversal removes exactly what was added once no package-lock
entry needs it: the whole-ledger replay (a new `NpmrcAllowRemote`
inverse, grouped with the npm lock kinds), the per-purl npm revert
behind scoped rollback / remove / the vendored takeover ("last one
out", same transaction, flushed after the lock through #247's shared
`staged::flush_staged`), and the vendored-supersedes-hosted reconcile.
A modified created file keeps the user's lines
(`redirect_npmrc_allow_remote_modified`, surfaced by rollback, remove,
vendor and the reconcile). A symlinked `.npmrc` refuses an unwind at
plan time, before anything is written.
- Hosted run: the `.npmrc` edit rides `rewrite.files` / `rewrite.edits`,
so it is written under #247's apply-lock window, after its whole-run
SYMLINK GUARD, and only after the ledger persisted — never on
`--dry-run` (which previews the write, also for a vendored → hosted
takeover; the root locks are now read for such a preview so the pnpm
`trustLockfile` preview sees the lock the wet run splices). An explicit
user value (project `.npmrc`, user/global/builtin config, or an
`npm_config_allow_remote` env var) is respected and named; a symlinked,
unreadable or bare-CR `.npmrc` is left alone; `--no-npm-allow-remote-
config` / `SOCKET_NO_NPM_ALLOW_REMOTE_CONFIG` opts out. Every variant
warns `redirect_npm_allow_remote` with the whole-tree tradeoff.
- `atomic_write_bytes_preserving_mode` creates its stage with the
destination's permission bits (kept inside #247's `commit_stage`
structure), so a 0600 token-bearing `.npmrc` is never staged world-
readable.
- remove: the hosted leg's advisories are printed inside #247's
`unwind_hosted` (so a run that then fails still reports them) and carried
into the success envelope's `warnings[]`.
Tests: npmrc/replay/takeover/scan unit tests, the flag's parse coverage,
`redirect_npm_allow_remote` (plus #247's invariants: the lock never
outlives the run, dry runs create no `.socket/`, a full rollback leaves no
`.socket/` and never the user's `.npmrc`), and the dry-run takeover
previews in `coverage_fix_scan_hosted_dryrun_vendored`.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
A verbatim move with no behavior change, so the next commit can give each lock format its shared entry model beside its registry view: `vendor/lock_inventory.rs` becomes a directory module with one file per format (`npm`, `npm_family`, `pnpm`, `yarn`, `bun`, `cargo`, `golang`, `composer`, `gem`, `pypi`), ledger recovery (`recover`) and the rewired-lock trust anchor (`wired`). `mod.rs` keeps the public API (`LockIntegrity`, `LockfileEntry`, `UnsupportedNpmLayout`, `lookup`, `inventory_project(_diagnosed)`, `recover_lock_entry`, `wired_vendor_integrity`); the three test modules move to `tests.rs`, `recover_tests.rs` and `python_lock_union_tests.rs`. Only imports, visibility (helpers another file calls become `pub(super)`), sibling-module paths (`super::state` -> `crate::vendor::state`), module docs and the single-file section banners changed; the test modules lost one indentation level and were re-wrapped by rustfmt. `git show --color-moved` shows everything else as moved. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…ckfiles
New read-only module `vex::discover`: `discover_patched_refs(_with)`
reads every supported ROOT lockfile / package-manager config and returns
the Socket patch wiring it finds — `Discovery { refs, diagnostics,
recognized, unlocked_pins, elsewhere }`, one `PatchedRef { purl, uuid, mode
(Hosted | Vendored), source_file, artifact_rel, locked_integrity,
integrity_required, url }` per live reference. It never touches the
network, never writes, and never fails the run: a malformed file is a
diagnostic (`lockfile_unreadable`, `lockfile_unparseable`,
`patched_ref_invalid`, `patched_ref_unattributable`).
One extractor per package-manager family: npm (package-lock /
shrinkwrap, pnpm every lock generation + Rush locks), yarn classic and
berry, bun (`bun.lock` / `bun.lockb`), cargo, golang (go.mod / go.work +
sums), pypi locks (uv, PEP 723 script locks, pylock, poetry, pdm), pypi
other (Pipfile.lock, requirements + `-r`, Hatch / PEP 621 direct refs),
gem, composer, maven, nuget; deno is explicitly empty. Every file present
is read — no precedence chain — because the hosted rewriter edits every
candidate it finds.
Every value is committed, tamperable data and is validated fail-closed:
- hosted: `hosted_patch_uuid` accepts only `https://patch.socket.dev` or
a configured `--patch-server-url` origin, no userinfo, and takes the
LAST canonical-uuid path segment (grant tokens may be uuid-shaped);
percent-encoding, `\/` escapes and fragments are handled;
- vendored: root-anchored `.socket/vendor/<eco>/<uuid>/…` paths whose
leaf names the entry's own artifact (`vendor_ref` takes the path
literally; only yarn / URL-form pip strip their `#…` / `::…` decorations);
- pins, not definitions: a registry / index / source definition alone
(cargo `[registries]`, nuget `<add>`, pom `<repository>`, uv index
tables, `.npmrc`) never makes a reference;
- contested locks: a lock that resolves the same name@version from a
non-Socket source drops the ref (`patched_ref_unattributable`);
- lockless cargo pins / exclusive nuget mappings are recorded as
`UnlockedPin`s that can only keep a ledger record live, never create a
ref; `recognized` lists every uuid a read file mentions, so a rejected
mention keeps nothing alive downstream.
Supporting core changes: `patch::redirect::{SOCKET_PATCH_SERVER_HOST,
hosted_patch_uuid, hosted_patch_url_uuids}` (the pipenv owner check uses
the shared host constant); the pnpm lock grammar and `hosted_url_version`
exported crate-wide so readers parse exactly what the writers write;
`utils::digest` (the SRI pin rule and the hex digest shapes — one copy for
the inventory, discovery, ledger recovery and the rewriters, each call
site keeping its case policy); and `utils::purl`'s validating purl
builders, which discovery and the inventory's registry views share. A few
writer helpers become `pub(crate)` so the extractors' tests derive their
fixtures from the writers themselves.
One lockfile traversal layer: discovery and `vendor::lock_inventory` (the
scan / get / vendor / repair inventory) read each format through ONE
reader that yields every entry, Socket-owned ones included — the
inventory's registry views drop those, the extractors classify them:
- `lock_inventory` becomes a directory module, one file per format, each
laid out as a pure entry model, a stat-only file-selection section and
the registry view (an architecture test enforces the layering). Entry
models: `npm_lock_nodes`; `pnpm_packages` over the hosted rewriter's
pnpm grammar (every key generation, CRLF included); the yarn
`classic_entries` / `berry_entries` models with one berry locator,
cache-key and checksum rule; `BunLockb::parse_packages`; and
`composer_lock_packages`, whose array index the composer writer's lock
walks use too.
- Every other format reads through the reader its writer owns:
`cargo_lock::locked_packages` and `cargo_config`'s `[patch]` /
`[registries]` walks; `go_mod_edit` / `go_sum_edit` read helpers; a new
`vendor::gemfile_lock` model; the `utils::python_lock` / `poetry_lock` /
`hatch` readers (uv source fields, script-lock pairing, the pyproject /
Hatch declaration walk); a new `utils::requirements` lexer lifted out of
the vendored requirements planner; and two new XML readers,
`vendor::maven_pom` and `vendor::nuget_config`, with
`nuget_feed::nuget_lock_entries` shared by discovery and the feed
writer.
- `DiscoverCtx::locate` classifies a lock location once (vendored path,
hosted uuid, decorated leaf) for every extractor. The npm-family
extractors iterate the entry models only, never the grammar primitives,
and every extractor reads content only through the recognizing ctx
(rule 11) — both enforced by architecture tests.
Ledger liveness is one rule too: `Discovery::{wires_package,
vendor_entry_live, redirect_record_live}`, held per call site by
`LedgerLiveness` (the sorted redirect-ledger files, the lock inventory
loaded lazily at most once), which the CLI's vex and scan both use. Cargo
crates.io provenance is explicit (`LockfileEntry::source_kind`), not
inferred from the checksum variant.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…m lockfiles `socket-patch vex` and the embedded `apply` / `scan` / `vendor --vex` now attest hosted and vendored patches with no `.socket/manifest.json` — and with no `.socket/vendor` ledgers either — by reading the wiring out of the project's lockfiles (`vex::discover`). This covers a depscan-opened PR, a clone that never committed its ledgers, and a lock-only CI checkout. Record view (`commands::vex_sources::plan`): four sources — the manifest, the redirect ledger's records, the vendor ledger's embedded records and lockfile discovery — merge into one view. A candidate's record must carry the uuid the lockfile actually WIRES; it comes from the first source that has one, else (online only) from the patch API by uuid, 10 fetches at a time with `get`'s one-shot 401/403 → public-proxy fallback (#247's uuid-only `ApiClient::fetch_patch`). Nothing is written: vex never takes the apply lock, never creates `.socket/`, never writes the manifest, and the `socket-patch.vendor.json` marker is never a record. Gates, applied before hashing and kept under `--no-verify` (which now skips only the hashing): - a vendor-ledger entry attests only while a lockfile still wires its artifact (`vendor_unwired`); a redirect-ledger record only while a lockfile wires its hosted patch (`redirect_unwired`); discovery is authoritative for every uuid a read file mentions, so a rejected mention keeps nothing alive; - `record_unavailable` (offline, 404, refused, transport error — the run continues), `record_mismatch`, `wiring_conflict` (lockfiles wire one package to several patches); - a malformed / unreadable `.socket/vendor/state.json` is the hard error `vendor_ledger_corrupt` (exit 2), mirroring `redirect_ledger_corrupt` — this supersedes #247's degrade-and-disclose posture for `vex` only; `setup --check` keeps #247's `vendor_context_from` / `warn_unreadable_vendor_state` path. Evidence: vendored refs hash the committed artifact (the ledger entry when it names the wired artifact, else one synthesized from the ref); hosted refs hash the installed copies the build CONSUMES (`vex_consumed::hosted_consumed_copies` → core `VendorContext::hosted` / `HostedCopies`: the Go replacement module, the Socket-registry cargo src dir, maven's suffixed version — never a pristine sibling), and with nothing installed a discovered pinned reference attests from its lockfile pin, like in-run `scan --mode hosted --vex`. Discovered refs bypass the Property 7 ecosystem filter. Commands: - `apply --vex` / `vendor --vex` with no manifest attest what the lockfiles and ledgers wire (nothing anywhere keeps the calm exit 0 and removes a stale document; `apply --check` and `--dry-run` never generate); #247's no-manifest lines ("No patch manifest found; nothing to apply.", "No manifest found, nothing to vendor.") are kept; - failed VEX runs carry the discovery diagnostics into `warnings[]` (standalone envelope, embedded envelopes, scan JSON — hosted included); - `manifest_not_found` now means no manifest AND nothing wired; - human output: `Note:` lines for superseded records / fetch failures, phrased omission reasons. Writer hardening: the manifest-driven standalone `vendor` now embeds the patch `record` in its ledger entries too (vendored mode already does, as `detached` entries). `detached` stays the "no manifest owner" flag, so the manifest reconcile, legacy-manifest migration and get/scan idempotency from #247 are unaffected. Every reader of embedded records shares one ownership rule (`commands::vendor_record_is_unowned`): a detached entry's record always stands alone, a standalone `vendor` entry's fallback copy only when no manifest entry covers it (by ledger key or base purl). `vex`, `list` and `setup --check` (`fold_vendor_records`, formerly `fold_detached_records`) all apply it, so one tree never lists "no patches" while its VEX document attests one. `repair` stays narrower: it keeps preferring a manifest that moved on to a newer uuid and falls back to the embedded copy only with no manifest at all. One liveness rule for vex and scan: `scan`'s cross-mode takeover classification (`classify_overlap_takeover`), its `hosted_wiring_retained` warning and `redirectState.wiringLive` ask the same core discovery (`commands::discover_wiring`) and liveness rule (`Discovery::redirect_record_live` / `vendor_entry_live` / `wires_package`, through one `LedgerLiveness` holder per call site) that gate attestation, replacing scan's private cargo / hosted / vendored checks and its looser text scan. The CLI also shares one purl splitter (`utils::purl::purl_parts`), one vendor-ledger lookup (`vendor::state::lookup_entry_kv`) and one npm alias-aware identity crawl (`ecosystem_dispatch::npm_paths_by_identity`) across vex, scan and vendor, and pairs PEP 723 script locks through `utils::python_lock::script_of_lock` like the rewriters do. Output follows the conventions from #248: `ui::plural` counts, a `ui::StatusLine` progress line for record fetches, and the shared `format_vex_written` / `format_vex_dry_run_skip` lines on the manifest-less `apply --vex` / `vendor --vex` paths. Manifest-less `vex` honours `--dry-run` and `-O -` like the manifest path. Product auto-detection adds go.mod, composer.json, pom.xml, a single `*.csproj` and a single `*.gemspec`, after the existing probes. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…pers
Hermetic suites (wiremock patch API, run in the default `test` job on
every OS): one `e2e_vex_lockfile_<pm>` per package manager — npm, pnpm,
yarn (classic + berry), bun, cargo, golang, uv, poetry, pdm, hatch,
pipenv, pip, gem, composer, maven, nuget, deno (negative) — covering
spoofed hosts, record mismatch, pristine / tampered installs, contested
and orphaned wiring, `--offline`, reverted locks (also `--no-verify`) and
the embedded commands; plus the cross-cutting `e2e_vex_manifestless_
embedded`, `e2e_vex_redirect`, `e2e_vex_vendor` and the
`vex_e2e_common_selftest` harness checks.
Real-PM capstones (gated like the existing suites, `_REQUIRED` env
pattern): every hosted / vendored build suite now ends in the manifest-
less VEX matrix over a fresh checkout — manifest absent (ledgers kept),
ledgers deleted (lockfile + API), offline (`record_unavailable`, zero
requests), reverted lock (never attested, verify or not) — through shared
helpers: `vex_e2e_common` (+ bun / uv matrices), `npm_e2e_common`
(+ `manifestless`), `yarn_berry_common`, `common/yarn_classic_vex`,
`common/bundler_e2e`, `cargo_e2e_matrix`, `golang_e2e_matrix`,
`maven_build_common`, `composer_e2e_common`, `vex_pdm_hatch_common`,
`vex_pipenv_pip_*`, `vex_pypi_real_common`. New real-toolchain suites:
`e2e_redirect_{composer,maven,uv}_build`, `e2e_vendor_maven_build`,
`e2e_golang_workspace_build`, `e2e_nuget_dotnet_build`,
`e2e_poetry_vex_build`, `e2e_vex_{pdm,pip,pipenv,hatch}_build`,
`e2e_deno_vex_build`; the docker / setup-matrix / production legs gain
their manifest-less VEX steps.
Re-pinned to #247 while rebasing:
- vendored mode writes no manifest: the "manifest deleted" steps are
naturally manifest-free, so the npm manifest-less matrix and the uv /
poetry vendored suites add a `legacy-manifest` cell (the record a
pre-5.0 vendored run left beside its ledger, via
`vex_e2e_common::seed_legacy_manifest`, must attest the same way), the
yarn 2/3 refusal suite seeds that legacy record explicitly (and still
requires `not_applied`, never an attestation), and `--detached` twins
assert no manifest in either spelling;
- a fully reverted project keeps no `.socket/`: suites that plant a stale
ledger back after a rollback recreate the directory first, and the
hosted rollback suite checks that `vex` does not recreate it;
- the no-manifest human lines follow #247's wording;
- a corrupt vendor ledger on a manifest-free project is now
`vendor_ledger_corrupt` (exit 2, the ledger named, never rewritten),
replacing #247's disclose-then-`manifest_not_found` expectation;
- repair: two tests pin the standalone-`vendor` embedded record (a
moved-on manifest still wins; with no manifest the embedded copy stands
in offline), replacing the pre-rebase test that assumed a non-detached
`scan --vendor` entry.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
ci.yml:
- `test` exports `SOCKET_PATCH_GO_E2E_REQUIRED` / `_VERSION` so the
real-go hosted/vendored suites fail instead of skipping on the Go that
is already installed for vexctl;
- `e2e` gains pinned-toolchain legs, each ending in the manifest-less VEX
matrix: composer 1 / 2.2 / 2 (hosted + vendored), bundler 1.17.3 →
4.0.21 (hosted + vendored + setup_matrix_gem), bun text/binary-lock
eras, the named corepack pnpm legs (3 OS), uv 0.1.45 → 0.12.x hosted +
vendored, poetry / pdm / hatch / pipenv / pip, maven 3.6.3 → 4.0.0-rc-6,
dotnet 6 → 10, deno; npm legs hard-require npm where
`Command::new("npm")` can resolve it;
- new jobs: `yarn-classic-matrix` (1.0.2 → 1.22.22), `yarn-berry-e2e`
(4.0.2 → 4.18.0 + macOS/Windows), `cargo-vex-matrix` (toolchains ×
Cargo.lock v1–v4); `e2e-docker` also runs the pypi vendored-PM suite.
Compatibility workflows: npm (npm 6–12 capstones, new), go (1.18.10 →
1.26.3, new), poetry (new), and the pnpm / pdm / pipenv / bun ones run
the manifest-less VEX steps and trigger on the vex sources. The bun
workflow keeps #247's two modes (hosted, vendored — the vendored-
detached leg collapsed into vendored).
Scripts: the bun / pdm / pipenv / poetry / uv backtests gain the
manifest-less VEX checks (a fresh checkout of the committed state must be
attested, also with the ledgers deleted; `--offline` with no ledger omits
`record_unavailable`; a reverted lock never attests, also under
`--no-verify`; a refused lock format attests nothing — the pdm refusal
check follows #247's manifest-free footprint), plus the
`{uv,yarn-berry,yarn-classic}-vex-matrix.sh` drivers and harness unit
tests.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
- CHANGELOG [Unreleased] (both entries are v5 MAJOR): the semver note now lists #247's changes and `vex` refusing stale ledger records and corrupt vendor ledgers; Changed (BREAKING) gains the ledger-liveness / `vendor_ledger_corrupt` entry (and #247's Fixed bullet that had `vex` disclose an unreadable ledger now points at it); Added gains the npm 12 `allow-remote` auto-config, manifest-less VEX, standalone `vendor` record embedding and the new product probes; Fixed gains the composer / gem / cargo v1 / yarn 4.0 / npm dual-lock rewriter fixes after #247's own entries. - CLI_CONTRACT.md: new "Manifest-less VEX (lockfile discovery)" section (inputs, per-ecosystem recognition table, record resolution, verification basis, liveness gates, run warnings), "Patch hosts", the embedded-VEX no-manifest rules, `manifest_not_found` for `vex`, the vendor ledger's `record` semantics (vendored-mode `detached` entries plus the standalone `vendor` fallback copy; the reconcile exemption keys on `detached`), and the new rollback warning code. - README: "No manifest needed for hosted and vendored patches", the rewritten `vex` how-it-works steps and product probes, "npm compatibility (hosted mode and npm 12)", the hosted `.npmrc` commit hint, and the ledger-liveness note under "Undo things". - docs/testing: npm-compatibility.md (npm 6–12, new), uv and bun tables, ecosystems.md. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Pin the exact output of lockfile discovery over every committed fixture project, so a behavior change to discovery (or to the lock readers it shares with the rewriters and vendor::lock_inventory) shows up as a golden diff to review. - Corpus: 182 projects under crates/socket-patch-core/tests/fixtures/ — every redirect case's input/ and expected/ tree, pnpm-hosted, poetry, pipenv, bun-lockb captures, and each pdm-native lock staged as pdm.lock — run through the full orchestrator and checked against rule 11's recognition-covers-refs invariant. - Rendering (src/vex/discover/testing/golden.rs): every PatchedRef field (plus lockfile_basis_ok), diagnostics (tempdir root and OS error numbers normalized), recognized identities, unlocked pins, resolved-elsewhere entries, and the live hosted / vendored ledger claims. The destructuring is exhaustive, so a new field fails to compile until the golden covers it. - Goldens: one JSON per fixture family (redirect-<eco>, bun-lockb, pdm-native, pipenv, pnpm-hosted, poetry) under tests/fixtures/vex-discover-golden/, mapping fixture path to output. Missing and orphaned entries fail. Regenerate after an intended change with SOCKET_PATCH_UPDATE_GOLDEN=1 cargo test -p socket-patch-core --lib vex::discover::testing::golden. Unix only: Windows checkouts convert some fixtures' line endings. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
faf9ed3 to
78a9f55
Compare
|
Pushed
Local gate: clippy |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
cf8150b
into
main
There was an error while loading. Please reload this page.
The cargo-vex-matrix windows-latest leg used the default pwsh shell, where "$CARGO_TEST_TOOLCHAIN" is an unset PowerShell variable, so it ran `rustup toolchain install ""` and failed. The leg was skipped on #251's own CI, so this is its first real run. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…ee ledger The poetry vendored capstone (added in #251, written before its rebase onto #247) still asserted `scan --vendor` writes `.socket/manifest.json`. Since #247 vendored mode is manifest-free (CLI_CONTRACT `scan --vendor`): the run writes only `.socket/vendor/**`, and each ledger entry is `detached: true` with the patch record embedded. The assertion therefore failed on every Poetry release in the CI matrix the first time the leg actually ran (#253). Assert the contract instead: no manifest is written, and the ledger entry for the vendored uuid is detached and embeds its record. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
… work under CI
yarn 3 and later turn enableImmutableInstalls on by default when ci-info
detects CI (CI / GITHUB_ACTIONS). With that default, the plain
`yarn install` that writes each fixture's first yarn.lock fails with YN0028
("The lockfile would have been created by this install, which is
explicitly forbidden"): exit 1, nothing on stderr. Every hosted, vendored,
pnpm-linker, workspaces and yarn-3 refusal fixture on the new yarn-berry
legs (4.0.2, 4.1.0, 4.6.0, 4.12.0 ubuntu+macOS, 4.18.0) failed that way. The
yarn 2 refusal legs passed because yarn 2 keeps the default off. The suites
were never run under CI before #251 added the legs; the main test job
soft-skips them.
yarn_berry_common::pin_berry_ci_defaults now sets CI=true (local runs get
the runner's defaults) and YARN_ENABLE_IMMUTABLE_INSTALLS=false. It is
applied in every berry suite's corepack helper, after the YARN_* scrub and
cache_env::isolate. The fresh-checkout installs still pass --immutable
explicitly, and yarn's flag outranks the setting, so lock enforcement is
unchanged.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
On Windows env var names are case-insensitive: run_isolated blanks NPM_CONFIG_ALLOW_REMOTE before the test sets npm_config_allow_remote=none, so the child sees a single variable under the first spelling and the warning (which names the variable as the OS reports it) says NPM_CONFIG_ALLOW_REMOTE=none. The product is right; the assertion was POSIX-only. Fixes outer_npm_config_layers_are_respected on test (windows-latest) after #251. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
The cargo-vex-matrix windows-latest leg used the default pwsh shell, where "$CARGO_TEST_TOOLCHAIN" is an unset PowerShell variable, so it ran `rustup toolchain install ""` and failed. The leg was skipped on #251's own CI, so this is its first real run. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…ee ledger The poetry vendored capstone (added in #251, written before its rebase onto #247) still asserted `scan --vendor` writes `.socket/manifest.json`. Since #247 vendored mode is manifest-free (CLI_CONTRACT `scan --vendor`): the run writes only `.socket/vendor/**`, and each ledger entry is `detached: true` with the patch record embedded. The assertion therefore failed on every Poetry release in the CI matrix the first time the leg actually ran (#253). Assert the contract instead: no manifest is written, and the ledger entry for the vendored uuid is detached and embeds its record. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
… work under CI
yarn 3 and later turn enableImmutableInstalls on by default when ci-info
detects CI (CI / GITHUB_ACTIONS). With that default, the plain
`yarn install` that writes each fixture's first yarn.lock fails with YN0028
("The lockfile would have been created by this install, which is
explicitly forbidden"): exit 1, nothing on stderr. Every hosted, vendored,
pnpm-linker, workspaces and yarn-3 refusal fixture on the new yarn-berry
legs (4.0.2, 4.1.0, 4.6.0, 4.12.0 ubuntu+macOS, 4.18.0) failed that way. The
yarn 2 refusal legs passed because yarn 2 keeps the default off. The suites
were never run under CI before #251 added the legs; the main test job
soft-skips them.
yarn_berry_common::pin_berry_ci_defaults now sets CI=true (local runs get
the runner's defaults) and YARN_ENABLE_IMMUTABLE_INSTALLS=false. It is
applied in every berry suite's corepack helper, after the YARN_* scrub and
cache_env::isolate. The fresh-checkout installs still pass --immutable
explicitly, and yarn's flag outranks the setting, so lock enforcement is
unchanged.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…es after #251 (#253) * test(hosted): match the allow-remote env var name case-insensitively On Windows env var names are case-insensitive: run_isolated blanks NPM_CONFIG_ALLOW_REMOTE before the test sets npm_config_allow_remote=none, so the child sees a single variable under the first spelling and the warning (which names the variable as the OS reports it) says NPM_CONFIG_ALLOW_REMOTE=none. The product is right; the assertion was POSIX-only. Fixes outer_npm_config_layers_are_respected on test (windows-latest) after #251. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * ci: run the cargo matrix toolchain install under bash on every OS The cargo-vex-matrix windows-latest leg used the default pwsh shell, where "$CARGO_TEST_TOOLCHAIN" is an unset PowerShell variable, so it ran `rustup toolchain install ""` and failed. The leg was skipped on #251's own CI, so this is its first real run. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * test(e2e_vex_build): poetry vendored capstone asserts the manifest-free ledger The poetry vendored capstone (added in #251, written before its rebase onto #247) still asserted `scan --vendor` writes `.socket/manifest.json`. Since #247 vendored mode is manifest-free (CLI_CONTRACT `scan --vendor`): the run writes only `.socket/vendor/**`, and each ledger entry is `detached: true` with the patch record embedded. The assertion therefore failed on every Poetry release in the CI matrix the first time the leg actually ran (#253). Assert the contract instead: no manifest is written, and the ledger entry for the vendored uuid is detached and embeds its record. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * test(yarn-berry): pin enableImmutableInstalls off so fixture installs work under CI yarn 3 and later turn enableImmutableInstalls on by default when ci-info detects CI (CI / GITHUB_ACTIONS). With that default, the plain `yarn install` that writes each fixture's first yarn.lock fails with YN0028 ("The lockfile would have been created by this install, which is explicitly forbidden"): exit 1, nothing on stderr. Every hosted, vendored, pnpm-linker, workspaces and yarn-3 refusal fixture on the new yarn-berry legs (4.0.2, 4.1.0, 4.6.0, 4.12.0 ubuntu+macOS, 4.18.0) failed that way. The yarn 2 refusal legs passed because yarn 2 keeps the default off. The suites were never run under CI before #251 added the legs; the main test job soft-skips them. yarn_berry_common::pin_berry_ci_defaults now sets CI=true (local runs get the runner's defaults) and YARN_ENABLE_IMMUTABLE_INSTALLS=false. It is applied in every berry suite's corepack helper, after the YARN_* scrub and cache_env::isolate. The fresh-checkout installs still pass --immutable explicitly, and yarn's flag outranks the setting, so lock enforcement is unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * test(yarn-berry): spawn corepack.cmd on Windows Node ships corepack on Windows as the corepack.cmd batch shim, and Command::new("corepack") only resolves corepack.exe. On the windows-latest yarn-berry leg every suite's availability probe therefore reported "`corepack [email protected]` unavailable" ([email protected] / 3.8.7 in the refusal suite), and SOCKET_PATCH_YARN_E2E_REQUIRED=1 turned each of those into a failure. yarn_berry_common::corepack_command() picks the spawnable name. Every berry suite's has_corepack_pm probe and corepack helper uses it. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * test(yarn-berry): show yarn's stdout when a fixture install fails yarn berry prints its errors (YN0028, YN0018, …) on stdout and leaves stderr empty. The fixture and bootstrap skip messages printed only stderr, so all 11 failures on each yarn-berry CI leg read "fixture `yarn install` failed (registry unreachable?):" followed by nothing. The real cause was YN0028 under CI's implicit immutable default. yarn_berry_common::yarn_output formats both streams, and every berry fixture/bootstrap skip now uses it. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * ci: select the pinned bundler with BUNDLER_VERSION on every bundler leg The Ruby 3.1 legs pinned to bundler 2.1.4 and 2.2.33 never ran those bundlers. setup-ruby's `bundler:` input only `gem install`s the release; with no lockfile to read, RubyGems' `bundle` binstub then activates the HIGHEST installed bundler, which on Ruby 3.1.7 is its default gem 2.3.27. tests/common/bundler_e2e.rs correctly panicked on every test ("bundle on PATH is 2.3.27"). The 1.17.3 legs passed only because the Bundler 1.x step already exported BUNDLER_VERSION. Export BUNDLER_VERSION for every pinned-bundler leg (a new step after the 1.x install), which makes the binstub pick exactly the pinned release in every process and also turns off bundler >= 2.3's lockfile-driven self-switch. Verified locally in Docker (Ruby 3.1.7 / 3.3.10 / 3.4.9, setup-ruby layout): all 16 e2e_{redirect,vendor}_gem_build legs and the setup_matrix_gem leg green with the export; the 2.1.4/2.2.33 legs fail exactly as in CI without it. The harness panic now names the fix. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * test(vex/deno): stop expecting a manifest from a refused vendored scan The real-deno negative capstone asserted that `scan --mode vendored` "left a manifest" for the patch it could not wire, then checked that the unapplied manifest attests nothing. Vendored mode is manifest-free: its download phase is detached (download_patch_records_with writes nothing; the vendor ledger alone carries records), and a vendor step refused with vendor_lockfile_missing records nothing. So the assertion failed on the first CI run of both deno legs (1.46.3, 2.9.7) at deno.rs:454. Assert the real contract instead: the download is detached, no ledger entry names the package, and there is nothing to attest (no manifest, exit 2 manifest_not_found, zero patch-API requests). The "unapplied manifest patch attests nothing" check moves to step 4, where the test stages the manifest itself, before `apply --vex` runs. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * test(e2e_vex_build): compare hatch env paths canonically on macOS The macOS hatch 1.18.1 leg failed all four cases with `".../private/var/.../six.py" outside "/var/.../app"`. The hatch bootstrap venv is built on the runner's actions/setup-python CPython, a macOS framework build, and a framework interpreter realpaths `sys.prefix` — so `six.__file__` names `/private/var/folders/...` while `hatch env find` echoes the `/var/folders/...` spelling of the same temp dir. Linux legs (and a uv-managed standalone Python locally) keep one spelling, which is why only the macOS leg tripped. Canonicalize both sides for the containment check only; the env dir handed on as VIRTUAL_ENV is unchanged. Reproduced locally by bootstrapping hatch 1.18.1 on Homebrew's framework CPython (4/4 fail before, 4/4 pass after). Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * test(maven e2e): scrub Maven 4's CI markers so a runner logs like a laptop The ubuntu Maven 4.0.0-rc-6 leg of e2e_vendor_maven_build failed at the TAMPER probe: Maven rejected the tampered file:// jar and built Central's pristine one (the load-bearing assertion held), but the output carried no "checksum" line, so "the file:// copy was rejected on its checksum" tripped. The CI dump shows no transfer lines at all, not even the Central download that must have happened after the purge. Root cause: Maven 4's CIDetectors (generic CI, GITHUB_ACTIONS, CIRCLECI, Jenkins WORKSPACE, TEAMCITY_VERSION, TRAVIS) make MavenInvoker pick the QuietMavenTransferListener whenever a CI is detected and --force-interactive is absent, even under -B. That listener drops both "Downloading from ..." and the "Checksum validation failed" warning. Maven 3 has no such detection, so only the 4.x legs log differently on a GitHub runner (and only this probe greps a warning a successful build prints; the redirect suite's checksum greps are on failed builds, whose exception text survives the quiet listener). Scrub those markers (plus the Maven config vars run() already dropped) in one mvn_command() used by both detect() and run(). --force-interactive was rejected: it flips the run interactive (progress-bar listener) and Maven 3 refuses the flag. The checksum assertion is unchanged. Repro: CI=true GITHUB_ACTIONS=true on the unfixed tree reproduces the CI panic at e2e_vendor_maven_build.rs:353 locally; with the fix both e2e_vendor_maven_build and e2e_redirect_maven_build pass under the same env on Maven 4.0.0-rc-6 and 3.9.16. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * test(e2e_nuget_dotnet_build): serialize dotnet spawns around a .NET 9 PAL race The ubuntu SDK 9 leg failed nuget_vendored_dotnet_restore_then_manifestless_vex at its first fixture restore (the hosted test passed): System.IO.IOException: The system cannot open the device or file specified. : 'NuGet-Migrations'. One or more system calls failed: mkdir("/tmp/.dotnet/shm/session2027", AllUsers_ReadWriteExecute) == -1; errno == EEXIST; at System.Threading.Mutex..ctor ... at NuGet.Common.Migrations.MigrationRunner.Run ... at Microsoft.DotNet.Configurer.DotnetFirstTimeUseConfigurer.Configure() Both tests run in parallel, each with a fresh HOME, so each first `dotnet restore` runs the first-use NuGet migrations under the named mutex `NuGet-Migrations`. On a fresh runner `/tmp/.dotnet` does not exist yet, and the .NET 9 runtime's named-mutex setup races when two processes create the shared-memory tree at once: the loser's session directory mkdir fails with EEXIST. Environment/tool race (SDK 9 PAL), exposed by the harness running two SDK processes concurrently. Reproduced in mcr.microsoft.com/dotnet/sdk:9.0 (9.0.318, the leg's SDK) with two concurrent first-run CLI commands per round, fresh HOMEs, `/tmp/.dotnet` wiped before each of 60 rounds: 9, 0, 2 and 7 of 120 processes died with the exact CI message across four batches; 0/120 with the root pre-created and 0/40 rounds run one at a time. Hold one binary-wide lock around every `dotnet` spawn (the --version probe and every restore). Only the SDK phases serialize; the socket-patch runs between them stay parallel. No assertion changes. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * test(yarn-berry): pin hardened mode off so PR runs install from the lock yarn 4 enables hardened mode when it detects a GitHub Actions run for a public pull request and then re-resolves every lock entry against the registry. The fresh-checkout installs point the registry at an unreachable address on purpose, so the hosted berry suites failed with ECONNREFUSED 127.0.0.1:1 on PR runs only (seen in the coverage job once the fixture installs stopped failing). Reproduced locally with a simulated public-PR event: 3 failures without the pin, 11/11 with it. --immutable --check-cache still verifies every checksum. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * test(yarn-berry): pin hardened mode off only where yarn has the setting The previous commit pinned YARN_ENABLE_HARDENED_MODE=false for every berry suite, including e2e_yarn_legacy_cachekey_refusal_build, which drives yarn 2.4.3 and 3.8.7. Those releases predate hardened mode and refuse every command while the variable is set: Usage Error: Unrecognized or legacy configuration settings found: enableHardenedMode so all four refusal cells failed at their fixture install under SOCKET_PATCH_YARN_E2E_REQUIRED=1 — how the yarn-berry-e2e job runs the suite — and soft-skipped everywhere else. pin_berry_ci_defaults now takes the yarn spec and pins hardened mode off for yarn 4+ only, removing the variable for yarn 2/3. Reproduced locally: 4/4 cells fail with the Usage Error before, 4/4 pass after. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * fix(yarn-berry): redirect and vendor CRLF (Windows) berry files byte-exactly yarn berry writes a file it creates with the OS line ending and keeps an existing file's majority ending on every later write (normalizeLineEndings in yarnpkg-fslib FakeFS.ts, called by Project.persistLockfile and, through changeFilePromise's automaticNewlines, Workspace.persistManifest; the same rule from 2.4.3 through 4.18.0). On Windows a fresh yarn.lock is therefore CRLF, and so is the package.json yarn first pretty-prints; a core.autocrlf checkout produces the same on any OS. Once the Windows yarn-berry suites ran (corepack.cmd), both modes failed on those files: - hosted: rewrite_yarn_berry refused every CRLF lock (redirect_yarn_berry_crlf_unsupported, redirected 0); - vendored: package.json was re-serialized LF on both the wiring and the revert, so `vendor --revert` never restored the CRLF manifest byte-for-byte. Hosted: the rewriter works on the LF-normalized lock and re-expands its output (utils::line_endings), keeps a leading BOM, and records the lock's on-disk CRLF fragments in the ledger, so the per-purl takeover and the whole-ledger replay restore them byte-exactly. Both replays now also match yarn blocks respelled in the live lock's ending when a checkout flipped its uniform ending since the redirect (the committed ledger keeps its fragments verbatim). Vendored: package.json is re-serialized in its own layout (vendor::common::JsonLayout: BOM, indent, line ending, trailing-newline shape) and parsed past a BOM, and lock entries are spliced in the terminator of the block they replace (yarn_classic_lock::block_eol, also used by the shared revert, so a lock whose endings were mixed after vendoring keeps every other line as it was). A yarn.lock or package.json that mixes CRLF and LF, or holds a bare CR, has no single ending to keep and fails yarn's own `--immutable` check (YN0028): both modes refuse it before any write (redirect_yarn_berry_mixed_line_endings / vendor_yarn_berry_mixed_line_endings) with `yarn install` as the remedy. Reverts never refuse on line endings. Readers: is_berry_lock, the vendor flavor sniff, repair's sniff, scan_blocks and the .yarnrc.yml compressionLevel read skip a leading BOM (a header-less `__metadata:` lock is berry; a BOM'd yarnrc's first-line knob is no longer read as unset). The lock inventory and manifest-less VEX already split CRLF lines; unit tests pin both. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * test(yarn-berry): run every berry shape on CRLF, BOM and mixed files Hermetic CLI coverage of the Windows file shapes: `scan --mode hosted` over CRLF and BOM + CRLF locks (every line kept CRLF, the ledger's fragments the on-disk CRLF bytes, re-run a no-op, `rollback` restoring the pristine lock byte-for-byte) and its mixed-ending refusal; `vendor` + `vendor --revert` over CRLF (+ BOM) package.json / yarn.lock pairs (byte-exact round trip) and the mixed-ending failed event; both mode takeovers on CRLF files (hosted -> vendored -> revert, vendored -> hosted -> rollback, BOM kept); and a manifest-less VEX cell over a CRLF + BOM vendored lock and manifest. Real yarn: SOCKET_PATCH_YARN_BERRY_EOL=crlf respells the files each fixture's first `yarn install` wrote CRLF — what yarn itself writes on Windows (a new lockfile and a freshly pretty-printed manifest get os.EOL) — and yarn keeps them CRLF on every later write, so the hosted, vendored, pnpm-linker, workspaces, legacy-refusal and mode-migration suites run on CRLF files on macOS / Linux as they do on windows-latest. Every fixture prints `BERRY-EOL|<yarn>|<flow>|<file>|yarn=…|flow=…`, the ending yarn wrote and the one the flow ran on. Against the pre-fix code this mode reproduces both Windows CI failures (redirect_yarn_berry_crlf_unsupported on the hosted suites; "revert must restore package.json byte-identical" on the vendored ones); with the fix, yarn 4.12.0 passes all five suites in both modes (103 VEX-MATRIX cells each) and both mode takeovers. Also drops a doubled doc-comment line in yarn_berry_common. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * docs: yarn berry line endings in the contract, changelog and a compatibility page CLI_CONTRACT: the hosted redirect keeps a CRLF lock's own line ending and BOM and records on-disk fragments; a mixed-ending lock is refused with redirect_yarn_berry_mixed_line_endings, which replaces v4's redirect_yarn_berry_crlf_unsupported (no longer emitted); the vendored yarn berry row keeps both files' layout, with the new vendor_yarn_berry_mixed_line_endings refusal in the code table; the per-purl revert and whole-ledger replay respell yarn blocks across a uniform LF <-> CRLF checkout flip. CHANGELOG [Unreleased]: the fix (hosted + vendored CRLF support, BOM tolerance) under Fixed, the two refusal codes and the CRLF test mode under Added. docs/testing/yarn-berry-compatibility.md (new): supported releases, the CI matrix, how yarn berry chooses line endings — cited to FakeFS.ts, Project.ts, Workspace.ts, Manifest.ts and syml.ts at @yarnpkg/cli/4.12.0 — the git autocrlf paths to CRLF, socket-patch's contract per mode, and how to run the suites locally in CRLF mode. docs/ecosystems.md links it from the yarn berry notes. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * fix(setup): keep a CRLF / BOM package.json's layout through setup and --remove `setup` re-serialized package.json through `serialize_json`, which always emits bare LF and never writes a BOM back. On a Windows yarn berry project (persistManifest pretty-prints the manifest with os.EOL) a two-key script edit became a whole-file CRLF -> LF diff that yarn then keeps (it follows the majority ending), and `setup --remove` could never land byte-identical on the pre-setup file. Render through the vendored backends' `JsonLayout` instead (BOM, indent, line ending, trailing-newline shape). The two BOM tests now assert the BOM survives; a new round-trip test covers LF, CRLF, BOM+CRLF, BOM+LF, no final newline and two final newlines, asserting setup keeps each shape and setup --remove restores the original bytes. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * fix(yarn-berry): run the new mode's berry gates before a takeover reverts the old one The reverts keep line endings as they are (a vendored or hosted revert never refuses on them, so a lock mixed after wiring stays mixed), while the forward hosted rewriter and vendored backend refuse a mixed file. Neither takeover checked first: - `scan`/`get --mode hosted` over a vendored berry purl reverted its wiring, ledger entry and artifact (`redirect_takeover_reverted_vendored`: "now fully hosted"), then the rewriter refused the mixed lock - `redirected: 0`, and the next `yarn install` pulled the unpatched registry package. - `vendor` / `scan --mode vendored` over a hosted berry purl reverted the hosted edits and dropped the redirect-ledger record (`vendor_takeover_reverted_redirect`), then failed `vendor_yarn_berry_mixed_line_endings`. Extract the rewriter's project gates into `redirect::preflight_yarn_berry_hosted` (mixed endings, cacheKey, `.yarnrc.yml` compressionLevel) and the backend's into `vendor::yarn_berry_vendor_preflight` (both files' endings, cacheKey, compressionLevel; berry flavor only), and run each before the matching takeover revert, mirroring the bun preflights - wet and --dry-run alike. A refused purl keeps the old mode's wiring byte-identical and is skipped / failed with the new mode's code. Tests: a hermetic in_process_vendor test drives both directions (mixed lock, mixed package.json, compressionLevel 9; wet and dry-run) and asserts the wiring snapshot is unchanged and no takeover is announced (fails on the pre-fix code in both directions); core unit tests pin that each preflight matches its forward gate's code and detail. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * test(yarn-berry): serialize yarn spawns on Windows around a shared-cache rename race The berry suites' parallel tests share one yarn cache folder. Two yarn processes fetching the same package both rename a .tmp over the cache zip, and on Windows the loser fails with EPERM while the winner holds the file (windows-latest yarn-berry 4.12.0: e2e_yarn4_workspaces_build hosted test, EPERM rename left-pad-npm-1.3.0-....zip-....tmp). A static lock in yarn_berry_common serializes yarn processes on Windows only (Unix rename-over is atomic), mirroring the DOTNET_SPAWN fix. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> * test: strip in-process env toggles unconditionally before spawning the CLI Test binaries that mix in-process command runs with spawned CLI runs raced: the in-process runs call apply_env_toggles, which std::env::set_var's SOCKET_OFFLINE / SOCKET_DEBUG / SOCKET_API_URL / SOCKET_PROXY_URL on the shared test process, and the spawn helpers only removed SOCKET_* vars that existed when they scanned the environment. A toggle set by a parallel test between that scan and the spawn was inherited. On test-release this made in_process_vendor's berry takeover test run its hosted scan offline ("cannot run with --offline/SOCKET_OFFLINE"). The helpers in all eight such binaries now remove those keys unconditionally; Command applies the removals to the environment captured at spawn time. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
There was an error while loading. Please reload this page.
Successfully merging this pull request may close these issues.
There was an error while loading. Please reload this page.