app
SOLIDWORKS Bridge
Public Made by Adomby adom
Launch, model and quit SOLIDWORKS from any Adom container. Nothing is ever lost.
← Commit history
docs: 0.5.5 live degrade proof + 0.5.6 drain closes by path (a title is not an identity); ecosystem rows 37/43
4 files changed
+106−3
README.md+35−2@@ -269,7 +269,40 @@ not create, and it never launches or stops SOLIDWORKS. ## Roadmap -- v0.5.4 (this): **adversary round 8 — the anchor was right, its failure was+- v0.5.6 (this): **a title is not an identity — and the drain's CLOSE was the one+ place that rule was missing.** Victims were chosen by PATH and then closed with+ `CloseDoc(title)`, which resolves by name and does not prompt: on Drew's live+ session, leaked temps titled `First.step-1.SLDPRT` sit beside his SAVED+ `…\validation\First.step-1.SLDPRT`, so SOLIDWORKS would have chosen which one+ died while the response recorded the temp as closed either way. The 254 green+ tests said nothing about it because the mock removed documents by a unique+ internal key. Now: the victim is closed by its full path; any victim whose title+ another OPEN document shares is refused and reported in `titleCollisions` with+ every path that shares it; every close is verified with+ `GetOpenDocumentByName`, and a close that reports success while the document is+ still open stops the drain and fails loudly as `wrong_document_closed`. The mock+ models `CloseDoc`'s by-name resolution, so a close-by-title now fails the suite.+ A walk that ended early (a `GetNext` on an invalidated iteration) is no longer+ reported as complete: `walkComplete` / `walkStopped` are measured and `done`+ requires reaching the end. 257 tests.+- v0.5.5: **the degrade path was itself an unbounded marshal.** Found on+ the live 2144-document session the hour 0.5.4 was deployed: `swx_docs` still+ timed out at 30 s and the com host was killed, while `GetDocumentCount`+ answered in 0.7 s. Above the cap the code called `GetDocuments` — one call that+ marshals every handle — and then `GetPathName` on all 2144 of them to fill a+ field named `importTempsInSample`. The drain had the same shape, so it would+ have wedged on the very session it exists to drain. Now everything walks:+ `GetFirstDocument()`/`GetNext()`, one document at a time, bounded by a count and+ a time box; the sample costs the sample; the drain scans a bounded window and+ reports `documentsScanned`; `documents()` refuses above the cap rather than+ letting a caller find the wedge; `close_all_documents` still closes a flooded+ session but reports counts with null lists instead of empty ones. And the mock+ can now be SLOW (`SWX_MOCK_DOC_LATENCY_MS`, `SWX_MOCK_OPEN_DOCS`), because the+ reason every test passed is that its enumeration was instant — the regression+ test holds the live shape: 2144 documents, cap 200, 15 ms each, `swx_docs` in+ under 3 s. Ecosystem row 37 is marked *mock-proven; failed live; re-proof+ pending* until it is re-run on Drew's session. 254 tests.+- v0.5.4: **adversary round 8 — the anchor was right, its failure was silent.** The drain compares a path SOLIDWORKS reported against a `%TEMP%` this process resolved, and the two can legitimately differ (ab may run under a service profile or a redirected TEMP, and the leaked temps were written by an@@ -288,7 +321,7 @@ not create, and it never launches or stops SOLIDWORKS. The 8.3 rule is now exact rather than merely tight: the mangled form needs its six-character head, the COLLISION form (`DREW~1` for `drew`, which is what a domain-joined machine really produces beside `drew.ADOM`) needs the whole name,- and a loose prefix is rejected. 250 tests.+ and a loose prefix is rejected. - v0.5.3: **adversary round 7 — the once-swept precondition, the one-sample exit, and the anchor that undid itself.** The drain's `%TEMP%` anchor was OR-ed with a shape test, which made it advisory: any path whose tail read
SKILL.md+13@@ -293,6 +293,19 @@ adom-bridge --ai-thread "<thread>" swx_save_as '{"outputPath":"C:\\parts\\bracke re-measured before EVERY rung, so a multi-rung ladder can refuse in the middle (`refusedMidLadder:true`) if a document appears while a rung is settling, and it never continues against a different process (`stop_identity_changed`).+- **On a flooded session, every list is a SAMPLE and says so.** Above the+ enumeration cap `swx_docs` returns `{count, truncated:true, sample,+ importTempsInSample, importTempsInSampleOf}` and NO `docs` list — it walks ten+ documents, it does not enumerate thousands (doing that killed the com host on a+ live 2144-document session). `swx_close_all_documents` on such a session still+ closes, but reports `closed`/`remaining` as **null** with counts, and names the+ unknown in `skipped`. Never read a null list as "nothing there".+- **The drain never closes a document whose title another open one shares.** It+ closes by full path and verifies each close, but `CloseDoc` resolves by name — so+ a shared title is refused and reported in `titleCollisions` (with every path that+ shares it) rather than letting SOLIDWORKS pick. The user decides: close it+ themselves, or save the other document under another name. `done:false` while any+ collision stands. - **`swx_close_import_temps` needs a resolved `%TEMP%`, and says so.** The drain identifies a temp by its LOCATION under this user's resolved temp directory — that path is the whole proof, because the close does not prompt. A path that
docs/ECOSYSTEM-TABLE.md+1−1@@ -108,7 +108,7 @@ templates**, alisamsam **22**. | 40 | **Crash-reporter recovery** (click "Restart SOLIDWORKS Design" once, structured match, ledgered) | ✗ | **shipped, DISABLED by default** until the real control-tree reply is captured | ✗ | ✗ | ✗ | ✗ | ✗ | | 41 | **COM retry-on-reject** (busy host: RPC_E_CALL_REJECTED / RETRYLATER backoff on every call) | ? | **✓ live 0.3.3** — 50-read smoke gate, 0 under-reporting, 11 rejects absorbed | ✗ | ✗ | ✗ | ✗ | ✗ | | 42 | **Real-work proof** (multi-assembly build + BOM exported from the assembly, verified against a packet) | ✓ (electronics) | **✓ live** — NSF crate: 5 assemblies, 383 placements, 32-row BOM, 28/28 part numbers match | ✗ | ✗ | ✗ | ✗ | ✗ |-| 43 | **Import / resource lifecycle — no leaked temps, drainable, enumeration never wedges** | ✗ | **✓ 0.5.0** — enumeration degrades above a cap (`{count,truncated,sample}`, never a 30s timeout+kill); `swx_close_import_temps` drains STEP/IGES `…\Temp\swx<pid>\IC~~\…` temps in the DEFAULT posture (path is the ownership proof, survives a pid change); import asserts a net +1. Root-caused a live 1342-doc / 8 GB leak. **0.5.3**: the resolved `%TEMP%` is the WHOLE location proof (8.3-aware, no shape-test fallback), and with no resolved root the drain refuses (`temp_root_unknown`) instead of closing nothing quietly. **0.5.4**: the report says which root it compared against (`tempRoot`) and counts documents that carry the launcher's tail but sit elsewhere (`importTempsElsewhere` + a sample), so a drain that matched nothing can no longer claim it drained everything — the mismatch case is real, because ab's `%TEMP%` need not be the one SOLIDWORKS used | ✗ | ✗ | ✗ | ✗ | ✗ |+| 43 | **Import / resource lifecycle — no leaked temps, drainable, enumeration never wedges** | ✗ | **✓ 0.5.0** — enumeration degrades above a cap (`{count,truncated,sample}`, never a 30s timeout+kill); `swx_close_import_temps` drains STEP/IGES `…\Temp\swx<pid>\IC~~\…` temps in the DEFAULT posture (path is the ownership proof, survives a pid change); import asserts a net +1. Root-caused a live 1342-doc / 8 GB leak. **0.5.3**: the resolved `%TEMP%` is the WHOLE location proof (8.3-aware, no shape-test fallback), and with no resolved root the drain refuses (`temp_root_unknown`) instead of closing nothing quietly. **0.5.4**: the report says which root it compared against (`tempRoot`) and counts documents that carry the launcher's tail but sit elsewhere (`importTempsElsewhere` + a sample), so a drain that matched nothing can no longer claim it drained everything. **0.5.6**: the victim is CLOSED by the path it was chosen by (it was closed by TITLE, which `CloseDoc` resolves by name without prompting — a live session held temps sharing a saved file's title), a shared title is refused and reported, and every close is verified by path — the mismatch case is real, because ab's `%TEMP%` need not be the one SOLIDWORKS used | ✗ | ✗ | ✗ | ✗ | ✗ | | 44 | **Graceful QUIT that is measured, not assumed** (never a kill, never answers a dialog, proves the exit from the process census) | ✗ | **⚠ 0.5.3 mock-proven, UNPROVEN LIVE** — a rung ladder (a `ExitApp` → f Restart Manager *without* `RmForceShutdown`), pinned at rung (a); 7 re-measured preconditions where UNKNOWN refuses and `force` waives the UNSAVED question ONLY; every COM reference released before and after each rung, with outstanding refs named in the report; success = *this pid AND this creation time* absent from the census, never `GetActiveObject`; per-session children reaped while the documented platform daemons are left alone; a dialog stops the ladder permanently and is never answered; nothing disowned until the exit is verified. every precondition re-measured before EVERY rung (a mid-ladder refusal is a real outcome), the exit POLLED to the rung's budget rather than sampled once (a real `ExitApp` returns before the process goes), the dialog census covering the `swCefSubProc` web-view children by parentage, the count cross-checked against the enumeration, and the message rungs re-verifying window ownership before they send. `tools/quit-gate.py` proves the session was bridge-launched and has not been run | ✗ | ✗ | ✗ | ✗ | ✗ | *(42 numbered rows; rows 29–31 are the N/A block that accounts for most of the Fusion verb-count gap. Rows 36–42 added 2026-09-08 from the crate build and the launch-resilience rounds — the bar is "significantly more powerful than the MCP servers", so every robustness capability gets a row with its evidence level, not a slogan.)*
docs/IMPORT-LEAK.md+57@@ -323,3 +323,60 @@ The lesson is the one this project keeps relearning, in a new place: **a mock that cannot exhibit the failure cannot prove the fix.** The cap was tested; the cost of the code under the cap was not, because nothing in the mock had a cost at all.++## Adversary round 9 → a title is not an identity, and the drain's CLOSE was the one place the rule was missing (0.5.6)++The rule has been enforced everywhere else in this bridge for four releases:+resolution puts `path` before `docName`, `swx_docs` refuses an ambiguous title+with `ambiguous_target`, ownership rows are keyed on the path when there is one,+`same_path` exists for exactly this reason. The drain broke it at the last step:++```rust+let mut victims: Vec<(String, String)> = …; // chosen BY PATH+for (title, path) in victims {+ if sw.call("CloseDoc", &[&Var::bstr(&title)]).is_ok() // …closed BY TITLE+```++`ISldWorks::CloseDoc` resolves a document **by name** and does not prompt.+SOLIDWORKS titles are not unique across paths, and Drew's live session holds+exactly the collision that matters: leaked conversion temporaries titled+`First.step-1.SLDPRT` open beside his **saved**+`C:\Users\drew\validation\First.step-1.SLDPRT`. Asking by that title lets+SOLIDWORKS choose which one dies — and the response would have recorded the+temp's path as closed either way.++**The 254 green tests were not evidence about the close key**: the mock's drain+removed the document by its own unique internal key, so it always closed exactly+the right one. Same failure of the mock as pass 8 (the root always agreed) and+pass 5's F3 (the path was always empty), in a new place.++0.5.6:++* **the victim is closed by the identity it was chosen by** — its full path. For+ an import temporary `GetPathName` IS the `IC~~` path, which is what qualified it+ in the first place. Never the bare title, here or in `close_doc` (which passes+ the path whenever the document has one; `resolve` already refused an ambiguous+ title before it).+* **a title collision is refused, not resolved.** Every document the walk passes+ contributes `(title, path)` — the title is read for all of them, not just the+ victims, which is one extra call per document inside an already bounded window —+ and any victim whose title another open document shares is skipped and reported+ as `titleCollisions: [{title, paths}]`. The hint says what only the user can+ decide: close it yourself, or save the other document under another name. The+ scan deliberately does **not** stop at `max`, because stopping it would blind+ the collision check to a document later in the window.+* **every close is verified by path.** `GetOpenDocumentByName(path)` is one call:+ after a close that reported success, the document must be gone. If it is still+ open then something else was closed, and the drain **stops immediately** and+ reports `wrong_document_closed` as an error with everything measured. That+ branch must be unreachable; the check is the proof rather than the hope.+* **the mock now models `CloseDoc`'s by-name resolution** — a full path matches+ only itself, a title resolves to the FIRST open document carrying it — so+ `a_saved_document_sharing_a_temps_title_survives_the_drain` is a real test+ instead of a pin, and a future close-by-title would fail the suite.+* **a walk that ended early is not reported as complete.** `walk_documents` now+ returns WHY it stopped (`reachedEnd` / `hitMax` / `timedOut` /+ `iterationFailed`), because a `GetNext` that fails on an invalidated iteration —+ a document closed under us — is not the end of the session. `walkComplete` and+ `walkStopped` are in the response and `done` requires `reachedEnd`, no+ collisions and no failed closes.