app
SOLIDWORKS Bridge
Public Made by Adomby adom
Launch, model and quit SOLIDWORKS from any Adom container. Nothing is ever lost.
main
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657
//! EVERY GUARD HAS A PRODUCTION CALLER.
//!
//! Five guards in this project have shipped unit-tested and unwired — three of
//! them about attribution, the last one (`identity_matches_attribution`, 0.6.7)
//! written into a verb by an edit that failed silently and was never verified.
//! Each time the tests were green, the reviews were green, and the guard did
//! nothing at all.
//!
//! A unit test proves a FUNCTION behaves. It says nothing about whether anything
//! calls it. This file is the difference, mechanically: for each guard below,
//! there must be at least one call from code that is not a test.
//!
//! It is deliberately crude — a text scan, not a call graph — because the failure
//! it catches is crude: nobody called the thing. A guard that moves lives behind
//! `#[cfg(windows)]` or a macro will need its entry updated, and that is the
//! correct amount of friction for deleting a guard's only caller.
//!
//! WHAT THIS DOES NOT PROVE, stated plainly so nobody reads more into a green
//! run than is there:
//!
//! * it proves a CALL EXISTS in a decision-making file. It does not prove the
//! call is REACHABLE — a guard behind `if false`, or in a branch no input can
//! select, passes this and does nothing. Only a verb-level test with the call
//! site mutated away proves reachability, and every guard here should have
//! one;
//! * it does not prove the call is in the RIGHT PLACE. `identity_matches_attribution`
//! called once at a front gate passes this file, and the per-rung re-sweep it
//! also belongs in could be deleted without a word from here (that one is
//! pinned by `quit::the_session_changing_between_rungs_is_caught_by_the_per_rung_sweep`);
//! * and it cannot see a guard whose call site is correct and whose ARGUMENTS
//! are wrong.
//!
//! It catches exactly one thing: a guard nothing calls. That has happened five
//! times here, so it is worth a file.
use std::path::Path;
/// THE FILES A GUARD MUST BE CALLED FROM to count as wired.
///
/// "Has a caller somewhere in src/" is weaker than it looks: a guard called only
/// by another guard, or only by a helper nothing reaches, satisfies it while
/// doing nothing. These are the files where the bridge actually decides
/// something — the verb layer, the two backends' command dispatch, the write
/// gate, and the launch machine.
///
/// `comhost/mock.rs` is deliberately NOT here. It ships in the binary, but a
/// guard whose only caller is the simulator is a guard that never runs on a real
/// machine, which is precisely the state this file exists to detect.
const ENTRY_POINTS: &[&str] = &[
"src/verbs.rs",
"src/verbs3.rs",
"src/writes.rs",
"src/launch.rs",
"src/comhost/com.rs",
"src/comhost/host.rs",
// Startup decides where the bridge's data lives and moves it there.
"src/server.rs",
];
/// `(function name, why it exists)` — the why is printed when it has no caller,
/// so the person reading the failure knows what stopped guarding.
const GUARDS: &[(&str, &str)] = &[
("identity_matches_attribution",
"an attribution record must not authorise ending a DIFFERENT session"),
("title_resolves_uniquely",
"a pathless document may only be closed by title when that title names exactly one"),
("is_import_temp_under_any",
"the resolved %TEMP% is the whole location proof for a close that does not prompt"),
("has_import_temp_tail",
"a drain that matched nothing must not report that it drained everything"),
("destination_state",
"a destination this bridge cannot stat is NOT free"),
("reattribute_after_attach",
"a session that came up after a sign-in is still ours"),
("descends_from_our_chain",
"lineage is the only proof that a running SOLIDWORKS came from our launch"),
("hwnd_still_owned",
"a recycled window handle must never be sent a close"),
("dialog_census_pids",
"a 3DEXPERIENCE sign-in lives in a CEF child, not in sldworks.exe"),
("check_preconditions",
"the quit ladder's seven preconditions, re-measured before every rung"),
("process_gone",
"a quit is proved by the census, never by what ExitApp returned"),
("liveness_verdict_for",
"busy and dead are different answers and one reading never says dead"),
("is_sign_in_window",
"a sign-in wait is not a stall, and the chain must not be killed during one"),
("may_terminate",
"the allowlist: nothing outside it is ever terminated"),
("attributed_chain",
"no handle, no kill — the chain comes from the invoke or from nothing"),
("blocking_dialog_now",
"the safe-close sweep stops at a dialog and never answers one"),
("unique_recovery_path",
"two open documents sharing a title must not share a recovery file"),
("recovery_dir",
"every sweep preserves into its own folder, so nothing overwrites a previous rescue"),
("dissolve_refusal",
"an unmeasured link-breaking API is refused on EVERY route, never silently dropped"),
("register_created",
"a document this bridge opened is owned at birth — the drain has no other key when it is pathless"),
("unknown_kind_floor",
"a document whose kind nobody could establish takes the STRICTEST floor"),
("write_manifest",
"the sweep records what it decided beside the files it wrote"),
("reverified_entry",
"a folder with no manifest is re-checked and reported as weaker evidence, not as the same"),
("how_to_open",
"a file the sweep refused is never offered to the user as recovered work"),
("with_data",
"a COM failure reaches the caller structured, not flattened into prose"),
("host_failure_entry",
"a host failure inside the sweep keeps its structure — it becomes a permanent manifest record"),
("recovery_file_verdict",
"a document is closed only against a file proved to BE a SOLIDWORKS document"),
("free_bytes",
"a sweep that cannot fit on the volume must not start writing"),
("from_type_name",
"the document's KIND comes from the backend, never re-derived from the name it produced"),
("estimate_writes_typed",
"how much this sweep is about to write, by document KIND, before it writes any of it"),
("refuse_inside_install",
"a recovery folder is never placed where an update deletes it (SEV-1, 2026-09-30)"),
("migrate_legacy",
"data an older bridge left in ab's install cache is copied out before anything reads the data root"),
("legacy_roots",
"the listing and the migration still read every location older bridges wrote to"),
("readiness::gate",
"no document command reaches a SOLIDWORKS that has not finished starting (crash measured 2026-10-01)"),
("presence::guard",
"no quit or sweep ends a session the user is working in (measured 2026-10-01: Drew took over a bridge-launched session)"),
("presence::measure",
"priming re-measures the user's presence before it creates and closes a document"),
("is_startup_exempt",
"every verb not on the exemption list waits for SOLIDWORKS to finish starting (adversary pass 42)"),
("stop_window_rungs_during_startup",
"an authorized quit reaches the window-message rungs during startup, with the COM rungs skipped"),
("prime_session",
"a declined quit on a never-had-a-document session is primed only when empty and authorized"),
("solidworks_gone",
"a sweep during which SOLIDWORKS dies says host_app_gone, never dialog_census_unknown"),
("root_is_temp_fallback",
"the only copy of unsaved work never goes to a directory the OS cleans on a schedule"),
("closed_in_place_entry",
"a document closed at its own path is listed, with whether that file is still untouched"),
];
/// Source files that are PRODUCTION code. A call from inside a `mod tests` block
/// does not count, so those blocks are stripped before scanning.
fn production_sources() -> Vec<(String, String)> {
let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
let mut out = Vec::new();
let mut stack = vec![root];
while let Some(dir) = stack.pop() {
let Ok(entries) = std::fs::read_dir(&dir) else { continue };
for e in entries.flatten() {
let p = e.path();
if p.is_dir() {
stack.push(p);
} else if p.extension().map(|x| x == "rs").unwrap_or(false) {
if let Ok(text) = std::fs::read_to_string(&p) {
out.push((p.to_string_lossy().to_string(), strip_test_modules(&text)));
}
}
}
}
out
}
/// Remove `#[cfg(test)] mod tests { … }` by brace counting. Crude and adequate:
/// the alternative is parsing Rust, and the thing being detected is "nobody calls
/// this at all".
fn strip_test_modules(text: &str) -> String {
let mut out = String::with_capacity(text.len());
let mut rest = text;
while let Some(i) = rest.find("#[cfg(test)]") {
out.push_str(&rest[..i]);
let after = &rest[i..];
let Some(brace) = after.find('{') else { break };
let mut depth = 0i32;
let mut end = None;
for (j, c) in after[brace..].char_indices() {
match c {
'{' => depth += 1,
'}' => {
depth -= 1;
if depth == 0 {
end = Some(brace + j + 1);
break;
}
}
_ => {}
}
}
match end {
Some(e) => rest = &after[e..],
None => break,
}
}
out.push_str(rest);
out
}
#[test]
fn every_guard_has_a_production_caller() {
let sources = production_sources();
assert!(sources.len() > 5, "the source scan found almost nothing: {}", sources.len());
let mut unwired: Vec<String> = Vec::new();
for (name, why) in GUARDS {
let needle = format!("{name}(");
let mut callers: Vec<String> = Vec::new();
for (file, text) in &sources {
for line in text.lines() {
let t = line.trim_start();
// The DEFINITION is not a call, and neither is a doc comment.
if t.starts_with("//") || t.starts_with("fn ") || t.starts_with("pub fn ")
|| t.starts_with("pub(crate) fn ") || t.starts_with("async fn ")
{
continue;
}
if line.contains(&needle) {
callers.push(file.clone());
break;
}
}
}
// ...and the caller must be somewhere the bridge DECIDES something.
let entry_callers: Vec<&String> = callers
.iter()
.filter(|f| {
let norm = f.replace('\\', "/");
ENTRY_POINTS.iter().any(|e| norm.ends_with(e))
})
.collect();
if entry_callers.is_empty() {
unwired.push(format!(
" {name} — {why}\n (callers found: {})",
if callers.is_empty() {
"none at all".to_string()
} else {
format!("{callers:?}, none of them an entry point")
}
));
}
}
assert!(
unwired.is_empty(),
"these guards have NO production caller. Each is unit-tested and does nothing:\n{}\n\n\
A unit test proves a function behaves; it says nothing about whether anything calls it. \
Five guards in this project have shipped this way. If one of these was deliberately \
retired, remove it from GUARDS in this file — deleting a guard should be a decision, not \
a silent edit.",
unwired.join("\n")
);
}
/// THE RECOVERY FILE MUST BE VERIFIED WHERE THE DECISION IS MADE — in BOTH
/// backends, through the SAME function.
///
/// `recovery_file_verdict` is the whole premise for every close in the sweep: a
/// file that exists, or even one that is large, is not necessarily the document.
/// The rule was checkable in the simulator and could have quietly gone missing
/// from the real backend — the exact shape of the five unwired guards this
/// project has already shipped. A backend with its own private version of the
/// check is the same defect wearing a disguise, so this looks for the shared
/// function by name.
#[test]
fn the_recovery_file_is_verified_in_both_backends() {
let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("src/comhost");
for file in ["com.rs", "mock.rs"] {
let text = strip_test_modules(
&std::fs::read_to_string(root.join(file)).expect("comhost source"),
);
let applied = text.lines().any(|l| {
let t = l.trim_start();
!t.starts_with("//") && l.contains("recovery_file_verdict(")
});
assert!(
applied,
"{file} never calls recovery_file_verdict.\n\n\
The backend decides there whether a document may be closed. Without the \
shared verdict the decision falls back to `exists()` or to a private size \
test — and a document whose only copy was in memory gets closed on the \
strength of an empty, truncated, or foreign file. If this rule genuinely \
moved, move this test with it."
);
}
}
/// AND IT MUST BE ASKED AGAIN AT THE MOMENT OF THE CLOSE.
///
/// A file verified at save time and closed against several seconds later is a
/// window, and the disk can fill inside it. Both backends re-verify in the close
/// phase for any document this sweep WROTE.
#[test]
fn the_close_phase_re_verifies_what_the_sweep_wrote() {
let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("src/comhost");
for file in ["com.rs", "mock.rs"] {
let text = strip_test_modules(
&std::fs::read_to_string(root.join(file)).expect("comhost source"),
);
assert!(
text.contains("written_here") && text.matches("recovery_file_verdict(").count() >= 2,
"{file} verifies the recovery file once, at save time. The close happens \
later, and the disk can fill in between — re-verify in the close phase for \
any document this sweep wrote."
);
}
}
/// THE KIND MUST NOT BE READ BACK OFF THE NAME IT PRODUCED.
///
/// **THIS IS DOCUMENTATION, NOT A GUARD.** It greps for the SHAPE of the pass-26
/// mistake, and a one-line alias — or a variable holding the path under another
/// name — walks straight past it. It is here so that someone editing the naming
/// code meets the story before they reintroduce the bug, not because it can stop
/// them. The guards are the behavioural tests: `a_document_nothing_can_classify_
/// is_never_called_a_part`, `an_unreadable_type_falls_back_to_the_documents_own_
/// path`, and adversary25's assembly-held-to-the-part-floor case. If this test
/// and those ever disagree, believe those.
///
/// It has already produced one false positive, in 0.7.6, when the naming chain
/// legitimately began consulting the document's own `GetPathName`. That is
/// evidence about the document, not a name this sweep produced — but the
/// identifier is spelled `path` either way.
///
/// The recovery file's extension is CHOSEN FROM the document's type, so
/// classifying the file by that extension is asking the question to its own
/// answer — one unreadable `GetType` named a truncated assembly `.SLDPRT` and the
/// part floor then passed it. The kind must travel with the document. This looks
/// for the shape of the mistake rather than waiting for it to be re-introduced by
/// someone who did not read the story.
#[test]
fn no_backend_classifies_a_recovery_file_by_the_name_it_chose() {
let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
for file in ["comhost/com.rs", "comhost/mock.rs", "comhost/host.rs"] {
let text = strip_test_modules(
&std::fs::read_to_string(root.join(file)).expect("source"),
);
for (i, line) in text.lines().enumerate() {
let t = line.trim_start();
if t.starts_with("//") {
continue;
}
let derives = line.contains("from_hint(") || line.contains("from_extension(");
// ONLY THE NAMES THIS SWEEP UNAMBIGUOUSLY CHOSE. `path` is
// deliberately NOT in this list: in the preserve phase it is
// `GetPathName` — the document's own file, legitimate evidence — and
// in the close phase it is the recovery key. One identifier, two
// provenances, and no grep can tell them apart. That ambiguity is the
// clearest statement of why this test is documentation and the
// behavioural tests are the guard.
let from_a_chosen_name = line.contains("&out")
|| line.contains("now_path")
|| line.contains("recoveryPath");
assert!(
!(derives && from_a_chosen_name),
"{file}:{} derives a document's KIND from a path this sweep chose:\n {}\n\n\
The recovery file's extension comes FROM the kind, so reading the kind back out \
of it cannot check anything — an unreadable GetType names the file .SLDPRT and \
the part floor then accepts a truncated assembly. Carry the kind with the \
document instead. (Classifying a TITLE the user wrote is fine; classifying a \
name this code produced is not.)",
i + 1,
line.trim()
);
}
}
}
/// "NOTHING" AND "OMITTED" MUST NOT BE CONFUSED AGAIN.
///
/// **This is documentation, not a guard** — the same caveat as the grep pin
/// above, and for the same reason: a local alias walks past it. The real guards
/// are the Windows-only unit test in `variant.rs`, which inspects the VARIANT
/// itself, and a live import.
///
/// It is here because this bridge passed `VT_EMPTY` for `LoadFile4`'s
/// `importData` from v0.2.0 until 2026-09-17 and nobody could see it: the COM
/// failure carrying `Type mismatch. (0x80020005) (argument index 1)` was being
/// dropped one frame above where it was made. `LoadFile4` had probably never
/// worked live. The simulator cannot catch this — it has no VARIANTs — so the
/// only thing standing between the next reader and the same mistake is that they
/// meet the story.
#[test]
fn an_omitted_object_argument_is_spelled_nothing_not_empty() {
let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("src/comhost");
let com = strip_test_modules(&std::fs::read_to_string(root.join("com.rs")).expect("com.rs"));
// LoadFile4's importData.
let load4 = com
.split("\"LoadFile4\",")
.nth(1)
.and_then(|rest| rest.split(");").next())
.unwrap_or("")
.to_string();
assert!(
!load4.is_empty(),
"the LoadFile4 call site could not be found — if it moved, move this test with it"
);
// THE ARGUMENT MAY BE BUILT ABOVE THE CALL — 0.8.1 does exactly that, so the
// matrix can pass a real ImportFileData object instead — so look at the
// construction as well as the call site. (A grep that only understood one
// spelling produced its second false positive here; that is what this test
// being documentation rather than a guard means in practice.)
let data_arg_default = com.contains("None => Var::null_dispatch(),");
assert!(
load4.contains("null_dispatch()") || data_arg_default,
"LoadFile4's importData is an [in] OBJECT parameter, and this bridge must pass Nothing \\
(VT_DISPATCH with a null pdispVal), not VT_EMPTY. Live 2026-09-17, VT_EMPTY answered \\
`Type mismatch. (0x80020005) (argument index 1)` — with rgvarg packed reversed, that is \\
this argument exactly. Call site:\\n{load4}"
);
assert!(
!load4.contains("Var::empty()") && !com.contains("None => Var::empty(),"),
"LoadFile4 is passing an EMPTY variant again. Nothing and omitted are different things to \\
IDispatch, and this one cost every import for as long as the bridge has existed:\\n{load4}"
);
// And the escape hatch's `null` literal, whose documented use — SelectByID2's
// callout — is an [in] object parameter of exactly the same kind.
assert!(
com.contains("Literal::Null => Var::null_dispatch()"),
"swx_com_eval's `null` literal must also be a null DISPATCH: its documented use is \\
SelectByID2's callout argument, an [in] object parameter like LoadFile4's importData."
);
}
/// THE IMPORT DIAGNOSTICS DIALOG IS NEVER ANSWERED, and that is a classification
/// fact rather than a policy comment.
///
/// `swImportAutoRunImportDiagnostics` is TRUE on this install, so a STEP with
/// faulty faces raises this MODAL dialog during an import. The choice it offers —
/// attempt to heal the faces, or not — changes the geometry the user ends up
/// with, so it belongs to them. A mutation flipping this kind from never-answer
/// to answerable broke nothing before this test existed.
#[test]
fn the_import_diagnostics_dialog_is_classified_and_never_answered() {
for title in [
"Import Diagnostics",
"SOLIDWORKS Import Diagnostics",
"import diagnostics",
] {
let (kind, blocking, never_answer) = swx_bridge::abwin::classify(title);
assert_eq!(kind, "import_diagnostics", "{title:?} must be recognised, not `unknown`");
assert!(blocking, "{title:?} is modal — an import waits behind it");
assert!(
never_answer,
"{title:?} must be NEVER-ANSWERED. Its buttons decide whether SOLIDWORKS alters the \
geometry of the user's imported body; that is not a decision a bridge makes on \
somebody's behalf, and the import reports `stoppedByDialog` so a person can."
);
}
}
/// THE GATES' FORMAT STRINGS MUST BE VALID, because a gate that crashes on its
/// own message is worse than a gate that fails.
///
/// Live, 2026-09-17: `NOTES.append("%TEMP%\\swx-drain-gate still holds files …" % …)`
/// raised `ValueError: unsupported format character 'T'` at the LAST line of a
/// run whose three real claims had all passed. It crashed before the verdict was
/// printed and before cleanup ran, so the gate reported failure and left its own
/// scratch directory behind — in a message about leaving a directory behind.
///
/// THIS USES PYTHON'S OWN PARSER, and the first version of this test did not.
/// A line-based grep missed the actual bug, because the `%` operator sits on the
/// NEXT line of an implicitly concatenated literal — it passed with the defect
/// restored, which makes it worse than nothing. Anything that checks somebody
/// else's language has to use that language's parser.
///
/// WHAT THIS DOES NOT COVER: unbound locals. The same gate also crashed on a name
/// assigned in one branch and read in another (`twin_name`, 0.8.4), which is the
/// same shape of failure — a crash in cleanup after every real claim passed. A
/// heuristic for that flagged `try`/`except` bindings and both-branch assignments
/// as well, and a test that cries wolf gets disabled, so it is not shipped here.
/// `pyflakes` does this correctly and is not installed in this container; the
/// standing practice instead is to give every cross-branch variable a value
/// before the branch.
#[test]
fn every_gate_format_string_is_valid() {
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
let checker = r#"
import ast, re, sys, pathlib
BAD = re.compile(r'%(?![-#0 +]*\d*(?:\.\d+)?[hlL]?[diouxXeEfFgGcrsa%])')
bad = []
n = 0
for f in sorted(pathlib.Path(sys.argv[1]).glob('*.py')):
tree = ast.parse(f.read_text())
for node in ast.walk(tree):
if (isinstance(node, ast.BinOp) and isinstance(node.op, ast.Mod)
and isinstance(node.left, ast.Constant) and isinstance(node.left.value, str)):
n += 1
m = BAD.search(node.left.value)
if m:
bad.append('%s:%d: %%%s is not a conversion -- %r'
% (f.name, node.lineno, node.left.value[m.end():m.end()+1],
node.left.value[max(0, m.start()-30):m.start()+40]))
print('CHECKED %d' % n)
for b in bad:
print('BAD ' + b)
"#;
let script = root.join("target").join("gate-format-check.py");
let _ = std::fs::create_dir_all(root.join("target"));
if std::fs::write(&script, checker).is_err() {
return;
}
let out = match std::process::Command::new("python3")
.arg(&script)
.arg(root.join("tools"))
.output()
{
Ok(o) => o,
// No python3 here: the gates are Python, so this check belongs wherever
// Python is. Silence beats a false pass, but a missing interpreter is
// not a defect in the gates.
Err(_) => return,
};
let text = String::from_utf8_lossy(&out.stdout).to_string();
let checked: usize = text
.lines()
.find_map(|l| l.strip_prefix("CHECKED "))
.and_then(|n| n.trim().parse().ok())
.unwrap_or(0);
assert!(
checked > 0,
"no %-format expressions were found in tools/ — the checker did not run:\n{}{}",
text,
String::from_utf8_lossy(&out.stderr)
);
let bad: Vec<&str> = text.lines().filter(|l| l.starts_with("BAD ")).collect();
assert!(
bad.is_empty(),
"a gate %-formats a string containing a literal `%` that is not a conversion. Python \
raises ValueError the moment that branch runs — which is how a gate crashed at its last \
line, after every real claim had passed, before printing its verdict and before cleaning \
up after itself. Use an f-string, or write `%%`:\n {}",
bad.join("\n ")
);
}
/// DOES THIS SENTENCE ASK THE USER TO CLOSE DOCUMENTS?
///
/// One rule, evaluated — not a pile of phrases that happen to make this
/// project's own prose pass. Exemptions are for CATEGORIES of thing that are
/// genuinely the user's (answering a dialog, restarting their session, looking
/// at their screen), never for the text of a sentence somebody wrote.
/// WHICH FILES DESCRIBE THE RULE, AND WHICH ARE GOVERNED BY IT.
///
/// THE DEFAULT IS GOVERNED, and it took three tries to get there. 0.8.7 stripped
/// quoted text everywhere so a changelog could record the sentences this rule
/// catches, which blinded SKILL.md. 0.8.8 made it per-file — SKILL.md verbatim,
/// every other markdown stripped — and that still leaked twice: a blockquoted ask
/// in `docs/RUNBOOK.md` slipped, because a runbook INSTRUCTS even though it lives
/// under `docs/`, and `skills/**/SKILL.md` was not scanned at all.
///
/// Both leaks are the same shape: a rule stated as "these files are governed",
/// which is wrong by omission the moment a file is added. So it is INVERTED, the
/// way every other default in this project already runs — an unknown document
/// kind takes the strictest floor, an unreadable census is not permission, an
/// unverifiable destination is not free:
///
/// * EVERY `.md`, `.rs` and `.py` in the repository is scanned, and NOTHING is
/// stripped from it;
/// * a short ALLOWLIST of files that DESCRIBES this bridge rather than driving
/// it — the README and the named reports under `docs/` — has quoted and code
/// spans stripped, because their changelogs and post-mortems quote the very
/// sentences the rule catches;
/// * a NEW FILE IS GOVERNED until somebody adds it to that list on purpose.
fn describes_rather_than_instructs(root: &Path, path: &std::path::Path) -> bool {
let rel = path.strip_prefix(root).unwrap_or(path).to_string_lossy().replace('\\', "/");
// NAMED, not pattern-matched: `docs/` also holds runbooks, and a runbook is
// an instruction that happens to live in a directory called docs.
const DESCRIBES: &[&str] = &[
"README.md",
"docs/QUIT-INVESTIGATION.md",
"docs/LAUNCH-RESILIENCE.md",
"docs/IMPORT-LEAK.md",
"docs/ECOSYSTEM-TABLE.md",
"docs/crate-build-log.md",
];
DESCRIBES.contains(&rel.as_str())
}
/// STRIP THE PARTS OF A MARKDOWN STATEMENT THAT ARE QUOTING, NOT INSTRUCTING.
///
/// A changelog that records *"Ask the user to close the remaining documents"* as
/// a bypass the pin must catch is documentation OF the rule, not a violation of
/// it — and the fix for that is never to add an exemption phrase, nor to edit the
/// changelog into vagueness so a test passes. Example violations belong in
/// quotes, in code spans, and in the `cases` table; the rule reads what is left.
///
/// This is a category (quoted text), not a phrase, which is the distinction the
/// eight deleted exemptions failed.
fn strip_quotations(statement: &str) -> String {
let mut out = String::new();
let mut quoted = false; // inside "…" or “…”
let mut code = false; // inside `…`
for c in statement.chars() {
match c {
'`' => {
code = !code;
continue;
}
'"' => {
quoted = !quoted;
continue;
}
'\u{201C}' => {
quoted = true;
continue;
}
'\u{201D}' => {
quoted = false;
continue;
}
_ => {}
}
if !quoted && !code {
out.push(c);
}
}
out
}
/// DOES THIS STATEMENT ASK THE USER TO CLOSE DOCUMENTS?
///
/// One rule, evaluated — not a pile of phrases that happen to make this project's
/// own prose pass. Exemptions are for CATEGORIES (quoting rather than
/// instructing; a decision that is genuinely the user's), never for the text of a
/// sentence somebody wrote.
fn asks_to_close(sentence: &str) -> bool {
let l = sentence.to_lowercase();
const ASKS: &[&str] = &["ask drew", "ask the user", "ask them", "asks the user",
"asks drew", "asks them"];
const CLOSE_REQUESTS: &[&str] = &[
"to close", "close them", "close the document", "close the remaining",
"close the leftover", "close every", "close it", "close their",
];
// THE PROHIBITION MAY NAME WHAT IT FORBIDS. "it never asks the user to close"
// states the rule; so does "do not ask the user to close".
if l.contains("never ask") || l.contains("do not ask") || l.contains("don't ask")
|| l.contains("despise")
{
return false;
}
let ask_at: Vec<usize> = ASKS
.iter()
.flat_map(|a| l.match_indices(a).map(|(i, m)| i + m.len()).collect::<Vec<_>>())
.collect();
if ask_at.is_empty() {
return false;
}
let close_at: Vec<usize> =
CLOSE_REQUESTS.iter().flat_map(|c| l.match_indices(c).map(|(i, _)| i).collect::<Vec<_>>()).collect();
if close_at.is_empty() {
return false;
}
// ORDER-INDEPENDENT, WITHIN ONE STATEMENT. The ask and the close have to be
// the same request, but English puts them in either order: "ask the user to
// close them" and "Close the documents (ask the user)" are the same
// instruction. Requiring close-after-ask let the second through. What the
// window still excludes is two unrelated clauses far apart — "close the
// bridge's own documents, then ask the user to restart" has no close request
// near the ask at all.
const WINDOW: usize = 70;
for a in &ask_at {
for c in &close_at {
let near = if c >= a { c - a <= WINDOW } else { a - c <= WINDOW };
if near {
return true;
}
}
}
false
}
/// THE RULE ABOVE, PINNED BY EXAMPLES — the way `classify_control` is.
///
/// The pin acquired eight phrase-shaped exemptions so it would pass over this
/// project's own commentary, and two of them were working bypasses: `"passed."`
/// exempted *"The sweep passed. Ask the user to close the remaining documents."*
/// and `"restart"` exempted *"after the restart, ask the user to close the
/// leftover documents"*. Both are planted here as MUST-CATCH cases, beside the
/// legitimate sentences each exemption was actually added for. A future
/// exemption has to keep every row true, which makes it provably safe rather
/// than provably convenient.
#[test]
fn the_never_ask_rule_is_pinned_by_examples() {
// (sentence, must this be flagged?)
let cases: &[(&str, bool)] = &[
// ── THE ADVERSARY'S FIVE, from passes 35 and 36. Four must be caught
// and one must not, and between them they pin both the phrase
// exemptions that were deleted and the DIRECTION the rule used to
// assume.
("The sweep passed. Ask the user to close the remaining documents.", true),
("After the restart, ask the user to close the leftover documents.", true),
("Ask the user to run the sweep, which will close the documents.", true),
// THE MIRROR ORDER: an imperative may lead with its object. Requiring
// close-after-ask let this through, and it is the same instruction.
("Close the documents (ask the user).", true),
// ...while the rule STATING itself, with the same two words in the same
// sentence, must still be allowed.
("The bridge closes; it never asks the user to close.", false),
// ── PLAIN VIOLATIONS.
("Ask Drew to close them himself.", true),
("ask the user to close them in SOLIDWORKS (File > Close)", true),
("A dialog is up, so ask the user to close the documents by hand.", true),
("If that fails, ask the user to close every open document and retry.", true),
// ── LEGITIMATE: the decision really is theirs.
("This bridge never answers one — show the user a screenshot and let them answer.", false),
("A dialog appeared; ask the user to answer it, then run this again.", false),
("ask the user to dismiss the dialog and retry", false),
("close the bridge's own documents, then ASK THE USER to restart", false),
("ask the user to look at their screen", false),
// ── THE RULE NAMING ITSELF.
("NEVER ask the user to close documents: this bridge closes them.", false),
("Do not ask the user to close anything — swx_close_all_safely does it.", false),
// ── DESCRIPTION, NOT A REQUEST.
("if a document of theirs has closed, it was NOT saved by this bridge", false),
// ── UNRELATED.
("Ask the user which configuration they want.", false),
("swx_close_all_safely preserves every document to recovery before closing.", false),
];
for (sentence, expected) in cases {
assert_eq!(
asks_to_close(sentence),
*expected,
"the never-ask rule got this wrong: {sentence:?} (expected flagged={expected})"
);
}
// QUOTING IS NOT INSTRUCTING — and the same sentence unquoted still is.
//
// The changelog entry for pass 35 records both bypass sentences as examples
// of what this rule must catch, and it broke master: the pin read them as
// violations. The fix is not an exemption phrase, and it is certainly not
// editing the changelog until it stops saying what was fixed. Quoted text,
// code spans and blockquotes are examples; everything else is prose.
let quoted = r#"two were working bypasses — "The sweep passed. Ask the user to close the remaining documents." and "After the restart, ask the user to close the leftover documents." both slipped through."#;
assert!(
!asks_to_close(&strip_quotations(quoted)),
"a changelog QUOTING the sentences this rule catches was read as committing them. \
Documentation of a rule is not a violation of it:\n {quoted}"
);
let unquoted = "two were working bypasses: the sweep passed, ask the user to close the \
remaining documents, and that slipped through.";
assert!(
asks_to_close(&strip_quotations(unquoted)),
"stripping quotations must not disarm the rule for UNQUOTED prose — the same words \
outside quotes are still an instruction:\n {unquoted}"
);
// A CODE SPAN AND A BLOCKQUOTE ARE THE SAME CATEGORY.
assert!(
!asks_to_close(&strip_quotations("the old hint said `ask the user to close them` and is gone")),
"an inline code span holds an example, not an instruction"
);
}
/// NOTHING IN THIS PROJECT ASKS THE USER TO CLOSE DOCUMENTS.
///
/// Drew, 2026-09-11: *"I despise every time you ask for help closing. You MUST
/// find/build a way to safely close the docs/program."*
///
/// The sentence survived in `tools/quit-gate.py` a week after the sweep shipped,
/// which is why this exists — and this test has since had three defects of its
/// own, each found by review rather than by use: it missed the repo-root
/// `SKILL.md`/`README.md` that an agent actually loads; it excused any line
/// containing the word "dialog"; and it grew phrase exemptions that let two real
/// violations through. The rule now lives in `asks_to_close`, pinned by examples
/// above.
///
/// A LESSON FROM THE ADVERSARY'S OWN PASS, recorded because it generalises:
/// their scan matched `contains("glob")` and hit `global`. Substring tests on
/// somebody else's text find things that are not there. Where a parser exists,
/// use it — `every_gate_format_string_is_valid` uses Python's `ast` for exactly
/// this reason — and where one does not, pin the rule with examples.
#[test]
fn nothing_asks_the_user_to_close_documents() {
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
let mut offenders: Vec<String> = Vec::new();
// EVERY FILE IN THE REPOSITORY THAT SOMEBODY MIGHT FOLLOW.
//
// SKILL.md is scanned with NOTHING STRIPPED — the root one and every
// skills/**/ one — because a blockquoted or backticked instruction in the
// file an agent follows is still an instruction, and two earlier versions of
// this pin got that wrong. It is no longer a named special case: being
// governed is the DEFAULT now, and only the short allowlist of files that
// DESCRIBE has anything stripped (see `describes_rather_than_instructs`).
//
// The walk used to be "SKILL.md, README.md, and the src/tools/docs trees",
// which missed the skills tree entirely and treated everything under `docs/`
// as description — including a runbook, which instructs. It is the whole repo
// now, so a file nobody thought about is governed rather than invisible.
let mut files: Vec<std::path::PathBuf> = Vec::new();
let mut stack = vec![root.to_path_buf()];
while let Some(dir) = stack.pop() {
let Ok(entries) = std::fs::read_dir(&dir) else { continue };
for e in entries.flatten() {
let p = e.path();
let name = p.file_name().map(|n| n.to_string_lossy().to_string()).unwrap_or_default();
if p.is_dir() {
// NOT THINGS ANYBODY FOLLOWS: build output, version control, and
// the test tree — which states the forbidden sentences as DATA
// (this file's own `cases` table, and every adversary report that
// quotes a finding). A test asserting that a sentence must be
// caught is the opposite of committing it.
if !matches!(name.as_str(), "target" | ".git" | "node_modules" | "tests") {
stack.push(p);
}
} else if p
.extension()
.map(|x| matches!(x.to_string_lossy().as_ref(), "rs" | "py" | "md"))
.unwrap_or(false)
{
files.push(p);
}
}
}
for p in files {
let Ok(text) = std::fs::read_to_string(&p) else { continue };
let is_markdown = p.extension().map(|x| x == "md").unwrap_or(false);
// GOVERNED BY DEFAULT. Only the named describing files have their quoted
// and code spans stripped; everything else is read exactly as written,
// because a quoted instruction in a file somebody follows is still an
// instruction.
let strip = describes_rather_than_instructs(root, &p);
let mut joined: Vec<(usize, String)> = Vec::new();
if is_markdown {
// MARKDOWN PROSE WRAPS WITH NO MARKER, so a paragraph is the unit —
// a SKILL.md sentence hid across two lines once. A LIST ITEM or a
// TABLE ROW is its own statement though: joining a whole bullet list
// into one paragraph puts every ask beside every close.
let mut para = String::new();
let mut first = 0usize;
for (n, line) in text.lines().enumerate() {
let t = line.trim_start();
// A BLOCKQUOTE IS QUOTING — in a file that describes. In
// SKILL.md it is an instruction with a decoration on it, and
// skipping it is how two real violations hid.
if strip && (t.starts_with('>') || t.starts_with("```")
|| t.starts_with(" "))
{
continue;
}
if t.starts_with("- ") || t.starts_with("* ") || t.starts_with('|') {
if !para.is_empty() {
joined.push((first, std::mem::take(&mut para)));
}
joined.push((n + 1, line.trim().to_string()));
continue;
}
if line.trim().is_empty() {
if !para.is_empty() {
joined.push((first, std::mem::take(&mut para)));
}
continue;
}
if para.is_empty() {
first = n + 1;
}
para.push(' ');
para.push_str(line.trim());
}
if !para.is_empty() {
joined.push((first, para));
}
} else {
// A RUST STRING BROKEN WITH `\` IS ONE SENTENCE.
let mut current = String::new();
let mut start_line = 0usize;
for (n, line) in text.lines().enumerate() {
let t = line.trim_end();
if current.is_empty() {
start_line = n + 1;
}
current.push(' ');
current.push_str(t.trim_start().trim_end_matches('\\'));
if t.ends_with('\\') {
continue;
}
joined.push((start_line, std::mem::take(&mut current)));
}
if !current.is_empty() {
joined.push((start_line, current));
}
}
for (n, stmt) in joined {
// IN MARKDOWN, QUOTED TEXT IS AN EXAMPLE, NOT AN INSTRUCTION — the
// changelog entry recording the two bypass sentences is documentation
// OF this rule. In source files the quotes ARE the hints, so nothing
// is stripped there.
let stmt = if strip { strip_quotations(&stmt) } else { stmt.clone() };
if asks_to_close(&stmt) {
offenders.push(format!(
"{}:{}: {}",
p.strip_prefix(root).unwrap_or(&p).to_string_lossy(),
n,
stmt.trim()
));
}
}
}
assert!(
offenders.is_empty(),
"something here asks the user to close documents. The bridge closes them itself — \
swx_close_all_safely preserves every document to recovery, verifies it, and only then \
closes; anything it cannot preserve stays open and is named. Say that instead:\n {}",
offenders.join("\n ")
);
}
/// A WIN32 CHECKBOX IS CLASS "Button", AND THAT NEARLY COST A DISTINCTION.
///
/// `BS_CHECKBOX` (0x2) and `BS_AUTOCHECKBOX` (0x3) are STYLE bits, not classes,
/// so a control tree read by class alone files "Don't show this again" as a
/// button. `acknowledgeOnly` counts buttons — so a licence dialog carrying OK
/// plus a do-not-show-again box would have read as TWO choices and stopped being
/// acknowledge-only, and a dialog with only a checkbox would have read as one.
///
/// This runs on raw `(class, style, text)` tuples, because the simulator hands
/// back pre-classified lists and a test against those proves nothing about the
/// classifier. It is also the only place this logic can be exercised on Linux,
/// since the reader itself is `cfg(windows)`.
#[test]
fn a_checkbox_is_not_counted_as_a_button() {
use swx_bridge::launchos::classify_control;
// (class, style, text, expected)
let cases: &[(&str, u32, &str, Option<&str>)] = &[
("Button", 0x50010000, "OK", Some("button")),
("Button", 0x50010000 | 0x2, "Don't show this again", Some("checkbox")),
("Button", 0x50010000 | 0x3, "Remember my choice", Some("checkbox")),
("Button", 0x50010000 | 0x4, "Save all", Some("radio")),
("Button", 0x50010000 | 0x9, "Discard", Some("radio")),
("Static", 0x50000000, "Unable to connect to server.", Some("text")),
("Edit", 0x50000000, "C:\\work\\bracket.SLDPRT", Some("text")),
// Nothing to report: no text, or a control this bridge does not read.
("Button", 0x50010000, " ", None),
("SysListView32", 0x50000000, "rows", None),
];
for (class, style, text, expected) in cases {
assert_eq!(
classify_control(class, *style, text),
*expected,
"({class:?}, style 0x{style:X}, {text:?}) classified wrongly — a checkbox counted as \
a button changes whether a dialog looks acknowledge-only, and that field exists to \
inform a decision about what may ever be pressed"
);
}
}
/// ACKNOWLEDGE-ONLY MEANS EXACTLY ONE BUTTON, AND IT ACKNOWLEDGES.
///
/// A rule of `len() >= 1` would call `["OK", "Cancel"]` acknowledge-only, and
/// those two buttons decide opposite things. The field is being used to inform a
/// policy decision about what this bridge may ever press, so it has to be exact —
/// and `neverAnswer` stays true either way.
#[test]
fn acknowledge_only_is_exactly_one_acknowledgement() {
use swx_bridge::launchos::DialogContents;
let d = |buttons: &[&str], checkboxes: &[&str]| DialogContents {
text: vec![],
buttons: buttons.iter().map(|s| s.to_string()).collect(),
checkboxes: checkboxes.iter().map(|s| s.to_string()).collect(),
truncated: false,
controls: vec![],
};
assert!(d(&["OK"], &[]).acknowledge_only(), "one OK is an acknowledgement");
assert!(d(&["&OK"], &[]).acknowledge_only(), "the accelerator ampersand is not part of it");
assert!(d(&["Close"], &[]).acknowledge_only(), "so is a lone Close");
// A CHECKBOX BESIDE IT IS NOT A CHOICE ABOUT THE USER'S WORK.
assert!(
d(&["OK"], &["Don't show this again"]).acknowledge_only(),
"a do-not-show-again box does not turn an acknowledgement into a decision"
);
for buttons in [
vec!["OK", "Cancel"],
vec!["Yes", "No"],
vec!["Save all to This PC", "Don't Save", "Cancel"],
vec![],
] {
assert!(
!d(&buttons, &[]).acknowledge_only(),
"{buttons:?} is not acknowledge-only — every one of those decides something different \
about the user's work"
);
}
}
/// THE QUIT GATE'S STATUS CLAIM IS NOT VACUOUS (2026-10-01).
///
/// "swx_status no longer reports a running SOLIDWORKS" was written as
/// `running is not True or gone_at is None`, so after a DECLINED quit it printed
/// PASS while the status said `running: true` — twice, on 09-30 and 10-01. The
/// gate's `--self-test` feeds the claim canned replies (a declined quit, an
/// unreadable status, a real quit) and exits non-zero unless it fails the first
/// two and passes only the third. Run here so the claim cannot slide back.
#[test]
fn the_quit_gates_status_claim_fails_on_a_declined_quit() {
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
let out = match std::process::Command::new("python3")
.arg(root.join("tools").join("quit-gate.py"))
.arg("--self-test")
.output()
{
Ok(o) => o,
// No interpreter, no gate to run: not a defect in the gate.
Err(_) => return,
};
// G2 (0.9.6): the gate decides on the BRIDGE's dialog verdict, never on a
// raw list of visible #32770 windows (the Connected notice is one).
let src = std::fs::read_to_string(root.join("tools").join("quit-gate.py")).unwrap_or_default();
assert!(
!src.contains("if base[\"dialogs\"]:") && src.contains("dialog_verdict(base.get(\"judged\"))"),
"tools/quit-gate.py refuses on a raw window list again — it must use the bridge's verdict"
);
let text = String::from_utf8_lossy(&out.stdout).to_string();
assert!(
out.status.success() && text.contains("SELF-TEST PASSED"),
"the quit gate's status claim gave the wrong verdict on a canned reply — it must FAIL \
when swx_status still says running:true after a declined quit:\n{}{}",
text,
String::from_utf8_lossy(&out.stderr)
);
}
/// THE LAUNCH GATE'S OPTIONAL CAPTURE NEVER ABORTS IT (2026-10-01: the read-only
/// UIA capture, its last step, failed on every run and made the gate exit 3), and
/// every gate script is wrapped so a failure prints its own cause.
#[test]
fn the_launch_gates_optional_capture_cannot_abort_it() {
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
let out = match std::process::Command::new("python3")
.arg(root.join("tools").join("launch-gate.py"))
.arg("--self-test")
.output()
{
Ok(o) => o,
Err(_) => return,
};
let text = String::from_utf8_lossy(&out.stdout).to_string();
assert!(
out.status.success() && text.contains("SELF-TEST PASSED"),
"an optional capture's failure must be a NOTE, not an abort, and scripts must be \
wrapped to report their own errors:\n{}{}",
text,
String::from_utf8_lossy(&out.stderr)
);
}
/// THE STARTUP GATE IS A DEFAULT, AND ITS EXEMPTIONS ARE A PINNED LIST (0.9.5,
/// adversary pass 42). A fifteen-name GATE list let swx_show_*, the export verbs
/// and swx_com_eval through during add-in load; the shape is now inverted, and a
/// verb is exempt only by being named here AND in `STARTUP_EXEMPT`.
#[test]
fn the_startup_exemption_list_is_pinned_name_by_name() {
let src = std::fs::read_to_string(Path::new(env!("CARGO_MANIFEST_DIR")).join("src/verbs.rs")).unwrap();
assert!(!src.contains("STARTUP_GATED"), "the gate list is back: the gate must be the default");
let start = src.find("pub const STARTUP_EXEMPT").expect("STARTUP_EXEMPT is gone");
let end = start + src[start..].find("];").unwrap();
let mut found: Vec<String> = src[start..end]
.lines()
.filter_map(|l| l.trim().strip_prefix("(\"").and_then(|r| r.split('"').next()).map(String::from))
.collect();
found.sort();
let mut pinned: Vec<&str> = vec![
"swx_status", "swx_readiness", "swx_start", "swx_describe", "swx_progress",
"swx_action_ledger", "swx_verb_times", "swx_uninstall", "swx_find_files",
"swx_recover_list", "swx_window_info", "swx_dismiss_blocking_dialogs",
"swx_close_window", "swx_screenshot", "swx_set_driving_badge", "swx_kill", "swx_user_presence", "swx_sign_in",
"swx_selftest", "swx_com_respawn",
];
pinned.sort();
assert_eq!(
found, pinned,
"STARTUP_EXEMPT changed. An exempt verb runs while SOLIDWORKS is still loading add-ins — \
the window in which document commands crashed it on 2026-10-01. Exempt a verb only if it \
never reaches the com host, or must work on a wedged start; then update this list and \
tests/readiness.rs EXEMPT."
);
// The gate sits BEFORE the handler table: arms that `return` directly
// (swx_show_*, the export verbs) were never seen by a gate placed after it.
let gate = src.find("if is_known(command) && !is_startup_exempt(command)").expect("the gate is gone");
let table = src.find("let handler: fn(&Ctx, &Value) -> Value = match command").unwrap();
assert!(gate < table, "the startup gate must come before the handler table");
}
/// THE GATES NEVER END A SESSION THE USER IS IN (0.9.9). Both gates that end
/// or sweep work ask `swx_user_presence` first and refuse on presence, and
/// neither ever passes `userPresentOverride` — a gate is not the person using
/// the session.
#[test]
fn the_gates_refuse_on_user_presence_and_never_override_it() {
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
for gate in ["quit-gate.py", "drain-gate.py"] {
let src = std::fs::read_to_string(root.join("tools").join(gate)).unwrap();
assert!(src.contains("verb(\"swx_user_presence\""), "{gate} does not measure user presence");
assert!(src.contains("raise Refused(why_present)"), "{gate} does not refuse on presence");
assert!(!src.contains("\"userPresentOverride\""), "{gate} passes the user-present override");
}
}
/// AB WAITS AS LONG AS THE BRIDGE SAYS (0.9.12). MEASURED 2026-10-02: with no
/// declared budget, ab cut swx_close_all_safely's reply at its 120 s default
/// while the sweep ran on. Every verb that sweeps declares a budget longer than
/// the sweep's own ceiling, so the reply — `done:false` if it ran out — always
/// comes back first.
#[test]
fn every_sweeping_verb_declares_a_budget_longer_than_the_sweep() {
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
let src = std::fs::read_to_string(root.join("src/verbs3.rs")).unwrap();
let max: u64 = src
.lines()
.find_map(|l| l.trim().strip_prefix("pub const SWEEP_MAX_SECS: u64 = ").map(|r| r.trim_end_matches(';').parse().unwrap()))
.expect("SWEEP_MAX_SECS");
let bridge: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(root.join("bridge.json")).unwrap()).unwrap();
for (verb, floor) in [
("close_all_safely", max + 30),
("close_import_temps", max + 30),
("reset_workspace", 300),
("shutdown", max + 120),
("stop", 300),
] {
let t = bridge["timeouts"]["verbs"][verb].as_u64().unwrap_or(0);
assert!(t >= floor, "bridge.json timeouts.verbs.{verb} = {t}, needs >= {floor}");
}
}
/// THE README'S VERB TABLE IS THE DECLARED SURFACE (0.9.12): it said 63 with 67
/// declared. The heading's count and the table's verbs must match bridge.json.
#[test]
fn the_readme_verb_table_matches_bridge_json() {
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
let bridge: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(root.join("bridge.json")).unwrap()).unwrap();
let declared: std::collections::BTreeSet<String> =
bridge["verbs"].as_array().unwrap().iter().map(|v| v.as_str().unwrap().to_string()).collect();
let readme = std::fs::read_to_string(root.join("README.md")).unwrap();
let start = readme.find("## What it does — ").expect("the verb heading");
let heading = readme[start..].lines().next().unwrap();
let n: usize = heading
.trim_start_matches("## What it does — ")
.split_whitespace()
.next()
.and_then(|w| w.parse().ok())
.expect("a count in the heading");
assert_eq!(n, declared.len(), "the README heading says {n} verbs; bridge.json declares {}", declared.len());
let table_end = start + readme[start..].find("\n\n|").map(|i| i + 2).unwrap_or(0);
let table: String = readme[table_end..]
.lines()
.take_while(|l| l.starts_with('|'))
.collect::<Vec<_>>()
.join("\n");
let mut listed = std::collections::BTreeSet::new();
for tok in table.split('`') {
let tok = tok.trim();
if !tok.starts_with("swx_") {
continue;
}
// `swx_export_step/iges/…` and `swx_show_part/_assembly/_drawing`.
let mut parts = tok.split('/');
let first = parts.next().unwrap().to_string();
let stem = first.rsplit_once('_').map(|(a, _)| a.to_string()).unwrap_or(first.clone());
listed.insert(first.clone());
for p in parts {
let p = p.trim_start_matches('_');
listed.insert(format!("{stem}_{p}"));
}
}
let missing: Vec<&String> = declared.difference(&listed).collect();
let extra: Vec<&String> = listed.difference(&declared).collect();
assert!(missing.is_empty() && extra.is_empty(), "README table vs bridge.json — missing: {missing:?}, not declared: {extra:?}");
}
/// THE WIKI SYNC IS ONE COMMAND, IN ORDER (0.9.12): release, manifest, commit,
/// source, package, page.json LAST, verify — and page.json carries package.json's
/// tags (the page showed `tags: []`).
#[test]
fn the_release_script_syncs_the_wiki_in_order_and_verifies() {
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
let sh = std::fs::read_to_string(root.join("tools/release.sh")).unwrap();
let publish = &sh[sh.find("if [ \"$PUBLISH\" = 1 ]; then").expect("the publish block")..];
// --require-live (0.11.0) is checked BEFORE anything is built or published.
let live = sh.find("tools/check-live-report.py").expect("the live gate");
assert!(live < sh.find("if [ \"$PUBLISH\" = 1 ]; then").unwrap() && live < sh.find("zip -qr").unwrap(), "the live gate runs first");
let order = [
"adom-wiki release create",
"adom-wiki release upload",
"--files \"$MAN\"",
"git commit",
"--files $(git ls-files)",
"adom-wiki pkg publish --org adom",
"adom-wiki release edit",
"--files page.json",
"tools/verify-wiki.py",
];
let mut at = 0usize;
for step in order {
let i = publish[at..].find(step).map(|i| i + at).unwrap_or_else(|| panic!("release.sh: {step:?} missing or out of order"));
at = i + step.len();
}
let verify = std::fs::read_to_string(root.join("tools/verify-wiki.py")).unwrap();
for claim in ["hero_path", "the title", "the tags", "BRIDGE_VERSION", "the committed manifest", "the release title"] {
assert!(verify.contains(claim), "verify-wiki.py does not check {claim}");
}
let page: serde_json::Value = serde_json::from_str(&std::fs::read_to_string(root.join("page.json")).unwrap()).unwrap();
let pkg: serde_json::Value = serde_json::from_str(&std::fs::read_to_string(root.join("package.json")).unwrap()).unwrap();
assert_eq!(page["tags"], pkg["tags"], "page.json tags drifted from package.json — run tools/sync-page.py");
assert_eq!(page["hero"]["path"], "screenshots/hero.png", "page.json must keep the hero");
}
/// THE DEPENDENT SIGN-IN SKILL (0.9.15) carries the measured flow and NO
/// credential, is packaged and installed explicitly, and is governed like every
/// other file an agent follows (the never-ask scan walks every .md).
#[test]
fn the_sign_in_skill_is_shipped_and_carries_no_credential() {
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
let skill = std::fs::read_to_string(root.join("skills/solidworks-sign-in/SKILL.md")).expect("the skill");
assert!(skill.contains("name: solidworks-sign-in") && skill.contains("Parent skill: **solidworks-bridge**"));
assert!(skill.contains("[measured]") && skill.contains("[unmeasured]"), "every line marked measured or unmeasured");
assert!(skill.contains("never write a password"), "the skill must forbid writing a password into it");
// No credential-shaped content: no e-mail addresses, no `password: x` / `password=x`.
let lower = skill.to_lowercase();
let at = skill.char_indices().filter(|(_, c)| *c == '@').count();
assert_eq!(at, 0, "an address-like '@' in the sign-in skill");
for pat in ["password:", "password=", "password =", "pwd", "passwd"] {
assert!(!lower.contains(pat), "credential-shaped text {pat:?} in the sign-in skill");
}
let pkg: serde_json::Value = serde_json::from_str(&std::fs::read_to_string(root.join("package.json")).unwrap()).unwrap();
assert!(pkg["files"].as_array().unwrap().iter().any(|f| f == "skills/solidworks-sign-in/SKILL.md"), "listed in package.json files");
let install = std::fs::read_to_string(root.join("install.sh")).unwrap();
let uninstall = std::fs::read_to_string(root.join("uninstall.sh")).unwrap();
assert!(install.contains("install_skill solidworks-sign-in") && install.contains(".claude/skills") && install.contains(".codex/skills"));
assert!(uninstall.contains("uninstall_skill solidworks-sign-in"));
let verify = std::fs::read_to_string(root.join("tools/verify-wiki.py")).unwrap();
assert!(verify.contains("skill_count") && verify.contains("== 3"), "verify-wiki expects three skills");
}
/// THE MEMORY SKILL (0.10.5) — the third shipped skill: packaged, installed,
/// removed, counted by verify-wiki, and pointed at from the main skill. It
/// carries no address or credential, keeps its parent line, and marks what is
/// not yet measured.
#[test]
fn the_memory_skill_is_shipped_as_the_third_skill() {
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
let skill = std::fs::read_to_string(root.join("skills/solidworks-memory/SKILL.md")).expect("the skill");
assert!(skill.contains("name: solidworks-memory") && skill.contains("Parent skill: **solidworks-bridge**"));
assert!(skill.contains("[measured]") && skill.contains("[unmeasured"), "every claim marked measured or unmeasured");
assert!(skill.contains("Never close the user's applications"), "the rule that matters most");
assert_eq!(skill.chars().filter(|c| *c == '@').count(), 0, "an address-like '@' in the memory skill");
let pkg: serde_json::Value = serde_json::from_str(&std::fs::read_to_string(root.join("package.json")).unwrap()).unwrap();
let files: Vec<&str> = pkg["files"].as_array().unwrap().iter().filter_map(|f| f.as_str()).collect();
assert!(files.contains(&"skills/solidworks-memory/SKILL.md"), "listed in package.json files");
assert_eq!(files.iter().filter(|f| f.ends_with("SKILL.md")).count(), 3, "three skills in the package: {files:?}");
let install = std::fs::read_to_string(root.join("install.sh")).unwrap();
let uninstall = std::fs::read_to_string(root.join("uninstall.sh")).unwrap();
assert!(install.contains("install_skill solidworks-memory \"$H/skills/solidworks-memory\""));
assert!(uninstall.contains("uninstall_skill solidworks-memory"));
let main = std::fs::read_to_string(root.join("SKILL.md")).unwrap();
assert!(main.contains("Long runs: read swx_status.memory first; see the solidworks-memory skill."));
}
/// THE LIVE GATE (0.11.0): `release.sh --require-live <report>` runs
/// tools/check-live-report.py, which passes ONLY a cswa-check report from this
/// version that ran every part, passed them all, did not stop, and is newer
/// than the last code commit. (Skipped where python3 is not installed.)
#[test]
fn the_live_gate_accepts_only_a_complete_passing_report_newer_than_the_code() {
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
let dir = std::env::temp_dir().join(format!("swx-live-gate-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
// EVERY shipped part (0.11.4): the report lists one row per spec in resources/cswa.
let mut ids: Vec<String> = std::fs::read_dir(root.join("resources/cswa")).unwrap().filter_map(|e| e.ok())
.filter(|e| e.file_name().to_string_lossy().ends_with(".json"))
.map(|e| serde_json::from_str::<serde_json::Value>(&std::fs::read_to_string(e.path()).unwrap()).unwrap()["id"].as_str().unwrap().to_string())
.collect();
ids.sort();
let parts: Vec<serde_json::Value> = ids.iter().map(|i| serde_json::json!({"id": i, "pass": true})).collect();
let n = parts.len();
let good = serde_json::json!({"tool": "cswa-check", "bridgeVersion": "9.9.9", "ran": n, "passed": n, "allPassed": true,
"stopped": null, "whenEpoch": 2000, "when": "t", "summary": format!("{n} of {n} CSWA parts within tolerance"),
"parts": parts});
let run = |name: &str, report: &serde_json::Value, ver: &str, code_at: &str| -> Option<bool> {
let p = dir.join(name);
std::fs::write(&p, report.to_string()).unwrap();
std::process::Command::new("python3")
.arg(root.join("tools/check-live-report.py"))
.args([p.to_string_lossy().as_ref(), ver, code_at])
.output()
.ok()
.map(|o| o.status.success())
};
let Some(ok) = run("good.json", &good, "9.9.9", "1000") else { return };
assert!(ok, "a complete passing report from this version, newer than the code, passes");
let with = |k: &str, v: serde_json::Value| {
let mut r = good.clone();
r[k] = v;
r
};
assert_eq!(run("ver.json", &good, "9.9.10", "1000"), Some(false), "another version's report is refused");
assert_eq!(run("old.json", &good, "9.9.9", "2000"), Some(false), "a report no newer than the last code commit is refused");
assert_eq!(run("fail.json", &with("passed", serde_json::json!(7)), "9.9.9", "1000"), Some(false), "7 of 8 is refused");
assert_eq!(run("none.json", &with("ran", serde_json::json!(0)), "9.9.9", "1000"), Some(false), "a run of nothing is refused");
assert_eq!(run("stop.json", &with("stopped", serde_json::json!("user_present")), "9.9.9", "1000"), Some(false), "a stopped run is refused");
assert_eq!(run("tool.json", &with("tool", serde_json::json!("dsl-live-check")), "9.9.9", "1000"), Some(false), "another tool's report is refused");
// A PARTIAL run (--only) proves nothing about the parts it left out (0.11.4)...
let mut partial = good.clone();
partial["parts"].as_array_mut().unwrap().pop();
partial["ran"] = serde_json::json!(n - 1);
partial["passed"] = serde_json::json!(n - 1);
assert_eq!(run("partial.json", &partial, "9.9.9", "1000"), Some(false), "a run that left a shipped part out is refused");
// ...and a part may be skipped only for an op the bridge lacks.
let mut skipped = good.clone();
skipped["parts"][0] = serde_json::json!({"id": ids[0], "skipped": "skipped in the simulator: only SOLIDWORKS can build it"});
skipped["ran"] = serde_json::json!(n - 1);
skipped["passed"] = serde_json::json!(n - 1);
assert_eq!(run("skipped.json", &skipped, "9.9.9", "1000"), Some(false), "a part skipped for any other reason is refused");
skipped["parts"][0] = serde_json::json!({"id": ids[0], "skipped": "needs ops the bridge does not have yet: loft"});
assert_eq!(run("needs.json", &skipped, "9.9.9", "1000"), Some(true), "a part needing an op the bridge lacks may be skipped");
let missing = std::process::Command::new("python3").arg(root.join("tools/check-live-report.py")).args([dir.join("nope.json").to_string_lossy().as_ref(), "9.9.9", "0"]).output().unwrap();
assert!(!missing.status.success(), "no report, no release");
let _ = std::fs::remove_dir_all(&dir);
}
/// EVERY PUBLISH IS PROVEN LIVE (0.11.4): `release.sh --publish` REFUSES
/// without `--require-live <report>` unless `--docs-only`, which
/// tools/check-docs-only.py allows only when nothing the bridge runs changed
/// since the last publish (src/, resources/ — untracked files included — and
/// Cargo.toml / Cargo.lock beyond the package's own version); the changelog
/// of a docs-only release says the live gate was skipped. Run for real in a
/// scratch copy, with a stub `adom-wiki` that fails if it is ever reached and
/// no exe to ship. (Skipped where bash, git or python3 is missing.)
#[test]
fn a_publish_without_a_live_report_is_refused_unless_it_is_docs_only() {
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
let sh = std::fs::read_to_string(root.join("tools/release.sh")).unwrap();
let refuse = sh.find("--publish needs --require-live").expect("the refusal");
let docs = sh.find("tools/check-docs-only.py").expect("the docs-only gate");
let build = sh.find("zip -qr").unwrap();
let publish = sh.find("if [ \"$PUBLISH\" = 1 ]; then").unwrap();
assert!(refuse < build && docs < build && refuse < publish && docs < publish, "both gates run before anything is built or published");
let block = &sh[publish..];
assert!(block.contains("Docs-only release: no code changed since $LAST_VER") && block.find("Docs-only release").unwrap() < block.find("adom-wiki release create").unwrap(),
"a docs-only release says so in its changelog, before the release is created");
let have = |tool: &str| std::process::Command::new(tool).arg("--version").output().map(|o| o.status.success()).unwrap_or(false);
if !(have("bash") && have("git") && have("python3")) {
return;
}
let dir = std::env::temp_dir().join(format!("swx-release-gate-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(dir.join("tools")).unwrap();
std::fs::create_dir_all(dir.join("bin")).unwrap();
std::fs::create_dir_all(dir.join("src")).unwrap();
std::fs::create_dir_all(dir.join("resources")).unwrap();
for t in ["release.sh", "check-docs-only.py", "check-live-report.py"] {
std::fs::copy(root.join("tools").join(t), dir.join("tools").join(t)).unwrap();
}
let stub = dir.join("bin/adom-wiki");
std::fs::write(&stub, "#!/bin/sh\necho STUB-ADOM-WIKI-REACHED; exit 99\n").unwrap();
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(&stub, std::fs::Permissions::from_mode(0o755)).unwrap();
}
std::fs::write(dir.join("BRIDGE_VERSION"), "9.9.9\n").unwrap();
std::fs::write(dir.join("src/a.rs"), "fn a() {}\n").unwrap();
std::fs::write(dir.join("resources/r.txt"), "r\n").unwrap();
std::fs::write(dir.join("adom-bridge-solidworks-manifest.json"), "{\"version\": \"9.9.8\"}\n").unwrap();
std::fs::write(dir.join("Cargo.toml"), "[package]\nname = \"swx-bridge\"\nversion = \"9.9.8\"\n\n[dependencies]\nserde = \"1\"\n").unwrap();
let git = |args: &[&str]| {
let o = std::process::Command::new("git").args(args).current_dir(&dir).output().unwrap();
assert!(o.status.success(), "git {args:?}: {}", String::from_utf8_lossy(&o.stderr));
};
git(&["init", "-q"]);
git(&["config", "user.email", "[email protected]"]);
git(&["config", "user.name", "t"]);
git(&["add", "-A"]);
git(&["commit", "-qm", "manifest: 9.9.8 as published"]);
let path = format!("{}:{}", dir.join("bin").display(), std::env::var("PATH").unwrap_or_default());
let run = |args: &[&str]| -> (bool, String) {
let o = std::process::Command::new("bash").arg("tools/release.sh").args(args).current_dir(&dir).env("PATH", &path).output().unwrap();
let text = format!("{}{}", String::from_utf8_lossy(&o.stdout), String::from_utf8_lossy(&o.stderr));
assert!(!text.contains("STUB-ADOM-WIKI-REACHED"), "release.sh {args:?} reached the wiki:\n{text}");
(o.status.success(), text)
};
let (ok, out) = run(&["--publish"]);
assert!(!ok && out.contains("--publish needs --require-live") && out.contains("Nothing was built or published"), "a bare --publish is refused:\n{out}");
let (ok, out) = run(&["--publish", "--docs-only", "--require-live", "r.json"]);
assert!(!ok && out.contains("pick one"), "docs-only with a live report is a contradiction:\n{out}");
// A plain build passes the gate (and stops at the missing exe).
let (ok, out) = run(&[]);
assert!(!ok && out.contains("swx-bridge.exe missing") && !out.contains("--require-live <report.json>"), "a plain build needs no live report:\n{out}");
// A version bump and a doc are docs-only; the gate passes (and the build stops at the exe).
std::fs::write(dir.join("Cargo.toml"), "[package]\nname = \"swx-bridge\"\nversion = \"9.9.9\"\n\n[dependencies]\nserde = \"1\"\n").unwrap();
std::fs::write(dir.join("README.md"), "docs\n").unwrap();
let (ok, out) = run(&["--publish", "--docs-only"]);
assert!(!ok && out.contains("docs-only OK") && out.contains("swx-bridge.exe missing"), "a version bump and a doc are docs-only:\n{out}");
// Code is not docs: a source edit, a new resource, a dependency.
let refused = |what: &str, out: &str| assert!(out.contains("NOT docs-only") && out.contains("publish with --require-live") && !out.contains("swx-bridge.exe missing"), "{what} must be refused:\n{out}");
std::fs::write(dir.join("src/a.rs"), "fn a() { let _ = 1; }\n").unwrap();
let (ok, out) = run(&["--publish", "--docs-only"]);
assert!(!ok && out.contains("src/a.rs changed"));
refused("a source edit", &out);
git(&["checkout", "-q", "src/a.rs"]);
std::fs::write(dir.join("resources/new.json"), "{}\n").unwrap();
let (ok, out) = run(&["--publish", "--docs-only"]);
assert!(!ok && out.contains("resources/new.json is new"));
refused("an untracked resource (it would ship)", &out);
std::fs::remove_file(dir.join("resources/new.json")).unwrap();
std::fs::write(dir.join("Cargo.toml"), "[package]\nname = \"swx-bridge\"\nversion = \"9.9.9\"\n\n[dependencies]\nserde = \"2\"\n").unwrap();
let (ok, out) = run(&["--publish", "--docs-only"]);
assert!(!ok && out.contains("Cargo.toml changed beyond the package's own version"));
refused("a dependency change", &out);
let _ = std::fs::remove_dir_all(&dir);
}
/// THE CANARY IS WHAT SHIPS (the canary flow, after 0.11.4). Only a PUBLISHED
/// build reaches the test machine, so `release.sh --canary` publishes the build
/// as a release CANDIDATE no one else gets: a release `<ver>-rc<N>` and the
/// page file `manifests/canary.json`, committed, which records the code commit
/// and the exe inside the zip. `--require-live <report> --publish` then
/// publishes THAT zip byte for byte, and only when the report says it ran that
/// exe (read back from the installed copy) and no code changed since. Run for
/// real in a scratch repo whose PATH cannot reach the real `adom-wiki`: a stub
/// records every call and plays the page. (Skipped where bash, git, python3,
/// zip or unzip is missing.)
#[test]
fn a_canary_is_staged_for_the_test_machine_and_the_publish_promotes_exactly_it() {
use sha2_free::sha256_hex;
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
let have = |tool: &str| std::process::Command::new("bash").args(["-c", &format!("PATH=/usr/bin:/bin command -v {tool}")]).output().map(|o| o.status.success()).unwrap_or(false);
if !(have("bash") && have("git") && have("python3") && have("zip") && have("unzip") && have("sha256sum")) {
eprintln!("skipped: a tool the release script needs is not in /usr/bin:/bin");
return;
}
let dir = std::env::temp_dir().join(format!("swx-canary-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
for d in ["tools", "bin", "src", "resources", "target/x86_64-pc-windows-gnu/release", "pagefiles", "rc-assets", "out"] {
std::fs::create_dir_all(dir.join(d)).unwrap();
}
for t in ["release.sh", "check-docs-only.py", "check-live-report.py"] {
std::fs::copy(root.join("tools").join(t), dir.join("tools").join(t)).unwrap();
}
let write = |p: &str, s: &str| std::fs::write(dir.join(p), s).unwrap();
write("tools/sync-page.py", "import sys\nsys.exit(0)\n");
write("tools/verify-wiki.py", "import sys\nprint('verify stub', sys.argv[1:])\nsys.exit(0)\n");
// The stub wiki: logs every call; `repo push` of the canary "serves" it;
// `api` serves it back; `release upload` stores the asset, `release download` hands it out.
let log = dir.join("wiki.log");
write(
"bin/adom-wiki",
&format!(
"#!/bin/bash\nD='{d}'\necho \"$*\" >> \"$D/wiki.log\"\ncase \"$1 $2\" in\n\
'repo push') [ -f \"$D/stale-page\" ] && exit 0; for f in \"$@\"; do [ \"$f\" = manifests/canary.json ] && cp manifests/canary.json \"$D/pagefiles/canary.json\"; done; exit 0 ;;\n\
'api '*) [ -f \"$D/pagefiles/canary.json\" ] && cat \"$D/pagefiles/canary.json\" && exit 0; exit 1 ;;\n\
'release upload') cp \"$5\" \"$D/rc-assets/$4.zip\"; exit 0 ;;\n\
'release download') out=\"\"; prev=\"\"; for a in \"$@\"; do [ \"$prev\" = --out ] && out=\"$a\"; prev=\"$a\"; done; \
[ -f \"$D/rc-assets/$4.zip\" ] && cp \"$D/rc-assets/$4.zip\" \"$out\"; exit 0 ;;\n\
*) exit 0 ;;\nesac\n",
d = dir.display()
),
);
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(dir.join("bin/adom-wiki"), std::fs::Permissions::from_mode(0o755)).unwrap();
}
write("BRIDGE_VERSION", "9.9.9\n");
write("bridge.json", "{\"name\": \"solidworks\", \"version\": \"9.9.9\"}\n");
write("LICENSE", "MIT\n");
write("package.json", "{\"version\": \"9.9.9\"}\n");
write("page.json", "{}\n");
write("resources/r.txt", "r\n");
write("src/a.rs", "fn a() {}\n");
write("Cargo.toml", "[package]\nname = \"swx-bridge\"\nversion = \"9.9.9\"\n");
write(".gitignore", "/dist/\n/target/\n/out/\n/pagefiles/\n/rc-assets/\n/bin/\nwiki.log\n");
let main_url = "https://wiki.adom.inc/api/v1/pages/solidworks-bridge/files/adom-bridge-solidworks-manifest.json";
write("adom-bridge-solidworks-manifest.json", &format!("{{\"manifest_version\": 1, \"name\": \"solidworks\", \"version\": \"9.9.8\", \"url\": \"u\", \"sha256\": \"s\", \"size\": 1, \"updateManifestUrl\": \"{main_url}\"}}\n"));
std::thread::sleep(std::time::Duration::from_millis(1100));
write("target/x86_64-pc-windows-gnu/release/swx-bridge.exe", "EXE-v1");
let exe_sha = sha256_hex(b"EXE-v1");
let git = |args: &[&str]| {
let o = std::process::Command::new("git").args(args).current_dir(&dir).output().unwrap();
assert!(o.status.success(), "git {args:?}: {}", String::from_utf8_lossy(&o.stderr));
String::from_utf8_lossy(&o.stdout).trim().to_string()
};
git(&["init", "-q"]);
git(&["config", "user.email", "[email protected]"]);
git(&["config", "user.name", "t"]);
git(&["add", "-A"]);
git(&["commit", "-qm", "manifest: 9.9.8 as published"]);
let code_commit = git(&["log", "-1", "--format=%H", "--", "src", "resources", "Cargo.toml"]);
// ONLY the stub can be reached: ~/.local/bin (the real adom-wiki) is not on this PATH.
let path = format!("{}:/usr/bin:/bin", dir.join("bin").display());
let which = std::process::Command::new("bash").args(["-c", "command -v adom-wiki"]).env("PATH", &path).output().unwrap();
assert_eq!(String::from_utf8_lossy(&which.stdout).trim(), dir.join("bin/adom-wiki").display().to_string(), "the stub, and only the stub, is adom-wiki");
let run = |args: &[&str]| -> (bool, String) {
let o = std::process::Command::new("bash").arg("tools/release.sh").args(args).current_dir(&dir).env("PATH", &path).output().unwrap();
(o.status.success(), format!("{}{}", String::from_utf8_lossy(&o.stdout), String::from_utf8_lossy(&o.stderr)))
};
let wiki = || std::fs::read_to_string(&log).unwrap_or_default();
// ── THE CANARY ──
let (ok, out) = run(&["--canary"]);
assert!(ok, "--canary stages the candidate:\n{out}\n{}", wiki());
let calls = wiki();
let at = |needle: &str| calls.find(needle).unwrap_or_else(|| panic!("no {needle:?} in the wiki calls:\n{calls}"));
assert!(at("release create adom/solidworks-bridge 9.9.9-rc1") < at("release upload adom/solidworks-bridge 9.9.9-rc1 dist/swx-bridge-v9.9.9.zip --platform any"));
assert!(at("release upload adom/solidworks-bridge 9.9.9-rc1") < at("release verify adom/solidworks-bridge 9.9.9-rc1"));
assert!(at("release verify") < at("repo push adom/solidworks-bridge --files manifests/canary.json"));
let canary: serde_json::Value = serde_json::from_str(&std::fs::read_to_string(dir.join("manifests/canary.json")).unwrap()).unwrap();
let zip_sha = sha256_hex(&std::fs::read(dir.join("dist/swx-bridge-v9.9.9.zip")).unwrap());
assert_eq!(canary["version"], "9.9.9");
assert_eq!(canary["url"], "https://wiki.adom.inc/download/adom/solidworks-bridge/9.9.9-rc1/swx-bridge-v9.9.9.zip");
assert_eq!(canary["sha256"], zip_sha.as_str());
assert_eq!(canary["updateManifestUrl"], main_url, "the canary never pins the test machine: it points at the main manifest");
assert_eq!(canary["canary"]["rc"], "9.9.9-rc1");
assert_eq!(canary["canary"]["codeCommit"], code_commit.as_str());
assert_eq!(canary["canary"]["exeSha256"], exe_sha.as_str());
assert_eq!(git(&["log", "-1", "--format=%s"]), format!("canary: 9.9.9-rc1 (sha256 {}…, {} bytes)", &zip_sha[..8], canary["size"]), "the canary is committed");
assert!(git(&["status", "--porcelain"]).is_empty(), "and leaves the tree clean: the main manifest untouched");
assert!(out.contains("bridge_install '{\"manifestUrl\":\"https://wiki.adom.inc/api/v1/pages/solidworks-bridge/files/manifests/canary.json\"}'")
&& out.contains("python3 tools/cswa-check.py --target adom_drew2 --report auto")
&& out.contains("tools/release.sh --require-live"), "it prints the install, the live check and the publish:\n{out}");
// A second canary of the same version is the next candidate.
std::fs::write(&log, "").unwrap();
let (ok, out) = run(&["--canary"]);
assert!(ok && wiki().contains("release create adom/solidworks-bridge 9.9.9-rc2"), "the next candidate is rc2:\n{out}\n{}", wiki());
let canary: serde_json::Value = serde_json::from_str(&std::fs::read_to_string(dir.join("manifests/canary.json")).unwrap()).unwrap();
assert_eq!(canary["canary"]["rc"], "9.9.9-rc2");
// --canary publishes nothing else, and records only what is committed.
let (ok, out) = run(&["--canary", "--publish"]);
assert!(!ok && out.contains("run it alone"), "{out}");
std::fs::write(dir.join("resources/r.txt"), "r2\n").unwrap();
std::fs::write(&log, "").unwrap();
let (ok, out) = run(&["--canary"]);
assert!(!ok && out.contains("uncommitted changes to what the canary would hold") && !wiki().contains("release create"), "{out}");
git(&["checkout", "-q", "resources/r.txt"]);
// ── THE PUBLISH PROMOTES IT ──
let report = |exe: Option<&str>, zip: Option<&str>| {
let mut r = serde_json::json!({"tool": "cswa-check", "bridgeVersion": "9.9.9", "ran": 1, "passed": 1, "allPassed": true, "stopped": null,
"whenEpoch": 4_000_000_000u64, "when": "later", "summary": "1 of 1 CSWA parts within tolerance", "parts": []});
if let Some(e) = exe {
r["bridge"] = serde_json::json!({"exeSha256": e, "zipSha256": zip});
}
let p = dir.join("out").join(format!("report-{}.json", exe.map(|e| &e[..6]).unwrap_or("none")));
std::fs::write(&p, r.to_string()).unwrap();
p.display().to_string()
};
let rc2_sha = canary["sha256"].as_str().unwrap().to_string();
let refused = |what: &str, args: &[&str], needle: &str| {
std::fs::write(&log, "").unwrap();
let (ok, out) = run(args);
assert!(!ok && out.contains(needle), "{what} must be refused ({needle:?}):\n{out}");
assert!(!wiki().contains("release create adom/solidworks-bridge 9.9.9 "), "{what}: nothing was published:\n{}", wiki());
};
refused("a report of another exe", &["--require-live", &report(Some(&"0".repeat(64)), None), "--publish"], "not the one being published");
refused("a report that does not say which build it ran", &["--require-live", &report(None, None), "--publish"], "does not say which build it ran");
refused("a report installed from another zip", &["--require-live", &report(Some(&exe_sha), Some(&"1".repeat(64))), "--publish"], "not the canary being published");
// Code committed after the canary: the canary is stale.
std::fs::write(dir.join("src/a.rs"), "fn a() { let _ = 2; }\n").unwrap();
git(&["commit", "-qam", "a code change after the canary"]);
refused("a canary older than the code", &["--require-live", &report(Some(&exe_sha), Some(&rc2_sha)), "--publish"], "the code changed since the canary 9.9.9-rc2");
git(&["reset", "-q", "--hard", "HEAD~1"]);
// A canary of another version, and a canary whose record names another exe.
std::fs::write(dir.join("BRIDGE_VERSION"), "9.9.10\n").unwrap();
refused("a canary of another version", &["--require-live", &report(Some(&exe_sha), Some(&rc2_sha)), "--publish"], "the canary is 9.9.9-rc2 (version 9.9.9), not 9.9.10");
std::fs::write(dir.join("BRIDGE_VERSION"), "9.9.9\n").unwrap();
let real = std::fs::read_to_string(dir.join("manifests/canary.json")).unwrap();
std::fs::write(dir.join("manifests/canary.json"), real.replace(&exe_sha, &"2".repeat(64))).unwrap();
refused("a canary record that names another exe", &["--require-live", &report(Some(&exe_sha), Some(&rc2_sha)), "--publish"], "the canary record names exe");
std::fs::write(dir.join("manifests/canary.json"), &real).unwrap();
// The zip on disk is not the canary's, and the wiki cannot hand it back.
let good_zip = std::fs::read(dir.join("rc-assets/9.9.9-rc2.zip")).unwrap();
assert_eq!(sha256_hex(&good_zip), rc2_sha, "the stub kept the uploaded candidate");
std::fs::write(dir.join("dist/swx-bridge-v9.9.9.zip"), b"tampered").unwrap();
std::fs::remove_file(dir.join("rc-assets/9.9.9-rc2.zip")).unwrap();
refused("a zip that is not the canary's", &["--require-live", &report(Some(&exe_sha), Some(&rc2_sha)), "--publish"], "neither in dist/ nor downloadable intact");
// ...but the canary's own asset, downloaded, is.
std::fs::write(dir.join("rc-assets/9.9.9-rc2.zip"), &good_zip).unwrap();
std::fs::write(&log, "").unwrap();
let (ok, out) = run(&["--require-live", &report(Some(&exe_sha), Some(&rc2_sha)), "--publish"]);
assert!(ok, "a passing live report of the canary publishes it:\n{out}\n{}", wiki());
assert!(out.contains("PROMOTING the canary 9.9.9-rc2") && out.contains("downloading 9.9.9-rc2"), "{out}");
assert_eq!(sha256_hex(&std::fs::read(dir.join("dist/swx-bridge-v9.9.9.zip")).unwrap()), rc2_sha, "NO REBUILD: the zip that ships is the canary's, byte for byte");
let calls = wiki();
assert!(calls.contains("release create adom/solidworks-bridge 9.9.9 ") && calls.contains("release upload adom/solidworks-bridge 9.9.9 dist/swx-bridge-v9.9.9.zip --platform any"), "{calls}");
let man: serde_json::Value = serde_json::from_str(&std::fs::read_to_string(dir.join("adom-bridge-solidworks-manifest.json")).unwrap()).unwrap();
assert_eq!(man["sha256"], rc2_sha.as_str(), "the main manifest names the canary's bytes");
assert_eq!(man["url"], "https://wiki.adom.inc/download/adom/solidworks-bridge/9.9.9/swx-bridge-v9.9.9.zip");
assert_eq!(git(&["log", "-1", "--format=%s"]), format!("manifest: 9.9.9 as published (sha256 {}…, {} bytes)", &rc2_sha[..8], good_zip.len()));
assert!(out.contains(&format!("bridge_install '{{\"manifestUrl\":\"{main_url}\"}}'")), "it says how to re-point the test machine if it is still on the canary:\n{out}");
// A page that does not serve the canary just pushed is caught on the read-back.
// (The exe is rebuilt first: the reset above rewrote src/a.rs, newer than it.)
std::thread::sleep(std::time::Duration::from_millis(1100));
std::fs::write(dir.join("target/x86_64-pc-windows-gnu/release/swx-bridge.exe"), "EXE-v1").unwrap();
std::fs::write(dir.join("stale-page"), "").unwrap();
std::fs::write(&log, "").unwrap();
let (ok, out) = run(&["--canary"]);
assert!(!ok && out.contains("ERROR: the page does not serve this canary"), "a stale page must fail the canary:\n{out}");
let _ = std::fs::remove_dir_all(&dir);
}
/// A small, dependency-free sha256 for the canary test (the crate has no
/// sha2 dependency): the reference algorithm, FIPS 180-4.
mod sha2_free {
pub fn sha256_hex(data: &[u8]) -> String {
const K: [u32; 64] = [
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be,
0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa,
0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85,
0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f,
0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
];
let mut h: [u32; 8] = [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19];
let mut msg = data.to_vec();
let bits = (data.len() as u64).wrapping_mul(8);
msg.push(0x80);
while msg.len() % 64 != 56 {
msg.push(0);
}
msg.extend_from_slice(&bits.to_be_bytes());
for chunk in msg.chunks(64) {
let mut w = [0u32; 64];
for i in 0..16 {
w[i] = u32::from_be_bytes([chunk[4 * i], chunk[4 * i + 1], chunk[4 * i + 2], chunk[4 * i + 3]]);
}
for i in 16..64 {
let s0 = w[i - 15].rotate_right(7) ^ w[i - 15].rotate_right(18) ^ (w[i - 15] >> 3);
let s1 = w[i - 2].rotate_right(17) ^ w[i - 2].rotate_right(19) ^ (w[i - 2] >> 10);
w[i] = w[i - 16].wrapping_add(s0).wrapping_add(w[i - 7]).wrapping_add(s1);
}
let mut v = h;
for i in 0..64 {
let s1 = v[4].rotate_right(6) ^ v[4].rotate_right(11) ^ v[4].rotate_right(25);
let ch = (v[4] & v[5]) ^ (!v[4] & v[6]);
let t1 = v[7].wrapping_add(s1).wrapping_add(ch).wrapping_add(K[i]).wrapping_add(w[i]);
let s0 = v[0].rotate_right(2) ^ v[0].rotate_right(13) ^ v[0].rotate_right(22);
let maj = (v[0] & v[1]) ^ (v[0] & v[2]) ^ (v[1] & v[2]);
let t2 = s0.wrapping_add(maj);
v = [t1.wrapping_add(t2), v[0], v[1], v[2], v[3].wrapping_add(t1), v[4], v[5], v[6]];
}
for i in 0..8 {
h[i] = h[i].wrapping_add(v[i]);
}
}
h.iter().map(|x| format!("{x:08x}")).collect()
}
#[test]
fn the_test_sha256_is_the_real_one() {
assert_eq!(sha256_hex(b""), "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
assert_eq!(sha256_hex(b"abc"), "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
}
}