main
Drew Owens 0.10.7 source e6a7431 6d ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892
//! MEMORY (0.10.5) — the commit charge, SOLIDWORKS's share of it, and who else
//! holds it.
//!
//! MEASURED 2026-10-14 on adom_drew2 (31.4 GB RAM, an automatic 46 GB page
//! file): the SOLIDWORKS Resource Monitor warned "Available committed memory is
//! critically low" at 72.3 of 76.6 GB committed (94 %), 4.2 GB available. The
//! holders were Fusion 360 (20.7 GB, the user's, two days old), the
//! bridge-started SOLIDWORKS (10.6 GB with ZERO documents open, after about a
//! hundred scratch parts were built and closed in it — SOLIDWORKS does not give
//! memory back when a document closes), Chrome (10.2 GB over 51 processes) and
//! WSL (4.7 GB). Quitting that SOLIDWORKS (swx_stop, rung a, 14 s) returned about
//! 16 GB of commit.
//!
//! So this module:
//!   * REPORTS — `swx_status.memory`, and a sentence naming the largest holders
//!     in every reply's `_hint` while pressure is not ok;
//!   * GUARDS — verbs that create or load geometry are refused while commit is
//!     critical (`low_commit_memory`), changing nothing, with the remedy that is
//!     actually safe;
//!   * lets LONG RUNS RECYCLE a session the bridge started (the self-test) —
//!     never one the user is working in, and never any other application.
//!
//! Everything about other processes is READ-ONLY. Nothing here acts on any
//! process; the only action anywhere in this file's orbit is `swx_stop` on the
//! bridge's own, empty SOLIDWORKS session, and that verb keeps every one of its
//! own refusals.

use serde_json::{json, Value};

use crate::launchos::{LaunchOs, ProcPrivate, SystemMemory};

/// SOLIDWORKS's own committed memory above which a long run recycles a session
/// the bridge started (6 GB: a sensible budget on a 32 GB PC).
pub const DEFAULT_BUDGET_MB: u64 = 6144;
pub const ENV_BUDGET: &str = "SWX_SW_MEMORY_BUDGET_MB";
/// CRITICAL: commit available under 8 % of the limit, or under 4 GB.
pub const CRITICAL_FRACTION: f64 = 0.08;
pub const CRITICAL_FLOOR_MB: u64 = 4096;
/// HIGH: commit available under 15 % of the limit (or SOLIDWORKS over budget).
pub const HIGH_FRACTION: f64 = 0.15;
/// How many holders a report names.
pub const TOP_N: usize = 5;
/// A recycle is worth its minutes only when SOLIDWORKS's own share is at least
/// this much (or over budget); below it the memory is held elsewhere and a
/// restart would not relieve anything.
pub const RECYCLE_WORTH_MB: u64 = 2048;

pub const READ_ONLY_NOTE: &str = "Facts, read-only. The bridge never closes, stops or kills a \
     process that is not SOLIDWORKS, whatever it holds — report the largest holders to the user \
     and let them decide. The only memory the bridge returns itself is a SOLIDWORKS session it \
     started, with no documents open (swx_stop, then swx_start).";

