fix(scan): surface hosted redirect state in report-only scan --json and list - #220
Merged
Mikola Lysenko (mikolalysenko) merged 2 commits intoAug 19, 2026
Merged
Conversation
…nd list Live-verified defect (gem live-matrix D3, 2/2 per cell on bundler 1.17/2.7/4.0 vs production): after `scan --mode hosted` + install, the hosted state was invisible to both read-only surfaces a user would check — * report-only `socket-patch scan --json` (exit 0) contained ZERO hosted/redirect/warning fields (byte-identical to a never-touched project's envelope): the `hosted_wiring_retained` warning rides only the agent-mode apply branch, and the `redirect` sub-object only a hosted-mode run's; * `socket-patch list --json` exited 1 `manifest_not_found` — hosted mode records its patches ONLY in the redirect ledger and never writes `.socket/manifest.json`. This matches the long-open "hosted-mode JSON gap" residual from the 2026-07-04 scan/mod.rs review; PR #207 surfaced the adjacent cross-mode warnings but not these two surfaces. Fix (both additive/MINOR per CLI_CONTRACT.md): * scan --json: every non-hosted-mode, non-vendored-mode envelope (report-only, agent, and the zero-discovery path) now carries a top-level `redirectState` block whenever the redirect ledger holds records: `{ mode, ledger, records: [{purl, uuid}], wiringLive }`. It is a descriptive STATE block, not a warning — records are the ledger's word, `wiringLive` the live lock's proof (the same `hosted_wiring_live` gate as the #207 warning). Hosted-mode runs keep their `redirect` sub-object and vendored runs their takeover warnings (both can mutate the ledger mid-run, so a pre-run snapshot would go stale). * list / list --json: hosted redirect-ledger records fold into the listing, labeled (`details.mode: "hosted"` + `details.ledger`; a `Mode: hosted` line on the human path), purl-sorted with the manifest entry first on a tie. A hosted-only project now exits 0 with its records; `manifest_not_found` still fires when NEITHER store has a record (an edits-only ledger asserts no patches), and a present-but-broken manifest still reports manifest_invalid / manifest_unreadable — corruption is never masked. A malformed ledger degrades to "nothing to consult" with a stderr warning (read-only consumer posture per load_redirect_state's contract). Test-first: tests/scan_invariants.rs (4 new, mirroring the #207 fixtures) and tests/cli_parse_list.rs (6 new subprocess tests) were RED against the old binary; plus in-module unit tests for the block builder and the list envelope labeling. CLI_CONTRACT.md updated (scan section, subcommands table, manifest_not_found row, list action-matrix row). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.
Bugbot Autofix is ON. A cloud agent has been kicked off to fix the reported issues.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit cd7519a. Configure here.
Mikola Lysenko (mikolalysenko)
enabled auto-merge (squash)
August 19, 2026 17:30
…rfaces Adversarial-review round on the redirectState/list-hosted work (PR #220), all defects reproduced RED-first: * vendored zero-discovery leak: the empty-crawl early-return gated the redirectState insertion on `!hosted` alone, so `scan --mode vendored --json` over an empty crawl carried the block the main path (and the contract) exclude — now `else if !vendor`, with omission tests pinning hosted AND vendored envelopes on both the zero-discovery and ≥1-package paths. * --silent violations: the malformed-ledger degradation warnings (list + both scan consults) ignored --silent; all three now flow through one shared crate::commands::load_redirect_state_lenient(cwd, silent). * records↔wiringLive join: records[] kept the ledger's verbatim API-spelled keys (percent-encoded scoped npm names, ?platform= qualifiers) while wiringLive was canonical — a live redirect read as "wiring unwound" under the contract's documented string-compare join. records[].purl is now canonicalized to wiringLive's spelling with the raw key preserved as records[].ledgerKey. * list --manifest-path store mixing: the ledger was always resolved against cwd, interleaving two projects' stores and letting a LOCAL ledger suppress the flagged project's manifest_not_found — the ledger now resolves against the project root the RESOLVED manifest path implies (.socket parent's parent, else the manifest's directory). * telemetry: patch_listed's patches_count had silently become "listed entries" (manifest + hosted, double-counting shared purls); restored to manifest patches only, pinned by a wiremock body capture. * wiringLive scope: the probes now use the post---ecosystems-filter scanned set (a filtered-out purl is "not covered this run", never "unwound"); contract documents the third silent cause. GC prune deliberately keeps the pre-filter set. * efficiency/structure: the live-wiring probe (full lockfile-inventory parse) runs once per run behind a new no-I/O gate and is shared by the block and the agent warning; redirect_state_json is now sync and takes the precomputed proof; the block's records gate is its single presence decision. * one mode-label owner: crate::commands::HOSTED_MODE_LABEL feeds both scan's block (previously echoed the ledger's opaque mode string — a legacy "redirect" ledger now still labels "hosted") and list's details. * list simplifications: exit predicate derived from combined_entries emptiness; build_list_envelope doc reflects the sort ownership; manifest_envelope test helper replaces the copy-pasted call sites. * test fixture promoted: tests/common/mod.rs::write_redirect_ledger, used by cli_parse_list. * contract: redirectState paragraph rewritten (canonical purl + ledgerKey, constant mode label, post-filter wiringLive + third silent cause, error envelopes excluded, --silent-gated degradation warning); list row gains the --manifest-path same-project scoping. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Wenxin Jiang (Wenxin-Jiang)
approved these changes
Aug 19, 2026
Mikola Lysenko (mikolalysenko)
deleted the
fix/scan-hosted-state-visibility
branch
August 19, 2026 19:17
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

fix(scan): surface hosted redirect state in report-only
scan --jsonandlistDefect (gem live-matrix D3 — live-verified 2/2 per cell vs production)
After hosted wiring (
scan --mode hosted) + a real install, the hosted state was invisible to both read-only surfaces a user would check, verified on three bundler majors against the production patch API (activestorage 6.0.3, hosted-wired Gemfile + patched gem on disk):scan --jsonlist --jsonmanifest_not_foundmanifest_not_foundmanifest_not_foundRoot cause (at d9afb08, unchanged on main):
hosted_wiring_retainedis pushed to the scan JSON envelope only inside the--applybranch and the post-apply text path; the read-only path loadsredirect_statebut never emits it. And hosted mode records its patches only in.socket/vendor/redirect-state.json— it never writes.socket/manifest.json— solisthard-failed on a purely hosted project while its patches were demonstrably live. A hosted-wired project's report-onlyscan --jsonwas byte-identical to a never-touched project's.This closes the long-open "hosted-mode JSON gap" residual (scan/mod.rs review 2026-07-04); #207 surfaced the adjacent cross-mode warnings but not these two surfaces.
Fix design (both additive/MINOR per CLI_CONTRACT.md's own rules)
scan --json— new top-levelredirectStateblock. Every non-hosted-mode, non-vendored-mode envelope (report-only,--mode agent/--apply/--sync, and the zero-discovery path) carries it whenever the redirect ledger holds ≥ 1 record:It is a descriptive state block, not a warning — a healthy hosted project's every scan must not read as an anomaly (the #207
hosted_wiring_retainedwarning stays exactly where it was: the agent-flow conversion-incomplete diagnostic, unchanged and now accompanied by the block).recordsare the ledger's word;wiringLiveis the live lock's proof, computed ONCE per run by the samehosted_wiring_livegate as the #207 warning and scoped to this run's post---ecosystems-filter counted purls — a record with no proof means unwound wiring, an unreadable lock, or a purl this run did not cover, never "still live". Error envelopes (offline refusal, all-batches-failed) are deliberately minimal and never carry the block. Hosted-mode runs keep theirredirectsub-object and vendored runs their takeover warnings; both can mutate the ledger mid-run, so neither duplicates a pre-run snapshot that could go stale.list/list --json— hosted records fold in. Redirect-ledger records are listed alongside manifest entries, labeled apart:details.mode: "hosted"+details.ledgeron the events (additive, absent on manifest entries; the constant mode name, whatever opaque mode string a pre-rename ledger carries), aMode: hosted (recorded in .socket/vendor/redirect-state.json)line on the human path. Global purl sort, manifest entry before the hosted record on a tie — when both stores hold a purl, both are shown. Exit semantics: records found (either store) → exit 0;manifest_not_foundstill fires when neither store has a record (an edits-only ledger asserts no patches); a present-but-broken manifest still reportsmanifest_invalid/manifest_unreadableregardless of ledger records — corruption is never masked. A malformed ledger degrades to "nothing to consult" with a stderr warning, muted by--silent(the read-only consumer postureload_redirect_statedocuments; the hosted write path hard-errors instead). Both stores always come from the SAME project: with--manifest-paththe ledger resolves against the root the resolved manifest path implies, never the local cwd. Thepatch_listedtelemetry metric keeps its pre-hosted meaning (patches_count= manifest patches only).Contract changes (CLI_CONTRACT.md)
redirectState) paragraph; the read-only sentence now names the block.listrow mentions the hosted ledger.manifest_not_foundrow:list's hosted-only behavior + the never-masked corruption rule.list'sdetails.mode/details.ledgerkeys.Tests (test-first — all RED against the old binary, GREEN now)
tests/scan_invariants.rs(+4, mirroring fix(scan): surface cross-mode state — hosted wiring retained, vendored ownership, berry vendored-entry refusal #207's fixtures): report-only block presence with records + live proof and no conversion warning; omission for edits-only/no ledger; records-vs-wiringLive split on a registry-clean lock; agent runs carry block + warning together.tests/cli_parse_list.rs(+6 subprocess tests): hosted-only exit 0 with labeled events (JSON + human); manifest+ledger coexistence incl. a same-purl tie; edits-only ledger stillmanifest_not_found; corrupt manifest never masked;--silentsuppression.redirect_state_jsongates/shape (scan), envelope labeling + hosted-only envelope (list).Gates:
cargo fmt --checkon touched files,cargo clippy --workspace --all-features -- -D warnings(the exact CI invocation) clean, and the fullscan_invariants,cli_parse_list,in_process_scan,in_process_redirect,in_process_redirect_pnpm,in_process_vendor,cli_parse_scan,cli_scan_silent,mode_migration_npm,mode_migration_cargo,repair_invariants,e2e_vex_redirect,e2e_safety_yarn_pnp,cli_parse_mainsuites plus the lib unit tests green. No rewriter touched, so the redirect goldens are unaffected.Review round (adversarial review, applied in fa3a797)
An adversarial review produced convergent findings; all were reproduced RED-first and fixed:
redirectStateon!hostedalone, soscan --mode vendored --jsonover an empty crawl carried the block. Gate fixed to!hosted && !vendor; omission now pinned for hosted AND vendored envelopes on both the zero-discovery and ≥1-package paths (hosted_mode_envelopes_omit_redirect_state,vendored_mode_envelopes_omit_redirect_state).--silentviolations — the malformed-ledger degradation warnings (list + both scan consults) ignored--silent; all three now flow through one sharedload_redirect_state_lenient(cwd, silent)(this also silent-gates the pre-existing main-path scan warning, per "errors only").records[]kept verbatim API-spelled ledger keys whilewiringLivewas canonical, so a live redirect of a%40scope%2F-encoded or?platform=-qualified purl read as "wiring unwound" under the documented string-compare join.records[].purlis now canonicalized towiringLive's spelling; the raw key is preserved asrecords[].ledgerKey.list --manifest-pathstore mixing — the ledger was resolved against cwd unconditionally, interleaving two projects' stores and letting a local ledger suppress the flagged project'smanifest_not_found. The ledger now resolves against the project root the resolved manifest path implies.patch_listed.patches_counthad silently become "listed entries" (manifest + hosted, double-counting shared purls); restored to manifest-only, pinned by a wiremock body capture.redirectState.--ecosystems-filter scanned set; "purl not covered this run" documented as the third silent cause (GC prune deliberately keeps its pre-filter set).8-9. Efficiency/structure — the live-wiring probe (a full lockfile-inventory parse) runs once per run behind a new no-I/O gate, shared by the block and the agent warning;
redirect_state_jsonis sync and takes the precomputed proof, its records gate being the block's single presence decision.10-13. Cleanups — one mode-label owner (
HOSTED_MODE_LABEL; a legacy"redirect"ledger now still labels"hosted"on both surfaces), list exit predicate derived from entry emptiness, envelope-doc sort ownership fixed,manifest_envelopetest helper, redirect-ledger fixture writer promoted totests/common/mod.rs.Possible follow-ups (out of scope here)
scan --jsonis similarly thin on vendored ownership (which packages the vendor ledger owns) — a separate observed gap, deliberately not addressed to keep the surface additive and reviewable.list,vex, and scan'supdates[]each fold manifest + redirect-ledger records with their own collision policy — real, but a core-level refactor.remove/repairhosted-awareness asymmetry:repairhas aredirect_only_projectskip whileremovestill errorsmanifest_not_foundon hosted-only projects.🤖 Generated with Claude Code