diff --git a/crates/socket-patch-core/src/patch/redirect/vlt.rs b/crates/socket-patch-core/src/patch/redirect/vlt.rs index f584a6eb2..0ae2d9e9d 100644 --- a/crates/socket-patch-core/src/patch/redirect/vlt.rs +++ b/crates/socket-patch-core/src/patch/redirect/vlt.rs @@ -17,9 +17,9 @@ use crate::constants::npm_family::{BUN_LOCKB, VLT_CONFIG, VLT_HIDDEN_LOCK_REL, V use crate::formats::governing_locks::{npm_locks_outside, NpmLockFamily}; use crate::vendor::vlt_lock_text::{ brotli_for_slot3, entry_text, has_brotli_flag, installs_outside_registry, is_default_registry, - is_registry_url_segment, nodes_block, parse_node_entry_text, parse_node_line, - parse_vendored_path, render_entry_line, render_tuple_with_slots, sniff_lock, split_dep_id, - split_lines, DepId, DepIdKind, LockSniff, NodeEntry, ParsedLock, SectionSpan, + is_registry_url_segment, nodes_block, parse_node_line, parse_vendored_path, render_entry_line, + render_tuple_with_slots, sniff_lock, split_dep_id, split_lines, DepId, DepIdKind, LockSniff, + ParsedLock, SectionSpan, }; /// The ledger kind of a hosted vlt node splice. @@ -288,29 +288,6 @@ fn ledger_key(name: &str, version: &str, extra: Option<&str>) -> String { } } -/// The DepID a [`KIND`] ledger edit records, from its `original` entry -/// text. -pub fn edit_dep_id(edit: &FileEdit) -> Option { - let text = edit.original.as_ref()?.as_str()?; - parse_node_entry_text(text).map(|entry| entry.key.to_string()) -} - -/// The node ids of a readable `vlt-lock.json`. -pub fn lock_node_ids(text: &str) -> Option> { - match sniff_lock(text) { - LockSniff::Readable(lock) => Some(lock.nodes()?.keys().cloned().collect()), - _ => None, - } -} - -/// Does a ledger edit of [`KIND`] belong to `name@version`? Claims are by -/// key, with a `~` boundary before a variant's extra segment. -pub(crate) fn claims_key(key: &str, name: &str, version: &str) -> bool { - let base = format!("{name}@{version}"); - key.strip_prefix(base.as_str()) - .is_some_and(|rest| rest.is_empty() || rest.starts_with('~')) -} - /// The one nodes-section line index keyed `id` that parses under the node /// grammar with slot [1] naming `name`; `None` when there are zero or /// several lines with that key, or the one line is outside the grammar. @@ -558,108 +535,6 @@ pub(super) fn rewrite_vlt_lock( } } -// ── revert ─────────────────────────────────────────────────────────────── - -fn slot_value(entry: &NodeEntry<'_>, index: usize) -> Option { - match entry.slot(index) { - None | Some("null") => None, - Some(raw) => serde_json::from_str(raw).ok(), - } -} - -fn same_slots(a: &NodeEntry<'_>, b: &NodeEntry<'_>) -> bool { - slot_value(a, 2) == slot_value(b, 2) && slot_value(a, 3) == slot_value(b, 3) -} - -/// The recorded DepID and entries of a [`KIND`] edit. -fn recorded(edit: &FileEdit) -> Result<(NodeEntry<'_>, NodeEntry<'_>), String> { - fn fragment(v: &Option) -> Option<&str> { - v.as_ref().and_then(Value::as_str) - } - let (Some(original), Some(new)) = (fragment(&edit.original), fragment(&edit.new)) else { - return Err(format!("{KIND} edit is missing its recorded fragments")); - }; - let (Some(original), Some(new)) = (parse_node_entry_text(original), parse_node_entry_text(new)) - else { - return Err(format!( - "{KIND} edit records fragments that are not vlt node entries" - )); - }; - if original.key != new.key { - return Err(format!("{KIND} edit records two different DepIDs")); - } - Ok((original, new)) -} - -/// The nodes-section lines of `text` that hold `new`'s pin under another -/// DepID: default-registry instances of the same `name@version` whose -/// slots [2] and [3] are exactly `new`'s. -fn carried_pin_lines(text: &str, lines: &[&str], new: &NodeEntry<'_>) -> Vec { - let Some(recorded_id) = split_dep_id(new.key) else { - return Vec::new(); - }; - let Some(identity) = recorded_id.registry_identity() else { - return Vec::new(); - }; - let LockSniff::Readable(lock) = sniff_lock(text) else { - return Vec::new(); - }; - let Some(span) = nodes_block(lines) else { - return Vec::new(); - }; - span.entry_lines() - .filter(|&i| { - parse_node_line(lines[i]).is_some_and(|line| { - line.entry.key != new.key - && split_dep_id(line.entry.key).is_some_and(|id| { - id.registry_identity() == Some(identity) - && is_default_registry(&id.first, lock.options()) - }) - && same_slots(&line.entry, new) - }) - }) - .collect() -} - -/// The DepIDs (with their slot [0] flags) a [`KIND`] edit's pin moved to -/// when vlt re-keyed its node: empty while `text` still holds the recorded -/// DepID. -pub(crate) fn carried_pin_ids(text: &str, edit: &FileEdit) -> Vec<(String, Option)> { - let Ok((_, new)) = recorded(edit) else { - return Vec::new(); - }; - let lines = split_lines(text); - let prefix = format!(" \"{}\": ", new.key); - if lines.iter().any(|l| l.starts_with(prefix.as_str())) { - return Vec::new(); - } - carried_pin_lines(text, &lines, &new) - .into_iter() - .filter_map(|i| parse_node_line(lines[i])) - .map(|l| (l.entry.key.to_string(), l.entry.elems[0].parse().ok())) - .collect() -} - -/// The `original` a fresh edit takes when it supersedes `old`, a recorded -/// edit whose DepID vanished, if vlt carried `old`'s pin to the fresh -/// DepID: the fresh entry is then Socket's, not the registry's, so it gets -/// `old`'s pristine slots back. `None` when the fresh entry is not `old`'s -/// pin (a re-lock that dropped it already left the pristine entry). -pub fn carried_pin_original(fresh: &FileEdit, old: &FileEdit) -> Option { - let fresh_original = parse_node_entry_text(fresh.original.as_ref()?.as_str()?)?; - let (old_original, old_new) = recorded(old).ok()?; - if !same_slots(&fresh_original, &old_new) { - return None; - } - let tuple = render_tuple_with_slots( - &fresh_original.elems, - has_brotli_flag(old_original.elems[0]), - old_original.slot(2).filter(|s| *s != "null"), - old_original.slot(3).filter(|s| *s != "null"), - ); - Some(Value::String(entry_text(fresh_original.key, &tuple))) -} - #[cfg(test)] mod tests { use super::*; @@ -722,17 +597,6 @@ mod tests { result.warnings.iter().map(|w| w.code.as_str()).collect() } - fn vlt_edit(original: &str, new: &str) -> FileEdit { - FileEdit { - path: VLT_LOCK.into(), - kind: KIND.into(), - action: "rewritten".into(), - key: Some("left-pad@1.3.0".into()), - original: Some(Value::String(original.into())), - new: Some(Value::String(new.into())), - } - } - const ID: &str = "~npm~left-pad@1.3.0"; fn registry_entry() -> String { @@ -836,28 +700,9 @@ mod tests { "left-pad@1.3.0", ] ); - for key in keys { - assert!(claims_key(key, "left-pad", "1.3.0"), "{key}"); - } assert!(result.confirmed_vlt_uuids.contains("uuid-left-pad")); } - #[test] - fn claims_stop_at_the_version_boundary() { - assert!(claims_key("@s/p@1.0.0", "@s/p", "1.0.0")); - assert!(claims_key("@s/p@1.0.0~peer.1", "@s/p", "1.0.0")); - for foreign in [ - "left-pad@1.3.01", - "left-pad@1.3.0-rc.1", - "left-pad@1.3.0(peer)", - "left-pad@1.3.0_x", - "long-left-pad@1.3.0", - "left-pad@1.3", - ] { - assert!(!claims_key(foreign, "left-pad", "1.3.0"), "{foreign}"); - } - } - #[test] fn residual_gate_refuses_when_the_parsed_node_is_not_the_spliced_line() { let entry = format!(" \"{ID}\": [0,\"left-pad\",\"{REG_SHA}\"]"); @@ -955,31 +800,6 @@ mod tests { assert!(result.warnings.is_empty()); } - #[test] - fn a_superseding_edit_keeps_the_pristine_slots_of_a_carried_pin() { - let old_id = "~npm~left-pad@1.3.0~peer.1"; - let new_id = "~npm~left-pad@1.3.0~peer.2"; - let old = vlt_edit( - &format!("\"{old_id}\": [0,\"left-pad\",\"{REG_SHA}\"]"), - &format!("\"{old_id}\": [0,\"left-pad\",\"{SHA}\",\"{URL}\"]"), - ); - let carried = vlt_edit( - &format!("\"{new_id}\": [1,\"left-pad\",\"{SHA}\",\"{URL}\"]"), - &format!("\"{new_id}\": [1,\"left-pad\",\"sha512-P2==\",\"u2\"]"), - ); - assert_eq!( - carried_pin_original(&carried, &old), - Some(Value::String(format!( - "\"{new_id}\": [1,\"left-pad\",\"{REG_SHA}\"]" - ))) - ); - let relocked = vlt_edit( - &format!("\"{new_id}\": [1,\"left-pad\",\"{REG_SHA}\"]"), - &format!("\"{new_id}\": [1,\"left-pad\",\"{SHA}\",\"{URL}\"]"), - ); - assert_eq!(carried_pin_original(&relocked, &old), None); - } - /// #372: vlt 1.3 records the brotli bit (4) on a node that resolved /// the registry's `.tar.br` alternate. Such a lock is canonical; the /// pin points the node at the hosted `.tgz`, so it clears bit 4 (and @@ -1010,22 +830,5 @@ mod tests { // A re-run over its own pin is a no-op. let again = rewrite(written, &[dep("left-pad", "1.3.0", Some(SHA))]); assert!(again.edits.is_empty(), "{:?}", again.edits); - - // Superseding a carried brotli pin restores the brotli bit with - // the pristine `.tar.br` slots. - let old = vlt_edit( - &format!("\"{peer}\": [6,\"left-pad\",\"{REG_SHA}\",\"{BR_URL}\"]"), - &format!("\"{peer}\": [2,\"left-pad\",\"{SHA}\",\"{URL}\"]"), - ); - let carried = vlt_edit( - &format!("\"{ID}\": [2,\"left-pad\",\"{SHA}\",\"{URL}\"]"), - &format!("\"{ID}\": [2,\"left-pad\",\"sha512-P2==\",\"u2\"]"), - ); - assert_eq!( - carried_pin_original(&carried, &old), - Some(Value::String(format!( - "\"{ID}\": [6,\"left-pad\",\"{REG_SHA}\",\"{BR_URL}\"]" - ))) - ); } } diff --git a/crates/socket-patch-core/src/patch/redirect/vlt_heal.rs b/crates/socket-patch-core/src/patch/redirect/vlt_heal.rs index ed6b07cf6..4c7f9d5be 100644 --- a/crates/socket-patch-core/src/patch/redirect/vlt_heal.rs +++ b/crates/socket-patch-core/src/patch/redirect/vlt_heal.rs @@ -14,17 +14,15 @@ use std::path::{Path, PathBuf}; use serde_json::{Map, Value}; -use super::{hosted_patch_uuid, vlt, RedirectState}; +use super::hosted_patch_uuid; use crate::constants::npm_family::{VLT_HIDDEN_LOCK_REL, VLT_STORE_DIR}; use crate::manifest::schema::PatchRecord; use crate::patch::apply::{is_safe_relative_subpath, normalize_file_path}; use crate::patch::file_hash::compute_file_git_sha256; use crate::patch::package::read_archive_bytes_to_map_strict; -use crate::utils::purl::purl_parts; use crate::utils::purl_key::PurlKey; use crate::vendor::vlt_lock_text::{ - is_default_registry, is_registry_package_name, parse_node_entry_text, sniff_lock, split_dep_id, - DepIdKind, LockSniff, + is_default_registry, is_registry_package_name, sniff_lock, split_dep_id, DepIdKind, LockSniff, }; /// The installed state a target should be in: patched after a redirect, @@ -102,14 +100,17 @@ pub struct Target<'a> { pub flags: Option, } -/// A ledger vlt node of one purl, for the rollback heal. +/// A Socket-hosted vlt node of one purl, for the rollback heal +/// ([`lock_targets`]). #[derive(Debug, Clone, PartialEq)] pub struct LedgerTarget { pub purl: String, pub dep_id: String, pub name: String, + /// Always `None`: v5 keeps no hosted ledger to read a record from, so + /// the heal judges the installed copy against the lock's own pins. pub record: Option, - /// Slot [0] of the recorded pristine entry. + /// Slot [0] of the node in the pre-restore lock. pub flags: Option, } @@ -484,64 +485,6 @@ pub fn lock_targets(lock: &str, origins: &[String], purls: &[String]) -> Vec, -) -> Vec { - let mut out: Vec = Vec::new(); - for purl in purls { - let Some((ecosystem, name, version)) = purl_parts(purl) else { - continue; - }; - if ecosystem != "npm" { - continue; - } - let record = state - .records - .iter() - .find(|(key, _)| PurlKey::same(key, purl)) - .map(|(_, record)| record.clone()); - for edit in &state.edits { - if edit.kind != vlt::KIND - || !edit - .key - .as_deref() - .is_some_and(|key| vlt::claims_key(key, &name, &version)) - { - continue; - } - let Some(entry) = edit - .original - .as_ref() - .and_then(Value::as_str) - .and_then(parse_node_entry_text) - else { - continue; - }; - let mut nodes = vec![(entry.key.to_string(), entry.elems[0].parse().ok())]; - nodes.extend(lock.map_or_else(Vec::new, |text| vlt::carried_pin_ids(text, edit))); - for (dep_id, flags) in nodes { - if out.iter().any(|t| t.dep_id == dep_id && t.purl == *purl) { - continue; - } - out.push(LedgerTarget { - purl: purl.clone(), - dep_id, - name: name.clone(), - record: record.clone(), - flags, - }); - } - } - } - out -} - #[cfg(test)] mod tests { use super::*; @@ -993,89 +936,47 @@ mod tests { assert!(socket_owned_instances("\u{feff}{}", &[]).is_empty()); } + /// The rollback heal's only target source (the ledger-driven + /// `ledger_targets` is gone): every Socket-owned instance of a wanted + /// purl, peer contexts included, with its slot [0] flags and no record. #[test] - fn ledger_targets_follow_the_claimed_edits() { - let mut state = RedirectState::new(); - let edit = |key: &str, id: &str| FileEdit { - path: "vlt-lock.json".into(), - kind: vlt::KIND.into(), - action: "rewritten".into(), - key: Some(key.into()), - original: Some(Value::String(format!("\"{id}\": [0,\"x\"]"))), - new: Some(Value::String(format!("\"{id}\": [0,\"x\",\"s\",\"u\"]"))), - }; - state.edits = vec![ - edit("left-pad@1.3.0", "~npm~left-pad@1.3.0"), - edit("left-pad@1.3.0~peer.2", "~npm~left-pad@1.3.0~peer.2"), - edit("left-pad@1.3.1", "~npm~left-pad@1.3.1"), + fn lock_targets_name_every_owned_instance_of_the_purls() { + let lock = r#"{ + "lockfileVersion": 1, + "options": {}, + "nodes": { + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-P","https://patch.socket.dev/patch/npm/t/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"], + "~npm~left-pad@1.3.0~peer.1": [3,"left-pad","sha512-P","https://patch.socket.dev/patch/npm/t/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"], + "~npm~left-pad@1.3.1": [0,"left-pad","sha512-Q","https://patch.socket.dev/patch/npm/t/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/left-pad-1.3.1.tgz"], + "~npm~ms@2.1.3": [2,"ms","sha512-R","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"] + }, + "edges": {} +} +"#; + let purls = [ + "pkg:npm/left-pad@1.3.0".to_string(), + "pkg:npm/ms@2.1.3".to_string(), ]; - state - .records - .insert("pkg:npm/left-pad@1.3.0".into(), record()); - let targets = ledger_targets(&state, &["pkg:npm/left-pad@1.3.0".into()], None); - let ids: Vec<&str> = targets.iter().map(|t| t.dep_id.as_str()).collect(); - assert_eq!(ids, ["~npm~left-pad@1.3.0", "~npm~left-pad@1.3.0~peer.2"]); - assert!(targets.iter().all(|t| t.record.is_some())); - assert!(targets.iter().all(|t| t.flags == Some(0))); - } - - #[test] - fn ledger_targets_follow_a_pin_vlt_carried_to_a_new_peer_context() { - let old_id = "~npm~left-pad@1.3.0~peer.0df72515a50372ba"; - let new_id = "~npm~left-pad@1.3.0~peer.32643a3290c32d5d"; - let mut state = RedirectState::new(); - state.edits = vec![FileEdit { - path: "vlt-lock.json".into(), - kind: vlt::KIND.into(), - action: "rewritten".into(), - key: Some("left-pad@1.3.0~peer.0df72515a50372ba".into()), - original: Some(Value::String(format!( - "\"{old_id}\": [0,\"left-pad\",\"r\"]" - ))), - new: Some(Value::String(format!( - "\"{old_id}\": [0,\"left-pad\",\"s\",\"u\"]" - ))), - }]; - let lock = format!( - "{{\n \"lockfileVersion\": 1,\n \"nodes\": {{\n \"{new_id}\": \ - [2,\"left-pad\",\"s\",\"u\"]\n }},\n \"edges\": {{}}\n}}\n" - ); - let purls = ["pkg:npm/left-pad@1.3.0".to_string()]; - let targets: Vec<(String, Option)> = ledger_targets(&state, &purls, Some(&lock)) - .into_iter() - .map(|t| (t.dep_id, t.flags)) + let targets = lock_targets(lock, &[], &purls); + let got: Vec<(&str, &str, Option)> = targets + .iter() + .map(|t| (t.purl.as_str(), t.dep_id.as_str(), t.flags)) .collect(); assert_eq!( - targets, - [(old_id.to_string(), Some(0)), (new_id.to_string(), Some(2))] + got, + [ + ("pkg:npm/left-pad@1.3.0", "~npm~left-pad@1.3.0", Some(0)), + ( + "pkg:npm/left-pad@1.3.0", + "~npm~left-pad@1.3.0~peer.1", + Some(3) + ), + ] ); - let kept = lock.replace(new_id, old_id); - assert_eq!(ledger_targets(&state, &purls, Some(&kept)).len(), 1); - } - - #[test] - fn ledger_targets_carry_the_recorded_flags() { - let mut state = RedirectState::new(); - state.edits = [ - (0, "~npm~left-pad@1.3.0"), - (3, "~npm~left-pad@1.3.0~peer.1"), - ] - .into_iter() - .map(|(flags, id)| FileEdit { - path: "vlt-lock.json".into(), - kind: vlt::KIND.into(), - action: "rewritten".into(), - key: Some(id.trim_start_matches("~npm~").into()), - original: Some(Value::String(format!("\"{id}\": [{flags},\"left-pad\"]"))), - new: None, - }) - .collect(); - let flags: Vec> = - ledger_targets(&state, &["pkg:npm/left-pad@1.3.0".into()], None) - .iter() - .map(|t| t.flags) - .collect(); - assert_eq!(flags, [Some(0), Some(3)]); + assert!(targets + .iter() + .all(|t| t.name == "left-pad" && t.record.is_none())); + assert!(lock_targets("\u{feff}{}", &[], &purls).is_empty()); } #[test] diff --git a/crates/socket-patch-core/src/vendor/jvm/apply.rs b/crates/socket-patch-core/src/vendor/jvm/apply.rs index 29c4a1715..c5d66f615 100644 --- a/crates/socket-patch-core/src/vendor/jvm/apply.rs +++ b/crates/socket-patch-core/src/vendor/jvm/apply.rs @@ -269,8 +269,9 @@ impl ProjectReader { } } -/// Read a project file for the planners (see [`ProjectReader::read`]). -pub fn read_project_file(root: &Path, rel: &str) -> Option> { +/// Read a project file for the planners' tests (see [`ProjectReader::read`]). +#[cfg(test)] +pub(crate) fn read_project_file(root: &Path, rel: &str) -> Option> { ProjectReader::new(root).read(rel) }