app
SOLIDWORKS Bridge
Public Made by Adomby adom
Launch, model and quit SOLIDWORKS from any Adom container. Nothing is ever lost.
main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
//! IS THE USER WORKING IN THIS SOLIDWORKS? — the user-presence guard (0.9.9).
//!
//! MEASURED 2026-10-01 on hosted 0.9.8: a "leave the machine as found"
//! `swx_stop` on a bridge-launched, relaunched, EMPTY-LOOKING session returned
//! `stop_blocked_by_dialog` — the dialog was SOLIDWORKS's own File Open dialog,
//! because DREW HAD STARTED USING THE SESSION. Minutes later it held 34
//! documents: his crate assembly and its parts. Nothing was lost only because
//! his dialog happened to be up; a session he had just brought to the front
//! would have been quit under him, and a sweep a minute later would have closed
//! his documents. The bridge had no notion that the USER had taken over a
//! session it launched.
//!
//! So, before every verb that ENDS work — swx_stop, swx_shutdown,
//! swx_close_all_safely, swx_close_all_documents, swx_reset_workspace, and the
//! quit ladder's priming — presence is MEASURED:
//!
//! * is a SOLIDWORKS window (the session's process or one of its children)
//! the FOREGROUND window, and how long since the last user input
//! (system-wide) — foreground AND input within `idle_secs()` (120 s);
//! * is any document open that the bridge did not put there itself. THE
//! BASELINE MAY CONTAIN ONLY DOCUMENTS THE BRIDGE ITSELF PUT THERE
//! (adversary pass 46): it starts EMPTY for every session — bridge-launched
//! or not — and grows only by what a bridge verb opened or created, measured
//! as the difference across that verb; it is never re-read from the open
//! set. So in a session the USER launched, every document is the user's, and
//! quit / sweep / drain refuse `user_is_active` without the explicit
//! override — the intended reading of "never end a session the user is
//! working in";
//! * was the session already marked `takenOverByUser`.
//!
//! PRESENT refuses `user_is_active` with the evidence. An unreadable
//! measurement is PRESENT (fail closed) — but only POSITIVE evidence marks the
//! session taken over, persistently for its (pid, creation time): from then on
//! the bridge never ends or sweeps it without `userPresentOverride:true` +
//! `userAuthorized:true` + a reason, even after the user steps away.
//! Attribution of the launch stays; authority to end the session does not.
use serde_json::{json, Value};
/// Input within this many seconds, with SOLIDWORKS in the foreground, is a
/// user at the keyboard. `SWX_PRESENCE_IDLE_SECS` overrides.
pub fn idle_secs() -> u64 {
std::env::var("SWX_PRESENCE_IDLE_SECS").ok().and_then(|v| v.trim().parse().ok()).unwrap_or(120)
}
/// The verbs that end a user's work, and so are guarded.
///
/// `swx_reset_workspace` is NOT here (0.10.9): it closes only the bridge's own
/// never-saved scratch documents, and while the user is present it does so for
/// every one EXCEPT the active document (see `verbs3::reset_workspace`).
/// MEASURED 2026-10-14: with it refused outright, 17 resets in a row were
/// refused while 18 new scratch parts were allowed — 18 of the bridge's windows
/// piled up on the user's screen.
pub const GUARDED: &[&str] = &["swx_stop", "swx_shutdown", "swx_close_all_safely", "swx_close_all_documents"];
/// The explicit user-present override: `userPresentOverride:true` +
/// `userAuthorized:true` + a reason.
pub fn overridden(args: &Value) -> bool {
args.get("userPresentOverride").and_then(|v| v.as_bool()) == Some(true)
&& args.get("userAuthorized").and_then(|v| v.as_bool()) == Some(true)
&& args.get("reason").and_then(|v| v.as_str()).map(|r| !r.trim().is_empty()).unwrap_or(false)
}
// ── IS THE USER AT THE MACHINE RIGHT NOW? (0.10.9) ───────────────────────────
//
// MEASURED 2026-10-14 (hosted 0.10.7): while Drew was at the keyboard, every
// close the bridge attempted was refused for his presence and every NEW scratch
// part was allowed — 18 of the bridge's windows piled up on his screen, checks
// went from 14 s to a 7-minute stall, SOLIDWORKS reached 15.1 GB and the
// machine 91 % committed. The rule is now symmetric: the bridge does not END a
// session the user is working in, and it does not POP its own windows on a
// user who is working.
/// The user's ACTIVITY, read without COM: input within `idle_secs()`
/// system-wide (GetLastInputInfo) is a user at the machine, whatever
/// application is in front. An unreadable input time counts as active.
#[derive(Clone, Debug)]
pub struct Activity {
pub active: bool,
pub idle_seconds: Option<u64>,
pub foreground: Value,
pub foreground_is_solidworks: Option<bool>,
}
impl Activity {
pub fn to_json(&self) -> Value {
json!({
"active": self.active,
"idleSeconds": self.idle_seconds,
"idleThresholdSecs": idle_secs(),
"foreground": self.foreground,
"foregroundIsSolidworks": self.foreground_is_solidworks,
"rule": "active = the user gave input (anywhere on the machine) within idleThresholdSecs; \
an unreadable input time counts as active. While active, the bridge does not \
create documents for its own purposes (self-test, batteries) and does not end \
the session.",
})
}
}
pub fn activity() -> Activity {
let os = crate::launchos::os();
// A simulated SOLIDWORKS with no presence scenario is on nobody's screen.
if crate::is_mock() && std::env::var(crate::launchos::ENV_FAKE_DIR).map(|d| d.is_empty()).unwrap_or(true) {
return Activity { active: false, idle_seconds: None, foreground: Value::Null, foreground_is_solidworks: None };
}
let input = os.seconds_since_last_input().ok();
let sw_pids: Vec<u32> = os
.processes()
.ok()
.map(|c| {
let sw: Vec<u32> = c.iter().filter(|p| p.is("SLDWORKS.exe")).map(|p| p.pid).collect();
let mut all = sw.clone();
all.extend(c.iter().filter(|p| p.ppid.map(|pp| sw.contains(&pp)).unwrap_or(false)).map(|p| p.pid));
all
})
.unwrap_or_default();
let (foreground, is_sw) = match os.foreground_window() {
Ok(Some(w)) => (json!({"title": w.title, "pid": w.pid}), Some(sw_pids.contains(&w.pid))),
Ok(None) => (json!({"unreadable": "no window has the foreground"}), None),
Err(e) => (json!({"unreadable": e}), None),
};
Activity {
active: input.map(|n| n < idle_secs()).unwrap_or(true),
idle_seconds: input,
foreground,
foreground_is_solidworks: is_sw,
}
}
/// How long a bridge-purpose creation waits for the user to go idle:
/// `waitForIdleSeconds`, else `SWX_WAIT_FOR_IDLE_SECS`, else 120 (at most 900).
pub fn wait_for_idle_secs(args: &Value) -> u64 {
args.get("waitForIdleSeconds")
.and_then(|v| v.as_u64())
.or_else(|| std::env::var("SWX_WAIT_FOR_IDLE_SECS").ok().and_then(|v| v.trim().parse().ok()))
.unwrap_or(120)
.min(900)
}
/// WAIT FOR THE USER TO GO IDLE, up to `secs`. `Ok` = idle (or became idle);
/// `Err` = still active when the time ran out — with the last reading.
pub fn wait_for_idle(secs: u64) -> Result<Activity, Activity> {
let poll = std::time::Duration::from_millis(
std::env::var("SWX_PRESENCE_POLL_MS").ok().and_then(|v| v.trim().parse().ok()).unwrap_or(5000),
);
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(secs);
loop {
let a = activity();
if !a.active {
return Ok(a);
}
if std::time::Instant::now() >= deadline {
return Err(a);
}
std::thread::sleep(poll.min(deadline.saturating_duration_since(std::time::Instant::now()).max(std::time::Duration::from_millis(10))));
}
}
/// A verb that would CREATE a visible document for the BRIDGE's own purposes:
/// the self-test's write level, and `swx_new_document` / `swx_run_script`
/// marked `purpose:"bridge"` (batteries, the bridge's own scratch). A user's
/// own request is not this.
pub fn creates_for_bridge(command: &str, args: &Value) -> bool {
match command {
"swx_selftest" => args.get("level").and_then(|l| l.as_str()) == Some("write"),
"swx_new_document" | "swx_run_script" | "swx_import" => {
args.get("purpose").and_then(|p| p.as_str()) == Some("bridge")
}
_ => false,
}
}
/// The refusal for a bridge-purpose creation while the user is active.
pub fn user_present_refusal(verb: &str, waited: u64, a: &Activity) -> Value {
let mut out = crate::verbs::err_code(
"user_present",
format!(
"{verb} refused: the user is at the machine (input {} ago) and stayed active for the {waited} s \
the bridge waited. It does not open its own windows on a user who is working. Nothing was created.",
a.idle_seconds.map(|n| format!("{n} s")).unwrap_or_else(|| "at an unreadable time".into())
),
"Run it when the user is away (swx_status.userPresence.active is false), or pass a longer \
waitForIdleSeconds. A script the USER asked for is not refused — this is only for the \
bridge's own documents (purpose:\"bridge\", batteries, the self-test).",
);
if let Value::Object(m) = &mut out {
m.insert("userPresence".into(), a.to_json());
m.insert("changedNothing".into(), json!(true));
m.insert("waitedSeconds".into(), json!(waited));
}
crate::ledger::record("user_present_refusal", json!({"verb": verb, "kind": "creation", "waitedSeconds": waited}));
out
}
fn path() -> std::path::PathBuf {
crate::scratch::root().join("presence.json")
}
fn key(pid: u32, created: Option<u64>) -> String {
format!("{pid}:{}", created.map(|c| c.to_string()).unwrap_or_else(|| "?".into()))
}
fn load() -> serde_json::Map<String, Value> {
std::fs::read_to_string(path())
.ok()
.and_then(|t| serde_json::from_str::<Value>(&t).ok())
.and_then(|v| v.as_object().cloned())
.unwrap_or_default()
}
fn save(mut m: serde_json::Map<String, Value>) {
while m.len() > 20 {
let oldest = m
.iter()
.min_by_key(|(_, v)| v.get("atMs").and_then(|a| a.as_u64()).unwrap_or(0))
.map(|(k, _)| k.clone());
match oldest {
Some(o) => {
m.remove(&o);
}
None => break,
}
}
let _ = crate::launch::write_atomic(&path(), &Value::Object(m).to_string());
}
fn entry(k: &str) -> Value {
load().get(k).cloned().unwrap_or_else(|| json!({}))
}
fn put(k: &str, v: Value) {
let mut m = load();
let mut v = v;
if let Value::Object(o) = &mut v {
o.insert("atMs".into(), json!(crate::launchos::epoch_ms() as u64));
}
m.insert(k.to_string(), v);
save(m);
}
/// The running session: (pid, creation time, documents, truncated). `None`
/// when SOLIDWORKS is not running or the host cannot say.
struct Session {
pid: u32,
created: Option<u64>,
docs: Vec<String>,
count: Option<u64>,
titles: std::collections::HashMap<String, String>,
}
fn doc_key(d: &Value) -> String {
let p = d.get("path").and_then(|p| p.as_str()).unwrap_or("");
if !p.is_empty() {
p.replace('/', "\\").to_lowercase()
} else {
format!("title:{}", d.get("title").and_then(|t| t.as_str()).unwrap_or("").to_lowercase())
}
}
fn session(ctx: &crate::verbs::Ctx) -> Option<Session> {
let st = ctx.call("app_state", json!({}), 30).ok()?;
if st.get("running").and_then(|v| v.as_bool()) == Some(false) {
return None;
}
let pid = st.get("processId").and_then(|v| v.as_u64()).map(|p| p as u32)?;
let created = crate::launchos::os()
.processes()
.ok()
.and_then(|ps| ps.iter().find(|p| p.pid == pid).and_then(|p| p.created_at))
.or_else(|| ctx.call("startup_state", json!({}), 20).ok().and_then(|s| s.get("processCreatedAt").and_then(|v| v.as_u64())));
let truncated = st.get("documentsTruncated").and_then(|v| v.as_bool()).unwrap_or(false);
let list: Vec<Value> = st.get("documents").and_then(|d| d.as_array()).cloned().unwrap_or_default();
let mut titles = std::collections::HashMap::new();
let docs: Vec<String> = if truncated {
Vec::new()
} else {
list.iter()
.map(|d| {
let k = doc_key(d);
titles.insert(k.clone(), d.get("title").and_then(|t| t.as_str()).unwrap_or("").to_string());
k
})
.collect()
};
let count = st
.get("documentCount")
.and_then(|v| v.as_u64())
.or_else(|| (!truncated).then_some(docs.len() as u64));
Some(Session { pid, created, docs, count, titles })
}
/// The open document set before a bridge verb runs — for
/// `note_bridge_changed`. `None` when it cannot be listed.
pub fn snapshot(ctx: &crate::verbs::Ctx) -> Option<(u32, Option<u64>, Vec<String>)> {
let s = session(ctx)?;
if s.docs.is_empty() && s.count.unwrap_or(0) > 0 {
return None; // truncated: unlistable, not empty
}
Some((s.pid, s.created, s.docs))
}
/// WHAT THE VERB ITSELF CHANGED goes into the baseline — the documents open
/// after it that were not open before it — and documents no longer open leave
/// it. Never a re-read of the whole open set (adversary pass 46, B3): a
/// document the user opened BETWEEN bridge verbs stays the user's. (One it
/// opens DURING a bridge verb cannot be told apart — the stated limit.)
pub fn note_bridge_changed(ctx: &crate::verbs::Ctx, before: Option<(u32, Option<u64>, Vec<String>)>) {
let Some(after) = session(ctx) else { return };
let k = key(after.pid, after.created);
let mut e = entry(&k);
let mut docs: Vec<String> = e
.get("bridgeDocs")
.and_then(|v| v.as_array())
.map(|a| a.iter().filter_map(|x| x.as_str().map(String::from)).collect())
.unwrap_or_default();
let truncated = after.docs.is_empty() && after.count.unwrap_or(0) > 0;
if !truncated {
docs.retain(|d| after.docs.contains(d));
if let Some((pid, created, before_docs)) = before {
if pid == after.pid && created == after.created {
for d in &after.docs {
if !before_docs.contains(d) && !docs.contains(d) {
docs.push(d.clone());
}
}
}
}
}
if let Value::Object(o) = &mut e {
o.insert("bridgeDocs".into(), json!(docs));
}
put(&k, e);
}
/// The running session's taken-over record, if it has one — for the reply of a
/// verb that adds a document to it (allowed: it adds, never removes).
pub fn taken_over_now(ctx: &crate::verbs::Ctx) -> Option<Value> {
let st = ctx.call("startup_state", json!({}), 20).ok()?;
let pid = st.get("processId").and_then(|v| v.as_u64()).map(|p| p as u32)?;
let created = crate::launchos::os()
.processes()
.ok()
.and_then(|ps| ps.iter().find(|p| p.pid == pid).and_then(|p| p.created_at))
.or_else(|| st.get("processCreatedAt").and_then(|v| v.as_u64()));
taken_over(pid, created).map(|e| {
json!({"takenOverByUser": true, "evidence": e.get("evidence").cloned().unwrap_or(Value::Null),
"note": "the user has taken over this session; documents may be added, and nothing \
will be closed or quit without the user-present override"})
})
}
/// Is this session marked as taken over by the user?
pub fn taken_over(pid: u32, created: Option<u64>) -> Option<Value> {
let e = entry(&key(pid, created));
(e.get("takenOverByUser").and_then(|v| v.as_bool()) == Some(true)).then_some(e)
}
fn mark_taken_over(pid: u32, created: Option<u64>, evidence: &Value) {
let k = key(pid, created);
let mut e = entry(&k);
if e.get("takenOverByUser").and_then(|v| v.as_bool()) == Some(true) {
return;
}
if let Value::Object(o) = &mut e {
o.insert("takenOverByUser".into(), json!(true));
o.insert("takenOverAtMs".into(), json!(crate::launchos::epoch_ms() as u64));
o.insert("evidence".into(), evidence.clone());
}
put(&k, e);
crate::ledger::record("session_taken_over_by_user", json!({"pid": pid, "createdAt": created, "evidence": evidence}));
}
/// PRESENCE WITHOUT COM (0.9.11, L4): for swx_user_presence while SOLIDWORKS
/// has not finished starting (or COM cannot answer) — the foreground window and
/// the last input are measured exactly as always, from the process census; the
/// DOCUMENT part is unknown, and unknown counts as present.
pub fn measure_without_com() -> Option<(bool, Value)> {
let os = crate::launchos::os();
let census = os.processes().ok()?;
let sw: Vec<&crate::launchos::ProcInfo> =
census.iter().filter(|p| p.image.eq_ignore_ascii_case("SLDWORKS.exe")).collect();
let [one] = sw.as_slice() else { return None };
let (pid, created) = (one.pid, one.created_at);
let children: Vec<u32> = census.iter().filter(|p| p.ppid == Some(pid)).map(|p| p.pid).collect();
let fg = os.foreground_window();
let input = os.seconds_since_last_input().ok();
let (fg_json, fg_is_sw) = match &fg {
Ok(Some(w)) => (
json!({"hwnd": w.hwnd, "pid": w.pid, "title": w.title, "className": w.class_name}),
w.pid == pid || children.contains(&w.pid),
),
Ok(None) => (json!({"unreadable": "no window has the foreground"}), false),
Err(e) => (json!({"unreadable": e}), false),
};
let at_keyboard = fg_is_sw && input.map(|n| n < idle_secs()).unwrap_or(false);
let taken = taken_over(pid, created).is_some();
let evidence = json!({
"present": true,
"foregroundWindow": fg_json,
"foregroundIsSolidworks": fg_is_sw,
"secondsSinceLastInput": input,
"idleThresholdSecs": idle_secs(),
"foreignDocuments": Value::Null,
"documentsUnknown": true,
"takenOverByUser": taken || at_keyboard,
"takenOverEarlier": taken,
"processId": pid,
"processCreatedAt": created,
"basis": if at_keyboard {
"SOLIDWORKS is the foreground window and the user gave input recently"
} else {
"SOLIDWORKS cannot be asked about its documents right now (it has not finished starting, \
or COM cannot answer), so whether the user has documents open is UNKNOWN — which counts \
as present"
},
});
if at_keyboard {
mark_taken_over(pid, created, &evidence);
}
Some((true, evidence))
}
/// MEASURE PRESENCE. `(present, evidence)`.
pub fn measure(ctx: &crate::verbs::Ctx) -> Option<(bool, Value)> {
let s = session(ctx)?;
let os = crate::launchos::os();
let k = key(s.pid, s.created);
let e = entry(&k);
let taken = e.get("takenOverByUser").and_then(|v| v.as_bool()) == Some(true);
// FOREGROUND: the session's process, or one of its children (a sign-in
// web view is a child process).
let census = os.processes().ok();
let children: Vec<u32> = census
.as_ref()
.map(|c| c.iter().filter(|p| p.ppid == Some(s.pid)).map(|p| p.pid).collect())
.unwrap_or_default();
let fg = os.foreground_window();
let input = os.seconds_since_last_input();
let simulator_without_scenario = crate::is_mock() && std::env::var(crate::launchos::ENV_FAKE_DIR).map(|d| d.is_empty()).unwrap_or(true);
let (fg_json, fg_is_sw, fg_unreadable) = match &fg {
Ok(Some(w)) => (
json!({"hwnd": w.hwnd, "pid": w.pid, "title": w.title, "className": w.class_name}),
w.pid == s.pid || children.contains(&w.pid),
false,
),
// NO FOREGROUND WINDOW is not "not SOLIDWORKS": Windows returns NULL on
// the secure desktop (a UAC prompt, the lock screen) and in a service or
// disconnected session, where the last-input time is stale too. It is
// UNREADABLE, which counts as present (adversary pass 46, B4).
Ok(None) => (json!({"unreadable": "no window has the foreground (secure desktop, lock screen, or a disconnected session)"}), false, true),
Err(e) => (json!({"unreadable": e}), false, true),
};
let (input_secs, input_unreadable) = match &input {
Ok(n) => (Some(*n), false),
Err(_) => (None, true),
};
// A simulated SOLIDWORKS with no presence scenario is on nobody's screen.
let (fg_unreadable, input_unreadable) = if simulator_without_scenario {
(false, false)
} else {
(fg_unreadable, input_unreadable)
};
let at_keyboard = fg_is_sw && input_secs.map(|n| n < idle_secs()).unwrap_or(false);
// FOREIGN DOCUMENTS: open now and NOT put there by the bridge. The
// baseline starts empty and holds only what bridge verbs opened or created.
let bridge_docs: Vec<String> = e
.get("bridgeDocs")
.and_then(|v| v.as_array())
.map(|a| a.iter().filter_map(|x| x.as_str().map(String::from)).collect())
.unwrap_or_default();
let truncated = s.docs.is_empty() && s.count.unwrap_or(0) > 0;
let foreign: Vec<String> = s.docs.iter().filter(|d| !bridge_docs.contains(d)).cloned().collect();
let foreign_titles: Vec<String> =
foreign.iter().map(|k| s.titles.get(k).cloned().unwrap_or_else(|| k.clone())).collect();
// A simulated SOLIDWORKS with no presence scenario has no user and no
// user's documents (mock only — LiveOs never takes this path).
let foreign_seen = !simulator_without_scenario
&& (!foreign.is_empty() || (truncated && s.count.unwrap_or(0) > bridge_docs.len() as u64));
let unreadable = fg_unreadable || input_unreadable;
let positive = at_keyboard || foreign_seen;
let present = positive || unreadable || taken;
let evidence = json!({
"present": present,
"foregroundWindow": fg_json,
"foregroundIsSolidworks": fg_is_sw,
"secondsSinceLastInput": input_secs,
"idleThresholdSecs": idle_secs(),
"foreignDocuments": {"count": if simulator_without_scenario { 0 } else { foreign.len() },
"titles": if simulator_without_scenario { json!([]) } else { json!(foreign_titles) },
"listTruncated": truncated,
"rule": "a document is the bridge's only if a bridge verb opened or created it; every other open document is the user's"},
"takenOverByUser": taken || positive,
"takenOverEarlier": taken,
"measurementUnreadable": unreadable,
"processId": s.pid,
"processCreatedAt": s.created,
"basis": if taken {
"this session was taken over by the user earlier; the bridge does not end it without the override"
} else if at_keyboard {
"SOLIDWORKS is the foreground window and the user gave input recently"
} else if foreign_seen {
"documents are open that this bridge did not open or create (in a session the user launched, every document is theirs)"
} else if unreadable {
"presence could not be measured, which counts as present"
} else {
"no sign of the user: SOLIDWORKS is not in the foreground with recent input, and no document appeared that the bridge did not open"
},
});
if positive {
mark_taken_over(s.pid, s.created, &evidence);
}
Some((present, evidence))
}
/// THE GUARD, for a verb that ends work. `Ok(evidence)` lets it run (absent,
/// not running, or overridden); `Err(refusal)` is `user_is_active`.
pub fn guard(ctx: &crate::verbs::Ctx, verb: &str, args: &Value) -> Result<Value, Value> {
let Some((present, evidence)) = measure(ctx) else {
return Ok(json!({"present": Value::Null, "why": "no running SOLIDWORKS session to measure"}));
};
if !present {
return Ok(evidence);
}
if overridden(args) {
let who = crate::caller::current();
crate::ledger::record(
"user_present_override",
json!({
"verb": verb,
"reason": args.get("reason"),
"evidence": evidence,
// WHO passed it (self-asserted, like the reason — recorded, never
// used to authorize).
"caller": who.as_ref().map(|c| json!({
"aiThread": c.thread,
"containerName": c.container,
"source": args.pointer("/caller/source").cloned().unwrap_or(Value::Null),
"callerReason": c.reason,
})),
}),
);
let mut e = evidence;
if let Value::Object(o) = &mut e {
o.insert("overridden".into(), json!(true));
}
return Ok(e);
}
let mut out = crate::verbs::err_code(
"user_is_active",
format!(
"{verb} refused: the user is working in this SOLIDWORKS ({}). The session is now the \
user's — the bridge does not end it or sweep it.",
evidence.get("basis").and_then(|b| b.as_str()).unwrap_or("present")
),
"Nothing was closed, saved or quit. This session belongs to the person using it: leave \
it alone. (A document counts as the bridge's only if a bridge verb opened or created it; \
in a session the user launched, every document is the user's, so quitting or sweeping it \
always needs their explicit say-so.) Only if the user has explicitly asked for it to be ended, pass \
userPresentOverride:true together with userAuthorized:true and their reason — a sweep \
under the override still preserves every document first.",
);
if let Value::Object(m) = &mut out {
m.insert("userPresence".into(), evidence);
}
crate::ledger::record("user_present_refusal", json!({"verb": verb}));
Err(out)
}