Skip to content

Commit b2aea01

Browse files
committed
Merge remote-tracking branch 'origin/release/v5-prerelease' into v5/waste-w4
2 parents 7ce6840 + 73c0c4f commit b2aea01

91 files changed

Lines changed: 9935 additions & 3418 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/bun-compatibility.yml‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,11 +95,11 @@ on:
9595
permissions:
9696
contents: read
9797

98-
# Supersede stale PR runs. The `main` guard is load-bearing: main runs are the
99-
# ONLY rust-cache writers (save-if), so they must never be cancelled mid-save.
98+
# Supersede stale PR runs only: main runs are the ONLY rust-cache writers
99+
# (save-if), so push, dispatch and schedule runs are never cancelled mid-save.
100100
concurrency:
101101
group: bun-patch-${{ github.event.pull_request.number || github.ref }}
102-
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
102+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
103103

104104
env:
105105
CARGO_PROFILE_DEV_DEBUG: '0'

‎.github/workflows/ci.yml‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@ on:
1717
permissions:
1818
contents: read
1919

20-
# Supersede stale runs on force-push / rapid PR updates. The `main` guard is
21-
# load-bearing: main runs are the ONLY rust-cache writers (save-if), so they
22-
# must never be cancelled mid-save.
20+
# A newer push to the same PR supersedes its older run; nothing else is
21+
# cancelled. Push, dispatch and schedule runs always finish: main runs are
22+
# the ONLY rust-cache writers (save-if) and must not die mid-save, and a
23+
# dispatched base-branch run is the base's only CI verdict.
2324
concurrency:
24-
group: ci-${{ github.ref }}
25-
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
25+
group: ci-${{ github.event.pull_request.number || github.ref }}
26+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
2627

2728
jobs:
2829
clippy:

‎.github/workflows/go-compatibility.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ permissions:
3030

3131
concurrency:
3232
group: go-compat-${{ github.event.pull_request.number || github.ref }}
33-
cancel-in-progress: true
33+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
3434

3535
env:
3636
SOCKET_NO_CONFIG: '1'

‎.github/workflows/pdm-compatibility.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ permissions:
4848

4949
concurrency:
5050
group: pdm-compat-${{ github.event.pull_request.number || github.ref }}
51-
cancel-in-progress: true
51+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
5252

5353
env:
5454
SOCKET_NO_CONFIG: '1'

‎.github/workflows/poetry-compatibility.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ permissions:
4040

4141
concurrency:
4242
group: poetry-compat-${{ github.event.pull_request.number || github.ref }}
43-
cancel-in-progress: true
43+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
4444

4545
env:
4646
SOCKET_NO_CONFIG: '1'

‎.github/workflows/vlt-compatibility.yml‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,11 +117,11 @@ on:
117117
permissions:
118118
contents: read
119119

120-
# Supersede stale PR runs; main runs are the only rust-cache writers, so they
121-
# are never cancelled mid-save.
120+
# Supersede stale PR runs only: main runs are the only rust-cache writers, so
121+
# push, dispatch and schedule runs are never cancelled mid-save.
122122
concurrency:
123123
group: vlt-compat-${{ github.event.pull_request.number || github.ref }}
124-
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
124+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
125125

126126
env:
127127
CARGO_PROFILE_DEV_DEBUG: '0'

‎crates/socket-patch-cli/CLI_CONTRACT.md‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -589,7 +589,10 @@ records and registry entries cannot be fetched offline — making zero network r
589589
included). A hosted wiring that discovery cannot attribute (a lock mentioning a recognized hosted
590590
uuid it rejected, or a pin with no lockfile) is refused with `hosted_wiring_contested` (exit 1,
591591
nothing touched) rather than ejecting a partial set; `rollback`, `remove` and `list` refuse the same
592-
way (`list` degrades to a warning when it can still list). `--vex` works as on the manifest-driven
592+
way (`list` degrades to a warning when it can still list). The grant token of an attributed pin's
593+
own URL is never contested wiring where the same file also names that pin's patch uuid (a uv
594+
pin's paired `pyproject.toml` `[tool.uv.sources]` entry, a vlt pin in a `vlt-lock.json` whose pins
595+
are withheld from the lock basis); any other unattributed uuid still is. `--vex` works as on the manifest-driven
593596
path. Without hosted pins the no-manifest no-op below is unchanged.
594597

