Skip to content

Commit 686e5fb

Browse files
v5 WS1+WS2: ledger-free hosted mode, upstream-restore rollback, vendor ejects hosted projects (#280)
* Stop writing the hosted redirect ledger from scan Hosted scan keeps its edits and records in memory only; the lockfiles are the record of a redirect. Replays the parked WIP (which was snapshotted on an older tree) as just its hosted.rs delta. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Restore hosted pins to their upstream registry entries instead of replaying the ledger Imports the stopped local WS1 agent's work-in-progress (backup/local-v5- ledger-free-hosted): core patch::redirect::upstream re-resolves npm-family, cargo and golang registry entries for every hosted pin vex::discover finds in the lockfiles, and rollback/remove/vendor route their hosted legs through it instead of the redirect ledger. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Update rollback prompt unit test for the upstream-restore wording Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Derive list, scan updates and takeover state from lockfile hosted pins v5 keeps no hosted ledger, so every reader that consulted .socket/vendor/redirect-state.json now reads the hosted pins lockfile discovery finds: - list shows each hosted pin with the lockfiles wiring it (details.lockfiles); a pre-v5 ledger only supplies the details of pins it still describes. - scan's updates[] fold, redirectState block and the agent-flow hosted_wiring_retained probe read the pins. - The hosted-over-vendored takeover classifier reads the pins; the vendored-over-hosted ledger reconcile (vendor_supersedes_redirect) is gone: once the lock routes a package to .socket/vendor/ no hosted state is left to go stale. - vex treats a malformed pre-v5 redirect ledger as an advisory. scan/mod.rs unit tests still need rewriting (WIP). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Drop the hosted ledger from get, repair, the in-memory engine and the scan fold - get's vendored lock-text gates read the lockfiles' hosted pins instead of the redirect ledger (DownloadParams carries --patch-server-url for it). - repair's hosted-only no-op fires for hosted lockfile pins (or a pre-v5 ledger). - The in-memory hosted engine neither reads nor emits .socket/vendor/redirect-state.json. - Disk hosted scan no longer folds edits into a throwaway ledger; its records feed only the stale-install probes and in-run VEX. - The cargo vendor backend's hosted_redirect_live refusal names rollback / git checkout instead of a ledger. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * vendor ejects a hosted project; vendoring over any hosted pin restores upstream first WS2: standalone `vendor` with no manifest now takes its patch set from the lockfiles' hosted pins (purl + the uuid in each hosted URL), fetches each record from the API, vendors it into .socket/vendor/ through the same step `scan --mode vendored` and `get --mode vendored` use, and rewires the lock from hosted to vendored. Without hosted pins it keeps the no-manifest no-op. The vendor takeover now restores the upstream registry entry before vendoring for every ecosystem, not only cargo/npm/golang, so the vendor ledger always records the upstream entry as its original and `vendor --revert` returns to upstream rather than to hosted. A pin whose upstream entry cannot be restored is refused with the checkout remedy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Rewrite scan/mod.rs takeover unit tests for lockfile-derived hosted state v5 hosted mode keeps no ledger, so the scan/mod.rs unit tests now make a purl "hosted" by writing a lockfile that pins the hosted URL instead of planting .socket/vendor/redirect-state.json: - overlap / classify_overlap_takeover tests use hosted package-lock, yarn (classic + berry), bun and cargo sparse-index pins; the non-default-host test configures --patch-server-url and pins that an unconfigured host is no pin. - New behavior pinned: a pre-v5 ledger on disk is never hosted state; a lock routed to vendored (npm or cargo) yields no pin and no overlap; an edits-only state names no package; a half-migrated project whose locks name both sides stays silent; the redirectState block has no ledger/ledgerKey fields. - hosted_wiring_retained_purls / redirect_state_json tests read the lockfile-derived state, keeping the probe's own liveness gate covered. - Removed tests of retired behavior: note_vendor_supersedes_redirect reconcile (wet/npmrc/dry-run/no-op/persist-failure), the edits-only fallback (degraded ledger, vlt tilde keys) and following the vendored remediation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Restore hosted gem and composer pins to their upstream entries Add the RubyGems and Composer restorers to the v5 ledger-free hosted unwind (`redirect::upstream`). Gem (Gemfile.lock / gems.locked + Gemfile / gems.rb): a converged Socket GEM section is removed and its spec moved back, in name order, into the upstream section (the single remaining one, else the manifest's global source or rubygems.org, else refused as ambiguous); a bundler <= 2.1 merged section loses only the Socket remote; the DEPENDENCIES `!` pin is dropped; CHECKSUMS is re-pinned from the rubygems.org compact index. The Gemfile source block becomes `gem "n", "v"[, opts]` again (the original constraint is not derivable), or is removed with its DEPENDENCIES entry only when provably a transitive append. The pre-2.6 mixed state is undone when a pin is supplied, keeping the untouched lock's own constraint. Composer: dist {type,url,reference,shasum} and the dropped source block are rebuilt from packagist p2 metadata (composer/2.0 minified, expanded), cross-checked against the lock's dist.reference, in the lock's indent, slash style and line endings. Non-packagist entries are refused. UpstreamClient gains cached rubygems and packagist lookups (SOCKET_RUBYGEMS_URL, SOCKET_PACKAGIST_URL). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Fix clippy findings in the eject and takeover paths Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Restore hosted PyPI pins to their upstream registry entries Add the Python family to the v5 hosted -> upstream restore: Pipfile.lock, requirements.txt, Hatch direct references (pyproject.toml / hatch.toml), poetry.lock, pdm.lock, and uv.lock / PEP 723 script locks / PEP 751 pylock files with their paired pyproject / script metadata. Each restorer rewrites only entries whose reference is a hosted URL for an in-scope patch uuid and re-derives what the hosted rewrite overwrote from PyPI's JSON API (UpstreamClient::pypi_files, base overridable with SOCKET_PYPI_JSON_API, cached like the other lookups). Where a field is not derivable the pin is refused instead of guessed: requirements hash-checking mode that no other line settles, a Pipfile.lock index that is not PyPI, PDM locks without cross_platform (or uv locks) when the release ships platform- or interpreter-specific wheels, uv locks with no sibling registry package to show the artifact shape, several or non-PyPI registries, exclude-newer / no-binary / no-build filtering, multi-clause uv specifiers with no spelling evidence, uv 0.2 [[distribution]] locks, offline runs and registry failures. The golden harness round-trips the native Poetry (1.0-2.4), PDM (every supported lock_version) and Pipenv fixtures plus synthetic requirements/Hatch/uv/pylock projects through the real hosted rewriter; the shared requirements golden restores modulo the grant's name casing and the uv golden (no registry sibling) is refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Restore vlt, Maven and NuGet hosted pins to their upstream entries vlt-lock.json: slot [2] from npm dist.integrity, slot [3] per the lock's own convention (same-era default-registry siblings, else DepID era and options.registries), every hosted instance of name@version together. Maven (no network): base versions back, added dependencyManagement entries, socket-patch repositories and emptied wrappers removed, trusted-checksum lines dropped and .mvn files deleted only when nothing but hosted content is left; module poms and stray suffix uses refuse. NuGet: socket-patch source and mapping removed, a mapping that only fans * out to every source dropped; packages.lock.json contentHash re-derived from nuget.org's catalog packageHash (SOCKET_NUGET_URL), refusing when the restored config does not resolve the id from nuget.org alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Drop dead fixture fields clippy flags in covgap_commands_rollback Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Retire a pre-v5 hosted ledger when rollback finds nothing else; update the cargo guard test rollback in a project whose only state is a stale pre-v5 redirect-state.json (no manifest, no vendor ledger, no hosted pin in the lockfiles) removes that file and exits 0 instead of failing on the missing manifest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Feed the hosted scan's in-run VEX this run's records The in-run `scan --mode hosted --vex` attestation read its hosted records from the ledger the run had just written. With no ledger, the run's fetched records reach the VEX builder in memory (VexBuildParams hosted_records), merged over any pre-v5 ledger's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Refresh doc comments that still described the hosted ledger Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * vendor --revert restores hosted pins a pre-v5 vendor ledger re-creates A package vendored over hosted wiring before v5 recorded the hosted fragment as its pre-vendor original, so reverting it wired the lock back to the patch server. After a wet revert, any hosted pin on a reverted purl is restored to its upstream registry entry (warning vendor_revert_restored_upstream; a refused restore is a failed event, hosted_restore_failed, exit 1). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Refuse around contested hosted wiring; refuse offline eject before any request Review follow-ups: - core HostedInventory keeps raw hosted wiring apart from attributable pins: a hosted identity discovery recognizes but cannot attribute (locks that disagree, a malformed reference, a lockless registry pin) is contested wiring. rollback (unscoped), remove, list and vendor refuse around it with hosted_wiring_contested, naming the files and the git checkout remedy, instead of reporting a bare project. - vendor's eject refuses offline (flag or env, wet or dry) with offline_eject_unavailable before it builds any request. - Drop the stray upstream/client.rs.orig merge backup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Make eject one transaction: plan, restore upstream, vendor, or roll back Review follow-ups on the hosted -> vendored eject: - Every patch record is fetched first; one the API cannot serve refuses the whole eject (eject_refused) before anything is touched. - The upstream restore is planned first (a dry resolve of every pin); a pin that cannot be restored refuses the whole eject with its remedy. - A dry run stops at the verified plan (eject_planned events) and writes nothing, not even .socket/. - The wet run takes the apply lock once, snapshots every file the eject can touch (root files, pin and restore files, cargo/maven config, the vendor ledger, vendored uuid dirs), restores the pins upstream BEFORE the vendor engine inventories sources (so a fresh checkout with nothing installed resolves the pristine registry package), vendors, and on any failure puts the snapshot back (eject_rolled_back): a failed eject leaves the project hosted, byte for byte. Adds failure-injection and dry-run coverage. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Rewrite hosted rollback/remove coverage-gap tests to the v5 upstream restore Hosted state is the lockfile pin: rollback/remove restore the default upstream registry entry (mock npm registry via SOCKET_NPM_REGISTRY), a refused pin (offline, registry 404, missing integrity) fails closed with the git-checkout remedy, and a pre-v5 ledger is never replayed but retired once no pin remains. Ledger-persist failure tests become restored-lockfile write failure tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Rewrite hosted rollback/remove/vex-step tests to the ledger-free v5 contract - in_process_rollback_hosted (+ vlt): hosted pins come from the lockfiles and are restored from a mock npm registry; per-pin refusal, scoped restores, legacy-ledger retirement (never replayed), preserve-state, and the vlt heal following restored pins. A vlt project re-locked onto the registry has no hosted state left to roll back. - coverage_fix_rollback_ecosystem_scoped_replay -> ..._scoped_hosted: --ecosystems never restores another ecosystem's pin nor retires the pre-v5 ledger while a pin remains. - e2e_golang_hosted_state: rollback and vendor takeover restore go.sum from a mock checksum database; offline refuses. - redirect_npm_allow_remote: no ledger records the .npmrc edit; restore deletes only a pristine scaffold .npmrc and reports a kept allow-remote=all line (npm_allow_remote_left). - hosted_symlinked_files, repair_invariants: no ledger is written; a lockfile-pin-only project takes the redirect_only_project skip. - vex_pipenv_pip_steps: a reverted checkout with no ledger is the plain manifest_not_found error; apply --vex fetches the record online when no ledger supplies it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Rewrite hosted scan/get integration tests for the ledger-free v5 contract Hosted scan/get write no .socket/vendor/redirect-state.json: assertions on the ledger become "no ledger" plus lockfile facts; pre-v5 (incl. corrupt) ledgers are pinned as ignored and left byte-identical; ledger-write failure tests become "a read-only .socket/vendor or a squatting dir no longer blocks the run"; re-runs are pinned idempotent on the lock bytes. Rollback round trips now name the mock host with --patch-server-url and mock the upstream registry (SOCKET_NPM_REGISTRY / SOCKET_PYPI_JSON_API); berry CRLF/BOM, bun digestless, pnpm, poetry, pdm and pipenv restores are checked against the pristine locks. bun.lockb rollback pins the refusal. Manifest-less VEX legs attest from lock + API, and use a synthesized pre-v5 ledger as the extra local record source for the offline and redirect_unwired legs. scan redirectState/hosted_wiring_retained/updates are pinned to lockfile pins; vendor_supersedes_redirect is gone. vlt_hosted_common gains assert_no_ledger, legacy_record_from_view and write_legacy_ledger (additive). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Tidy hosted remove/rollback messages and legacy-ledger residue - remove's hosted_revert_failed message is the restore's own refusal (it already names the pin and the remedy) instead of wrapping it twice. - Retiring a pre-v5 ledger prunes the emptied .socket/vendor/. - The in-run hosted VEX summary says patches are attested from their patch records, not from a ledger. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Rewrite list and vex ledger tests for lockfile-derived hosted state list: hosted entries are lockfile pins (details.mode hosted, details.lockfiles, no details.ledger); a pre-v5 ledger only details a matching pin and never lists a record by itself. vex: a malformed pre-v5 redirect ledger is the redirect_ledger_corrupt warning, the run proceeds and the file is left byte-identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Rewrite manifest-less VEX suites for the ledger-free hosted mode v5 hosted mode writes no .socket/vendor/redirect-state.json, so the shared harnesses (vex_pdm_hatch_common, vex_pipenv_pip_common, vex_e2e_common/bun and vlt, npm_e2e_common/manifestless) now assert the hosted wiring run left NO ledger, while vendored keeps its .socket/vendor/state.json cells. Hosted cells that relied on the ledger now pin the new contract: offline with no local record is record_unavailable, online attests from the API, and a reverted hosted lock leaves nothing to discover. vendor over hosted pins is the eject flow, so the manifest-less embedded cells drive apply for hosted checkouts and a new cell pins the eject path. One focused cell shows a committed pre-v5 ledger still lets a hosted pin attest offline (new additive helpers: write_legacy_redirect_ledger, assert_no_hosted_ledger, LEGACY_REDIRECT_LEDGER). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Teach the real-uv VEX matrix the ledger-free hosted mode A hosted uv flow writes no ledger: assert that, run the embedded steps online (no local record), expect nothing discovered once the wiring is reverted, and take a hosted production leg's record from the public patch API instead of the removed ledger. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Run the real-uv hosted revert against the v5 rollback contract A hosted uv flow commits no .socket/, so copy_tree tolerates a missing source; rollback needs --patch-server-url for a pin on the mock origin, and restores each pin to its upstream registry entry or refuses it with the version-control hint (asserting nothing was written) instead of replaying ledger bytes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Delete the hosted ledger replay engine; keep a read-only legacy loader v5 derives hosted state from lockfile pins and restores upstream entries by re-resolving them from the registry, so nothing reverts recorded ledger fragments any more. Remove the replay machinery and the writers behind it: - patch/redirect/replay.rs and takeover.rs (fragment replay, per-purl npm/cargo/golang revert, redirect_revert_supported) - state.rs persist_redirect_state, drop_superseded_purl, quarantine and the unclassified-edit guards; load_redirect_state stays for migration reads and save_redirect_state stays doc(hidden) for laying down pre-v5 fixtures - npmrc unwind planners, bun.lockb snapshot restore, pipenv/vlt/bun text inverses and the EOL fragment respelling that only replay used - tests that only exercised replay; rewrite round-trips in the poetry/uv suites keep their forward and idempotency assertions (the upstream restore of those formats is covered by upstream_restore_golden) hosted_url_names/hosted_url_version move to a small hosted_url module shared by the bun rewriter and VEX discovery. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Document the ledger-free hosted contract and hosted eject Update CLI_CONTRACT.md, README.md, CHANGELOG.md [Unreleased] and the testing/ecosystem docs to v5's WS1/WS2 behaviour: hosted mode writes no redirect ledger; rollback/remove restore hosted pins to their upstream registry entries (per-format coverage, refusals incl. --offline and bun.lockb, the git checkout remedy); list/vex/scan/repair derive hosted state from the lockfiles (details.lockfiles, the new redirectState shape, redirect_ledger_corrupt as a warning); vendor ejects a hosted project and vendor --revert returns to upstream; vendor_supersedes_redirect and hosted_revert_unsupported are gone; the pre-v5 ledger is read for migration only and retired by rollback; new registry env overrides. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Rewrite vendor takeover covgap tests to lockfile hosted pins; add WS2 eject tests covgap_commands_vendor: a pre-v5 redirect ledger (even malformed) is now ignored; the takeover guard is driven by hosted pins in package-lock.json on a --patch-server-url origin (dry-run advisory against a mock registry, wet takeover + revert back to the upstream registry entry, offline refusal redirect_revert_failed, unconfigured origin is not hosted). The redirect-ledger write-failure test has no subject anymore and is removed. vendor_eject (new): standalone vendor in a hosted npm project ejects the pins into .socket/vendor/ (no ledger, no manifest), vendor --revert returns to upstream, a failed view fetch is patch_fetch_failed/exit 1, and no pins keeps the no-manifest no-op. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Rewrite in_process_vendor hosted takeovers to the v5 upstream restore The berry CRLF takeover and the pnpm/package-lock hosted->vendored conversions no longer see a redirect ledger: vendor runs online against a mock npm registry (SOCKET_NPM_REGISTRY) with the patch-server origin configured, restores the upstream entry, and vendor --revert / rollback land on the upstream registry entry. Adds an offline-refusal test (redirect_revert_failed with the checkout remedy). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * mode_migration_npm: hosted takeovers restore the upstream registry entry Hosted mode writes no ledger; vendor over a hosted yarn pin now runs online with --patch-server-url and restores the upstream entry first (classic legs read a registry document mirrored from the pristine lock via SOCKET_NPM_REGISTRY, so the unwind is hermetic). The reverse classic leg replaces the ledger-originals check with a rollback back to the pristine registry lock. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * mode_migration_cargo: hosted takeovers restore the crates.io entry Vendor over a hosted cargo pin runs online with --patch-server-url and a sparse-index mirror (SOCKET_CRATES_INDEX) of the pristine checksum; the ledger-deletion fail-closed test becomes an offline-refusal test (redirect_revert_failed + checkout remedy) keeping the half-reverted hosted_redirect_live backstop, which now names rollback / git checkout. The hosted leg of the manifest-less VEX matrix fetches its record from the API (no hosted ledger). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * mode_migration_bun: no hosted ledger; unwinds restore the upstream 4-tuple Every run carries SOCKET_PATCH_SERVER_URL (the mock patch origin) and SOCKET_NPM_REGISTRY (a mirror of the pristine lock's integrities), so vendor takeovers, rollback and remove restore the registry 4-tuple; the ledger record/edit assertions become no-ledger assertions. VEX over a stale manifest may name the superseded uuid only in vex_record_superseded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * in_process_vendor_bun_takeover: hosted pins without a ledger bun: every run names http://patch.test the patch server and points SOCKET_NPM_REGISTRY at one shared registry mirror of the pristine integrities; hand-written hosted fixtures are the lock's URL 3-tuples only, and the ledger record/edit assertions become no-ledger assertions (the unwinds restore the registry 4-tuple byte-exactly). vlt: scan/vendor/get/rollback over a hosted pin run online against the mock origin and a registry mirror; the fixture lock records options.registries so the upstream restore re-derives slot [3] exactly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * e2e_bun_lockb: binary hosted pins are refused by rollback and vendor takeover v5 restores a hosted pin's upstream entry instead of replaying a ledger, which a binary bun.lockb cannot get: vendor over the hosted pin (dry and wet) and rollback of it now refuse with the git-checkout remedy and write nothing; the tests apply that remedy and continue the round trip. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * e2e_redirect_bun_build: no hosted ledger; rollback restores the upstream 4-tuple The hosted run writes only bun.lock (asserted: no ledger). Rollback and its dry run carry the mock origin as the patch server and a registry mirror of the pristine integrity, so the upstream restore lands on the pre-redirect lock byte for byte; the repeat-run heal is checked in the lock alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * e2e_redirect_yarn_classic_build: hosted run writes only yarn.lock Assert no redirect ledger is written, and carry .socket/ into the fresh checkout only when it exists (v5 hosted mode writes nothing there). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * e2e_redirect_gem_stale_install: no ledger fallback in hosted mode The re-fire-from-ledger test becomes a re-scan with a failing record fetch: record_fetch_failed with the v5 VEX-omission detail, exit 0, wiring byte-identical, no ledger. Manifest-less VEX drops the ledger-kept cells: a stale unconverged pair and a reverted pair attest nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * e2e_redirect_pnpm_build: no hosted ledger; rollback restores the resolution The hosted run and its idempotent re-run write no ledger (the trust setting and lock splice are checked on disk). The v5/v6 synthetic legs replace the ledger-original checks with a rollback that re-resolves the upstream integrity from a registry mirror and lands on the pristine lock byte for byte; the pinned matrix rollback runs online the same way, and its vex checks fetch the record from the API (no ledger to read offline). .socket/ is copied into fresh checkouts only when present. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * e2e_redirect_cargo_shapes: remove restores crates.io entries without a ledger The post-install vex fetches records from the API (no hosted ledger), and remove runs with a sparse-index mirror of the pristine checksums and the mock origin named the patch server, restoring every shape byte for byte. One inherent v5 difference is pinned: a .cargo config that lacked a final newline gets it back, since without a ledger fragment the rewriter's separator is indistinguishable from a terminated file's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * e2e_redirect_cargo_build: the three-file rewrite is the whole hosted state Assert no ledger after scan/get --mode hosted; the post-install vex and the manifest-less matrix fetch the record from the API (the ledger-kept cells go), and reverted locks attest nothing. .socket/ travels into the fresh checkout only when present. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * golang hosted e2e: go.mod/go.sum is the whole hosted state get --mode hosted in a module and in a go.work root now asserts no redirect ledger is written; the manifest-less VEX tail already covers the ledger-less shape. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Format the rewritten vendor/hosted test files Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Pin that a transactional eject emits no per-purl takeover warning Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Document the transactional eject, offline refusal and contested wiring Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Drop a stale comment on the hosted unwind error Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Prove eject from a fresh hosted checkout; verify hash-pinned requirements The eject restores every hosted pin's upstream registry entry before the vendor engine takes its source inventory, so a fresh checkout (no node_modules, empty CARGO_HOME, no virtualenv) fetches the pristine source from the registry and verifies it against the restored checksum. New subprocess tests pin that for npm, cargo and pypi. The pypi case exposed that requirements.txt `==` pins never carried their `--hash=sha256:` digests into the lock inventory, so a hash-pinned pin with no installed copy was unverifiable. Carry them as the entry's integrity (any-of, like Pipfile.lock) while the file resolves from the public index; an index option keeps every pin unverifiable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Give the stale-Pipfile rollback test a derivable hash mode A requirements.txt whose every line is a hosted pin is refused by the upstream restore (hash-checking mode is not derivable). Add an unhashed, unpatched sibling so the test still pins a successful restore. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * node addon smoke: a wet hosted session writes no ledger The in-memory hosted engine emits only lockfile/config edits in v5, so assert no .socket/ output instead of requiring the redirect ledger. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * covgap scan_hosted: unreadable-workspace case asserts no ledger Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Rewrite the CI-only e2e suites and backtests to the ledger-free hosted contract v5 hosted mode writes no .socket/vendor/redirect-state.json: every #[ignore]d real-toolchain capstone and every compatibility backtest that read the redirect ledger now asserts it is absent and reads the facts from the lockfile (or the public API record) instead. - npm / yarn berry / composer / gem / maven / nuget / rush / poetry / pdm / hatch / pypi-real / yarn-classic matrices: no-ledger assertions; vex offline with no local record is record_unavailable; a reverted hosted lock names the patch nowhere (manifest_not_found, exit 2); a manifest-less `vendor --vex` is no longer run over hosted pins (it ejects them now). - Mock-origin hosted pins pass --patch-server-url to vex / rollback / remove / vendor / scan --mode vendored; the vlt harness gains rollback_upstream / Fixture::rollback with SOCKET_NPM_REGISTRY pointed at the harness registry. - Rollback legs expect the upstream registry entry back: npm (real registry, JSON-equal lock, .npmrc removed), composer 2 (byte-identical from packagist; composer 1's inline repository refuses with the git checkout remedy), maven (--offline, byte-identical pom), poetry (real PyPI), vlt (byte-exact per era); the production npm leg gains a real-registry rollback on a copy. - vlt: URL-less / update-dropped pins now find no state ("Manifest not found"); the TS-written lock restores to its input and the pre-v5 ledger is retired; mode migrations restore upstream before vendoring. - Backtests (bun, pdm, pipenv, poetry, vlt, uv docs): hosted records come from the public /patch/view/<uuid>; noLedger checks are unconditional for hosted; rollback checks expect the upstream entry (bun custom registry slot comes back as "", bun.lockb refuses with the remedy); the vlt downgrade leg asserts the pin with no ledger. Core fix: the vlt upstream restore added a slot [3] tarball URL to a single-node lock whose options record `registry` (vlt rc.33 .. 1.0.4 with config.registry), which vlt itself never writes (save.ts omits the resolved URL when it starts with the configured registry); rollback was not byte-exact on those eras. The no-siblings fallback now honours that rule (unit-tested). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Hold the vlt configured-registry slot [3] rule pending real-vlt evidence The rule in 44240a2 drops slot [3] when a lock records options.registry, matching vlt rc.33..1.0.4, but it breaks the vlt-lock-v1-both-registry-keys golden, and newer vlt releases have not been checked yet. Restore the previous behaviour until real captures decide it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Backtests: send a User-Agent on public patch-view fetches The hosted backtest legs now read the patch record from the public proxy's /patch/view/<uuid>, and CI got HTTP 403 for Python's default urllib User-Agent (the poetry 1.4-2.x native legs). Send an explicit agent, as the vlt backtest's api_get already does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Re-land the vlt configured-registry slot [3] rule on real-vlt evidence vlt's lockfile save (identical in 1.0.0-rc.33, 1.0.4, 1.0.10 and 1.2.0) writes a default-registry node's resolved URL (slot [3]) only when no `registry` is configured or the URL does not start with it, and records that `registry` in `options.registry`. Real installs of left-pad@1.3.0 with `config.registry` + `registries.npm` set to the default registry produce a byte-identical 3-tuple lock on all four releases. - upstream::vlt: the no-siblings fallback of `records_url` no longer adds slot [3] under a recorded `options.registry` the node resolves under (unit test covers both sides). - fixtures: `lock-v1-both-registry-keys` was hand-written with slot [3]; its input (and expected-edits original) now match the real lock byte for byte, and the real capture is added as `capture-1.2.0-config-registry` (vex-discover golden extended). - docs/testing/vlt-compatibility.md records the rule and the evidence. Without the rule the real-vlt legs that restore a single-node lock of a `config.registry` project (the harness configures it for rc.33 .. 1.0.4) are not byte-exact: hosted idempotence / crlf_lock and the migration scoped_unwind / rollback_from_mixed / agent_rollback_after_takeovers legs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * hosted-e2e uv leg: serve the record of the uuid the lock pins pypi_uv_lock_hosted_install_proof handed all three PYPI_UUIDS to uv_vex::production_manifestless. With no local record (v5 hosted keeps no ledger) production_record takes the FIRST of those the public proxy answers for, so the VEX stand-in served de58c8b8 while the resolver had granted (and uv.lock pinned) e828efa5: every manifest-less cell then asked the stand-in for e828efa5 and got `vex_record_not_found` / `record_unavailable`. Pass exactly the wired uuid instead; if production ever grants a uuid its public view will not serve, production_record now fails naming it rather than masking it behind another patch's record. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * PDM static_urls restore writes files in URL order; bun backtest User-Agent on artifact fetches PDM orders a static_urls entry's files by URL (the sdist under 0c/39 precedes the wheel under b0/53 for urllib3 1.26.18); the upstream restore wrote them in filename order, so hosted rollback wasn't byte-exact. The golden now uses real bucketed URLs so the order differs from filename order. The bun backtest's lockb digest and asset downloads now send the same User-Agent as the patch-view fetch (patch.socket.dev 403s urllib's default). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * covgap rollback: macOS-only blob-pin check derives hashes from the fixture bytes The fixture no longer carries before_hash/after_hash; the macos-gated manifest-write-failure test still read them and broke the macOS build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Round-trip hosted rollback of unpopulated Poetry 1.0/1.1 locks Poetry 1.0/1.1 record `[metadata.files] <name> = []` against today's PyPI JSON API, while locks written earlier list every release file. The hosted rewrite replaced either with the one-entry patched array, so rollback could not tell them apart and always re-derived the full release list: the backtest's `direct` and `crlf` shapes (literal `urllib3 = []`) failed rollbackRestoresLockBytes on Poetry 1.0.10 and 1.1.15. The rewriter now keeps that bit in the patched entry's layout: one file per line (Poetry's own rendering) when the original listed files, inline when it was `[]`; a re-run keeps the layout it finds. The restore reads it back and writes the full release list or `[]`. Adds a golden round-trip over every native fixture generation (1.0.10 to 2.4.3), LF and CRLF, alongside the existing populated-shape one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * yarn berry e2e: manifest-less VEX matrix expects no hosted ledger The berry matrix still asserted the pre-v5 .socket/vendor redirect ledger after a hosted flow. v5 hosted mode writes none: the offline cell now expects record_unavailable, a reverted hosted checkout discovers nothing (exit 2 manifest_not_found), and manifest-less apply --vex is a calm no-op. The yarn4 pnpm-linker and workspaces fresh checkouts copy .socket/ only when it exists, as the node-modules berry test already does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * e2e vex poetry: accept the one-file-per-line metadata.files pin A populated lock-1.0/1.1 [metadata.files] entry now keeps Poetry's multi-line layout after the hosted rewrite (so rollback can tell it from an originally empty one); the pin-spelling matrix strips that form too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Keep Pipenv's index on hosted entries so rollback restores it exactly The hosted Pipfile.lock rewrite dropped each entry's `index`, and the ledger-free upstream restore then guessed it from sibling registry entries (none -> the PyPI source name; all siblings index-less -> none). Pipenv's own choice cannot be re-derived from the lock or the Pipfile: for the same Pipfile it depends on the release and the locking environment (measured with real `pipenv lock`): shape 2018.11.26 2020-2022 2023.12.1-2026.8.0 direct pypi pypi pypi extras table pypi pypi (none) marker-excluded pypi (none) (none) transitive (none) (none) (none) So the backtest's rollbackRestoresLockBytes failed on 2022.12.19 extras (transitive pysocks sibling has no index -> index dropped) and on 2022.12.19 / 2026.8.0 marker-excluded (no siblings -> "pypi" added), and rollbackAfterRelockRetires failed on 2026.8.0 marker-excluded: the relock hybrid (our `file` kept, `version` and registry `hashes` restored, no `index`) was restored with an `index` Pipenv never wrote. The hosted rewrite now keeps `index` exactly as Pipenv wrote it (present or absent) and only drops `version`; the restore carries the entry's `index` back unchanged instead of choosing one, refusing when it (or the Pipfile's explicit index) does not name a PyPI source in `_meta.sources`. A `file` entry carrying `index` installs the referenced wheel itself (direct_url.json present) on Pipenv 2018.11.26, 2020.11.15, 2021.11.23, 2022.12.19, 2023.12.1, 2024.4.1, 2025.1.3 and 2026.8.0 (`install --deploy`, `sync`, `verify`), and a marker-excluded one stays uninstalled; Pipenv 7-11 ignore `index` on a `path` entry (convert_deps_to_pip skips it for file/path deps). Tests: real Pipenv 2018.11.26 / 2022.12.19 / 2026.8.0 locks for the extras and marker-excluded shapes (tests/fixtures/pipenv-shapes) round trip byte for byte in LF and CRLF, and the 2026.8.0 marker-excluded relock hybrid restores the pristine bytes. The backtest's allCategoriesRewritten now expects hosted entries to keep the pristine `index` (vendored still drops it). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Restore hosted bun.lockb pins natively for the vendor takeover With the hosted ledger gone, `vendor` over a live hosted pin in a binary bun.lockb (Bun 0.8.1-1.1.x's default lock, Bun 1.2's legacy lock) was refused `redirect_revert_failed`: format_of() mapped bun.lockb to Unsupported, so the backtest matrix's hosted-then-vendored cell exited partial_failure. The upstream restore gains a bun.lockb restorer (patch/redirect/upstream/bun_lockb.rs). It rebuilds each hosted remote-tarball record as Bun's npm registry record for name@version, from the registry's dist.tarball / dist.integrity (SOCKET_NPM_REGISTRY aware), via the new BunLockb::set_registry_package. That function re-interns the URL (re-using the original pool offset), drops the hosted URL string from the pool tail and re-derives the metadata hash. The staged restore view now carries binary files. To make the rebuild byte-exact, set_package keeps the registry record's inactive bytes (padding, semver) when it writes a remote tarball record. Early writers leave uninitialized padding there (Bun 0.8.1), so this matters. A re-pin to a later grant's URL now drops the superseded URL from the pool. A record without the retained bytes is rebuilt the way Bun writes one; prerelease versions are refused in that case. The rebuild is exact for every fixture writer except a format-1 lock (kept promoted) and workspace locks (behaviors kept normalized). So only the vendor takeover and eject opt in (RestoreOptions::bun_lockb): their vendor ledger records the rebuilt record, and `vendor --revert` returns the pre-hosted bytes. `rollback` / `remove` keep refusing a hosted bun.lockb pin with the `git checkout -- bun.lockb` remedy, as the harness's rollbackLockbRefused expects. An offline vendor still refuses. Tests: core restorer tests over every real fixture, codec tests for the rebuild (retained and zeroed records, re-pin), a hermetic CLI test (vendor_eject_bun_lockb.rs: takeover, eject, rollback and offline refusals on Bun 0.8.1 / 1.1.38 / 1.2.0 locks), and the real-Bun e2e_bun_lockb takeover now vendors online and reverts byte-exact. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * bun backtest: inject the custom-registry slot with byte I/O On Windows, Path.write_text turned the injected bun.lock's LF into CRLF, so the LF pre-injection bytes could never match the (EOL-preserving) upstream restore: custom-registry hosted rollbackOriginalFiles failed on Windows only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Demote hosted format-1 bun.lockb locks exactly on the vendor takeover The bun backtest's binary job failed only for the format-1 writers (Bun 0.1.1 / 0.1.6, readers 0.5.9 and 1.4.2) after 65aa074: the hosted rewrite promotes a format-1 lock to format 2 (format 1 has no URL column), and the new native upstream restorer rebuilt the registry record inside that promoted lock. The vendor ledger then recorded the promoted bytes as its pre-vendor original, so `vendor --revert` returned a format-2 lock and e2e_bun_lockb's "the revert restores the pre-hosted bytes exactly" assertion failed. A promoted lock is byte-for-byte indistinguishable from one Bun 0.1.7+ wrote (same pool order, same metadata hash), and hosted mode keeps no ledger, so the restorer could not tell. The codec now marks a lock whenever an edit had to normalize it: seven magic bytes and a flag byte in the last eight bytes of the root package's resolution (the root resolution's value union, which no Bun reader reads; early writers leave uninitialized bytes there, and 1.4.2 / 0.5.9 read such locks). For a promoted lock those bytes are new, so the mark overwrites nothing. promote_legacy_format sets NORMALIZED_FORMAT_1; normalize_workspace_behaviors sets NORMALIZED_WORKSPACE when it changes a dependency behavior or workspace literal. The vendor ledger's layout_original path normalizes the original the same way, so its exact revert is unchanged. The bun.lockb upstream restorer then: - demotes a NORMALIZED_FORMAT_1 lock back to format 1 once every hosted record is rebuilt (BunLockb::demote_legacy_format, which drops the URL column and the URLs the promotion appended to the pool, and succeeds only if promoting the result again reproduces the lock byte for byte). Otherwise the pins are refused with the `git checkout -- bun.lockb` remedy. - refuses a NORMALIZED_WORKSPACE lock outright with that remedy, since clearing the workspace behavior bit cannot be undone. It no longer takes the lock over non-exactly. Tests: the upstream restorer's byte-exact test now covers 0.1.1 / 0.1.6. Workspace-normalized extension locks refuse, and so does a lock whose mark carries an unknown flag. The codec rebuild test checks the exact demotion. vendor_eject_bun_lockb adds the 0.1.1 / 0.1.6 takeover with an exact revert and a workspace-lock refusal. Locally with real Bun, the 1.4.2 reader x 0.1.1 / 0.1.6 writer cells now pass the takeover and the exact revert. They then stop at the 0.5.9 legacy reader, which segfaults on any networked install in this sandbox. The 1.1.45 / 1.2.0 / 1.4.2 cells pass everything except the `extensions` shape, which needs api.github.com (403 here). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * ci: give the Windows test leg a 50-minute budget The Windows leg runs the same suite ~1.6x slower than macOS. On the base branch it already took 34m40s of the flat 35-minute budget, and with this PR's added tests it was cancelled at the limit mid-run (no failures). Linux and macOS keep 35 minutes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ * Check out the Poetry and Pipenv lock fixtures byte-exact on Windows The Windows test leg's CRLF checkout (core.autocrlf) turned the LF-committed tests/fixtures/poetry, pipenv and pipenv-shapes locks into CRLF, so the byte-exact upstream-restore round trips (and their derived CRLF variants, which became \r\r\n) and the Poetry VEX pin-spelling test failed on Windows only. Mark them -text like the other captured-lock fixtures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tncU7u7FK48tubFNA12UQ --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 8ae7dc3 commit 686e5fb

191 files changed

Lines changed: 23293 additions & 21273 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.gitattributes‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ crates/socket-patch-core/tests/fixtures/redirect/** -text
66

77
crates/socket-patch-core/tests/fixtures/pdm-native/*.lock -text
88

9+
# Poetry and Pipenv locks are real `poetry lock` / `pipenv lock` output: the
10+
# upstream restore and VEX tests round-trip them byte for byte and derive
11+
# their CRLF variants from the LF bytes themselves.
12+
crates/socket-patch-core/tests/fixtures/poetry/** -text
13+
crates/socket-patch-core/tests/fixtures/pipenv/** -text
14+
crates/socket-patch-core/tests/fixtures/pipenv-shapes/** -text
15+
916
# The captured pnpm 1-12 locks are byte-real: the hosted/vendored rewriters
1017
# refuse CRLF by design (vendor_lockfile_crlf_unsupported), and the tests
1118
# derive their CRLF variants from the LF bytes themselves.

‎.github/workflows/ci.yml‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,9 @@ jobs:
237237
matrix:
238238
os: [ubuntu-latest, macos-latest, windows-latest]
239239
runs-on: ${{ matrix.os }}
240-
timeout-minutes: 35
240+
# Windows runs the same suite ~1.6x slower than macOS: on the base
241+
# branch it already took 34m40s of a flat 35m budget.
242+
timeout-minutes: ${{ matrix.os == 'windows-latest' && 50 || 35 }}
241243
steps:
242244
- name: Checkout
243245
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

‎CHANGELOG.md‎

Lines changed: 209 additions & 37 deletions
Large diffs are not rendered by default.

‎README.md‎

Lines changed: 134 additions & 84 deletions
Large diffs are not rendered by default.

‎crates/socket-patch-cli/CLI_CONTRACT.md‎

Lines changed: 149 additions & 124 deletions
Large diffs are not rendered by default.

‎crates/socket-patch-cli/src/args.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,7 @@ impl GlobalArgs {
427427
}
428428

429429
/// The project root whose `.socket/` state stores — manifest, vendor
430-
/// ledger, redirect ledger — belong together: the RESOLVED manifest's
430+
/// ledger — belong together: the RESOLVED manifest's
431431
/// directory, stepping out of a standard `.socket/` layout when the
432432
/// manifest lives in one. For the default `<cwd>/.socket/manifest.json`
433433
/// this is exactly `cwd`; for a `--manifest-path` into another project

‎crates/socket-patch-cli/src/commands/get.rs‎

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -541,8 +541,8 @@ pub struct GetArgs {
541541
/// [default: hosted; agent with `--save-only` or `--global`]
542542
// agent = record in .socket/manifest.json + blobs and apply in place;
543543
// hosted = rewrite lockfiles so the patched deps resolve to Socket's
544-
// hosted patch server (no manifest, no blobs; state lives in the
545-
// redirect ledger); vendored = commit patched artifacts under
544+
// hosted patch server (no manifest, no blobs, no ledger: the lockfile
545+
// is the record); vendored = commit patched artifacts under
546546
// .socket/vendor/ and rewire the lockfile (no manifest, no blobs; the
547547
// vendor ledger carries the records). Hosted/vendored runs produce the
548548
// same on-disk result as `scan --mode hosted|vendored` selecting the
@@ -1222,6 +1222,9 @@ pub struct DownloadParams {
12221222
/// `false`: their patch content is staged in memory and the committed
12231223
/// artifact is the patch — nothing should land in `.socket/blobs`.
12241224
pub persist_blobs: bool,
1225+
/// `--patch-server-url`: the extra origin whose URLs count as hosted
1226+
/// when lockfile discovery reads the project's hosted pins.
1227+
pub patch_server_url: Option<String>,
12251228
}
12261229

12271230
impl DownloadParams {
@@ -1851,10 +1854,9 @@ type LockRefusals = HashMap<String, (&'static str, String)>;
18511854
/// classic / yarn berry gates and cargo's locked-version gate), over the
18521855
/// patches the phase would otherwise fetch a view for — past the Bun
18531856
/// refusal and the ledger's idempotency skip, which take precedence in the
1854-
/// fetch loop. A purl the hosted redirect ledger claims is left to the
1855-
/// vendor loop: its takeover reverts the hosted lock edits first, and the
1856-
/// revert rewrites the very text the gates read. A redirect ledger that
1857-
/// cannot be read leaves every purl to the loop.
1857+
/// fetch loop. A purl the lockfiles pin hosted is left to the vendor loop:
1858+
/// its takeover restores the upstream lock entry first, and the restore
1859+
/// rewrites the very text the gates read.
18581860
///
18591861
/// Only a package the vendor loop would hand to its backend is refused
18601862
/// here (see [`crate::commands::vendor::lock_refusals_reaching_backend`]):
@@ -1872,11 +1874,23 @@ async fn lock_text_refusals_for(
18721874
) -> LockRefusals {
18731875
let cwd = params.cwd.as_path();
18741876
let claimed: Vec<String> =
1875-
match socket_patch_core::patch::redirect::load_redirect_state(cwd).await {
1876-
Ok(Some(state)) => state.records.keys().map(|k| canonical_purl(k)).collect(),
1877-
Ok(None) => Vec::new(),
1878-
Err(_) => return HashMap::new(),
1879-
};
1877+
socket_patch_core::patch::redirect::upstream::HostedPin::all(
1878+
&socket_patch_core::vex::discover_patched_refs_with(
1879+
cwd,
1880+
&socket_patch_core::vex::DiscoverOptions {
1881+
patch_server_origins: params
1882+
.patch_server_url
1883+
.iter()
1884+
.filter(|url| !url.trim().is_empty())
1885+
.cloned()
1886+
.collect(),
1887+
},
1888+
)
1889+
.await,
1890+
)
1891+
.into_iter()
1892+
.map(|pin| canonical_purl(&pin.purl))
1893+
.collect();
18801894
let candidates: Vec<(&str, &str)> = selected
18811895
.iter()
18821896
.filter(|sr| bun_refusal.filter(|r| r.applies_to(&sr.purl)).is_none())
@@ -3663,12 +3677,13 @@ fn get_download_params(args: &GetArgs, save_only: bool, persist_blobs: bool) ->
36633677
strict: args.common.strict,
36643678
ecosystems: args.common.ecosystems.clone(),
36653679
persist_blobs,
3680+
patch_server_url: args.common.patch_server_url.clone(),
36663681
}
36673682
}
36683683

36693684
/// `get … --mode hosted`: hand the selected (purl, uuid) pairs to scan's
36703685
/// hosted engine ([`super::scan::boxed_run_redirect_selected`]) — lockfile
3671-
/// rewrite + redirect ledger, no manifest, no blobs — so the on-disk result
3686+
/// rewrite only, no manifest, no blobs, no ledger — so the on-disk result
36723687
/// matches `scan --mode hosted` selecting the same patches. The engine owns
36733688
/// all output (and honors `--dry-run` internally); in JSON mode it nests its
36743689
/// `redirect` block into the get base envelope passed as `scan_result`.
@@ -5223,6 +5238,7 @@ mod tests {
52235238
strict: false,
52245239
ecosystems: None,
52255240
persist_blobs: false,
5241+
patch_server_url: None,
52265242
}
52275243
}
52285244

@@ -5891,6 +5907,7 @@ mod tests {
58915907
ecosystems: None,
58925908
// The vendor-detached posture this fn exists for.
58935909
persist_blobs: false,
5910+
patch_server_url: None,
58945911
}
58955912
}
58965913

0 commit comments

Comments
 (0)