← Commit history
BRIDGE_VERSION+1−1
@@ -1 +1 @@-0.11.1+0.11.2
Cargo.lock+1−1
@@ -122,7 +122,7 @@ dependencies = [  [[package]] name = "swx-bridge"-version = "0.11.1"+version = "0.11.2" dependencies = [  "miniz_oxide",  "serde",
Cargo.toml+1−1
@@ -1,6 +1,6 @@ [package] name = "swx-bridge"-version = "0.11.1"+version = "0.11.2" edition = "2021" rust-version = "1.80" description = "Adom SOLIDWORKS bridge — swx_* verbs over the SOLIDWORKS COM API, as a single native binary"
README.md+22−1
@@ -506,7 +506,28 @@ not.  ## Roadmap -- v0.11.1 (this): **which verb put that dialog up?** Measured 2026-10-15 on+- v0.11.2 (this): **the File ▸ Open was the launch's own.** The laptop's+  ledger had the sequence:+  - the 3DEXPERIENCE helper sign-in took 2 min 13 s;+  - the supervisor called that a stall, killed the chain and invoked the shortcut+    again;+  - SOLIDWORKS came up 6 s later, from the FIRST invoke;+  - the second invoke reached it as an "open" request, and that raised the+    dialog.++  Now:+  - **`helperSignIn` pauses the stall clocks.** Only a dead chain is a stall, and+    past 600 s the launch STOPS rather than retries.+  - **A retry never re-invokes while any `sldworks.exe` from this launch+    exists.** It reads the census before cleaning up and watches for 15 s before+    any retry. It ledgers `launch_retry_declined` and waits to attach instead.+  - **Each route invocation is a verb for the dialog census**+    (`launch.invoke`, `launch.retry`), with a 90 s grace after it ends. An empty+    File ▸ Open a route provably raised is cancelled by the launch itself and by+    `swx_start`, and ledgered.++  See docs/LAUNCH-RESILIENCE.md. 684 tests.+- v0.11.1: **which verb put that dialog up?** Measured 2026-10-15 on   adom_drew2:   - a File ▸ Open dialog appeared during a bridge battery, with Drew away;   - it was `"Open "`, `#32770`, owned by the SOLIDWORKS main frame, with the
SKILL.md+4−1
@@ -96,7 +96,10 @@ BEFORE you do it:    and `attribution` (the bridge verb that raised it, if one provably did). The    bridge cancels only a standard file dialog with an EMPTY name that it raised    itself (`swx_dismiss_blocking_dialogs {close, confirm, reason}`); every other-   dialog is the user's to answer.+   dialog is the user's to answer. A launch retry never re-invokes over a+   running SOLIDWORKS, and the 3DEXPERIENCE helper sign-in is never treated as a+   stall (a second invoke at a starting SOLIDWORKS raised a File ▸ Open,+   measured 2026-10-15). 9. **Never end a session the user is working in, and never pile windows on    them.** Every quit and sweep refuses `user_is_active` when SOLIDWORKS is in    front with recent input, or documents appeared that the bridge did not open;
bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "solidworks",   "displayName": "SOLIDWORKS",-  "version": "0.11.1",+  "version": "0.11.2",   "description": "Bridge for SOLIDWORKS desktop CAD — create and open documents, a modeling DSL, file management (find/open/save/export/Pack and Go), custom properties, BOM generation, design analysis (mass properties, bodies, parameters, interference, rebuild health) and background window/dialog etiquette, over the SOLIDWORKS COM API. Single native binary: no interpreter, no PowerShell, ~10ms cold start.",   "author": "Adom Inc.",   "license": "MIT",
docs/LAUNCH-RESILIENCE.md+53−3
@@ -143,9 +143,59 @@ The route that reaches `attached` is written to `launch-state.json` as `preferredRoute` and tried FIRST next time, so a machine pays the fallback cost once. A remembered route that is not a known route is ignored, never run. -**Budgets** (env overrides exist only so the suite can compress them):-`SWX_LAUNCH_RUN_BEGIN_SECS` 20 · `SWX_LAUNCH_SLDWORKS_SECS` 120 ·-`SWX_LAUNCH_ATTACH_SECS` 240 · `SWX_LAUNCH_INFLIGHT_SECS` 240.+**Budgets, per stage** (env overrides exist only so the suite can compress them):++| Stage | What counts as a stall | Budget |+|---|---|---|+| `invoked` / `launcherStarted` | no `Run Begin` | `SWX_LAUNCH_RUN_BEGIN_SECS` 20 |+| `runBegin` (no helper log yet) | no `sldworks.exe` | `SWX_LAUNCH_SLDWORKS_SECS` 120 |+| `helperSignIn` (0.11.2) | **the clocks are PAUSED.** Only a DEAD chain (every process of it gone, no `sldworks.exe`) is a stall. Past `SWX_LAUNCH_HELPER_SECS` (600) the launch STOPS: `helperSignInTooLong`, nothing killed, nothing re-invoked | 600, then stop |+| 3DEXPERIENCE ID sign-in window up | paused (0.6.x) | none |+| `sldworksUp` / `attaching` | COM does not attach → stop, never a stall | `SWX_LAUNCH_ATTACH_SECS` 240 |++`SWX_LAUNCH_INFLIGHT_SECS` 240 · `SWX_LAUNCH_RETRY_SETTLE_MS` 15000 ·+`SWX_LAUNCH_DIALOG_GRACE_MS` 90000.++### A retry never re-invokes over a running SOLIDWORKS (0.11.2)++THE INCIDENT, from the laptop's ledger (2026-10-15, attempt 24, route+`platformShortcut`):++| Time (Z) | Event |+|---|---|+| 14:48:58 | `solidworks_launch_started` |+| 14:49:02 | `launcherStarted` |+| 14:49:04 | `runBegin`, `helperSignIn` |+| **14:51:11** | stage `stalled` + `solidworks_launch_stalled {cleanup:"cleaned", killed:[CATSTART.exe 39612, …]}` |+| 14:51:11 | stage `invoked`: the shortcut invoked AGAIN |+| 14:51:14 / :16 | `launcherStarted`, `runBegin` |+| **14:51:17** | `sldworksUp`, 6 s after the re-invoke: the FIRST invoke's SOLIDWORKS, already coming up |+| 14:51:53 | attached, pid 82476, attributed by ancestry |+| 15:00:15 | first sighting of the dialog: `stop_blocked_by_dialog` "Open " |++The "stall" was false: the 3DEXPERIENCE helper sign-in simply took more than two+minutes. The second invoke reached the already-starting Connected session as an+"open" request, and SOLIDWORKS raised File ▸ Open, which blocked every quit+after it.++So:++- **`helperSignIn` is not a stall.** The clocks pause from the helper's log until+  `sldworks.exe` appears (see the table above).+- **No re-invoke while this launch has a SOLIDWORKS.** Any `sldworks.exe` outside+  the FIRST route's baseline belongs to this launch. When a route stalls, the+  census is read before anything is cleaned up. Before any retry, the supervisor+  watches for `SWX_LAUNCH_RETRY_SETTLE_MS`. If such a SOLIDWORKS is there:+  - nothing is killed and nothing is invoked;+  - `launch_retry_declined {why, pid, createdAt}` is ledgered;+  - the supervisor waits to attach to that SOLIDWORKS.+- **Each route invocation is a "verb" for the dialog census**+  (`launch.invoke`, `launch.retry`), in flight until `SWX_LAUNCH_DIALOG_GRACE_MS`+  after it ends. A File ▸ Open with an EMPTY name that a route provably raised+  (proved absent after it began, no user input since) is cancelled by the+  supervisor itself, and by `swx_start`. It is ledgered as `dialog_cancelled`+  with `by:"the launch supervisor"`. A launch never leaves the session blocked+  by its own dialog.  ### When SOLIDWORKS is up but COM will not attach 
install.sh

  
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "solidworks-bridge",-  "version": "0.11.1",+  "version": "0.11.2",   "type": "app",   "description": "Skills for your container so your AI knows how to drive the SOLIDWORKS bridge (swx_* verbs). The bridge runtime itself is the release zip — a single native swx-bridge.exe; Adom Bridge loads that.",   "tags": [
src/dialogs.rs+61−1
@@ -142,7 +142,9 @@ pub fn bridge_caused(s: &Value, last_input_now_ms: Option<u64>) -> Option<String     let absent = s.get("absentAtMs").and_then(|v| v.as_u64())?;     let input_then = s.get("lastInputMs").and_then(|v| v.as_u64())?;     let input_now = last_input_now_ms?;-    s.get("appearedDuring")?.as_array()?.iter().find_map(|d| {+    // The most recent verb first: a retry in flight beside an earlier route's+    // grace window is the one that raised it.+    s.get("appearedDuring")?.as_array()?.iter().rev().find_map(|d| {         let started = d.get("startedMs").and_then(|v| v.as_u64())?;         let verb = d.get("verb").and_then(|v| v.as_str())?;         (started <= absent && input_then < started && input_now < started).then(|| verb.to_string())@@ -199,6 +201,64 @@ pub fn end(id: u64) -> Vec<Value> {     fresh } +/// Cancel every dialog that is up, provably raised by the LAUNCH supervisor+/// (`launch.invoke` / `launch.retry`), and is the standard file dialog with an+/// empty name — what a re-invoke at a starting SOLIDWORKS raises (measured+/// 2026-10-15). Ledgered; a launch never leaves the session blocked by its own+/// Open dialog. Everything else is left alone.+pub fn cancel_launch_file_dialogs(os: &dyn crate::launchos::LaunchOs) -> Vec<Value> {+    let mut out = Vec::new();+    let Some(up) = observe(os) else { return out };+    for s in up {+        let Some(h) = s.get("hwnd").and_then(|h| h.as_i64()) else { continue };+        if let Ok((verb, sighting)) = may_cancel(h, os) {+            if !verb.starts_with("launch.") {+                continue;+            }+            let r = os.cancel_dialog(h);+            crate::ledger::record(+                "dialog_cancelled",+                json!({"hwnd": h, "title": sighting["title"], "raisedDuring": verb, "by": "the launch supervisor",+                       "sighting": sighting, "ok": r.is_ok(), "error": r.as_ref().err()}),+            );+            out.push(json!({"hwnd": h, "title": sighting["title"], "raisedDuring": verb, "ok": r.is_ok()}));+        }+    }+    out+}++/// A launch route, bracketed like a verb: in flight from its invoke until+/// `grace` after it ends, watched (and its own empty File ▸ Open cancelled)+/// all that time on a thread of its own.+pub struct LaunchBracket {+    id: u64,+    grace: std::time::Duration,+}++impl LaunchBracket {+    pub fn begin(label: &str, grace: std::time::Duration) -> LaunchBracket {+        LaunchBracket { id: begin(label), grace }+    }+}++impl Drop for LaunchBracket {+    fn drop(&mut self) {+        let (id, grace) = (self.id, self.grace);+        std::thread::spawn(move || {+            let os = crate::launchos::os();+            let until = std::time::Instant::now() + grace;+            loop {+                let _ = cancel_launch_file_dialogs(os.as_ref());+                if std::time::Instant::now() >= until {+                    break;+                }+                std::thread::sleep(std::time::Duration::from_millis(500));+            }+            end(id);+        });+    }+}+ /// Does `dispatch` bracket this verb? Every known verb except the ones that /// only read the bridge's own state (they never touch SOLIDWORKS). pub fn brackets(verb: &str) -> bool {
src/launch.rs+135−1
@@ -595,6 +595,33 @@ pub fn inflight_budget() -> Duration { pub fn backoff_budget() -> Duration {     Duration::from_secs(secs_env("SWX_LAUNCH_BACKOFF_SECS", 60)) }+/// THE 3DEXPERIENCE HELPER SIGN-IN may take this long before the launch gives+/// up on it (0.11.2) — and giving up STOPS, it never re-invokes. MEASURED+/// 2026-10-15: helperSignIn took 2 min 13 s on adom_drew2, the old clock+/// called that a stall, killed the chain and invoked the shortcut again — and+/// the re-invoke reached the already-starting SOLIDWORKS as an "open" request:+/// a File ▸ Open dialog that blocked every quit after it.+pub fn helper_budget() -> Duration {+    Duration::from_secs(secs_env("SWX_LAUNCH_HELPER_SECS", 600))+}+/// Before re-invoking after a stall, watch this long for a SOLIDWORKS the+/// stalled route started anyway (measured: it came up 6 s after the cleanup).+pub fn retry_settle() -> Duration {+    Duration::from_millis(secs_env("SWX_LAUNCH_RETRY_SETTLE_MS", 15_000))+}+/// How long after a route a dialog that appears is still attributed to it+/// (and, when it is a file dialog with an empty name, cancelled by it).+pub fn dialog_grace() -> Duration {+    Duration::from_millis(secs_env("SWX_LAUNCH_DIALOG_GRACE_MS", 90_000))+}++/// A SOLIDWORKS that this launch did not find running before it began: any+/// sldworks.exe outside the FIRST route's baseline. While one exists, no route+/// is invoked again — a second invoke reaches it as an "open" request.+pub fn new_solidworks(census: &[ProcInfo], first_baseline: &[u32]) -> Option<ProcInfo> {+    census.iter().find(|p| p.is("SLDWORKS.exe") && !first_baseline.contains(&p.pid)).cloned()+}+ fn poll_interval() -> Duration {     Duration::from_millis(secs_env("SWX_LAUNCH_POLL_MS", 1000).max(50)) }@@ -1599,7 +1626,36 @@ pub fn mark_attached() { fn run_supervisor(channel: Channel, os: Box<dyn LaunchOs>) {     let plan = attempt().map(|a| a.plan).unwrap_or_default();     let shortcut = attempt().map(|a| a.shortcut).unwrap_or_default();+    // WHAT RAN BEFORE THIS LAUNCH BEGAN — the FIRST route's baseline. Any+    // sldworks.exe outside it is this launch's, whichever route started it.+    let first_baseline: Vec<u32> = os.processes().map(|c| c.iter().map(|p| p.pid).collect()).unwrap_or_default();     for (index, route) in plan.iter().enumerate() {+        // NEVER RE-INVOKE OVER A RUNNING SOLIDWORKS (0.11.2). Before a retry,+        // watch a moment for a SOLIDWORKS the stalled route started anyway; if+        // one is there, wait for it instead.+        if index > 0 {+            let until = std::time::Instant::now() + retry_settle();+            loop {+                if let Some(sw) = os.processes().ok().and_then(|c| new_solidworks(&c, &first_baseline)) {+                    crate::ledger::record(+                        "launch_retry_declined",+                        json!({"route": route, "why": format!("a SOLIDWORKS from this launch is already running (pid {}, createdAt {:?})", sw.pid, sw.created_at),+                               "pid": sw.pid, "createdAt": sw.created_at}),+                    );+                    wait_for_new_solidworks(&channel, sw.pid);+                    return;+                }+                if std::time::Instant::now() >= until {+                    break;+                }+                std::thread::sleep(poll_interval());+            }+        }+        // EVERY INVOKE IS A "VERB" FOR THE DIALOG CENSUS (0.11.2): a dialog+        // that appears while this route runs — or within the grace after it —+        // is attributed to it, and a File ▸ Open with an empty name that it+        // raised is cancelled by it.+        let _dialogs = crate::dialogs::LaunchBracket::begin(if index == 0 { "launch.invoke" } else { "launch.retry" }, dialog_grace());         // EVERY PER-ROUTE FACT RESETS. `root_confirmed` and `unattributed_log`         // did not, with two consequences: `notConfirmed` became unreachable         // after route 1 (so H2 was restored for routes 2-4), and the log gate's@@ -1759,6 +1815,17 @@ fn run_supervisor(channel: Channel, os: Box<dyn LaunchOs>) {                 return;             }             RouteOutcome::Stalled { note, saw_log, unattributed_log } => {+                // A SOLIDWORKS THIS LAUNCH STARTED IS ALREADY UP: the "stall" is+                // over. Its chain is not killed and nothing is invoked again.+                if let Some(sw) = os.processes().ok().and_then(|c| new_solidworks(&c, &first_baseline)) {+                    crate::ledger::record(+                        "launch_retry_declined",+                        json!({"route": route, "note": note, "why": format!("a SOLIDWORKS from this launch is already running (pid {}, createdAt {:?})", sw.pid, sw.created_at),+                               "pid": sw.pid, "createdAt": sw.created_at}),+                    );+                    wait_for_new_solidworks(&channel, sw.pid);+                    return;+                }                 // THE FOUR WAYS A ROUTE CAN END, and they are not the same.                 //   * attributed AND confirmed: the invoke told us its root and                 //     we saw it, so the chain is ours by proof and we end it;@@ -1850,6 +1917,30 @@ fn run_supervisor(channel: Channel, os: Box<dyn LaunchOs>) {     ); } +/// A SOLIDWORKS from this launch is up: wait for COM to attach to IT, for the+/// attach budget, instead of starting anything else.+fn wait_for_new_solidworks(channel: &Channel, pid: u32) {+    set_stage_at_least(Stage::SldworksUp);+    set_stage_at_least(Stage::Attaching);+    let until = std::time::Instant::now() + attach_budget();+    while std::time::Instant::now() < until {+        if channel.attached() {+            set_stage(Stage::Attached);+            finish("attached", None);+            return;+        }+        std::thread::sleep(attach_poll_interval());+    }+    finish(+        "sldworksUpNotAttached",+        Some(format!(+            "sldworks.exe (pid {pid}) from this launch is running but COM did not attach within {}s — it may be at a \+             3DEXPERIENCE sign-in only the user can answer. Nothing was invoked again.",+            attach_budget().as_secs()+        )),+    );+}+ fn record_try(route: &str, invoked_at: u64, stalled: bool, c: Cleanup, note: Option<String>) {     update(|a| {         let stage = a.stage;@@ -2003,6 +2094,12 @@ fn watch_route(channel: &Channel, os: &dyn LaunchOs, route: &str, w: &Watch) ->     let mut signed_in_paused = Duration::ZERO;     let mut sign_in_announced = false;     let mut sign_in_hwnd: Option<i64> = None;+    // ── THE HELPER SIGN-IN PAUSE (0.11.2). From the helper's log appearing+    //    until sldworks.exe does, the stall clocks STOP: that stage can take+    //    minutes (and may be waiting on the user). Only a DEAD chain ends it.+    let mut helper_since: Option<std::time::Instant> = None;+    let mut helper_paused = Duration::ZERO;+    let mut chain_alive = true;     loop {         if last_attach_probe.elapsed() >= attach_poll_interval() {             last_attach_probe = std::time::Instant::now();@@ -2095,6 +2192,9 @@ fn watch_route(channel: &Channel, os: &dyn LaunchOs, route: &str, w: &Watch) ->                     .any(|l| baseline_was_empty || l.is_newer_than(w.base_stamp, w.base_created))                 {                     set_stage_at_least(Stage::HelperSignIn);+                    if helper_since.is_none() && !seen_sldworks && helper_paused.is_zero() {+                        helper_since = Some(std::time::Instant::now());+                    }                 }             }         }@@ -2232,7 +2332,14 @@ fn watch_route(channel: &Channel, os: &dyn LaunchOs, route: &str, w: &Watch) ->                 sldworks_pid = Some(sw.pid);                 set_stage_at_least(Stage::SldworksUp);                 set_stage_at_least(Stage::Attaching);+                // The helper stage is over: the clocks resume.+                if let Some(t) = helper_since.take() {+                    helper_paused += t.elapsed();+                }             }+            // IS OUR CHAIN STILL THERE? Only a chain we have SEEN can be dead.+            let ours_now = chain_pids_now();+            chain_alive = !root_confirmed || ours_now.is_empty() || census.iter().any(|p| ours_now.contains(&p.pid));         }          if seen_run_begin {@@ -2310,7 +2417,34 @@ fn watch_route(channel: &Channel, os: &dyn LaunchOs, route: &str, w: &Watch) ->         // while a human is being asked for a password.         let elapsed = started             .elapsed()-            .saturating_sub(signed_in_paused + sign_in_since.map(|t| t.elapsed()).unwrap_or_default());+            .saturating_sub(signed_in_paused + sign_in_since.map(|t| t.elapsed()).unwrap_or_default())+            .saturating_sub(helper_paused + helper_since.map(|t| t.elapsed()).unwrap_or_default());++        // THE HELPER STAGE: not a stall while the chain lives. A dead chain is+        // one; a helper that outlasts its own (long) budget STOPS the launch —+        // it is never retried, because a retry is a second invoke at whatever+        // the first one is still bringing up.+        if let Some(t) = helper_since {+            if !chain_alive {+                return RouteOutcome::Stalled {+                    note: format!("route '{route}' reached the 3DEXPERIENCE helper sign-in and its launcher chain then EXITED with no sldworks.exe"),+                    saw_log,+                    unattributed_log,+                };+            }+            if t.elapsed() > helper_budget() {+                finish(+                    "helperSignInTooLong",+                    Some(format!(+                        "the 3DEXPERIENCE helper sign-in has run {}s with no sldworks.exe — it may be waiting on the user. \+                         Nothing was killed and the shortcut was NOT invoked again (a second invoke reaches a starting \+                         SOLIDWORKS as an 'open' request: a File ▸ Open dialog, measured 2026-10-15)",+                        t.elapsed().as_secs()+                    )),+                );+                return RouteOutcome::Stop;+            }+        }          if !seen_run_begin && elapsed > run_begin_budget() {             if log_baseline_unknown {
src/launchos.rs+18−3
@@ -701,6 +701,10 @@ pub struct RouteSpec {     pub helper_after_ms: Option<u64>,     pub sldworks_after_ms: Option<u64>,     pub attach_after_ms: Option<u64>,+    /// `sldworksSurvivesKill` (0.11.2): the launcher had already spawned+    /// sldworks.exe when its chain was killed, so it comes up anyway — measured+    /// 2026-10-15, 6 s after the "stall" cleanup.+    pub sldworks_survives_kill: bool, }  impl Default for RouteSpec {@@ -718,6 +722,7 @@ impl Default for RouteSpec {             helper_after_ms: None,             sldworks_after_ms: None,             attach_after_ms: None,+            sldworks_survives_kill: false,         }     } }@@ -797,6 +802,7 @@ impl FakeOs {             helper_after_ms: ms("helperAfterMs"),             sldworks_after_ms: ms("sldworksAfterMs"),             attach_after_ms: ms("attachAfterMs"),+            sldworks_survives_kill: entry.and_then(|e| e.get("sldworksSurvivesKill")).and_then(|v| v.as_bool()).unwrap_or(false),         }     } @@ -867,6 +873,15 @@ impl FakeOs {         text.push_str(&line.to_string());         text.push('\n');         let _ = std::fs::write(&path, text);+        // `dialogOnInvoke` (0.11.2): window rows this route's invoke puts up —+        // the File ▸ Open a re-invoke at a starting Connected SOLIDWORKS raised.+        let routes: Value = std::fs::read_to_string(dir.join("routes.json")).ok().and_then(|t| serde_json::from_str(&t).ok()).unwrap_or(Value::Null);+        if let Some(rows) = routes.get(route).and_then(|r| r.get("dialogOnInvoke")).and_then(|d| d.as_array()) {+            let wpath = dir.join("windows.json");+            let mut all: Vec<Value> = std::fs::read_to_string(&wpath).ok().and_then(|t| serde_json::from_str(&t).ok()).unwrap_or_default();+            all.extend(rows.iter().cloned());+            let _ = std::fs::write(&wpath, Value::Array(all).to_string());+        }         seq     } @@ -886,7 +901,7 @@ impl FakeOs {         let dead = me.terminated_pids();         me.invocations()             .into_iter()-            .filter(|i| me.alive(i, &dead))+            .filter(|i| me.alive(i, &dead) || me.spec(&i.route).sldworks_survives_kill)             .find(|i| me.reached(i, me.spec(&i.route).attach_after_ms))             .map(|i| i.sldworks_pid())     }@@ -896,7 +911,7 @@ impl FakeOs {         let dead = me.terminated_pids();         me.invocations()             .iter()-            .filter(|i| me.alive(i, &dead))+            .filter(|i| me.alive(i, &dead) || me.spec(&i.route).sldworks_survives_kill)             .any(|i| me.reached(i, me.spec(&i.route).attach_after_ms))     } }@@ -1053,7 +1068,7 @@ impl LaunchOs for FakeOs {                     }                 }             }-            if self.alive(&inv, &dead) && self.reached(&inv, spec.sldworks_after_ms) {+            if (self.alive(&inv, &dead) || spec.sldworks_survives_kill) && self.reached(&inv, spec.sldworks_after_ms) {                 out.push(ProcInfo {                     pid: inv.sldworks_pid(),                     image: "SLDWORKS.exe".into(),
src/verbs.rs+14
@@ -806,7 +806,21 @@ fn swx_progress(ctx: &Ctx, _args: &Value) -> Value {  // ── verbs: app lifecycle ──────────────────────────────────────────────────── +/// `swx_start`, and then (0.11.2) the launch's own mess cleared: a File ▸ Open+/// with an empty name that a launch route provably raised is cancelled here+/// too — ledgered — so a launch never leaves the session blocked. fn swx_start(ctx: &Ctx, args: &Value) -> Value {+    let mut out = swx_start_inner(ctx, args);+    let cancelled = crate::dialogs::cancel_launch_file_dialogs(crate::launchos::os().as_ref());+    if !cancelled.is_empty() {+        if let Value::Object(m) = &mut out {+            m.insert("dialogsCancelled".into(), json!(cancelled));+        }+    }+    out+}++fn swx_start_inner(ctx: &Ctx, args: &Value) -> Value {     // SOLIDWORKS VISUALIZE (0.9.19): its platform shortcut, its own readiness.     if arg_str(args, "app") == Some("visualize") {         return crate::visualize::start(ctx, args);
tests/adversary.rs+1
@@ -88,6 +88,7 @@ impl Sim {             ("SWX_LAUNCH_SLDWORKS_SECS", "4"),             ("SWX_LAUNCH_ATTACH_SECS", "8"),             ("SWX_LAUNCH_POLL_MS", "100"),+            ("SWX_LAUNCH_RETRY_SETTLE_MS", "400"),             ("SWX_LAUNCH_ATTACH_POLL_MS", "150"),             ("SWX_LAUNCH_INFLIGHT_SECS", "120"),         ];
tests/adversary13.rs+1
@@ -177,6 +177,7 @@ fn a_session_descended_from_a_foreign_launcher_is_never_adopted_as_ours() {             ("SWX_LAUNCH_SLDWORKS_SECS", "2"),             ("SWX_LAUNCH_ATTACH_SECS", "3"),             ("SWX_LAUNCH_POLL_MS", "100"),+            ("SWX_LAUNCH_RETRY_SETTLE_MS", "400"),             ("SWX_LAUNCH_BACKOFF_SECS", "0"),         ],     );
tests/adversary14.rs+2
@@ -59,6 +59,7 @@ fn a_session_with_no_lineage_link_is_never_adopted_however_often_status_is_read(             ("SWX_LAUNCH_SLDWORKS_SECS", "2"),             ("SWX_LAUNCH_ATTACH_SECS", "3"),             ("SWX_LAUNCH_POLL_MS", "100"),+            ("SWX_LAUNCH_RETRY_SETTLE_MS", "400"),             ("SWX_LAUNCH_BACKOFF_SECS", "0"),         ],     );@@ -157,6 +158,7 @@ fn our_own_launcher_is_not_a_foreign_chain() {             ("SWX_LAUNCH_SLDWORKS_SECS", "2"),             ("SWX_LAUNCH_ATTACH_SECS", "3"),             ("SWX_LAUNCH_POLL_MS", "100"),+            ("SWX_LAUNCH_RETRY_SETTLE_MS", "400"),             ("SWX_LAUNCH_BACKOFF_SECS", "0"),         ],     );
tests/adversary15.rs+1
@@ -67,6 +67,7 @@ fn an_unattributable_routes_own_chain_is_not_a_foreign_chain() {             ("SWX_LAUNCH_SLDWORKS_SECS", "2"),             ("SWX_LAUNCH_ATTACH_SECS", "3"),             ("SWX_LAUNCH_POLL_MS", "100"),+            ("SWX_LAUNCH_RETRY_SETTLE_MS", "400"),             ("SWX_LAUNCH_BACKOFF_SECS", "0"),         ],     );
tests/adversary16.rs+1
@@ -61,6 +61,7 @@ fn a_user_chain_during_an_unattributable_route_is_neither_killed_nor_adopted() {             ("SWX_LAUNCH_SLDWORKS_SECS", "2"),             ("SWX_LAUNCH_ATTACH_SECS", "3"),             ("SWX_LAUNCH_POLL_MS", "100"),+            ("SWX_LAUNCH_RETRY_SETTLE_MS", "400"),             ("SWX_LAUNCH_BACKOFF_SECS", "0"),         ],     );
tests/adversary17.rs+1
@@ -75,6 +75,7 @@ fn re_attribution_succeeds_when_every_precondition_is_met() {             ("SWX_LAUNCH_SLDWORKS_SECS", "2"),             ("SWX_LAUNCH_ATTACH_SECS", "2"),             ("SWX_LAUNCH_POLL_MS", "100"),+            ("SWX_LAUNCH_RETRY_SETTLE_MS", "400"),             ("SWX_LAUNCH_BACKOFF_SECS", "0"),         ],     );
tests/adversary18.rs+2
@@ -67,6 +67,7 @@ fn re_attribution_claims_only_the_process_com_is_attached_to() {             ("SWX_LAUNCH_SLDWORKS_SECS", "2"),             ("SWX_LAUNCH_ATTACH_SECS", "2"),             ("SWX_LAUNCH_POLL_MS", "100"),+            ("SWX_LAUNCH_RETRY_SETTLE_MS", "400"),             ("SWX_LAUNCH_BACKOFF_SECS", "0"),         ],     );@@ -161,6 +162,7 @@ fn the_reattribution_evidence_names_the_process_it_proved() {             ("SWX_LAUNCH_SLDWORKS_SECS", "2"),             ("SWX_LAUNCH_ATTACH_SECS", "2"),             ("SWX_LAUNCH_POLL_MS", "100"),+            ("SWX_LAUNCH_RETRY_SETTLE_MS", "400"),             ("SWX_LAUNCH_BACKOFF_SECS", "0"),         ],     );
tests/adversary2.rs+1
@@ -79,6 +79,7 @@ impl Sim {             ("SWX_LAUNCH_SLDWORKS_SECS", "4"),             ("SWX_LAUNCH_ATTACH_SECS", "8"),             ("SWX_LAUNCH_POLL_MS", "100"),+            ("SWX_LAUNCH_RETRY_SETTLE_MS", "400"),             ("SWX_LAUNCH_ATTACH_POLL_MS", "150"),             ("SWX_LAUNCH_INFLIGHT_SECS", "120"),             ("SWX_LAUNCH_BACKOFF_SECS", "0"),
tests/adversary3.rs+1
@@ -80,6 +80,7 @@ impl Sim {             ("SWX_LAUNCH_SLDWORKS_SECS", "6"),             ("SWX_LAUNCH_ATTACH_SECS", "12"),             ("SWX_LAUNCH_POLL_MS", "100"),+            ("SWX_LAUNCH_RETRY_SETTLE_MS", "400"),             ("SWX_LAUNCH_ATTACH_POLL_MS", "150"),             ("SWX_LAUNCH_INFLIGHT_SECS", "120"),             ("SWX_LAUNCH_BACKOFF_SECS", "0"),
tests/adversary4.rs+1
@@ -56,6 +56,7 @@ impl Sim {             ("SWX_LAUNCH_SLDWORKS_SECS", "6"),             ("SWX_LAUNCH_ATTACH_SECS", "12"),             ("SWX_LAUNCH_POLL_MS", "100"),+            ("SWX_LAUNCH_RETRY_SETTLE_MS", "400"),             ("SWX_LAUNCH_ATTACH_POLL_MS", "150"),             ("SWX_LAUNCH_BACKOFF_SECS", "0"),         ];
tests/launch.rs+109
@@ -114,6 +114,9 @@ impl Machine {             ("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)@@ -1787,3 +1790,109 @@ fn a_launch_record_that_outlived_its_processes_is_ended_not_attached() {         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"), "");+}
tests/signin.rs+1
@@ -51,6 +51,7 @@ impl Machine {             ("SWX_LAUNCH_SLDWORKS_SECS", "4"),             ("SWX_LAUNCH_ATTACH_SECS", "8"),             ("SWX_LAUNCH_POLL_MS", "100"),+            ("SWX_LAUNCH_RETRY_SETTLE_MS", "400"),             ("SWX_LAUNCH_ATTACH_POLL_MS", "150"),             ("SWX_LAUNCH_INFLIGHT_SECS", "120"),             ("SWX_SIGN_IN_VERIFY_SECS", "3"),
uninstall.sh