main
Drew Owens 0.3.3 source + crate build: BOM exported from the SOLIDWORKS assembly, build log §1–11, release script 477e64f 1mo ago

three-musketeers build — solidworks-bridge Rust port — 2026-09-01

Task: Port the Adom SOLIDWORKS bridge from Python + PowerShell to a single native Rust binary, at top-tier quality (kicad/pup/native-browser bar). Stage: Compiled + mock-tested on Linux; Windows COM backend compile-verified, awaiting a real SOLIDWORKS install for live shakedown (trial serial pending from Hawk Ridge). Rounds: 2 (build → adversary → fix → verify + regression-adversary → final tightening → converge). Models: Builder Opus · Adversary Opus (fresh context each round) · Verifier Sonnet. Plus a parity-diff pass (Sonnet) against the deleted Python implementation.

What was built

One binary, swx-bridge (spawn.kind:"exe", ~10ms cold start, no interpreter to provision), replacing server.py + com_host.py + the PowerShell COM sidecar. Same 23 swx_* verbs, same response shapes, same error codes, same mock data as v0.1.

  • Two modes in one exe. HTTP server (tiny_http + serde) that ab drives; and --com-host, a child process the server spawns from its own current_exe(), speaking JSON-lines over stdio. One file to ship, one killImageName for both.
  • COM in a killable child, not a thread. A wedged COM call (SOLIDWORKS on a modal, a runaway rebuild) can't be killed as a thread but can as a process. The server owns the child's lifecycle: lazy spawn, one request in flight, per-request timeout, kill + respawn on crash/hang, stderr tail surfaced in the failing verb's detail. Real COM is late-bound IDispatch via the windows crate (GetActiveObject before CoCreateInstance, STA), behind a Backend trait with a MockBackend twin so the whole surface tests with no SOLIDWORKS and no Windows.
  • Verb families: file management (find/open/docs/save/close/export STEP·IGES·STL·PDF·DXF·DWG·PNG·JPG·Parasolid/pack-and-go), custom properties (get/set), BOM (per-file+configuration quantities with custom-property columns, JSON or CSV to disk), analysis (mass properties, interference, rebuild-health census), plus status/readiness/describe/state/progress/screenshot/com_eval/uninstall.
  • SDK etiquette throughout: _hint on every response, _reportIssues on failures, windowEtiquette:"background", terminal progress frames + a live swx_progress, one log line per verb with caller identity, 48h/300-file scratch prune, an swx_uninstall that removes only the bridge's own exports dir.

How to run / exercise it

cd /home/adom/project/solidworks-bridge
cargo build                                            # linux debug (mock mode)
cargo test                                             # 40 unit + 4 e2e + 5 protocol
cargo build --release --target x86_64-pc-windows-gnu   # -> target/.../release/swx-bridge.exe (~851K)
SWX_MOCK=1 SWX_SCRATCH_DIR=/tmp/swx ./target/debug/swx-bridge --port 24695 &
curl -s localhost:24695/status
curl -s localhost:24695/command -d '{"command":"swx_bom","args":{"format":"csv"}}'

Plan amendments (builder was right where the plan was wrong)

Recorded in full in docs/RUST-PORT-BRIEF.md → "Build notes — 2026-09-01". Highlights:

