app
SOLIDWORKS Bridge
Public Made by Adomby adom
Launch, model and quit SOLIDWORKS from any Adom container. Nothing is ever lost.
main
Drew Owens
0.9.10 source: presence baseline holds only bridge-put documents (adversary pass 46); manifest as published
bf99cca
19d ago
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532
//! SEV-1, MEASURED 2026-09-30: WHERE THE BRIDGE'S DATA LIVES.
//!
//! Until 0.9.0 every file this bridge wrote on its own initiative — the recovery
//! folders that hold the ONLY copy of work the user had not saved, the action
//! ledger, the ownership registry — lived in `bridges-cache\solidworks`, the
//! directory ab INSTALLS the bridge into. ab's updater re-extracts that
//! directory, and on Drew's machine it was found CREATED 2026-09-18 09:42 by the
//! auto-install of 0.8.9: every recovery folder from 09-17 was gone. Only test
//! documents were in them. The next time it would not have been.
//!
//! Every test here drives the real binary with the two locations laid out the
//! way ab lays them out under `%LOCALAPPDATA%`, and asserts one of the four
//! things the fix promises:
//!
//! * the data root is NOT the install cache, and nothing is written there;
//! * what an older bridge left in the install cache is COPIED out, verified
//! byte for byte, never over anything, with the originals left in place —
//! each file once;
//! * `swx_recover_list` still reads the install cache, and says which of its
//! rescues is the only copy;
//! * a sweep REFUSES to place recovery under an install location, however the
//! path is spelled — and closes nothing when it refuses.
//
// 0.9.10: the documents these sessions are seeded with were not opened by the
// bridge, so the user-presence guard counts them as the USER's (adversary pass
// 46: the baseline may contain only documents the bridge put there). Every
// sweep below passes `userPresentOverride` + `userAuthorized`, standing in for
// the user's explicit request — which is what these sweeps model.
mod common;
use common::{Bridge, TempDir};
use serde_json::{json, Value};
use std::path::{Path, PathBuf};
fn s(v: &Value) -> String {
serde_json::to_string_pretty(v).unwrap_or_default()
}
/// A machine whose window census can be read, so the sweep is allowed to close.
fn a_visible_machine(tag: &str) -> TempDir {
let d = TempDir::new(tag);
std::fs::write(d.join("windows.json"), "[]").unwrap();
d
}
/// `%LOCALAPPDATA%`, with ab's layout under it — and, beside it, the one
/// directory only this bridge writes to.
struct Layout {
local: TempDir,
}
impl Layout {
fn new(tag: &str) -> Layout {
Layout { local: TempDir::new(tag) }
}
/// The data root since 0.9.1: a SIBLING of `Adom Bridge\`.
fn data(&self) -> PathBuf {
self.local.0.join("adom-solidworks-bridge")
}
/// ab's own directory: its installer's and its uninstaller's.
fn ab(&self) -> PathBuf {
self.local.0.join("Adom Bridge")
}
fn cache(&self) -> PathBuf {
self.ab().join("bridges-cache")
}
/// The first legacy root: 0.8.x's install cache.
fn legacy(&self) -> PathBuf {
self.cache().join("solidworks")
}
/// The second legacy root: 0.9.0's data root, inside ab's directory.
fn legacy_data(&self) -> PathBuf {
self.ab().join("solidworks-data")
}
}
/// Unsaved work the sweep has to write somewhere: a pathless import and a saved
/// document with changes.
fn a_session_with_unsaved_work(root: &Path) {
std::fs::create_dir_all(root).unwrap();
std::fs::write(
root.join("mock-solidworks-open-docs.json"),
json!({
"running": true, "untitled": 1, "active": "untitled:a",
"docs": [
{"docType": "part", "path": "untitled:a", "title": "Imported.step.SLDPRT",
"saved": false, "dirty": true},
{"docType": "part", "path": "C:\\Users\\drew\\work\\bracket.SLDPRT",
"title": "bracket.SLDPRT", "saved": true, "dirty": true}
],
"files": {}
})
.to_string(),
)
.unwrap();
}
/// A file that looks like what SOLIDWORKS writes: four varying bytes, the format
/// marker at offset 4, and a body.
fn a_document(len: usize) -> Vec<u8> {
let mut v = vec![0x9f, 0xe4, 0x18, 0x9f, 0x00, 0x00, 0x00, 0x04];
v.resize(len, b'x');
v
}
fn write(path: &Path, bytes: &[u8]) {
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
std::fs::write(path, bytes).unwrap();
}
/// A sweep folder the way a pre-0.9.0 bridge left it: the rescue and its record.
fn a_legacy_sweep(recovery: &Path, sweep: &str, name: &str, bytes: &[u8]) -> PathBuf {
a_legacy_sweep_of(recovery, sweep, &[(name, bytes)])
}
/// ...with several rescues in it.
fn a_legacy_sweep_of(recovery: &Path, sweep: &str, rescues: &[(&str, &[u8])]) -> PathBuf {
let dir = recovery.join(sweep);
let mut entries: Vec<Value> = Vec::new();
for (name, bytes) in rescues {
write(&dir.join(name), bytes);
entries.push(json!({
"name": name,
"recoveryPath": dir.join(name).to_string_lossy(),
"title": name, "originalPath": Value::Null, "kind": "part",
"namingFallback": Value::Null, "originalBytes": Value::Null,
"recoveryBytes": bytes.len(), "signatureOk": true,
"verified": true, "verdictReason": Value::Null, "preserved": true,
"closed": true, "wasDirty": true, "wasPathless": true
}));
}
write(
&dir.join("manifest.json"),
s(&json!({
"manifestVersion": 1,
"sweep": sweep,
"reason": "the user asked me to close SOLIDWORKS",
"failures": [],
"files": entries
}))
.as_bytes(),
);
dir
}
fn sweeps_named<'a>(list: &'a Value, sweep: &str) -> Vec<&'a Value> {
list["sweeps"]
.as_array()
.map(|a| a.iter().filter(|x| x["sweep"] == sweep).collect())
.unwrap_or_default()
}
fn ledger_count(root: &Path, action: &str) -> usize {
std::fs::read_to_string(root.join("ledger.jsonl"))
.unwrap_or_default()
.lines()
.filter(|l| l.contains(&format!("\"action\":\"{action}\"")))
.count()
}
// ─────────────────────────────────────────────────────────────────────────────
#[test]
fn recovery_is_written_to_the_data_root_never_into_the_install_cache() {
let lay = Layout::new("dataroot-default");
a_session_with_unsaved_work(&lay.data());
let machine = a_visible_machine("dataroot-default-machine");
let local = lay.local.path();
// NO OVERRIDE: the root comes from %LOCALAPPDATA%, as it does on Drew's
// machine. (An empty SWX_SCRATCH_DIR is the harness's way of not setting it.)
let b = Bridge::start_with(&[
("SWX_SCRATCH_DIR", ""),
("LOCALAPPDATA", &local),
("SWX_LAUNCH_FAKE_DIR", &machine.path()),
]);
b.call("swx_start", json!({}));
let r = b.call("swx_close_all_safely", json!({"userPresentOverride": true, "userAuthorized": true, "reason": "the user asked me to close SOLIDWORKS"}));
check!(b, "swx_close_all_safely: the sweep runs in the default layout", r["success"] == true && r["done"] == true, s(&r));
let dir = PathBuf::from(r["recoveryDir"].as_str().unwrap_or(""));
check!(
b,
"swx_close_all_safely: the rescue is written under the DATA root",
dir.starts_with(lay.data().join("recovery")),
format!("{} vs {}", dir.display(), lay.data().display())
);
check!(
b,
"swx_close_all_safely: ...and NOT under ab's install cache, which an update empties",
!dir.starts_with(lay.cache()),
dir.display().to_string()
);
let written: Vec<PathBuf> = r["recovered"]
.as_array()
.cloned()
.unwrap_or_default()
.iter()
.filter_map(|x| x["recoveryPath"].as_str().map(PathBuf::from))
.collect();
check!(b, "swx_close_all_safely: both unsaved documents were written", written.len() == 2, s(&r));
for f in &written {
check!(
b,
"swx_close_all_safely: every rescue exists, in the data root",
f.exists() && f.starts_with(lay.data()),
f.display().to_string()
);
}
// THE REST OF THE DATA WENT WITH IT.
check!(
b,
"swx_close_all_safely: the ledger is in the data root",
lay.data().join("ledger.jsonl").exists(),
lay.data().display().to_string()
);
check!(
b,
"swx_close_all_safely: and nothing at all was written into ab's directory",
!lay.ab().exists(),
lay.ab().display().to_string()
);
let l = b.call("swx_recover_list", json!({}));
check!(
b,
"swx_recover_list: reads the data root",
l["recoveryRoot"] == json!(lay.data().join("recovery").to_string_lossy()),
s(&l)
);
check!(
b,
"swx_recover_list: ...and names both legacy locations it also reads, oldest first",
l["legacyRecoveryRoot"] == json!(lay.legacy().join("recovery").to_string_lossy())
&& l["legacyRecoveryRoots"]
== json!([
{"path": lay.legacy().join("recovery").to_string_lossy(), "location": "legacyInstallCache"},
{"path": lay.legacy_data().join("recovery").to_string_lossy(), "location": "legacyDataRoot"},
]),
s(&l)
);
let sweep = l["sweeps"].as_array().and_then(|a| a.first()).cloned().unwrap_or(json!({}));
check!(
b,
"swx_recover_list: a sweep in the data root is not reported at risk",
sweep["location"] == "data" && sweep["atRiskFromUpdate"] == false && sweep["warning"].is_null(),
s(&sweep)
);
}
#[test]
fn what_an_older_bridge_left_in_the_install_cache_is_copied_out_verified_and_left_in_place() {
let lay = Layout::new("dataroot-migrate");
let legacy = lay.legacy();
let rescue = a_document(64 * 1024);
let stuck_bytes = a_document(32 * 1024);
let sweep_dir = a_legacy_sweep_of(
&legacy.join("recovery"),
"20260917-101500",
&[("bracket.SLDPRT", &rescue), ("stuck.SLDPRT", &stuck_bytes)],
);
let manifest_bytes = std::fs::read(sweep_dir.join("manifest.json")).unwrap();
let ledger = b"{\"at\":1789000000,\"action\":\"document_created\",\"detail\":{}}\n{\"at\":1789000100,\"action\":\"close_all_safely\",\"detail\":{}}\n".to_vec();
write(&legacy.join("ledger.jsonl"), &ledger);
let timings = br#"{"swx_docs":{"count":3,"secs":[0.1,0.2,0.3]}}"#.to_vec();
write(&legacy.join("timings.json"), &timings);
let scratch_doc = a_document(20 * 1024);
write(&legacy.join("exports").join("scratch").join("Part1.SLDPRT"), &scratch_doc);
write(&legacy.join("owned-docs.json"), br#"{"documents":[]}"#);
// WHAT IS NOT DATA: the bridge's own install. Never copied.
write(&legacy.join("swx-bridge.exe"), b"MZ not really");
write(&legacy.join("bridge.json"), br#"{"name":"solidworks"}"#);
// A FILE THAT IS ALREADY IN THE DATA ROOT is never overwritten.
write(&legacy.join("settings.json"), br#"{"badge":false}"#);
write(&lay.data().join("settings.json"), br#"{"badge":true}"#);
// AND ONE THAT CANNOT BE READ: its copy fails, and must leave nothing behind.
let stuck = legacy.join("recovery").join("20260917-101500").join("stuck.SLDPRT");
let unreadable = {
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(&stuck, std::fs::Permissions::from_mode(0o000)).unwrap();
// Root reads anything; the failure half of this test needs a user that
// cannot.
std::fs::read(&stuck).is_err()
};
let local = lay.local.path();
let env = [("SWX_SCRATCH_DIR", ""), ("LOCALAPPDATA", local.as_str())];
let b = Bridge::start_with(&env);
// BEFORE ANY VERB: this is what the migration left, untouched by anything else.
let data = lay.data();
let same = |rel: &str, want: &[u8]| std::fs::read(data.join(rel)).map(|got| got == want).unwrap_or(false);
check!(b, "migration: a rescue is copied byte for byte", same("recovery/20260917-101500/bracket.SLDPRT", &rescue), data.display().to_string());
check!(b, "migration: ...with the sweep's own record", same("recovery/20260917-101500/manifest.json", &manifest_bytes), data.display().to_string());
check!(b, "migration: the bridge's scratch documents come too", same("exports/scratch/Part1.SLDPRT", &scratch_doc), data.display().to_string());
check!(b, "migration: and its timings", same("timings.json", &timings), data.display().to_string());
check!(b, "migration: and the ownership registry", data.join("owned-docs.json").exists(), data.display().to_string());
let migrated_ledger = std::fs::read(data.join("ledger.jsonl")).unwrap_or_default();
check!(
b,
"migration: the ledger is carried over whole, and the move is recorded in it",
migrated_ledger.starts_with(&ledger)
&& ledger_count(&data, "data_migrated_from_install_cache") == 1,
String::from_utf8_lossy(&migrated_ledger).to_string()
);
check!(
b,
"migration: the bridge's own install is not data and is not copied",
!data.join("swx-bridge.exe").exists() && !data.join("bridge.json").exists(),
data.display().to_string()
);
check!(
b,
"migration: a file already in the data root is NEVER overwritten",
same("settings.json", br#"{"badge":true}"#),
data.display().to_string()
);
// THE ORIGINALS ARE LEFT EXACTLY WHERE THEY WERE.
check!(
b,
"migration: every original is left in place, unchanged",
std::fs::read(sweep_dir.join("bracket.SLDPRT")).ok().as_deref() == Some(&rescue[..])
&& std::fs::read(legacy.join("ledger.jsonl")).ok().as_deref() == Some(&ledger[..])
&& std::fs::read(legacy.join("exports/scratch/Part1.SLDPRT")).ok().as_deref() == Some(&scratch_doc[..])
&& std::fs::read(legacy.join("settings.json")).ok().as_deref() == Some(&br#"{"badge":false}"#[..]),
legacy.display().to_string()
);
let marker: Value = serde_json::from_str(
&std::fs::read_to_string(data.join("migrated-from-legacy.json")).unwrap_or_default(),
)
.unwrap_or(json!({}));
let listed = |key: &str, file: &str| {
marker[key].as_array().map(|a| a.iter().any(|e| e["file"] == file)).unwrap_or(false)
};
// SAME NAME, DIFFERENT BYTES: the legacy settings went BESIDE the existing
// file — never over it, and never dropped either.
let beside = marker["copied"]
.as_array()
.and_then(|a| a.iter().find(|e| e["file"] == "settings.json").cloned())
.unwrap_or(json!({}));
let beside_as = beside["copiedAs"].as_str().unwrap_or("").to_string();
check!(
b,
"migration: a different file of the same name is copied BESIDE the existing one, not dropped",
beside["outcome"] == "copiedBeside"
&& beside_as.starts_with("settings.json.legacy-")
&& same(&beside_as, br#"{"badge":false}"#),
s(&marker)
);
check!(
b,
"migration: the record says what was copied, by root, size, mtime and digest, and that the originals stayed",
marker["originalsLeftInPlace"] == true
&& marker["markerVersion"] == 2
&& listed("copied", "recovery/20260917-101500/bracket.SLDPRT")
&& marker["copied"].as_array().map(|a| a.iter().all(|e| {
e["legacyRoot"].is_string() && e["bytes"].is_u64() && e["modifiedMs"].is_u64()
&& e["sha256"].as_str().map(|h| h.len() == 64).unwrap_or(false)
})).unwrap_or(false),
s(&marker)
);
if unreadable {
check!(
b,
"migration: a copy that cannot be made is reported, and leaves NOTHING behind in the data root",
listed("failed", "recovery/20260917-101500/stuck.SLDPRT")
&& !data.join("recovery/20260917-101500/stuck.SLDPRT").exists(),
s(&marker)
);
}
// THE LISTING, AFTER: the copy is the one to use, and the original says so.
let l = b.call("swx_recover_list", json!({}));
let both = sweeps_named(&l, "20260917-101500");
let ours = both.iter().find(|x| x["location"] == "data").cloned().cloned().unwrap_or(json!({}));
let theirs = both.iter().find(|x| x["location"] == "legacyInstallCache").cloned().cloned().unwrap_or(json!({}));
let bracket = ours["files"]
.as_array()
.and_then(|a| a.iter().find(|f| f["name"] == "bracket.SLDPRT").cloned())
.unwrap_or(json!({}));
check!(
b,
"swx_recover_list: the migrated rescue is listed from the data root, with the sweep's own verdict",
bracket["source"] == "manifest" && bracket["verified"] == true && ours["atRiskFromUpdate"] == false,
s(&l)
);
check!(
b,
"swx_recover_list: the original is listed from the install cache, AT RISK",
theirs["atRiskFromUpdate"] == true,
s(&l)
);
if unreadable {
// One of its files did not copy, so this is still the only copy of it.
check!(
b,
"swx_recover_list: a legacy sweep with a file that did not copy claims NO copy",
theirs["copiedTo"].is_null()
&& theirs["warning"].as_str().unwrap_or("").contains("NO copy"),
s(&theirs)
);
// AND THE DATA-ROOT SIDE DOES NOT CALL IT GONE. Its manifest came across
// and names the file; the file is still in the old location.
let stuck_here = ours["files"]
.as_array()
.and_then(|a| a.iter().find(|f| f["name"] == "stuck.SLDPRT").cloned())
.unwrap_or(json!({}));
check!(
b,
"swx_recover_list: a file whose copy failed is NOT COPIED YET, pointing at its only copy — not GONE",
stuck_here["notCopiedYet"] == true
&& stuck_here["present"] == false
&& stuck_here["legacyPath"] == json!(stuck.to_string_lossy())
&& stuck_here["howToOpen"].as_str().unwrap_or("").contains("NOT COPIED YET"),
s(&ours)
);
}
drop(b);
// ── SECOND START. The user deleted one migrated copy; the unreadable file
// became readable; and an older bridge (a rollback) wrote a new rescue.
std::fs::remove_file(data.join("exports/scratch/Part1.SLDPRT")).unwrap();
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(&stuck, std::fs::Permissions::from_mode(0o644)).unwrap();
}
let late = a_document(48 * 1024);
a_legacy_sweep(&legacy.join("recovery"), "20260930-120000", "late.SLDPRT", &late);
let b = Bridge::start_with(&env);
check!(
b,
"migration: a rescue that appeared since is copied on the next start",
same("recovery/20260930-120000/late.SLDPRT", &late),
data.display().to_string()
);
check!(
b,
"migration: a copy the user deleted is NOT brought back — each file is migrated once",
!data.join("exports/scratch/Part1.SLDPRT").exists(),
data.display().to_string()
);
if unreadable {
check!(
b,
"migration: a copy that failed is retried on the next start",
data.join("recovery/20260917-101500/stuck.SLDPRT").exists(),
data.display().to_string()
);
let l = b.call("swx_recover_list", json!({}));
let theirs = sweeps_named(&l, "20260917-101500")
.into_iter()
.find(|x| x["location"] == "legacyInstallCache")
.cloned()
.unwrap_or(json!({}));
let ours = sweeps_named(&l, "20260917-101500")
.into_iter()
.find(|x| x["location"] == "data")
.cloned()
.unwrap_or(json!({}));
let stuck_here = ours["files"]
.as_array()
.and_then(|a| a.iter().find(|f| f["name"] == "stuck.SLDPRT").cloned())
.unwrap_or(json!({}));
check!(
b,
"swx_recover_list: ...and the retried copy is listed as a rescue in the data root",
stuck_here["source"] == "manifest" && stuck_here["verified"] == true,
s(&ours)
);
check!(
b,
"swx_recover_list: once every file has a copy, the original points at it",
theirs["copiedTo"] == json!(data.join("recovery").join("20260917-101500").to_string_lossy())
&& theirs["warning"].as_str().unwrap_or("").contains("use that one"),
s(&theirs)
);
}
check!(
b,
"migration: the second run is on the record too",
ledger_count(&data, "data_migrated_from_install_cache") == 2,
std::fs::read_to_string(data.join("ledger.jsonl")).unwrap_or_default()
);
drop(b);
// ── THIRD START, nothing new: nothing is copied and nothing is recorded.
let b = Bridge::start_with(&env);
b.call("swx_recover_list", json!({}));
check!(
b,
"migration: a start with nothing new to copy does nothing",
ledger_count(&data, "data_migrated_from_install_cache") == 2
&& !data.join("exports/scratch/Part1.SLDPRT").exists(),
std::fs::read_to_string(data.join("ledger.jsonl")).unwrap_or_default()
);
}
/// A sweep whose recovery folder would land under an install location — the
/// install directory itself, or ab's install cache — is refused before anything
/// is written or closed, however the path is spelled.
#[test]
fn a_sweep_refuses_to_place_recovery_under_an_install_location() {
let t = TempDir::new("dataroot-refuse");
let inst = t.join("inst");
std::fs::create_dir_all(&inst).unwrap();
std::os::unix::fs::symlink(&inst, t.join("link")).unwrap();
std::fs::create_dir_all(t.join("elsewhere")).unwrap();
let p = |x: &Path| x.to_string_lossy().to_string();
let unrelated = p(&t.join("unrelated"));
// ab's own directory, as `%LOCALAPPDATA%\Adom Bridge` — given only to the
// case that needs it; everywhere else LOCALAPPDATA is empty, i.e. unset.
let local = t.join("local");
// (label, data root, install dir, ab's install cache, LOCALAPPDATA)
let cases: Vec<(&str, String, String, String, String)> = vec![
("inside the install directory", p(&inst.join("data")), p(&inst), unrelated.clone(), String::new()),
("through a link to it", p(&t.join("link").join("data")), p(&inst), unrelated.clone(), String::new()),
("spelled with ..", format!("{}/../inst/data", p(&t.join("elsewhere"))), p(&inst), unrelated.clone(), String::new()),
("spelled in another case", p(&inst.join("data")).replace("/inst/", "/INST/"), p(&inst), unrelated.clone(), String::new()),
("AS the install directory", p(&t.join("eq")), p(&t.join("eq").join("recovery")), unrelated.clone(), String::new()),
("inside ab's install cache", p(&t.join("cache").join("data")), unrelated.clone(), p(&t.join("cache")), String::new()),
// 0.9.0's OWN ROOT: inside the directory ab's installer and uninstaller own
// (adversary pass 39). Not the cache, not the install directory — refused
// for whose directory it is.
("inside ab's own directory", p(&local.join("Adom Bridge").join("solidworks-data")), unrelated.clone(), unrelated.clone(), p(&local)),
];
for (label, root, install, cache, localappdata) in cases {
// The mock session lives in the data root; for the case-spelled variant,
// at the path as the filesystem sees it.
let seed = PathBuf::from(if label == "spelled in another case" { p(&inst.join("data")) } else { root.clone() });
a_session_with_unsaved_work(&seed);
let machine = a_visible_machine("dataroot-refuse-machine");
let b = Bridge::start_with(&[
("SWX_SCRATCH_DIR", &root),
("SWX_INSTALL_DIR", &install),
("SWX_LEGACY_DIR", &cache),
("LOCALAPPDATA", &localappdata),
("SWX_LAUNCH_FAKE_DIR", &machine.path()),
]);
b.call("swx_start", json!({}));
let before = b.call("swx_docs", json!({}));
let r = b.call("swx_close_all_safely", json!({"userPresentOverride": true, "userAuthorized": true, "reason": "the user asked me to close SOLIDWORKS"}));
check!(
b,
&format!("swx_close_all_safely: refuses a recovery folder {label}"),
r["success"] == false && r["errorCode"] == "recovery_inside_install_dir",
s(&r)
);
let after = b.call("swx_docs", json!({}));
check!(
b,
&format!("swx_close_all_safely: ...and closes NOTHING when it refuses ({label})"),
before["count"].as_u64().is_some() && before["count"] == after["count"],
format!("before {} after {}", s(&before), s(&after))
);
check!(
b,
&format!("swx_close_all_safely: ...and writes nothing ({label})"),
!seed.join("recovery").exists(),
seed.display().to_string()
);
check!(
b,
&format!("swx_close_all_safely: the bridge said so in its log at start ({label})"),
b.log().contains("WARNING recovery is inside an install location"),
b.log()
);
}
// A SIBLING THAT MERELY SHARES A PREFIX is not inside anything.
let sibling = p(&t.join("inst-data"));
a_session_with_unsaved_work(Path::new(&sibling));
let machine = a_visible_machine("dataroot-refuse-sibling");
let b = Bridge::start_with(&[
("SWX_SCRATCH_DIR", &sibling),
("SWX_INSTALL_DIR", &p(&inst)),
("SWX_LEGACY_DIR", &unrelated),
("SWX_LAUNCH_FAKE_DIR", &machine.path()),
]);
b.call("swx_start", json!({}));
let r = b.call("swx_close_all_safely", json!({"userPresentOverride": true, "userAuthorized": true, "reason": "the user asked me to close SOLIDWORKS"}));
check!(
b,
"swx_close_all_safely: a data root beside the install directory is not refused",
r["success"] == true && r["done"] == true,
s(&r)
);
}
/// A rescue that exists ONLY in the install cache — written by an older bridge
/// after this one started, say — is listed, and the listing says it is the only
/// copy and that the next update deletes it.
#[test]
fn recover_list_finds_a_rescue_left_only_in_the_install_cache() {
let t = TempDir::new("dataroot-legacy-list");
let data = t.join("data");
let cache = t.join("cache");
std::fs::create_dir_all(&data).unwrap();
let b = Bridge::start_with(&[
("SWX_SCRATCH_DIR", &data.to_string_lossy()),
("SWX_LEGACY_DIR", &cache.to_string_lossy()),
]);
let rescue = a_document(40 * 1024);
a_legacy_sweep(&cache.join("recovery"), "20260929-080000", "gear.SLDPRT", &rescue);
let l = b.call("swx_recover_list", json!({}));
let sweep = sweeps_named(&l, "20260929-080000").first().cloned().cloned().unwrap_or(json!({}));
check!(
b,
"swx_recover_list: a rescue left in the install cache is listed",
sweep["location"] == "legacyInstallCache",
s(&l)
);
check!(
b,
"swx_recover_list: ...marked at risk from the next update, as the ONLY copy",
sweep["atRiskFromUpdate"] == true
&& sweep["copiedTo"].is_null()
&& sweep["warning"].as_str().unwrap_or("").contains("NO copy"),
s(&sweep)
);
let gear = sweep["files"]
.as_array()
.and_then(|a| a.iter().find(|f| f["name"] == "gear.SLDPRT").cloned())
.unwrap_or(json!({}));
check!(
b,
"swx_recover_list: ...and its verdict is read from its own record, as anywhere else",
gear["source"] == "manifest" && gear["verified"] == true && gear["bytes"] == rescue.len(),
s(&gear)
);
}
/// A COPY THAT DOES NOT MATCH ITS ORIGINAL IS NOT A COPY. It is removed — a
/// half-copied rescue must never sit in the data root looking like a whole one —
/// the failure is on the record, the original stays where it was, the listing
/// says the original is the only copy, and the next start tries again.
#[test]
fn a_copy_that_does_not_match_its_original_is_discarded_and_retried() {
let lay = Layout::new("dataroot-corrupt");
let rescue = a_document(64 * 1024);
let sweep_dir = a_legacy_sweep(&lay.legacy().join("recovery"), "20260917-090000", "shaft.SLDPRT", &rescue);
let data = lay.data();
std::fs::create_dir_all(&data).unwrap();
std::fs::write(data.join("mock-migrate-corrupts-copy"), "").unwrap();
let local = lay.local.path();
let env = [("SWX_SCRATCH_DIR", ""), ("LOCALAPPDATA", local.as_str())];
let b = Bridge::start_with(&env);
let marker: Value = serde_json::from_str(
&std::fs::read_to_string(data.join("migrated-from-legacy.json")).unwrap_or_default(),
)
.unwrap_or(json!({}));
let failed = |m: &Value, f: &str| m["failed"].as_array().map(|a| a.iter().any(|e| e["file"] == f)).unwrap_or(false);
check!(
b,
"migration: a copy that does not verify is reported as failed",
failed(&marker, "recovery/20260917-090000/shaft.SLDPRT"),
s(&marker)
);
check!(
b,
"migration: ...and REMOVED, so no partial rescue sits in the data root",
!data.join("recovery/20260917-090000/shaft.SLDPRT").exists(),
data.display().to_string()
);
check!(
b,
"migration: ...and the original is untouched",
std::fs::read(sweep_dir.join("shaft.SLDPRT")).ok().as_deref() == Some(&rescue[..]),
sweep_dir.display().to_string()
);
let l = b.call("swx_recover_list", json!({}));
check!(
b,
"migration: a sweep none of whose files copied leaves no empty folder to be listed beside it",
sweeps_named(&l, "20260917-090000").len() == 1
&& !data.join("recovery").join("20260917-090000").exists(),
s(&l)
);
let theirs = sweeps_named(&l, "20260917-090000")
.into_iter()
.find(|x| x["location"] == "legacyInstallCache")
.cloned()
.unwrap_or(json!({}));
check!(
b,
"swx_recover_list: the original is the only copy, and says so",
theirs["atRiskFromUpdate"] == true
&& theirs["copiedTo"].is_null()
&& theirs["warning"].as_str().unwrap_or("").contains("NO copy"),
s(&l)
);
drop(b);
// THE DISK RECOVERS. The next start retries, and this time it lands.
std::fs::remove_file(data.join("mock-migrate-corrupts-copy")).unwrap();
let b = Bridge::start_with(&env);
let copy = data.join("recovery/20260917-090000/shaft.SLDPRT");
check!(
b,
"migration: a copy that failed is retried on the next start, and verified",
std::fs::read(©).ok().as_deref() == Some(&rescue[..]),
copy.display().to_string()
);
let marker: Value = serde_json::from_str(
&std::fs::read_to_string(data.join("migrated-from-legacy.json")).unwrap_or_default(),
)
.unwrap_or(json!({}));
check!(
b,
"migration: ...and the record no longer lists it as failing",
!failed(&marker, "recovery/20260917-090000/shaft.SLDPRT")
&& marker["copied"].as_array().map(|a| a.iter().any(|e| e["file"] == "recovery/20260917-090000/shaft.SLDPRT")).unwrap_or(false),
s(&marker)
);
}
/// THE MIGRATION RUNS BEFORE THE BRIDGE READS ITS OWN STATE.
///
/// `Ctx::new` restores the ownership registry from the data root. A migration
/// that ran after it would copy the registry into place and leave the running
/// bridge owning nothing until its NEXT restart — so the first bridge after an
/// update would refuse to close or write to every document it had created
/// before it, which is the update-shaped version of bug #5.
#[test]
fn ownership_an_older_bridge_recorded_is_restored_by_the_first_start_after_the_move() {
let lay = Layout::new("dataroot-ownership");
let legacy = lay.legacy();
std::fs::create_dir_all(&legacy).unwrap();
// AN OLDER BRIDGE, writing into the install cache, creates a document.
{
let old = Bridge::start_in(TempDir::borrowed(&legacy), &[]);
old.call("swx_start", json!({}));
let made = old.call("swx_new_document", json!({"type": "part", "keepUnsaved": true}));
check!(old, "precondition: the older bridge created a document", made["success"] == true, s(&made));
check!(
old,
"precondition: ...and recorded that it owns it, in the install cache",
std::fs::read_to_string(legacy.join("owned-docs.json")).unwrap_or_default().contains("Part"),
legacy.display().to_string()
);
}
// SOLIDWORKS KEEPS RUNNING ACROSS THE UPDATE. The simulator keeps its session
// in the bridge's root, so it is carried over by hand — that is the
// simulator's plumbing, not the bridge's data, and not part of what is tested.
let data = lay.data();
std::fs::create_dir_all(&data).unwrap();
for e in std::fs::read_dir(&legacy).unwrap().flatten() {
let name = e.file_name().to_string_lossy().to_string();
if name.starts_with("mock-") && e.path().is_file() {
std::fs::copy(e.path(), data.join(&name)).unwrap();
}
}
let local = lay.local.path();
let b = Bridge::start_with(&[("SWX_SCRATCH_DIR", ""), ("LOCALAPPDATA", local.as_str())]);
let st = b.call("swx_status", json!({}));
check!(
b,
"migration: the FIRST start after the move already owns what the older bridge created",
b.log().contains("ownership registry restored") && st["success"] == true,
b.log()
);
}
/// A START THAT IS EXPECTED TO DIE, under the mock-only interruption fault.
/// Spawned directly — the harness rightly treats a bridge that never answers as a
/// failure — and never waited on forever: if the fault were not seen, the bridge
/// would serve until killed, and a suite that HANGS instead of failing hides the
/// very regression it exists to catch. Returns the exit code, or `None` if it
/// had to be killed.
fn a_start_that_should_die(local: &str) -> Option<i32> {
let mut child = std::process::Command::new(common::BIN)
.args(["--port", &common::free_port().to_string()])
.env("SWX_MOCK", "1")
.env("ADOM_BIND_HOST", "127.0.0.1")
.env("SWX_SCRATCH_DIR", "")
.env("LOCALAPPDATA", local)
.env_remove("ADOM_DIRECT_API_URL")
.env_remove("ADOM_BRIDGE_TOKEN")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.spawn()
.expect("run the bridge");
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(30);
loop {
if let Ok(Some(status)) = child.try_wait() {
return status.code();
}
if std::time::Instant::now() > deadline {
let _ = child.kill();
let _ = child.wait();
return None;
}
std::thread::sleep(std::time::Duration::from_millis(50));
}
}
/// A START THAT DIES MID-COPY leaves nothing under the real name.
///
/// The migration runs at start-up, and a start can die at any point — a crash, a
/// power cut, ab killing a bridge that is slow to answer `/status`. A copy made
/// straight to the real name would leave a TRUNCATED file there, and the
/// never-overwrite rule would then protect the truncated copy forever while the
/// next update deleted the only whole one. So every copy is made under a
/// temporary name and renamed into place once it verifies. And if the original
/// is gone by the time anyone looks, the half-copied bytes are listed as what
/// they are: incomplete, and possibly all that is left.
#[test]
fn a_start_that_dies_mid_copy_leaves_no_truncated_file_under_the_real_name() {
let lay = Layout::new("dataroot-interrupted");
let rescue = a_document(64 * 1024);
// `zeta` sorts after `manifest.json`, and the start is let through ONE copy:
// so it dies with the record already across and the rescue half-written —
// the state in which a listing could call the rescue GONE.
let legacy_sweep = a_legacy_sweep(&lay.legacy().join("recovery"), "20260917-080000", "zeta.SLDPRT", &rescue);
let data = lay.data();
std::fs::create_dir_all(&data).unwrap();
std::fs::write(data.join("mock-migrate-dies-mid-copy"), "1").unwrap();
let local = lay.local.path();
// THE START THAT DIES. Spawned directly: the harness rightly treats a bridge
// that never answers as a failure.
assert_eq!(a_start_that_should_die(&local), Some(3), "precondition: the start died mid-copy");
let here = data.join("recovery").join("20260917-080000");
let left: Vec<String> = std::fs::read_dir(&here)
.map(|d| d.flatten().map(|e| e.file_name().to_string_lossy().to_string()).collect())
.unwrap_or_default();
assert!(
left.iter().any(|n| n == "manifest.json")
&& left.iter().any(|n| n == "zeta.SLDPRT.migrating")
&& !left.iter().any(|n| n == "zeta.SLDPRT"),
"a start that died mid-copy must leave its half-written bytes ONLY under a temporary name, \
never under the real one — the never-overwrite rule would protect a truncated rescue \
forever: {left:?}"
);
// THE UPDATE ARRIVES before the bridge starts again, and takes the original.
std::fs::remove_dir_all(&legacy_sweep).unwrap();
std::fs::remove_file(data.join("mock-migrate-dies-mid-copy")).unwrap();
let b = Bridge::start_with(&[("SWX_SCRATCH_DIR", ""), ("LOCALAPPDATA", local.as_str())]);
let l = b.call("swx_recover_list", json!({}));
let ours = sweeps_named(&l, "20260917-080000").first().cloned().cloned().unwrap_or(json!({}));
let files = ours["files"].as_array().cloned().unwrap_or_default();
// TWO entries, and they agree: the partial bytes, which are not a rescue,
// and the file the manifest names, which says ONLY A PARTIAL COPY of it
// exists — not GONE, and not "moved or deleted by something else", because
// nothing moved it: the copy never finished.
let partial = files.iter().find(|f| f["name"] == "zeta.SLDPRT.migrating").cloned().unwrap_or(json!({}));
let named = files.iter().find(|f| f["name"] == "zeta.SLDPRT").cloned().unwrap_or(json!({}));
check!(
b,
"swx_recover_list: the half-copied bytes are listed as a PARTIAL COPY, never as a rescue",
files.len() == 2
&& partial["partialCopy"] == true
&& partial["copyOf"] == "zeta.SLDPRT"
&& partial["verified"] == false
&& partial["howToOpen"].as_str().unwrap_or("").contains("PARTIAL COPY"),
s(&l)
);
let how = named["howToOpen"].as_str().unwrap_or("");
check!(
b,
"swx_recover_list: ...and the file it was copying says ONLY A PARTIAL COPY exists — not GONE",
named["present"] == false
&& named["partialCopyOnly"] == true
&& named["partialCopy"] == "zeta.SLDPRT.migrating"
&& named["verified"] == false
&& how.contains("ONLY A PARTIAL COPY EXISTS")
&& !how.contains("GONE")
&& !how.contains("moved or deleted by something else"),
s(&named)
);
drop(b);
// AND WHEN THE ORIGINAL IS STILL THERE, the next start simply copies it.
let lay = Layout::new("dataroot-interrupted-retry");
let legacy_sweep = a_legacy_sweep(&lay.legacy().join("recovery"), "20260917-081500", "gear.SLDPRT", &rescue);
let data = lay.data();
std::fs::create_dir_all(&data).unwrap();
std::fs::write(data.join("mock-migrate-dies-mid-copy"), "").unwrap();
let local = lay.local.path();
assert_eq!(a_start_that_should_die(&local), Some(3), "precondition: the start died mid-copy");
std::fs::remove_file(data.join("mock-migrate-dies-mid-copy")).unwrap();
let b = Bridge::start_with(&[("SWX_SCRATCH_DIR", ""), ("LOCALAPPDATA", local.as_str())]);
let here = data.join("recovery").join("20260917-081500");
let names: Vec<String> = std::fs::read_dir(&here)
.map(|d| d.flatten().map(|e| e.file_name().to_string_lossy().to_string()).collect())
.unwrap_or_default();
check!(
b,
"migration: the next start copies the file whole and leaves no temporary behind",
std::fs::read(here.join("gear.SLDPRT")).ok().as_deref() == Some(&rescue[..])
&& std::fs::read(here.join("manifest.json")).ok()
== std::fs::read(legacy_sweep.join("manifest.json")).ok()
&& !names.iter().any(|n| n.ends_with(".migrating")),
format!("{names:?}")
);
}
/// THE DATA ROOT IS A DIRECTORY ONLY THIS BRIDGE WRITES TO (adversary pass 39).
///
/// 0.9.0 moved the data root out of the directory ab re-extracts on every bridge
/// update, and into `%LOCALAPPDATA%\Adom Bridge\solidworks-data` — inside the
/// directory ab's INSTALLER owns ("installs to %LOCALAPPDATA%\Adom Bridge\", "the
/// uninstaller lives at %LOCALAPPDATA%\Adom Bridge\uninstall.exe", per ab's
/// docs). What an ab uninstall, a repair or "delete the Adom Bridge folder and
/// reinstall" does to the rest of that directory is undocumented and unmeasured.
/// So the root is a SIBLING, named for this bridge alone: not under any path
/// that contains `Adom Bridge`, and not under the install cache.
#[test]
fn the_data_root_is_a_directory_only_this_bridge_writes() {
let lay = Layout::new("dataroot-sibling");
let local = lay.local.path();
let b = Bridge::start_with(&[("SWX_SCRATCH_DIR", ""), ("LOCALAPPDATA", local.as_str())]);
let l = b.call("swx_recover_list", json!({}));
let recovery = PathBuf::from(l["recoveryRoot"].as_str().unwrap_or(""));
let root = recovery.parent().map(|p| p.to_path_buf()).unwrap_or_default();
check!(
b,
"swx_recover_list: the data root is %LOCALAPPDATA%\\adom-solidworks-bridge",
root == lay.data(),
format!("{} vs {}", root.display(), lay.data().display())
);
check!(
b,
"swx_recover_list: ...not under ANY path containing `Adom Bridge`",
!root
.components()
.any(|c| c.as_os_str().to_string_lossy().to_ascii_lowercase().contains("adom bridge")),
root.display().to_string()
);
check!(
b,
"swx_recover_list: ...and not under ab's directory or its install cache",
!root.starts_with(lay.ab()) && !root.starts_with(lay.cache()),
root.display().to_string()
);
}
/// TWO LEGACY ROOTS, OLDEST FIRST. 0.9.0's data root — `Adom Bridge\solidworks-data`
/// — is migrated like 0.8.x's install cache, after it. Rescues with distinct
/// names simply move. A LEDGER in both is MERGED (0.9.2): 0.9.1 gave the real
/// name to the first root's copy and put the second BESIDE it, and adversary pass
/// 40 showed that picks the stale one on exactly Drew's timeline — the cache
/// re-created by 0.8.9 after 0.9.0 had copied it and kept appending, so the 0.9.0
/// era sat in a side file `swx_action_ledger` never reads. This test pinned that
/// naming rule; it is changed ON PURPOSE: the live ledger is now the union of
/// both, ordered by `at`, and both originals are kept beside it. 0.9.0's own
/// migration record is not data and does not come along.
#[test]
fn the_0_9_0_data_root_is_migrated_after_the_install_cache_and_nothing_is_lost() {
let lay = Layout::new("dataroot-two-legacies");
let cache_ledger = b"{\"at\":1788000000,\"action\":\"document_created\",\"detail\":{\"title\":\"FROM-0.8.9\"}}\n".to_vec();
let mut data_ledger = cache_ledger.clone();
data_ledger.extend_from_slice(b"{\"at\":1789000000,\"action\":\"close_all_safely\",\"detail\":{\"title\":\"FROM-0.9.0\"}}\n");
write(&lay.legacy().join("ledger.jsonl"), &cache_ledger);
write(&lay.legacy_data().join("ledger.jsonl"), &data_ledger);
write(&lay.legacy_data().join("owned-docs.json"), br#"{"documents":[]}"#);
write(&lay.legacy_data().join("migrated-from-legacy.json"), br#"{"copied":[],"why":"0.9.0's own record"}"#);
let old = a_document(48 * 1024);
let newer = a_document(56 * 1024);
a_legacy_sweep(&lay.legacy().join("recovery"), "20260917-090000", "old.SLDPRT", &old);
let newer_sweep = a_legacy_sweep(&lay.legacy_data().join("recovery"), "20260930-170000", "newer.SLDPRT", &newer);
// THE SAME SWEEP AND NAME IN BOTH, WITH DIFFERENT BYTES: contrived, and the
// case where "never over it, never dropped" has to hold for a rescue.
let mut other = a_document(48 * 1024);
other[100] = b'y';
write(&lay.legacy_data().join("recovery").join("20260917-090000").join("old.SLDPRT"), &other);
let local = lay.local.path();
let b = Bridge::start_with(&[("SWX_SCRATCH_DIR", ""), ("LOCALAPPDATA", local.as_str())]);
let data = lay.data();
let read = |rel: &str| std::fs::read(data.join(rel)).unwrap_or_default();
check!(
b,
"migration: rescues from BOTH older locations are copied into the data root",
read("recovery/20260917-090000/old.SLDPRT") == old
&& read("recovery/20260930-170000/newer.SLDPRT") == newer
&& read("owned-docs.json") == br#"{"documents":[]}"#.to_vec(),
data.display().to_string()
);
let marker: Value = serde_json::from_str(
&std::fs::read_to_string(data.join("migrated-from-legacy.json")).unwrap_or_default(),
)
.unwrap_or(json!({}));
let ledger_besides: Vec<String> = std::fs::read_dir(&data)
.map(|d| {
d.flatten()
.map(|e| e.file_name().to_string_lossy().to_string())
.filter(|n| n.starts_with("ledger.jsonl.legacy-"))
.collect()
})
.unwrap_or_default();
// THE LIVE LEDGER IS THE MERGE: every line of both, each once, in `at` order
// — here that is exactly 0.9.0's (a superset of the cache's), followed by
// whatever this start itself recorded.
let live = read("ledger.jsonl");
let mut beside_contents: Vec<Vec<u8>> = ledger_besides.iter().map(|n| read(n)).collect();
beside_contents.sort();
let mut want = vec![cache_ledger.clone(), data_ledger.clone()];
want.sort();
check!(
b,
"migration: two ledgers are MERGED into the live one, and BOTH originals are kept beside it",
live.starts_with(&data_ledger) && beside_contents == want,
format!("{ledger_besides:?}\n{}", s(&marker))
);
let merge = marker["merged"].as_array().and_then(|a| a.iter().find(|m| m["file"] == "ledger.jsonl").cloned()).unwrap_or(json!({}));
check!(
b,
"migration: ...and the merge is on the record, with where each original went and what the result holds",
merge["kind"] == "ledger"
&& merge["result"]["entries"] == 2
&& merge["liveBefore"]["copiedAs"].as_str().map(|n| n.starts_with("ledger.jsonl.legacy-")).unwrap_or(false)
&& merge["incoming"]["copiedAs"].as_str().map(|n| n.starts_with("ledger.jsonl.legacy-")).unwrap_or(false)
&& ledger_count(&data, "legacy_files_merged") == 1,
s(&marker)
);
check!(
b,
"migration: the order is recorded, oldest first, and 0.9.0's own record is not copied as data",
marker["from"]
== json!([lay.legacy().to_string_lossy(), lay.legacy_data().to_string_lossy()])
&& marker["markerVersion"] == 2,
s(&marker)
);
// A FILE ONLY THE SECOND LEGACY ROOT HOLDS, named by a record in the data
// root (built after the start, so the migration cannot dissolve the state).
write(&lay.legacy_data().join("recovery").join("20260930-170000").join("late.SLDPRT"), &a_document(40 * 1024));
write(
&data.join("recovery").join("20260930-170000").join("manifest.json"),
json!({"manifestVersion": 1, "files": [
{"name": "newer.SLDPRT", "title": "newer.SLDPRT", "kind": "part", "recoveryBytes": newer.len(), "verified": true, "preserved": true, "closed": true},
{"name": "late.SLDPRT", "title": "late.SLDPRT", "kind": "part", "recoveryBytes": 40 * 1024, "verified": true, "preserved": true, "closed": true},
]}).to_string().as_bytes(),
);
let l = b.call("swx_recover_list", json!({}));
let ours = sweeps_named(&l, "20260930-170000")
.into_iter()
.find(|x| x["location"] == "data")
.cloned()
.unwrap_or(json!({}));
let late = ours["files"].as_array().and_then(|a| a.iter().find(|f| f["name"] == "late.SLDPRT").cloned()).unwrap_or(json!({}));
check!(
b,
"swx_recover_list: a file only 0.9.0's location holds is NOT COPIED YET, pointing there",
late["notCopiedYet"] == true
&& late["verified"].is_null()
&& late["legacyPath"]
== json!(lay.legacy_data().join("recovery").join("20260930-170000").join("late.SLDPRT").to_string_lossy()),
s(&ours)
);
let first = sweeps_named(&l, "20260917-090000")
.into_iter()
.find(|x| x["location"] == "data")
.cloned()
.unwrap_or(json!({}));
let beside = first["files"]
.as_array()
.and_then(|a| a.iter().find(|f| f["copiedBeside"] == true).cloned())
.unwrap_or(json!({}));
check!(
b,
"migration + swx_recover_list: a same-named rescue with different bytes is kept BESIDE, and listed as such",
read("recovery/20260917-090000/old.SLDPRT") == old
&& beside["besideOf"] == "old.SLDPRT"
&& std::fs::read(beside["path"].as_str().unwrap_or("")).ok() == Some(other.clone())
&& beside["howToOpen"].as_str().unwrap_or("").contains("A COPY KEPT BESIDE")
&& !first["files"].as_array().map(|a| a.iter().any(|f| f["name"].as_str().unwrap_or("").starts_with("manifest.json"))).unwrap_or(true),
s(&first)
);
let theirs = sweeps_named(&l, "20260930-170000")
.into_iter()
.find(|x| x["location"] == "legacyDataRoot")
.cloned()
.unwrap_or(json!({}));
// `late.SLDPRT` has no copy, so this sweep is still the only copy of it.
check!(
b,
"swx_recover_list: a rescue in 0.9.0's location is listed, at risk (ab's directory), honest about its copy",
theirs["atRisk"] == true
&& theirs["atRiskFromUpdate"] == false
&& theirs["copiedTo"].is_null()
&& theirs["warning"].as_str().unwrap_or("").contains("ab's own directory")
&& theirs["directory"] == json!(newer_sweep.to_string_lossy()),
s(&theirs)
);
let older = sweeps_named(&l, "20260917-090000")
.into_iter()
.find(|x| x["location"] == "legacyInstallCache")
.cloned()
.unwrap_or(json!({}));
check!(
b,
"swx_recover_list: a fully copied legacy sweep points at its copy",
older["copiedTo"] == json!(data.join("recovery").join("20260917-090000").to_string_lossy()),
s(&older)
);
}
/// A LEGACY FILE THAT CHANGES AFTER ITS COPY IS COPIED AGAIN — BESIDE THE FIRST
/// COPY, NEVER OVER IT — AND SAID TO HAVE CHANGED (adversary pass 39, sev-2).
///
/// 0.9.0 settled a migrated file by its NAME, so the two files that change by
/// nature — the append-only ledger and the rewritten ownership registry — were
/// never looked at again: a rollback to an older bridge appends to the OLD
/// ledger, and the next update deletes it. Now each copy is recorded by (legacy
/// root, path, size, mtime, sha256 of the copy), and a later start that finds the
/// legacy file different copies it again as `<name>.legacy-<stamp>` and reports
/// it — in the migration record, in the ledger, and in `swx_recover_list`.
#[test]
fn a_legacy_file_that_changes_after_its_copy_is_copied_again_beside_it() {
let lay = Layout::new("dataroot-changed");
let legacy_ledger = lay.legacy().join("ledger.jsonl");
let legacy_owned = lay.legacy().join("owned-docs.json");
let before = b"{\"at\":1789000000,\"action\":\"document_created\",\"detail\":{\"title\":\"BEFORE\"}}\n".to_vec();
write(&legacy_ledger, &before);
write(&legacy_owned, br#"{"documents":[]}"#);
let legacy_settings = lay.legacy().join("settings.json");
write(&legacy_settings, br#"{"badge":false}"#);
write(&lay.legacy().join("timings.json"), br#"{"swx_docs":{"count":3,"x":1}}"#);
let local = lay.local.path();
let env = [("SWX_SCRATCH_DIR", ""), ("LOCALAPPDATA", local.as_str())];
let data = lay.data();
let besides = |prefix: &str| -> Vec<String> {
let mut v: Vec<String> = std::fs::read_dir(&data)
.map(|d| {
d.flatten()
.map(|e| e.file_name().to_string_lossy().to_string())
.filter(|n| n.starts_with(prefix))
.collect()
})
.unwrap_or_default();
v.sort();
v
};
let marker = || -> Value {
serde_json::from_str(&std::fs::read_to_string(data.join("migrated-from-legacy.json")).unwrap_or_default())
.unwrap_or(json!({}))
};
drop(Bridge::start_with(&env));
let first_copy = std::fs::read(data.join("ledger.jsonl")).unwrap_or_default();
assert!(first_copy.starts_with(&before), "precondition: the first start copied the legacy ledger");
// A ROLLBACK: the older bridge appends to its ledger and rewrites its registry.
let mut rolled = before.clone();
rolled.extend_from_slice(b"{\"at\":1790000000,\"action\":\"document_closed\",\"detail\":{\"title\":\"DURING-ROLLBACK\"}}\n");
write(&legacy_ledger, &rolled);
write(&legacy_owned, br#"{"documents":[{"title":"Part9","session":4242}]}"#);
// A DIFFERENT SIZE WITH THE SAME MTIME: a restore that keeps timestamps, or a
// filesystem whose mtime is coarser than the two writes (FAT32 keeps two
// seconds). Only the size can tell.
let legacy_timings = lay.legacy().join("timings.json");
let timings_mtime = std::fs::metadata(&legacy_timings).unwrap().modified().unwrap();
write(&legacy_timings, br#"{"swx_docs":{"count":4}}"#);
std::fs::File::options()
.write(true)
.open(&legacy_timings)
.unwrap()
.set_modified(timings_mtime)
.unwrap();
// SAME SIZE, DIFFERENT CONTENT, LATER MTIME: only the mtime can tell.
let settings_was = std::fs::metadata(&legacy_settings).unwrap().modified().unwrap();
write(&legacy_settings, br#"{"badge":"no!"}"#);
std::fs::File::options()
.write(true)
.open(&legacy_settings)
.unwrap()
.set_modified(settings_was + std::time::Duration::from_secs(60))
.unwrap();
let b = Bridge::start_with(&env);
let ledger_besides = besides("ledger.jsonl.legacy-");
let owned_besides = besides("owned-docs.json.legacy-");
// 0.9.2: A CHANGED LEDGER IS MERGED INTO THE LIVE ONE (adversary pass 40).
// 0.9.1 kept the rollback's entries only in a side file, which
// `swx_action_ledger` never reads — the same defect as picking one of two
// roots' ledgers. Now the live ledger holds both histories, and BOTH originals
// are kept beside it: the live file as it was before the merge, and the
// changed legacy version.
let live_text = String::from_utf8_lossy(&std::fs::read(data.join("ledger.jsonl")).unwrap_or_default()).to_string();
check!(
b,
"migration: the CHANGED legacy ledger is MERGED into the live one — both histories, each line once",
live_text.contains("BEFORE")
&& live_text.contains("DURING-ROLLBACK")
&& live_text.matches("\"title\":\"BEFORE\"").count() == 1,
live_text.clone()
);
let beside_ledgers: Vec<Vec<u8>> = ledger_besides.iter().map(|n| std::fs::read(data.join(n)).unwrap_or_default()).collect();
check!(
b,
"migration: ...and both versions are kept beside it: the live file as it was, and the changed one",
ledger_besides.len() == 2
&& beside_ledgers.iter().any(|v| v == &rolled)
&& beside_ledgers.iter().any(|v| v.starts_with(&before) && !String::from_utf8_lossy(v).contains("DURING-ROLLBACK")),
format!("{ledger_besides:?}")
);
let timings_besides = besides("timings.json.legacy-");
check!(
b,
"migration: a size change with the mtime put back is caught by its size and kept beside",
timings_besides.len() == 1
&& std::fs::read(data.join(&timings_besides[0])).ok() == Some(br#"{"swx_docs":{"count":4}}"#.to_vec()),
format!("{timings_besides:?}")
);
let settings_besides = besides("settings.json.legacy-");
check!(
b,
"migration: a same-size rewrite is caught by its mtime and kept beside too",
settings_besides.len() == 1
&& std::fs::read(data.join(&settings_besides[0])).ok() == Some(br#"{"badge":"no!"}"#.to_vec()),
format!("{settings_besides:?}")
);
// THE REGISTRY IS UNITED, not picked: the live registry now proposes the
// rollback's claim too (restore re-validates every claim against the live
// session), and both versions are kept beside it.
let live_owned: Value = serde_json::from_str(&std::fs::read_to_string(data.join("owned-docs.json")).unwrap_or_default()).unwrap_or(json!({}));
check!(
b,
"migration: the rewritten ownership registry is UNITED with the live one, both versions kept beside",
live_owned["documents"].as_array().map(|a| a.iter().any(|d| d["title"] == "Part9" && d["session"] == 4242)).unwrap_or(false)
&& owned_besides.len() == 2
&& owned_besides.iter().any(|n| {
std::fs::read(data.join(n)).ok()
== Some(br#"{"documents":[{"title":"Part9","session":4242}]}"#.to_vec())
}),
format!("{owned_besides:?} {}", s(&live_owned))
);
let m = marker();
let changed = m["changedSinceCopy"].as_array().cloned().unwrap_or_default();
let ledger_change = changed.iter().find(|e| e["file"] == "ledger.jsonl").cloned().unwrap_or(json!({}));
check!(
b,
"migration: the record says it CHANGED SINCE ITS COPY — what it was, what it is, where it went",
ledger_change["was"]["bytes"] == before.len()
&& ledger_change["now"]["bytes"] == rolled.len()
&& ledger_change["now"]["sha256"].as_str().map(|h| h.len() == 64).unwrap_or(false)
&& ledger_change["copiedAs"]
.as_str()
.map(|c| ledger_besides.iter().any(|n| n == c) && std::fs::read(data.join(c)).ok() == Some(rolled.clone()))
.unwrap_or(false)
&& ledger_change["mergedInto"] == "ledger.jsonl"
&& changed.iter().any(|e| e["file"] == "owned-docs.json"),
s(&m)
);
check!(
b,
"migration: ...and the ledger says so in lines of its own — the change, and the merge",
ledger_count(&data, "legacy_file_changed_since_copy") == 1
&& ledger_count(&data, "legacy_files_merged") == 1,
std::fs::read_to_string(data.join("ledger.jsonl")).unwrap_or_default()
);
let l = b.call("swx_recover_list", json!({}));
check!(
b,
"swx_recover_list: reports what changed since its copy",
l["migration"]["changedSinceCopy"].as_array().map(|a| a.len()) == Some(4),
s(&l["migration"])
);
drop(b);
// NOTHING CHANGED: nothing is copied again.
drop(Bridge::start_with(&env));
let b = Bridge::start_with(&env);
check!(
b,
"migration: an unchanged legacy file is not copied or merged again",
besides("ledger.jsonl.legacy-").len() == 2
&& marker()["changedSinceCopy"].as_array().map(|a| a.len()) == Some(4)
&& ledger_count(&data, "legacy_files_merged") == 1,
s(&marker())
);
drop(b);
// IT CHANGES AGAIN: merged again, two more copies beside — and the earlier
// copies beside are never overwritten.
let earlier: Vec<(String, Vec<u8>)> =
ledger_besides.iter().map(|n| (n.clone(), std::fs::read(data.join(n)).unwrap_or_default())).collect();
let mut again = rolled.clone();
again.extend_from_slice(b"{\"at\":1791000000,\"action\":\"document_closed\",\"detail\":{\"title\":\"AGAIN\"}}\n");
write(&legacy_ledger, &again);
let b = Bridge::start_with(&env);
let ledger_besides_now = besides("ledger.jsonl.legacy-");
let live_now = std::fs::read_to_string(data.join("ledger.jsonl")).unwrap_or_default();
check!(
b,
"migration: a second change is merged too, and every earlier copy beside stays as it was",
ledger_besides_now.len() == 4
&& earlier.iter().all(|(n, v)| std::fs::read(data.join(n)).ok().as_ref() == Some(v))
&& ledger_besides_now.iter().any(|n| std::fs::read(data.join(n)).ok() == Some(again.clone()))
&& live_now.contains("AGAIN")
&& live_now.matches("DURING-ROLLBACK").count() == 1,
format!("{ledger_besides_now:?}\n{live_now}")
);
}
/// NOT WHERE THE OS CLEANS (adversary pass 39, sev-3).
///
/// With neither `LOCALAPPDATA` nor `USERPROFILE`, the only location left is the
/// temp directory — which Storage Sense and Disk Cleanup empty by design. The
/// root may still point there (the bridge has to keep its notes somewhere), but
/// NO SWEEP writes the only copy of unsaved work there: it refuses with
/// `data_root_is_temp` before writing or closing anything, and the start-up log
/// says why. With `USERPROFILE`, the profile is used instead — before temp, always.
#[test]
fn with_no_profile_location_every_sweep_refuses_rather_than_write_to_temp() {
let t = TempDir::new("dataroot-temp");
let child_tmp = t.join("child-tmp");
std::fs::create_dir_all(&child_tmp).unwrap();
let c = child_tmp.to_string_lossy().to_string();
let temp_root = child_tmp.join("adom-solidworks-bridge");
a_session_with_unsaved_work(&temp_root);
let machine = a_visible_machine("dataroot-temp-machine");
let b = Bridge::start_with(&[
("SWX_SCRATCH_DIR", ""),
("LOCALAPPDATA", ""),
("USERPROFILE", ""),
("TMPDIR", &c),
("TMP", &c),
("TEMP", &c),
("SWX_LAUNCH_FAKE_DIR", &machine.path()),
]);
check!(
b,
"start-up: a data root in the temp directory is announced, with what it means",
b.log().contains("WARNING data root") && b.log().contains("data_root_is_temp"),
b.log()
);
b.call("swx_start", json!({}));
let before = b.call("swx_docs", json!({}));
let r = b.call("swx_close_all_safely", json!({"userPresentOverride": true, "userAuthorized": true, "reason": "the user asked me to close SOLIDWORKS"}));
check!(
b,
"swx_close_all_safely: REFUSES to put the only copy of unsaved work in the temp directory",
r["success"] == false && r["errorCode"] == "data_root_is_temp",
s(&r)
);
let after = b.call("swx_docs", json!({}));
check!(
b,
"swx_close_all_safely: ...closing nothing and writing nothing",
before["count"].as_u64().is_some()
&& before["count"] == after["count"]
&& !temp_root.join("recovery").exists(),
format!("before {} after {}", s(&before), s(&after))
);
drop(b);
// WITH A PROFILE: the profile, not temp — and the sweep runs.
let profile = t.join("profile");
let profile_root = profile.join("adom-solidworks-bridge");
a_session_with_unsaved_work(&profile_root);
let pr = profile.to_string_lossy().to_string();
let b = Bridge::start_with(&[
("SWX_SCRATCH_DIR", ""),
("LOCALAPPDATA", ""),
("USERPROFILE", &pr),
("TMPDIR", &c),
("TMP", &c),
("TEMP", &c),
("SWX_LAUNCH_FAKE_DIR", &machine.path()),
]);
b.call("swx_start", json!({}));
let r = b.call("swx_close_all_safely", json!({"userPresentOverride": true, "userAuthorized": true, "reason": "the user asked me to close SOLIDWORKS"}));
check!(
b,
"swx_close_all_safely: with USERPROFILE the root is the profile's, and the sweep runs there",
r["success"] == true
&& PathBuf::from(r["recoveryDir"].as_str().unwrap_or("")).starts_with(profile_root.join("recovery"))
&& !b.log().contains("WARNING data root"),
s(&r)
);
}
/// TWO REGISTRIES ARE UNITED, NOT PICKED — and one that cannot be merged is
/// still never dropped (adversary pass 40, E1).
///
/// The ownership registry in each older location holds the claims of the bridge
/// that wrote it. Keeping one copy under the real name disowns the other's
/// documents (fail-safe, but wrong); the union proposes every claim, and
/// `owned::restore` re-validates each against the live session anyway. A
/// registry that is not one this bridge wrote cannot be merged: it is copied
/// beside the live one, reported `mergeFailed`, and the live one is untouched.
#[test]
fn two_registries_are_united_and_an_unmergeable_one_is_kept_beside() {
let lay = Layout::new("dataroot-registries");
write(&lay.legacy().join("owned-docs.json"), br#"{"documents":[{"title":"PartA","session":1}]}"#);
write(&lay.legacy_data().join("owned-docs.json"), br#"{"documents":[{"title":"PartB","session":2}]}"#);
write(&lay.legacy().join("com-host-created.json"), br#"{"a":1}"#);
// Not a registry (no `documents` list): the merge must refuse and keep it.
write(&lay.legacy().join("settings.json"), br#"{"badge":true}"#);
write(&lay.legacy_data().join("settings.json"), br#"{"badge":false}"#);
let local = lay.local.path();
let b = Bridge::start_with(&[("SWX_SCRATCH_DIR", ""), ("LOCALAPPDATA", local.as_str())]);
let data = lay.data();
let live: Value =
serde_json::from_str(&std::fs::read_to_string(data.join("owned-docs.json")).unwrap_or_default())
.unwrap_or(json!({}));
let titles: Vec<String> = live["documents"]
.as_array()
.map(|a| a.iter().filter_map(|d| d["title"].as_str().map(|t| t.to_string())).collect())
.unwrap_or_default();
check!(
b,
"migration: the live registry is the UNION of both older registries",
titles == vec!["PartA".to_string(), "PartB".to_string()],
s(&live)
);
let marker: Value = serde_json::from_str(
&std::fs::read_to_string(data.join("migrated-from-legacy.json")).unwrap_or_default(),
)
.unwrap_or(json!({}));
check!(
b,
"migration: ...recorded as a registry merge with both originals beside it",
marker["merged"]
.as_array()
.map(|a| a.iter().any(|m| m["file"] == "owned-docs.json" && m["kind"] == "registry" && m["result"]["entries"] == 2))
.unwrap_or(false),
s(&marker)
);
// settings.json is not a mergeable file: the second version goes beside, the
// first keeps the name — as before 0.9.2.
let settings_beside: Vec<String> = std::fs::read_dir(&data)
.map(|d| {
d.flatten()
.map(|e| e.file_name().to_string_lossy().to_string())
.filter(|n| n.starts_with("settings.json.legacy-"))
.collect()
})
.unwrap_or_default();
check!(
b,
"migration: a file that is not a ledger or a registry is still kept beside, not merged",
std::fs::read(data.join("settings.json")).ok() == Some(br#"{"badge":true}"#.to_vec())
&& settings_beside.len() == 1
&& std::fs::read(data.join(&settings_beside[0])).ok() == Some(br#"{"badge":false}"#.to_vec()),
format!("{settings_beside:?}")
);
drop(b);
// A REGISTRY THAT CANNOT BE MERGED. A third location is not needed: make the
// older registry change into something unmergeable, and start again.
write(&lay.legacy_data().join("owned-docs.json"), b"{not json");
let b = Bridge::start_with(&[("SWX_SCRATCH_DIR", ""), ("LOCALAPPDATA", local.as_str())]);
let after: Value =
serde_json::from_str(&std::fs::read_to_string(data.join("owned-docs.json")).unwrap_or_default())
.unwrap_or(json!({}));
let kept_beside = std::fs::read_dir(&data)
.map(|d| {
d.flatten().any(|e| {
e.file_name().to_string_lossy().starts_with("owned-docs.json.legacy-")
&& std::fs::read(e.path()).ok() == Some(b"{not json".to_vec())
})
})
.unwrap_or(false);
let marker2: Value = serde_json::from_str(
&std::fs::read_to_string(data.join("migrated-from-legacy.json")).unwrap_or_default(),
)
.unwrap_or(json!({}));
check!(
b,
"migration: an unmergeable registry leaves the live one untouched and is kept beside, never dropped",
after == live
&& kept_beside
&& marker2["mergeFailed"].as_array().map(|a| a.iter().any(|e| e["file"] == "owned-docs.json")).unwrap_or(false),
format!("{}\n{}", s(&after), s(&marker2))
);
}
/// THE SAME SIZE AND MTIME: HASHED UNDER THE CAP, SAID OUT LOUD OVER IT
/// (adversary pass 40, E2). Every record says how its file is compared.
#[test]
fn a_record_says_whether_its_file_is_compared_by_hash_or_only_by_size_and_mtime() {
let lay = Layout::new("dataroot-hashcap");
write(&lay.legacy().join("owned-docs.json"), br#"{"documents":[]}"#);
let big = vec![b'x'; 16 * 1024 * 1024 + 1];
write(&lay.legacy().join("exports").join("big.step"), &big);
let local = lay.local.path();
let b = Bridge::start_with(&[("SWX_SCRATCH_DIR", ""), ("LOCALAPPDATA", local.as_str())]);
let marker: Value = serde_json::from_str(
&std::fs::read_to_string(lay.data().join("migrated-from-legacy.json")).unwrap_or_default(),
)
.unwrap_or(json!({}));
let rec = |f: &str| {
marker["copied"]
.as_array()
.and_then(|a| a.iter().find(|r| r["file"] == f).cloned())
.unwrap_or(json!({}))
};
check!(
b,
"migration: a small file is compared by size, mtime AND sha256 of the legacy file",
rec("owned-docs.json")["comparedBy"] == "size+mtime+sha256"
&& rec("owned-docs.json")["legacySha256"].as_str().map(|h| h.len() == 64).unwrap_or(false),
s(&marker)
);
check!(
b,
"migration: a file over the 16 MiB cap is compared by size and mtime only — and its record SAYS so",
rec("exports/big.step")["comparedBy"] == "size+mtime (over the 16 MiB hash cap)"
&& marker["hashCapBytes"] == 16 * 1024 * 1024,
s(&rec("exports/big.step"))
);
}
/// A RECOVERY FOLDER THAT CANNOT BE MADE IS REFUSED ONCE, BY NAME, BEFORE THE
/// FIRST SAVE (adversary pass 40, E3) — and nothing is written or closed.
#[test]
fn a_recovery_folder_that_cannot_be_made_is_refused_once_by_name() {
let scratch = TempDir::new("dataroot-unwritable");
a_session_with_unsaved_work(&scratch.0);
std::fs::write(scratch.join("recovery"), b"a file where the folder must go").unwrap();
let machine = a_visible_machine("dataroot-unwritable-machine");
let b = Bridge::start_in(TempDir::borrowed(&scratch.0), &[("SWX_LAUNCH_FAKE_DIR", &machine.path())]);
b.call("swx_start", json!({}));
let r = b.call("swx_close_all_safely", json!({"userPresentOverride": true, "userAuthorized": true, "reason": "the user asked me to close SOLIDWORKS"}));
check!(
b,
"swx_close_all_safely: refuses with recovery_dir_unwritable, naming the cause once",
r["success"] == false
&& r["errorCode"] == "recovery_dir_unwritable"
&& r["recovered"].is_null(),
s(&r)
);
check!(
b,
"swx_close_all_safely: ...before anything was written: the file in the way is untouched",
std::fs::read(scratch.join("recovery")).ok() == Some(b"a file where the folder must go".to_vec()),
String::new()
);
}