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
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
//! ADVERSARIAL REVIEW OF 0.5.1 — pass six.
//!
//! F1 is properly fixed in SHAPE: `is_import_temp` is now a segment walk, and
//! every path my round-five tests closed on stays closed. F2 is closed in both
//! places (an unreadable count is UNKNOWN-and-truncated, never a full marshal).
//! F3's caveat is honest. `liveness_verdict` gets the hard part right — no
//! `dead` from one sample, a short span, or unknown responsiveness, and
//! `verdict_advice("dead")` puts the decision with the user.
//!
//! What is left is two things the segment walk still cannot see, and a family of
//! verbs that read an UNLISTABLE session as an EMPTY one.
//!
//! Every #[test] here FAILS on e723504 except the two positive controls, which
//! are here so a fix cannot over-tighten.
mod common;
use common::{Bridge, TempDir};
use serde_json::{json, Value};
use swx_bridge::comhost::host::is_import_temp;
use swx_bridge::launch::liveness_verdict;
fn s(v: &Value) -> String {
serde_json::to_string_pretty(v).unwrap_or_default()
}
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()
}
// ════════════════════════════════════════════════════════════════════════════
// 1. DATA LOSS: the walk proves a LAYOUT, not a LOCATION
// ════════════════════════════════════════════════════════════════════════════
/// The walk asks for a `temp` segment, then `swx<digits>`, then `IC~~`, then a
/// file. It never asks WHERE that chain is. `%TEMP%` is a specific directory;
/// `D:\Temp\` is a folder a user made.
///
/// The realistic route in: the 1342 leaked temps are real files still sitting on
/// disk. Recovering one — copying `…\Temp\swx107688\IC~~\First.step.SLDPRT`
/// somewhere safe that keeps the folder layout, or just opening it where it lies
/// — makes it the user's document. `cmd_close_import_temps` then closes it with
/// `CloseDoc`, which does not prompt, in the DEFAULT posture, with no write
/// opt-in and no host veto (the drain is deliberately exempt).
#[test]
fn a_temp_shaped_chain_on_another_volume_is_not_the_launchers_temp() {
let users_own = r"D:\Temp\swx107688\IC~~\First.step.SLDPRT";
assert!(
!is_import_temp(users_own),
"'{users_own}' is not under %TEMP% — it is a folder the user made (or a copy they took of \
a leaked temp they wanted to keep), and it qualified for a no-prompt CloseDoc. The proof \
has to be anchored to the resolved %TEMP% root, not just to the segment shape."
);
}
/// The live evidence is unambiguous about the layout:
/// `C:\Users\drew\AppData\Local\Temp\swx107688\IC~~\First.step.SLDPRT` — `IC~~`
/// sits DIRECTLY under `swx<pid>`. The walk accepts it at any depth below, which
/// widens the surface for no benefit.
///
/// The asymmetry says which way to err: a MISS leaves a temp undrained (memory,
/// recoverable by a restart), a FALSE MATCH closes a document without a prompt
/// (data loss). Tighten to the observed layout.
#[test]
fn ic_must_sit_directly_under_the_swx_pid_folder() {
let deep = r"C:\Users\drew\AppData\Local\Temp\swx107688\projects\2026\IC~~\customer.sldprt";
assert!(
!is_import_temp(deep),
"'{deep}' matched although IC~~ is three levels below swx107688. The launcher writes \
swx<pid>\\IC~~\\ directly; accepting any depth below buys nothing and widens a \
no-prompt-close rule."
);
}
/// The cases that MUST keep matching — the real layout, forward slashes, the 8.3
/// short form, mixed case. These pass today.
#[test]
fn the_real_launcher_layout_still_matches() {
for p in [
r"C:\Users\drew\AppData\Local\Temp\swx107688\IC~~\First.step.SLDPRT",
"C:/Users/drew/AppData/Local/Temp/swx107688/IC~~/First.step.SLDPRT",
r"C:\USERS\DREW\APPDATA\LOCAL\TEMP\SWX107~1\IC~~\FIRST.STEP.SLDPRT",
] {
assert!(is_import_temp(p), "the launcher's own layout must still be recognised: {p}");
}
}
/// ...and the round-five closures must stay closed.
#[test]
fn the_round_five_data_loss_paths_stay_closed() {
for p in [
r"C:\work\IC~~\drafts\Temp\swx_export\customer-master.sldprt",
r"D:\Temp\swx-archive\2026\IC~~\customer-final.sldprt",
r"C:\Users\drew\AppData\Local\Temp\swxnotes\IC~~\keep.sldprt",
// IC~~ as the FILE, not a folder.
r"C:\Users\drew\AppData\Local\Temp\swx107688\IC~~",
] {
assert!(!is_import_temp(p), "round five closed this and it must stay closed: {p}");
}
}
// ════════════════════════════════════════════════════════════════════════════
// 2. AN UNLISTABLE SESSION READ AS AN EMPTY ONE
// ════════════════════════════════════════════════════════════════════════════
/// `resolve_document` refuses honestly with `document_list_unavailable` when the
/// open set is truncated — that fix is real. But two verbs take the same
/// truncated (therefore EMPTY) `open` list and iterate it instead of refusing:
/// `reset_workspace` and `close_all_documents`. They correctly skip the
/// ownership prune, then find nothing to close, and report `success:true` with
/// `closed:[]`.
///
/// A caller that asked the bridge to clean up its own documents is told the
/// workspace is clean. On the session this release exists for — 1342 open docs —
/// that is the most misleading possible answer.
#[test]
fn reset_workspace_refuses_when_it_cannot_see_the_session() {
let b = Bridge::start_with(&[("SWX_DOC_ENUM_CAP", "3")]);
b.call("swx_start", json!({"reason": "modelling"}));
let steps = TempDir::new("adv6-steps");
// Five bridge-created documents, over the cap of three.
for i in 0..5 {
let f = fixture(&steps, &format!("Part{i}.step"));
b.call("swx_import", json!({"filePath": f}));
}
let docs = b.call("swx_docs", json!({}));
assert!(
docs["truncated"] == true,
"precondition: the open set is over the cap and unlistable: {}",
s(&docs)
);
let r = b.call("swx_reset_workspace", json!({"reason": "cleaning up after the build"}));
let closed_none = r["closed"].as_array().map(|a| a.is_empty()).unwrap_or(true);
assert!(
!(r["success"] == true && closed_none),
"swx_reset_workspace reported success having closed NOTHING, on a session holding five \
documents this bridge created — because the open list was truncated, so the loop it \
iterates was empty. The honest answer is document_list_unavailable, the same refusal \
resolve_document makes.\n{}",
s(&r)
);
}
/// POSITIVE CONTROL, and it passes: `close_all_documents` does NOT depend on the
/// truncated list to act — it delegates to the host's single `CloseAllDocuments`
/// COM call and reports the documents the host says it closed. (An earlier
/// version of this test passed for the wrong reason, refused by the write
/// posture before it ever reached the loop; the opt-in below fixes that.)
#[test]
fn close_all_documents_refuses_when_it_cannot_see_the_session() {
// The write opt-in is ON, so the verb reaches the loop under review rather
// than being refused by the posture — an earlier version of this test passed
// because the posture refused it first, which proves nothing about the hole.
let b = Bridge::start_with(&[
("SWX_DOC_ENUM_CAP", "3"),
("SWX_ALLOW_USER_WRITES", "1"),
]);
b.call("swx_start", json!({"reason": "modelling"}));
let steps = TempDir::new("adv6-steps2");
for i in 0..5 {
let f = fixture(&steps, &format!("Part{i}.step"));
b.call("swx_import", json!({"filePath": f}));
}
assert!(
b.call("swx_docs", json!({}))["truncated"] == true,
"precondition: unlistable"
);
let r = b.call(
"swx_close_all_documents",
json!({"reason": "clearing the session", "confirm": true, "allowUserWrites": true}),
);
assert!(
r["errorCode"] != "user_writes_not_enabled",
"precondition: the write posture must not refuse this before the loop is reached: {}",
s(&r)
);
let says_ok = r["success"] == true;
let closed_none = r["closed"].as_array().map(|a| a.is_empty()).unwrap_or(true);
assert!(
!(says_ok && closed_none),
"swx_close_all_documents reported success having closed nothing on an unlistable session. \
A destructive verb must refuse when it cannot see what it would act on, not report a \
clean no-op.\n{}",
s(&r)
);
}
// ════════════════════════════════════════════════════════════════════════════
// 4. BUSY IS AN ABSOLUTE, NOT A RATE
// ════════════════════════════════════════════════════════════════════════════
/// `liveness_verdict` gets the asymmetry right — `dead` needs two readings, a
/// span of at least `DEAD_SAMPLE_SECS`, and no new window, and unknown
/// responsiveness is never a verdict. But `moving` is an ABSOLUTE threshold:
///
/// ```text
/// cpu_active_seconds > 0.25 || memory_delta_mb.abs() >= 8
/// ```
///
/// with no reference to the span. Over a ten-minute sample, 0.3 seconds of CPU
/// is 0.05% of one core — a housekeeping timer, not a rebuild — and an 8 MB
/// wobble is noise. Both read as `busy`, whose advice is "TELL THE USER TO WAIT
/// … it usually finishes … Do NOT advise ending the task". A wedged session with
/// one spinning background thread is therefore reported as "wait" for ever, and
/// there is no elapsed-time input that could ever escalate it.
#[test]
fn a_trickle_of_cpu_over_a_long_sample_is_not_evidence_of_work() {
// 0.3s of CPU across 600s: 0.05% of a core.
let v = liveness_verdict(Some(false), Some(0.3), Some(600.0), Some(0), Some(0));
assert_ne!(
v, "busy",
"0.3s of CPU over a 600s sample is 0.05% of one core — noise, not a rebuild — and the \
verdict was '{v}', whose advice tells the caller to wait and not to end the task. The \
threshold is absolute; it has to be a RATE against the sample span."
);
// And an 8 MB drift over ten minutes is not progress either.
let m = liveness_verdict(Some(false), Some(0.0), Some(600.0), Some(8), Some(0));
assert_ne!(
m, "busy",
"an 8 MB memory change across 600s was read as work in progress ('{m}')."
);
}
/// The properties that MUST hold, pinned so a rate fix cannot break them.
#[test]
fn the_liveness_asymmetry_holds() {
// A real rebuild: 21s of CPU in 30s.
assert_eq!(liveness_verdict(Some(false), Some(21.0), Some(30.0), Some(40), Some(0)), "busy");
// Unknown responsiveness is never a verdict.
assert_eq!(liveness_verdict(None, Some(0.0), Some(60.0), Some(0), Some(0)), "unknown");
// One reading / too short a span is never dead.
assert_eq!(liveness_verdict(Some(false), None, None, None, None), "unknown");
assert_eq!(liveness_verdict(Some(false), Some(0.0), Some(3.0), Some(0), Some(0)), "unknown");
// A dialog on screen means something is being asked, not that nothing runs.
assert_eq!(liveness_verdict(Some(false), Some(0.0), Some(60.0), Some(0), Some(1)), "unknown");
// No work over a long sample, nothing on screen: dead.
assert_eq!(liveness_verdict(Some(false), Some(0.0), Some(60.0), Some(0), Some(0)), "dead");
}
// ════════════════════════════════════════════════════════════════════════════
// 3. ONE IMPORT, TWO OWNERSHIP ROWS
// ════════════════════════════════════════════════════════════════════════════
/// F3's fix added `remember_scratch_at` so an import temp is remembered BY PATH
/// (ownership is decided by path whenever a document has one). But the
/// unconditional `remember_scratch(title, session)` above it was kept, and the
/// two dedupe on different keys — the first on `(title, session)` with
/// `path:None`, the second on `(path, session)`. Neither sees the other, so
/// every import writes TWO rows for ONE document.
///
/// The phantom `path:None` row claims the TITLE with no path, which is a wider
/// ownership claim than what was created; `trackedScratchDocuments` reports
/// double; and `owned-docs.json` — rewritten atomically on every
/// remember/forget/rename/prune — carries twice the rows it should, which on the
/// session this release exists for (1342 temps) is 2684.
#[test]
fn imports_record_one_owned_row_each_even_when_unlistable() {
// WHY THE CAP MATTERS HERE: with a listable session the prune that runs at
// the top of reset_workspace drops the phantom row (its path:None cannot
// match an open document that reports a path), so the duplication is
// invisible. On a TRUNCATED session the prune is correctly skipped — and the
// phantoms then accumulate unbounded, which is precisely the 1342-temp state
// this release exists for.
let b = Bridge::start_with(&[("SWX_DOC_ENUM_CAP", "3")]);
b.call("swx_start", json!({"reason": "importing"}));
let steps = TempDir::new("adv6-dup");
for i in 0..5 {
let f = fixture(&steps, &format!("Widget{i}.step"));
b.call("swx_import", json!({"filePath": f}));
}
assert!(
b.call("swx_docs", json!({}))["truncated"] == true,
"precondition: the session is unlistable, so the prune is skipped"
);
let r = b.call("swx_reset_workspace", json!({"reason": "checking what is tracked"}));
let tracked = r["trackedScratchDocuments"].as_array().cloned().unwrap_or_default();
assert_eq!(
tracked.len(),
5,
"five imports produced {} ownership rows. remember_scratch (title, path:None) and \
remember_scratch_at (title, path) both fire and dedupe on different keys, so each import \
writes TWO rows; on a truncated session nothing prunes the phantom away. owned-docs.json \
is rewritten atomically on every remember/forget/prune, so at the 1342 temps this \
release was written for that is 2684 rows per write — and the title-only row claims \
ownership of the title with no path at all.\ntracked: {:?}",
tracked.len(),
tracked
);
}
/// And the reason the sweep gives for skipping a temp is wrong. `reset_workspace`
/// computes `saved = path.is_some()`, but an import temp HAS a path
/// (`…\IC~~\…`) and has never been saved anywhere the user could find it. The
/// verb therefore skips its own import temps with "has been saved to disk; pass
/// force:true" — a statement that is not true of a conversion temporary, and one
/// that invites a caller to pass `force:true` on a session where the honest
/// answer is "use swx_close_import_temps".
#[test]
fn an_import_temp_is_not_described_as_saved_to_disk() {
let b = Bridge::start();
b.call("swx_start", json!({"reason": "importing"}));
let steps = TempDir::new("adv6-skipreason");
let f = fixture(&steps, "Bracket.step");
b.call("swx_import", json!({"filePath": f}));
let r = b.call("swx_reset_workspace", json!({"reason": "sweeping"}));
let why: Vec<String> = r["skipped"]
.as_array()
.cloned()
.unwrap_or_default()
.iter()
.filter_map(|x| x.get("why").and_then(|w| w.as_str()).map(String::from))
.collect();
assert!(
!why.iter().any(|w| w.contains("saved to disk")),
"the sweep skipped an import temporary saying it 'has been saved to disk'. It has a temp \
PATH, which is not the same as being saved: nothing the user could open is on disk at a \
location they chose. `saved = path.is_some()` conflates the two.\nskipped: {:?}\n{}",
why,
s(&r)
);
}
/// `new_windows.unwrap_or(0) > 0` — a window census that FAILED becomes "no
/// dialog on screen", which is one of the three things `dead` requires. So an
/// unreadable desktop contributes positively to the gravest verdict the bridge
/// can reach, on a machine where the census failing and SOLIDWORKS being wedged
/// are correlated. Every other unknown in this function returns "unknown"; this
/// one is silently a zero.
#[test]
fn an_unreadable_window_census_cannot_support_a_dead_verdict() {
let v = liveness_verdict(Some(false), Some(0.0), Some(60.0), Some(0), None);
assert_ne!(
v, "dead",
"the window census could not be taken (new_windows:None) and the verdict was still \
'{v}'. `unwrap_or(0)` turns 'I could not look for a dialog' into 'there is no dialog', \
which is a precondition of `dead` — the one verdict whose advice sends the user to end \
their CAD session."
);
}