From 670f8a19261c9900a77324758c6c42f486282124 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 28 Oct 2026 07:53:14 +0000 Subject: [PATCH 1/3] Start fix for #1774 Assisted-by: Claude Code:claude-opus-5-5 From a7a9fe8285ac4482b991efb850a82b5a9db9fbc4 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 28 Oct 2026 08:02:56 +0000 Subject: [PATCH 2/3] Fix nightly vlt migration tests for vendor eject The nightly mode_migration_vlt legs have failed on every vlt release since #1648. That change made plain `vendor` with a hosted pin in scope an eject: the dry run reports `eject_planned`, and the wet run no longer announces a per-purl takeover. The PR-tier migration suites were updated, but these real-vlt tests run only nightly and still expected the old takeover advisories. Assert the eject contract instead: `eject_planned` on the dry run, and on the wet `vendor` no takeover event, no revert failure, and the `redirect_vlt_reinstall_required` warning from the store heal. `scan --mode vendored` keeps asserting its takeover. The vendor runs and every later check (pure vendored lock, fresh checkouts, exact revert) are unchanged. Fixes #1774 Assisted-by: Claude Code:claude-opus-5-5 --- .../tests/mode_migration_vlt.rs | 58 +++++++++++++------ 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/crates/socket-patch-cli/tests/mode_migration_vlt.rs b/crates/socket-patch-cli/tests/mode_migration_vlt.rs index aa9f27694..e950e5678 100644 --- a/crates/socket-patch-cli/tests/mode_migration_vlt.rs +++ b/crates/socket-patch-cli/tests/mode_migration_vlt.rs @@ -111,7 +111,8 @@ fn api_upstream(fx: &Fixture, dir: &Path, head: &[&str], extra: &[&str]) -> Sock } /// `vendor` over a (possibly) hosted project: no `--offline`, since the -/// takeover restores the hosted pin's upstream entry first. +/// eject fetches the hosted pin's record and restores its upstream entry +/// first. fn vendor_upstream(fx: &Fixture, dir: &Path, extra: &[&str]) -> SocketOut { api_upstream(fx, dir, &["vendor"], extra) } @@ -286,10 +287,12 @@ async fn vlt_pinned_matrix_migration_vendored_then_hosted() { fx.leg.ran(); } -/// Hosted → vendored through `scan --mode vendored` and through `vendor` -/// (a staged manifest): the takeover restores the hosted pin's upstream -/// entry first, the lock is vendored, fresh checkouts are patched, and -/// `vendor --revert` restores the pristine (upstream) lock. +/// Hosted → vendored through `scan --mode vendored` (a per-purl takeover) +/// and through `vendor` (a staged manifest, but a hosted pin is in scope, +/// so `vendor` ejects the lockfile's hosted pin instead): either way the +/// hosted pin's upstream entry is restored first, the lock is vendored, +/// fresh checkouts are patched, and `vendor --revert` restores the +/// pristine (upstream) lock. #[tokio::test(flavor = "multi_thread")] #[ignore = "real vlt: SOCKET_PATCH_VLT_E2E_JS"] async fn vlt_pinned_matrix_migration_hosted_then_vendored() { @@ -309,10 +312,7 @@ async fn vlt_pinned_matrix_migration_hosted_then_vendored() { vendor_upstream(&fx, &dir, &[]) }; assert_eq!(out.code, 0, "{driver}: {out}"); - assert!( - has_code(&out.json(), "vendor_takeover_reverted_redirect"), - "{driver}: {out}" - ); + assert_vendored_over_hosted(driver, &out); assert_pure_vendored(&dir, fx.t()); assert_fresh( &fx, @@ -332,14 +332,38 @@ async fn vlt_pinned_matrix_migration_hosted_then_vendored() { fx.leg.ran(); } +/// The wet hosted → vendored advisory for `driver`. `scan --mode vendored` +/// announces its per-purl takeover; plain `vendor` with a hosted pin in +/// scope runs the eject instead, which announces no takeover but, having +/// healed the warm vlt store, asks for a `vlt install`. +fn assert_vendored_over_hosted(driver: &str, out: &SocketOut) { + let doc = out.json(); + assert!(!has_code(&doc, "redirect_revert_failed"), "{driver}: {out}"); + if driver == "scan" { + assert!( + has_code(&doc, "vendor_takeover_reverted_redirect"), + "{driver}: {out}" + ); + } else { + assert!( + !has_code(&doc, "vendor_takeover_reverted_redirect"), + "{driver}: the eject is not a takeover: {out}" + ); + assert!( + has_code(&doc, "redirect_vlt_reinstall_required"), + "{driver}: {out}" + ); + } +} + fn vendor_revert_all(dir: &Path) -> SocketOut { vendor_cmd(dir, &["--revert"]) } // ── 3. dry-run parity ───────────────────────────────────────────────────── -/// Over a live hosted redirect `vendor --dry-run` previews the takeover -/// and `scan --mode vendored --dry-run` says `would_vendor`; over a live +/// Over a live hosted redirect `vendor --dry-run` previews the eject of +/// the hosted pin (`eject_planned`) and `scan --mode vendored --dry-run` says `would_vendor`; over a live /// vendored state `scan --mode hosted --dry-run` previews /// `redirect_would_revert_vendored`. No preview writes a byte, and the wet /// runs then do what was previewed. @@ -357,10 +381,9 @@ async fn vlt_pinned_matrix_migration_dry_run_parity() { let before = project_bytes(&fx.proj); let out = vendor_upstream(&fx, &fx.proj, &["--dry-run"]); assert_eq!(out.code, 0, "{out}"); - assert!( - has_code(&out.json(), "vendor_would_revert_redirect"), - "{out}" - ); + let doc = out.json(); + assert!(has_code(&doc, "eject_planned"), "{out}"); + assert!(!has_code(&doc, "vendor_would_revert_redirect"), "{out}"); assert!(!out.stdout.contains("vendor_lock_entry_not_found"), "{out}"); let out = vendored_scan(&fx, &fx.proj, &["--dry-run"]); assert_eq!(out.code, 0, "{out}"); @@ -382,10 +405,7 @@ async fn vlt_pinned_matrix_migration_dry_run_parity() { ); let out = vendor_upstream(&fx, &fx.proj, &[]); assert_eq!(out.code, 0, "{out}"); - assert!( - has_code(&out.json(), "vendor_takeover_reverted_redirect"), - "{out}" - ); + assert_vendored_over_hosted("vendor", &out); assert_pure_vendored(&fx.proj, fx.t()); let before = project_bytes(&fx.proj); let out = hosted_scan(&fx, &fx.proj, &["--dry-run"]); From ccb4d431df585c90bbea2d5018c64fc8b94192ee Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 28 Oct 2026 08:08:19 +0000 Subject: [PATCH 3/3] Tighten vlt eject assertions from code review Tie the dry run's `eject_planned` to the hosted pin (exactly one plan, not the bystander), check that the eject leaves the staged manifest byte-identical, and require the warm vlt store heal warning from both the scan takeover and the eject. Pick the driver's contract with a bool instead of a driver-name string, and fix stale doc wording. Refs #1774 Assisted-by: Claude Code:claude-opus-5-5 --- .../tests/mode_migration_vlt.rs | 85 +++++++++++-------- 1 file changed, 50 insertions(+), 35 deletions(-) diff --git a/crates/socket-patch-cli/tests/mode_migration_vlt.rs b/crates/socket-patch-cli/tests/mode_migration_vlt.rs index e950e5678..f954bce0f 100644 --- a/crates/socket-patch-cli/tests/mode_migration_vlt.rs +++ b/crates/socket-patch-cli/tests/mode_migration_vlt.rs @@ -1,10 +1,10 @@ //! Real-vlt mode migrations (suite `migration`, DESIGN §8.3, §4.10). //! -//! Hosted ⇄ vendored takeovers (both drivers), dry-run parity, scoped and -//! unscoped unwinds, agent-mode interplay (r-tests T21), package-manager -//! switches (T37) and the vlt upgrade (T19, `SOCKET_PATCH_VLT_E2E_UPGRADE_JS`) -//! against the REAL vlt under test, every terminal state proven by a fresh -//! checkout's locked install. Each leg is `vlt_pinned_matrix_migration_` +//! Hosted ⇄ vendored migrations (scan takeovers, the `vendor` eject), +//! dry-run parity, scoped and unscoped unwinds, agent-mode interplay +//! (r-tests T21), package-manager switches (T37) and the vlt upgrade (T19, +//! `SOCKET_PATCH_VLT_E2E_UPGRADE_JS`) against the REAL vlt under test, +//! every terminal state proven by a fresh checkout's locked install. Each leg is `vlt_pinned_matrix_migration_` //! and prints one `VLT-LEG` line. #[path = "common/rollback_json.rs"] @@ -305,14 +305,22 @@ async fn vlt_pinned_matrix_migration_hosted_then_vendored() { fx.vlt_ok(&fx.proj, &fx.leg.locked_install_args()); for driver in ["scan", "vendor"] { let dir = copy_project(&fx, &fx.proj, &format!("drv-{driver}")); - let out = if driver == "scan" { - vendored_scan(&fx, &dir, &[]) - } else { + let eject = driver == "vendor"; + let out = if eject { stage_manifest(&dir, &[fx.t()]); - vendor_upstream(&fx, &dir, &[]) + let manifest = std::fs::read(dir.join(".socket/manifest.json")).unwrap(); + let out = vendor_upstream(&fx, &dir, &[]); + assert_eq!( + std::fs::read(dir.join(".socket/manifest.json")).unwrap(), + manifest, + "{driver}: the eject leaves the manifest untouched: {out}" + ); + out + } else { + vendored_scan(&fx, &dir, &[]) }; assert_eq!(out.code, 0, "{driver}: {out}"); - assert_vendored_over_hosted(driver, &out); + assert_vendored_over_hosted(eject, driver, &out); assert_pure_vendored(&dir, fx.t()); assert_fresh( &fx, @@ -332,28 +340,24 @@ async fn vlt_pinned_matrix_migration_hosted_then_vendored() { fx.leg.ran(); } -/// The wet hosted → vendored advisory for `driver`. `scan --mode vendored` -/// announces its per-purl takeover; plain `vendor` with a hosted pin in -/// scope runs the eject instead, which announces no takeover but, having -/// healed the warm vlt store, asks for a `vlt install`. -fn assert_vendored_over_hosted(driver: &str, out: &SocketOut) { +/// The wet hosted → vendored advisories. `scan --mode vendored` announces +/// its per-purl takeover; plain `vendor` with a hosted pin in scope +/// (`eject`) runs the eject instead, which announces no takeover. Both +/// heal the warm vlt store and ask for a `vlt install`. +fn assert_vendored_over_hosted(eject: bool, tag: &str, out: &SocketOut) { let doc = out.json(); - assert!(!has_code(&doc, "redirect_revert_failed"), "{driver}: {out}"); - if driver == "scan" { - assert!( - has_code(&doc, "vendor_takeover_reverted_redirect"), - "{driver}: {out}" - ); - } else { - assert!( - !has_code(&doc, "vendor_takeover_reverted_redirect"), - "{driver}: the eject is not a takeover: {out}" - ); - assert!( - has_code(&doc, "redirect_vlt_reinstall_required"), - "{driver}: {out}" - ); - } + assert_eq!( + has_code(&doc, "vendor_takeover_reverted_redirect"), + !eject, + "{tag}: only the scan takeover announces a takeover: {out}" + ); + // The eject reports a failed restore as a run error (exit 1); the scan + // takeover reports it per event, so check it there. + assert!(!has_code(&doc, "redirect_revert_failed"), "{tag}: {out}"); + assert!( + has_code(&doc, "redirect_vlt_reinstall_required"), + "{tag}: the warm vlt store is healed: {out}" + ); } fn vendor_revert_all(dir: &Path) -> SocketOut { @@ -363,8 +367,8 @@ fn vendor_revert_all(dir: &Path) -> SocketOut { // ── 3. dry-run parity ───────────────────────────────────────────────────── /// Over a live hosted redirect `vendor --dry-run` previews the eject of -/// the hosted pin (`eject_planned`) and `scan --mode vendored --dry-run` says `would_vendor`; over a live -/// vendored state `scan --mode hosted --dry-run` previews +/// the hosted pin (`eject_planned`) and `scan --mode vendored --dry-run` +/// says `would_vendor`; over a live vendored state `scan --mode hosted --dry-run` previews /// `redirect_would_revert_vendored`. No preview writes a byte, and the wet /// runs then do what was previewed. #[tokio::test(flavor = "multi_thread")] @@ -382,7 +386,18 @@ async fn vlt_pinned_matrix_migration_dry_run_parity() { let out = vendor_upstream(&fx, &fx.proj, &["--dry-run"]); assert_eq!(out.code, 0, "{out}"); let doc = out.json(); - assert!(has_code(&doc, "eject_planned"), "{out}"); + let planned: Vec<&Value> = doc["events"] + .as_array() + .map(|evs| { + evs.iter() + .filter(|e| e["errorCode"] == "eject_planned") + .collect() + }) + .unwrap_or_default(); + assert!( + planned.len() == 1 && planned[0]["purl"] == fx.t().purl(), + "one eject planned, for the hosted pin only: {out}" + ); assert!(!has_code(&doc, "vendor_would_revert_redirect"), "{out}"); assert!(!out.stdout.contains("vendor_lock_entry_not_found"), "{out}"); let out = vendored_scan(&fx, &fx.proj, &["--dry-run"]); @@ -405,7 +420,7 @@ async fn vlt_pinned_matrix_migration_dry_run_parity() { ); let out = vendor_upstream(&fx, &fx.proj, &[]); assert_eq!(out.code, 0, "{out}"); - assert_vendored_over_hosted("vendor", &out); + assert_vendored_over_hosted(true, "vendor", &out); assert_pure_vendored(&fx.proj, fx.t()); let before = project_bytes(&fx.proj); let out = hosted_scan(&fx, &fx.proj, &["--dry-run"]);