app
SOLIDWORKS Bridge
Public Made by Adomby adom
Launch, model and quit SOLIDWORKS from any Adom container. Nothing is ever lost.
main
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898
//! LAUNCH RESILIENCE, end to end against the real compiled binary.
//!
//! Every test here drives a SIMULATED MACHINE (`SWX_LAUNCH_FAKE_DIR`) whose
//! behaviour is written down as a scenario — "route A starts a chain and stalls
//! at SecurityContext; route B reaches Run Begin and attaches" — and then
//! measures WHAT THE BRIDGE DID ABOUT IT on that machine: which processes it
//! ended, how many launcher invocations it made, what it left running. Not what
//! it returned.
//!
//! That distinction is the whole point. The pre-0.4 bridge returned
//! `stillLaunching:true` with a hint promising "call swx_start again, which
//! ATTACHES rather than launching a second copy" — and every one of those calls
//! fired the launcher again. The return value was right and the effect was
//! wrong, four times over, on somebody's real desktop.
mod common;
use common::{AbStub, Bridge, TempDir};
use serde_json::{json, Value};
use std::time::{Duration, Instant};
fn s(v: &Value) -> String {
serde_json::to_string_pretty(v).unwrap_or_default()
}
/// The pids `FakeOs` gives the Nth simulated launcher invocation.
fn chain_pids(n: u64) -> (u64, u64) {
let base = 900_000 + n * 10;
(base, base + 1)
}
/// A simulated machine. `routes` says what each launch route does; the
/// pre-existing processes are the ones that MUST still be there afterwards.
struct Machine {
dir: TempDir,
}
impl Machine {
fn new(tag: &str, routes: Value) -> Machine {
let dir = TempDir::new(tag);
std::fs::write(dir.join("routes.json"), routes.to_string()).unwrap();
// The Platform's daemons and a chain the USER started a minute ago.
// Nothing this bridge does may touch any of them, ever.
std::fs::write(
dir.join("preexisting.json"),
json!([
{"pid": 6076, "image": "3DEXPERIENCELauncher.exe", "ageSeconds": 30000},
{"pid": 90552, "image": "3DEXPERIENCELauncherSysTray.exe", "ageSeconds": 30000},
{"pid": 90553, "image": "3DEXPERIENCELauncherService.exe", "ageSeconds": 30000},
{"pid": 70001, "image": "CATSTART.exe", "ageSeconds": 60},
{"pid": 70002, "image": "SWXDesktopLauncher.exe", "ageSeconds": 60}
])
.to_string(),
)
.unwrap();
Machine { dir }
}
fn sldworks(self, state: Value) -> Machine {
std::fs::write(self.dir.join("sldworks.json"), state.to_string()).unwrap();
self
}
/// Also list SOLIDWORKS itself as already running.
fn with_running_solidworks(self) -> Machine {
let mut rows: Vec<Value> =
serde_json::from_str(&std::fs::read_to_string(self.dir.join("preexisting.json")).unwrap())
.unwrap();
rows.push(json!({"pid": 88556, "image": "SLDWORKS.exe", "ageSeconds": 7200}));
std::fs::write(self.dir.join("preexisting.json"), Value::Array(rows).to_string()).unwrap();
self
}
fn lines(&self, file: &str) -> Vec<Value> {
std::fs::read_to_string(self.dir.join(file))
.unwrap_or_default()
.lines()
.filter_map(|l| serde_json::from_str(l).ok())
.collect()
}
/// Every launcher invocation the bridge made, in order.
fn invocations(&self) -> Vec<String> {
self.lines("invocations.jsonl")
.iter()
.filter_map(|v| v.get("route").and_then(|r| r.as_str()).map(String::from))
.collect()
}
/// Every process the bridge terminated, in order.
fn terminated(&self) -> Vec<(u64, String)> {
self.lines("terminated.jsonl")
.iter()
.filter_map(|v| {
Some((
v.get("pid")?.as_u64()?,
v.get("image")?.as_str()?.to_string(),
))
})
.collect()
}
fn boot(&self, scratch: TempDir, extra: &[(&str, &str)]) -> Bridge {
let lnk = format!("{}/SOLIDWORKS Design.lnk", self.dir.path());
std::fs::write(&lnk, "not really a shortcut").unwrap();
let mut env: Vec<(&str, &str)> = vec![
("SWX_SOLIDWORKS_SHORTCUT", Box::leak(lnk.into_boxed_str())),
("SWX_LAUNCH_FAKE_DIR", Box::leak(self.dir.path().into_boxed_str())),
// Compressed budgets: the real ones are 20s / 120s / 240s, and a
// suite that waited them out would never be run.
("SWX_LAUNCH_RUN_BEGIN_SECS", "2"),
("SWX_LAUNCH_SLDWORKS_SECS", "4"),
("SWX_LAUNCH_ATTACH_SECS", "8"),
("SWX_LAUNCH_POLL_MS", "100"),
("SWX_LAUNCH_ATTACH_POLL_MS", "150"),
("SWX_LAUNCH_INFLIGHT_SECS", "120"),
("SWX_LAUNCH_HELPER_SECS", "30"),
("SWX_LAUNCH_RETRY_SETTLE_MS", "400"),
("SWX_LAUNCH_DIALOG_GRACE_MS", "3000"),
];
env.extend_from_slice(extra);
Bridge::start_in(scratch, &env)
}
}
/// Poll `swx_status` until the launch attempt finishes, or give up. Returns the
/// last status seen — a test asserts on the MACHINE, this only paces it.
fn wait_for_launch(b: &Bridge, secs: u64) -> Value {
let deadline = Instant::now() + Duration::from_secs(secs);
let mut last = json!({});
loop {
last = b.call("swx_status", json!({}));
if last["launch"]["finished"] == true {
return last;
}
if Instant::now() > deadline {
return last;
}
std::thread::sleep(Duration::from_millis(150));
}
}
#[test]
fn a_stalled_route_is_cleaned_up_and_the_next_route_takes_over() {
// FAILURE MODE A, exactly as measured on 2026-09-04: the chain starts, the
// launcher log stops at `SecurityContext:`, `Run Begin` never comes.
let m = Machine::new(
"fallback",
// Both ATTRIBUTABLE routes: only a route that can say what it started
// may have its chain cleaned up, so the fallback story is told with the
// two that can. See `launch::AttributionRule`.
json!({
"shellExecute": {"chain": true},
"powershell": {"chain": true, "runBeginAfterMs": 0, "helperAfterMs": 100,
"sldworksAfterMs": 200, "attachAfterMs": 400}
}),
);
let b = m.boot(TempDir::new("scratch"), &[]);
let r = b.call("swx_start", json!({"reason": "modelling a bracket for Drew", "waitSeconds": 0}));
check!(b, "swx_start reports a launch in flight, not a failure", r["success"] == true && r["stillLaunching"] == true, s(&r));
let st = wait_for_launch(&b, 25);
check!(b, "swx_start reaches an attached SOLIDWORKS by falling back", st["launch"]["outcome"] == "attached", s(&st));
// THE EFFECT, on the machine.
check!(
b,
"exactly two launcher invocations: the stalled route, then the next one",
m.invocations() == vec!["shellExecute".to_string(), "powershell".to_string()],
format!("{:?}", m.invocations())
);
let (cat1, lnk1) = chain_pids(1);
let killed = m.terminated();
check!(
b,
"ONLY the stalled chain we started was ended — by pid, both of it, nothing else",
killed.iter().map(|(p, _)| *p).collect::<Vec<_>>() == vec![cat1, lnk1],
format!("terminated: {killed:?}")
);
check!(
b,
"...and it was ended as the images it really was",
killed.iter().all(|(_, i)| i == "CATSTART.exe" || i == "SWXDesktopLauncher.exe"),
format!("{killed:?}")
);
for guarded in [6076u64, 90552, 90553, 70001, 70002, 88556] {
check!(
b,
"no daemon, no pre-existing chain and no SOLIDWORKS was ever terminated",
!killed.iter().any(|(p, _)| *p == guarded),
format!("pid {guarded} was terminated: {killed:?}")
);
}
let (cat2, lnk2) = chain_pids(2);
check!(
b,
"the WORKING route's chain was left alone",
!killed.iter().any(|(p, _)| *p == cat2 || *p == lnk2),
format!("{killed:?}")
);
// The stall is on the record, with what it cost.
let tried = &st["launch"]["routesTried"];
check!(b, "the stalled route is recorded", tried[0]["route"] == "shellExecute" && tried[0]["stalled"] == true, s(&st["launch"]));
check!(
b,
"...and that the chain it ended was one the INVOKE identified, not one it recognised",
tried[0]["attributed"] == true && tried[0]["cleanup"] == "cleaned",
s(&st["launch"])
);
check!(
b,
"...naming the exact processes it ended",
tried[0]["killed"][0]["pid"] == cat1 && tried[0]["killed"][1]["pid"] == lnk1,
s(&st["launch"])
);
let ledger = b.call("swx_action_ledger", json!({"limit": 200}));
check!(b, "the stall and the cleanup are in the human's ledger", s(&ledger).contains("solidworks_launch_stalled"), s(&ledger));
check!(b, "...and so is Run Begin never arriving", s(&ledger).contains("never logged 'Run Begin'"), s(&ledger));
}
#[test]
fn the_route_that_worked_is_remembered_and_tried_first_next_time() {
let scratch = TempDir::new("scratch");
let m = Machine::new(
"preferred",
json!({
"shellExecute": {"chain": true},
"powershell": {"chain": true, "runBeginAfterMs": 0, "sldworksAfterMs": 100, "attachAfterMs": 300}
}),
);
{
let b = m.boot(TempDir::borrowed(&scratch.0), &[]);
b.call("swx_start", json!({"reason": "first launch of the day", "waitSeconds": 0}));
let st = wait_for_launch(&b, 25);
check!(b, "the working route is remembered on disk", st["launch"]["preferredRoute"] == "powershell", s(&st["launch"]));
}
// A DIFFERENT SERVER PROCESS, same scratch dir. The memory has to be the
// FILE, not the process — a bridge is restarted far more often than a
// desktop is.
let state: Value = serde_json::from_str(
&std::fs::read_to_string(scratch.join("launch-state.json")).unwrap(),
)
.unwrap();
assert_eq!(state["preferredRoute"], "powershell", "{}", s(&state));
// Every route still stalls except the remembered one, and the remembered
// one is tried FIRST — so a second cold start costs one invocation, not two.
let m2 = Machine::new(
"preferred2",
json!({"powershell": {"chain": true, "runBeginAfterMs": 0, "sldworksAfterMs": 100, "attachAfterMs": 300}}),
);
std::fs::copy(scratch.join("launch-state.json"), m2.dir.join("seed.json")).unwrap();
let scratch2 = TempDir::new("scratch2");
std::fs::copy(scratch.join("launch-state.json"), scratch2.join("launch-state.json")).unwrap();
let b2 = m2.boot(scratch2, &[]);
b2.call("swx_start", json!({"reason": "second launch", "waitSeconds": 0}));
let st2 = wait_for_launch(&b2, 25);
check!(b2, "the second cold start attaches", st2["launch"]["outcome"] == "attached", s(&st2["launch"]));
check!(
b2,
"...on the FIRST invocation, because the remembered route led the plan",
m2.invocations() == vec!["powershell".to_string()],
format!("{:?}", m2.invocations())
);
check!(b2, "...and nothing had to be cleaned up", m2.terminated().is_empty(), format!("{:?}", m2.terminated()));
}
#[test]
fn three_calls_during_a_launch_start_exactly_zero_extra_launchers() {
// FAILURE MODE B. The old hint said calling again ATTACHES rather than
// launching a second copy. It did not. Four launcher chains accumulated.
let m = Machine::new(
"inflight",
json!({"shellExecute": {"chain": true, "runBeginAfterMs": 0, "helperAfterMs": 50,
"sldworksAfterMs": 150, "attachAfterMs": 600000}}),
);
let b = m.boot(TempDir::new("scratch"), &[("SWX_LAUNCH_ATTACH_SECS", "120")]);
let first = b.call("swx_start", json!({"reason": "starting SOLIDWORKS for Drew", "waitSeconds": 0}));
check!(b, "the first call starts the launch", first["stillLaunching"] == true, s(&first));
// Let the machine get past Run Begin so the calls land mid-flight.
std::thread::sleep(Duration::from_millis(700));
for n in 1..=3 {
let again = b.call("swx_start", json!({"reason": "checking on it", "waitSeconds": 0}));
check!(b, "a call during flight never fails", again["success"] == true, s(&again));
check!(
b,
"...it reports the SAME attempt as still launching",
again["stillLaunching"] == true && again["launchedNow"] == false,
format!("call {n}: {}", s(&again))
);
check!(
b,
"...and it says, in words, that it launched nothing",
again["_hint"].as_str().unwrap_or("").contains("NOTHING WAS LAUNCHED BY THIS CALL"),
s(&again)
);
check!(
b,
"...carrying the live stage so the caller knows what it is waiting for",
["invoked", "launcherStarted", "runBegin", "helperSignIn", "sldworksUp", "attaching"]
.contains(&again["stage"].as_str().unwrap_or("")),
s(&again)
);
}
// A call with NO reason must not be refused either — polling with swx_start
// is what this bridge's own hint tells a caller to do.
let bare = b.call("swx_start", json!({}));
check!(b, "polling with swx_start needs no reason while a launch is in flight", bare["success"] == true && bare["stillLaunching"] == true, s(&bare));
check!(
b,
"ONE launcher invocation, after five swx_start calls",
m.invocations() == vec!["shellExecute".to_string()],
format!("{:?}", m.invocations())
);
check!(b, "and nothing was terminated on the way", m.terminated().is_empty(), format!("{:?}", m.terminated()));
// swx_status tells the same story, which is where a caller should be looking.
let st = b.call("swx_status", json!({}));
check!(b, "swx_status carries the live launch block", st["launch"]["inFlight"] == true && st["launch"]["route"] == "shellExecute", s(&st["launch"]));
}
#[test]
fn the_machine_stops_at_the_first_chain_it_cannot_prove_is_its_own() {
// All four routes behave the way cmd/start did on 2026-09-04: they start a
// launcher chain that never reaches Run Begin.
//
// The two ATTRIBUTABLE routes hand back the root they started, so their
// chains are ended. `explorer` cannot say what it started — and it started
// SOMETHING (a launcher log appeared) — so the machine stops there rather
// than putting a fourth chain on the desktop beside one nobody can find.
let m = Machine::new("exhaust", json!({}));
let b = m.boot(
TempDir::new("scratch"),
&[("SWX_LAUNCH_RUN_BEGIN_SECS", "1"), ("SWX_LAUNCH_BACKOFF_SECS", "0")],
);
b.call("swx_start", json!({"reason": "trying to start SOLIDWORKS", "waitSeconds": 0}));
let st = wait_for_launch(&b, 40);
check!(b, "the attempt ends", st["launch"]["outcome"] == "exhausted", s(&st["launch"]));
check!(
b,
"it stopped at the first route whose chain it could not prove was its own",
m.invocations() == vec!["shellExecute", "powershell", "explorer"],
format!("{:?}", m.invocations())
);
let tried = st["launch"]["routesTried"].as_array().cloned().unwrap_or_default();
check!(
b,
"the two attributable routes ended the chains they started",
tried[0]["attributed"] == true
&& tried[0]["cleanup"] == "cleaned"
&& tried[1]["attributed"] == true
&& tried[1]["cleanup"] == "cleaned",
s(&st["launch"])
);
check!(
b,
"...and the unattributable one is recorded as exactly that, not as a clean recovery",
tried[2]["attributed"] == false && tried[2]["cleanup"] == "notAttributable",
s(&st["launch"])
);
check!(
b,
"...saying, in words, that a chain may remain which the bridge cannot identify",
tried[2]["cleanupError"].as_str().unwrap_or("").contains("MAY still be running"),
s(&st["launch"])
);
// THE EFFECT. Four processes ended — both attributable chains — and the
// chain the bridge could not prove was its own was LEFT ALONE.
let killed = m.terminated();
check!(b, "two attributable chains, four processes", killed.len() == 4, format!("{killed:?}"));
for ours in [900010u64, 900011, 900020, 900021] {
check!(b, "each attributed chain was ended", killed.iter().any(|(p, _)| *p == ours), format!("{killed:?}"));
}
for unproven in [900030u64, 900031] {
check!(
b,
"a chain the bridge could not prove was its own is NEVER terminated",
!killed.iter().any(|(p, _)| *p == unproven),
format!("{killed:?}")
);
}
for guarded in [6076u64, 90552, 90553, 70001, 70002] {
check!(b, "no daemon and no pre-existing chain was touched", !killed.iter().any(|(p, _)| *p == guarded), format!("{killed:?}"));
}
check!(
b,
"the advice is to ask the USER, which is the honest remaining move",
st["launch"]["advice"].as_str().unwrap_or("").contains("ASK THE USER"),
s(&st["launch"])
);
// And a fresh swx_start afterwards is allowed to try again — a finished
// attempt is not a permanent lock.
let again = b.call("swx_start", json!({"reason": "trying once more", "waitSeconds": 0}));
check!(b, "a finished attempt does not block the next one", again["success"] == true, s(&again));
let deadline = Instant::now() + Duration::from_secs(10);
while m.invocations().len() <= 3 && Instant::now() < deadline {
std::thread::sleep(Duration::from_millis(100));
}
check!(b, "...and it really did invoke again", m.invocations().len() > 3, format!("{:?}", m.invocations()));
std::thread::sleep(Duration::from_millis(1500));
check!(
b,
"a retry after a failure tries ONE route, not the whole plan again",
m.invocations().len() == 4,
format!("{:?}", m.invocations())
);
}
#[test]
fn a_route_that_cannot_say_what_it_started_is_still_tried_when_it_started_nothing() {
// The stop rule is about what a route LEFT BEHIND, not about whether it was
// attributable. A route that started nothing at all leaves nothing behind,
// so the machine may go on and try the ones that might actually work — the
// two unattributable routes are kept precisely because they may be the ones
// that launch SOLIDWORKS on this machine.
let m = Machine::new(
"nochain-continue",
json!({"shellExecute": {"chain": false}, "powershell": {"chain": false},
"explorer": {"chain": false},
"cmdStart": {"chain": true, "runBeginAfterMs": 0, "sldworksAfterMs": 100, "attachAfterMs": 300}}),
);
let b = m.boot(TempDir::new("scratch"), &[("SWX_LAUNCH_RUN_BEGIN_SECS", "1")]);
b.call("swx_start", json!({"reason": "starting SOLIDWORKS", "waitSeconds": 0}));
let st = wait_for_launch(&b, 40);
check!(b, "the last route works and the launch attaches", st["launch"]["outcome"] == "attached", s(&st["launch"]));
check!(
b,
"every route was tried, because none of the earlier ones left anything behind",
m.invocations() == vec!["shellExecute", "powershell", "explorer", "cmdStart"],
format!("{:?}", m.invocations())
);
check!(b, "and nothing was terminated anywhere", m.terminated().is_empty(), format!("{:?}", m.terminated()));
check!(
b,
"a route that started nothing says so, rather than inventing a stall at SecurityContext",
st["launch"]["routesTried"][0]["note"]
.as_str()
.unwrap_or("")
.contains("NO launcher log at all"),
s(&st["launch"])
);
// AND IT IS NOT MEMORISED. `cmdStart` cannot say what it started, so no
// launcher log can be attributed to it — the `Run Begin` in the log that
// appeared might have been written by a launch the USER started while we
// watched. SOLIDWORKS is up and usable (that is what `attached` means and
// it is proven by COM), but nothing proves THIS ROUTE did it, so the route
// memory is left alone rather than taught something that may be false.
check!(
b,
"an unattributable route is never learned as 'the one that works'",
st["launch"]["preferredRoute"].is_null() && st["launch"]["runBeginSeen"] == false,
s(&st["launch"])
);
check!(
b,
"...and the log it could not attribute is reported as exactly that",
st["launch"]["unattributedLog"] == true,
s(&st["launch"])
);
}
#[test]
fn a_root_the_census_cannot_see_is_still_confirmed_by_its_children() {
// `rootVisible:false` — the invoke hands back a root whose image this
// bridge does not watch (a shim, a wrapper), so the census never shows it.
// Its CHILDREN are there, and their ppid points at the handle-proven pid,
// so the lineage is confirmed and the chain is cleaned up. Getting this
// wrong reported a "clean recovery" with a real chain still running.
let m = Machine::new(
"rootinvisible",
json!({"shellExecute": {"chain": true, "rootVisible": false}}),
);
let b = m.boot(TempDir::new("scratch"), &[("SWX_LAUNCH_RUN_BEGIN_SECS", "1")]);
b.call("swx_start", json!({"reason": "starting SOLIDWORKS", "waitSeconds": 0}));
let st = wait_for_launch(&b, 30);
let tried = &st["launch"]["routesTried"][0];
check!(b, "the lineage was confirmed through the invisible root", tried["rootConfirmed"] == true, s(&st["launch"]));
check!(b, "...so the chain was cleaned, not reported clean", tried["cleanup"] == "cleaned", s(&st["launch"]));
let killed = m.terminated();
check!(
b,
"the child the root started WAS ended",
killed.iter().any(|(p, _)| *p == 900011),
format!("{killed:?}")
);
check!(
b,
"...and the root itself was not, because the census never showed it",
!killed.iter().any(|(p, _)| *p == 900010),
format!("{killed:?}")
);
}
#[test]
fn each_route_carries_the_log_evidence_that_belongs_to_it() {
// The gate's per-route table is its central measurement. Every field it
// prints must come from the ROUTE, not from the attempt — the attempt's
// copies are reset when the next route begins, so reading them per row
// printed the last route's evidence against all of them.
let m = Machine::new(
"perroute",
json!({
"shellExecute": {"chain": false},
"powershell": {"chain": true, "runBeginAfterMs": 0, "sldworksAfterMs": 100, "attachAfterMs": 300}
}),
);
let b = m.boot(TempDir::new("scratch"), &[("SWX_LAUNCH_RUN_BEGIN_SECS", "1")]);
b.call("swx_start", json!({"reason": "starting SOLIDWORKS", "waitSeconds": 0}));
let st = wait_for_launch(&b, 30);
let tried = st["launch"]["routesTried"].as_array().cloned().unwrap_or_default();
check!(b, "both routes are in the table", tried.len() == 2, s(&st["launch"]));
check!(
b,
"the route that started nothing has no log and no Run Begin OF ITS OWN",
tried[0]["launcherLog"].is_null() && tried[0]["runBeginSeen"] == false,
s(&tried[0])
);
check!(
b,
"the route that worked has both, and says which root it was given",
tried[1]["runBeginSeen"] == true
&& tried[1]["launcherLog"].is_string()
&& tried[1]["rootPid"].is_u64()
&& tried[1]["rootConfirmed"] == true,
s(&tried[1])
);
check!(
b,
"the attach is recorded as an ordering hint that is explicitly NOT proof",
st["launch"]["lastRouteBeforeAttach"]["route"] == "powershell"
&& st["launch"]["lastRouteBeforeAttach"]["proof"] == false,
s(&st["launch"])
);
}
#[test]
fn an_attributable_route_that_never_sees_its_own_chain_stops_the_machine() {
// The invoke handed back a root and the census never showed it, nor
// anything descending from it. That is not "there was nothing to kill" —
// it is "I never found what I started", and another route would put a
// second chain beside one nobody can account for.
let m = Machine::new(
"notconfirmed",
// `attributable:false` alone models "no handle"; here the route DOES
// report a root, but the simulated chain never appears in the census.
// Route 1 starts nothing (so the machine reaches route 2), and route 2
// reports a root whose chain the census never shows. Before the
// per-route reset, `root_confirmed` carried over and `notConfirmed`
// was unreachable after route 1.
json!({"shellExecute": {"chain": false},
"powershell": {"chain": true, "invisibleChain": true}}),
);
let b = m.boot(TempDir::new("scratch"), &[("SWX_LAUNCH_RUN_BEGIN_SECS", "1")]);
b.call("swx_start", json!({"reason": "starting SOLIDWORKS", "waitSeconds": 0}));
let st = wait_for_launch(&b, 30);
let tried = &st["launch"]["routesTried"][1];
check!(b, "the route is recorded as never confirmed", tried["cleanup"] == "notConfirmed", s(&st["launch"]));
check!(b, "...as a FAILURE to account, not a clean recovery", tried["cleanupFailed"] == true, s(&st["launch"]));
check!(b, "...and it says a chain may remain", tried["chainMayRemain"] == true, s(&st["launch"]));
check!(
b,
"the machine stopped rather than starting another chain",
m.invocations().len() == 2,
format!("{:?}", m.invocations())
);
check!(b, "and nothing was terminated", m.terminated().is_empty(), format!("{:?}", m.terminated()));
}
#[test]
fn an_attributable_route_that_gets_no_handle_cleans_up_nothing_and_stops() {
// THE SHAPE ShellExecuteExW IS MOST LIKELY TO PRODUCE on a .lnk: the shell
// hands the link to an already-running process (DDE) and returns no handle,
// so a route that CAN normally attribute does not, on this launch. It must
// behave exactly like `explorer`: adopt nothing, kill nothing, and stop
// once a launcher log shows something started.
let m = Machine::new(
"nohandle",
json!({"shellExecute": {"chain": true, "attributable": false}}),
);
let b = m.boot(TempDir::new("scratch"), &[("SWX_LAUNCH_RUN_BEGIN_SECS", "1")]);
b.call("swx_start", json!({"reason": "starting SOLIDWORKS", "waitSeconds": 0}));
let st = wait_for_launch(&b, 30);
check!(b, "nothing was attributed", st["launch"]["attributed"] == false, s(&st["launch"]));
let tried = &st["launch"]["routesTried"][0];
check!(b, "the route is recorded as unattributable", tried["cleanup"] == "notAttributable", s(&st["launch"]));
check!(b, "...and says a chain may remain", tried["chainMayRemain"] == true, s(&st["launch"]));
check!(
b,
"the machine stopped after ONE route, because a log says something started",
m.invocations() == vec!["shellExecute".to_string()],
format!("{:?}", m.invocations())
);
check!(b, "NOTHING was terminated", m.terminated().is_empty(), format!("{:?}", m.terminated()));
let chain = m.lines("invocations.jsonl").len();
check!(b, "one invocation only", chain == 1, format!("{chain}"));
}
#[test]
fn a_retry_after_a_failure_waits_instead_of_starting_more_chains() {
let m = Machine::new("backoff", json!({}));
let b = m.boot(
TempDir::new("scratch"),
&[("SWX_LAUNCH_RUN_BEGIN_SECS", "1"), ("SWX_LAUNCH_BACKOFF_SECS", "300")],
);
b.call("swx_start", json!({"reason": "starting SOLIDWORKS", "waitSeconds": 0}));
let st = wait_for_launch(&b, 40);
check!(b, "precondition: the attempt failed", st["launch"]["outcome"] == "exhausted", s(&st["launch"]));
let after_first = m.invocations().len();
let r = b.call("swx_start", json!({"reason": "and again", "waitSeconds": 0}));
check!(b, "the retry is refused with a branchable code", r["errorCode"] == "host_app_launch_backoff", s(&r));
check!(b, "...saying how long", r["backoffSeconds"].as_u64().unwrap_or(0) > 0, s(&r));
check!(
b,
"...and pointing at the human, which is the honest remaining move",
r["_hint"].as_str().unwrap_or("").contains("ASK THE USER"),
s(&r)
);
std::thread::sleep(Duration::from_millis(800));
check!(
b,
"NOT ONE new launcher chain was started by the refused retry",
m.invocations().len() == after_first,
format!("{:?}", m.invocations())
);
}
#[test]
fn solidworks_up_but_not_attaching_stops_the_machine_instead_of_launching_another() {
// sldworks.exe IS on the user's desktop — probably at a 3DEXPERIENCE
// sign-in only they can answer. That is NOT a stalled launcher: the chain
// must not be killed for it, no further route may be tried (a second route
// would put a SECOND SOLIDWORKS on their screen), and it must not be
// reported as attached.
let m = Machine::new(
"upnotattached",
json!({"shellExecute": {"chain": true, "runBeginAfterMs": 0, "sldworksAfterMs": 100}}),
);
let b = m.boot(TempDir::new("scratch"), &[("SWX_LAUNCH_ATTACH_SECS", "3")]);
b.call("swx_start", json!({"reason": "starting SOLIDWORKS", "waitSeconds": 0}));
let st = wait_for_launch(&b, 25);
check!(
b,
"the outcome names what is true: up, and not attached",
st["launch"]["outcome"] == "sldworksUpNotAttached",
s(&st["launch"])
);
check!(b, "...and it is NEVER reported as attached", st["launch"]["stage"] != "attached", s(&st["launch"]));
check!(
b,
"...pointing at the sign-in only the user can answer",
st["launch"]["error"].as_str().unwrap_or("").contains("sign-in"),
s(&st["launch"])
);
check!(b, "no second route was tried", m.invocations() == vec!["shellExecute".to_string()], format!("{:?}", m.invocations()));
check!(b, "and the chain was NOT killed — SOLIDWORKS is coming up on their screen", m.terminated().is_empty(), format!("{:?}", m.terminated()));
check!(
b,
"the route is not remembered as one that worked",
st["launch"]["preferredRoute"].is_null(),
s(&st["launch"])
);
}
#[test]
fn the_crash_reporters_restart_button_is_clicked_once_and_nothing_else_is() {
// FAILURE MODE C, 2026-09-03: sldworks crashed on first spawn, the crash
// reporter appeared, and clicking "Restart SOLIDWORKS Design" launched it.
// pid 900012 is the sldworks.exe the simulator spawns for invocation 1 —
// the crash reporter must be ATTRIBUTED to the process we are launching,
// not merely titled like one.
let ab = AbStub::start(json!([
{"hwnd": 111, "title": "SOLIDWORKS Design has stopped working", "className": "#32770", "pid": 900012, "rect": {"left": 0}},
{"hwnd": 222, "title": "Save changes to Part1?", "className": "#32770", "pid": 900012, "rect": {"left": 0}},
{"hwnd": 333, "title": "gallia - Google Chrome", "className": "Chrome_WidgetWin_1", "pid": 4242, "rect": {"left": 0}}
]));
ab.set_response(
"desktop_find_control",
json!({"success": true, "found": true, "name": "Restart SOLIDWORKS Design", "role": "button", "invokable": true}),
);
ab.set_response("desktop_ui_click", json!({"success": true, "invoked": true, "name": "Restart SOLIDWORKS Design"}));
let m = Machine::new(
"crash",
json!({"shellExecute": {"chain": true, "runBeginAfterMs": 0, "sldworksAfterMs": 100, "attachAfterMs": 1200}}),
);
let b = m.boot(
TempDir::new("scratch"),
&[
("ADOM_DIRECT_API_URL", &ab.url),
("ADOM_BRIDGE_TOKEN", "test-token"),
// SHIPPED OFF. The click is decided from the shape of ab's
// desktop_find_control reply, and that shape has never been seen on
// a real desktop — so the feature is armed explicitly, here and by
// the live gate, and nowhere else.
("SWX_CRASH_RECOVERY", "on"),
],
);
b.call("swx_start", json!({"reason": "starting SOLIDWORKS to model a bracket", "waitSeconds": 0}));
let st = wait_for_launch(&b, 25);
check!(b, "the launch still completes", st["launch"]["outcome"] == "attached", s(&st["launch"]));
let clicks = ab.calls_to("desktop_ui_click");
check!(b, "the Restart button was clicked EXACTLY once, however many polls ran", clicks.len() == 1, format!("{clicks:?}"));
let args = &clicks[0]["args"];
check!(b, "...on the crash reporter's window", args["hwnd"] == 111, s(&clicks[0]));
check!(b, "...targeting the button BY NAME, never a coordinate", args["contains"] == "Restart SOLIDWORKS" && args["role"] == "button", s(&clicks[0]));
check!(
b,
"...with a reason a human can read in their Activity Log",
args["reason"].as_str().unwrap_or("").contains("crashed"),
s(&clicks[0])
);
check!(
b,
"the button was CONFIRMED to exist before it was clicked",
!ab.calls_to("desktop_find_control").is_empty(),
format!("{:?}", ab.calls())
);
// NOTHING ELSE was touched. The Save-changes dialog is the one that must
// never be answered by a bridge, and it was on screen the whole time.
check!(b, "no window was closed", ab.calls_to("desktop_ui_window").is_empty(), format!("{:?}", ab.calls()));
check!(b, "no shell command was run", ab.calls_to("shell_execute").is_empty(), format!("{:?}", ab.calls()));
check!(
b,
"the user's Save-changes dialog was never touched",
!ab.calls().iter().any(|c| c.to_string().contains("222")),
format!("{:?}", ab.calls())
);
let ledger = b.call("swx_action_ledger", json!({"limit": 200}));
check!(b, "the click is on the record", s(&ledger).contains("solidworks_crash_reporter_restarted"), s(&ledger));
}
#[test]
fn a_window_that_merely_looks_like_a_crash_reporter_is_never_clicked() {
// The gate is the BUTTON, not the title. ab says the control is not there,
// so nothing may be clicked — a title is a guess and a click is an act.
let ab = AbStub::start(json!([
{"hwnd": 111, "title": "SOLIDWORKS Error Report", "className": "#32770", "pid": 900012, "rect": {"left": 0}}
]));
ab.set_response("desktop_find_control", json!({"success": true, "found": false}));
let m = Machine::new(
"nocrash",
json!({"shellExecute": {"chain": true, "runBeginAfterMs": 0, "sldworksAfterMs": 100, "attachAfterMs": 1000}}),
);
let b = m.boot(
TempDir::new("scratch"),
&[
("ADOM_DIRECT_API_URL", &ab.url),
("ADOM_BRIDGE_TOKEN", "test-token"),
("SWX_CRASH_RECOVERY", "on"),
],
);
b.call("swx_start", json!({"reason": "starting SOLIDWORKS", "waitSeconds": 0}));
wait_for_launch(&b, 25);
check!(b, "no click was dispatched at all", ab.calls_to("desktop_ui_click").is_empty(), format!("{:?}", ab.calls()));
check!(b, "...though the window WAS looked at", !ab.calls_to("desktop_find_control").is_empty(), format!("{:?}", ab.calls()));
}
#[test]
fn crash_recovery_is_off_unless_it_is_explicitly_armed() {
// It ships DISABLED. The decision to click rests entirely on the shape of
// ab's desktop_find_control reply, and no reply from that verb has ever
// been observed on a real desktop — so until the live gate captures one,
// the bridge reports the crash reporter and does nothing about it.
let ab = AbStub::start(json!([
{"hwnd": 111, "title": "SOLIDWORKS Design has stopped working", "className": "#32770", "pid": 900012, "rect": {"left": 0}}
]));
ab.set_response(
"desktop_find_control",
json!({"success": true, "found": true, "name": "Restart SOLIDWORKS Design", "invokable": true}),
);
let m = Machine::new(
"crashoff",
json!({"shellExecute": {"chain": true, "runBeginAfterMs": 0, "sldworksAfterMs": 100, "attachAfterMs": 1000}}),
);
let b = m.boot(
TempDir::new("scratch"),
&[("ADOM_DIRECT_API_URL", &ab.url), ("ADOM_BRIDGE_TOKEN", "test-token")],
);
b.call("swx_start", json!({"reason": "starting SOLIDWORKS", "waitSeconds": 0}));
let st = wait_for_launch(&b, 25);
check!(b, "the launch is unaffected", st["launch"]["outcome"] == "attached", s(&st["launch"]));
check!(
b,
"ab was never even asked about the window, let alone told to click it",
ab.calls_to("desktop_find_control").is_empty() && ab.calls_to("desktop_ui_click").is_empty(),
format!("{:?}", ab.calls())
);
check!(
b,
"...and the report says so, so a reader knows the feature is not armed",
st["launch"]["crashRecovery"] == "off",
s(&st["launch"])
);
}
#[test]
fn a_hung_solidworks_is_reported_as_hung_and_a_second_one_is_never_launched() {
// FAILURE MODE D. SOLIDWORKS is alive, the window is not answering, COM
// times out. The old bridge said "not running, and this edition cannot be
// started programmatically" — and on a shortcut machine it would have
// launched a SECOND SOLIDWORKS onto the user's desktop.
let m = Machine::new("hung", json!({}))
.with_running_solidworks()
.sldworks(json!({"responding": false, "hwnd": 4242, "privateMB": 2400, "handles": 9000}));
let scratch = TempDir::new("scratch");
let scratch_path = scratch.0.clone();
let b = m.boot(scratch, &[]);
// BOTH signals, from two independent causes: Windows says the window is not
// answering (above), and a COM call this bridge made overran its budget.
// Neither alone is a hang, and the bridge must not manufacture the second
// one out of its own busy com host — see the adversary test.
std::fs::write(scratch_path.join("mock-com-timeout"), "").unwrap();
let r = b.call("swx_start", json!({"reason": "trying to use SOLIDWORKS"}));
check!(b, "the bridge says it is HUNG, with a branchable code", r["errorCode"] == "host_app_hung", s(&r));
check!(
b,
"...and says only the user can end it, and why",
r["_hint"].as_str().unwrap_or("").contains("ONLY THE USER CAN END IT"),
s(&r)
);
check!(b, "NOTHING was launched", m.invocations().is_empty(), format!("{:?}", m.invocations()));
check!(b, "and nothing was killed", m.terminated().is_empty(), format!("{:?}", m.terminated()));
check!(
b,
"the evidence is reported, not just the verdict",
r["solidworksProcess"]["responding"] == false && r["solidworksProcess"]["processId"] == 88556,
s(&r)
);
// swx_status tells the same story WITHOUT COM — which is the point, because
// COM is what has stopped answering.
let st = b.call("swx_status", json!({}));
check!(b, "swx_status reports responding:false read from the OS, not from COM", st["solidworks"]["responding"] == false, s(&st["solidworks"]));
check!(b, "...with the memory and handle counts a human needs", st["solidworks"]["privateMemoryMB"] == 2400 && st["solidworks"]["handles"] == 9000, s(&st["solidworks"]));
check!(
b,
"...and resident memory beside the commit charge, so neither is read as 'memory'",
st["solidworks"]["workingSetMB"].is_u64(),
s(&st["solidworks"])
);
// TAKE THE TIMEOUT AWAY and the verdict must go with it: a window that is
// not pumping messages, on its own, is a rebuild — not a hang.
std::fs::remove_file(scratch_path.join("mock-com-timeout")).unwrap();
let st2 = b.call("swx_status", json!({}));
check!(
b,
"one signal alone is never a hang",
st2["solidworks"]["hung"] != json!(true),
s(&st2["solidworks"])
);
}
#[test]
fn a_very_large_session_is_an_observation_not_a_verdict() {
// 2026-09-03: 17.4 GB private, 52 documents, and BOTH stock templates
// returned no document. That measurement is worth REPORTING — and it is not
// a verdict. The 0.4.0 threshold (12 GB / 40 docs) called the same
// machine's HEALTHY 13.1 GB / 33-document session out of room and told the
// user to restart it, so memory is now advisory and the only sufficient
// signal is SOLIDWORKS handing back no document (below).
let m = Machine::new("exhausted", json!({}))
.with_running_solidworks()
.sldworks(json!({"responding": true, "hwnd": 4242, "privateMB": 17900, "handles": 21770}));
let b = m.boot(TempDir::new("scratch"), &[]);
let r = b.call("swx_start", json!({"reason": "trying to model"}));
check!(
b,
"swx_start refuses to launch a second copy, and names what is ACTUALLY known",
r["errorCode"] == "host_app_com_unreachable",
s(&r)
);
check!(b, "nothing was launched", m.invocations().is_empty(), format!("{:?}", m.invocations()));
let st = b.call("swx_status", json!({}));
check!(
b,
"the memory figure is reported as an OBSERVATION",
st["solidworks"]["memoryAdvisory"].as_str().unwrap_or("").contains("17900"),
s(&st["solidworks"])
);
check!(
b,
"...explicitly not a verdict",
st["solidworks"]["memoryAdvisory"].as_str().unwrap_or("").contains("not a verdict"),
s(&st["solidworks"])
);
check!(
b,
"...and `exhausted` stays null, because nothing has proved it",
st["solidworks"]["exhausted"].is_null(),
s(&st["solidworks"])
);
}
#[test]
fn a_new_document_that_comes_back_empty_is_exhaustion_not_a_template_fault() {
let m = Machine::new(
"newdoc",
json!({"shellExecute": {"chain": true, "runBeginAfterMs": 0, "sldworksAfterMs": 0, "attachAfterMs": 0}}),
);
let b = m.boot(TempDir::new("scratch"), &[]);
b.call("swx_start", json!({"reason": "modelling", "waitSeconds": 10}));
let ok = b.call("swx_new_document", json!({"type": "part"}));
check!(b, "a healthy session creates documents normally", ok["success"] == true, s(&ok));
// Now SOLIDWORKS accepts the call and hands back nothing — the signature.
std::fs::write(b.scratch.join("mock-newdoc-empty"), "").unwrap();
let r = b.call("swx_new_document", json!({"type": "assembly"}));
check!(b, "an empty NewDocument is reported as exhaustion", r["errorCode"] == "host_app_exhausted", s(&r));
check!(b, "...quoting what SOLIDWORKS actually said", r["hostAppError"].as_str().unwrap_or("").contains("created no document"), s(&r));
check!(b, "...and telling the caller not to loop on it", r["_hint"].as_str().unwrap_or("").contains("Do NOT retry"), s(&r));
}
#[test]
fn ownership_survives_an_unanswerable_probe_and_a_com_host_respawn() {
// BUG #5, in both the shapes that were observed. No simulator needed: this
// is about the SERVER's registry, not about launching.
let scratch = TempDir::new("scratch");
let scratch_path = scratch.0.clone();
let b = Bridge::start_in(TempDir::borrowed(&scratch_path), &[]);
b.call("swx_start", json!({}));
let made = b.call("swx_new_document", json!({"type": "part"}));
let title = made["created"]["title"].as_str().unwrap_or("Part1").to_string();
check!(b, "the bridge created a document", made["success"] == true, s(&made));
let write = b.call("swx_props_set", json!({"targetDocument": title, "properties": {"Owner": "bridge"}}));
check!(b, "it may write to its own document", write["success"] == true, s(&write));
// ...and the claim is ON DISK, not only in this process's memory.
let registry: Value =
serde_json::from_str(&std::fs::read_to_string(scratch_path.join("owned-docs.json")).unwrap()).unwrap();
check!(b, "the ownership registry is persisted", s(®istry).contains(&title), s(®istry));
// THE OBSERVED FAILURE: one probe SOLIDWORKS could not answer. The old code
// read that as "no session, nothing open" and pruned the registry empty —
// permanently disowning documents the bridge had created seconds earlier.
std::fs::write(scratch_path.join("mock-fail-app-state"), "").unwrap();
let blind = b.call("swx_props_set", json!({"targetDocument": title, "properties": {"Owner": "bridge"}}));
check!(b, "an unanswerable probe fails HONESTLY", blind["success"] == false, s(&blind));
check!(
b,
"...and never claims the document was not ours",
!s(&blind).contains("did not create"),
s(&blind)
);
std::fs::remove_file(scratch_path.join("mock-fail-app-state")).unwrap();
let after = b.call("swx_props_set", json!({"targetDocument": title, "properties": {"Owner": "bridge"}}));
check!(b, "OWNERSHIP SURVIVED the blind window", after["success"] == true, s(&after));
// THE SECOND SHAPE: the com host is killed mid-build and respawns.
let host_pid = b.call("swx_status", json!({}))["com"]["hostPid"].as_u64().unwrap();
assert!(
std::process::Command::new("kill")
.args(["-9", &host_pid.to_string()])
.status()
.expect("kill the com host")
.success(),
"could not kill com host {host_pid}"
);
std::thread::sleep(Duration::from_millis(300));
let st = b.call("swx_status", json!({}));
check!(b, "the com host really was replaced", st["com"]["hostPid"].as_u64().unwrap_or(host_pid) != host_pid, s(&st["com"]));
let survived = b.call("swx_props_set", json!({"targetDocument": title, "properties": {"Owner": "bridge"}}));
check!(b, "OWNERSHIP SURVIVED the respawn", survived["success"] == true, s(&survived));
// ...and a whole new SERVER reads the registry back off disk.
drop(b);
let b2 = Bridge::start_in(TempDir::borrowed(&scratch_path), &[]);
let st2 = b2.call("swx_status", json!({}));
check!(b2, "a restarted bridge restores the registry", b2.log().contains("ownership registry restored"), b2.log());
check!(b2, "...and is otherwise healthy", st2["success"] == true, s(&st2));
}
#[test]
fn a_bridge_that_cannot_see_the_machine_still_launches_rather_than_refusing() {
// No simulator, not Windows: the process census cannot be taken. UNKNOWN
// must not become "SOLIDWORKS is running" (which would refuse every launch)
// NOR a silent claim that it is not. The launch proceeds, and the report
// says the census failed rather than inventing a census.
let lnk = TempDir::new("lnk");
let path = format!("{}/SOLIDWORKS Design.lnk", lnk.path());
std::fs::write(&path, "x").unwrap();
let b = Bridge::start_with(&[
("SWX_SOLIDWORKS_SHORTCUT", &path),
("SWX_LAUNCH_RUN_BEGIN_SECS", "1"),
("SWX_LAUNCH_POLL_MS", "100"),
]);
let r = b.call("swx_start", json!({"reason": "starting SOLIDWORKS", "waitSeconds": 0}));
check!(b, "the launch is attempted", r["success"] == true && r["stillLaunching"] == true, s(&r));
let st = b.call("swx_status", json!({}));
check!(
b,
"an unavailable census is reported as unavailable, never as 'not running'",
st["solidworks"]["running"].is_null() || st["solidworks"]["censusError"].is_string(),
s(&st["solidworks"])
);
check!(
b,
"...and responsiveness is UNKNOWN rather than a comfortable 'yes'",
st["solidworks"]["responding"].is_null(),
s(&st["solidworks"])
);
}
#[test]
fn a_corrupt_registry_falls_back_to_the_previous_generation() {
// `write_atomic` keeps one generation as `.bak`, and it is a strictly
// better answer than owning nothing: it is a registry this bridge itself
// wrote one write ago, so at worst it is missing the newest claim — while
// owning nothing refuses every document the bridge created before the
// restart, which IS bug #5.
let scratch = TempDir::new("scratch");
let dir = scratch.0.clone();
{
let b = Bridge::start_in(TempDir::borrowed(&dir), &[]);
b.call("swx_start", json!({}));
let a = b.call("swx_new_document", json!({"type": "part"}));
check!(b, "a document is created and owned", a["success"] == true, s(&a));
// A second write, so a .bak generation exists containing the first.
let c = b.call("swx_new_document", json!({"type": "part"}));
check!(b, "...and a second", c["success"] == true, s(&c));
}
check!(
Bridge::start_in(TempDir::borrowed(&dir), &[]),
"precondition: a backup generation exists",
dir.join("owned-docs.bak").exists(),
format!("{:?}", std::fs::read_dir(&dir).unwrap().count())
);
// A rewrite interrupted half way through.
std::fs::write(dir.join("owned-docs.json"), r#"{"documents":[{"title":"Part1","sess"#).unwrap();
let b2 = Bridge::start_in(TempDir::borrowed(&dir), &[]);
let log = b2.log();
check!(b2, "the corruption is reported, loudly", log.contains("UNREADABLE") || log.contains("could not be read"), log.clone());
check!(
b2,
"...and the previous generation is used instead of owning nothing",
log.contains("recovered from owned-docs.bak"),
log
);
}
#[test]
fn an_unknown_interference_count_reaches_the_caller_as_null_with_guidance() {
// The shape has to be TESTED, not just produced: `count: null` on a
// clearance report is the one null in this bridge that can reach a physical
// part, and every caller's truthiness check reads it as zero.
let b = Bridge::start();
b.call("swx_start", json!({"reason": "checking clearances"}));
let made = b.call("swx_new_document", json!({"type": "assembly"}));
let title = made["created"]["title"].as_str().unwrap_or("Assem1").to_string();
let ok = b.call("swx_interference", json!({"targetDocument": title}));
check!(b, "a normal check reports a count and says it is known", ok["count"] == 1 && ok["countKnown"] == true, s(&ok));
std::fs::write(b.scratch.join("mock-interference-count-unknown"), "").unwrap();
let r = b.call("swx_interference", json!({"targetDocument": title}));
check!(b, "an uncountable check answers null, never 0", r["count"].is_null() && r["countKnown"] == false, s(&r));
check!(
b,
"...and the hint tells the caller not to use a truthiness check on it",
r["_hint"].as_str().unwrap_or("").contains("never with a truthiness check")
&& r["_hint"].as_str().unwrap_or("").contains("countKnown"),
s(&r)
);
check!(
b,
"...saying plainly that null is not a clear result",
r["_hint"].as_str().unwrap_or("").contains("NOT a clear result"),
s(&r)
);
// The same shape on the other two newly-nullable counts.
std::fs::write(b.scratch.join("mock-doccount-unknown"), "").unwrap();
let rec = b.call("swx_check_recovery", json!({}));
check!(b, "an unknown open-document count is null, not 0", rec["com"]["openDocuments"].is_null(), s(&rec));
std::fs::write(b.scratch.join("mock-component-count-unknown"), "").unwrap();
let add = b.call("swx_add_component", json!({
"targetDocument": title,
"components": [{"file": "C:\\mock\\bracket.sldprt", "xMm": 0, "yMm": 0, "zMm": 0}]
}));
check!(
b,
"an uncountable assembly refuses the insert rather than risking a DOUBLE insert",
add["success"] == false && add["errorCode"] == "component_count_failed",
s(&add)
);
check!(
b,
"...and says why, in terms of the harm",
add["error"].as_str().unwrap_or("").contains("TWICE"),
s(&add)
);
}
#[test]
fn a_not_responding_solidworks_that_is_burning_cpu_is_reported_as_busy() {
// LIVE, 2026-09-08: 17.9 GB with an unsaved assembly, not-responding and a
// COM timeout for ~5 minutes, ~70% of a core, memory creeping, NO dialog —
// and it RECOVERED once Drew saved. The bridge said "only the user can end
// it". `verdict` exists so a caller can tell "wait" from "it is gone".
let m = Machine::new("busy", json!({}))
.with_running_solidworks()
.sldworks(json!({
"responding": false, "hwnd": 4242, "privateMB": 17900, "handles": 21000,
// Every metrics read advances the simulated CPU clock: work IS
// happening, which is the whole point.
"cpuSeconds": 1000.0, "cpuSecondsPerRead": 12.0
}));
let scratch = TempDir::new("scratch");
let dir = scratch.0.clone();
let b = m.boot(scratch, &[]);
std::fs::write(dir.join("mock-com-timeout"), "").unwrap();
// Two reads far enough apart: the verdict needs a sample, and one reading is
// never enough to call a session dead.
b.call("swx_status", json!({}));
let st = b.call("swx_status", json!({}));
let sw = &st["solidworks"];
check!(b, "the window is not responding and a COM call timed out", sw["hung"] == true, s(sw));
check!(b, "...but the verdict is BUSY, not dead", sw["verdict"] == "busy", s(sw));
check!(
b,
"...and the advice is to WAIT, not to end the task",
sw["verdictAdvice"].as_str().unwrap_or("").contains("WAIT")
&& !sw["verdictAdvice"].as_str().unwrap_or("").contains("ONLY THE USER CAN END IT"),
s(sw)
);
check!(b, "...with the CPU it burned across the sample", sw["cpuActiveSeconds"].as_f64().unwrap_or(0.0) > 0.0, s(sw));
check!(b, "...and the memory delta beside it", sw["memoryDeltaMB"].is_i64(), s(sw));
}
#[test]
fn one_reading_never_calls_a_session_dead() {
// The guard that would have stopped the wrong call: with no previous sample
// there is no CPU delta, so the verdict is UNKNOWN however bad it looks.
let m = Machine::new("onesample", json!({}))
.with_running_solidworks()
.sldworks(json!({"responding": false, "hwnd": 4242, "privateMB": 17900, "cpuSecondsPerRead": 0.0}));
let scratch = TempDir::new("scratch");
let dir = scratch.0.clone();
let b = m.boot(scratch, &[]);
std::fs::write(dir.join("mock-com-timeout"), "").unwrap();
let st = b.call("swx_status", json!({}));
let sw = &st["solidworks"];
check!(b, "a single reading is UNKNOWN, never dead", sw["verdict"] == "unknown", s(sw));
check!(
b,
"...and it says to poll again rather than concluding",
sw["verdictAdvice"].as_str().unwrap_or("").contains("Poll swx_status again"),
s(sw)
);
}
// ════════════════════════════════════════════════════════════════════════════
// LIVE, 2026-09-11 — A SIGN-IN WAIT IS NOT A STALL
// ════════════════════════════════════════════════════════════════════════════
/// Drew's machine, and the worst thing this bridge has done to date.
///
/// A cold launch stopped at the 3DEXPERIENCE ID sign-in — a native WebView2
/// window (`DSYSysLoginPanelWebView2Class`) owned by `SWXDesktopLauncher.exe` in
/// the bridge's OWN chain, with no controls UIA can even see. The
/// "sldworks.exe within 120s" deadline then fired while he was reading it, and
/// the machine killed its own chain — including the window he was about to type
/// into — escalated to the next route, killed that too, and finished
/// `exhausted`.
///
/// The launch was not stalled. It was waiting for a person, and a person is
/// slower than any budget worth setting.
#[test]
fn a_sign_in_window_pauses_every_deadline_instead_of_killing_the_chain() {
// sldworks NEVER appears while the sign-in is up: on the old code the
// 4-second budget here would fire and kill the chain.
let m = Machine::new(
"signin",
json!({"shellExecute": {"chain": true, "runBeginAfterMs": 0}}),
);
// The sign-in window, owned by whichever of our launcher pids is asked.
std::fs::write(
m.dir.join("sign-in.json"),
json!({"hwnd": 777001, "className": "DSYSysLoginPanelWebView2Class",
"title": "Login | 3DEXPERIENCE ID | Dassault Systemes"})
.to_string(),
)
.unwrap();
let b = m.boot(TempDir::new("signin-scratch"), &[]);
let r = b.call("swx_start", json!({"waitSeconds": 0, "reason": "the user asked for SOLIDWORKS"}));
check!(b, "the launch is in flight", r["success"] == true || r["stillLaunching"] == true, s(&r));
// Well past the sldworks budget (4s here, 120s live) with the window up.
std::thread::sleep(std::time::Duration::from_secs(7));
let st = b.call("swx_status", json!({}));
let launch = &st["launch"];
check!(
b,
"NOTHING was killed while the user was being asked to sign in",
m.terminated().is_empty(),
format!("{:?}", m.terminated())
);
check!(
b,
"...the machine did not escalate to another route",
m.invocations() == vec!["shellExecute".to_string()],
format!("{:?}", m.invocations())
);
check!(
b,
"...the stage SAYS it is waiting on a sign-in",
launch["stage"] == "helperSignIn" || launch["signInSeen"] == true,
s(launch)
);
check!(b, "...and the attempt is not finished", launch["finished"] != true, s(launch));
// 0.9.12: SAID PLAINLY, with the window — "waitingOn: userSignIn".
check!(
b,
"...swx_status says it is waiting on the USER's sign-in, naming the window",
st["waitingOn"] == "userSignIn"
&& st["signInWindow"]["title"].as_str().unwrap_or("").contains("3DEXPERIENCE ID") && st["signInWindow"]["hwnd"].is_number(),
s(&st)
);
// The user signs in: the window closes and SOLIDWORKS comes up.
std::fs::remove_file(m.dir.join("sign-in.json")).unwrap();
std::fs::write(
m.dir.join("routes.json"),
json!({"shellExecute": {"chain": true, "runBeginAfterMs": 0, "sldworksAfterMs": 0,
"attachAfterMs": 0}})
.to_string(),
)
.unwrap();
let mut attached = false;
for _ in 0..60 {
std::thread::sleep(std::time::Duration::from_millis(250));
let st = b.call("swx_status", json!({}));
if st["launch"]["stage"] == "attached" || st["com"]["running"] == true {
attached = true;
break;
}
}
let st = b.call("swx_status", json!({}));
check!(b, "once signed in, the launch attaches", attached, s(&st["launch"]));
check!(
b,
"...and the session is still recorded as OURS — attribution survives the wait",
st["launch"]["attributed"] == true,
s(&st["launch"])
);
check!(b, "...with nothing killed, start to finish", m.terminated().is_empty(),
format!("{:?}", m.terminated()));
}
/// The other half: an unreadable window census must PAUSE too. Resuming a
/// deadline on a census we could not take is how the machine killed a window it
/// could not see.
#[test]
fn a_window_census_that_cannot_be_read_pauses_the_deadline_too() {
let m = Machine::new(
"signin-blind",
json!({"shellExecute": {"chain": true, "runBeginAfterMs": 0}}),
);
std::fs::write(m.dir.join("window-census-fails"), "").unwrap();
let b = m.boot(TempDir::new("signin-blind-scratch"), &[]);
b.call("swx_start", json!({"waitSeconds": 0, "reason": "launching"}));
std::thread::sleep(std::time::Duration::from_secs(7));
check!(
b,
"a blind census does not license a kill",
m.terminated().is_empty(),
format!("{:?}", m.terminated())
);
}
/// RE-ATTRIBUTION NEEDS LINEAGE, not just timing.
///
/// A session that comes up after the bridge gave up IS often the one it started
/// — that is the live failure 0.6.0 fixed. But "it started after our invoke and
/// we never saw another launcher chain" is timing plus the ABSENCE of an
/// observation, and absence is not evidence: a chain that started and finished
/// between two censuses was never seen at all. 0.6.1 requires a positive link —
/// a parent walk that reaches our own chain, or a launcher log a thread of our
/// chain wrote.
#[test]
fn a_session_with_no_link_to_our_chain_is_not_adopted_after_the_fact() {
let m = Machine::new(
"reattr-nolineage",
// Every route fails to attach, so the attempt finishes un-attached.
json!({"shellExecute": {"chain": true, "runBeginAfterMs": 0},
"powershell": {"chain": false},
"explorer": {"chain": false},
"cmdStart": {"chain": false}}),
);
let b = m.boot(TempDir::new("reattr-nolineage-scratch"), &[]);
b.call("swx_start", json!({"waitSeconds": 0, "reason": "launching"}));
// Let the attempt run itself out.
let mut finished = false;
for _ in 0..80 {
std::thread::sleep(std::time::Duration::from_millis(250));
if b.call("swx_status", json!({}))["launch"]["finished"] == true {
finished = true;
break;
}
}
check!(b, "precondition: the attempt finished without attaching", finished, "never finished");
// NOW SOLIDWORKS APPEARS — with no parent link to anything of ours.
std::fs::write(
m.dir.join("preexisting.json"),
json!([{"pid": 99001, "image": "SLDWORKS.exe", "ageSeconds": 0}]).to_string(),
)
.unwrap();
let st = b.call("swx_status", json!({}));
check!(
b,
"a SOLIDWORKS with no proven link to our chain is NOT claimed as ours",
st["launch"]["attributed"] != true && st["launch"]["outcome"] != "attachedAfterSignIn",
s(&st["launch"])
);
}
/// PASS 16, finding 2. A DEAD parent may be lineage — the launcher always exits
/// once SOLIDWORKS is up — but "a pid we once recorded" is not enough once that
/// process is gone: the NUMBER is free. A user's launcher that reuses it would
/// have its SOLIDWORKS record our dead pid as its parent, with nothing alive left
/// to contradict it, and "the child is younger than the parent" is vacuously true
/// of anything that started later.
///
/// So the child must have been created inside the window we actually WATCHED the
/// parent run, plus a grace for the poll interval.
#[test]
fn a_child_that_appeared_long_after_our_launcher_died_is_not_its_descendant() {
let m = Machine::new(
"lineage-window",
json!({"shellExecute": {"chain": true, "runBeginAfterMs": 0, "sldworksAfterMs": 0}}),
);
let b = m.boot(TempDir::new("lineage-window-scratch"), &[]);
b.call("swx_start", json!({"waitSeconds": 0, "reason": "launching"}));
let mut finished = false;
for _ in 0..80 {
std::thread::sleep(std::time::Duration::from_millis(250));
if b.call("swx_status", json!({}))["launch"]["finished"] == true {
finished = true;
break;
}
}
check!(b, "precondition: the attempt finished", finished, "never finished");
// A SOLIDWORKS that claims our launcher's pid as its parent but was created
// an hour after we last saw that launcher alive: a recycled pid, not a child.
let chain = b.call("swx_status", json!({}))["launch"]["chainPids"].clone();
let launcher = chain
.as_array()
.and_then(|a| a.iter().find(|p| p["image"] == "SWXDesktopLauncher.exe"))
.and_then(|p| p["pid"].as_u64())
.unwrap_or(0);
check!(b, "precondition: we recorded a launcher pid", launcher > 0, format!("{chain}"));
std::fs::write(m.dir.join("sldworks-parent"), launcher.to_string()).unwrap();
std::fs::write(
m.dir.join("preexisting.json"),
json!([{"pid": 99123, "image": "SLDWORKS.exe", "ageSeconds": -3600}]).to_string(),
)
.unwrap();
let st = b.call("swx_status", json!({}));
check!(
b,
"a process created long after that launcher died is not adopted as its child",
st["launch"]["outcome"] != "attachedAfterSignIn",
s(&st["launch"])
);
}
/// PID REUSE, END TO END — through the whole bridge, with the simulator finally
/// able to pose it (`census-overrides.json` retires a pid and reissues it with a
/// new creation time).
///
/// HONEST LIMIT OF THIS TEST, because a test nobody can characterise is worse
/// than none: it asserts the OUTCOME (the bridge does not adopt that session),
/// and I could NOT establish which guard produces it. Disabling the `same_process`
/// check and the lifetime-window check — together, not just one — leaves it
/// passing, so something earlier in `reattribute_after_attach` is refusing and
/// this test does not pin the lineage rule. The rule itself is proved directly,
/// with a mutation check that does fail when it is removed, in
/// `launch::tests::a_child_created_after_we_last_saw_the_parent_alive_is_not_its_descendant`.
/// What this test genuinely covers is the WHOLE PATH: a census that retires and
/// reissues a pid does not end with somebody else's session recorded as ours.
#[test]
fn a_recycled_launcher_pid_does_not_make_someone_elses_solidworks_ours() {
let m = Machine::new(
"pid-reuse",
json!({"shellExecute": {"chain": true, "runBeginAfterMs": 0, "sldworksAfterMs": 0}}),
);
let b = m.boot(TempDir::new("pid-reuse-scratch"), &[]);
b.call("swx_start", json!({"waitSeconds": 0, "reason": "launching"}));
let mut finished = false;
for _ in 0..80 {
std::thread::sleep(std::time::Duration::from_millis(250));
if b.call("swx_status", json!({}))["launch"]["finished"] == true {
finished = true;
break;
}
}
check!(b, "precondition: the attempt finished", finished, "never finished");
let chain = b.call("swx_status", json!({}))["launch"]["chainPids"].clone();
let launcher = chain
.as_array()
.and_then(|a| a.iter().find(|p| p["image"] == "SWXDesktopLauncher.exe"))
.and_then(|p| p["pid"].as_u64())
.unwrap_or(0);
check!(b, "precondition: we recorded and watched a launcher", launcher > 0, format!("{chain}"));
let before = b.call("swx_status", json!({}))["launch"]["outcome"].clone();
// OUR launcher is retired and the number REISSUED to the user's, created now
// — inside the grace window, so timing alone cannot tell them apart. Their
// SOLIDWORKS, created now too, names that number as its parent.
std::fs::write(
m.dir.join("census-overrides.json"),
json!([
{"retire": launcher},
{"pid": launcher, "image": "SWXDesktopLauncher.exe", "ageSeconds": 0},
{"pid": 991234, "image": "SLDWORKS.exe", "ageSeconds": 0, "ppid": launcher}
])
.to_string(),
)
.unwrap();
// ...and COM becomes attachable, which is what makes re-attribution REACHABLE
// at all: without an attach it returns before it ever consults lineage, and
// this test would pass while proving nothing.
std::fs::write(
m.dir.join("routes.json"),
json!({"shellExecute": {"chain": true, "runBeginAfterMs": 0, "sldworksAfterMs": 0,
"attachAfterMs": 0}})
.to_string(),
)
.unwrap();
// The user signs in and SOLIDWORKS comes up; the next call attaches to it —
// which is the real sequence, and the only thing that makes re-attribution
// REACHABLE. Without an attach it returns before consulting lineage at all
// and this test would pass while proving nothing.
let mut attached = false;
for _ in 0..40 {
b.call("swx_start", json!({"waitSeconds": 0, "reason": "attaching to the signed-in session"}));
std::thread::sleep(std::time::Duration::from_millis(250));
if b.call("swx_status", json!({}))["com"]["running"] == true {
attached = true;
break;
}
}
check!(b, "precondition: COM attached, so lineage is the guard under test", attached, "never");
let st = b.call("swx_status", json!({}));
check!(
b,
"a SOLIDWORKS whose parent merely REUSED our launcher's pid is not adopted",
st["launch"]["outcome"] != "attachedAfterSignIn",
format!("was {before} / now {}", s(&st["launch"]))
);
}
/// RE-ATTRIBUTION IS ONLY POSSIBLE FOR AN ATTRIBUTED ROUTE, end to end.
///
/// `explorer` and `cmdStart` cannot say what they started, so nothing of theirs
/// is ever recorded as our chain and there is no identity to walk a parent back
/// to. A session that comes up after one of them may well BE ours — and this
/// bridge still will not claim it, because it cannot prove it.
///
/// The RULE is pinned deterministically in
/// `launch::tests::an_unattributable_route_has_no_identities_to_walk_back_to`;
/// this one covers the path through the running bridge. Same honest limit as the
/// test above: it asserts the outcome, not which guard produced it.
#[test]
fn an_unattributable_route_can_never_re_attribute_a_session() {
let m = Machine::new(
"reattr-unattributable",
// shellExecute reports no root at all here; explorer runs and stalls.
json!({"shellExecute": {"chain": false}, "powershell": {"chain": false},
"explorer": {"chain": true, "runBeginAfterMs": 0},
"cmdStart": {"chain": false}}),
);
let b = m.boot(TempDir::new("reattr-unattributable-scratch"), &[]);
b.call("swx_start", json!({"waitSeconds": 0, "reason": "launching"}));
let mut finished = false;
for _ in 0..100 {
std::thread::sleep(std::time::Duration::from_millis(250));
if b.call("swx_status", json!({}))["launch"]["finished"] == true {
finished = true;
break;
}
}
check!(b, "precondition: the attempt finished", finished, "never finished");
// A SOLIDWORKS appears, descended from the explorer route's own launcher —
// which really may be ours. The bridge cannot PROVE it, so it does not say it.
let st0 = b.call("swx_status", json!({}));
check!(
b,
"precondition: no attribution root was recorded for the route that ran",
st0["launch"]["attribution"].is_null() || st0["launch"]["attributed"] != true,
s(&st0["launch"])
);
std::fs::write(
m.dir.join("preexisting.json"),
json!([{"pid": 99321, "image": "SLDWORKS.exe", "ageSeconds": 0}]).to_string(),
)
.unwrap();
// COM attaches — so re-attribution is REACHED and refuses on the rule, rather
// than never being asked.
std::fs::write(
m.dir.join("routes.json"),
json!({"explorer": {"chain": true, "runBeginAfterMs": 0, "sldworksAfterMs": 0,
"attachAfterMs": 0}})
.to_string(),
)
.unwrap();
let mut attached = false;
for _ in 0..40 {
b.call("swx_start", json!({"waitSeconds": 0, "reason": "attaching to the session"}));
std::thread::sleep(std::time::Duration::from_millis(250));
if b.call("swx_status", json!({}))["com"]["running"] == true {
attached = true;
break;
}
}
check!(b, "precondition: COM attached, so the rule is what refuses", attached, "never");
let st = b.call("swx_status", json!({}));
check!(
b,
"an unattributable route never re-attributes a session, however plausible it looks",
st["launch"]["outcome"] != "attachedAfterSignIn" && st["launch"]["attributed"] != true,
s(&st["launch"])
);
}
/// THE POSITIVE CASE'S MIRROR. Same scenario, one thing changed: our launcher is
/// retired and its pid REISSUED to a different process before COM attaches. The
/// session must NOT be adopted, and the report must name which clause refused —
/// so the next person diagnosing this reads one field instead of the source.
///
/// Mutation-checked: removing the lineage rule makes this fail.
#[test]
fn a_reissued_launcher_pid_is_refused_and_the_report_names_the_clause() {
let m = Machine::new(
"reissue-named",
// The attach lands AFTER the attempt has given up — which is the whole
// sign-in shape, and the only state in which re-attribution is even
// consulted.
json!({"shellExecute": {"chain": true, "runBeginAfterMs": 0, "sldworksAfterMs": 0,
"attachAfterMs": 6000}}),
);
std::fs::write(m.dir.join("preexisting.json"), json!([]).to_string()).unwrap();
let b = m.boot(TempDir::new("reissue-named-scratch"), &[("SWX_LAUNCH_ATTACH_SECS", "2")]);
b.call("swx_start", json!({"waitSeconds": 0, "reason": "launching"}));
// Let the chain be recorded and watched.
let mut launcher = 0u64;
for _ in 0..60 {
std::thread::sleep(std::time::Duration::from_millis(200));
let st = b.call("swx_status", json!({}));
if let Some(a) = st["launch"]["chainPids"].as_array() {
if let Some(p) = a.iter().find(|p| p["image"] == "SWXDesktopLauncher.exe") {
launcher = p["pid"].as_u64().unwrap_or(0);
if st["launch"]["finished"] == true {
break;
}
}
}
}
check!(b, "precondition: our launcher was recorded and watched", launcher > 0, "none");
// ...then that pid is REISSUED to a different process, and a SOLIDWORKS
// claiming it as its parent appears. Both facts are exactly what a recycled
// pid looks like from outside.
std::fs::write(
m.dir.join("census-overrides.json"),
json!([
{"retire": launcher},
{"pid": launcher, "image": "SWXDesktopLauncher.exe", "ageSeconds": 0},
{"pid": 991234, "image": "SLDWORKS.exe", "ageSeconds": 0, "ppid": launcher}
])
.to_string(),
)
.unwrap();
let mut st = json!({});
for _ in 0..40 {
std::thread::sleep(std::time::Duration::from_millis(250));
st = b.call("swx_status", json!({}));
if st["com"]["attached"] == true {
break;
}
}
check!(b, "precondition: COM attached, so the lineage walk runs", st["com"]["attached"] == true, s(&st["com"]));
check!(
b,
"the session is NOT adopted on a reissued pid",
st["launch"]["outcome"] != "attachedAfterSignIn",
s(&st["launch"])
);
// TWO LAYERS, since 0.6.7: the report carries the COMPOSITE reason (no
// candidate could be proved), and the ledger carries the per-candidate
// clause — because "the last one stands" hid which process failed which test.
let why = st["launch"]["reattributionRefusedBecause"].as_str().unwrap_or("").to_string();
check!(
b,
"...the report names the composite reason",
why == "no_candidate_descends" || why == "attached_pid_not_ours",
format!("reattributionRefusedBecause={why:?} / {}", s(&st["launch"]))
);
let ledger = s(&b.call("swx_action_ledger", json!({"limit": 80})));
check!(
b,
"...and the ledger names the clause the REISSUED pid failed on",
ledger.contains("alive_but_different_process") || ledger.contains("child_outside_window"),
ledger.chars().rev().take(900).collect::<String>().chars().rev().collect::<String>()
);
}
/// CONSENT-AFFECTING, from pass 19. Re-attribution must claim ONLY the process
/// COM is attached to.
///
/// 0.6.6 used the attached pid to ORDER the candidates and took the first that
/// descended — so with two SOLIDWORKS running it could prove lineage for OUR
/// launcher's child while COM was attached to a DIFFERENT process, and then
/// label the session `attributed:true`. Everything downstream reads that label:
/// the quit gate's bridge-launched proof, and a quit ladder that builds its
/// Identity from the COM pid. The bridge would have been willing to END a session
/// nothing had proved was ours.
///
/// Staged here so the attached process is NOT ours and a non-attached one IS:
/// the route's own SOLIDWORKS is re-parented to a stranger, and a second one is
/// given our launcher as its parent.
#[test]
fn re_attribution_claims_only_the_process_com_is_attached_to() {
let m = Machine::new(
"attached-only",
json!({"shellExecute": {"chain": true, "runBeginAfterMs": 0, "sldworksAfterMs": 0,
"attachAfterMs": 6000}}),
);
std::fs::write(m.dir.join("preexisting.json"), json!([]).to_string()).unwrap();
let b = m.boot(TempDir::new("attached-only-scratch"), &[("SWX_LAUNCH_ATTACH_SECS", "2")]);
b.call("swx_start", json!({"waitSeconds": 0, "reason": "launching"}));
let mut launcher = 0u64;
let mut sw_pid = 0u64;
for _ in 0..60 {
std::thread::sleep(std::time::Duration::from_millis(200));
let st = b.call("swx_status", json!({}));
if let Some(a) = st["launch"]["chainPids"].as_array() {
if let Some(p) = a.iter().find(|p| p["image"] == "SWXDesktopLauncher.exe") {
launcher = p["pid"].as_u64().unwrap_or(0);
}
}
if let Some(p) = st["solidworks"]["processId"].as_u64() {
sw_pid = p;
}
if st["launch"]["finished"] == true && launcher > 0 && sw_pid > 0 {
break;
}
}
check!(b, "precondition: chain and SOLIDWORKS observed", launcher > 0 && sw_pid > 0,
format!("launcher={launcher} sw={sw_pid}"));
// The ATTACHED process is re-parented to a stranger; a SECOND SOLIDWORKS,
// which COM is NOT attached to, descends from our launcher — created at the
// moment we last SAW that launcher alive, so it is genuinely inside the
// lineage window and the ONLY thing that can refuse it is the pid-equality
// rule under test.
let last_seen = b.call("swx_status", json!({}))["launch"]["chainLastSeen"]
.as_array()
.and_then(|rows| {
rows.iter()
.find(|r| r[0].as_u64() == Some(launcher))
.and_then(|r| r[2].as_u64())
})
.unwrap_or(0);
check!(b, "precondition: we know when the launcher was last seen", last_seen > 0, "none");
// ABSOLUTE, not an age: the census computes "now" when it is read, so a
// relative age drifts by however long the test takes to get there — and the
// window is one census interval wide.
std::fs::write(
m.dir.join("census-overrides.json"),
json!([
{"pid": sw_pid, "image": "SLDWORKS.exe", "ageSeconds": 0, "ppid": 4242},
{"pid": 900777, "image": "SLDWORKS.exe", "createdAt": last_seen, "ppid": launcher}
])
.to_string(),
)
.unwrap();
let mut st = json!({});
for _ in 0..40 {
std::thread::sleep(std::time::Duration::from_millis(250));
st = b.call("swx_status", json!({}));
if st["com"]["attached"] == true {
break;
}
}
check!(b, "precondition: COM attached", st["com"]["attached"] == true, s(&st["com"]));
check!(
b,
"precondition: the attached process is NOT the one that descends from our chain",
st["com"]["processId"].as_u64() == Some(sw_pid),
s(&st["com"])
);
// NOTE on `attributed`: it stays TRUE here, and correctly — this attempt DID
// launch a chain and attribute its root. What must not happen is the extra
// claim that the session COM is now talking to is that launch's product.
check!(
b,
"the session is NOT re-claimed, because the process COM answers for is not ours",
st["launch"]["outcome"] != "attachedAfterSignIn",
s(&st["launch"])
);
check!(
b,
"...and no claim is recorded about which SOLIDWORKS this attempt produced",
st["launch"]["attributedSolidworks"].is_null(),
s(&st["launch"])
);
check!(
b,
"...and the refusal NAMES that clause",
st["launch"]["reattributionRefusedBecause"] == "attached_pid_not_ours",
format!("{}", st["launch"]["reattributionRefusedBecause"])
);
}
/// MEASURED 2026-10-12 (hosted 0.9.27): with SOLIDWORKS NOT running, `launch`
/// still said stage "attached" and "SOLIDWORKS is up and COM-attached. Carry
/// on." about the launch of 2026-10-02 — its process ten days gone. A record
/// that outlived every process it names is ENDED.
#[test]
fn a_launch_record_that_outlived_its_processes_is_ended_not_attached() {
let m = Machine::new("launch-stale-record", json!({}));
// pid 32224 has been REUSED by an unrelated process created just now: the
// same number, another process (identity is pid + creation time).
let mut rows: Vec<Value> = serde_json::from_str(&std::fs::read_to_string(m.dir.join("preexisting.json")).unwrap()).unwrap();
rows.push(json!({"pid": 32224, "image": "SWXDesktopUpdate.exe", "ageSeconds": 5}));
std::fs::write(m.dir.join("preexisting.json"), Value::Array(rows).to_string()).unwrap();
let scratch = TempDir::new("launch-stale-record-scratch");
std::fs::write(
scratch.join("launch-state.json"),
json!({"attempt": {
"id": 7, "startedAt": 1790953690u64, "invokedAt": 1790953690u64, "route": "shellExecute",
"stage": "attached", "finished": true, "outcome": "attached",
"attribution": {"pid": 32224, "createdAt": 1790953692u64, "source": "shellExecuteHandle"},
"attributedSolidworks": {"pid": 41000, "createdAt": 1790953720u64},
"chainPids": [{"pid": 32224, "image": "CATSTART.exe", "createdAt": 1790953692u64}]
}}).to_string(),
)
.unwrap();
let b = m.boot(TempDir::borrowed(&scratch.0), &[]);
let st = b.call("swx_status", json!({}));
let l = &st["launch"];
check!(
b,
"stage ended (not attached), finished, endedWhy names the gone pids, the old stage kept as recordedStage",
l["stage"] == "ended" && l["finished"] == true && l["recordedStage"] == "attached"
&& l["endedWhy"].as_str().unwrap_or("").contains("32224"),
s(l)
);
check!(
b,
"the advice never says carry on while nothing it names runs",
!l["advice"].as_str().unwrap_or("").to_lowercase().contains("carry on") && l["advice"].as_str().unwrap_or("").contains("OVER"),
s(l)
);
}
// ── 0.11.2: THE FALSE STALL OF 2026-10-15 ───────────────────────────────────
//
// From the laptop's ledger (attempt 24, route platformShortcut): Run Begin at
// 14:49:04 and the helper sign-in with it; at 14:51:11 the supervisor called
// the route STALLED (no sldworks.exe within the budget), killed CATSTART and
// invoked the shortcut AGAIN; at 14:51:17 sldworks.exe came up — the FIRST
// invoke's — and the second invoke reached it as an "open" request: a File ▸
// Open dialog that blocked every quit for the rest of the day.
/// The File ▸ Open as measured, owned by the simulated main frame, its frame disabled.
fn open_dialog() -> Value {
json!({"hwnd": 205698, "className": "#32770", "title": "Open ", "visible": true, "owner": 6622700,
"ownerEnabled": false, "style": 0x96CC_02C4u64, "exStyle": 0x10101u64, "rect": [0, 0, 853, 533]})
}
fn ledger_actions(b: &Bridge) -> Vec<Value> {
b.call("swx_action_ledger", json!({"limit": 500}))["actions"].as_array().cloned().unwrap_or_default()
}
#[test]
fn a_slow_helper_sign_in_is_not_a_stall_and_nothing_is_invoked_again() {
// The incident scaled to the suite's budgets (sldworks 4 s): the helper
// sign-in from +0.1 s, sldworks.exe at +5.5 s — past the sldworks budget,
// as 2 min 13 s was past 120 s — and attach at +6.5 s. A re-invoke would
// put up the Open dialog.
let m = Machine::new(
"helper-slow",
json!({
"shellExecute": {"chain": true, "runBeginAfterMs": 0, "helperAfterMs": 100, "sldworksAfterMs": 5500, "attachAfterMs": 6500},
"powershell": {"chain": true, "runBeginAfterMs": 0, "sldworksAfterMs": 100, "attachAfterMs": 300, "dialogOnInvoke": [open_dialog()]}
}),
);
let b = m.boot(TempDir::new("scratch"), &[]);
b.call("swx_start", json!({"reason": "modelling for Drew", "waitSeconds": 0}));
let st = wait_for_launch(&b, 30);
check!(b, "the launch ATTACHES to the first invoke's SOLIDWORKS", st["launch"]["outcome"] == "attached", s(&st["launch"]));
check!(b, "ONE invocation: the shortcut is never invoked again", m.invocations() == vec!["shellExecute".to_string()], format!("{:?}", m.invocations()));
check!(b, "nothing was killed: a chain in its helper sign-in is not stalled", m.terminated().is_empty(), format!("{:?}", m.terminated()));
check!(
b,
"no stall on the record, and no dialog on the screen",
!ledger_actions(&b).iter().any(|e| e["action"] == "solidworks_launch_stalled")
&& !std::fs::read_to_string(m.dir.join("windows.json")).unwrap_or_default().contains("205698"),
s(&json!(ledger_actions(&b)))
);
}
#[test]
fn a_solidworks_that_comes_up_after_a_stall_cleanup_is_waited_for_never_re_invoked_over() {
// No helper log, so the sldworks budget (4 s) does run out — but the
// launcher had already spawned sldworks.exe, which comes up at +4.3 s
// although its chain was killed.
let m = Machine::new(
"settle",
json!({
"shellExecute": {"chain": true, "runBeginAfterMs": 0, "sldworksAfterMs": 4300, "sldworksSurvivesKill": true, "attachAfterMs": 5000},
"powershell": {"chain": true, "runBeginAfterMs": 0, "sldworksAfterMs": 100, "attachAfterMs": 300, "dialogOnInvoke": [open_dialog()]}
}),
);
let b = m.boot(TempDir::new("scratch"), &[("SWX_LAUNCH_RETRY_SETTLE_MS", "2500")]);
b.call("swx_start", json!({"reason": "modelling for Drew", "waitSeconds": 0}));
let st = wait_for_launch(&b, 30);
check!(b, "the next route is NEVER invoked over a SOLIDWORKS this launch started", m.invocations() == vec!["shellExecute".to_string()], format!("{:?}", m.invocations()));
check!(
b,
"...the retry is declined on the record, naming the SOLIDWORKS",
ledger_actions(&b).iter().any(|e| e["action"] == "launch_retry_declined" && e["detail"]["why"].as_str().unwrap_or("").contains("already running")),
s(&json!(ledger_actions(&b)))
);
check!(b, "...and the launch attaches to it", st["launch"]["outcome"] == "attached", s(&st["launch"]));
}
#[test]
fn a_file_dialog_a_retry_raised_is_attributed_to_it_and_cancelled_by_the_launch() {
// A genuine stall (no Run Begin, no SOLIDWORKS) → a legitimate retry,
// whose invoke puts up the Open dialog anyway. It is the launch's own: the
// supervisor attributes it to `launch.retry` and cancels it.
let m = Machine::new(
"retry-dialog",
json!({
"shellExecute": {"chain": true},
"powershell": {"chain": true, "runBeginAfterMs": 0, "sldworksAfterMs": 100, "attachAfterMs": 300, "dialogOnInvoke": [open_dialog()]}
}),
);
std::fs::write(m.dir.join("file-dialogs.json"), json!({"205698": {"standard": true, "fileName": ""}}).to_string()).unwrap();
std::fs::write(m.dir.join("last-input-secs"), "3600").unwrap();
let b = m.boot(TempDir::new("scratch"), &[]);
b.call("swx_start", json!({"reason": "modelling for Drew", "waitSeconds": 0}));
let st = wait_for_launch(&b, 30);
check!(b, "the retry attaches", st["launch"]["outcome"] == "attached" && m.invocations().len() == 2, s(&st["launch"]));
let deadline = Instant::now() + Duration::from_secs(8);
let mut cancelled = None;
while Instant::now() < deadline && cancelled.is_none() {
cancelled = ledger_actions(&b).into_iter().find(|e| e["action"] == "dialog_cancelled");
std::thread::sleep(Duration::from_millis(200));
}
let c = cancelled.unwrap_or(Value::Null);
check!(
b,
"the dialog is attributed to launch.retry and CANCELLED by the launch, ledgered",
c["detail"]["raisedDuring"] == "launch.retry" && c["detail"]["ok"] == true && c["detail"]["hwnd"] == 205698,
s(&json!(ledger_actions(&b)))
);
check!(b, "...the window is gone", !std::fs::read_to_string(m.dir.join("windows.json")).unwrap_or_default().contains("205698"), "");
}