← Commit history

ecosystem row 37: degrade live-proven on 2144 docs (0.5.5)

Drew Owens ·8a49dd70ab ·1mo ago ·parent 1e43f88
2 files changed +59−1
docs/ECOSYSTEM-TABLE.md+1−1
@@ -102,7 +102,7 @@ templates**, alisamsam **22**. | 34 | **Deny-by-default write gate** | ✗ no gate, no confirm/reason in its catalog | **✓ live — unique. Two independent layers, 8 adversarial rounds, held in all 3 live runs** | ✗ | ✗ | partial ("adapter safety wrappers") | partial (macro exec needs permission) | ✗ | | 35 | Host-ecosystem integration (Adom Bridge, Activity Log, skill) | ✓ native | **✓ native** | ✗ MCP clients only | ✗ | ✗ | ✗ | ✗ | | 36 | **Ownership persists across bridge/host restarts** (bridge-created docs stay closable without opt-in) | ✗ (no ownership model) | **✓ live 2026-09-08** — `owned-docs.json` keyed to the SOLIDWORKS process; proven create → host kill → close in default posture | ✗ | ✗ | ✗ | ✗ | ✗ |-| 37 | **Honest UNKNOWN instead of a fabricated empty/zero** (doc list, counts, dialogs, interference) | partial | **✓ 0.4.x** — `docs_enumeration_failed`, `Option` counts, `unsaved_unknown`, `count:null` semantics in hints; 29 adversary tests | ✗ | ✗ | ✗ | ✗ | ✗ |+| 37 | **Honest UNKNOWN instead of a fabricated empty/zero** (doc list, counts, dialogs, interference) | **✓ LIVE 2026-09-09** — on a real 2144-document session `swx_docs` answers in 3.4 s with `count:2144, truncated:true`, a 10-doc sample (8 import temps) — after 0.5.4 wedged 30 s on the same session (fixed 0.5.5: bounded GetFirstDocument/GetNext walk, never GetDocuments above the cap; simulator now charges per-doc latency) | **⚠ 0.4.x-0.5.5, mock-proven; the ENUMERATION half FAILED LIVE** — `docs_enumeration_failed`, `Option` counts, `unsaved_unknown`, `count:null` semantics in hints; 29 adversary tests. **Live 2026-09-09: `swx_docs` still timed out at 30s and the com host was killed on a 2144-document session** — the degrade path called `GetDocuments` (one call, every handle) and then `GetPathName` on all 2144. `GetDocumentCount` answered in 0.7s. The mock's enumeration was instant, so this row was mock-proven and false live. **Fixed in 0.5.5** (walk `GetFirstDocument`/`GetNext` for the sample only, time-boxed; the drain walks a bounded window too) with the live session held in the suite via a per-document latency model — **re-proof on the live 2144-doc session pending** | ✗ | ✗ | ✗ | ✗ | ✗ | | 38 | **Hung / exhausted host detection, and BUSY vs DEAD** (Not Responding + real COM timeout; NewDocument-returns-nothing; CPU/memory delta over a sample) | ✗ | **✓ 0.5.1** evidence-based `host_app_hung` / `host_app_exhausted` + `verdict:"busy"\|"dead"\|"unknown"` from `cpuActiveSeconds`/`memoryDeltaMB` — measured live: 17.9 GB not-responding for ~5 min at ~70% of a core and it RECOVERED, so "not responding" now advises WAIT, not "end the task". One reading never says dead | ✗ | ✗ | ✗ | ✗ | ✗ | | 39 | **Stalled-launcher recovery with attribution law** (kill only a chain proven ours by handle; never daemons, never sldworks) | ✗ | **✓ shipped 0.4.4**, 4 adversary rounds, **live gate pending** | ✗ | ✗ | ✗ | ✗ | ✗ | | 40 | **Crash-reporter recovery** (click "Restart SOLIDWORKS Design" once, structured match, ledgered) | ✗ | **shipped, DISABLED by default** until the real control-tree reply is captured | ✗ | ✗ | ✗ | ✗ | ✗ |
docs/IMPORT-LEAK.md+58
@@ -265,3 +265,61 @@ six-character head (`ADVERS~1` for `adversary`, so `a~1` is rejected), the collision form requires the whole name (`DREW~1` for `drew`), and neither admits a loose prefix — which was the actual looseness worth removing. Pass 7's positive control stays green because the case it pins is real.++## LIVE, 2026-09-09 — the degrade path was itself an unbounded marshal (0.5.5)++0.5.4 was deployed to the dev bridge and `swx_docs` was run against Drew's real+session: **2144 documents open, and it still timed out at 30 s and the com host+was killed.** `GetDocumentCount` had answered in 0.7 s.++The cap was doing its job and the code under it was not. Above the cap,+`cmd_docs` did this:++```rust+let all = sw.call("GetDocuments", &[])?.as_array();   // EVERY handle, one call+let sample = all.iter().take(DOC_SAMPLE)…             // …to look at ten+let temps  = all.iter().filter(|d| GetPathName…)      // …then 2144 round trips+```++`GetDocuments` marshals the whole array in a single call and did not return+within the budget; the `temps` loop then added one `GetPathName` per document —+2144 more round trips — to compute a number reported in a field literally named+`importTempsInSample`. **The mock's enumeration was instant**, so every test+passed and ecosystem row 37's "degrades honestly instead of timing out" was+mock-proven and false live. The same shape was in `cmd_close_import_temps`: the+drain would have wedged on the exact session it exists to drain.++0.5.5:++* **`walk_documents`** — `GetFirstDocument()` → `GetNext()`, one document at a+  time, bounded by a count AND a time box. Measured live the day before on the+  1342-document session: `$sw.GetFirstDocument().GetNext().GetTitle()` answered+  immediately. Cost is now O(what we look at), never O(what is open).+* **`cmd_docs` above the cap walks `DOC_SAMPLE` documents and nothing more.**+  `importTempsInSample` is counted over the sample only — as its name always+  said — and reports `importTempsInSampleOf` so a bare number cannot read as a+  session count. A walk stopped by its 5 s budget says+  `sampleTruncatedByTime:true`.+* **The drain walks too**, in two phases: collect up to `max` victims from a+  bounded window (about 4× the batch, 20 s budget), then close them by title —+  closing while walking would invalidate the iteration. It reports+  `documentsScanned` and `walkTruncatedByTime`, so `importTempsElsewhere` is+  readable as a count over the window rather than over the session.+* **`documents()` refuses above the cap** (`docs_enumeration_capped`) instead of+  letting any caller discover the wedge the hard way. `close_all_documents` gets+  a capped branch that still performs the close — one COM call — and reports+  counts with `closed`/`remaining`/`wouldPrompt` as **null**, never as empty+  lists, with the server turning that into an explicit UNKNOWN entry in+  `skipped`. `app_state` and `check_recovery` already read the count only.+* **The mock can now be slow.** `SWX_MOCK_DOC_LATENCY_MS` charges per document+  touched, at exactly the points the real backend pays a round trip, and+  `SWX_MOCK_OPEN_DOCS` floods a session. The regression test holds the live+  shape: 2144 documents, cap 200, 15 ms/document — 32 s of simulated cost if+  anything marshals the session — and requires `swx_docs` to answer in under 3 s+  with `truncated:true`, the drain to close a bounded batch without scanning+  2144, and `app_state` to degrade the same way.++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.