← Commit history

docs: 0.7.x — the bridge closes its own documents (save-to-recovery, verified on disk, then close by path) and quits under standing authorization; ecosystem row 45; 23 adversary passes

Drew Owens ·40a1231cbd ·1mo ago ·parent c28d266
4 files changed +167−2
README.md+88−1
@@ -267,6 +267,63 @@ bytes on disk, and then asserts SOLIDWORKS is left with **exactly** the documents it started with. It never opens, saves or closes a document it did not create, and it never launches or stops SOLIDWORKS. +## Closing and quitting++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."*++So `swx_shutdown {reason}` does the whole thing: it saves every open document into+a recovery folder, closes them, and quits SOLIDWORKS. It needs no write opt-in and+no per-document confirmation, because the authorization is standing.++The authorization is to CLOSE. It is not an authorization to lose anything, and+those are different permissions. What replaces the question is a sequence:++**nothing closes before it is on disk.**++Every document, the user's or the bridge's, saved or not, is written to+`<bridge cache>\exports\recovery\<yyyymmdd-hhmmss>\` and verified on disk+before anything closes. A document that could not be written is *not* closed —+closing a document whose only copy is in memory is the one outcome this design+exists to prevent, and the response names any such document in `remaining`. A+document the user had saved and then edited is copied into recovery under a new+name, so their file on disk is never modified and their unsaved changes survive+too. Because every document is clean by the time a close happens, a save prompt+cannot appear; if any dialog does appear, the sweep stops and reports it rather+than answering it.++"On disk" is measured in **bytes**, not existence. A full disk leaves a zero-byte+file behind and `exists()` says yes to it, so each recovery write is checked+against a minimum size and the size is published per document as `recoveryBytes`.+A file that holds nothing is not a save, and the document stays open.++The dialog check has a third answer besides *yes* and *no*: **unknown**. If the+window census cannot be read, this bridge cannot say whether a prompt is on+screen, and the two halves of the sweep treat that differently — saving loses+nothing, so the saves go ahead; closing while blind can lose everything, so+nothing closes. The response is `stoppedBy:"dialogCensusUnknown"` with+`dialogCensusReadable:false`, the work is already safe in the recovery folder, and+running it again when the census is readable finishes the job.++The saves also use `SaveAs3` with `swSaveAsOptions_Silent` **alone** — no `Copy`+bit — which *re-points* the open document at the recovery file. That is+deliberate: the close that follows is then closing a document whose entire content+is already on disk, and the user's original file is left exactly as it was. Both+halves are pinned by tests that read the original bytes back and read the close+key out of an ordering journal.++| Verb | What it does |+|---|---|+| `swx_shutdown {reason}` | preserve everything, close everything, quit. Idempotent. |+| `swx_close_all_safely {reason}` | the same sweep, without the quit |+| `swx_recover_list` | what each sweep preserved, and where — newest first |++`swx_stop` is unchanged and still quits only a session with zero documents open;+under `userAuthorized:true` it no longer requires a launch attribution (the user+may quit a session this bridge did not start) and the window-message rungs become+available, which matters when COM is wedged behind a licence dialog. It still+never kills anything.+ ## Install, launch, sign-in  This install is **SOLIDWORKS Connected / Makers (3DEXPERIENCE)**, and the first@@ -347,7 +404,37 @@ not.  ## Roadmap -- v0.6.9 (this): **two things the suite could not see.** The quit ladder+- v0.7.1 (this): **the close sweep, adversary-hardened.** Four things stood+  between "nothing is lost" and a claim anyone could check. The recovery write is+  now verified by SIZE through a constant both backends share, and the size is+  published as `recoveryBytes` — a zero-byte file is what a full disk produces and+  `exists()` passes it, which would have authorised closing a document whose only+  copy was in memory. An UNREADABLE window census is now its own outcome: the+  saves proceed, no document is closed, and the reply says+  `stoppedBy:"dialogCensusUnknown"` rather than implying a dialog check that never+  happened. The `SaveAs3` re-point is stated in the code and pinned by a test that+  reads the user's original bytes back byte-for-byte. And the simulator now models+  what makes the close ORDER matter — a component cannot close while its assembly+  holds it open, and a refusal is written down — so a sweep that asks in the wrong+  order fails the test instead of quietly succeeding on the retry. Each of the+  four is mutation-verified: disable the rule and exactly one test fails.+  316 tests.+- v0.7.0: **the bridge closes its own documents now.** Drew: *"I despise+  every time you ask for help closing. You MUST find/build a way to safely close+  the docs/program."* `swx_shutdown {reason}` saves every open document into a+  recovery folder, closes them all, and quits — no write opt-in, no per-document+  confirmation, no environment flag. The authorization is to CLOSE, not to lose+  anything, so what replaces the consent question is a sequence: **nothing closes+  before it is on disk**, verified per document, and anything that could not be+  written is deliberately left open and named. A document the user had saved and+  then edited is copied into recovery rather than saved over, so their file is+  never modified and their unsaved changes survive too. A save prompt cannot+  appear because nothing is unsaved by the time a close happens — and if a dialog+  appears anyway, the sweep stops and reports it rather than answering it.+  `swx_close_all_safely` is the sweep without the quit, `swx_recover_list` finds+  what was preserved, and `swx_stop {userAuthorized:true}` may now quit a session+  this bridge did not launch. 308 tests.+- v0.6.9: **two things the suite could not see.** The quit ladder   re-measures every precondition before every rung, and the launch-claim check is   part of that sweep — but nothing could demonstrate it, because the simulator's   attached pid was fixed for a whole run, so the FRONT GATE would always be what
SKILL.md+40
@@ -14,6 +14,46 @@ adom-bridge --ai-thread "<your thread>" swx_status '{}' adom-bridge --ai-thread "<your thread>" swx_bom '{"path":"C:\\designs\\gearbox.sldasm","format":"csv"}' ``` +## Closing and quitting: never ask the user to do it++- **The bridge never asks you to close.** `swx_shutdown {reason}` saves every open+  document into a recovery folder, closes them all, and quits SOLIDWORKS. No write+  opt-in, no per-document confirmation, no environment flag — the user has already+  authorized this standingly. It needs a reason, because the reason is written for+  them.+- **The safety property is a SEQUENCE, not a question: nothing closes before it is+  on disk.** Every document — theirs or the bridge's, saved or not — is written to+  `<bridge cache>\exports\recovery\<timestamp>\` and verified there before+  anything is closed. A document that could NOT be written is not closed, and the+  response names it in `remaining`.+- **The user's own files are never written over.** A document they had saved and+  then edited is copied into recovery under a new name; their file on disk is+  untouched and their unsaved changes still survive.+- **"On disk" is measured in bytes.** Each recovery file is checked against a+  minimum size and the size is reported per document as `recoveryBytes`. A full+  disk leaves a zero-byte file that `exists()` accepts; that is not a save, and+  the document stays open.+- **A save prompt cannot appear**, because nothing is unsaved by the time a close+  happens. If ANY dialog appears the sweep stops, reports it in `stoppedByDialog`,+  and closes nothing further. It is never answered.+- **If the window census cannot be READ, nothing closes.** Unknown is not "no+  dialog". The saves still run — saving loses nothing — but no document is closed+  and the reply carries `stoppedBy:"dialogCensusUnknown"`,+  `dialogCensusReadable:false`, `done:false`. The work is already safe in the+  recovery folder; tell the user that, and run it again when `swx_status` shows+  the session is readable.+- **`swx_close_all_safely {reason}`** is the same sweep without the quit, for when+  the user wants an empty session rather than no SOLIDWORKS.+- **`swx_recover_list`** shows what every sweep preserved and where, newest first.+  Tell the user this path when you close their work; `swx_open` reopens any of it.+- **`swx_shutdown` is idempotent.** SOLIDWORKS not running answers `success:true,+  alreadyStopped:true`. It is the last thing you do, and the last thing should be+  safe to repeat.+- **What did NOT change.** `swx_stop` still quits only a session with zero+  documents open; the ladder still refuses on a dialog, still measures the exit+  from the process census, and still never kills anything. The authorization moved+  the consent question, not the data-safety guarantees.+ ## Install, launch, sign-in  - **This edition cannot be launched by COM, so do not try.** The install here is
docs/ECOSYSTEM-TABLE.md+3−1
@@ -111,7 +111,9 @@ templates**, alisamsam **22**. | 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.)*+| 45 | **Closes its own documents without asking — and loses nothing doing it** (preserve-then-close: every open document written to a recovery folder and verified there BEFORE anything closes) | ✗ | **⚠ 0.7.1 mock-proven, NOT RUN LIVE** — `swx_shutdown {reason}` / `swx_close_all_safely {reason}` save every open document into `…\exports\recovery\<timestamp>\`, verify it, close parents before children, then quit; no write opt-in, no per-document confirmation (Drew, 2026-09-11: *"I despise every time you ask for help closing"*). The authorization is to CLOSE, not to lose anything, so the consent question is replaced by checkable rules: the recovery write is verified by SIZE against a shared constant and published as `recoveryBytes` (a zero-byte file is what a full disk leaves and `exists()` passes it); a document that could not be written is left OPEN and named in `remaining`; the user's saved-then-edited file is copied to recovery under a new name and their original is asserted byte-for-byte unchanged; `SaveAs3` uses `swSaveAsOptions_Silent` ALONE so the document RE-POINTS at the recovery file and the close key is read from the document after the save; a dialog stops the sweep and is never answered; and an UNREADABLE window census is its own outcome — saves proceed, nothing closes, `stoppedBy:"dialogCensusUnknown"`. Ordering is proven from a journal, not from the end state. Every rule is mutation-verified (disable it and exactly one test fails). **No live run yet** — first live use is `swx_close_all_safely` alone | ✗ | ✗ | ✗ | ✗ | ✗ |++*(45 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; rows 43–45 from the import-leak, quit and safe-close work — the bar is "significantly more powerful than the MCP servers", so every robustness capability gets a row with its evidence level, not a slogan.)*  ## Per-competitor notes 
docs/QUIT-INVESTIGATION.md+36
@@ -1680,6 +1680,42 @@ anywhere; `swSendToService.exe` is not attested and probably does not exist. **Connected specifics:** [CADmunity — Using SOLIDWORKS APIs with SOLIDWORKS Connected](https://forum.cadmunity.com/t/using-solidworks-apis-with-solidworks-connected-3d-experience/5439) · [swyftsol — How to Exit 3DEXPERIENCE PLM Services](https://support.swyftsol.com/knowledge-base/how-to-exit-3dexperience-plm-services) +### 5.12 What 0.7.0-0.7.1 changed above this ladder++Everything in §5.1-§5.11 still describes `swx_stop`, and none of it was relaxed.+What changed is that the ladder is no longer the only way a session ends. 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."*++`swx_stop` refuses while documents are open, and that refusal was correct — it is+the reason this whole investigation exists — but it made "please close your+documents" the bridge's standard answer. The fix is not to weaken the refusal. It+is to remove its cause: `swx_shutdown` preserves every open document to a recovery+folder, closes them, and only then enters this ladder, which by that point has+zero documents open and every precondition satisfied honestly rather than waived.++The consent question moves; the data-safety guarantees do not. The new sequence+carries its own rules, each one checkable and each one mutation-verified:++- **Nothing closes before it is on disk**, verified per document, and "on disk" is+  measured in BYTES against a shared constant (`recoveryBytes` in the reply) — a+  zero-byte file is what a full disk leaves behind and `exists()` accepts it.+- **A document that could not be written is left OPEN** and named in `remaining`.+- **The user's own file is never written over.** `SaveAs3` with+  `swSaveAsOptions_Silent` ALONE re-points the document at the recovery file, so+  their original is untouched and the close key read back from the document is the+  recovery path.+- **Parents close before children**, because a component cannot close while its+  assembly holds it — proven from an ordering journal, not from the end state.+- **A dialog stops the sweep and is never answered** (§5.6's rule, unchanged), and+  a dialog census that cannot be READ is its own outcome: saves proceed, nothing+  closes, `stoppedBy:"dialogCensusUnknown"`. UNKNOWN still refuses.++Under `userAuthorized:true` the ladder also no longer requires a launch+attribution — the user may quit a session this bridge did not start — and the+window-message rungs become available, which matters when COM is wedged behind a+licence dialog. It still never kills anything.+ **In this repo:** `docs/LAUNCH-RESILIENCE.md` (the attribution law, the kill allowlist, the launch state machine) · `docs/FUSION-PARITY-BRIEF.md` §"The gate was closed in the wrong direction" (the only ledger-backed decline we have) ·