Plan assumed Builder did instead Why
Adopt hello-rust's release profile wholesale (incl. panic="abort") Dropped panic="abort" The com host catches panics and answers them as errors — that needs unwinding. A bad late-bound call should cost one verb, not the user's open documents.
"next call respawns the child" is enough crash recovery Reap-before-write + a single retry limited to pre-execution failures The naive reading still costs one failed verb; a mid-request death is reported, never silently re-run (re-running an export behind the caller's back is worse than an error).
swx_com_eval runs raw PowerShell/script A late-bound call-chain evaluator ($sw/$doc dotted chains, literal args), parser outside cfg(windows) so it unit-tests here No script host on the live machine; auditable; testable on Linux.
platform: "win32" (Python's sys.platform) platform: "windows" Cleaner; documented as an intentional value change, not silent drift.

Adversary findings resolved

Round 1: 1 Critical, 6 High, 8 Med, 7 Low — all fixed or rebutted with reason. Round 2 (regression): 0 substantive, 5 Low residuals + 1 doc nit — all fixed. Selected:

Finding Location Sev Resolution
VARIANT double-free: arg VARIANTs bitwise-copied into a Vec<VARIANT> — the windows 0.62.2 VARIANT DOES have drop glue, so every BSTR/IDispatch arg was freed twice (heap corruption on first real swx_open) variant.rs Critical Fixed: DISPPARAMS args are Vec<ManuallyDrop<VARIANT>>; whole Var ownership model re-audited to one-AddRef/one-Release. Round-2 adversary confirmed no double-free and no under-free/leak, compile-verified for the Windows target.
find_files returned OLDEST files when limit tripped (truncated in walk order, then sorted) findfiles.rs High Fixed: bounded size-limit min-heap by mtime → correct newest-first AND O(limit) memory.
swx_uninstall remove_dir_all'd ab's own cache root (the running exe) verbs.rs, scratch.rs High Fixed: scoped to <cache>/exports, best-effort per item; e2e asserts the cache root survives.
swx_status (the statusVerb ab polls after a timeout) blocked behind the com-host mutex verbs.rs, comhost/mod.rs High Fixed: try_lock snapshot (busy/hostPid/inFlight); poison distinguished from contention so it can't lie busy after a panic.
No panic isolation in HTTP workers → 500 + permanent worker loss + process death server.rs High Fixed: two-layer catch_unwind → JSON success:false + _reportIssues; worker survives.
swx_bom labelled risk:"read" but wrote a caller-chosen file (no relay-caller approval prompt) verbs.rs High Fixed: relabelled write; safe_out refuses relative/../drive-relative out on bom-csv, export, pack-and-go.
Interference Done() skipped on panic; scalar SAFEARRAYs read as []; non-silent OpenDoc fallback; silent-zero rebuild errors; mock/COM eval drift; swx_stop reported failure after quitting com.rs, variant.rs, mock.rs Med All fixed (Drop guard; scalar→1-elem coercion + VT_UNKNOWN; fallback removed; unreadableFeatures; mock runs the real parser; ExitApp RPC-disconnect = success).
Round-2 residuals: drive-relative C:foo admitted; status busy-forever after poison; find_files unbounded buffer; watchdog STILL_ACTIVE/259 misread + pid-reuse race; doc nit verbs.rs, findfiles.rs, host.rs Low All fixed (separator-required absolute check; poison-aware snapshot; bounded heap; WaitForSingleObject; pid-reuse documented as benign).

Rebutted/kept with reason: coverage gate stays call-based (the three-way bridge.json↔describe↔calls check already enforces it); topLevelOnly default false kept (it's the documented default, echoed back); free_port TOCTOU left (single-instance guard makes any collision a clean failure).

Behavioral verification (Verifier, Sonnet — drove the real binary over HTTP)

Flow exercised What the verifier did Result Notes
Health + describe GET /status; swx_describe PASS led/summary/tooltip, mock:true, all 23 verbs listed
Full CAD flow start→open asm→bom(json+csv)→mass_props→interference→rebuild→export STEP→pack-and-go→props get/set/save→close→stop PASS CSV parsed with Material/PartNo columns; STEP had real bytes; props round-tripped (dirty:true→save→dirty:false)
find_files ordering real .sldprt/.sldasm/.slddrw + ~$lock + .txt fixtures, distinct mtimes PASS limit:2 returned the two NEWEST; lock + .txt excluded; subdir descended; query/types correct
Error paths unknown verb, malformed JSON, 9MB body, com_eval w/o reason, bom/interference on a part, .. csv out PASS 400 / 413 / errorCode:"reason_required" / clean assembly-required failure / errorCode:"unsafe_path"
Panic recovery SWX_TEST_PANIC=swx_docs then call, then swx_status PASS clean JSON error, server survived, next request succeeded
Slow status SWX_MOCK_SLOW_MS=6000, swx_start in background, poll swx_status PASS status answered in 0.013s with busy snapshot while start took the full ~6s
Uninstall scoping populate exports/ + sibling dirs, swx_uninstall PASS removed only exports/, siblings + root untouched
Full suite + cross-compile cargo test; windows-gnu release build PASS 49 passed / 0 failed / 1 ignored; swx-bridge.exe produced, valid MZ PE header

Open items / human calls

  • The Windows COM backend has never executed. It compiles clean for x86_64-pc-windows-gnu and the double-free class is fixed, but marshalling correctness (late-bound byref [out] params, SAFEARRAY-vs-scalar return shapes, GetErrorCode2's signature) can only be flushed by a live run. Round-2 adversary confidence: ~75% on memory-safety, residual risk is functional/marshalling. First live-run order to exercise the risky marshalling: swx_open (byref errors/warnings) → swx_props_get (Get4 double-byref) → swx_interference (single vs multiple) → swx_pack_and_go. Removing the non-silent OpenDoc6 fallback is the single biggest first-contact risk — watch that swx_open still succeeds on a doc with missing references.
  • Watchdog wedged-child test is #[ignore]d (PID-namespace-sensitive sandbox); the mechanism is code-verified and the graceful-orphan path is covered. Confirm on a real Windows host that the parent-liveness poll + taskkill path fires when the server is hard-killed mid-COM-call.
  • Not done (deliberate, later steps): publish (release zip + manifest + skills pkg to wiki adom/solidworks-bridge per the adom-bridge-sdk-publish flow), hero image, and the v0.3 roadmap (drawing-table BOM extraction, swx_show_* family, measured cold/warm start history).
  • Two cosmetic verifier notes (not defects): the generic COM-error _hint also shows for caller-side validation failures (e.g. bom-on-a-part); swx_com_eval grammar requires a $sw./$doc. prefix, enforced and explained but worth a doc example.