v5 prerelease: scan → vex → vendor workflow, hosted by default - #277
Mikola Lysenko (mikolalysenko) wants to merge 27 commits into
Conversation
List scan, vex, vendor and list first, then the agent-mode commands (get, apply, setup, rollback, remove, repair), and add a short "Typical workflow" footer to the root help. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A merged patch folds several advisories into one blob and is the package's cumulative fix, so the newest one the user can download now wins outright. Packages with no merged patch keep the old order: highest severity, then newest. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A bare scan now runs hosted mode: it rewrites lockfiles so only the patched dependencies resolve to Socket-hosted packages. A path-scoped, --prune or global scan with no mode only reports, since it has no lockfile to rewire. scan asks nothing any more: no confirm prompt in any mode and no patch menu. It takes the top-ranked patch per package. New --package filter (repeatable or comma-separated, env SOCKET_SCAN_PACKAGES) scopes a scan to packages by name or purl. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
scan's updates[] now also sees the hosted pins the lockfiles wire, not only the redirect ledger's records, so a hosted project that never committed its ledger still reports a superseding patch. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
GlobalArgs gains crawler_options(), is_global(), ecosystem_selected() and purl_ecosystem_selected(). They replace eight hand-copied CrawlerOptions literals, seven --ecosystems predicates and seven global checks across scan, vendor, vex, apply, setup, rollback, get and repair. vendor's predicate also matched case-insensitively and accepted for golang. clap validates the names first, so neither form ever reached it. It now uses the same exact match as everything else. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
In hosted and vendored mode, and so in a bare scan, positional PATHs now name project directories: each directory, or directory glob such as apps/*, is scanned on its own as if it were --cwd, under a "== <dir> ==" header. The exit code is the worst of the runs. A PATH that is not a directory is a usage error. --json takes a single directory so stdout stays one document. Agent-mode PATHs keep their installed-path glob meaning. The changelog's v5 section now leads with the scan → vex → vendor workflow and describes the new scan defaults. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
candidate_supersedes still put severity above merge state, so scan's updates[] and [UPDATE] marker could name a different patch from the one scan selects. It now calls a new ranking::batch_supersedes, which uses the same merged-first key as the selection and ignores the tier and uuid tiebreaks and missing dates. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
socket-patch get <id>, and the bare-UUID shortcut, now redirect the package to its Socket-hosted patched copy. Agent mode (manifest + in-place apply) is --mode agent. It stays the default with --save-only, which records a manifest entry, and with --global/--global-prefix, since those have no project lockfile to rewrite. The agent-mode test fixtures now pass --mode agent. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The README now leads with the scan → vex → vendor workflow: hosted mode first, vendored for offline installs, agent mode as the older install-hook flow, and the command reference in that order. The contract and README now match the code on these points: - a bare scan and get run hosted mode - scan never prompts - --package - PATH semantics in each mode - the merged-first patch ranking - the exit-2 cases - the env var table The Bundler plugin README now says setup writes a path: source and that failures warn by default. The docs/testing notes drop references to files and flags that no longer exist. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Comments and doc comments now describe the current code:
- references to renamed or deleted functions, files, tests and flags are
fixed;
- notes about pre-v5 scan behavior (prompts, report-only non-TTY scans,
severity-first ranking, PATH rejection) are gone;
- TODOs for finished work are removed;
- history narration ("used to", bug diaries, PR and audit tags, dated
verification notes) is rewritten as the invariant it protects;
- doc comments attached to the wrong item are moved.
Two CI path filters that named the deleted vendor/lock_inventory.rs now
match vendor/lock_inventory/**. No code behavior changes.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- An exported-but-empty SOCKET_SCAN_PACKAGES= filtered every package out of a scan. It is now scrubbed like the other local env vars, and blank --package specs are ignored. - Manifest-less VEX liveness for a rebuilt cargo vendor entry (no recorded wiring) now also probes the root Cargo.toml, where v5 writes the [patch.crates-io] table, not only the pre-v5 .cargo/config files. - setup names the dependency it writes, socket-patch[hook]. The repair_vendor hint and the hosted --prune warning name the current flags. - Drop scan's unreachable patch-menu spacing and the vendored arm's leftover blank line from the removed confirm prompt. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko)
left a comment
There was a problem hiding this comment.
Release review at 8ae7dc37: hold the release until the integrated hosted lifecycle is working and green. The direction is right, but the stack currently removes some visible CLI surface while adding new restoration/state machinery behind it.
I reviewed all seven open PRs and left specific feedback:
| PR | Assessment |
|---|---|
| #279 | Keep setup/hook removal. Finish consistent empty-list status, command grouping and one result schema. |
| #280 | Fix offline HTTP, fresh-checkout eject and dropped wiring diagnostics. Share restore grammar; delete the checked-in .orig. |
| #281 | Good registry/model direction; complete shared grammar instead of retaining parallel readers/writers. Reproduced inherited Gem checksum duplication. |
| #282 | Good core/Node boundary. Remove repeated ledger sorting and finish one project snapshot plus ledger-free disk/memory integration. |
| #283 | Good backend consolidation, but two reproduced repair regressions: lost offline source bytes and silently changed lock integrity. |
| #270 | Useful Composer correctness foundation; no new blocker confirmed. Reuse its identity/installer rules in the v5 models and restore path. |
The simplification I would ship:
- One normal workflow:
scanselects and applies hosted patches;scan --vex <file>optionally emits the report in the same run;vendorchanges the same selected patch set to committed artifacts. Keep standalonevex/listfor inspection. Make targetedgeta thin selector over that same execution path, and route undo/remove through the same transition planner. Preserve the planned agent-mode escape hatch without duplicating its orchestration. - One data path: project snapshot → parsed format models → patch selection → typed change plan → verified artifacts → commit → human/JSON rendering. Share inventory/wiring views, keep VEX eligibility separate, and reload/invalidate after writes. Hiding flags is not a substitute for deleting parallel execution branches.
- One acquisition path with explicit policy: reuse service artifacts where available; isolate the local-build fallback. Repair can reuse acquisition but must preserve the original artifact identity and lock wiring. Hosted→vendor should not pass through a committed unpatched state when conversion fails.
- Delete obsolete hosted write/replay machinery after checking consumers. Keep a small legacy reader. Prefer removing duplicate parsers and event-to-JSON-to-event conversions over cutting supported ecosystems or package-manager versions.
- Measure the intended savings: crawl/parse counts, API requests, no-op re-run work and end-to-end latency.
Ledgers::owned()currently adds a repeated full sort/scan;ProjectContextstill caches two separate parses. These are more useful optimization targets than moving functions between files.
Release gates:
- All ten pairs of the five sibling cleanup heads produced merge conflicts in
git merge-tree, including edits to files another PR deletes. Stack/rebase in a declared order and test the actual combined tree; per-branch results do not validate the release. - Run a lifecycle matrix: fresh lock-only checkout → hosted scan → native install → VEX/list → vendor → offline install → repair → rollback. Cover human/JSON/dry-run parity, zero HTTP offline, and failure injection that preserves existing protection.
- Migrate the remaining tests that assume agent defaults or a hosted ledger. I checked the base Cargo CI failures and a #280 Go job; those include obsolete ledger assumptions, not evidence that every red job is a new product defect. They still need to be green under the new contract before release.
- Finish the v5 version bump and docs against that integrated behavior. Workspace version is still
4.0.0in this draft.
The inline comments cover a reproduced multi-project VEX overwrite and a patch-selection invariant that deserves an explicit decision before unattended mutation becomes the default.
Validation: source/diff and CI-log review across all seven PRs; isolated CLI builds/reproductions; 36 upstream restoration goldens passed; repair behavior compared before/after. No full cross-platform matrix or conflict-resolved integration build was run. Cloud delegation was attempted but rejected with 403: GitHub connector is disabled or unavailable, so this review ran locally in isolated worktrees.
| } | ||
| let mut child = args.clone(); | ||
| child.paths.clear(); | ||
| child.common.cwd = dir.clone(); |
There was a problem hiding this comment.
[P2] Give each project's VEX output a distinct destination
Each child keeps the same vex.vex path, and generate_vex writes that path relative to the process working directory. scan apps/* --vex out.vex.json therefore overwrites the first project's document with the second while reporting both writes as successful. A later project's failed generation can also remove the earlier project's output through stale-document cleanup.
Reproduced with two projects carrying distinct root product names: exit 0, two successful-write messages, final document contains only product b. Resolve per-project output explicitly, aggregate into one document, or reject a single output path for multiple projects before making changes. Add a two-project regression including a later failure.
| not_paid: p.tier != "paid", | ||
| uuid: &p.uuid, | ||
| not_merged: !merged, | ||
| severity: if merged { 0 } else { severity }, |
There was a problem hiding this comment.
Selection-policy concern: advisory count does not prove cumulative coverage
This promotes every multi-advisory patch above every single-advisory patch and erases its severity. The new test a_merged_patch_beats_a_higher_severity_single_one deliberately selects a 2020 high+high bundle over a 2026 critical fix; the next test selects a low+low bundle over a critical+high one with different advisory IDs.
Because scan automatically replaces one patch per PURL, the “merged means cumulative” assumption needs an authoritative API contract/selection field, or a coverage check that prevents dropping already-fixed advisories and silently ignoring a newer critical fix. Prefer a server-recommended cumulative patch with explicit supersession/coverage. If no candidate safely supersedes the current patch, report the uncovered conflict instead of inferring it from len() >= 2. This is a critique of the intended policy, not a claim that a production incident has been observed.
|
[agent] Detailed v5 implementation handoff, incorporating Mikola's latest direction from the review discussion. Target: make depscan the owner of package construction and remove CLI repacking where the hosted artifact contract supports it. Lockfile changes are part of the intended workflow and should remain supported. Mikola is comfortable keeping the entire repacking engine in depscan and prefers deleting the CLI copy if feasible. Avoid breaking or rewriting depscan's working builder merely to achieve code sharing. This supersedes my earlier suggestion to expose the Rust repacker to both callers. Prefer the smaller migration: preserve depscan's existing implementation, turn the CLI into an artifact consumer, and delete redundant client construction paths. What is actually shared today In the inspected depscan checkout ( There is real duplication. Depscan's Berry writer explicitly identifies itself as a port of the CLI implementation. The npm paths also differ: depscan preserves upstream tar entry order and uses epoch timestamps; the CLI sorts paths and uses npm's fixed 1985 timestamp. Equal patched members therefore do not imply equal archive digests. Preserve already-published bytes and identities during this migration. Suggested implementation sequence
Other deletions that complement this boundary
Coordination and validation The current sibling PRs are active and overlap heavily. Inspect their latest heads and existing reviews first. In particular, #283 advanced to Use the existing release review and linked per-PR reviews as additional context. Revalidate findings against the integrated tree. Preserve all currently supported package-manager versions and the planned agent-mode escape hatch; update Acceptance checks should cover:
Please push a focused draft PR targeting Cloud delegation from this session is unavailable: task creation returned |
…r 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>
…'s ledger rebuild (#283) * Consolidate vendored apply/revert/repair into one VendoredBackend vendor, scan/get --mode vendored, vendor --revert, rollback's vendored leg, remove and repair now go through one VendoredBackend { apply, revert, repair } over the shared engine (vendor_records_reusing, dispatch_revert_one_opts). The boxed_* scan shims collapse into one boxed_vendor_step; the engine future stays boxed inside apply for the Windows 1 MiB main-thread stack. repair no longer re-synthesizes vendor ledger entries from lockfiles. A lockfile reference with no ledger entry fails with vendor_ledger_missing (artifact-level event: uuid + details.{ecosystem,path}); the remedy is restoring state.json from version control. Missing or corrupt artifacts are re-vendored through the same engine as vendor, so the patch service's prebuilt artifact is downloaded first under --vendor-source auto, with the local build as the fallback. The fingerprint post-verify, set-aside of corrupt bytes and carried-inventory refresh are kept. Removed with the rebuild: repair_vendor.rs, gem Gemfile wiring reconstruction, and registry_fetch::fetch_npm_unverified. The packing code (npm_pack, pypi_wheel, berry_zip, registry_fetch, prestage) stays: depscan does not call it (verified against depscan master 784013d6), but it is the CLI's own --vendor-source build/auto fallback. Tests for the reconstruction path are replaced by vendor_ledger_missing pins per flavor; CLI_CONTRACT, README, CHANGELOG and the v5 plan are updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DMhWChtaNX5FJYNDq3NBJa * Point the vlt coverage map at the renamed repair test vlt-coverage.json still named vlt_repair_reconstructs_the_ledger_from_the_lock, and vendor_vlt_lock_out_of_sync lost the only assertion the coverage check could see when the lock-only reference test switched to vendor_ledger_missing. vendor_vlt_out_of_sync now asserts the refusal detail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DMhWChtaNX5FJYNDq3NBJa * Drop the ledger-less mirror leg from the bun binary workspace test native_binary_alias_and_transitive still expected repair to rebuild a workspace mirror after deleting state.json. Repair now reports that as vendor_ledger_missing (pinned in native_binary_hosted_vendored_takeover_roundtrip), so the leg is gone; the missing/corrupt mirror legs keep running and assert the ledger stays byte-identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DMhWChtaNX5FJYNDq3NBJa * Pass --mode agent to the gated agent-mode get fixtures get defaults to hosted since 5e5f5ed, which moved the agent-mode fixtures to --mode agent but missed the #[ignore]d real-toolchain suites (e2e_vlt, e2e_npm, e2e_pypi, e2e_gem, e2e_safety_pnpm). Their plain `get <uuid>` now redirects instead of applying in place, so e.g. vlt_pinned_matrix_agent_get_and_remove saw the copy Absent. This PR touches the vlt-compatibility path filter, which surfaced it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DMhWChtaNX5FJYNDq3NBJa * Keep an unpersistable inventory refresh loud, drop reconstruction persist tests A carried-inventory refresh whose ledger write fails now reports vendor_inventory_refreshed next to vendor_state_write_failed and keeps the member-verified rebuild on disk, instead of falling through to vendor_artifact_rebuild_failed and removing it. The persist-failure tests for the removed backfill / anchored / soft reconstruction paths go with them; they only run as non-root, which is why the root sandbox skipped them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DMhWChtaNX5FJYNDq3NBJa * Restore the macOS-only PatchedFixture hash fields The dead-field clippy fix removed before_hash/after_hash, but the macOS immutable-flag rollback tests read them, so test (macos-latest) no longer compiled. Keep the fields and allow dead_code off macOS only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DMhWChtaNX5FJYNDq3NBJa * Keep repair on the recorded artifact identity Review of #283 found two regressions in the shared-engine repair: - A corrupt artifact was moved aside before staging, so its afterHash-verified members were no longer harvested: an offline repair that the previous implementation completed failed with "no local source". The members are now harvested first and passed as the seed. - The post-verify reloaded the ledger the re-vendor had just written, so a service archive with different bytes (same members, new gzip mtime) was committed as `rebuilt` with a rewired lock and new fingerprint. Repair now verifies against the original entry; when the result is not the recorded artifact, that candidate's wiring files and ledger entry are put back from a pre-run snapshot, and a service copy falls back to a build-only rebuild. Legitimate backend migrations of a verified rebuild (the cargo version retag) are kept. Both reproductions are pinned in repair_vendor_e2e. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DMhWChtaNX5FJYNDq3NBJa * Make repair's wiring undo FIFO-safe, atomic and symlink-preserving Bugbot on #283: the identity-undo snapshot read lockfiles with bare tokio::fs::read (a FIFO at a wiring path blocks open(2)) and skipped symlinked lockfiles, and the put-back wrote them in place (no stage+fsync+rename, mode bits dropped). The snapshot now reads through read_regular_to_bytes and records a symlinked lockfile's link text; the undo re-links a link that the engine's rename replaced, then writes the target through atomic_write_bytes_preserving_mode. Pinned by repair_identity_undo_follows_a_symlinked_lockfile. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DMhWChtaNX5FJYNDq3NBJa --------- Co-authored-by: Claude <noreply@anthropic.com>
|
Landing: #283 (WS5 VendoredBackend) landed on release/v5-prerelease as 06437d2; next up #281 (needs re-merge of the base). #288 fast lane still draft. Generated by Claude Code |
| /// prints even under `--silent` (`json` mutes it: the envelope carries it). | ||
| fn fail(env: &mut Envelope, json: bool, purl: &str, code: &str, detail: String) { | ||
| if !json { | ||
| eprintln!("{}", format_repair_failure(purl, &detail)); |
| // uuid and the referenced path instead. | ||
| let detail = format_ledger_missing(eco, uuid); | ||
| if !common.json { | ||
| eprintln!("Error: Cannot repair vendored artifact {path}: {detail}"); |
* Cancel only superseded PR runs in CI A CI or compatibility run is now cancelled only when a newer push to the same pull request replaces it. Push, dispatch and scheduled runs always finish, so a manually dispatched run on the v5 base branch (its only CI verdict, since push CI runs on main alone) is no longer killed by a later dispatch or by the non-main cancel rule, and main keeps finishing its rust-cache saves. CI now groups PR runs by PR number, like the compatibility workflows already do. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AfGnDQMy2FvpExneXmR1qp * Expect native path separators in scan headers scan_hosted_paths_run_once_per_project_directory compared the per-directory `== apps/a ==` header against a literal forward-slash path, but scan prints the directory glob matched, which Windows spells `apps\a`. The Windows test leg failed on this since 62f07c7, and because every e2e job waits on `test`, the whole e2e tier was skipped on v5 PRs. Build the expected header from path components. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AfGnDQMy2FvpExneXmR1qp * Run the vlt agent get test in agent mode get defaults to hosted mode since 5e5f5ed, so the real-vlt get_and_remove leg ran a hosted get and found the installed copy unpatched (Absent, expected Patched). Pass --mode agent, as the other agent-mode fixtures already do. Same change as cc5f1b6 on #285; it blocked the e2e tier's e2e_vlt jobs now that they run. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AfGnDQMy2FvpExneXmR1qp * Run the pnpm safety e2e gets in agent mode get defaults to hosted mode since 5e5f5ed, so the three pnpm safety tests ran a hosted redirect and found proj_a's installed copy unpatched and no pnpm-layout note. They test the in-place apply path, so pass --mode agent. These e2e-tier tests had not run since that change because a red base test skipped the tier. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AfGnDQMy2FvpExneXmR1qp * Let the NuGet hosted e2e run without .socket/ v5 hosted mode writes no `.socket/` directory (the lock pins are the whole hosted state), so the hosted leg's fresh_checkout panicked with NotFound copying a tree that no longer exists. Copy it only when the run left one; the vendored leg still carries its ledger through. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AfGnDQMy2FvpExneXmR1qp --------- Co-authored-by: Claude <noreply@anthropic.com>
* Drop unused fields from the rollback covgap fixture `cargo clippy --all-targets -- -D warnings` failed on the dead before_hash/after_hash fields of PatchedFixture. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R11RZRvYFL3fzmEnkFAU4A * Add an opt-in NuGet fallback vendoring layout Setting SOCKET_PATCH_NUGET_LAYOUT=fallback vendors a patched NuGet package under a Socket-only version V' (the upstream version plus a 4th part derived from the patch uuid). The package is committed already extracted, as a NuGet fallback package folder under .socket/vendor/nuget/<uuid>/. It is wired through a generated socket-patch.targets file (imported via CustomAfterDirectoryBuildTargets from a Directory.Build.props block) and byte-exact packages.lock.json splices. Because nothing else can produce V', the patched bytes never enter or collide with the shared global packages folder, and nuget.config and source mapping are left alone. Restore-time and build-time guards fail closed on a tampered, incomplete or missing seed, or an unpatched resolution (SOCKETPATCH001/002/005/007). Supported shapes: SDK-style solutions with per-project lock files under --locked-mode, and Central Package Management with or without transitive pinning. Other shapes are refused with explicit codes. The default layout is unchanged. A repo is opted in only by the env var or an existing nuget-fallback ledger entry. Tests: - unit tests; - lock goldens captured from real dotnet; - an ignored real-SDK e2e (sln_locked, cpm_locked, cpm_pinning, sln_patch_update). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R11RZRvYFL3fzmEnkFAU4A * Add the NuGet vendoring design docs/design/nuget-vendoring.md covers: - the verified NuGet facts behind the design; - the four candidate designs and the judge scores; - the decision (unique-version fallback seed); - the mechanism, supported shapes, failure modes, signing policy and migration; - the server/CLI split, the test plan and the adversarial review dispositions; - where the prototype overrides the design. The raw research and review notes are committed alongside in nuget-vendoring-research/. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R11RZRvYFL3fzmEnkFAU4A * Run the vlt agent get test in agent mode get now defaults to hosted mode (5e5f5ed), so the real-vlt get_and_remove leg ran a hosted get and found the installed copy unpatched. It now passes --mode agent, like the other agent-mode fixtures that commit updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R11RZRvYFL3fzmEnkFAU4A * Gate the rollback fixture's blob hashes to macOS d9a3f53 removed PatchedFixture's before_hash/after_hash as dead code, but a macOS-only test reads them, which broke the macOS build. Restore the fields under #[cfg(target_os = "macos")], so they exist where they are read and clippy stays clean elsewhere. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R11RZRvYFL3fzmEnkFAU4A * Move the NuGet fallback prototype to a follow-up branch Vendored NuGet is future work for v5, so the opt-in fallback layout prototype (9 new modules, routing hooks and a real-dotnet e2e) moves to v5/nuget-vendoring-prototype, unmerged. This branch keeps the design doc, its research notes and the two test fixes. The doc now points at the prototype branch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R11RZRvYFL3fzmEnkFAU4A --------- Co-authored-by: Claude <noreply@anthropic.com>
Keep the v5 waste review and the repacking-to-depscan design from PR #286 as reference docs. #286 is being closed without merging, and its findings now belong to follow-up PRs. Each doc starts with a line that links the triage map on #286, which gives every finding's owner. Co-authored-by: Claude <noreply@anthropic.com>
* Plan staged patch rollout for v5 Adds the design for rolling Socket patches out gradually: a `patches:` block in socket.yml that narrows what scan may patch (paths, ecosystems, packages, severity floor, on/off), and a severity-ordered per-run cap on new patches so each scan lands the next few most critical fixes. The plan splits the work into two parallel items with a frozen interface, lists every hard-coded filter and where it belongs, and covers the depscan autopatch follow-up. configuration.md now records that socket-patch reads socket.yml for selection policy only, with the trust boundary unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Revise rollout plan after adversarial review Three independent reviews (ambiguity, churn, trust boundary) found gaps that would have let the two implementations disagree or let a repo file widen or stall the rollout. The plan now: - matches paths against marker files with the backend's top-down gitignore rules, so projectIgnorePaths means the same everywhere - uses one data source for severity, supersession and ordering - spends the budget only on patches the planning pass proves can land, and admits nothing new when a lookup failed - uses the merged recorded view in every mode and engine - has depscan read the policy from the base commit for PR jobs - hardens file handling (regular files, aliases, size, encoding, trusted repo root) and reports what a policy hides Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Close interface gaps in the rollout plan A final consistency pass found places where the two work items would have produced incompatible code: base purls admitted in one directory being charged again in the next, no defined hand-off of the unfiltered offers from the severity filter to classification, no shared repo-relative path helper, and override sources the JSON must report but the interface could not carry. The shared contract now defines each of these, and the parity tests match each engine's budget scope. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Clarify who shapes scan's selection output The plan said both that the selector returns the shared offers struct (work item A) and that it returns admitted/deferred rows (work item B). The selector now returns the offers, and B adds the rollout stage after it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Apply socket.yml policy in memory path selection Bugbot on #290: the plan had the in-memory engine's path selector apply only the built-in test/fixture ignores, because it runs before socket.yml is read. A negation such as `!/e2e/tests/` could then never bring those trees back in depscan, while it works on disk. Selection is now two-phase: the caller fetches the root policy file(s) first and passes their text to selectHostedScanPaths, which applies the full path policy. A listed policy file that is not passed fails closed. A new memory test covers the negation case. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
Discovery's sweep recognizes every uuid-shaped segment of a hosted URL, so it also records the pin's grant token. HostedInventory counted that token as contested wiring in two ledger-free shapes: - uv: the paired pyproject.toml repeats the uv.lock pin's URL in [tool.uv.sources] (the transitive override case), but pyproject.toml is not a pin file. - vlt: a vlt-lock.json whose pins are refs but withheld from the lock basis (a lock some vlt release discards) is a flagged pin file. Unscoped rollback then failed with hosted_wiring_contested after it had already restored the pin. The token of an attributed pin's own URL is now excused in a file that also names that pin's patch uuid. Any other unpinned uuid, a half-reverted pair and a contradicted lock are still contested. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Drop never-read fields from the rollback covgap fixture clippy -D warnings rejects the dead before_hash/after_hash fields. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018BYsX2VfVNKHvfFAJnFryc * Read pnpm-lock.yaml through one format model formats::pnpm owns the pnpm lock grammar in every generation (shrinkwrap, 5.x block, 5.4/6.0/9.0 flow, Rush nested locks): PnpmLock::parse once, then entries(), resolves(), wired_refs(), wired_integrity(), vendored_in_use(), plan_hosted() and the restore_upstream() hook. The redirect rewriter's pnpm leg, the lock inventory, lockfile discovery, repair's integrity anchor and flavor sniff, the vendored v9/legacy planners and get's pnpm-PnP probe all read through it instead of their own walkers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018BYsX2VfVNKHvfFAJnFryc * Derive every wiring-file list from formats::registry() The hosted candidate list, repair's vendored-reference search space, lockfile discovery's vendored-liveness probe, the in-memory engine's root markers and file ecosystems, the npm flavor probe guard and pnpm detection now filter one table instead of keeping five parallel lists. The hosted candidate order is pinned by value. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018BYsX2VfVNKHvfFAJnFryc * Read Cargo.lock through one format model formats::cargo owns the lock read model (LockedPackage, v1 [metadata] checksums, [[patch.unused]], dependency references) behind CargoLock: entries() for the inventory, packages() for lockfile discovery and the vendor probes, dependents() for the hosted planner's unpinnable-dependents refusal (which re-parsed the lock with its own walker), vendored_in_use() for the vendored-copy claim, and the restore_upstream() hook. The hosted planner's Cargo.lock splice moves to formats::cargo::hosted with the line-grammar probes the rewriter reads with (is_locked, locked_versions), replacing three copies of the header probe. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018BYsX2VfVNKHvfFAJnFryc * Read composer.lock through one format model formats::composer owns the entry walk (ComposerLockPackage, now with its ownership gate wired_to) behind ComposerLock: entries() for the inventory and packages() for lockfile discovery and the vendored backend. The vendored backend's entry_is_wired and repair's recorded-fragment reader, which read dist fields straight off the JSON, go through the entry model. The hosted planner's byte scanner moves verbatim to formats::composer::hosted; its equivalence oracle stays green. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018BYsX2VfVNKHvfFAJnFryc * Read Bundler locks through one format model vendor::gemfile_lock becomes formats::gem: GemfileLock::parse plus entries() for the inventory (moved out of lock_inventory::gem, which keeps only its view I/O and ledger recovery's remote set), the restore_upstream() hook, and gem_download_url. The hosted planner's lock-source convergence moves verbatim to formats::gem::hosted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018BYsX2VfVNKHvfFAJnFryc * Keep the rollback fixture's hashes for the macOS-only test The blob-retention test that reads before_hash/after_hash is #[cfg(target_os = "macos")]; allow the fields as dead elsewhere instead of dropping them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018BYsX2VfVNKHvfFAJnFryc * Route the yarn grammar split and bun.lock prelude through formats formats::yarn owns the one yarn.lock grammar decision: sniff_grammar (the head sniff the vendor flavor probe, the lock-inventory view and repair's reference flavor each re-derived) and is_berry_lock (the whole-file check the hosted rewriters and discovery share; the classic vendored backend's refusal gate now uses it too, so a BOM'd berry lock no longer slips past it). formats::bun::BunTextLock is the gate + split + packages parse five bun.lock readers copied; each keeps its own refusal wording. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018BYsX2VfVNKHvfFAJnFryc * Read repair's yarn/bun trust anchor through the entry models wired_vendor_integrity scanned yarn.lock and bun.lock with a six-line forward window from any line naming the artifact. It now reads the entry models lockfile discovery uses: live classic blocks' integrity, berry checksum (yarn 4.0.x bare hex promoted under cacheKey 10c0), and bun's tarball tuple. That fixes a berry block whose carried dependencies pushed checksum out of the window (no anchor), a bare-hex checksum (no anchor), a shadowed classic block being read, and bun's digest-less re-save borrowing the next package's sha512 (a wrong anchor). Entries that disagree yield no anchor. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018BYsX2VfVNKHvfFAJnFryc * Pin gem CHECKSUMS through the shared entry grammar The hosted writer found the dep's CHECKSUMS row with a regex that only matched a lowercase terminal sha256, while the discovery reader accepts uppercase digests, extra digest tokens and bare entries. Any of those fell through to the insert branch and added a second, conflicting row (which the reader then treats as no pin). The writer now locates the entry with formats::gem's split_checksum_entry inside the CHECKSUMS section and replaces that row in place, keeping its line ending and recording the old row verbatim for revert. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018BYsX2VfVNKHvfFAJnFryc * Answer pnpm in-use from the model's parse; drop the placeholder hook PnpmLock computes its vendored-uuid set at parse time with one walk over the packages/snapshots block keys (the vendored planners' key grammar, each line's \r dropped). pnpm_entry_in_use memoizes the same set per lock bytes instead of keeping LockIndex's copy and the LF-only vendored_in_use_lines scan. A CRLF lock now answers like its LF twin (in use) instead of undeterminable; the unwired-revert guard still refuses. formats::LockModel with its default-unsupported restore_upstream() is removed: hosted upstream restoration belongs to the ledger-free hosted workstream and should land on these models, not beside them as a placeholder. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018BYsX2VfVNKHvfFAJnFryc * Move the maven pom and nuget config readers into formats vendor::maven_pom becomes formats::maven and nuget_config's routing reader becomes formats::nuget; the NuGet config file names and the stat-only same-file check stay in vendor::nuget_config with the callers' I/O. Lockfile discovery, their only reader, imports the models directly; the purity guards follow the files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018BYsX2VfVNKHvfFAJnFryc * Splice Cargo.lock at the spans of its one parse CargoLock::parse now reads the lock with toml_edit's spanned Document and records each [[package]]'s header, version/source/checksum value spans and string values, every table header, the [root] strings and the [metadata] lines. The hosted planner (CargoLock::plan_hosted) splices at those spans, and the rewriter's is_locked / locked_versions probes answer from the same parse, so the separate `[[package]]\nname = ...` text grammar, its regexes and block walkers are gone. The previous line-grammar planner is kept test-only as the oracle: randomized v1 and v3/v4 locks (plus twins, [root], sourceless v1, bare blocks, 1.2k-block locks) plan to identical bytes and FileEdits for every package, a re-run, and a second package over the result. The one allowed difference is a lock the old grammar could not read (a final block with no newline after `version`), which the span planner now finds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018BYsX2VfVNKHvfFAJnFryc --------- Co-authored-by: Claude <noreply@anthropic.com>
|
Coordinator: #281 (WS3 lock models) landed on release/v5-prerelease as 73c0c4f; next in order is #279, which needs a re-merge of the base. Generated by Claude Code |
* Remove the setup subcommand and its install hooks (v5 WS7) `socket-patch setup` (and --check/--remove/--exclude) is gone, with every install hook it wired: npm postinstall/dependencies scripts, the socket-patch[hook] .pth wheel, the in-tree Bundler plugin + Gemfile block, and Composer post-install/update scripts. `apply` stays; agent mode in CI is `scan --mode agent` once, then `socket-patch apply` after each install. Deleted: commands/setup.rs, core setup/** and the setup-only package_json helpers, the setup tests and setup-matrix suites, the setup-e2e feature, the setup-matrix CI job, tests/setup_matrix and scripts/setup-matrix.sh. vex's install-hook "Property 7" filter goes with it. The socket-patch-hook wheel and socket-patch-bundler gem are dropped from the build and publish workflows (sources kept, frozen, pending an owner decision). Also the plan's small follow-ups: drop the core crate's deprecated re-export aliases (and the CI grep that guarded them), the unused utils::process::tool_command, the vacuous e2e_cargo/e2e_golang CI rows, add the merged 01019627 and 9c2b4925 gem patches to the vendored production e2e, and retire the backtest-poetry "known crawler gap" label. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PQaKzoW5dSw9u5pgAAvVRj * Streamline the patch UI (v5 WS8) - `-h` lists about eight options per command (`cli_command()` marks the rest hide_short_help; `--help` is unchanged); `scan --apply/--vendor` are hidden (still accepted). - Human warnings drop the `(code)` tag (`Warning: …`, `GC: skipped: …`); JSON keeps every code. Error lines keep theirs. - Human text says "hosted", not "redirect" (JSON keys unchanged). - npm's allow-remote notice is one line; `--verbose`/JSON keep the full policy text. - One `ui::next_steps` renderer for hosted and vendored results. - Hosted and vendored `get` never prompt: top-ranked patch per package, like scan, in JSON too. Agent-mode `get` keeps its picker and confirm. - `list` with nothing to list says `No patches in this project. Run \`socket-patch scan\`.` (exit codes unchanged: 1 missing, 0 empty). - One cancel line (`ui::CANCELLED`) and one paid upsell (`ui::PAID_UPGRADE`). - `get`'s self-enforced flag conflicts and `rollback --one-off` exit 2, like every other usage error. Docs: CLI_CONTRACT (human output conventions, exit codes, get prompts), README, CHANGELOG [Unreleased], v5 plan status. Tests updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PQaKzoW5dSw9u5pgAAvVRj * Pin the real-vlt get_and_remove leg to --mode agent `get <uuid>` defaults to hosted since v5, so the leg's in-place patched/pristine assertions need agent mode spelled out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PQaKzoW5dSw9u5pgAAvVRj * Match the hosted-ledger persist-failure wording in two covgap tests These chmod-guarded tests skip under root, so the WS8 wording change ("hosted redirect ledger" -> "hosted ledger") only showed up in CI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PQaKzoW5dSw9u5pgAAvVRj * Empty list exits 0; group help by task; document the setup upgrade Review follow-ups: - `list` on a project with no manifest and no ledger record is an empty list: exit 0, the empty-project line (human) or the success envelope with `events: []` (`--json`). Only an unreadable or invalid manifest fails. Hosted mode writes no manifest, so this is the normal case. - Root help groups the commands by task (patch, undo, ship, agent mode) instead of calling get/rollback/remove "older agent-mode commands"; the subcommand list follows the same order. `-h` keeps --cwd, --ecosystems and --offline, and moves `scan --prune` to --help. - README gains "Upgrading from `setup`": move to hosted or keep agent mode, and the exact hook to delete per ecosystem. The CHANGELOG and the frozen hook/plugin READMEs link it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PQaKzoW5dSw9u5pgAAvVRj * Carry the hosted wording and code-free warnings onto #280's new tests and docs #280 added tests and contract lines with the pre-WS8 human strings ("Would redirect", "<purl> redirected, but its patch record ...", `Warning (<code>): ...`). Switch them to this branch's conventions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PQaKzoW5dSw9u5pgAAvVRj * Give the cargo safety VEX baseline a vulnerability to attest With setup's install-hook filter gone, the manifest-backed agent-mode cargo patch attests, but the staged minimal manifest carries no vulnerabilities, so vex ended no_applicable_patches (exit 1). Add one vulnerability to the entry before the baseline run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PQaKzoW5dSw9u5pgAAvVRj --------- Co-authored-by: Claude <noreply@anthropic.com>
|
Coordinator: #279 landed as f6bdad5 (CI reds all base-inherited: yarn-classic Generated by Claude Code |
…#282) * Share one hosted engine between disk and memory Extract the plan -> rewrite -> edits stages of `run_redirect_selected` into `socket_patch_core::hosted::engine`, a set of pure functions over a `ProjectView` (build_candidates, bun_lockb_symlinked, withhold_everywhere, read_candidate_files, wheel_targets, rewrite, guard). The disk flow (`scan`/`get --mode hosted`) keeps only the apply lock, the host probes (pipenv version, gem/python/vlt stale installs), the vendored takeover, the symlink refusal and the commit of the rewritten files. The in-memory engine moves to `socket_patch_core::hosted::memory` and runs the same stages over `ProjectView::Memory`; its duplicated redirect.rs / ledger.rs orchestration is deleted. The pnpm trust / npm allow-remote planners move to `hosted::guidance`, the vlt preflight to `hosted::vlt`, and the redirect-ledger delta to `hosted::ledger`, which the engine never calls, so removing the hosted ledger only touches the two callers. `socket-patch-node` now depends on socket-patch-core only; the CLI re-exports `hosted_memory` for `hosted-bundle` and the tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KHZ8uzdXfkG2zH8ZYDG8ju * Split the vendored takeover out of run_redirect_selected Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KHZ8uzdXfkG2zH8ZYDG8ju * Undo unrelated rustfmt churn from the takeover split The previous commit ran `cargo fmt --all` over a tree that is not rustfmt-clean, reformatting ~30 files it does not otherwise touch. Restore those files; no code changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KHZ8uzdXfkG2zH8ZYDG8ju * Read the patch stores through one Ledgers view and ProjectContext `socket_patch_core::ledgers` holds the one owner-precedence rule for the manifest, the vendor ledger and the hosted redirect ledger (manifest > vendored > hosted by ledger key; a manifest key claims every vendor entry filed under it or naming it as base purl) and the views every reader derives from it: `owned` (one group per owner key, losers as alternates), `listed` (every copy worth showing), `matching` (remove/rollback identifiers) and `hosted_vendored_overlap`. It replaces list's combined_entries, fold_vendor_records / vendor_record_is_unowned, scan's merge_ledger_records_for_updates, vex_sources' build_candidates and overlap_from_states, and rollback/remove's per-store matching loops. `LoadedLedgers::load` loads the three stores once, each with its own outcome so every caller keeps its error posture. `commands::context::ProjectContext` lazily loads the stores, the lockfile inventory and the wiring discovery at most once per run; scan's discovery phase, list and get read through it. `get`'s installed-version narrowing now reuses scan's lockfile and vendored-ledger supplements instead of its own inventory and ledger reads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KHZ8uzdXfkG2zH8ZYDG8ju * Index vendor-ledger claims once per view `Ledgers::owned` re-sorted and scanned every vendor key for each manifest key. Group the vendor entries under their claiming manifest key in one pass over the sorted ledger (`claims`), so each view is O(V log V + M) instead of O(M * V log V). Same ordering and alternates. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KHZ8uzdXfkG2zH8ZYDG8ju * Return typed warnings from the hosted engine; JSON in hosted::render The engine's own warnings (rush repo-state, pnpm trustLockfile, npm allow-remote, vlt artifact-unverifiable, record_fetch_failed, the in-memory takeover refusal) were built as serde_json values inside orchestration. They are now RewriteWarning values; the new hosted::render module holds the only JSON spelling (warnings, skipped entries, the nested redirect block), consumed by the disk adapter and the in-memory engine. No output change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KHZ8uzdXfkG2zH8ZYDG8ju * Read the lock set and discovery through one per-run snapshot ProjectView gains a Snapshot variant: the disk under a read-through cache (DiskSnapshot), so each lock or config file is read at most once per run and every reader sees the same bytes; probes that are not content reads still go to the disk. Lockfile discovery's guarded reads now go through a ProjectView (discover_patched_refs_in), and ProjectContext backs both locks() and discovery() with one snapshot of --cwd. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KHZ8uzdXfkG2zH8ZYDG8ju --------- Co-authored-by: Claude <noreply@anthropic.com>
* Build e2e test binaries once and tier PM legs The e2e matrix recompiled the CLI and its test binary in each of its 176 legs. e2e-build now compiles every CLI test target once per OS and the legs run the downloaded binaries from the same checkout path. That compile also replaces the --all-features --no-run pass in test and test-release, so each of those compiles one feature set. PR runs keep every named version boundary, the oldest and newest release of each tool and every vlt era. The 31 middle e2e rows, 2 yarn-berry releases and 10 cargo toolchain x lock cells move to *-full jobs that run on main pushes, a new nightly schedule and dispatch. - e2e-docker (a subset of coverage-docker) runs nightly only. - Dockerfile.base is built once per run and loaded by each docker leg. - The hermetic maven/nuget crawl tests run in `test`; their rows and e2e_composer's are gone. e2e_safety_cargo_build rides cargo-vex. - pdm-compat builds the capstone once, skips the 7 cells ci.yml runs, drops the Windows native rows (they never ran), and fails a cell whose bootstrap fails or whose rows all skip. - vlt-compat install-proof leaves ci.yml's identical cells to it. - npm/pnpm compatibility are path-filtered on PRs; the disarmed vlt serve watchdog runs daily instead of every 6 hours. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HK6zFsizxHRUtyxPvdhM4c * Address review: env parity, filters, PR cells - The legs that run test binaries directly set SOCKET_NO_CONFIG and SOCKET_NO_UPDATE_CHECK, which cargo's [env] gave `cargo test`. - cargo 1.93.1 with its own lock (the pinned toolchain the removed e2e_safety_cargo_build rows ran) stays on PRs; 1.82.0 own-lock moves to the full tier. - Poetry 2.0.1, the first lock 2.1 writer, stays on PRs. - e2e-build gets 60 minutes on Windows. - npm/pnpm filters also watch .cargo/config.toml and cache_env.rs. - vlt install-proof notes a cell left entirely to ci.yml. - pdm-compat saves its build cache from main only. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HK6zFsizxHRUtyxPvdhM4c * Run the full tier on v5 pushes; review fixes - ci.yml also runs on pushes to release/v5-prerelease, whose PRs skip the full tier and which has no nightly; e2e-docker runs there too. - cargo 1.93.1 with its own lock runs on macOS and Windows on PRs, the cell the old e2e_safety_cargo_build rows ran there. - e2e-build and pdm-compat print rendered compile errors (json-render-diagnostics). - vlt-compat and pdm-compat also trigger on ci.yml changes, and the vlt dedupe only counts ci rows with the same test filter. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HK6zFsizxHRUtyxPvdhM4c * Run the vlt agent get test in agent mode get now defaults to hosted mode (5e5f5ed), so the real-vlt get_and_remove leg ran a hosted get and found the installed copy unpatched. It now passes --mode agent, like the other agent-mode fixtures that commit updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R11RZRvYFL3fzmEnkFAU4A (cherry picked from commit cc5f1b6) * Give the nightly CI run its own concurrency group A concurrency group holds one pending run. Sharing main's group let a queued main push and the nightly cancel each other, and the nightly is the only automatic run of e2e-docker. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HK6zFsizxHRUtyxPvdhM4c --------- Co-authored-by: Claude <noreply@anthropic.com>
|
Coordinator: #291 (v5 CI: build e2e binaries once and tier the PM matrix) landed on release/v5-prerelease as f9cb7e1, ahead of #287. Its only red checks (4 vlt install-proof cells, yarn-classic 1.0.2/1.6.0/1.7.0/1.9.4) fail the same way on the base. #287 is still blocked: it is a draft and nobody has pushed the docs-only reduction. Next up: #292. Generated by Claude Code |
* Fix apply of created files from diff caches A diff archive has no delta for a file the patch creates, yet the disk stager counted a cached diff archive as covering the whole patch. With only diffs on disk, `apply --offline` passed the gate, patched the modified files, then failed on the created file's missing blob and left the package half-patched; online `apply` never fetched that blob at all. Coverage is now per file: a diff covers only files with a before-hash, and created files need their blob. Online, a cached diff archive no longer suppresses the download, and the top-up fetches just the created files' blobs. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzdTEhubve9yWfqBE7vAsB * Batch vendor package-reference requests A vendor run asked the patch service for each package's download reference in its own request, though the endpoint takes 500 uuids at once: N round trips and N quota units for N packages. The run's download plan now resolves every planned uuid in one request, sent by the first planned call in place of its own and with the same retries, so an outage costs what it did before. Each package takes its answer from that batch at its turn; one still building is asked again then, as before. Hosted scan's reference lookup is chunked at the endpoint's 500-uuid cap, which it used to exceed with a 400. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzdTEhubve9yWfqBE7vAsB * Skip downloading pypi sdists vendoring rejects pypi vendoring is wheel-based, yet a pypi patch the service serves as an sdist (every patch without a file qualifier) was downloaded in full, then rejected because it is not a .whl. The service's reference already names the artifact, so a pypi reference whose artifact is not a wheel is now refused before the download, in the vendor loop and in its download plan alike. The outcome is unchanged: `auto` warns and builds the wheel locally, and `service` refuses. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzdTEhubve9yWfqBE7vAsB * Repair downloads created files' blobs A default (diff-mode) `repair` downloaded only diff archives, but a diff has no delta for a file the patch creates. After such a repair `apply --offline` still could not apply a patch that creates files. In diff mode, repair now also downloads the blobs of created files (and lists them under `--offline` and `--dry-run`), reported as their own blob download. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzdTEhubve9yWfqBE7vAsB * Defer pristine fetches the service makes moot With the patch service on, `vendor` deferred the registry download of a not-installed package only for cargo; npm, golang and composer packages were downloaded and verified up front even when the service's prebuilt artifact made the pristine copy unnecessary. Those backends also ask the service first and read the pristine tree only on a local-build fallback, so their download is now deferred the same way. A package is deferred only when its fetch would really download: the fetchers' pre-download refusals (a foreign yarn berry cacheKey, a go module go fetches without a proxy, a composer entry with no dist URL) are now one shared check that both the fetch and the deferral use. pypi and gem keep the up-front fetch, which their installed-variant probe reads. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzdTEhubve9yWfqBE7vAsB * Run the vlt get e2e leg in agent mode A bare `get` defaults to hosted mode since v5, so the real-vlt get_and_remove leg found the installed copy unpatched. Pass `--mode agent` as #283 does, which this ports. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzdTEhubve9yWfqBE7vAsB * Address review of the waste-review fixes - repair --json no longer counts created-file blobs twice (once under the diff-mode event); the closing line names both failure counts when both passes fail. - The vendor reference batch names the plan from the first call's position on, so a package the loop passed over is never granted. - The npm and yarn classic registry views no longer take a non-http resolution's integrity (a local tarball's hash, a git commit id) as a registry integrity, so such a package is never deferred behind, or vendored from, the service's registry build. - CHANGELOG entries for the new behavior, and the repair event row in CLI_CONTRACT. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzdTEhubve9yWfqBE7vAsB * Keep yarn git deps out of the registry view A yarn classic block resolved to a git repository over plain https (`https://…/repo.git#<commit>`, or a codeload tarball) passed as a registry tarball: its commit id became a sha1 integrity, and an `integrity` field on any git block was kept. With npm now deferring behind the patch service, such a lockfile-only git dependency could be vendored from the service's registry build instead of refusing `vendor_fetch_unverifiable`. A git resolution, over any protocol, now carries no URL and no integrity in the registry view, like npm's non-registry entries. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzdTEhubve9yWfqBE7vAsB --------- Co-authored-by: Claude <noreply@anthropic.com>
* Share one hosted-URL check for pdm and poetry The pdm and poetry lockfile rewriters each carried an identical copy of the rule that decides whether an existing pin is an earlier hosted redirect of the same artifact. They now use one copy in python_lock, so the two rewriters cannot drift apart. No behavior change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018JsczaHn8e6YrCpxs1NznQ * Remove the never-implemented --one-off flag `get --one-off` and `rollback --one-off` (and SOCKET_ONE_OFF) never did anything: they only failed with a "not yet implemented" usage error. v5.0 drops them. Passing `--one-off` is now an ordinary unknown-flag error (still exit 2), and SOCKET_ONE_OFF is ignored. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Drop blanket dead-code allow on vlt lock parser The whole vlt lockfile text module was exempt from dead-code checks, which hid an unused edge-rendering method. The exemption is gone, the unused method is deleted, and the macOS-only global node_modules helper now opts out of the lint only on builds where it is truly unused. No behavior change for vlt users. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Stop reading never-written package archives Nothing has written `.socket/packages/<uuid>.tar.gz` for several releases, yet apply, vendor and repair still probed and overlaid that directory and the patch pipeline tried it before the diff archive. v5.0 drops the read path and the `appliedVia: "package"` JSON value. The GC sweeps (scan --prune, rollback, remove, repair) now remove any leftover `.socket/packages/` files whole, so old projects are cleaned up; the `removedPackageArchives` counter keeps reporting them. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Drop v3 env names and legacy scan spellings v5.0 removes compatibility surface kept since v3/v4: - The legacy env names SOCKET_PATCH_PROXY_URL, SOCKET_PATCH_DEBUG and SOCKET_PATCH_TELEMETRY_DISABLED are no longer read (use SOCKET_*). - The hidden `scan --redirect` flag (use `--mode hosted`) and the hidden no-op `scan --detached` flag are gone; both are now unknown flags (exit 2). - The hidden `--mode` values `host`, `redirect` and `vendor` (scan and get) are rejected; only hosted, vendored and agent remain. The hidden `scan --apply` and `scan --vendor` spellings stay. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Delete uncalled public helpers in core vendor code Several public functions in the core crate had no callers anywhere in the workspace: the bun workspace artifact snapshot, the vlt lock sniff wrapper, the in-memory project's binary/symlink/path helpers, and the disk snapshot's invalidate, sync text read and disk-root accessors. They are removed. The in-memory project's text/present/entries helpers that only tests use are now test-only. No user-visible behavior change. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Drop the never-read berry zip URL from hosted The hosted engine copied the yarn-berry cache-zip URL into every redirect entry, but no rewriter ever read it: berry pins only the zip's checksum, which is still taken from the patch reference. The field is gone from DepOverride; references that still send it parse as before. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Note the v5 dead-code removals in the changelog Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Share one service fallback policy across backends Every service-backed vendor backend (npm tarball and directory, pypi, cargo, composer, golang, gem, and the frozen maven/nuget path) decided on its own when a patch-service miss falls back to a local build, when `--vendor-source=service` refuses, and when tampered bytes are fatal. Seven hand-copied versions of that policy could drift apart. The policy now lives once in service_fetch.rs: ServicePolicy maps the Pending, Unavailable, Failed and IntegrityMismatch outcomes, and each backend keeps only its own handling of a ready artifact. Warnings, refusal codes, messages and check order are unchanged, including the npm tarball backend's wording for a failed request in service mode. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Align tests and docs with package-archive removal Three integration tests still expected a leftover package archive to be a patch source or to survive GC; they now pin the v5.0 rule (not a source, always swept). Also: a rollback --one-off rejection test, test names that no longer mention the removed --detached flag, a dead berryZipUrl branch in the hosted memory harness, and contract rows that still listed `--download-mode package`. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
|
Coordinator: #296 (remove dead code and v3 shims) landed on release/v5-prerelease as 1e3ace6. Its only red checks were base-inherited: yarn-classic ×4 Generated by Claude Code |
* Pin the #257 oracle sweeps with golden digests The randomized equivalence sweeps that compare the blocking-pool crawlers and single-pass rewriters with their pre-#257 oracles now also record, per case, a digest of the generated input and of the output both implementations agreed on, in crates/socket-patch-core/tests/equivalence/*.golden. The oracles still run, so every recorded output is proven equal to the oracle's. The next commit deletes the oracles and keeps the goldens. The crawler goldens were blessed on Linux as a non-root user: the sweeps strip permissions and plant symlinks, so they replay only there (other platforms and root still run the sweeps, without the golden comparison). Also moves the crawler sweeps' xorshift test RNG to crate::test_rng, and renames crawlers::oracle_support to crawlers::test_tree. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N9YdJqaGiT9Jf5LN1hDFhB * Retire the #257 crawler and rewriter oracles The equivalence sweeps now replay against the golden digests pinned in the previous commit instead of re-running the verbatim previous implementations: the crawler oracles (cargo, composer, go, maven, npm, nuget, python), the hosted rewriter oracles and the reference POM parser are deleted. Every sweep still generates the same inputs from the same seeds, so a changed output is still caught per case. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N9YdJqaGiT9Jf5LN1hDFhB * Pin the pdm and poetry rewrites by golden The pdm plan, pdm and poetry parse-reuse rewrites were checked against #257's verbatim fresh-parse implementations. Their sweeps now record per-case digests, blessed while those oracles still agreed with production, and the oracles are deleted. Golden keys with whitespace are normalized so a case label can never split a golden line. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N9YdJqaGiT9Jf5LN1hDFhB * Merge 67 CLI test binaries into command suites The subprocess-only integration tests for apply, get, remove, rollback, repair, scan, vendor, update and the global CLI now build as one binary per command instead of 67, so CI links and runs 58 fewer test binaries. Each file keeps its name as a module, and the shared helpers are declared once per suite, so their self-tests run once per suite instead of once per file. Binaries a workflow names, the docker e2e suites, crawl_fd_limit_e2e and the in-process suites (which share process-global notice and config state) stay separate. Doc links to the moved files are updated. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N9YdJqaGiT9Jf5LN1hDFhB * Point docs at the merged test suites Rows that named a moved test binary now name its suite and module. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N9YdJqaGiT9Jf5LN1hDFhB * Keep the crawler oracles A crawler's output follows the directory order the filesystem hands back, which differs between filesystems: every crawler golden blessed on ext4 failed when replayed on tmpfs, and macOS and Windows skipped them. The per-call oracles walk the same tree as the crawler, so they check the parallel walk on every platform; restore them and drop the crawler goldens. The rewriter goldens are pure text and stay. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N9YdJqaGiT9Jf5LN1hDFhB --------- Co-authored-by: Claude <noreply@anthropic.com>
|
Coordinator: #297 (one suite per command, retire #257 oracles) landed on release/v5-prerelease as b97a1c2. Its only red checks were base-inherited: yarn-classic ×4 and vlt install-proof 0.0.0-1/0.0.0-11 ×4. Next up: #293, then #294, each after it merges the new base. #287 is still skipped (draft, not yet reduced to docs-only). Generated by Claude Code |
* Plan staged patch rollout for v5 Adds the design for rolling Socket patches out gradually: a `patches:` block in socket.yml that narrows what scan may patch (paths, ecosystems, packages, severity floor, on/off), and a severity-ordered per-run cap on new patches so each scan lands the next few most critical fixes. The plan splits the work into two parallel items with a frozen interface, lists every hard-coded filter and where it belongs, and covers the depscan autopatch follow-up. configuration.md now records that socket-patch reads socket.yml for selection policy only, with the trust boundary unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Revise rollout plan after adversarial review Three independent reviews (ambiguity, churn, trust boundary) found gaps that would have let the two implementations disagree or let a repo file widen or stall the rollout. The plan now: - matches paths against marker files with the backend's top-down gitignore rules, so projectIgnorePaths means the same everywhere - uses one data source for severity, supersession and ordering - spends the budget only on patches the planning pass proves can land, and admits nothing new when a lookup failed - uses the merged recorded view in every mode and engine - has depscan read the policy from the base commit for PR jobs - hardens file handling (regular files, aliases, size, encoding, trusted repo root) and reports what a policy hides Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Close interface gaps in the rollout plan A final consistency pass found places where the two work items would have produced incompatible code: base purls admitted in one directory being charged again in the next, no defined hand-off of the unfiltered offers from the severity filter to classification, no shared repo-relative path helper, and override sources the JSON must report but the interface could not carry. The shared contract now defines each of these, and the parity tests match each engine's budget scope. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Clarify who shapes scan's selection output The plan said both that the selector returns the shared offers struct (work item A) and that it returns admitted/deferred rows (work item B). The selector now returns the offers, and B adds the rollout stage after it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Add the socket.yml selection policy to core New socket_patch_core::policy module: the SelectionPolicy, Offers and repo-root helpers that the staged-rollout plan freezes as the shared contract between the socket.yml work and the --max-new-patches work. It reads the repo root's socket.yml/socket.yaml strictly and fails closed: bad YAML, a misspelled `patches` block, unknown keys (with a did-you-mean hint), wrong types, empty allowlists, bad globs and anchors or aliases inside the keys we read are all errors that name the key path. Path lists match marker files with npm `ignore` semantics, walked top-down; a golden fixture generated from the npm package pins that. The built-in test/fixture ignores become overridable defaults for discovered roots. --package matching moves to core so scan and the policy share it. Claude-Session: https://claude.ai/code/session_01BKsyzefGhAnPkYmXCwq3H3 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Honor socket.yml patch policy in disk scans scan now reads the repo root's socket.yml before any write and applies its patches block in hosted, vendored and agent mode, --dry-run included: path filters on project roots (PATH-glob matches also get the built-in test/fixture ignores), ecosystem and package filters on crawled packages, and a severity floor on the patches a package may receive. An invalid or ambiguous file fails the run with exit 1 and an errorCode before any request. Packages that already carry a patch are never removed, upgraded or replaced by the policy: they are held and reported under policy.retained. A recorded patch below a new floor stays in place. patches.enabled: false reports what would be patched and writes nothing. New flags: --no-socket-yml / SOCKET_NO_SOCKET_YML and --min-severity / SOCKET_MIN_SEVERITY. Every successful scan --json result gains a top-level policy block. A PATH outside the repository root is a usage error. Claude-Session: https://claude.ai/code/session_01BKsyzefGhAnPkYmXCwq3H3 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Apply socket.yml policy in the in-memory engine selectHostedScanPaths now streams the root socket.yml/socket.yaml and returns them as policyPaths; it drops test and fixture trees through the policy's built-in default ignores instead of a hard-coded segment list (structural excludes like node_modules and vendor stay fixed). The session reads the policy before any root is processed: path filters run before the project limit, ecosystem and package filters on each root's packages, and the severity floor before selection. A listed policy file that arrives without content, or an invalid one, yields policyError with no root processed and no file changed. New options noSocketYml, minSeverity and policyPaths; the result gains a policy block. hosted-bundle and index.d.ts carry the new fields. get now warns policy_bypassed when the repo's socket.yml would have skipped the package it patches. Claude-Session: https://claude.ai/code/session_01BKsyzefGhAnPkYmXCwq3H3 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Document the socket.yml patch policy CLI_CONTRACT.md gains a "socket.yml patch policy" section (grammar, precedence, paths, lookup, validation, commands, error codes and the policy JSON block), the flag and env rows, and the "narrow or pace" half of the trust-boundary rule. README adds a "Roll out gradually" section with copyable socket.yml recipes, CHANGELOG lists the new policy and the breaking scan changes, and the design docs record the decisions made while building it. Claude-Session: https://claude.ai/code/session_01BKsyzefGhAnPkYmXCwq3H3 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Harden the socket.yml policy after review Fixes from an adversarial self-review of the policy work: - A non-ASCII package spec no longer panics validation, and error text, warnings and verbose lines drop terminal escapes and bidi or zero-width characters. - Ignore lists that fail to compile together are an error instead of silently matching nothing, and the policy file is opened by its resolved path without following a swapped-in symlink. - A top-level key that looks like a misspelled `patches` (`patchs`), a top-level merge key or an aliased key now fails closed. - Repo-root lookup follows a `.git` symlink and trusts the checkout owner under root and sudo, so CI containers keep the policy. - The severity floor always reports the patch it held back, report- only --json runs report what a floor or `enabled: false` hides, a root skipped as a whole is always one entry and no longer prints "No packages found", warnings print once per invocation, and the policy line is omitted when there is nothing to say. - policy entries are sorted and use canonical purls on disk and in memory; the in-memory engine applies a socket.yml negation of a built-in ignore to roots it was given, and policyError carries no CLI-only remedy. - A lockfile-less disk root matches path filters by its manifests. Tests cover each fix, plus the prune universe, agent path filters, a vendored package held byte-identical, and tighter oracles; docs are corrected where they overstated what the human output names. Claude-Session: https://claude.ai/code/session_01BKsyzefGhAnPkYmXCwq3H3 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Give the release test job more time The optimized test job now has to build two more crates and one more test binary for the socket.yml policy. It ran out of time on its last 40 minutes, about a minute short, and a docs-only change already takes 38. Raise the limit to 50 minutes so it can finish. Claude-Session: https://claude.ai/code/session_01BKsyzefGhAnPkYmXCwq3H3 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Apply socket.yml paths during memory selection The merged plan makes in-memory path selection two-phase: the host fetches the root socket.yml first and passes its text to selectHostedScanPaths. Selection now applies the full path policy, so a negation such as `!/e2e/tests/` brings a test tree back in memory exactly as on disk. A listed policy file that is missing, symlinked or invalid returns policyError with nothing selected. Selection returns policySha256, and the session fails closed when the policy it reads differs. Roots the policy excludes keep their marker files presence-only, so the session still lists them as filtered. Claude-Session: https://claude.ai/code/session_01BKsyzefGhAnPkYmXCwq3H3 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Keep excluded roots out of the memory stream Review follow-ups to two-phase selection: - Roots the policy excludes are reported in ignoredSample instead of streamed presence-only, so a repo with many fixture lockfiles no longer runs into the session's file limit. - A session that bypasses socket.yml while selection applied it now fails closed instead of processing roots it never fetched. - The docs say policy text must decode losslessly (TextDecoder drops a BOM) and when policySha256 is null. Claude-Session: https://claude.ai/code/session_01BKsyzefGhAnPkYmXCwq3H3 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Keep scan -h short with the policy flags The v5 help rules cap each command's short help at about eight options. `--no-socket-yml` and `--min-severity` pushed `scan -h` to ten, so they now appear only in `scan --help`, like the other advanced scan flags. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BKsyzefGhAnPkYmXCwq3H3 * Fail report-only JSON when detail queries fail A report-only `scan --json` (`--prune` with no mode) with a severity floor or `enabled: false` fetches patch details to fill `policy.filtered[]`. If every query failed it still printed a success envelope and exited 0. It now reports the error and exits 1, like the agent and vendored runs. Human-mode policy warnings now print `Warning: <detail>` like the rest of `scan`; the code stays in the JSON `warnings[]` entry. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BKsyzefGhAnPkYmXCwq3H3 --------- Co-authored-by: Claude <noreply@anthropic.com>
The v5 prerelease centres the CLI on one workflow:
socket-patch scanpatches dependencies by rewriting lockfiles to Socket-hosted patched packages. It writes nothing but those lockfile edits.socket-patch vexemits OpenVEX for vulnerability scanners.socket-patch vendorejects the patches into.socket/vendor/for offline installs.socket-patch listshows what a project has. The older agent-mode commands (get,apply,setup,rollback,remove,repair) keep working but move after these in help and docs.This is a draft. It lands in stages, one commit per step.
Done so far
--helplists scan, vex, vendor and list first, then the agent-mode commands, with a "Typical workflow" footer.scanruns hosted mode. A path-scoped,--pruneor global scan with no--modeonly reports, because it has no lockfile to rewire.scan: no confirm prompt in any mode and no patch menu.--packagefilter: scopes a scan by name or purl, and can be repeated or comma-separated.apps/*) name project directories, each scanned as if it were--cwd.updates[]also counts the hosted pins in the lockfiles, not only ledger records.GlobalArgs::{crawler_options, is_global, ecosystem_selected, purl_ecosystem_selected}replace 8 hand-copiedCrawlerOptionsliterals and 7 ecosystem-scope predicates.Still to come
.socket/vendor/redirect-state.json.vex,listandvendorread the hosted pins from the lockfiles.rollbackrebuilds the originals from the registry. Existing ledgers are still read.vendorejects hosted pins into vendored artifacts.Known red
e2e_redirect_cargo_build(2 cases): the embeddedscan --vexnow writes the hosted ledger. These pass once scan stops writing the ledger (the ledger-free step above).🤖 Generated with Claude Code