app
SOLIDWORKS Bridge
Public Made by Adomby adom
Launch, model and quit SOLIDWORKS from any Adom container. Nothing is ever lost.
main
Drew Owens
0.9.0: bridge data moved out of the install cache (recovery survives updates), closed-in-place records, gates route thr…
5d02ca9
20d ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
//! ADVERSARIAL REVIEW OF 0.7.8 — pass thirty.
//!
//! THE MANIFEST IS SOUND ON EVERY POINT IT WAS BUILT FOR.
//!
//! * written after EVERY save (verbs3.rs:826) and EVERY close (:895), so a
//! bridge that dies mid-sweep leaves a record of everything decided up to
//! that document;
//! * atomic — `manifest.json.tmp` then `rename` — so a crash mid-write leaves
//! the previous complete manifest, never a truncated one that the listing
//! would believe;
//! * a crash between `SaveAs3` returning and the rewrite leaves a file with no
//! entry, and that falls to `reverified_entry`, which yields `verified: null`
//! — UNKNOWN — for a file that merely passes marker and floor. It returns
//! `false` only for the conclusive case. That is the right asymmetry and it
//! is the one I asked for in pass 29;
//! * a manifest whose `files` is not an array is rejected wholesale by the
//! `.filter()`, so a partial hand-edit degrades to re-verification rather
//! than being half-believed;
//! * a file the manifest names and the folder no longer holds is reported
//! `present:false`, `verified:false`.
//!
//! The JSON-lines protocol is also fine: `send` uses `serde_json::to_string`,
//! not `to_string_pretty`, so a `description` carrying quotes or newlines is
//! escaped into one line, and a BSTR is `from_utf16_lossy` before it is ever a
//! Rust string, so no invalid UTF-8 can reach the frame.
//!
//! What is left is one gap in each half, and they have the same shape: a fact
//! that was known is not carried to the place that needs it.
mod common;
use common::*;
use serde_json::json;
fn s(v: &serde_json::Value) -> String {
serde_json::to_string_pretty(v).unwrap_or_default()
}
/// SEVERITY 1 — THE VERDICT IS NOT BOUND TO THE BYTES IT WAS A VERDICT ABOUT.
///
/// Entries are matched to files by `name` and nothing else. The manifest records
/// `recoveryBytes` — what the file weighed when the sweep vouched for it — and
/// the listing reads `bytes` live from `metadata()`. **The two are never
/// compared.** So a file that changed after the sweep is reported with the
/// sweep's `verified:true` beside its new size, and `howToOpen` says to open it.
///
/// This is the one case where a wrong answer costs the user work, because
/// `verified:true` in a manifest means the document WAS CLOSED — these bytes are
/// the only copy. The realistic causes are not adversarial: a disk that filled
/// after the sweep, an antivirus quarantine that truncates in place, a sync
/// client mid-upload, a cleanup tool. The listing already handles the file being
/// GONE (`present:false`). A file that was emptied is the same event and is
/// reported as a rescue.
///
/// It is also the answer to "can `source:\"manifest\"` be spoofed": the honest
/// version is not an attacker with write access to the user's scratch — that
/// person has better options — it is that `source:"manifest"` reads as
/// provenance while nothing binds the record to the file. Comparing the two
/// numbers the manifest and the directory already hold closes both, with no new
/// data and no hash: if `bytes != recoveryBytes`, the sweep's verdict is about a
/// file that no longer exists, so report `verified: null` and say the file
/// changed. That is exactly the `reverified` rule 0.7.8 already established.
#[test]
fn a_manifest_verdict_does_not_survive_the_file_changing_underneath_it() {
let scratch = TempDir::new("adv29-bind");
let b = Bridge::start_in(TempDir::borrowed(&scratch.0), &[]);
let root = scratch.join("recovery").join("20260917-000000");
std::fs::create_dir_all(&root).unwrap();
let mut body = vec![0u8; 2_000_000];
body[4..8].copy_from_slice(&[0x00, 0x00, 0x00, 0x04]);
std::fs::write(root.join("bracket.SLDPRT"), &body).unwrap();
std::fs::write(
root.join("manifest.json"),
json!({
"manifestVersion": 1,
"sweep": "20260917-000000",
"files": [{
"name": "bracket.SLDPRT",
"recoveryPath": root.join("bracket.SLDPRT").to_string_lossy(),
"title": "bracket.SLDPRT",
"kind": "part",
// VOUCHED FOR AT TWO MEGABYTES, and the document was CLOSED
// against it. These bytes are the only copy.
"recoveryBytes": 2_000_000,
"signatureOk": true,
"verified": true,
"preserved": true,
"closed": true,
}],
})
.to_string(),
)
.unwrap();
// The sweep is over. The disk fills, or a scanner quarantines in place, and
// the file is truncated. Marker intact, 900 bytes of a 2 MB document.
std::fs::write(root.join("bracket.SLDPRT"), &body[..900]).unwrap();
let r = b.call("swx_recover_list", json!({}));
let f = r["sweeps"][0]["files"][0].clone();
assert!(f["source"] == json!("manifest"), "precondition: read from the manifest: {}", s(&f));
assert!(f["bytes"] == json!(900), "precondition: the live size is read: {}", s(&f));
assert!(
f["verified"] != json!(true),
"the listing reports verified:true for a file that is 900 bytes, using a verdict the \
sweep reached when it was 2,000,000 — a number the manifest itself records as \
recoveryBytes, right next to the live size this verb just read. Nothing compares them. \
verified:true in a manifest means the DOCUMENT WAS CLOSED, so this file is the only copy \
of that work and the user is being told it is intact. The verb already reports a file \
that vanished as present:false; a file that was emptied is the same event. Entry: {}",
s(&f)
);
}
/// SEVERITY 2 — THE SWEEP RECORDS THE PROSE AND DROPS THE STRUCTURE.
///
/// 0.7.8 exists so a caller can read the translator's own HRESULT instead of
/// guessing from a sentence, and `sidecar_error` carries `e.data` through as
/// `hostError`. But `swx_close_all_safely` does not go through `sidecar_error`
/// for a host failure. It builds its own entry, at verbs3.rs:807 and again at
/// :881:
///
/// ```ignore
/// Err(e) => {
/// close_failed.push(json!({"why": format!("the com host errored: {}", e.message)}));
/// break 'sweep;
/// }
/// ```
///
/// `e.data` is discarded at the construction site, so no structured detail can
/// reach the caller from this verb no matter what the host sends. The same holds
/// at verbs3.rs:1408, :1753 and :2143, which build `{"why": e.message}` for
/// per-document failures.
///
/// It matters here more than in `swx_import`, for two reasons. The sweep is the
/// verb that runs when SOLIDWORKS is already misbehaving, so it is the one most
/// likely to meet an HRESULT worth reading. And its records are now written into
/// `manifest.json` and kept — so a prose-only verdict is not merely harder to
/// diagnose in the moment, it is the permanent record of why a document was not
/// preserved.
///
/// The test asks only that the field EXIST on a host-failure entry. It cannot be
/// null-because-the-host-sent-nothing here; it is absent because nothing
/// constructs it.
#[test]
fn a_sweep_that_fails_on_the_host_carries_the_structured_reason() {
let scratch = TempDir::new("adv29-hosterr");
std::fs::write(scratch.join("mock-com-timeout"), "").unwrap();
let machine = TempDir::new("adv29-hosterr-machine");
std::fs::write(machine.join("windows.json"), "[]").unwrap();
let b = Bridge::start_in(
TempDir::borrowed(&scratch.0),
&[("SWX_LAUNCH_FAKE_DIR", &machine.path())],
);
let r = b.call("swx_close_all_safely", json!({"reason": "closing"}));
let failed = r["closeFailed"].as_array().cloned().unwrap_or_default();
assert!(!failed.is_empty(), "precondition: the host failed the sweep: {}", s(&r));
let e = &failed[0];
assert!(
e["why"].as_str().unwrap_or("").contains("com host errored"),
"precondition: this is the host-failure entry: {}",
s(e)
);
assert!(
e.get("hostError").is_some(),
"the sweep's host-failure entry carries only prose. verbs3.rs:807 and :881 build \
{{\"why\": format!(\"the com host errored: {{}}\", e.message)}} and discard e.data, so \
the structured COM detail 0.7.8 was built to surface cannot reach the caller from this \
verb whatever the host sends. This is the verb that runs when SOLIDWORKS is already \
misbehaving, and since 0.7.8 its records are persisted into manifest.json — so a \
prose-only verdict is the permanent record of why a document was not preserved. \
Entry: {}",
s(e)
);
}