You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 0ee6dd4
Browse filesBrowse the repository at this point in the historyBrowse files
`remove pkg:pypi/typing_extensions@4.7.1` and the same `rollback`
exited 1 with "No patch found" and left the patch in place, although
`get` had applied it with that exact identifier. Patches are stored
under the canonical name (`typing-extensions`), but users and scripts
type the name the way pyproject.toml or requirements.txt spells it
(`typing_extensions`, `Jinja2`, `ruamel.yaml`).
purl equality now also treats two pypi purls as equal when their names
share a PEP 503 canonical form, next to the existing composer rule, so
the manifest, hosted-pin and vendor-ledger matchers that `remove` and
`rollback` use all accept every spelling. Versions and qualifiers still
have to match exactly. CLI_CONTRACT.md documents the rule for both
commands.
Fixes#1024
Assisted-by: Claude Code:claude-opus-5-5
Copy file name to clipboardExpand all lines: crates/socket-patch-cli/CLI_CONTRACT.md
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -103,7 +103,7 @@ Beyond the globals above, each subcommand defines a small set of local arguments
103
103
|`scan`|`--min-severity <critical\|high\|medium\|moderate\|low\|none>`|`SOCKET_MIN_SEVERITY`| (v5.0) Severity floor for the patch a package may receive (worst advisory severity; unknown severity is skipped whenever a floor is set). Beats `patches.minSeverity`; the flag beats the env; `none` lifts the floor. A malformed value is exit 2. |
104
104
|`get`, `scan`|`--all-releases`|`SOCKET_ALL_RELEASES`| Download patches for every release/distribution variant of a matched package — PyPI wheel/sdist (`artifact_id`), RubyGems (`platform`), Maven (`classifier`) — not just the one(s) matching the locally-installed distribution. On `scan` this makes the stored manifest portable across environments (e.g. cross-platform CI caches). On `get` (v3.6) it ALSO disables the coarse installed-**version** narrowing of CVE/GHSA fan-outs (see "get --mode and installed narrowing"): every found version's patch is fetched, installed or not |
105
105
|`get`| positional `identifier`; `--id` / `--cve` / `--ghsa` / `--package` (`-p`); `--save-only` (alias `--no-apply`); `--mode <hosted\|vendored\|agent>`|`SOCKET_SAVE_ONLY`| Patch lookup + consumption mode (v3.6). `--mode` reuses scan's value enum (same hidden value aliases `host`/`redirect`/`vendor`; deliberately no env binding, matching scan). Default (v5.0): `hosted`, like scan; `agent` (save + apply in place) when `--save-only` or `--global`/`--global-prefix` is given. An explicit `--mode hosted\|vendored` with `--global`/`--global-prefix` is a usage error (exit 2, scan's wording: global installs have no project lockfile). An explicit `--save-only` conflicts with `--mode hosted\|vendored` — rejected with **exit 1** via get's established self-enforced-conflict style (unlike scan's exit-2 mode conflicts; see the exit-code table) |
106
-
|`remove`| positional `identifier`; `--skip-rollback`; `--preserve-state` (v5.0) |`SOCKET_SKIP_ROLLBACK`, `SOCKET_PRESERVE_STATE`| Manifest entry removal. `--preserve-state` is the single-patch twin of `rollback --preserve-state`: restore the tree and unwind the identifier's vendored/hosted wiring, but keep the manifest entry, the vendored artifact + ledger entry, and skip all GC. Combining it with `--skip-rollback` is a self-enforced usage error (exit 2): one flag keeps the tree and drops the state, the other restores the tree and keeps the state — together they select the do-nothing quadrant ("the combination would be a no-op: nothing would change"). The conflict fires whether either flag is spelled on the command line or sourced from its env var |
106
+
|`remove`| positional `identifier`; `--skip-rollback`; `--preserve-state` (v5.0) |`SOCKET_SKIP_ROLLBACK`, `SOCKET_PRESERVE_STATE`| Manifest entry removal. The identifier matches like a `rollback` target (a PyPI name compares by its PEP 503 canonical form). `--preserve-state` is the single-patch twin of `rollback --preserve-state`: restore the tree and unwind the identifier's vendored/hosted wiring, but keep the manifest entry, the vendored artifact + ledger entry, and skip all GC. Combining it with `--skip-rollback` is a self-enforced usage error (exit 2): one flag keeps the tree and drops the state, the other restores the tree and keeps the state — together they select the do-nothing quadrant ("the combination would be a no-op: nothing would change"). The conflict fires whether either flag is spelled on the command line or sourced from its env var |
107
107
|`rollback`| optional variadic positional `targets` (PURL \| UUID \| path glob); `--preserve-state` (v5.0) |`SOCKET_PRESERVE_STATE`| Rollback scope. Multiple targets union. A token becomes a path glob ONLY when it is path-SHAPED — contains a separator (`/` or `\`) or a glob metacharacter (`*?[`), or starts with `./`, or is absolute; a `pkg:` prefix is a PURL and every other bare word keeps identifier (PURL/UUID) semantics, so a mistyped identifier or truncated UUID stays a safe exit-1 "No patch found matching identifier: X" (with a hint suggesting `./X` or `X/**` for directory targeting) instead of silently becoming a path scope. An unparseable glob is a usage error (exit 2) |
|`repair`|`--download-only`|`SOCKET_DOWNLOAD_ONLY`| Repair-specific cleanup mode (mutually exclusive with `--offline`; combining them is a usage error, exit 2) |
`rollback [TARGET]...` — zero or more targets, unioned. `pkg:` tokens are PURLs (base purl matches every release variant; qualified purl exact), other identifier-shaped tokens are UUIDs, and only **path-shaped** tokens (separator, glob metachar `*?[`, `./` prefix, or absolute) are path globs — see the per-subcommand args table for the safety rationale. Identifier matching runs across ALL THREE sources (a hosted pin matches by purl or by the patch uuid in its hosted URL); an identifier matching nothing anywhere is the familiar exit-1 error. Path globs use the same matcher as `scan [PATHS]` (ancestor rule, `require_literal_separator`, absolute-only outside `--cwd`, Windows case-insensitive): installed copies of every candidate purl are discovered and purls with ≥ 1 matching copy are selected. Scoping sentences (shared with scan):
888
+
`rollback [TARGET]...` — zero or more targets, unioned. `pkg:` tokens are PURLs (base purl matches every release variant; qualified purl exact; PyPI names compare by their PEP 503 canonical form, so `pkg:pypi/typing_extensions@4.7.1` selects `pkg:pypi/typing-extensions@4.7.1`), other identifier-shaped tokens are UUIDs, and only **path-shaped** tokens (separator, glob metachar `*?[`, `./` prefix, or absolute) are path globs — see the per-subcommand args table for the safety rationale. Identifier matching runs across ALL THREE sources (a hosted pin matches by purl or by the patch uuid in its hosted URL); an identifier matching nothing anywhere is the familiar exit-1 error. Path globs use the same matcher as `scan [PATHS]` (ancestor rule, `require_literal_separator`, absolute-only outside `--cwd`, Windows case-insensitive): installed copies of every candidate purl are discovered and purls with ≥ 1 matching copy are selected. Scoping sentences (shared with scan):
889
889
890
890
* **A target that selects nothing is an error on `rollback` (exit 1) and an empty scan on `scan` (exit 0).** Each rollback path pattern must select at least one patched package; the error names the pattern and the reachability rule.
891
891
* **Path targets select installed copies; entries with no installed copy are reachable only by identifier or unscoped runs.**
0 commit comments