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.6.9 source: launch resilience, honest counts, ownership persistence, drain, quit ladder — 21 adversary passes, 303 te…
cf16621
1mo ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949
//! WORKSTREAM A — import / document-enumeration robustness.
//!
//! A live 0.4.4 dev bridge was found holding 1342 open documents (8 GB), all
//! `%TEMP%\swx<pid>\IC~~\First.step.SLDPRT` — the same two STEP fixtures
//! imported over and over and never closed. `swx_docs` blocked the full 30s
//! marshalling them and got the com host killed; that leak was the real root of
//! the crate-build memory exhaustion (see docs/IMPORT-LEAK.md).
//!
//! `swx_import`'s contract is CORRECT and unchanged: it returns one open,
//! unsaved, bridge-created document, exactly like `swx_new_document`. These
//! tests cover the three defects around it: enumeration must degrade (never
//! wedge), a drain verb must clear the temps in the default posture, and import
//! must assert the net count it added.
//!
//! The cap is lowered to a handful via `SWX_DOC_ENUM_CAP` so the degrade/drain
//! paths are reached with a few imports rather than 200+.
mod common;
use common::{Bridge, TempDir};
use serde_json::{json, Value};
fn s(v: &Value) -> String {
serde_json::to_string_pretty(v).unwrap_or_default()
}
/// A tiny STEP fixture on disk (content is irrelevant to the mock).
fn fixture(dir: &TempDir, name: &str) -> String {
let p = dir.join(name);
std::fs::write(&p, b"ISO-10303-21;\nHEADER;\nENDSEC;\n").unwrap();
p.to_string_lossy().to_string()
}
#[test]
fn import_asserts_exactly_one_new_document_and_says_it_stays_open() {
let b = Bridge::start();
b.call("swx_start", json!({}));
let files = TempDir::new("fixtures");
let r = b.call("swx_import", json!({"filePath": fixture(&files, "First.step")}));
check!(b, "import succeeds", r["success"] == true, s(&r));
check!(
b,
"it added exactly one open document and says so",
r["documentsOpened"] == 1 && r["netCountOk"] == true,
s(&r)
);
check!(b, "...and reports the resulting title", r["imported"]["title"].as_str().unwrap_or("").ends_with(".SLDPRT"), s(&r));
check!(
b,
"...and the hint says it stays open until closed or saved, like new_document",
r["_hint"].as_str().unwrap_or("").contains("stays open"),
s(&r)
);
}
#[test]
fn a_huge_document_count_degrades_instead_of_timing_out() {
// Cap 3: the 4th open document tips swx_docs into the truncated report.
let b = Bridge::start_with(&[("SWX_DOC_ENUM_CAP", "3")]);
b.call("swx_start", json!({}));
let files = TempDir::new("fixtures");
for i in 0..5 {
let r = b.call("swx_import", json!({"filePath": fixture(&files, &format!("F{i}.step"))}));
check!(b, "each import adds one", r["documentsOpened"] == 1, s(&r));
}
let docs = b.call("swx_docs", json!({}));
check!(b, "swx_docs still answers success — never a timeout", docs["success"] == true, s(&docs));
check!(b, "...and reports it truncated rather than enumerating", docs["truncated"] == true, s(&docs));
check!(b, "...with the authoritative count", docs["count"].as_u64().unwrap_or(0) >= 5, s(&docs));
check!(
b,
"...a bounded sample, not the whole list",
docs["sample"].as_array().map(|a| a.len() <= 10 && !a.is_empty()).unwrap_or(false),
s(&docs)
);
check!(b, "...the full docs array is empty (not enumerated)", docs["docs"] == json!([]), s(&docs));
check!(
b,
"...and the hint points at the drain verb",
docs["_hint"].as_str().unwrap_or("").contains("swx_close_import_temps"),
s(&docs)
);
// swx_status must also stay fast — its openDocs is GetDocumentCount, and
// the launch/health block must not have wedged on the count either.
let st = b.call("swx_status", json!({}));
check!(b, "swx_status answers with a large count and does not wedge", st["success"] == true, s(&st));
}
#[test]
fn close_import_temps_drains_them_in_the_default_posture() {
let b = Bridge::start_with(&[("SWX_DOC_ENUM_CAP", "3")]);
b.call("swx_start", json!({}));
let files = TempDir::new("fixtures");
for i in 0..6 {
b.call("swx_import", json!({"filePath": fixture(&files, &format!("Leak{i}.step"))}));
}
// A non-temp document the drain must NEVER touch.
let made = b.call("swx_new_document", json!({"type": "part"}));
let keep = made["created"]["title"].as_str().unwrap_or("Part1").to_string();
// Default posture (no SWX_ALLOW_USER_WRITES): a reason is enough.
let gate = b.call("swx_close_import_temps", json!({}));
check!(b, "the drain needs a reason", gate["errorCode"] == "reason_required", s(&gate));
let drain = b.call("swx_close_import_temps", json!({"reason": "clearing leaked STEP import temps"}));
check!(b, "it runs without the write opt-in", drain["success"] == true, s(&drain));
check!(b, "...and reports how many it closed", drain["closed"].as_u64().unwrap_or(0) >= 6, s(&drain));
check!(b, "...and that it finished", drain["done"] == true, s(&drain));
// The imports are gone; the real document the bridge created is untouched.
let docs = b.call("swx_docs", json!({}));
check!(b, "the count dropped below the cap once the temps were drained", docs["truncated"] != json!(true), s(&docs));
let titles: Vec<String> = docs["docs"].as_array().cloned().unwrap_or_default()
.iter().filter_map(|d| d["title"].as_str().map(String::from)).collect();
check!(b, "the new_document survived the drain", titles.contains(&keep), format!("{titles:?}"));
check!(
b,
"no import temp remains",
!titles.iter().any(|t| t.contains(".step") || t.starts_with("Leak")),
format!("{titles:?}")
);
}
#[test]
fn the_drain_is_bounded_per_call_and_reports_more_remaining() {
// batch:2 forces more than one pass over 5 temps, so `done:false` is
// exercised — the property that keeps a 1342-count drain from wedging the
// com host in a single unbounded call.
let b = Bridge::start_with(&[("SWX_DOC_ENUM_CAP", "3")]);
b.call("swx_start", json!({}));
let files = TempDir::new("fixtures");
for i in 0..5 {
b.call("swx_import", json!({"filePath": fixture(&files, &format!("B{i}.step"))}));
}
let first = b.call("swx_close_import_temps", json!({"reason": "bounded drain", "batch": 2, "maxSeconds": 5}));
check!(b, "a bounded drain still succeeds", first["success"] == true, s(&first));
check!(b, "...and closed at least the first batch", first["closed"].as_u64().unwrap_or(0) >= 2, s(&first));
// Whether one verb call finished depends on wall budget; a second call
// must always be able to finish the rest.
let second = b.call("swx_close_import_temps", json!({"reason": "finish the drain"}));
check!(b, "a second call finishes", second["success"] == true && second["done"] == true, s(&second));
let docs = b.call("swx_docs", json!({}));
check!(b, "everything drained", docs["truncated"] != json!(true), s(&docs));
}
#[test]
fn import_temps_survive_a_com_host_respawn_and_are_still_drainable() {
// The 1342 belonged to a DEAD bridge process — the identifying proof is the
// IC~~ path, which survives any respawn or pid change, not the in-memory
// registry. Killing the com host mid-life must not make the temps
// un-drainable.
let scratch = TempDir::new("scratch");
let dir = scratch.0.clone();
let b = Bridge::start_in(TempDir::borrowed(&dir), &[("SWX_DOC_ENUM_CAP", "3")]);
b.call("swx_start", json!({}));
let files = TempDir::new("fixtures");
for i in 0..5 {
b.call("swx_import", json!({"filePath": fixture(&files, &format!("R{i}.step"))}));
}
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().unwrap().success(),
"kill com host {host_pid}"
);
std::thread::sleep(std::time::Duration::from_millis(300));
// The mock persists its session across respawns, so the temps are still
// open; the drain identifies them by path, not by a registry the respawn
// cleared.
let drain = b.call("swx_close_import_temps", json!({"reason": "drain after a respawn"}));
check!(b, "the drain works after a respawn", drain["success"] == true, s(&drain));
check!(b, "...and still closed the temps", drain["closed"].as_u64().unwrap_or(0) >= 5, s(&drain));
}
#[test]
fn an_unreadable_document_count_is_unknown_and_never_enumerated() {
// F2. `truncated = count.map(..).unwrap_or(false)` fell through to the FULL
// enumeration when the count came back unreadable — which is the 30s wedge
// the cap exists to prevent, and most likely precisely when the session is
// under the memory pressure that made the count unreadable.
let b = Bridge::start();
b.call("swx_start", json!({}));
let files = TempDir::new("fixtures");
for i in 0..3 {
b.call("swx_import", json!({"filePath": fixture(&files, &format!("U{i}.step"))}));
}
std::fs::write(b.scratch.join("mock-doccount-none"), "").unwrap();
let docs = b.call("swx_docs", json!({}));
check!(b, "swx_docs still answers", docs["success"] == true, s(&docs));
check!(b, "the count is null — UNKNOWN, not zero", docs["count"].is_null(), s(&docs));
check!(b, "...and it is reported as truncated, so nothing was enumerated", docs["truncated"] == true, s(&docs));
check!(b, "...with no list and no sample invented", docs["docs"] == json!([]) && docs["sample"] == json!([]), s(&docs));
check!(
b,
"...and the hint says UNKNOWN is not zero",
docs["_hint"].as_str().unwrap_or("").contains("not zero"),
s(&docs)
);
// OWNERSHIP MUST NOT BE PRUNED against an unlistable session — the registry
// still holds what the bridge created.
let tracked = b.call("swx_reset_workspace", json!({"titles": ["nothing-by-this-name"]}));
check!(
b,
"ownership survived an unreadable count (the prune is skipped when truncated)",
tracked["trackedScratchDocuments"].as_array().map(|a| !a.is_empty()).unwrap_or(false),
s(&tracked)
);
// ...and a verb that cannot identify its target says THAT, rather than
// claiming the document is not open — which would be a confident wrong
// answer about the user's session.
let closed = b.call("swx_close_doc", json!({"name": "Part1", "save": false}));
check!(
b,
"a verb refuses with 'list unavailable', never a false 'not open'",
closed["errorCode"] == "document_list_unavailable",
s(&closed)
);
check!(
b,
"...and points at the drain",
closed["_hint"].as_str().unwrap_or("").contains("swx_close_import_temps"),
s(&closed)
);
}
#[test]
fn a_mock_temp_is_matched_by_the_same_path_proof_the_real_drain_uses() {
// F3. The mock used to report path:"" for a temp and match the drain on a
// mock-only flag, so `is_import_temp` — the ownership proof the whole drain
// rests on — was never exercised against a value the real backend produces.
let b = Bridge::start();
b.call("swx_start", json!({}));
let files = TempDir::new("fixtures");
let r = b.call("swx_import", json!({"filePath": fixture(&files, "Proof.step")}));
let path = r["imported"]["path"].as_str().unwrap_or("").to_string();
check!(
b,
"an imported document reports the temp path a real GetPathName would",
swx_bridge::comhost::host::is_import_temp(&path),
format!("path={path} / {}", s(&r))
);
// ...and a document the bridge created normally is NOT a temp.
let made = b.call("swx_new_document", json!({"type": "part"}));
let mpath = made["created"]["path"].as_str().unwrap_or("");
check!(b, "a new_document is not an import temp", !swx_bridge::comhost::host::is_import_temp(mpath), s(&made));
}
/// ADVERSARY PASS 7, D-1. The drain's location proof is the RESOLVED `%TEMP%`,
/// and nothing else — the shape of the path is not a substitute for it. So when
/// `%TEMP%` cannot be resolved, the drain must REFUSE rather than report that it
/// found nothing to close: on a session holding hundreds of leaked temps,
/// `closed:0, done:true` is a lie a caller acts on.
#[test]
fn an_unresolvable_temp_dir_refuses_the_drain_instead_of_closing_nothing() {
let b = Bridge::start();
b.call("swx_start", json!({}));
let files = TempDir::new("fixtures-noroot");
b.call("swx_import", json!({"filePath": fixture(&files, "Leak.step")}));
// %TEMP% unreadable, between calls.
std::fs::write(b.scratch.join("mock-temp-root-unknown"), "").unwrap();
let r = b.call(
"swx_close_import_temps",
json!({"reason": "draining the leak the session is holding"}),
);
check!(
b,
"the drain REFUSES when the temp root is unknown",
r["success"] == false && r["errorCode"] == "temp_root_unknown",
s(&r)
);
check!(
b,
"...and says why in terms of the harm, not the plumbing",
r["error"].as_str().unwrap_or("").contains("the ONLY proof")
&& r["_hint"].as_str().unwrap_or("").contains("no save prompt"),
s(&r)
);
check!(b, "...and closed nothing", r["closed"].is_null(), s(&r));
// With the root readable again, the same call drains.
std::fs::remove_file(b.scratch.join("mock-temp-root-unknown")).unwrap();
let ok = b.call("swx_close_import_temps", json!({"reason": "draining the leak"}));
check!(
b,
"the drain works again once %TEMP% resolves",
ok["success"] == true && ok["closed"].as_u64().unwrap_or(0) >= 1,
s(&ok)
);
}
/// ADVERSARY PASS 8, F. The drain compares a path SOLIDWORKS reported against a
/// directory THIS process resolved, and the two can legitimately differ — ab may
/// run under a service profile or a redirected `TEMP`, and the temps this verb
/// exists for were written by an earlier, dead bridge process. On 0.5.3 a
/// mismatch matched nothing and answered `success:true, closed:0, done:true`
/// with the hint *"Every open STEP/IGES import temporary was closed"* — three of
/// them open. A miss is the safe direction for the CLOSE, never for the REPORT.
#[test]
fn a_root_mismatch_is_reported_as_a_mismatch_not_as_a_finished_drain() {
let b = Bridge::start_with(&[("SWX_TEMP_ROOT", r"C:\Windows\Temp")]);
b.call("swx_start", json!({}));
let files = TempDir::new("fixtures-mismatch");
for i in 0..3 {
b.call("swx_import", json!({"filePath": fixture(&files, &format!("Widget{i}.step"))}));
}
let r = b.call("swx_close_import_temps", json!({"reason": "draining leaked temps"}));
check!(b, "nothing was closed — the root really does not match", r["closed"] == 0, s(&r));
check!(
b,
"...the response names the root it compared against",
r["tempRoot"].as_str().unwrap_or("").contains("windows\\temp"),
s(&r)
);
check!(
b,
"...counts the documents that carry the launcher's tail but failed that test",
r["importTempsElsewhere"] == 3,
s(&r)
);
check!(
b,
"...with a sample path an operator can act on",
r["importTempsElsewhereSample"].as_str().unwrap_or("").contains("IC~~"),
s(&r)
);
check!(b, "...and does NOT claim the drain is done", r["done"] == false, s(&r));
let hint = r["_hint"].as_str().unwrap_or("");
check!(
b,
"...and never says every temporary was closed",
!hint.contains("Every open STEP/IGES import temporary was closed"),
hint.to_string()
);
check!(
b,
"...but says what to do about it",
hint.contains("SWX_TEMP_ROOT") && hint.contains("may not be the one SOLIDWORKS uses"),
hint.to_string()
);
}
/// The other direction: when the root DOES match, the report still names it — an
/// operator reading a live run should never have to infer which directory was
/// compared — and a real drain is still reported as done.
#[test]
fn a_matching_root_is_named_in_the_report_too() {
let b = Bridge::start();
b.call("swx_start", json!({}));
let files = TempDir::new("fixtures-match");
b.call("swx_import", json!({"filePath": fixture(&files, "Widget.step")}));
let r = b.call("swx_close_import_temps", json!({"reason": "draining"}));
check!(b, "it drained", r["closed"] == 1 && r["done"] == true, s(&r));
check!(
b,
"...and named the root it required the documents to sit under",
r["tempRoot"].as_str().unwrap_or("").contains("temp"),
s(&r)
);
check!(b, "...with no near misses left behind", r["importTempsElsewhere"] == 0, s(&r));
}
/// LIVE DEFECT, 2026-09-09, Drew's 2144-document session: `swx_docs` still timed
/// out at 30 s and the com host was killed, while `GetDocumentCount` answered in
/// 0.7 s. The degrade path called `GetDocuments` — one call that marshals EVERY
/// handle — and then `GetPathName` on all 2144 of them to fill a field named
/// `importTempsInSample`. The mock's enumeration was instant, so the "degrades
/// instead of timing out" claim in the ecosystem table was mock-proven and false.
///
/// This test holds the live session: 2144 documents, a cap of 200, and a
/// per-document cost of 15 ms (2144 x 15 ms ~ 32 s, so the old code cannot pass).
/// A mock that cannot be slow cannot prove anything about a budget.
#[test]
fn a_flooded_session_is_summarised_in_seconds_not_marshalled_for_thirty() {
let b = Bridge::start_with(&[
("SWX_MOCK_OPEN_DOCS", "2144"),
("SWX_DOC_ENUM_CAP", "200"),
("SWX_MOCK_DOC_LATENCY_MS", "15"),
]);
b.call("swx_start", json!({}));
let t0 = std::time::Instant::now();
let r = b.call("swx_docs", json!({}));
let took = t0.elapsed();
check!(
b,
"swx_docs answers about a 2144-document session in seconds",
took < std::time::Duration::from_secs(3),
format!("took {:.1}s / {}", took.as_secs_f64(), s(&r))
);
check!(b, "...with the count", r["count"] == 2144, s(&r));
check!(b, "...marked truncated", r["truncated"] == true, s(&r));
check!(
b,
"...and a sample, not the list",
r["sample"].as_array().map(|a| a.len()).unwrap_or(0) == 10 && r["docs"] == json!([]),
s(&r)
);
check!(
b,
"the import-temp count is over the SAMPLE and says so",
r["importTempsInSample"] == 10 && r["importTempsInSampleOf"] == 10,
s(&r)
);
// The DRAIN is the verb this session exists for, and it had the same shape:
// it marshalled every handle before it closed anything.
let t1 = std::time::Instant::now();
// `maxSeconds` bounds the VERB (it loops batches until the session is
// drained); `batch` bounds each com call. Both matter on a 2144-document
// session, and the point here is that neither one marshals it.
let d = b.call(
"swx_close_import_temps",
json!({"batch": 50, "maxSeconds": 5, "reason": "draining the flood"}),
);
let drain_took = t1.elapsed();
check!(
b,
"the drain closes a bounded batch without marshalling the session",
d["success"] == true
&& d["closed"].as_u64().unwrap_or(0) >= 50
&& drain_took < std::time::Duration::from_secs(20),
format!("took {:.1}s / {}", drain_took.as_secs_f64(), s(&d))
);
check!(
b,
"...and does not claim the flood is drained when it stopped at its budget",
d["done"] == false,
s(&d)
);
check!(
b,
"...and it scans a bounded window, not everything open",
d["documentsScanned"].as_u64().unwrap_or(u64::MAX) < 2144,
s(&d)
);
// ...and every OTHER verb that reads the session has to survive it too: this
// is the call `ownership_context` makes on nearly every verb.
let t2 = std::time::Instant::now();
let st = b.call("swx_get_app_state", json!({}));
check!(
b,
"app_state degrades on the same session instead of enumerating it",
t2.elapsed() < std::time::Duration::from_secs(3)
&& st["documentsTruncated"] == true
&& st["documents"] == json!([]),
format!("took {:.1}s / {}", t2.elapsed().as_secs_f64(), s(&st))
);
}
/// ADVERSARY PASS 9. The victim was chosen by PATH and closed by TITLE, and
/// `ISldWorks::CloseDoc` resolves by name without prompting — so on Drew's live
/// session (leaked temps titled `First.step-1.SLDPRT` beside his SAVED
/// `…\validation\First.step-1.SLDPRT`) SOLIDWORKS would have picked which one
/// died, and the response would have recorded the temp's path as closed either
/// way. The mock could not show it: its drain removed by a unique internal key.
///
/// The mock now models `CloseDoc`'s by-name resolution, so this is a real test of
/// the close key: a title collision is REFUSED, a unique title still drains, and
/// the user's saved document is still open at the end.
#[test]
fn a_title_collision_is_refused_while_a_unique_temp_in_the_same_batch_still_drains() {
let b = Bridge::start();
b.call("swx_start", json!({}));
let files = TempDir::new("adv9-mine");
// Two imports of the SAME fixture: SOLIDWORKS titles both `Twin.step-1…`-ish,
// which is precisely how a session ends up with colliding titles.
b.call("swx_import", json!({"filePath": fixture(&files, "Twin.step")}));
b.call("swx_import", json!({"filePath": fixture(&files, "Twin.step")}));
// ...and one import whose title nothing else shares.
b.call("swx_import", json!({"filePath": fixture(&files, "Lonely.step")}));
let before = b.call("swx_docs", json!({}));
let titles: Vec<String> = before["docs"]
.as_array()
.cloned()
.unwrap_or_default()
.iter()
.map(|d| d["title"].as_str().unwrap_or("").to_string())
.collect();
let twins = titles.iter().filter(|t| t.contains("Twin")).count();
let r = b.call("swx_close_import_temps", json!({"reason": "draining"}));
if twins > 1 {
// The colliding pair must be reported and NOT closed...
check!(
b,
"a shared title is reported as a collision, with the paths",
r["titleCollisions"].as_array().map(|a| !a.is_empty()).unwrap_or(false)
&& r["titleCollisions"][0]["paths"].as_array().map(|a| a.len() >= 2).unwrap_or(false),
s(&r)
);
check!(b, "...and the drain does not call itself done", r["done"] == false, s(&r));
check!(
b,
"...and the hint tells the user what to do about it",
r["_hint"].as_str().unwrap_or("").contains("same title")
&& r["_hint"].as_str().unwrap_or("").contains("Close it yourself"),
r["_hint"].as_str().unwrap_or("").to_string()
);
}
// ...while the document whose title is unique is gone.
let after = b.call("swx_docs", json!({}));
let after_titles: Vec<String> = after["docs"]
.as_array()
.cloned()
.unwrap_or_default()
.iter()
.map(|d| d["title"].as_str().unwrap_or("").to_string())
.collect();
check!(
b,
"the temporary with a unique title WAS drained",
!after_titles.iter().any(|t| t.contains("Lonely")),
format!("{after_titles:?} / {}", s(&r))
);
check!(
b,
"every close is verified, and none went to the wrong document",
r["closeFailed"].as_array().map(|a| a.is_empty()).unwrap_or(true)
&& r["errorCode"].is_null(),
s(&r)
);
}
/// `swx_close_doc` closes by PATH too, and the mock's faithful `CloseDoc` is what
/// makes that provable: with two open documents sharing a title, closing the one
/// identified by path must leave the other alone.
#[test]
fn closing_one_of_two_same_titled_documents_leaves_the_other_open() {
let b = Bridge::start_with(&[("SWX_ALLOW_USER_WRITES", "1")]);
b.call("swx_start", json!({}));
let files = TempDir::new("adv9-closedoc");
let a = b.call("swx_import", json!({"filePath": fixture(&files, "Pair.step")}));
let bb = b.call("swx_import", json!({"filePath": fixture(&files, "Pair.step")}));
let p1 = a["imported"]["path"].as_str().unwrap_or("").to_string();
let p2 = bb["imported"]["path"].as_str().unwrap_or("").to_string();
check!(
b,
"two open documents, different paths",
!p1.is_empty() && !p2.is_empty() && p1 != p2,
format!("{p1} / {p2}")
);
let closed = b.call("swx_close_doc", json!({"path": p1.clone()}));
check!(b, "the one named by path closed", closed["success"] == true, s(&closed));
let after = b.call("swx_docs", json!({}));
let paths: Vec<String> = after["docs"]
.as_array()
.cloned()
.unwrap_or_default()
.iter()
.map(|d| d["path"].as_str().unwrap_or("").to_string())
.collect();
check!(
b,
"...and the OTHER document, which shares its title, is still open",
paths.iter().any(|p| p == &p2) && !paths.iter().any(|p| p == &p1),
format!("{paths:?}")
);
}
/// ADVERSARY PASS 10, item 4. That `CloseDoc` accepts a FULL PATH is this
/// bridge's premise: the help text says only "name of the document to close", and
/// no rung of this has run live. If the premise is wrong the call returns success
/// and changes nothing — which must be reported as a CAPABILITY failure, must not
/// be confused with closing the wrong document, and must NEVER be retried by
/// title (a title is not an identity, which is the whole reason the close key
/// changed).
#[test]
fn a_close_by_path_that_does_nothing_is_a_capability_failure_not_a_wrong_close() {
let b = Bridge::start();
b.call("swx_start", json!({}));
let files = TempDir::new("adv10-premise");
b.call("swx_import", json!({"filePath": fixture(&files, "Premise.step")}));
let before = b.call("swx_docs", json!({}));
let count_before = before["count"].as_u64().unwrap_or(0);
// The premise fails: CloseDoc(path) returns success and closes nothing.
std::fs::write(b.scratch.join("mock-closedoc-ignores-path"), "").unwrap();
let r = b.call("swx_close_import_temps", json!({"reason": "draining"}));
check!(
b,
"it is reported as a capability problem, by its own error code",
r["success"] == false && r["errorCode"] == "close_by_path_unsupported",
s(&r)
);
check!(
b,
"...NOT as a wrong document closed",
r["errorCode"] != "wrong_document_closed" && r["closeIgnoredPath"] == true,
s(&r)
);
check!(
b,
"...and it refuses to fall back to a title, in as many words",
r["_hint"].as_str().unwrap_or("").contains("will NOT retry by title"),
r["_hint"].as_str().unwrap_or("").to_string()
);
// NOTHING was closed, and the session is exactly as it was.
let after = b.call("swx_docs", json!({}));
check!(
b,
"the session is untouched",
after["count"].as_u64().unwrap_or(0) == count_before,
format!("{} -> {}", count_before, s(&after))
);
// With the premise holding, the same call drains.
std::fs::remove_file(b.scratch.join("mock-closedoc-ignores-path")).unwrap();
let ok = b.call("swx_close_import_temps", json!({"reason": "draining"}));
check!(
b,
"and once the path form works, it drains and names what it closed",
ok["success"] == true
&& ok["closed"].as_u64().unwrap_or(0) >= 1
&& ok["closedDocs"][0]["path"].as_str().unwrap_or("").contains("IC~~"),
s(&ok)
);
check!(
b,
"...with the walk's own verdict published",
ok["walkStop"] == "reachedEnd" && ok["walkComplete"] == true,
s(&ok)
);
check!(
b,
"...and the collision claim scoped to what it scanned",
ok["titleCollisionsCheckedOver"].is_number(),
s(&ok)
);
}
/// ADVERSARY PASS 11, G-2. `tools/drain-gate.py` needs to save a copy of a
/// bridge-created document into a bridge-created scratch directory, and it must
/// be able to do that in the DEFAULT posture — no `SWX_ALLOW_USER_WRITES`.
///
/// That is the choke-point rule working as designed (the document is ours, and
/// the destination does not exist, so nothing of the user's can be lost), and the
/// gate's collision half depends on it. Pinned here so the rule cannot regress
/// into "every save needs the opt-in", which would make the live proof
/// unreachable on a bridge that is not running wide open.
#[test]
fn the_default_posture_lets_the_bridge_save_its_own_document_to_a_new_path() {
let b = Bridge::start();
b.call("swx_start", json!({}));
let files = TempDir::new("default-save");
let r = b.call("swx_import", json!({"filePath": fixture(&files, "Gate.step")}));
let title = r["imported"]["title"].as_str().unwrap_or("").to_string();
let path = r["imported"]["path"].as_str().unwrap_or("").to_string();
let out = files.join("Gate-twin.SLDPRT");
let saved = b.call("swx_save_as", json!({
"path": path, "name": title, "out": out.to_string_lossy(), "copy": true,
"reason": "the drain gate saves a copy of its own document to prove the close key",
}));
check!(
b,
"a BRIDGE-CREATED document saves to a NON-EXISTING path with no write opt-in",
saved["success"] == true,
s(&saved)
);
let opened = b.call("swx_open", json!({
"path": out.to_string_lossy(),
"reason": "the drain gate opens the copy so its title collides with the temporary's",
}));
check!(
b,
"...and the bridge can open the file it just wrote, same posture",
opened["success"] == true,
s(&opened)
);
// ...and the guard is still a guard: a path that ALREADY EXISTS and is not
// ours still needs the opt-in.
let theirs = files.join("TheirFile.SLDPRT");
std::fs::write(&theirs, b"not ours").unwrap();
let over = b.call("swx_save_as", json!({
"path": path, "name": title, "out": theirs.to_string_lossy(), "copy": true,
"reason": "attempting to overwrite a file the bridge did not write",
}));
check!(
b,
"...while overwriting an EXISTING file still refuses in the default posture",
over["success"] == false,
s(&over)
);
}
// ════════════════════════════════════════════════════════════════════════════
// LIVE, 2026-09-11 — A FRESH IMPORT HAS NO PATH, AND TWO OF THEM SHARE A TITLE
// ════════════════════════════════════════════════════════════════════════════
/// `drain-gate.py` imported one fixture twice on Drew's machine and BOTH
/// documents reported `path:""`. A fresh `LoadFile4` import has no path at all —
/// the `IC~~` paths on the 1342 leaked temps came from a different route — and
/// SOLIDWORKS titles both imports from the same file stem. Three open documents,
/// one title, no paths: `swx_save_as`, `swx_reset_workspace` and `swx_close_doc`
/// all refused with `ambiguous_target` (correctly), the drain matched none of
/// them and reported `closed:0, done:true`, and the session could only be cleared
/// by hand.
///
/// A title is not an identity, so the bridge stops creating documents that have
/// nothing else.
#[test]
fn two_imports_of_one_file_are_each_addressable_because_each_gets_a_file() {
let b = Bridge::start();
b.call("swx_start", json!({}));
// The LIVE shape: imports come back pathless.
std::fs::write(b.scratch.join("mock-import-pathless"), "").unwrap();
let files = TempDir::new("identity-live");
let fx = fixture(&files, "DrainGate.step");
let one = b.call("swx_import", json!({"filePath": fx.clone()}));
let two = b.call("swx_import", json!({"filePath": fx.clone()}));
check!(b, "both imports succeeded", one["success"] == true && two["success"] == true,
format!("{} / {}", s(&one), s(&two)));
let p1 = one["documentIdentity"]["path"].as_str().unwrap_or("").to_string();
let p2 = two["documentIdentity"]["path"].as_str().unwrap_or("").to_string();
check!(
b,
"each import was given a file of its own, at birth",
!p1.is_empty() && !p2.is_empty() && p1 != p2,
format!("{p1:?} / {p2:?}")
);
let t1 = one["imported"]["title"].as_str().unwrap_or("").to_string();
let t2 = two["imported"]["title"].as_str().unwrap_or("").to_string();
check!(b, "...and therefore DIFFERENT titles", t1 != t2, format!("{t1} / {t2}"));
// The thing that was impossible live: address ONE of them, by name.
let closed = b.call("swx_close_doc", json!({"path": p1.clone()}));
check!(b, "one of the two can be closed, unambiguously", closed["success"] == true, s(&closed));
let docs = b.call("swx_docs", json!({}));
let paths: Vec<String> = docs["docs"].as_array().cloned().unwrap_or_default()
.iter().map(|d| d["path"].as_str().unwrap_or("").to_string()).collect();
check!(b, "...and the OTHER one is untouched", paths.iter().any(|p| p == &p2), format!("{paths:?}"));
}
/// And the drain has to be able to clear a session full of the OLD kind —
/// pathless, bridge-created imports — because documents made before 0.6.0 (or
/// with keepUnsaved:true) still exist. It owns them by REGISTRY, not by path
/// shape, and it must not claim `done` while any it cannot name is still open.
#[test]
fn the_drain_closes_bridge_owned_pathless_imports_and_names_what_it_cannot() {
let b = Bridge::start();
b.call("swx_start", json!({}));
std::fs::write(b.scratch.join("mock-import-pathless"), "").unwrap();
let files = TempDir::new("identity-old");
// keepUnsaved:true reproduces exactly what the live session held.
let a = b.call("swx_import", json!({"filePath": fixture(&files, "Old.step"), "keepUnsaved": true}));
check!(b, "an opted-out import is pathless", a["imported"]["path"] == "", s(&a));
check!(
b,
"...and SAYS its title is the only name it has",
a["documentIdentity"]["identity"] == "titleOnly",
s(&a)
);
let drain = b.call("swx_close_import_temps", json!({"reason": "clearing the session"}));
check!(
b,
"the drain closes it by OWNERSHIP even though no IC~~ path matches",
drain["ownedUnsavedImportsClosed"].as_u64().unwrap_or(0) == 1,
s(&drain)
);
let docs = b.call("swx_docs", json!({}));
check!(b, "...and it is really gone", docs["count"] == 0, s(&docs));
}
/// BUG C. An import whose com host TIMED OUT after `LoadFile4` had already
/// succeeded left a document open that the bridge did not know it had made — it
/// was still there at the next baseline, owned by nobody. A timeout is "I do not
/// know", and the answer to that is to LOOK.
#[test]
fn an_import_that_timed_out_after_it_worked_is_reconciled_not_abandoned() {
let b = Bridge::start();
b.call("swx_start", json!({}));
let files = TempDir::new("timeout-recon");
let fx = fixture(&files, "Ghost.step");
// The host answers the mutating call with a TIMEOUT while the document it
// created stays open — the live shape.
std::fs::write(b.scratch.join("mock-import-pathless"), "").unwrap();
let before = b.call("swx_docs", json!({}))["count"].as_u64().unwrap_or(0);
std::fs::write(b.scratch.join("mock-import-timeout-after-open"), "").unwrap();
let r = b.call("swx_import", json!({"filePath": fx}));
let _ = std::fs::remove_file(b.scratch.join("mock-import-timeout-after-open"));
check!(b, "the verb reports the failure honestly", r["success"] == false, s(&r));
let docs = b.call("swx_docs", json!({}));
check!(
b,
"precondition: the document really was created despite the timeout",
docs["count"].as_u64().unwrap_or(0) == before + 1,
s(&docs)
);
check!(
b,
"the bridge ADOPTED the document its timed-out call had created",
r["reconciliation"]["reconciled"] == true
&& r["reconciliation"]["adopted"]["title"].is_string(),
s(&r)
);
// ...which is the whole point: it can now clean up after itself.
let swept = b.call("swx_reset_workspace", json!({"reason": "clearing the adopted document"}));
check!(
b,
"...so the sweep can close it",
swept["closed"].as_array().map(|a| !a.is_empty()).unwrap_or(false),
s(&swept)
);
}
/// PASS 13, hardening 3. Adoption is what makes this bridge willing to CLOSE a
/// document, so "one document appeared and it looks right" is not a standard it
/// may use. If the session gained MORE than the one document our call was about,
/// the match is not evidence that ours is the one we can see — and the cheapest
/// wrong answer is claiming one of the user's.
#[test]
fn a_timed_out_import_adopts_nothing_when_the_session_gained_more_than_one_document() {
let b = Bridge::start();
b.call("swx_start", json!({}));
std::fs::write(b.scratch.join("mock-import-pathless"), "").unwrap();
let files = TempDir::new("recon-noisy");
// The user opens something of their own WHILE our import is timing out: the
// session gains two documents, only one of which is ours.
std::fs::write(b.scratch.join("mock-import-timeout-after-open"), "").unwrap();
std::fs::write(b.scratch.join("mock-open-extra-doc"), "").unwrap();
let r = b.call("swx_import", json!({"filePath": fixture(&files, "Noisy.step")}));
let _ = std::fs::remove_file(b.scratch.join("mock-import-timeout-after-open"));
let _ = std::fs::remove_file(b.scratch.join("mock-open-extra-doc"));
check!(b, "the verb still reports the failure", r["success"] == false, s(&r));
check!(
b,
"it adopts NOTHING when the session gained more than our one document",
r["reconciliation"]["reconciled"] == false && r["reconciliation"]["adopted"].is_null(),
s(&r)
);
check!(
b,
"...and says why, in terms of what it could not establish",
r["reconciliation"]["warning"].as_str().unwrap_or("").contains("exactly one"),
s(&r)
);
}
/// PASS 14, item 2. The guard has to hold on BOTH paths that close a pathless
/// document by title — the registry drain and the workspace sweep — because
/// `CloseDoc` resolves a name case-insensitively and `Part1.SLDPRT` /
/// `part1.SLDPRT` are one name to the act.
fn session_with_a_case_collision(scratch: &TempDir, session: i64) {
// PIN THE SESSION ID. Ownership is scoped to the SOLIDWORKS session, so a
// fixture that writes an ownership row has to agree with the session the mock
// will report — otherwise the row is correctly ignored as belonging to some
// other session, and the guard under test is never reached.
std::fs::write(scratch.join("mock-solidworks-session"), session.to_string()).unwrap();
std::fs::write(
scratch.join("mock-solidworks-open-docs.json"),
json!({
"running": true, "untitled": 2, "active": "untitled:ours",
"docs": [
// OURS, pathless: closable only by title.
{"docType": "part", "path": "untitled:ours", "title": "Part1.SLDPRT",
"saved": false, "importTemp": false},
// THE USER'S, differing only in case, and saved — unrecoverable.
{"docType": "part", "path": "C:\\Users\\drew\\work\\part1.SLDPRT",
"title": "part1.SLDPRT", "saved": true, "importTemp": false}
],
"files": {}
})
.to_string(),
)
.unwrap();
std::fs::write(
scratch.join("owned-docs.json"),
json!({"documents": [{"title": "Part1.SLDPRT", "session": session, "path": null}]})
.to_string(),
)
.unwrap();
}
#[test]
fn the_registry_drain_refuses_a_title_that_differs_only_in_case() {
let scratch = TempDir::new("case-drain");
session_with_a_case_collision(&scratch, 700837);
let b = Bridge::start_in(TempDir::borrowed(&scratch.0), &[]);
b.call("swx_start", json!({"reason": "draining"}));
let r = b.call("swx_close_import_temps", json!({"reason": "draining"}));
let after = b.call("swx_docs", json!({}));
let alive: Vec<String> = after["docs"].as_array().cloned().unwrap_or_default()
.iter().map(|d| d["path"].as_str().unwrap_or("").to_string()).collect();
check!(
b,
"the user's saved part1.SLDPRT is untouched",
alive.iter().any(|p| p == "C:\\Users\\drew\\work\\part1.SLDPRT"),
format!("{alive:?} / {}", s(&r))
);
let titles: Vec<String> = after["docs"].as_array().cloned().unwrap_or_default()
.iter().map(|d| d["title"].as_str().unwrap_or("").to_string()).collect();
check!(
b,
"...and OUR Part1.SLDPRT was not closed either — neither can be named",
titles.iter().any(|t| t == "Part1.SLDPRT"),
format!("{titles:?}")
);
check!(
b,
"...the refusal is reported, not silent",
r["ownedUnsavedImportsUnaddressable"].as_array().map(|a| !a.is_empty()).unwrap_or(false)
&& r["done"] == false,
s(&r)
);
}
/// The SAME hazard in the other verb that closes a pathless document BY TITLE.
///
/// Not `swx_close_all_documents`: that is one `CloseAllDocuments` call, closing
/// everything by explicit authorization, and it names nothing. `swx_reset_workspace`
/// is the one that picks documents out of the session by name, which is where a
/// name that matches two documents can do harm.
#[test]
fn the_workspace_sweep_refuses_a_title_that_differs_only_in_case() {
let scratch = TempDir::new("case-sweep");
session_with_a_case_collision(&scratch, 700837);
let b = Bridge::start_in(TempDir::borrowed(&scratch.0), &[]);
b.call("swx_start", json!({"reason": "sweeping"}));
let r = b.call("swx_reset_workspace", json!({"reason": "clearing our scratch documents"}));
let after = b.call("swx_docs", json!({}));
let alive: Vec<String> = after["docs"].as_array().cloned().unwrap_or_default()
.iter().map(|d| d["path"].as_str().unwrap_or("").to_string()).collect();
check!(
b,
"the user's saved part1.SLDPRT survived a sweep aimed at OUR Part1.SLDPRT",
alive.iter().any(|p| p == "C:\\Users\\drew\\work\\part1.SLDPRT"),
format!("{alive:?} / {}", s(&r))
);
check!(
b,
"...and the sweep SAYS it could not name the document it meant",
s(&r).contains("answers to the same title"),
s(&r)
);
}