# F50 Suspend / Activate live evidence — 2026-05-06 Live re-run of `analysis/frida/mx-nmx-trace.js` (with the F46 hook additions for `LmxProxy.dll!CLMXProxyServer.Suspend` / `.Activate`) against `MxTraceHarness.exe` on the local AVEVA install. Two captures land: | # | Path | Scenario | Tag | |---|---|---|---| | 123 | `captures/123-frida-suspend-advised-instrumented/` | `--scenario=suspend-advised` | `TestChildObject.ScanState` | | 124 | `captures/124-frida-activate-advised-instrumented/` | `--scenario=activate-advised` | `TestChildObject.ScanState` | ## Capture 123 — `Suspend` IS server-side After `mx.suspend.begin` fires at `17:23:51.949Z`, NMX wire traffic appears within ~140ms: ```text 17:23:51.949Z mx.suspend.begin (CLMXProxyServer.Suspend, serverHandle=1, itemHandle=1) 17:23:51.949Z mx.suspend.end (Status: Success=-1 / MxCategoryPending / MxSourceRequestingLmx / Detail=0) 17:23:52.089Z nmx.enter PutRequest body= 2d 01 00 ← command 0x2D, version 0x0001 cd 2a ee ee b2 76 06 4f b4 58 5c a0 2d f7 a8 93 ← 16-byte correlation_id (matches the prior AdviseSupervisory) 01 00 05 00 01 00 02 00 01 00 ← reserved / engine + handle context 69 00 0a 00 47 92 00 00 ← attribute / property ids 03 00 00 00 ← trailer 17:23:52.089Z nmx.enter TransferData (envelope wraps the above 41-byte body, target_galaxy=1, target_platform=1, target_engine=2) 17:23:52.090Z nmx.leave TransferData (HRESULT 0 = success) 17:23:52.090Z nmx.leave PutRequest (HRESULT 0 = success) 17:23:52.123Z nmx.enter ProcessDataReceived (50-byte op-status frame back from engine) 17:23:52.183Z call.enter CUserConnectionCallback.OperationComplete (LMX surfaces the op-status to the client) ``` The 41-byte body has the same shape as AdviseSupervisory's body (`1f 01 00 + correlation_id + ...`) — same family of `INmxService2` item-control ops. The opcode `0x2D` is what `LmxProxy.dll!CLMXProxyServer.Suspend` puts on the wire. ## Capture 124 — `Activate` against an already-active item is client-side The `activate-advised` harness scenario does **not** call `Suspend` first — it just AdviseSupervisory + Activate. So the Activate is invoked on an already-active item. After `mx.activate.begin` fires at `17:26:02.982Z`, the next NMX traffic is at `17:26:10.20Z` (7+ seconds later — that's the harness's UnAdvise / Unregister at scenario teardown). No wire op fires for the Activate itself. ```text 17:26:02.982Z mx.activate.begin (CLMXProxyServer.Activate, serverHandle=1, itemHandle=1) 17:26:02.982Z mx.activate.end (Status: Success=-1 / category=175 / Detail=0) ← returns instantly client-side 17:26:10.206Z nmx.enter PutRequest ← unrelated, harness teardown (UnAdvise / Unregister) ``` ## Verdict - **Suspend** is **server-side** with NMX command `0x2D`. The wire body shape matches AdviseSupervisory's structurally (` `). The full body decode (engine / handle / attribute id meanings of bytes 19–40) is left for a future codec port — the M6 F50 deliverable is the existence + opnum + correlation-id evidence. - **Activate** (against a non-suspended item) is **client-side only** in this scenario — the LMX proxy returns success without emitting wire traffic. We don't have direct evidence for Activate-after-Suspend (the harness's `activate-advised` scenario doesn't sequence them); circumstantial reasoning is that since Suspend goes server-side, Activate likely also does when it has a suspension to revert. If a future capture is needed, add a `suspend-then-activate` scenario to `MxTraceHarness/Program.cs`. ## What this changes - R5 in `design/70-risks-and-open-questions.md` moves to "settled — Suspend is wire op `0x2D`, Activate behaviour is conditional." - A future codec follow-up could port the `0x2D` body shape into a typed encoder/decoder under `crates/mxaccess-codec/src/`. Not blocking M6 / V1 — `Session::suspend` / `Session::activate` aren't part of the public API today; they'd be additions. - `analysis/proxy/nmxsvcps-procedures.tsv` could grow a row for opnum `0x2D` once someone correlates the Frida capture against the `INmxService2` MIDL. Out of scope for F50. ## Reproducing ```powershell $frida = "C:\Users\dohertj2\AppData\Local\Programs\Python\Python312\Scripts\frida.exe" $harness = "C:\Users\dohertj2\Desktop\mxaccess\src\MxTraceHarness\bin\Release\net481\MxTraceHarness.exe" $script = "C:\Users\dohertj2\Desktop\mxaccess\analysis\frida\mx-nmx-trace.js" $cap = "C:\Users\dohertj2\Desktop\mxaccess\captures\-frida--instrumented" mkdir $cap & $frida -f $harness -l $script -- ` --scenario=suspend-advised ` # or activate-advised --tag=TestChildObject.ScanState ` --duration=8 ` --log="$cap\harness.log" ` --client="MxFridaTrace-" ` > "$cap\frida.stdout.jsonl" 2> "$cap\frida.stderr.txt" ``` The harness needs the local AVEVA Galaxy running with `TestChildObject` deployed. Frida 17.x; Python 3.12. The `MxTraceHarness.exe` is the x86 / net481 build at `bin/Release/net481/` — `dotnet build src/MxTraceHarness/MxTraceHarness.csproj /p:Configuration=Release` produces it.