/// The session budget: `SWX_SW_MEMORY_BUDGET_MB`, else 6144.
pub fn budget() -> (u64, &'static str) {
    match std::env::var(ENV_BUDGET).ok().and_then(|v| v.trim().parse::<u64>().ok()).filter(|v| *v >= 256) {
        Some(v) => (v, ENV_BUDGET),
        None => (DEFAULT_BUDGET_MB, "default"),
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Pressure {
    Ok,
    High,
    Critical,
    /// The commit charge could not be read. Never reported as "ok".
    Unknown,
}

impl Pressure {
    pub fn name(self) -> &'static str {
        match self {
            Pressure::Ok => "ok",
            Pressure::High => "high",
            Pressure::Critical => "critical",
            Pressure::Unknown => "unknown",
        }
    }
    pub fn pressing(self) -> bool {
        matches!(self, Pressure::High | Pressure::Critical)
    }
}

pub fn gb(mb: u64) -> String {
    format!("{:.1} GB", mb as f64 / 1024.0)
}

/// THE RULE. Critical: commit available under 8 % of the limit or under 4 GB.
/// High: under 15 %, or SOLIDWORKS over its session budget. A commit charge
/// that cannot be read is Unknown (or High, if SOLIDWORKS alone is over budget).
pub fn pressure(sys: Option<&SystemMemory>, sw_private_mb: Option<u64>, budget_mb: u64) -> (Pressure, Vec<String>) {
    let over = sw_private_mb.filter(|p| *p > budget_mb);
    let over_why = |p: u64| format!("SOLIDWORKS holds {} — over its {} session budget", gb(p), gb(budget_mb));
    let Some(s) = sys.filter(|s| s.commit_limit_mb > 0) else {
        return match over {
            Some(p) => (Pressure::High, vec![over_why(p), "the commit charge could not be read".into()]),
            None => (Pressure::Unknown, vec!["the commit charge could not be read".into()]),
        };
    };
    let avail = s.commit_avail_mb as f64;
    let limit = s.commit_limit_mb as f64;
    let mut why = Vec::new();
    if avail < CRITICAL_FRACTION * limit {
        why.push(format!("{} of commit available is under 8 % of the {} limit", gb(s.commit_avail_mb), gb(s.commit_limit_mb)));
    }
    if s.commit_avail_mb < CRITICAL_FLOOR_MB {
        why.push(format!("{} of commit available is under 4 GB", gb(s.commit_avail_mb)));
    }
    if !why.is_empty() {
        if let Some(p) = over {
            why.push(over_why(p));
        }
        return (Pressure::Critical, why);
    }
    if avail < HIGH_FRACTION * limit {
        why.push(format!("{} of commit available is under 15 % of the {} limit", gb(s.commit_avail_mb), gb(s.commit_limit_mb)));
    }
    if let Some(p) = over {
        why.push(over_why(p));
    }
    if why.is_empty() {
        (Pressure::Ok, why)
    } else {
        (Pressure::High, why)
    }
}

/// The largest single processes.
pub fn top_processes(list: &[ProcPrivate], n: usize) -> Vec<ProcPrivate> {
    let mut v = list.to_vec();
    v.sort_by(|a, b| b.private_mb.cmp(&a.private_mb).then(a.pid.cmp(&b.pid)));
    v.truncate(n);
    v
}

/// One application's total (Chrome is 51 processes).
#[derive(Clone, Debug, PartialEq)]
pub struct ImageTotal {
    pub name: String,
    pub processes: usize,
    pub private_mb: u64,
}

/// The largest applications, by image name (case-insensitive).
pub fn top_images(list: &[ProcPrivate], n: usize) -> Vec<ImageTotal> {
    let mut totals: Vec<ImageTotal> = Vec::new();
    for p in list {
        match totals.iter_mut().find(|t| t.name.eq_ignore_ascii_case(&p.name)) {
            Some(t) => {
                t.processes += 1;
                t.private_mb += p.private_mb;
            }
            None => totals.push(ImageTotal { name: p.name.clone(), processes: 1, private_mb: p.private_mb }),
        }
    }
    totals.sort_by(|a, b| b.private_mb.cmp(&a.private_mb).then(a.name.cmp(&b.name)));
    totals.truncate(n);
    totals
}

/// "Fusion360.exe 20.7 GB, SLDWORKS.exe 10.6 GB, chrome.exe 10.2 GB (51 processes)".
pub fn holders_sentence(images: &[ImageTotal]) -> String {
    images
        .iter()
        .map(|i| {
            if i.processes > 1 {
                format!("{} {} ({} processes)", i.name, gb(i.private_mb), i.processes)
            } else {
                format!("{} {}", i.name, gb(i.private_mb))
            }
        })
        .collect::<Vec<_>>()
        .join(", ")
}

/// One cheap reading: the commit pair and SOLIDWORKS's own share.
#[derive(Clone, Debug)]
pub struct Sample {
    pub sys: Result<SystemMemory, String>,
    pub sw_pid: Option<u32>,
    pub sw_private_mb: Option<u64>,
    pub sw_handles: Option<u32>,
    pub budget_mb: u64,
    pub budget_source: &'static str,
}

impl Sample {
    pub fn pressure(&self) -> (Pressure, Vec<String>) {
        pressure(self.sys.as_ref().ok(), self.sw_private_mb, self.budget_mb)
    }
    pub fn commit_avail_mb(&self) -> Option<u64> {
        self.sys.as_ref().ok().map(|s| s.commit_avail_mb)
    }
}

/// The SOLIDWORKS whose memory is reported: the one COM is attached to, when it
/// is in the census; else the newest sldworks.exe.
pub fn solidworks_pid(os: &dyn LaunchOs) -> Option<u32> {
    let census = os.processes().ok()?;
    let sw: Vec<_> = census.iter().filter(|p| p.is("SLDWORKS.exe")).collect();
    if let Some(a) = crate::launch::attached_solidworks_pid() {
        if sw.iter().any(|p| p.pid == a) {
            return Some(a);
        }
    }
    sw.iter().max_by_key(|p| p.created_at.unwrap_or(0)).map(|p| p.pid)
}

pub fn sample(os: &dyn LaunchOs) -> Sample {
    let (budget_mb, budget_source) = budget();
    let sw_pid = solidworks_pid(os);
    let (sw_private_mb, sw_handles) = sw_pid.map(|p| os.memory_of(p)).unwrap_or((None, None));
    Sample { sys: os.system_memory(), sw_pid, sw_private_mb, sw_handles, budget_mb, budget_source }
}

/// The `memory` block.
pub fn report_of(s: &Sample, procs: Result<Vec<ProcPrivate>, String>) -> Value {
    let (p, why) = s.pressure();
    let mut v = json!({
        "pressure": p.name(),
        "pressureWhy": why,
        "sessionBudgetMb": s.budget_mb,
        "sessionBudgetSource": s.budget_source,
        "solidworks": s.sw_pid.map(|pid| json!({
            "pid": pid,
            "privateMb": s.sw_private_mb,
            "handles": s.sw_handles,
            "overBudget": s.sw_private_mb.map(|m| m > s.budget_mb),
        })),
        "readOnly": READ_ONLY_NOTE,
        "rule": "critical: commit available under 8 % of the limit or under 4 GB; high: under 15 %, \
                 or SOLIDWORKS over sessionBudgetMb (SWX_SW_MEMORY_BUDGET_MB). Verbs that create or \
                 load geometry are refused at critical (low_commit_memory).",
    });
    if let Value::Object(m) = &mut v {
        match &s.sys {
            Ok(sys) => {
                let used = sys.commit_limit_mb.saturating_sub(sys.commit_avail_mb);
                m.insert("commitUsedMb".into(), json!(used));
                m.insert("commitLimitMb".into(), json!(sys.commit_limit_mb));
                m.insert("commitAvailableMb".into(), json!(sys.commit_avail_mb));
                m.insert(
                    "commitUsedPercent".into(),
                    json!(if sys.commit_limit_mb > 0 { (used as f64 * 1000.0 / sys.commit_limit_mb as f64).round() / 10.0 } else { 0.0 }),
                );
                m.insert("physicalTotalMb".into(), json!(sys.phys_total_mb));
                m.insert("physicalAvailableMb".into(), json!(sys.phys_avail_mb));
            }
            Err(e) => {
                m.insert("error".into(), json!(e));
            }
        }
        match procs {
            Ok(list) => {
                let images = top_images(&list, TOP_N);
                m.insert(
                    "topProcesses".into(),
                    json!(top_processes(&list, TOP_N)
                        .iter()
                        .map(|p| json!({"name": p.name, "pid": p.pid, "privateMb": p.private_mb}))
                        .collect::<Vec<_>>()),
                );
                m.insert(
                    "topImages".into(),
                    json!(images.iter().map(|i| json!({"name": i.name, "processes": i.processes, "privateMb": i.private_mb})).collect::<Vec<_>>()),
                );
                m.insert("largestHolders".into(), json!(holders_sentence(&images)));
            }
            Err(e) => {
                m.insert("topProcesses".into(), Value::Null);
                m.insert("topProcessesError".into(), json!(e));
            }
        }
    }
    v
}

pub fn report(os: &dyn LaunchOs) -> Value {
    report_of(&sample(os), os.private_by_process())
}

/// The numbers in one clause: "72.3 GB of 76.6 GB committed (94.4 %), 4.2 GB
/// available".
pub fn commit_clause(report: &Value) -> String {
    match (report["commitUsedMb"].as_u64(), report["commitLimitMb"].as_u64(), report["commitAvailableMb"].as_u64()) {
        (Some(u), Some(l), Some(a)) => format!(
            "{} of {} committed ({} %), {} available",
            gb(u),
            gb(l),
            report["commitUsedPercent"].as_f64().unwrap_or(0.0),
            gb(a)
        ),
        _ => "the commit charge could not be read".to_string(),
    }
}

/// The sentence every reply carries while pressure is high or critical.
pub fn pressure_hint(report: &Value) -> Option<String> {
    let p = report["pressure"].as_str()?;
    if p != "high" && p != "critical" {
        return None;
    }
    let sw = match (report.pointer("/solidworks/privateMb").and_then(|v| v.as_u64()), report["sessionBudgetMb"].as_u64()) {
        (Some(m), Some(b)) => format!(" SOLIDWORKS holds {} (its session budget is {}).", gb(m), gb(b)),
        _ => String::new(),
    };
    let holders = report["largestHolders"].as_str().filter(|h| !h.is_empty()).unwrap_or("could not be read");
    let what = if p == "critical" {
        "Verbs that create or load geometry are refused until it recovers (low_commit_memory)."
    } else {
        "Split or pause long runs."
    };
    Some(format!(
        "MEMORY {}: {}.{sw} Largest holders: {holders}. {what} A SOLIDWORKS session the bridge \
         started, with no documents open, returns its memory with swx_stop {{userAuthorized:true}} \
         then swx_start. NEVER close the user's applications; tell the user. See swx_status.memory \
         and the solidworks-memory skill.",
        p.to_uppercase(),
        commit_clause(report),
    ))
}

/// EVERY REPLY, while memory is pressing: the hint, and the numbers. Cheap when
/// it is not (one GlobalMemoryStatusEx and SOLIDWORKS's own counters); the
/// all-process read happens only when there is something to say.
pub fn annotate(out: &mut Value) {
    let os = crate::launchos::os();
    let s = sample(os.as_ref());
    if !s.pressure().0.pressing() {
        return;
    }
    let report = report_of(&s, os.private_by_process());
    let Some(sentence) = pressure_hint(&report) else { return };
    if let Value::Object(m) = out {
        match m.get_mut("_hint") {
            Some(Value::String(h)) if !h.contains("MEMORY HIGH") && !h.contains("MEMORY CRITICAL") => {
                h.push(' ');
                h.push_str(&sentence);
            }
            Some(Value::String(_)) => {}
            Some(_) => {
                m.insert("memoryHint".into(), json!(sentence));
            }
            None => {
                m.insert("_hint".into(), json!(sentence));
            }
        }
        m.entry("memoryPressure").or_insert(json!({
            "pressure": report["pressure"],
            "commitAvailableMb": report["commitAvailableMb"],
            "commitUsedPercent": report["commitUsedPercent"],
            "solidworksPrivateMb": report.pointer("/solidworks/privateMb").cloned().unwrap_or(Value::Null),
            "largestHolders": report["largestHolders"],
        }));
    }
}

/// The ledger's memory sample: commit available and SOLIDWORKS's share, or
/// nothing when neither can be read.
pub fn ledger_sample() -> Option<Value> {
    let os = crate::launchos::os();
    let s = sample(os.as_ref());
    let avail = s.commit_avail_mb();
    if avail.is_none() && s.sw_private_mb.is_none() {
        return None;
    }
    Some(json!({"commitAvailMb": avail, "swPrivateMb": s.sw_private_mb}))
}

/// The same, for the one log line per verb.
pub fn log_suffix() -> String {
    let os = crate::launchos::os();
    let s = sample(os.as_ref());
    match (s.commit_avail_mb(), s.sw_private_mb) {
        (None, None) => String::new(),
        (a, w) => format!(
            " | mem: commit available {}, SOLIDWORKS {}",
            a.map(gb).unwrap_or_else(|| "unknown".into()),
            w.map(gb).unwrap_or_else(|| "not running".into())
        ),
    }
}

// ── hidden documents (0.10.6) ───────────────────────────────────────────────

/// What the hidden-creation bracket did.
#[derive(Debug)]
pub struct Bracket<T, E> {
    pub made: Result<T, E>,
    /// DocumentVisible(false) succeeded (if not, nothing was created).
    pub set_ok: bool,
    /// DocumentVisible(true) succeeded afterwards.
    pub restored: bool,
}

/// THE HIDDEN-CREATION BRACKET, shared by the COM host and the simulator:
/// `set_visible(false)`, then `make` (only if that worked), then
/// `set_visible(true)` — ALWAYS, whatever `make` did, retried up to `tries`
/// times. ISldWorks::DocumentVisible is a SESSION setting: left false, every
/// document of that type the user opens next would be invisible.
pub fn hidden_bracket<T, E>(
    mut set_visible: impl FnMut(bool) -> Result<(), E>,
    make: impl FnOnce() -> Result<T, E>,
    tries: usize,
) -> Bracket<T, E> {
    let off = set_visible(false);
    let set_ok = off.is_ok();
    let made = match off {
        Ok(()) => make(),
        Err(e) => Err(e),
    };
    let mut restored = false;
    for _ in 0..tries.max(1) {
        if set_visible(true).is_ok() {
            restored = true;
            break;
        }
    }
    Bracket { made, set_ok, restored }
}

// ── the guard ───────────────────────────────────────────────────────────────

/// Verbs that create or load geometry.
pub fn guarded(command: &str, args: &Value) -> bool {
    match command {
        "swx_new_document" | "swx_open" | "swx_import" | "swx_render_image" | "swx_show_part" | "swx_show_assembly"
        | "swx_show_drawing" => true,
        "swx_selftest" => args.get("level").and_then(|l| l.as_str()) == Some("write"),
        "swx_run_script" => args
            .get("script")
            .and_then(|s| s.as_array())
            .map(|steps| steps.iter().any(|st| matches!(st.get("op").and_then(|o| o.as_str()), Some("new_part" | "new_assembly"))))
            .unwrap_or(false),
        _ => false,
    }
}

/// A caller's override: `ignoreMemoryPressure:true` with a reason of 8+ characters.
pub fn override_reason(args: &Value) -> Result<Option<String>, ()> {
    if args.get("ignoreMemoryPressure").and_then(|v| v.as_bool()) != Some(true) {
        return Ok(None);
    }
    match args.get("reason").and_then(|r| r.as_str()).map(str::trim).filter(|r| r.chars().count() >= 8) {
        Some(r) => Ok(Some(r.to_string())),
        None => Err(()),
    }
}

/// What the guard does with a pressure and an override.
#[derive(Clone, Debug, PartialEq)]
pub enum GuardDecision {
    Proceed,
    /// Critical, run anyway: the caller said so, with a reason (ledgered).
    Overridden(String),
    Refuse { override_without_reason: bool },
}

pub fn guard_decision(p: Pressure, over: Result<Option<String>, ()>) -> GuardDecision {
    if p != Pressure::Critical {
        return GuardDecision::Proceed;
    }
    match over {
        Ok(Some(r)) => GuardDecision::Overridden(r),
        Ok(None) => GuardDecision::Refuse { override_without_reason: false },
        Err(()) => GuardDecision::Refuse { override_without_reason: true },
    }
}

/// Whose SOLIDWORKS is this, and is anything open in it?
#[derive(Clone, Debug, Default, PartialEq)]
pub struct SessionFacts {
    pub com_pid: Option<u32>,
    pub open_docs: Option<u64>,
    /// `None` = cannot tell, which is never read as "the bridge's".
    pub bridge_started: Option<bool>,
}

/// Pure: the launch record's claim against the session COM answers for. Both
/// pid AND creation time, like the quit ladder — a pid alone is reusable.
pub fn bridge_started(claim: Option<(u32, u64)>, com_pid: Option<u32>, created: Option<u64>) -> Option<bool> {
    match (com_pid, created) {
        (Some(pid), Some(c)) => Some(claim == Some((pid, c))),
        _ => None,
    }
}

pub fn session_facts(ctx: &crate::verbs::Ctx, os: &dyn LaunchOs) -> SessionFacts {
    let Ok(state) = ctx.call("app_state", json!({}), 20) else { return SessionFacts::default() };
    if state.get("running").and_then(|v| v.as_bool()) == Some(false) {
        return SessionFacts::default();
    }
    let com_pid = state.get("processId").and_then(|v| v.as_u64()).map(|p| p as u32);
    let open_docs = state.get("documentCount").and_then(|v| v.as_u64());
    // A launch that attached but never named its SOLIDWORKS is attributed now
    // (0.10.7) — on the Connected edition that was every launch.
    if com_pid.is_some() {
        crate::launch::attribute_on_attach(os, com_pid);
    }
    let created = com_pid.and_then(|pid| os.processes().ok()?.iter().find(|p| p.pid == pid).and_then(|p| p.created_at));
    SessionFacts { com_pid, open_docs, bridge_started: bridge_started(crate::launch::attributed_solidworks(), com_pid, created) }
}

/// THE SAFE REMEDY, and only the safe one.
pub fn remedy(bridge_started: Option<bool>, open_docs: Option<u64>, sw_private_mb: Option<u64>) -> String {
    match (bridge_started, open_docs) {
        (Some(true), Some(0)) => format!(
            "This SOLIDWORKS session was started by the bridge and has no documents open: \
             swx_stop {{\"userAuthorized\": true, \"reason\": \"...\"}} then swx_start frees about {} \
             (SOLIDWORKS keeps memory after its documents close).",
            sw_private_mb.map(gb).unwrap_or_else(|| "its memory".into())
        ),
        (Some(true), Some(n)) => format!(
            "This SOLIDWORKS session was started by the bridge but has {n} document(s) open. Close \
             the bridge's own (swx_reset_workspace); once none are open, swx_stop \
             {{\"userAuthorized\": true}} then swx_start returns its memory. A document the user \
             opened is theirs: tell the user which applications hold the most, and never close \
             them."
        ),
        _ => "Tell the user which applications hold the most memory (largestHolders) and let \
              them decide. Never close them — not their SOLIDWORKS, not Fusion, not the browser, \
              nothing."
            .to_string(),
    }
}

/// The guard, for the dispatcher. `Ok(Some(override))` runs the verb and says it
/// was overridden; `Err(refusal)` changes nothing.
pub fn guard(ctx: &crate::verbs::Ctx, command: &str, args: &Value) -> Result<Option<Value>, Value> {
    let os = crate::launchos::os();
    let s = sample(os.as_ref());
    let (p, why) = s.pressure();
    let decision = guard_decision(p, override_reason(args));
    if decision == GuardDecision::Proceed {
        return Ok(None);
    }
    let report = report_of(&s, os.private_by_process());
    if let GuardDecision::Overridden(reason) = &decision {
        crate::ledger::record(
            "memory_guard_overridden",
            json!({"verb": command, "reason": reason, "pressure": p.name(), "why": why,
                   "commitAvailableMb": report["commitAvailableMb"], "commitLimitMb": report["commitLimitMb"],
                   "solidworksPrivateMb": s.sw_private_mb}),
        );
        return Ok(Some(json!({"overridden": true, "reason": reason, "pressure": p.name(), "commit": commit_clause(&report)})));
    }
    let facts = session_facts(ctx, os.as_ref());
    let fix = remedy(facts.bridge_started, facts.open_docs, s.sw_private_mb);
    crate::ledger::record(
        "memory_guard_refused",
        json!({"verb": command, "why": why, "commitAvailableMb": report["commitAvailableMb"],
               "commitLimitMb": report["commitLimitMb"], "solidworksPrivateMb": s.sw_private_mb}),
    );
    let without_reason = matches!(decision, GuardDecision::Refuse { override_without_reason: true });
    let mut out = crate::verbs::err_code(
        "low_commit_memory",
        format!(
            "{command} refused: available committed memory is critically low — {}. Creating or \
             loading geometry now risks allocation failures in every application on this PC. \
             Nothing was changed.{}",
            commit_clause(&report),
            if without_reason { " (ignoreMemoryPressure needs a reason of 8+ characters.)" } else { "" }
        ),
        format!(
            "Largest holders: {}. {fix} To run it anyway, pass ignoreMemoryPressure:true with a \
             reason; the override is ledgered. See the solidworks-memory skill.",
            report["largestHolders"].as_str().unwrap_or("could not be read")
        ),
    );
    if let Value::Object(m) = &mut out {
        m.insert("memory".into(), report.clone());
        m.insert("largestHolders".into(), report["largestHolders"].clone());
        m.insert("remedy".into(), json!(fix));
        m.insert(
            "solidworksSession".into(),
            json!({"pid": facts.com_pid, "bridgeStarted": facts.bridge_started, "openDocuments": facts.open_docs}),
        );
        m.insert("changedNothing".into(), json!(true));
    }
    Err(out)
}

// ── long runs ───────────────────────────────────────────────────────────────

/// Between two checks of a long run.
#[derive(Clone, Debug, PartialEq)]
pub enum Between {
    Continue,
    /// Carry on, and say so: commit is HIGH, but SOLIDWORKS is within its
    /// budget — the memory is the other applications', and stopping would only
    /// throttle the run (0.10.7, measured: a run stopped before T01 at 86.9 %
    /// committed while SOLIDWORKS held 1.5 GB).
    Note(String),
    Recycle(String),
    Stop(String),
}

/// Does this reading call for a decision that needs the SESSION's facts (whose
/// it is, what is open)? Only SOLIDWORKS over its budget, or commit CRITICAL.
pub fn needs_attention(p: Pressure, sw_private_mb: Option<u64>, budget_mb: u64) -> bool {
    p == Pressure::Critical || sw_private_mb.map(|m| m > budget_mb).unwrap_or(false)
}

/// THE RULE BETWEEN CHECKS (0.10.7):
///   * SOLIDWORKS OVER ITS BUDGET: recycle it if it is the bridge's own, empty
///     session; otherwise STOP — it is SOLIDWORKS that is growing.
///   * commit CRITICAL: recycle the bridge's own empty session if SOLIDWORKS
///     holds enough to matter; otherwise STOP.
///   * commit HIGH with SOLIDWORKS within budget: CARRY ON and report it — the
///     reused document keeps SOLIDWORKS flat, and the memory is elsewhere.
pub fn between_checks(p: Pressure, sw_private_mb: Option<u64>, budget_mb: u64, bridge_started: Option<bool>, open_docs: Option<u64>) -> Between {
    let over = sw_private_mb.filter(|m| *m > budget_mb);
    let not_recyclable = |why: String| -> Between {
        match (bridge_started, open_docs) {
            (Some(true), Some(n)) if n > 0 => {
                Between::Stop(format!("{why}; the session is the bridge's but {n} document(s) are open, so it cannot be recycled safely"))
            }
            (Some(true), None) => Between::Stop(format!("{why}; the open-document count could not be read, so it cannot be recycled safely")),
            (Some(false), _) => Between::Stop(format!("{why}; this SOLIDWORKS was not started by the bridge, so the bridge will not end it")),
            (None, _) => Between::Stop(format!(
                "{why}; whose SOLIDWORKS session this is could not be established, so the bridge will not end it"
            )),
            (Some(true), Some(_)) => Between::Stop(why),
        }
    };
    let ours_and_empty = matches!((bridge_started, open_docs), (Some(true), Some(0)));
    if let Some(m) = over {
        let why = format!("SOLIDWORKS holds {} (budget {})", gb(m), gb(budget_mb));
        return if ours_and_empty { Between::Recycle(why) } else { not_recyclable(why) };
    }
    match p {
        Pressure::Critical => {
            let why = "committed memory is critical".to_string();
            let worth = sw_private_mb.map(|m| m >= RECYCLE_WORTH_MB).unwrap_or(false);
            if ours_and_empty && worth {
                Between::Recycle(why)
            } else if ours_and_empty {
                Between::Stop(format!(
                    "{why}; SOLIDWORKS itself holds only {} — the memory is held by other applications \
                     (largestHolders), so recycling SOLIDWORKS would not relieve it",
                    sw_private_mb.map(gb).unwrap_or_else(|| "an unknown amount".into())
                ))
            } else {
                not_recyclable(why)
            }
        }
        Pressure::High => Between::Note(format!(
            "committed memory is high, but SOLIDWORKS holds {} — within its {} budget — so the run carries on \
             (the memory is held by other applications; see largestHolders)",
            sw_private_mb.map(gb).unwrap_or_else(|| "an unknown amount".into()),
            gb(budget_mb)
        )),
        _ => Between::Continue,
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn sys(limit: u64, avail: u64) -> SystemMemory {
        SystemMemory { commit_limit_mb: limit, commit_avail_mb: avail, phys_total_mb: 32_000, phys_avail_mb: 4_000 }
    }

    fn pp(name: &str, pid: u32, mb: u64) -> ProcPrivate {
        ProcPrivate { name: name.into(), pid, private_mb: mb }
    }

    /// The numbers measured on 2026-10-14, before and after the quit.
    #[test]
    fn the_measured_incident_is_critical_and_the_quit_made_it_ok() {
        // 72.3 of 76.6 GB committed, 4.2 GB available, SOLIDWORKS 10.6 GB.
        let before = sys(78_438, 4_300);
        let (p, why) = pressure(Some(&before), Some(10_854), DEFAULT_BUDGET_MB);
        assert_eq!(p, Pressure::Critical, "{why:?}");
        assert!(why.iter().any(|w| w.contains("under 8 %")) && why.iter().any(|w| w.contains("over its 6.0 GB")), "{why:?}");
        // After the quit: 56.1 of 73.7 GB, 17.7 GB available, no SOLIDWORKS.
        assert_eq!(pressure(Some(&sys(75_469, 18_125)), None, DEFAULT_BUDGET_MB).0, Pressure::Ok);
    }

    #[test]
    fn thresholds() {
        let b = DEFAULT_BUDGET_MB;
        // Under 8 % is critical even with plenty of absolute room on a big PC...
        assert_eq!(pressure(Some(&sys(200_000, 15_000)), None, b).0, Pressure::Critical);
        // ...and under 4 GB is critical even when that is over 8 % of a small one.
        assert_eq!(pressure(Some(&sys(40_000, 4_000)), None, b).0, Pressure::Critical);
        assert_eq!(pressure(Some(&sys(40_000, 4_096)), None, b).0, Pressure::High, "10 % is high, not critical");
        // 8..15 % is high.
        assert_eq!(pressure(Some(&sys(100_000, 10_000)), None, b).0, Pressure::High);
        assert_eq!(pressure(Some(&sys(100_000, 15_000)), None, b).0, Pressure::Ok, "15 % exactly is ok");
        // SOLIDWORKS over its budget is high on an otherwise roomy machine.
        assert_eq!(pressure(Some(&sys(100_000, 50_000)), Some(b + 1), b).0, Pressure::High);
        assert_eq!(pressure(Some(&sys(100_000, 50_000)), Some(b), b).0, Pressure::Ok, "at the budget is not over it");
        // Unreadable commit is UNKNOWN, never ok — or high if SOLIDWORKS alone says so.
        assert_eq!(pressure(None, None, b).0, Pressure::Unknown);
        assert_eq!(pressure(Some(&sys(0, 0)), Some(100), b).0, Pressure::Unknown);
        assert_eq!(pressure(None, Some(b + 1), b).0, Pressure::High);
    }

    #[test]
    fn the_largest_holders_are_named_per_process_and_per_application() {
        let mut list = vec![pp("Fusion360.exe", 10, 21_197), pp("SLDWORKS.exe", 20, 10_854), pp("vmmemWSL", 30, 4_812)];
        for i in 0..51 {
            list.push(pp("chrome.exe", 100 + i, 200));
        }
        let top = top_processes(&list, TOP_N);
        assert_eq!(top.iter().map(|p| p.pid).collect::<Vec<_>>(), [10, 20, 30, 100, 101], "largest first, ties by pid");
        let images = top_images(&list, TOP_N);
        assert_eq!(images[0], ImageTotal { name: "Fusion360.exe".into(), processes: 1, private_mb: 21_197 });
        assert_eq!(images[2], ImageTotal { name: "chrome.exe".into(), processes: 51, private_mb: 10_200 });
        assert_eq!(
            holders_sentence(&images),
            "Fusion360.exe 20.7 GB, SLDWORKS.exe 10.6 GB, chrome.exe 10.0 GB (51 processes), vmmemWSL 4.7 GB"
        );
        // Case-insensitive grouping.
        assert_eq!(top_images(&[pp("Chrome.exe", 1, 10), pp("chrome.EXE", 2, 5)], 5)[0].processes, 2);
    }

    #[test]
    fn the_guard_refuses_only_at_critical_and_an_override_needs_a_reason() {
        assert_eq!(guard_decision(Pressure::Ok, Ok(None)), GuardDecision::Proceed);
        assert_eq!(guard_decision(Pressure::High, Ok(None)), GuardDecision::Proceed, "high warns, never refuses");
        assert_eq!(guard_decision(Pressure::Unknown, Ok(None)), GuardDecision::Proceed);
        assert_eq!(guard_decision(Pressure::Critical, Ok(None)), GuardDecision::Refuse { override_without_reason: false });
        assert_eq!(guard_decision(Pressure::Critical, Err(())), GuardDecision::Refuse { override_without_reason: true });
        assert_eq!(guard_decision(Pressure::Critical, Ok(Some("the user said go".into()))), GuardDecision::Overridden("the user said go".into()));
        assert_eq!(override_reason(&json!({})), Ok(None));
        assert_eq!(override_reason(&json!({"ignoreMemoryPressure": true})), Err(()));
        assert_eq!(override_reason(&json!({"ignoreMemoryPressure": true, "reason": "  short  "})), Err(()));
        assert_eq!(override_reason(&json!({"ignoreMemoryPressure": true, "reason": "Drew asked for it"})), Ok(Some("Drew asked for it".into())));
        assert_eq!(override_reason(&json!({"ignoreMemoryPressure": false, "reason": "Drew asked for it"})), Ok(None));
    }

    #[test]
    fn guarded_verbs_are_the_ones_that_create_or_load_geometry() {
        for v in ["swx_new_document", "swx_open", "swx_import", "swx_render_image", "swx_show_part", "swx_show_assembly", "swx_show_drawing"] {
            assert!(guarded(v, &json!({})), "{v}");
        }
        assert!(guarded("swx_selftest", &json!({"level": "write"})));
        assert!(!guarded("swx_selftest", &json!({"level": "read"})) && !guarded("swx_selftest", &json!({})));
        assert!(guarded("swx_run_script", &json!({"script": [{"op": "new_part"}, {"op": "rebuild"}]})));
        assert!(guarded("swx_run_script", &json!({"script": [{"op": "new_assembly"}]})));
        assert!(!guarded("swx_run_script", &json!({"script": [{"op": "rebuild"}]})), "a script on an open part loads nothing new");
        for v in ["swx_status", "swx_stop", "swx_close_doc", "swx_reset_workspace", "swx_inspect_bodies", "swx_export_step", "swx_docs"] {
            assert!(!guarded(v, &json!({})), "{v} frees or reads; never refused for memory");
        }
    }

    #[test]
    fn the_remedy_is_the_safe_one() {
        let ours = remedy(Some(true), Some(0), Some(10_854));
        assert!(ours.contains("swx_stop") && ours.contains("swx_start") && ours.contains("10.6 GB"), "{ours}");
        for (b, d) in [(Some(false), Some(0)), (None, None), (Some(false), Some(3)), (None, Some(0))] {
            let r = remedy(b, d, Some(10_854));
            assert!(r.contains("Tell the user") && r.contains("Never close them") && !r.contains("swx_stop"), "{b:?} {d:?}: {r}");
        }
        let busy = remedy(Some(true), Some(2), Some(10_854));
        assert!(busy.contains("2 document(s)") && busy.contains("never close"), "{busy}");
    }

    #[test]
    fn only_the_bridges_own_session_counts_as_the_bridges() {
        assert_eq!(bridge_started(Some((700, 1_000)), Some(700), Some(1_000)), Some(true));
        assert_eq!(bridge_started(Some((700, 1_000)), Some(700), Some(1_001)), Some(false), "pid reused: a different process");
        assert_eq!(bridge_started(Some((700, 1_000)), Some(701), Some(1_000)), Some(false));
        assert_eq!(bridge_started(None, Some(700), Some(1_000)), Some(false), "no launch record: not the bridge's");
        assert_eq!(bridge_started(Some((700, 1_000)), Some(700), None), None, "cannot tell is never yes");
        assert_eq!(bridge_started(Some((700, 1_000)), None, None), None);
    }

    #[test]
    fn a_long_run_recycles_only_its_own_empty_session_and_otherwise_stops() {
        let b = DEFAULT_BUDGET_MB;
        assert_eq!(between_checks(Pressure::Ok, Some(1_000), b, Some(false), Some(4)), Between::Continue);
        // OVER BUDGET: the bridge's own empty session is recycled; anything else stops.
        assert!(matches!(between_checks(Pressure::Ok, Some(b + 1), b, Some(true), Some(0)), Between::Recycle(_)));
        for (bs, d, says) in [
            (Some(true), Some(1), "1 document(s) are open"),
            (Some(true), None, "could not be read"),
            (Some(false), Some(0), "not started by the bridge"),
            (None, Some(0), "could not be established"),
        ] {
            match between_checks(Pressure::High, Some(b + 1), b, bs, d) {
                Between::Stop(why) => assert!(why.contains("SOLIDWORKS holds") && why.contains(says), "{why}"),
                other => panic!("{bs:?} {d:?}: {other:?} — only the bridge's own empty session is recycled"),
            }
        }
        // CRITICAL: recycled when it is ours, empty and SOLIDWORKS holds enough to matter...
        assert!(matches!(between_checks(Pressure::Critical, Some(RECYCLE_WORTH_MB), b, Some(true), Some(0)), Between::Recycle(_)));
        // ...otherwise it stops.
        for sw in [Some(1_000), None] {
            match between_checks(Pressure::Critical, sw, b, Some(true), Some(0)) {
                Between::Stop(why) => assert!(why.contains("held by other applications"), "{why}"),
                other => panic!("{other:?}"),
            }
        }
        assert!(matches!(between_checks(Pressure::Critical, Some(1_500), b, Some(false), Some(0)), Between::Stop(_)));
        // HIGH WITH SOLIDWORKS WITHIN BUDGET: the run CARRIES ON and says so —
        // measured 2026-10-14: 86.9 % committed, Fusion 20.4 GB, SOLIDWORKS 1.5 GB,
        // and 0.10.6 stopped before T01. Whoever's session it is.
        for bs in [Some(false), Some(true), None] {
            match between_checks(Pressure::High, Some(1_536), b, bs, Some(0)) {
                Between::Note(why) => assert!(why.contains("carries on") && why.contains("1.5 GB"), "{why}"),
                other => panic!("{bs:?}: {other:?} — high pressure that is not SOLIDWORKS's must not throttle the run"),
            }
        }
        assert!(!needs_attention(Pressure::High, Some(1_536), b), "high alone needs no session facts");
        assert!(needs_attention(Pressure::Critical, Some(10), b) && needs_attention(Pressure::Ok, Some(b + 1), b));
        assert_eq!(between_checks(Pressure::Unknown, Some(10), b, None, None), Between::Continue);
    }

    #[test]
    fn the_hidden_bracket_always_puts_the_setting_back() {
        use std::cell::RefCell;
        let calls = RefCell::new(Vec::new());
        // Creation fails: the setting is still restored.
        let b = hidden_bracket(
            |v| {
                calls.borrow_mut().push(v);
                Ok::<(), String>(())
            },
            || Err::<u32, String>("NewDocument failed".into()),
            3,
        );
        assert!(b.made.is_err() && b.set_ok && b.restored);
        assert_eq!(*calls.borrow(), vec![false, true], "off, then on — after a failure too");
        // The restore fails twice, then works: retried, and reported restored.
        let n = RefCell::new(0);
        let b = hidden_bracket(
            |v| {
                if v {
                    *n.borrow_mut() += 1;
                    if *n.borrow() < 3 {
                        return Err("busy".to_string());
                    }
                }
                Ok(())
            },
            || Ok::<u32, String>(7),
            3,
        );
        assert_eq!((b.made, b.restored, *n.borrow()), (Ok(7), true, 3));
        // It never works: reported NOT restored, after every try.
        let n = RefCell::new(0);
        let b = hidden_bracket(|v| if v { *n.borrow_mut() += 1; Err("no".to_string()) } else { Ok(()) }, || Ok::<u32, String>(1), 3);
        assert!(!b.restored && *n.borrow() == 3);
        // DocumentVisible(false) fails: nothing is created — and it is still set back.
        let made = RefCell::new(false);
        let calls = RefCell::new(Vec::new());
        let b = hidden_bracket(
            |v| {
                calls.borrow_mut().push(v);
                if v { Ok(()) } else { Err("refused".to_string()) }
            },
            || {
                *made.borrow_mut() = true;
                Ok::<u32, String>(1)
            },
            3,
        );
        assert!(!b.set_ok && b.made.is_err() && !*made.borrow() && b.restored);
        assert_eq!(*calls.borrow(), vec![false, true]);
    }

    #[test]
    fn the_report_and_its_hint_name_the_holders() {
        let s = Sample {
            sys: Ok(sys(78_438, 4_300)),
            sw_pid: Some(20),
            sw_private_mb: Some(10_854),
            sw_handles: Some(4_000),
            budget_mb: DEFAULT_BUDGET_MB,
            budget_source: "default",
        };
        let r = report_of(&s, Ok(vec![pp("Fusion360.exe", 10, 21_197), pp("SLDWORKS.exe", 20, 10_854)]));
        assert_eq!(r["pressure"], "critical");
        assert_eq!(r["commitUsedMb"], 78_438 - 4_300);
        assert_eq!(r["commitUsedPercent"], 94.5);
        assert_eq!(r["solidworks"]["overBudget"], true);
        assert_eq!(r["topProcesses"][0]["name"], "Fusion360.exe");
        let h = pressure_hint(&r).unwrap();
        assert!(h.starts_with("MEMORY CRITICAL: 72.4 GB of 76.6 GB committed (94.5 %), 4.2 GB available."), "{h}");
        assert!(h.contains("Fusion360.exe 20.7 GB, SLDWORKS.exe 10.6 GB") && h.contains("NEVER close the user's applications"), "{h}");
        // Nothing to say when it is fine.
        let ok = report_of(&Sample { sys: Ok(sys(78_438, 40_000)), sw_private_mb: Some(1_000), ..s.clone() }, Ok(vec![]));
        assert_eq!(pressure_hint(&ok), None);
        // An unreadable process list is said, not hidden.
        let r = report_of(&s, Err("no".into()));
        assert!(r["topProcesses"].is_null() && r["topProcessesError"] == "no");
    }
}