Skip to content

v5: remove setup (WS7) + patch UI streamlining (WS8) - #279

Merged
Mikola Lysenko (mikolalysenko) merged 12 commits into
release/v5-prereleasefrom
v5/remove-setup-and-ui
Sep 28, 2026
Merged

Mikola Lysenko (mikolalysenko) merged 12 commits into
release/v5-prereleasefrom
v5/remove-setup-and-ui

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Sep 27, 2026 •

Copy link
Copy Markdown
Collaborator

Covers WS7, WS8, the "Remaining small follow-ups" in docs/design/v5-plan.md, and the review follow-ups in c4f3625.

WS7: remove setup

  • Deleted the setup subcommand and everything only it used: core setup/**, the setup-only package_json helpers, commands/setup.rs, its tests and the setup-matrix suites, the setup-e2e feature, the setup-matrix CI job, tests/setup_matrix, scripts/setup-matrix.sh, and the setup-only gem Dockerfiles.
  • vex's install-hook "Property 7" filter is gone. A manifest setup block still parses, but nothing reads it.
  • The socket-patch-hook wheel and the socket-patch-bundler gem are no longer built or published (publish workflows, build-pypi-wheels.py, version-sync.sh), and the socket-patch[hook] extra is dropped.
    • Owner decision pending: their sources under pypi/socket-patch-hook/ and gem/socket-patch-bundler/ are kept, frozen, with a deprecation README.
  • The README's new "Upgrading from setup" section gives each ecosystem's hook to delete, plus how to move to hosted mode or keep agent mode. The CHANGELOG and the frozen package READMEs link to it.
  • Follow-ups:
    • dropped the core crate's deprecated re-export aliases, and the CI grep that guarded them
    • dropped the unused tool_command
    • dropped the vacuous e2e_cargo/e2e_golang e2e rows
    • added the gem patches 01019627 and 9c2b4925 to GEM_PATCHES
    • retired the backtest-poetry "known crawler gap" label

WS8: patch UI streamlining

  • Help: the root help groups commands by task:

    • patch: scan, get, list
    • undo: remove, rollback
    • ship: vex, vendor
    • agent mode: apply, repair

    The subcommand list and the README command reference follow the same order.

  • Short help: -h lists about 8 options per command, including --json, --dry-run, --cwd, --ecosystems and --offline. --help is unchanged. scan --apply/--vendor/--prune are hidden from -h but still accepted.

  • Warning codes: human warnings drop the (code) tag (Warning: …, GC: skipped: …); the JSON keeps every code. Error lines keep Error (<code>) so --silent output stays grep-able.

  • Wording: human text says "hosted" instead of "redirect", e.g. Switched N packages to hosted patches; rewrote M files.. JSON keys and codes are unchanged.

  • npm allow-remote: the notice is one line; --verbose and --json keep the full text.

  • Next steps: hosted and vendored runs share one numbered block from ui::next_steps.

  • get prompts: hosted and vendored get never prompt. Like scan, they take the top-ranked patch, in JSON too, so there is no selection_required outside agent mode. Agent-mode get keeps its picker and confirmation.

  • Empty list (BREAKING): a project with no manifest and no records exits 0. It prints No patches in this project. Run \socket-patch scan`., or under --jsonthe success envelope withevents: []`. Only unreadable or invalid state exits 1.

  • Shared strings: one cancel line (Cancelled; no changes made.) and one paid-plan upsell line.

  • Exit codes: get's own flag-conflict errors and rollback --one-off now exit 2 (previously 1), like every other usage error.

  • Docs: CLI_CONTRACT has a new "Human output conventions" section plus updated exit-code and prompt rows. README, CHANGELOG and the plan status are updated.

Testing

Follow-ups (not blocking)

  • One result type and json_envelope output for scan/get, as a separate PR against release/v5-prerelease once the stack lands.
  • F15, needs an owner decision: delete the frozen pypi/socket-patch-hook/ and gem/socket-patch-bundler/ sources and yank the published packages.
  • F15: remove the parse-only SetupConfig in manifest/schema.rs. It is kept so older manifests with a setup block still load; removing it needs a decision on unknown-key handling.
  • F40: done here (the setup-matrix CI job is removed).

🤖 Generated with Claude Code

https://claude.ai/code/session_01PQaKzoW5dSw9u5pgAAvVRj


Note

Medium Risk
Planned v5 major release with wide CLI contract and publish pipeline changes; behavior is documented but removes a formerly supported install-hook workflow users may still rely on.

Overview
v5 breaking change: removes socket-patch setup and all install-hook plumbing (npm postinstall scripts, Python socket-patch[hook], Bundler plugin gem, Composer script hooks). Core setup / package_json modules, setup-matrix tests, the setup-e2e feature, and the experimental setup-matrix CI job go with it. Publishing no longer ships socket-patch-hook on PyPI or socket-patch-bundler on RubyGems; CI lint/publish steps for those artifacts are dropped. VEX no longer skips agent patches for ecosystems without hooks; the ecosystem_not_setup path is retired.

CLI UX (also breaking where noted): help is grouped by task; -h is shortened and legacy scan --apply / --vendor flags are hidden. Human warnings drop (code) prefixes; copy uses hosted instead of redirect. Hosted/vendored get auto-picks patches with no prompt (like scan). list on an empty project exits 0 with a scan nudge. get / rollback --one-off usage errors exit 2. Shared cancel and paid-plan strings; unified Next steps for hosted/vendored runs.

Docs (README “Upgrading from setup”, CHANGELOG) and workflows (ci.yml matrix cleanup, alias-path grep removal) reflect the above.

Reviewed by Cursor Bugbot for commit 2374af1. Configure here.

`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
- `-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
`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
@mikolalysenko

Mikola Lysenko (mikolalysenko) commented Sep 27, 2026 •

Copy link
Copy Markdown
Collaborator Author

CI failure summary. Current commit: f200524.

Also failing on the base branch (release/v5-prerelease @ 8ae7dc3, CI run 36352437716), so not caused by this PR:

  • e2e_redirect_cargo_build (test on all OSes, test-release, coverage). Two cases, cargo_hosted_fresh_checkout_fetch_pulls_patched_crate_and_vex_verifies and cargo_get_uuid_hosted_fresh_checkout_fetch, fail at step (3): vex --offline with the ledgers deleted exits 0, but the test expects exit 1 / record_unavailable. It reproduces locally on an untouched copy of the base branch. This is hosted-ledger/VEX code, which WS1 (ledger-free hosted mode) rewrites. No fix to port yet.
  • covgap_commands_scan_mod on test (windows-latest). The base's Windows job fails on the same target. It passes on Linux and macOS.

Fixed in this PR:

  • install-proof (vlt compatibility, every version) failed in vlt_pinned_matrix_agent_get_and_remove. The test ran plain get <uuid>, which now defaults to hosted. c3d4b38 adds --mode agent.
  • coverage failed in covgap_commands_remove because two assertions still expected the old wording ("hosted redirect ledger" → "hosted ledger"). Fixed in f200524.

Likely flaky, to be re-run once:

  • e2e_vlt on test (ubuntu-latest). It passes on macOS and locally, and its one test that doesn't need vlt installed has a 3.5 s timing check.
  • native (macos-latest, 1.3.0) (bun compatibility). Only hosted legs fail, alongside two 180 s bun install timeouts. Every other bun job that finished passed, including Ubuntu on 1.3.0.

Generated by Claude Code

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

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

v5 review at f200524f — removing install-hook setup is the largest concrete simplification in this stack. Keep that deletion and the explicit CI apply escape hatch.

A few interface changes would make this a simpler product, beyond hiding flags:

  • Make an empty project a successful empty list. I built this head and checked both forms: human output says “No patches in this project” but exits 1; --json exits 1 with manifest_not_found. Absence of an agent manifest is normal for hosted v5. Return one consistent empty result in both renderers; reserve failure for unreadable/corrupt state. This is an intentional v5 contract improvement, not a claim that this PR introduced the old exit code.
  • Fix the primary help's command grouping. It calls get and rollback “older agent-mode commands”, while get defaults to hosted and rollback is needed to undo hosted wiring. Describe the lifecycle by user intent, with agent-specific commands in their own group. Short help currently keeps --prune but hides --cwd, --ecosystems, and --offline; prioritize the hosted workflow's useful controls.
  • Consolidate results before rendering. The deferred scan/get JSON-envelope work is worth doing in this major release: one typed operation result, one exit-status rule, human and JSON renderers. The current split preserves a large amount of branching and lets their stories drift.
  • Make the removal actionable for existing users. Include a short upgrade recipe for existing npm/Composer scripts and installed Python/Bundler hooks, alongside the frozen-package notices. Keep explicit apply instructions for users retaining agent mode.

Validation: built this head; exercised root/scan help and empty list in human/JSON modes. I have not run the full compatibility matrix.

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
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Thanks for the review. c4f3625 makes three of the four changes. I've held back the fourth and propose a plan for it below.

  • Empty list: a project with no manifest and no ledger record now exits 0. Human output prints No patches in this project. Run \socket-patch scan`., and --jsonprints the success envelope withevents: []. Only an unreadable or invalid manifest (manifest_unreadable/manifest_invalid`) still exits 1. CLI_CONTRACT, README and CHANGELOG are updated to match.

  • Help: the root help now groups commands by what you're doing:

    • patch: scan, get, list
    • undo: remove, rollback
    • ship: vex, vendor
    • agent mode: apply, repair

    The subcommand list and the README command reference follow the same order, and the "older agent-mode commands" wording is gone. -h now shows --cwd, --ecosystems and --offline, and scan --prune moves to --help.

  • Upgrade recipe: the README has a new "Upgrading from setup" section.

    • Step 1 is choosing a mode. To move to hosted, run rollback, then scan, then commit. To keep agent mode, run apply in CI after every install.
    • Step 2 lists the exact hook to delete for each ecosystem: the package.json postinstall/dependencies command, the Composer script entries, socket-patch[hook] plus pip uninstall socket-patch-hook, and the Bundler plugin block, directory and stamp.
    • The CHANGELOG and both frozen package READMEs link to it.

Not done: one result type for scan/get, with human and JSON renderers. I agree it belongs in v5, but I think it needs its own PR. scan and get build their JSON ad hoc in about 120 places across roughly 24k lines. About 1,350 test assertions read those shapes (redirect, patches, scannedPackages, errorCode, …), and many of them are in real-toolchain suites I can't run locally. The pipenv, poetry and pdm backtest scripts read those shapes too, and socket-cli and depscan probably do as well. Proposed plan:

  1. One typed result. Add a single OperationResult for scan and get, with one exit-status rule. Render today's JSON and human output from it, byte-for-byte unchanged, so the existing tests prove nothing drifted.
  2. Switch the JSON. Move scan and get onto json_envelope in one contract-breaking step. Update CLI_CONTRACT, the CHANGELOG and the backtest scripts in the same commit, and coordinate the socket-cli and depscan changes for the same release.

If you'd rather have it in this PR anyway, say so and I'll start on step 1 here.


Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Restack request from the v5 coordinator (please act now).

#280 (ledger-free hosted) is now merged into release/v5-prerelease as 686e5fb4. Please merge origin/release/v5-prerelease into this branch, resolve the conflicts, get CI to "base-inherited reds only", and mark the PR ready for review (not draft) when done.

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Owner decision (via coordinator): land this PR without the shared scan/get result type. Do that as a follow-up PR against release/v5-prerelease once the stack has landed. Priority now: merge the base (#280), resolve conflicts, get green, and mark ready.

…setup-and-ui

Conflicts resolved toward #280's model: no hosted ledger, rollback/remove/
vendor restore hosted pins via patch::redirect::upstream. This branch's
changes stay on top: `setup` removed, human text says "hosted" and drops
warning codes, one numbered Next steps block, empty `list` exits 0 (the
contested-wiring error from #280 still exits 1), shared cancel line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PQaKzoW5dSw9u5pgAAvVRj
… 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
@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review September 28, 2026 11:29
@mikolalysenko

Mikola Lysenko (mikolalysenko) commented Sep 28, 2026 •

Copy link
Copy Markdown
Collaborator Author

Ready for review. Head: d951455e8e7d7f657cdbbd2df614252790ac30d7, restacked on release/v5-prerelease @ 06437d2 (#283, merged into this branch as d951455).

Merging #283:

CI on d951455: every failure is one the base branch also has.

Follow-ups, not blocking (also listed in the PR body):

  1. A shared scan/get result type plus json_envelope output, as a follow-up PR once the stack lands.
  2. F15: decide whether to delete the frozen hook wheel and Bundler plugin sources and yank the published packages.
  3. F15: remove the parse-only SetupConfig leftover once older-manifest handling is decided.

I'll re-merge the base when #281 lands.


Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

#283 landed on release/v5-prerelease as 06437d2; please merge origin/release/v5-prerelease again, resolve conflicts, get green, and keep it ready.


Generated by Claude Code

Take #283's deletion of repair_vendor.rs and its ledger-rebuild tests.
Keep this PR's help grouping in the README command table with #283's
"re-vendor" wording, drop the setup-only `ecosystem_not_setup` row
from CLI_CONTRACT, and keep vendor advisories code-free in human output.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PQaKzoW5dSw9u5pgAAvVRj
…etup-and-ui

Take the base's configuration.md deferred-defaults paragraph, which
already accounts for `setup` being removed in v5.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PQaKzoW5dSw9u5pgAAvVRj
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
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

#281 landed on release/v5-prerelease as 73c0c4f; please merge origin/release/v5-prerelease again, resolve conflicts, get green, and keep it ready.


Generated by Claude Code

Keep this branch's deletion of the setup-only package_json module
(#281 had only repointed find.rs at the format registry), and take
#281's registry-backed npm_family in constants.rs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PQaKzoW5dSw9u5pgAAvVRj
@mikolalysenko
Mikola Lysenko (mikolalysenko) merged commit f6bdad5 into release/v5-prerelease Sep 28, 2026
515 of 523 checks passed
@mikolalysenko
Mikola Lysenko (mikolalysenko) deleted the v5/remove-setup-and-ui branch September 28, 2026 19:19
Mikola Lysenko (mikolalysenko) pushed a commit that referenced this pull request Sep 28, 2026
Brings in #279, which removes `setup` and streamlines the patch UI.
Scan keeps `selection_args`, which `get` now calls again. The exit-2
row of the CLI contract keeps the removed-`setup` wording and the
v5.0 socket.yml and SOCKET_MAX_NEW_PATCHES usage errors.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko) pushed a commit that referenced this pull request Sep 28, 2026
Brings in #279 (setup removed, patch UI streamlined). Conflicts:
- core lib.rs keeps `policy` and drops `setup`.
- scan keeps policy-aware selection (`select_accessible`) and the
  base's `selection_args`, which `get` now uses.
- CLI_CONTRACT exit-code rows take the base's text plus the
  socket.yml rows.

Claude-Session: https://claude.ai/code/session_01BKsyzefGhAnPkYmXCwq3H3
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko) pushed a commit that referenced this pull request Sep 28, 2026
The staged-rollout and socket.yml flags pushed scan's short help past
nine options once #279 trimmed -h. --max-new-patches and
--no-socket-yml now show only in --help and parse as before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko) pushed a commit that referenced this pull request Sep 28, 2026
…to v5/one-hosted-engine

setup.rs goes, with the setup-only fold_vendor_records helper this
branch had moved onto the Ledgers view; vendor_record_is_unowned has no
callers left here (list, vex and scan read Ledgers), so it goes too.
#279's hosted wording lands where this branch moved the code: the pnpm
trust guidance in core hosted::guidance, the record_fetch_failed and
rush repo-state texts in core hosted::engine (shared by the memory
engine), and the human warning/summary text in the CLI scan/hosted.rs,
which also gets the one-line npm allow-remote note and its test. The
CHANGELOG owner-rule entry drops its `setup --check` clauses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KHZ8uzdXfkG2zH8ZYDG8ju
Mikola Lysenko (mikolalysenko) added a commit that referenced this pull request Sep 29, 2026
* 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 rollout planner for capped scans

A new core module plans a capped scan: rows that already carry a
patch always go through, NEW packages are admitted most critical
first until the budget is spent, and the rest are deferred with
their rank. The order is total and uses no dates, so repeated scans
on an unchanged repo land the same patches and converge.

ranking gains search_result_supersedes, the by-package twin of
batch_supersedes, so ALREADY vs UPGRADE is judged on the same
records that pick the patch.

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>

* Cap new patches per scan, most critical first

scan gains --max-new-patches <N|none> (env SOCKET_MAX_NEW_PATCHES).
Each selected package is classified against the recorded state
(manifest, hosted pins, vendor ledger): packages that already carry a
patch always go through, and an already-applied patch is kept unless
the selection really supersedes it, so re-scans never swap patches.
Packages getting their first patch are admitted most severe first
until the budget is spent; the rest are deferred to the next scan.

Hosted, vendored and agent runs decide eligibility with their own
write-free checks before any budget is spent, so a patch that cannot
land never holds a slot. Several PATH directories share one budget.
--json reports a rollout block, deferred rows show up in
redirect.skipped[] as rollout_deferred, and human output adds a
Rollout line and a Next up list. updates[] now uses the by-package
records.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Test the scan cap end to end in every mode

Nine packages under a cap of three roll forward three per run in
hosted, agent and vendored mode, most severe first, and a fourth run
changes nothing. The dry run predicts the wet run, upgrades land with
a cap of zero, patches that cannot land hold no slot, a failed lookup
admits nothing new, and several project directories share one budget.

A hosted pin on a patch server scan does not recognize still counts
as applied when the lockfile names its patch uuid, so the rollout
cannot stall on a missing --patch-server-url.

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>

* Cap new patches in the in-memory hosted engine

The napi engine and hosted-bundle take maxNewPatches (a count or
"none"), maxNewPatchesCap (a server ceiling that only tightens) and
inFlightPatches (ranked first). One budget spans every project root:
the engine classifies each root against its committed manifest,
vendor ledger and the hosted pins its lockfiles name, plans once
after every root's write-free checks, and rewrites a root again
without its deferred rows. Results gain a session-level rollout
block and ProjectResult.deferred; deferred rows also appear in
skipped[] as rollout_deferred.

Parity tests hold disk and memory to the same rollout and redirect
blocks run after run until converged, and show memory's run-wide
budget beside disk's per-directory one.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Document the per-run cap on new patches

CLI_CONTRACT.md gains the limit's semantics (classification,
eligibility, unit, budget scope, order, convergence), the flag and env
rows, the rollout block, rollout_deferred and a jq recipe; "Which
patch gets selected" now describes the by-package supersession and the
separate cross-package order. README adds a gradual-rollout task and
the flag row; CHANGELOG adds both entries. The design doc records the
gaps B decided.

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>

* Fix rollout review findings

The recorded view is now an index built once per project and keyed
like discovery (case-folded nuget/composer, PEP 503 pypi names), and it
keeps every hosted pin, so a case-folded pin or a pair of pinned
qualifier twins no longer reads as NEW or as a phantom upgrade. The
hosted gate uses key sets instead of linear scans, takes the apply lock
for rows an earlier directory admitted, and error envelopes drop the
rollout block. The in-memory engine keeps the first pass's skips when
it rewrites a root again, and a root whose lookups all failed freezes
new admissions. Human output words dry runs, incomplete lookups, zero
caps and shared budgets correctly, an explicit flag no longer reads
the env value, and the docs say the socket.yml layer arrives with the
patch policy. New tests cover reference failures, upgrades under a cap
of zero in hosted mode, recorded packages with failed lookups, and
stronger dry-run and parity checks.

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>

* Match zero-cap test to dry-run wording

The dry-run tense fix changed the deferred line to "would be
deferred"; the unit test still expected the wet-run wording.

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

* v5: keep scan -h within the option budget

The staged-rollout and socket.yml flags pushed scan's short help past
nine options once #279 trimmed -h. --max-new-patches and
--no-socket-yml now show only in --help and parse as before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants