From 4f6746b0e7e46b5d45a857592c603d6ceb4769f9 Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Fri, 9 Oct 2026 12:27:50 -0400 Subject: [PATCH] docs: complete v5 package targeting migration guidance --- crates/socket-patch-cli/CLI_CONTRACT.md | 38 +++++++++++++++++- docs/migrating-to-v5.md | 51 +++++++++++++++++++++++++ docs/usage.md | 13 ++++--- 3 files changed, 96 insertions(+), 6 deletions(-) diff --git a/crates/socket-patch-cli/CLI_CONTRACT.md b/crates/socket-patch-cli/CLI_CONTRACT.md index bb4592f4b..4ba3de8da 100644 --- a/crates/socket-patch-cli/CLI_CONTRACT.md +++ b/crates/socket-patch-cli/CLI_CONTRACT.md @@ -36,7 +36,43 @@ Rows are in `--help` order (v5.0): the hosted/vendored workflow (`scan` → `vex **Bare-UUID fallback.** `socket-patch ` is rewritten to `socket-patch get `, also when root-position flags come first (`socket-patch --json `); a UUID after a subcommand name is that subcommand's operand. The UUID shape checked is the standard 8-4-4-4-12 hex pattern (case-insensitive), the target grammar's. -**Target grammar (v5.0).** `get`, `remove`, `rollback` and the bare-UUID fallback classify their package/patch token with one parser (core `utils::target`): a UUID; `CVE-…` / `GHSA-…` (case-insensitive); a `pkg:` purl (with a version: that release, a base purl covering every release variant and a `?qualified` one exactly one; without a version: every version); otherwise an **exact** package name — full name or last segment, case-insensitive, PEP 503 for PyPI — the same matcher as `scan --package` and `socket.yml`. A name never matches by prefix or substring, and a Go major-version suffix (`v2`) is never a name. **Ambiguous names**: `get`, `remove` and `rollback` act on one package per name, so a name whose last-segment rule reaches several packages (`core` → `@angular/core` and `@babel/core`; the same name in two ecosystems) is refused with exit 1 before anything is searched or changed — `"core" is ambiguous: it names pkg:npm/@angular/core, pkg:npm/@babel/core; use the full name or a purl` (`remove --json`: `errorCode: "ambiguous_target"`; `get` / `rollback --json`: `{status: "error", error}`). Several versions of one package are not ambiguous. `scan --package` and `socket.yml` keep selecting every package the name reaches. `get ` searches every installed version of the matched name (within `--ecosystems`) and prints `Matched: …` on stderr; with no exact match it is `no_match` (exit 0, no API call) and, in human mode, suggests up to five near names (`Did you mean: …?`) without acting on them. `remove` / `rollback` accept the same names and versionless purls against manifest records, vendor-ledger entries and hosted pins; CVE/GHSA ids match no record there. A name containing `/` (composer `vendor/pkg`, a go module path) is path-shaped, so `rollback` first tries it as a name: when it selects a recorded or hosted patch it is a target, otherwise a path glob. +**Target grammar (v5.0).** `get`, `remove`, `rollback` and the bare-UUID +fallback classify their package/patch token with one parser (core `utils::target`): +a UUID; `CVE-…` / `GHSA-…` (case-insensitive); a `pkg:` purl (with a version: +that release, a base purl covering every release variant and a `?qualified` one +exactly one; without a version: every version); otherwise an **exact** package +name — full name or last segment, case-insensitive, PEP 503 for PyPI — the same +matcher as `scan --package` and `socket.yml`. A name never matches by prefix or +substring, and a Go major-version suffix (`v2`) alone never selects a module. + +A full-name match takes precedence over last-segment matches: `lodash` selects +only `lodash` when `@types/lodash` is also present. **Ambiguous names**: `get`, +`remove` and `rollback` act on one package per name, so a name that still reaches +several packages (`core` → `@angular/core` and `@babel/core`; the same name in two +ecosystems) is refused with exit 1 before any patch search or mutation. The +message lists versionless purls and a remedy: `"core" is ambiguous: it names +pkg:npm/@angular/core, pkg:npm/@babel/core; use the full name or a purl`. All three +commands report `status: "error"` and `error.code: "ambiguous_target"` under +`--json`. Several versions of one package are not ambiguous. `scan --package` and +`socket.yml` keep selecting every package the name reaches. + +`get ` searches every installed version of the matched name (within +`--ecosystems`) and prints `Matched: …` on stderr. With no exact match in a +nonempty inventory it is `no_match` (exit 0, no patch-search API call) and, in +human mode, suggests up to five near names (`Did you mean: …?`) without acting on +them. An empty inventory is `no_packages` (exit 0). `--ecosystems` also filters +advisory/purl search results and UUID selection before any patch is written; a +UUID outside the selected ecosystems is `not_found` (exit 0). + +`remove` / `rollback` accept the same names and versionless purls against manifest +records, vendor-ledger entries and hosted pins; CVE/GHSA ids match no record +there. An npm scoped name (`@babel/core`) is always a package target. A relative +name containing `/` (composer `vendor/pkg`, a Go module path), without glob +metacharacters or `.` / `..` path segments, is path-shaped, so `rollback` first +tries it as a name: when it selects a recorded or hosted patch it is a target, +otherwise a path glob. Use `./vendor/pkg` or `'vendor/pkg/**'` to explicitly select +installed copies by path. See the [migration guide](../../docs/migrating-to-v5.md#package-targeting) +for examples and script migration guidance. **Root `--update` flag.** `socket-patch --update [VERSION]` updates the binary itself from GitHub Releases. It is a root flag, not a subcommand: argv is rewritten (the same mechanism as the bare-UUID fallback) onto an internal hidden subcommand whose name carries no stability guarantee — script the flag, never the internal name. Combining the flag with a subcommand (`socket-patch --update scan`) is a usage error (exit 2). Full contract: [Self-update contract](#self-update-contract-socket-patch---update). diff --git a/docs/migrating-to-v5.md b/docs/migrating-to-v5.md index 25b12b257..f61436cfe 100644 --- a/docs/migrating-to-v5.md +++ b/docs/migrating-to-v5.md @@ -36,6 +36,57 @@ existing scripts against the new CLI; the [changelog](../CHANGELOG.md) and `SOCKET_ORG_SLUG` to get org patches. The org is resolved once per run, so an embedded `--vex` no longer resolves it again. +## Package targeting + +`get ` now selects an exact package name instead of automatically choosing +the nearest fuzzy match. For example, `get yaml` never selects `yaml-ast-parser`; +near names are suggestions only. It queries every installed version of the +selected package, including older nested copies. If a script should select only +one release, use a versioned PURL such as `pkg:npm/lodash@4.17.20`. + +`get`, `remove`, and `rollback` share these package/patch target forms: + +| Target | Meaning | +| --- | --- | +| Package name, such as `lodash` or `@babel/core` | Every installed version for `get`; every recorded version for `remove` / `rollback`, including vendor records and hosted pins | +| Versionless PURL, such as `pkg:npm/lodash` | Select the package across versions, with an explicit ecosystem | +| Versioned PURL, such as `pkg:npm/lodash@4.17.20` | Select that release; an unqualified PURL covers its release variants, while a qualified PURL selects one variant | +| Patch UUID | Select one patch; `socket-patch ` is shorthand for `get `, including after root flags such as `--json` | +| CVE or GHSA ID | Search by advisory with `get`; these IDs do not select records for `remove` / `rollback`, so use the package PURL or patch UUID there | + +Name matching uses the same case-insensitive rules as `scan --package`, including +PyPI normalization of `.`, `_`, and `-`. A full-name match takes precedence: +`get lodash` selects `lodash` when both it and `@types/lodash` are installed. A +last-segment name such as `core` is accepted only when it identifies one package. +If it reaches both `@angular/core` and `@babel/core`, `get`, `remove`, and `rollback` +refuse it with exit 1 and `error.code: "ambiguous_target"` under `--json`, before +changing any patches. The message lists exact versionless PURLs to use instead. +The same name in two ecosystems also needs a PURL to disambiguate it. Multiple +versions of one package are not ambiguous. `scan --package` and `socket.yml` +continue to select every package a name matches. Go major-version suffixes such +as `v2` alone do not select modules; use the full module path or PURL. + +`get --ecosystems` now scopes package-name discovery and filters every search +result and UUID selection before any patch is written, in every mode. A UUID +outside the selected ecosystems returns `status: "not_found"` with exit 0. A +name absent from a nonempty package inventory returns `status: "no_match"` with +exit 0, without searching for or applying a suggested package. An empty inventory +returns `status: "no_packages"`. Scripts should inspect these statuses instead of +assuming exit 0 means a patch was selected. + +`rollback` also accepts path globs. An npm scoped name such as `@babel/core` is +always a package target. A slash-containing name such as `monolog/monolog` is a +package target when it selects a recorded patch or hosted pin; otherwise it is +treated as a path glob. Use an explicit path such as `./monolog/monolog` or a +quoted glob such as `'node_modules/**'` to select installed copies by location. + +```sh +socket-patch get yaml --ecosystems npm --dry-run +socket-patch get pkg:npm/lodash@4.17.20 --dry-run +socket-patch remove pkg:npm/@babel/core --dry-run +socket-patch rollback pkg:composer/monolog/monolog --dry-run +``` + ## JSON output Every `--json` failure now reports its top-level `error` as an object, diff --git a/docs/usage.md b/docs/usage.md index 6a607be25..9a5f35001 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -36,9 +36,12 @@ Replace the example identifiers with the advisory or package you need. `get` als accepts a patch UUID or an exact package name, which covers every installed version of that package. `remove` and `rollback` take the same names. A short name that names several packages (`core` for `@angular/core` and `@babel/core`) is refused: -use the full name or a purl. `get` defaults to hosted mode; `--save-only` and -global targeting default to agent mode instead. Hosted and vendored `get` do not -prompt. Agent-mode searches can offer an interactive choice. +use the full name or a purl. Near names are only suggestions, and `--ecosystems` +scopes both `get` searches and UUID selection. See the +[v5 targeting changes](migrating-to-v5.md#package-targeting) for target forms, +ambiguity rules, and no-match statuses. `get` defaults to hosted mode; `--save-only` +and global targeting default to agent mode instead. Hosted and vendored `get` do +not prompt. Agent-mode searches can offer an interactive choice. For each package version, automatic selection prefers the highest severity among downloadable patches, then the most distinct advisories fixed, then the newest @@ -221,8 +224,8 @@ The removed `setup` command is covered in the [migration guide](migrating-to-v5. | Command | Effect | | --- | --- | | `list` | Show agent records, vendor records, and hosted pins; an empty project succeeds | -| `rollback [PURL\|UUID\|PATH]...` | Restore selected patches, or all patches when no target is given, and remove their local state | -| `remove ` | Restore and remove one patch | +| `rollback [NAME\|PURL\|UUID\|PATH]...` | Restore selected patches, or all patches when no target is given, and remove their local state | +| `remove ` | Restore and remove matching patches; a name or versionless PURL selects every recorded version | | `vendor --revert` | Undo vendoring from its recorded edits and remove the vendored artifacts | | `repair` | Restore missing patch data or damaged vendored artifacts and clean unused data | | `scan --mode agent --prune` | Patch discovered packages and remove records for dependencies that left the project |