595598
**Prebuilt vendor artifacts (`--vendor-source`)**: by default (`auto`) `vendor` first tries to

‎crates/socket-patch-cli/src/commands/get.rs‎

Lines changed: 8 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use socket_patch_core::api::types::{
1111
};
1212
use socket_patch_core::crawlers::fuzzy_match::fuzzy_match_packages;
1313
use socket_patch_core::crawlers::{CrawlerOptions, Ecosystem};
14+
use socket_patch_core::formats::pnpm::PnpmLock;
1415
use socket_patch_core::manifest::operations::{read_manifest, write_manifest};
1516
use socket_patch_core::manifest::schema::{
1617
PatchFileInfo, PatchManifest, PatchRecord, VulnerabilityInfo,
@@ -1498,47 +1499,6 @@ fn purl_has_version(purl: &str) -> bool {
14981499
})
14991500
}
15001501

1501-
/// Does the raw pnpm-lock text RESOLVE `name@version`? Boundary-anchored
1502-
/// probes over the three lock grammars — a plain `contains` collides on
1503-
/// version prefixes (`left-pad@1.3.0` matches inside
1504-
/// `left-pad@1.3.0-beta.1`), name suffixes (`pad@1.3.0` inside
1505-
/// `left-pad@1.3.0`), and unscoped-inside-scoped names (`name@1.0.0` inside
1506-
/// `@scope/name@1.0.0`). The needles cover v6/v9's `name@version` and v5's
1507-
/// `/name/version` key spellings; a match counts only when the preceding
1508-
/// char cannot extend the name (start/whitespace/quote, or a `/` delimiter
1509-
/// itself preceded by such a boundary) and the following char cannot extend
1510-
/// the version (so `:`, `'`, `(`, and v5's `_peer` suffix all accept).
1511-
/// Heuristic by design: a false negative degrades to a calm skip, a false
1512-
/// positive costs one grant request the rewriter's per-dep confirmation
1513-
/// then ignores.
1514-
fn pnpm_lock_resolves(text: &str, name: &str, version: &str) -> bool {
1515-
let version_boundary = |c: char| !(c.is_ascii_alphanumeric() || matches!(c, '.' | '-' | '+'));
1516-
let name_boundary = |c: char| matches!(c, ' ' | '\t' | '\n' | '\r' | '\'' | '"');
1517-
for needle in [format!("{name}@{version}"), format!("/{name}/{version}")] {
1518-
for (pos, _) in text.match_indices(needle.as_str()) {
1519-
let before_ok = match text[..pos].chars().next_back() {
1520-
None => true,
1521-
// v5/v6's leading key delimiter — legitimate only when the
1522-
// char before it is itself a boundary (otherwise this is a
1523-
// scoped `@scope/<name>` tail: a DIFFERENT package).
1524-
Some('/') => text[..pos - 1]
1525-
.chars()
1526-
.next_back()
1527-
.is_none_or(name_boundary),
1528-
Some(c) => name_boundary(c),
1529-
};
1530-
let after_ok = text[pos + needle.len()..]
1531-
.chars()
1532-
.next()
1533-
.is_none_or(version_boundary);
1534-
if before_ok && after_ok {
1535-
return true;
1536-
}
1537-
}
1538-
}
1539-
false
1540-
}
1541-
15421502
/// Outcome of the coarse installed-VERSION narrowing over a CVE/GHSA/PURL
15431503
/// search fan-out (see [`filter_to_installed_purls`]).
15441504
struct InstalledNarrowing {
@@ -1576,7 +1536,7 @@ struct InstalledNarrowing {
15761536
/// `yarn_pnp_unsupported`, not a false "not installed"). pnpm PnP skips
15771537
/// carry `pnpm_pnp_unsupported` in agent/vendored modes; hosted mode — the
15781538
/// refusal's own remedy — keeps the versions the raw pnpm-lock.yaml text
1579-
/// resolves ([`pnpm_lock_resolves`]), labels a judged miss
1539+
/// resolves ([`PnpmLock::resolves`]), labels a judged miss
15801540
/// `package_not_installed` like any other mode, and reserves the layout
15811541
/// code for an unreadable lock (no judgment possible).
15821542
///
@@ -1645,6 +1605,7 @@ async fn filter_to_installed_purls(
16451605
let pnpm_pnp_lock_text: Option<String> = (pnp_pnpm && mode == super::scan::ScanMode::Hosted)
16461606
.then(|| std::fs::read_to_string(common.cwd.join("pnpm-lock.yaml")).ok())
16471607
.flatten();
1608+
let pnpm_pnp_lock = pnpm_pnp_lock_text.as_deref().map(PnpmLock::parse);
16481609

16491610
let mut out = InstalledNarrowing {
16501611
kept: Vec::new(),
@@ -1674,8 +1635,8 @@ async fn filter_to_installed_purls(
16741635
// The pnpm PnP refusal's own remedy is the hosted lockfile
16751636
// rewrite — but only for versions the lock ACTUALLY resolves:
16761637
// keeping the whole fan-out would request grants for every
1677-
// version ever patched. Anchored probe over the raw lock text
1678-
// (see `pnpm_lock_resolves`); a hit is kept (the rewriter's
1638+
// version ever patched. The lock model's key probe
1639+
// (`PnpmLock::resolves`); a hit is kept (the rewriter's
16791640
// per-dep confirmation still decides). A judged MISS is a
16801641
// genuine "version not resolved" verdict — the layout blocked
16811642
// nothing — so it carries the same `package_not_installed` code
@@ -1684,9 +1645,9 @@ async fn filter_to_installed_purls(
16841645
let decoded = canon(&result.purl);
16851646
let coord = decoded.strip_prefix("pkg:npm/").unwrap_or(&decoded);
16861647
if mode == super::scan::ScanMode::Hosted {
1687-
match (pnpm_pnp_lock_text.as_deref(), coord.rsplit_once('@')) {
1688-
(Some(text), Some((name, version))) => {
1689-
if pnpm_lock_resolves(text, name, version) {
1648+
match (&pnpm_pnp_lock, coord.rsplit_once('@')) {
1649+
(Some(lock), Some((name, version))) => {
1650+
if lock.resolves(name, version) {
16901651
out.kept.push(result.clone());
16911652
continue;
16921653
}
@@ -3983,77 +3944,6 @@ pub(crate) fn base64_decode(input: &str) -> Result<Vec<u8>, String> {
39833944
mod tests {
39843945
use super::*;
39853946

3986-
/// The pnpm-PnP hosted lock probe must be boundary-anchored: plain
3987-
/// substring matching collides on version prefixes, name suffixes, and
3988-
/// unscoped-inside-scoped names.
3989-
#[test]
3990-
fn pnpm_lock_resolves_is_boundary_anchored() {
3991-
// v9/v6/v5 key spellings all resolve.
3992-
assert!(pnpm_lock_resolves(
3993-
"lockfileVersion: '9.0'\n\nsnapshots:\n\n left-pad@1.3.0:\n",
3994-
"left-pad",
3995-
"1.3.0"
3996-
));
3997-
assert!(pnpm_lock_resolves(
3998-
" /left-pad@1.3.0:\n resolution: {}\n",
3999-
"left-pad",
4000-
"1.3.0"
4001-
));
4002-
assert!(pnpm_lock_resolves(
4003-
" /left-pad/1.3.0:\n resolution: {}\n",
4004-
"left-pad",
4005-
"1.3.0"
4006-
));
4007-
// Peer-qualified keys still resolve: v9 `(peer)` and v5 `_peer`.
4008-
assert!(pnpm_lock_resolves(
4009-
" 'left-pad@1.3.0(react@18.0.0)':\n",
4010-
"left-pad",
4011-
"1.3.0"
4012-
));
4013-
assert!(pnpm_lock_resolves(
4014-
" /left-pad/1.3.0_react@18.0.0:\n",
4015-
"left-pad",
4016-
"1.3.0"
4017-
));
4018-
// Scoped names resolve in both quoted-v9 and v6 spellings.
4019-
assert!(pnpm_lock_resolves(
4020-
" '@scope/name@1.0.0':\n",
4021-
"@scope/name",
4022-
"1.0.0"
4023-
));
4024-
assert!(pnpm_lock_resolves(
4025-
" /@scope/name@1.0.0:\n",
4026-
"@scope/name",
4027-
"1.0.0"
4028-
));
4029-
4030-
// Version-prefix collision: 1.3.0 must NOT match 1.3.0-beta.1.
4031-
assert!(!pnpm_lock_resolves(
4032-
" left-pad@1.3.0-beta.1:\n",
4033-
"left-pad",
4034-
"1.3.0"
4035-
));
4036-
// Name-suffix collision: `pad` must NOT match inside `left-pad`.
4037-
assert!(!pnpm_lock_resolves(" left-pad@1.3.0:\n", "pad", "1.3.0"));
4038-
assert!(!pnpm_lock_resolves(" /left-pad/1.3.0:\n", "pad", "1.3.0"));
4039-
// Unscoped-inside-scoped: `name` must NOT match `@scope/name`.
4040-
assert!(!pnpm_lock_resolves(
4041-
" '@scope/name@1.0.0':\n",
4042-
"name",
4043-
"1.0.0"
4044-
));
4045-
assert!(!pnpm_lock_resolves(
4046-
" /@scope/name@1.0.0:\n",
4047-
"name",
4048-
"1.0.0"
4049-
));
4050-
// Absent version: never resolves.
4051-
assert!(!pnpm_lock_resolves(
4052-
" left-pad@1.3.0:\n",
4053-
"left-pad",
4054-
"2.0.0"
4055-
));
4056-
}
40573947
use socket_patch_core::api::types::{PatchFileResponse, VulnerabilityResponse};
40583948
use std::collections::HashMap;
40593949

@@ -5032,36 +4922,6 @@ mod tests {
50324922
);
50334923
}
50344924

5035-
// --- pnpm_lock_resolves: needle at byte 0 ------------------------------
5036-
// The boundary probe reads the char BEFORE the match; a match at the very
5037-
// start of the text has none (`None => true`). A regression that indexes
5038-
// `text[..pos - 1]` unconditionally would underflow/panic here.
5039-
5040-
#[test]
5041-
fn pnpm_lock_resolves_needle_at_start_of_text() {
5042-
// pos == 0, plain v9 spelling: no preceding char is a valid boundary.
5043-
assert!(pnpm_lock_resolves("left-pad@1.3.0:\n", "left-pad", "1.3.0"));
5044-
// pos == 0, v5/v6 `/name/version` and `/name@version` spellings: the
5045-
// leading `/` delimiter itself has nothing before it.
5046-
assert!(pnpm_lock_resolves(
5047-
"/left-pad/1.3.0:\n",
5048-
"left-pad",
5049-
"1.3.0"
5050-
));
5051-
assert!(pnpm_lock_resolves(
5052-
"/left-pad@1.3.0:\n",
5053-
"left-pad",
5054-
"1.3.0"
5055-
));
5056-
// Still boundary-checked at the start of text: a scoped tail whose
5057-
// name begins mid-token must NOT match.
5058-
assert!(!pnpm_lock_resolves(
5059-
"@scope/left-pad@1.3.0:\n",
5060-
"left-pad",
5061-
"1.3.0"
5062-
));
5063-
}
5064-
50654925
// --- write_all_patch_blobs ---------------------------------------------
50664926
// The per-patch fan-out over write_blob_entry: the FIRST bad entry must
50674927
// fail the whole patch (Err(())) and leave nothing outside the blobs

0 commit comments

Comments
 (0)