# MXAccess capture run - 2026-04-25 This run used the primary installed MXAccess interop assembly: ```text C:\Program Files (x86)\ArchestrA\Framework\Bin\ArchestrA.MXAccess.dll ``` The harness is in `src\MxTraceHarness` and builds as `net481` x86 because MXAccess activates the 32-bit `LMXProxy.LMXProxyServer` COM server. ## Galaxy repository inputs The tag selector is: ```text analysis\sql\select_capture_tags.sql ``` It connects to the Galaxy repository described in `C:\Users\dohertj2\Desktop\lmxopcua\gr\connectioninfo.md`: ```text Server=localhost; Database=ZB; Integrated Security=SSPI ``` The output from this run is saved at: ```text analysis\db\capture-tag-candidates.tsv ``` Selected test tags: | Runtime reference | Type | Security | Notes | | --- | --- | --- | --- | | `TestChildObject.TestBool` | Boolean | Operate / 1 | scalar read/subscription | | `TestChildObject.TestInt` | Integer | Operate / 1 | scalar read/subscription/write | | `TestChildObject.TestString` | String | Operate / 1 | scalar read/subscription | | `TestChildObject.TestStringArray[]` | String array, length 10 | Operate / 1 | array read/subscription | | `NoSuchObject_999.NoSuchAttr` | invalid | n/a | negative resolution path | Important naming result: array attributes must be passed to MXAccess with the `[]` suffix. `TestChildObject.TestStringArray` produced `MxCategoryConfigurationError`, detail `1003`; `TestChildObject.TestStringArray[]` returned `System.String[]` with quality `192`. ## Harness behavior captured Built command: ```text dotnet build src\MxTraceHarness\MxTraceHarness.csproj -c Release ``` Executable: ```text src\MxTraceHarness\bin\Release\net481\MxTraceHarness.exe ``` Scenarios now implemented: | Scenario | High-level sequence | | --- | --- | | `register` | `Register`, wait, `Unregister` | | `add-remove` | `Register`, `AddItem`, wait, `RemoveItem`, `Unregister` | | `subscribe` | `Register`, `AddItem`, `AdviseSupervisory`, wait callbacks, `UnAdvise`, `RemoveItem`, `Unregister` | | `write` | `Register`, `AddItem`, `AdviseSupervisory`, wait initial callback, `Write`, wait `OnWriteComplete`, cleanup | Write precondition found in this run: - `Write` immediately throws `E_INVALIDARG` if called after `AddItem` only. - `Write` succeeds after `AdviseSupervisory` has established the item connection. - The fourth `Write` argument should follow the existing bridge convention: pass the Galaxy `security_classification` value for the attribute. For `TestChildObject.TestInt`, that value is `1`. Successful same-value write: ```text captures\010-write-test-int-advised-same-value\harness.log ``` Observed sequence: ```text mx.event.data-change: TestChildObject.TestInt = 99, quality 192 mx.write.begin: value 99, UserId/security classification 1 mx.write.end mx.event.write-complete: MxCategoryOk, MxSourceRespondingAutomationObject, detail 0 ``` ## Captures produced Every capture folder contains `harness.log`, `netsh.etl`, `network.pcapng`, and tool stdout/stderr files unless noted. | Folder | Result | | --- | --- | | `captures\001-register` | register/unregister success | | `captures\002-add-remove-scalar` | scalar `AddItem`/`RemoveItem` success | | `captures\003-subscribe-scalars` | bool/int/string initial data changes, quality `192` | | `captures\004-subscribe-array-runtime-name` | array without `[]` returns configuration error detail `1003` | | `captures\005-subscribe-array-bracketed-name` | array with `[]` returns `System.String[]`, length 10, quality `192` | | `captures\006-add-invalid` | invalid `AddItem` still returns an item handle; no validation until advise | | `captures\007-subscribe-invalid` | invalid subscribe returns configuration error detail `6` | | `captures\008-write-test-int-same-value` | write without advise throws `E_INVALIDARG` using fourth arg `0` | | `captures\009-write-test-int-same-value-security-1` | write without advise still throws `E_INVALIDARG` using fourth arg `1` | | `captures\010-write-test-int-advised-same-value` | advised same-value write succeeds | | `captures\011-pktmon-subscribe-scalar-loopback-probe` | `pktmon` probe; still did not expose `::1` NMX loopback traffic | Converted pcap summaries: ```text analysis\network\pcap-summary.txt captures\011-pktmon-subscribe-scalar-loopback-probe\pcap-summary.txt ``` ## Network capture status `netsh trace` and `pktmon` both captured external/background traffic and can be converted to pcapng. They did not expose the local loopback session that `NmxSvc.exe` keeps open on IPv6 loopback. Current `NmxSvc.exe` socket evidence during the run: ```text TCP 10.100.0.48:5026 LISTEN UDP 10.100.0.48:5026 TCP ::1:49829 <-> ::1:49704 ESTABLISHED ``` Interpretation: the payload needed for a native managed client is probably on the local `::1` connection between the 32-bit MXAccess stack and `NmxSvc.exe`, not on the physical NIC path. Capturing that requires one of: - working Npcap loopback capture, - API Monitor / debugger tracing at Winsock or COM method boundaries, - ETW provider capture if the AVEVA NMX components emit enough payload detail, - direct lower-level COM tracing around `INmx4.PutRequest2` / `GetResponse2` and `IDataClient` methods. Wireshark 4.6.4 and `etl2pcapng` 1.11.0 were installed. Update after interactive install: Npcap 1.87 is now installed and working. `dumpcap -D` lists `\Device\NPF_Loopback (Adapter for loopback traffic capture)`. The verification capture is: ```text captures\012-npcap-loopback-subscribe-scalar\ ``` Files: ```text loopback.pcapng harness.log nmx-loopback-frames.tsv ``` The focused loopback capture includes the active MXAccess/NMX conversation: ```text ::1:59335 <-> ::1:49704 803 frames, about 84 kB, duration 7.6793 s ``` It also saw the pre-existing service connection: ```text ::1:49829 <-> ::1:49704 4 frames, about 526 bytes ``` This confirms Npcap loopback capture is the correct mechanism for collecting the actual local NMX payloads needed for protocol reconstruction. ## Npcap loopback protocol captures The repeatable runner is: ```text analysis\scripts\run_loopback_capture.ps1 ``` Focused captures completed after Npcap verification: | Folder | Result | | --- | --- | | `captures\013-loopback-subscribe-scalars` | good bool/int/string subscribe | | `captures\014-loopback-subscribe-array-bracketed` | good string-array subscribe using `[]` suffix | | `captures\015-loopback-subscribe-invalid` | invalid reference subscribe | | `captures\016-loopback-write-test-int-advised` | advised same-value write succeeds | Extraction and summary helpers: ```text analysis\scripts\extract_nmx_loopback.py analysis\scripts\extract_tcp_conversations.py analysis\scripts\decode_tcp_payload_packets.py analysis\scripts\decode_mixed_local_stream.py analysis\scripts\analyze_write_window.py analysis\scripts\diff_write_window_records.py analysis\scripts\run_frida_mx_trace.ps1 analysis\scripts\extract_frida_trace.py analysis\scripts\summarize_dcerpc.py analysis\network\dcerpc-loopback-summary.tsv analysis\network\write-window-tcp-payloads.tsv ``` Important packet result: the `::1: <-> ::1:49704` traffic is DCE/RPC, not a simple tag-string socket protocol. The observed interface UUIDs are: ```text 4e0c90df-e39d-4164-a421-ace89484c602 1981974b-6bf7-46cb-9640-0260bbb551ba ``` Those UUIDs were not found as direct keys under the checked COM registry interface, CLSID, or TypeLib areas. The likely decode targets are therefore the native proxy/stub binaries: ```text C:\Program Files (x86)\ArchestrA\Framework\Bin\NmxSvcps.dll C:\Program Files (x86)\ArchestrA\Framework\Bin\WWProxyStub.dll ``` Good scalar subscribe, good array subscribe, and successful advised write share the same main `49704` DCE/RPC shape: 165 ctx-1 opnum-3 request/response pairs, 10 ctx-0/ctx-1 setup groups, and 3 ctx-1 opnum-5 pairs. The invalid subscribe adds one more setup group and 11 more opnum-3 pairs, matching the observed behavior that invalid references fail during advise/resolution, not `AddItem`. The successful write has a second important observation. The harness write call occurred at relative time about `10.34s`, and the write-complete callback at about `10.55s`. No `49704` DCE/RPC frames appear in that exact window. The active payload is primarily on: ```text 127.0.0.1:57415 <-> 127.0.0.1:57433 ``` That stream is compact binary traffic with small control messages and apparent little-endian length prefixes. This means the managed replacement likely needs to reproduce both the DCE/RPC coordination path and a separate local binary callback/request channel. The write-window stream is extracted as: ```text captures\016-loopback-write-test-int-advised\tcp-stream-127_0_0_1_57415-to-127_0_0_1_57433.bin captures\016-loopback-write-test-int-advised\tcp-stream-127_0_0_1_57433-to-127_0_0_1_57415.bin ``` The stream has a mixed-record framing: - 12-byte control records: `int32 code_or_status`, `int32 token_low`, `int32 token_high`. - Data records: `uint32 body_length`, followed by the data body. - Positive control values can announce one or more following data records. - `-1` appears as a normal acknowledgement/status control. - `-2` appears around write-window status/control exchanges. Additional differential write captures: | Folder | Result | | --- | --- | | `captures\017-loopback-write-test-int-100` | value changed `99 -> 100`; pcap usable | | `captures\018-loopback-write-test-int-101` | write succeeded, but pcap is header-only and should not be used | | `captures\019-loopback-write-test-int-101-rerun` | pcap usable, but same-value write because value was already `101` | | `captures\020-loopback-write-test-int-102` | value changed `101 -> 102`; pcap usable | | `captures\021-loopback-write-test-int-sequence-103-105` | same-session sequence changed `102 -> 103 -> 104 -> 105`; pcap usable | | `captures\022-frida-write-test-int-sequence-106-108` | Frida hooks validated Ghidra RVAs; first buffer dump helper was incomplete | | `captures\023-frida-write-test-int-sequence-109-111` | Frida hooks captured raw write values in `LmxProxy` and `NmxAdptr` buffers | | `captures\024-frida-write-test-bool-sequence` | bool write matrix: `VT_BOOL`, 37-byte `PutRequest`, value slot at offset `18` | | `captures\025-frida-write-test-float-sequence` | float write matrix: 40-byte `PutRequest`, `float32` at offset `18` | | `captures\026-frida-write-test-double-sequence` | double write matrix: 44-byte `PutRequest`, `float64` at offset `18` | | `captures\027-frida-write-test-string-sequence` | string write matrix: UTF-16LE payload at offset `26` | | `captures\028-frida-write-test-datetime-sequence` | datetime write matrix: outbound UTF-16LE display string; callback FILETIME | | `captures\029-frida-write-test-int-array` | int array write succeeds; packed `int32` values at `PutRequest` offset `28` | | `captures\030-frida-write-test-bool-array` | bool array write succeeds, but alternating requested values returned as paired values; needs follow-up | | `captures\031-frida-write-test-float-array` | float array write succeeds; packed `float32` values at `PutRequest` offset `28` | | `captures\032-frida-write-test-double-array` | double array write succeeds; packed `float64` values at `PutRequest` offset `28` | | `captures\033-frida-write-test-string-array` | string array write succeeds; per-element UTF-16LE records | | `captures\034-frida-write-test-datetime-array` | datetime array write succeeds, but body dump was truncated by the old 256-byte Frida cap | | `captures\035-frida-write-test-datetime-array-full` | datetime array rerun with full 4096-byte Frida dump cap | | `captures\036-frida-write-secured-test-int` | `WriteSecured` against Operate int rejected with `0x80004021` before value-bearing body | | `captures\037-frida-write-secured2-test-int` | `WriteSecured2` against Operate int rejected with `E_INVALIDARG` before value-bearing body | | `captures\038-frida-write-secured-protectedvalue` | `WriteSecured` against real SecuredWrite bool rejected with `0x80004021` | | `captures\039-frida-write-secured-verified-protectedvalue1` | `WriteSecured` against real VerifiedWrite bool rejected with `0x80004021` | | `captures\040-frida-write-normal-secured-protectedvalue` | normal `Write` with user/security `2` succeeds against SecuredWrite bool | | `captures\041-frida-write-normal-verified-protectedvalue1` | normal `Write` with user/security `3` succeeds against VerifiedWrite bool | | `captures\042-frida-write2-test-int-timestamp` | `Write2` succeeds; int at offset `18`, FILETIME at offset `24` | | `captures\043-frida-loopback-write-test-int-115` | combined Frida plus Npcap loopback capture; exact adapter bodies are not present verbatim in TCP streams | | `captures\044-frida-loopback-write-test-int-123456789` | combined Frida plus Npcap loopback capture with distinctive value; raw scalar also absent from full pcap payload scan | The same-session sequence was captured with: ```text --scenario=write --tag=TestChildObject.TestInt --type=int --values=103,104,105 --user-id=1 --write-delay-ms=1000 --write-interval-ms=700 --duration=5 ``` Generated write-window analyses: ```text captures\017-loopback-write-test-int-100\write-window-mixed-records.tsv captures\020-loopback-write-test-int-102\write-window-mixed-records.tsv captures\021-loopback-write-test-int-sequence-103-105\write-window-mixed-records.tsv analysis\network\write-window-body-diff-017-vs-020.tsv analysis\network\write-window-body-diff-021-w0-vs-w1.tsv analysis\network\write-window-body-diff-021-w1-vs-w2.tsv ``` The sequence capture is important because it keeps one MXAccess session alive while changing only the requested int value. The decoded local-stream records around write-complete still do not contain `103`, `104`, or `105` as plain little-endian int32 payloads. The bytes that move consistently in the visible records are request/session counters and checksummed or opaque body fields. The pcap-only mixed-stream layer did not isolate the scalar value. Headless Ghidra plus Frida then located it one native layer higher: ```text CLMXProxyServer::Write variant A RVA 0x12c0c CNmxAdapter::PutRequest RVA 0x15169 CNmxAdapter::TransferData RVA 0x10996 CNmxAdapter::ProcessDataReceived RVA 0x112da ``` In `captures\023-frida-write-test-int-sequence-109-111`, the raw scalar values are visible as little-endian int32: | Function | Body size | Value offsets | | --- | ---: | --- | | `CLMXProxyServer::Write` | call args | `args[5] = 109, 110, 111` | | `CNmxAdapter::PutRequest` | `40` | offset `18` | | `CNmxAdapter::TransferData` | `86` | offset `64` | | `CNmxAdapter::ProcessDataReceived` | `88` | offset `84` | The later Frida captures generalize the body format: | Type | `PutRequest` | `TransferData` | Callback/update | Encoding | | --- | --- | --- | --- | --- | | bool | size `37`, offset `18` | size `83`, offset `64` | size `85`, offset `84` | `VT_BOOL`; true `ff ff ff 00` in write body and `ff` in data-change body; false `00 ff ff 00` and `00` | | int | size `40`, offset `18` | size `86`, offset `64` | size `88`, offset `84` | little-endian `int32` | | float | size `40`, offset `18` | size `86`, offset `64` | size `88`, offset `84` | little-endian `float32` | | double | size `44`, offset `18` | size `90`, offset `64` | size `92`, offset `84` | little-endian `float64` | | string | size `58` or `60`, offset `26` | size `104` or `106`, offset `72` | size `106` or `108`, offset `92` | UTF-16LE | | datetime | size `86`, offset `26` | size `132`, offset `72` | size `98`, offset `88` | outbound UTF-16LE display string; callback FILETIME | Full matrix: ```text analysis\frida\write-body-matrix.tsv ``` Array matrix: ```text analysis\frida\write-array-body-matrix.tsv ``` Write-mode matrix: ```text analysis\frida\write-mode-matrix.tsv ``` Frida-to-TCP mapper: ```text analysis\scripts\run_frida_loopback_capture.ps1 analysis\scripts\map_frida_to_tcp.py analysis\scripts\parse_dcerpc_streams.py captures\043-frida-loopback-write-test-int-115\frida-to-tcp-map.tsv captures\044-frida-loopback-write-test-int-123456789\frida-to-tcp-map.tsv ``` Array body summary: | Type | `PutRequest` | `TransferData` | Callback/update | Encoding | | --- | --- | --- | --- | --- | | int[] | size `86`, first value offset `28` | size `132`, first value offset `74` | size `134`, first value offset `94` | descriptor kind `0x42`, count `10`, width `4`, packed `int32` | | bool[] | size `66`, first value offset `28` | size `112`, first value offset `74` | size `114`, first value offset `94` | descriptor kind `0x41`, count `10`, width `2`; observed value pairing needs follow-up | | float[] | size `86`, first value offset `28` | size `132`, first value offset `74` | size `134`, first value offset `94` | descriptor kind `0x43`, count `10`, width `4`, packed `float32` | | double[] | size `126`, first value offset `28` | size `172`, first value offset `74` | size `174`, first value offset `94` | descriptor kind `0x44`, count `10`, width `8`, packed `float64` | | string[] | size `256`, first string bytes at `41` | size `302`, first string bytes at `87` | size `304`, first string bytes at `107` | descriptor kind `0x45`, per-element UTF-16LE variable records | | datetime[] | size `596`, first string bytes at `41` | size `642`, first string bytes at `87` | size `214`, first FILETIME at `94` | outbound per-element display strings; callback/update FILETIME sequence | Secured/verified write result: the public `WriteSecured` and `WriteSecured2` methods are exposed by COM but did not produce value-bearing requests in these captures. Actual `SecuredWrite` and `VerifiedWrite` attributes accepted normal `Write` calls when the fourth argument matched the Galaxy security classification (`2` or `3`). `Write2` result: the timestamped int write still uses a 40-byte body with the value at offset `18`. A FILETIME timestamp is embedded at `PutRequest` offset `24`, `TransferData` offset `70`, and callback/update offset `75`. Transport correlation result: in capture `043`, the raw `int32` value `115` appears in TCP streams, but the exact Frida `PutRequest`, `TransferData`, and callback bodies do not. The `::1:49704` hits around the scalar align with DCE/RPC metadata/call IDs rather than the native adapter body. Capture `044` uses `123456789` to avoid that ambiguity. The raw scalar is not found in a full pcap payload scan, in parsed `::1:49704` DCE/RPC stubs, or in the mixed local stream. This confirms the wire representation is transformed or encoded before TCP. Detailed notes are in: ```text docs\Loopback-Protocol-Findings.md docs\NMX-COM-Contracts.md ``` ## Protocol facts established - `Register` returns session handle `1` for these short-lived runs. - `AddItem` allocates local item handles but does not prove the reference exists. - `AdviseSupervisory` triggers item resolution and initial data/status callback. - Good scalar reads return: - `MXSTATUS_PROXY.success = -1` - `category = MxCategoryOk` - `detectedBy = MxSourceRequestingLmx` - `detail = 0` - quality `192` - Invalid subscribe returns: - value `null` - quality `0` - `category = MxCategoryConfigurationError` - `detectedBy = MxSourceRequestingLmx` - `detail = 6` - Array name missing `[]` returns: - value `null` - quality `0` - `category = MxCategoryConfigurationError` - `detectedBy = MxSourceRespondingAutomationObject` - `detail = 1003` - Successful write completion returns: - `success = -1` - `category = MxCategoryOk` - `detectedBy = MxSourceRespondingAutomationObject` - `detail = 0` Later targeted non-core type captures: - `062-frida-subscribe-intl-shortdesc`: `TestChildObject.ShortDesc` (`InternationalizedString`) resolves and advises. A callback record used normal string wire kind `0x05` with compact empty payload `04 00 00 00`. - `063-frida-subscribe-elapsed-time-deadband`: `TestMachine_001.TestAlarm001.Alarm.TimeDeadband` (`ElapsedTime`) resolves and advises. A callback record used wire kind `0x07` with four-byte zero payload. - `064-frida-subscribe-intl-percent` and `065-frida-subscribe-intl-mb`: non-empty internationalized-string references resolved and advised but did not emit value callbacks during the capture window. - `066` through `069`: timestamped `Write2` captures for bool, float, double, and string. Float, double, and string use the same fixed/variable timestamped suffix shape as int. Timestamped bool uses wire kind `0x01`, one value byte, then the normal timestamp suffix. - `070` through `072`: timestamped `Write2` captures for int[], bool[], and string[]. These match the existing managed array timestamp encoder. The bool-array capture preserves the earlier MXAccess marshaling behavior where requested alternating bools arrive as paired true/false values. - `073`, `074`, and `076`: timestamped `Write2` captures for float[], double[], and datetime[]. These match the managed array timestamp encoder. The initial `075` datetime[] run used the wrong tag name and did not emit the expected `0x37` write body; `076` is the valid capture for `TestDateTimeArray[]`. - `mxaccess-suspend-*` / `mxaccess-activate-*`: `Suspend` and `Activate` throw `0x80070057` before `AdviseSupervisory`. The advised variants succeed on scan-state targets: suspend returns pending/requesting-LMX, activate returns ok/requesting-LMX. Frida captures `077` and `078` show no additional NMX request body after the public method call, so this behavior appears local to the MXAccess/LMX layer once the item has been advised. - `079-frida-add-buffered-advise-testint`: `AddBufferedItem("TestInt", "TestChildObject")` followed by `AdviseSupervisory` sends an item-control `0x10` reference-registration body for `TestInt.property(buffer)` in context `TestChildObject`. It does not send a normal `0x1f` advise body for the buffered handle. - `080-frida-buffered-external-write-testint`: while the buffered handle is advised, adding normal writer handles in the same session sends normal reference-registration bodies, but no `OnBufferedDataChange` payload was observed. This capture supplied the stable normal and buffered `0x10` registration bodies, plus the matching `0x11` registration-result frames, used by `NmxReferenceRegistrationMessage` and `NmxReferenceRegistrationResultMessage` tests. - `085-frida-subscribe-property-buffer` and `086-frida-write-property-buffer`: direct literal `TestChildObject.TestInt.property(buffer)` add/advise follows normal `AdviseSupervisory`, not `AddBufferedItem`; the item-control `0x10` registration uses the full literal item with an empty context and receives a `0x11` registration result containing the same Base Runtime Object internal error text. No `OnBufferedDataChange` event was fired. - `087-frida-authenticate-administrator-empty` and `088-frida-authenticate-invalid-empty`: password-redacted auth hook shows `CLMXProxyServer.AuthenticateUser` returning `S_OK` and user ID `1` for both `Administrator` and an invalid user name with password length `0`. The only NMX traffic observed after the auth call is the normal unregister-time system reference cleanup, not an auth request body. - `111-frida-write-secured-auth-protectedvalue` and `112-frida-write-secured-auth-verified-protectedvalue1`: pre-authenticating with `AuthenticateUser` returns user handle `1`, but public `WriteSecured` still returns `0x80004021` before any value-bearing NMX write body is emitted. - `113-frida-write-secured2-auth-protectedvalue`, `114-frida-write-secured2-auth-protectedvalue-false`, `115-frida-write-secured2-auth-protectedvalue-true`, and `116-frida-write-secured2-auth-verified-protectedvalue1`: authenticated public `WriteSecured2` succeeds and emits NMX command `0x38` over normal transfer kind `Write` (`3`). The decoded boolean body layout is: command and 14-byte reference-handle projection, boolean wire kind `0x01`, a four-byte boolean scalar whose first byte carries the value, FILETIME timestamp, a 16-byte current-user authenticator token, UTF-16LE client-name byte length, null-terminated UTF-16LE client name, a 16-byte verifier token, `0xffff`, client token, and write index. The verifier token is all zeros when `VerifierUserId` is `0` and equals the authenticated token when the verifier handle is `1`. - `117-frida-write-secured2-auth-testint`: after pre-authentication, public `WriteSecured2` also succeeds against `TestChildObject.TestInt` and emits command `0x38` with integer wire kind `0x02`. This proves the secured2 body is not boolean-only: it reuses the normal timestamped `0x37` value and timestamp prefix, then appends current-user token, client name, verifier token, `0xffff`, client token, and write index. - `089-frida-write-testint-wrong-type`: writing string `not_an_int` to integer `TestChildObject.TestInt` sends a normal `0x37` write body using string wire kind `0x05`. NMX responds with a length-prefixed completion-only status body whose inner completion byte is `0x41`; MXAccess returns `S_OK` from `Write` but does not fire `OnWriteComplete` during the harness wait. - `090-frida-write-invalid-reference`: invalid reference add/advise produces the expected `0x10` registration and `0x11` registration-result failure. The later public `Write` call returns `S_OK` but no value-bearing `0x37` body or write-complete event is observed. - `091-frida-write-testint-double-type`, `092-frida-write-testbool-string-type`, and `093-frida-write-testdatetime-string-type`: double-to-int writes emit a length-prefixed completion-only byte `0x00`, but MXAccess still does not fire `OnWriteComplete`; string-to-bool and string-to-time wrong-type writes match the string-to-int failure pattern and emit completion byte `0x41`. - `analysis/ghidra/exports/LmxProxy.dll.buffered-decompile.md`: decompile of `AddBufferedItem`, `Fire_OnBufferedDataChange`, and `SetBufferedUpdateInterval`. It confirms the `.property(buffer)` suffix, the buffered item-record marker, the seven-argument event firing path, and 100 ms tick rounding for buffered update intervals. - `analysis/ghidra/exports/LmxProxy.dll.buffered-event-xrefs.md` and `analysis/ghidra/exports/LmxProxy.dll.buffered-event-caller-decompile.md`: `Fire_OnBufferedDataChange` has one direct caller, `FUN_1001657f`. That function is the same native `OnDataChange callback received` path used for normal data changes. It looks up the item record and branches on a buffered item flag at offset `0x28`: normal items call the `_ILMXProxyServerEvents` `OnDataChange` helper, while buffered items convert the value to value, quality, and timestamp SAFEARRAY variants and call `_ILMXProxyServerEvents2::Fire_OnBufferedDataChange`. - `analysis/ghidra/exports/LmxProxy.dll.buffered-value-conversion-decompile.md`: decompile of the buffered value conversion helper shows the public buffered event value argument is a SAFEARRAY of values, the quality argument is a `VT_I2` SAFEARRAY, the timestamp argument is a `VT_UI8`/FILETIME SAFEARRAY, and the status argument is the normal `MXSTATUS_PROXY[]` SAFEARRAY. This confirms the event shape even though a live buffered payload has not yet been produced. - `analysis/ghidra/exports/LmxProxy.dll.auth-decompile.md`: decompile of `AuthenticateUser` and `ArchestrAUserToId`. Both increment a session-local user counter and store a mapping from that generated handle to a GUID/token identity before returning the generated handle to the public API caller. - `analysis/ghidra/exports/LmxProxy.dll.events-decompile.md`: decompile of `Fire_OnWriteComplete` and `Fire_OperationComplete`. Both build the same three-argument COM event payload: server handle, item handle, and one `MXSTATUS_PROXY` SAFEARRAY. `OnWriteComplete` dispatches event ID `2`; `OperationComplete` dispatches event ID `3`. No capture in this set emitted `mx.event.operation-complete`. - `analysis/ghidra/exports/LmxProxy.dll.event-xrefs.md` and `analysis/ghidra/exports/LmxProxy.dll.event-callers-decompile.md`: generated with headless Ghidra to identify the event helper callers. `Fire_OnWriteComplete` is called only from `FUN_10016b50`, which logs `OnSetAttributeResult callback received`. `Fire_OperationComplete` is called only from `FUN_10016d4b`, which logs `OperationComplete callback received`. This confirms that mapping write completion statuses to both public events would be incorrect; `OperationComplete` needs a distinct native callback capture before the managed compatibility event can fire. - `analysis/ghidra/exports/LmxProxy.dll.operation-candidates-decompile.md`: decompile of public operation candidates. `RemoveItem` performs local item cleanup, while `Suspend` and `Activate` query an `IMxScanOnDemand` interface and synchronously call vtable offsets `0x0c` and `0x10`, respectively. These paths did not reveal a call to `Fire_OperationComplete`, matching captures `077` and `078`, which returned status structs but emitted no operation event. - `118-frida-suspend-advised-scanstate-long` and `119-frida-activate-advised-scanstate-long`: reran advised `DevAppEngine.ScanState` suspend/activate with direct hooks on `CUserConnectionCallback.OnSetAttributeResult` and `CUserConnectionCallback.OperationComplete`. The hooks installed, but neither callback entry point was called during the 20 second waits. This strengthens the conclusion that these public scan-on-demand calls return local status and do not trigger the public `OperationComplete` event on this node. - `094-frida-buffered-separate-writer`: the harness was adjusted so `buffered-external-write` registers a separate writer server handle before adding/advising/writing `TestChildObject.TestInt`. The capture still produced no `mx.event.buffered-data-change` and no `Fire_OnBufferedDataChange` Frida entry. It did show the buffered `0x10` registration/`0x11` result for `TestInt.property(buffer)` in context `TestChildObject`, plus normal writer subscription/data callbacks. This rules out same-server-handle writer reuse as the reason buffered callbacks were absent. - `120-frida-buffered-history-testhistoryvalue`: first historized-attribute buffered attempt against `TestHistoryValue`, but the harness argument used `--item-context` instead of its actual `--context` switch, so MXAccess registered `TestHistoryValue.property(buffer)` with an empty context. This is retained only as a harness-option correction. - `121-frida-buffered-history-testhistoryvalue-context`: repeated the capture with `--context=TestMachine_001`. GR identifies `TestMachine_001.TestHistoryValue` as a deployed, historized integer dynamic attribute. Native MXAccess emitted the expected buffered `0x10` registration and `0x11` result for `TestHistoryValue.property(buffer)` in context `TestMachine_001`, and the separate writer session successfully wrote `201`, `202`, and `203` through `TestMachine_001.TestHistoryValue`. No public `mx.event.buffered-data-change` and no `Fire_OnBufferedDataChange` Frida entry were observed. - `122-frida-buffered-history-testhistoryvalue-plainadvise`: added a harness `--plain-advise` probe switch and repeated the same historized buffered scenario using public `Advise` instead of `AdviseSupervisory`. The registration/result bodies matched the context-bearing buffered shape, writer writes succeeded, and writer-session normal `0x32` data callbacks were seen, but the buffered subscriber still did not enter `Fire_OnBufferedDataChange`. This makes the remaining buffered gap a runtime/source-delivery condition, not a plain-versus-supervisory advise mismatch. - `095-frida-write-elapsed-int`: writing `1000` as an `Int32` to `TestMachine_001.TestAlarm001.Alarm.TimeDeadband` emitted a normal `0x37` write body with integer wire kind `0x02`; MXAccess did not emit a special elapsed write kind for an integer caller value. - `096-frida-write-intl-string`: writing `"hello-native"` as a `string` to `TestChildObject.ShortDesc` emitted a normal `0x37` write body with string wire kind `0x05`. The runtime returned a completion-only status byte `0xef`, so success semantics still need more captures, but outbound encoding is now defined for the caller-variant path. - `097-frida-write-bool-array-pattern`: first attempted a bool-array pattern with `--values`, which the harness interprets as ten separate writes of one-element arrays. This capture is useful only as a harness argument reminder. - `098-frida-write-bool-array-pattern-10`: writing one ten-element `bool[]` value with the requested pattern `true,false,false,true,true,false,true,false,false,true` confirmed the x86 MXAccess COM automation path still emits a paired/shifted VARIANT_BOOL-style wire payload: `true,true,false,false,false,false,true,true,true,true`. The NMX array descriptor remains `0x41`, count `10`, width `2`. The managed encoder keeps direct per-element `bool[]` encoding for native .NET callers, while the observed x86 COM projection is preserved as a golden compatibility capture. - `099-frida-plain-advise-testint`: public `CLMXProxyServer.Advise` for `TestChildObject.TestInt` emitted the same item-control `0x1f` body shape as the earlier `AdviseSupervisory` scalar subscription capture, including the trailing option value `3`. The wrapper's shared `Advise`/`AdviseSupervisory` path now has capture support for this scalar case. - `100-frida-subscribe-string-array`: subscribing to `TestChildObject.TestStringArray[]` produced a subscription-status callback with wire kind `0x45`, count `10`, width code `4`, and string-array element records, but the observed callback buffer stopped inside the final `"JJ10"` element after `"JJ1"`. MXAccess did not raise a public `OnDataChange` event. - `101-frida-write-string-array-update`: writing `KA1;KB2;KC3;KD4;KE5;KF6;KG7;KH8;KI9;KJ10` emitted a complete outbound `0x37` string-array body. The following callback again carried a `0x45` string-array record but stopped inside the final `"KJ10"` element after `"KJ1"`, and no public data-change event was observed. Current managed decoding treats this malformed callback value as incomplete rather than fabricating a string array. - `102-frida-subscribe-intl-shortdesc-after-write`: subscribing to `TestChildObject.ShortDesc` after the earlier string write still produced the compact empty string callback form (`wire kind 0x05`, record length `4`) and no public data-change event. The earlier `096` write returned completion-only `0xef`, so it did not establish a non-empty `InternationalizedString` callback value. - `103-frida-subscribe-elapsed-after-write`: subscribing to `TestMachine_001.TestAlarm001.Alarm.TimeDeadband` produced a non-zero `ElapsedTime` callback value using wire kind `0x07` followed by a 4-byte little-endian millisecond count (`00 e4 0b 54`, `0x540be400`). This confirms the existing elapsed-time callback decoder works for non-zero values as well as the earlier zero capture. ## Managed x64 live probes - `probe-remqi-managed`: a .NET 10 x64 process using managed NTLM activation successfully resolved `INmxService2`, completed `RemQueryInterface`, and returned partner version `6`. - `probe-session-write`: direct managed `WriteAsync` to `TestChildObject.TestInt` returns success through `INmxService2.TransferData`. The managed sender now uses the captured transfer kind for normal writes (`Write`, value `3`) rather than the earlier item-control kind. - `probe-session-subscribe`: fixed local engine IDs caused stale registration collisions and `UnAdvise` failures (`0x80041101`). The managed defaults now derive the local engine ID from the current process ID; unique engine IDs receive both `0x32` subscription-status and `0x33` data-update callbacks and clean up without that failure. - The live managed subscriber receives status-only scalar callbacks for `TestChildObject.TestInt`: the raw `0x33` body contains status, quality, timestamp, and wire kind `0x02`, but no four-byte integer payload. The same status-only result occurs when an x86 MXAccess writer changes the tag while the managed subscriber is active. This narrows the missing value-delivery piece to the pre-advise AddItem/metadata registration path that MXAccess emits as `0x17`, not to the scalar callback decoder. - A managed encoder for the observed `0x17` metadata body reproduces the capture exactly and `NmxSvc` accepts it before advise, but it still does not turn the managed scalar callback into a value-bearing record. The `0x17` body is therefore documented as a captured metadata primitive, not a complete AddItem/value-subscription registration. - Replaying the observed pre-advise `0x17` metadata body from the managed NTLM/DCOM path now reproduces the MXAccess-like callback sequence in x64: a 706-byte `0x40` metadata response containing `DevPlatform.GR.TimeOfLastDeploy`, `DevPlatform.GR.TimeOfLastConfigChange`, and the text `An internal error occurred in the Base Runtime Object`; a 151-byte `0x32` metadata status callback with two datetime records; the 92-byte operation status frame; then the normal 108-byte scalar `0x32` status-only callback for `TestChildObject.TestInt`. This proves the pre-advise metadata primitive is live and decoded enough to inspect, but its runtime result is an internal base-object error rather than successful value-subscription state. - A same-session managed subscribe-then-write probe also remains status-only: after `SubscribeAsync(TestChildObject.TestInt)`, managed `WriteAsync(331)` returns success, then the callback is `0x33` with wire kind `0x02` but no integer payload. This rules out cross-client delivery as the only cause; the native library still needs additional LMX item/write state beyond the current GR handle, NMX advise, and direct NMX write bodies. - Ghidra review of `CReferenceStringResolutionService` explains why replaying only the wire-visible metadata request is insufficient. The resolver compares `TimeOfLastDeploy` and `TimeOfLastConfigChange` against pending reference status details, and on a usable GR subscription status it calls an in-process `OnSetAttributeResult` path directly before removing the pending request. That state mutation is not produced by the current managed x64 replay, which only sends the `NmxSvc` request and receives callbacks. - Fixed Frida captures `104` and `105` corrected the stack argument mapping for `PrebindReference` and `UserRegisterPreboundReference`. Native `TestChildObject.ShortDesc` returns public prebound/reference handles of `1`, sends `0x1f` advise with transfer kind `ItemControl` (`2`), and its `IMxReference.GetMxHandle` value uses property id `10` even though the GR attribute category is `11`. - After changing the managed sender to use transfer kind `2` for `0x1f` advise and changing the GR resolver to synthesize value handles with property id `10`, .NET 10 x64 `SubscribeAsync(TestChildObject.ShortDesc)` receives the native-equivalent 112-byte callback: command `0x32`, status/detail `3/0`, quality `0x00C0`, wire kind `0x05`, and empty string value. This proves the primary value-subscription path can be reproduced in full managed code for at least one x86 value-bearing capture. - A managed-client regression check now asserts the distinct transfer kinds: normal `0x37` writes use transfer kind `Write` (`3`), while `0x1f` advise uses transfer kind `ItemControl` (`2`). The generated `ShortDesc` `"hello-native"` write body matches capture `096` byte-for-byte, including the value handle with property id `10`. - Live x64 subscribe-write probes after that correction matched the native outcomes: `TestChildObject.ShortDesc` still returns completion-only byte `0xef`, which is therefore native-equivalent for the current `InternationalizedString` caller path rather than a managed-only transport failure; `TestChildObject.TestInt` returns completion-only byte `0x00` and a status-only `0x33` update callback with wire kind `0x02`. - Fresh x86 MXAccess baseline captures on the current VM state: `106-native-subscribe-testint-current` and `107-native-write-testint-current` show public MXAccess also raises no `mx.event.data-change` for `TestChildObject.TestInt` subscribe-only or subscribe-then-write, even though the write call returns success. Older captures `003`, `011`, `012`, `017`, and `018` prove this tag previously emitted value-bearing public events, so the current status-only managed result is not by itself a managed transport failure; it reflects the current runtime/Galaxy state or deployment value-delivery state. - Fresh current-state capture `108-native-subscribe-scalar-current` subscribed to `TestBool`, `TestInt`, `TestFloat`, `TestString`, and `ShortDesc` through native x86 MXAccess and raised no public `mx.event.data-change` for any of them. Because native x86 also suppresses the decoded empty `ShortDesc` adapter callback at the public event layer, `MxNativeCompatibilityServer` now suppresses empty `InternationalizedString` `DataChanged` promotion while leaving the low-level `MxNativeSession.CallbackReceived` event intact. - The new .NET 10 x64 compatibility probe `--probe-compatibility-subscribe` validates that public-facade behavior: `TestChildObject.ShortDesc` and `TestChildObject.TestInt` both reported `compat_data_changes=0`, matching fresh native x86 public-event behavior on the current VM state. - The companion `--probe-compatibility-subscribe-write` probe validates public write-path facade behavior: `TestChildObject.TestInt = 793` and `TestChildObject.ShortDesc = hello-compat` both completed the managed write call but reported `compat_subscribe_write_data_changes=0` and `compat_subscribe_write_completes=0`, matching the current native public behavior where completion-only NMX statuses do not surface as `OnWriteComplete`. - Invalid-reference parity is now modeled in the compatibility facade: `NoSuchObject_999.NoSuchAttr` returns an item handle from `AddItem`, and `AdviseSupervisory` emits one public data-change with `value=null`, `quality=0`, `status_success=0`, `ConfigurationError`, `RequestingLmx`, and detail `6`, matching captures `007` and `015`. A subsequent compatibility write to that invalid handle returns without adding a public write-complete event, matching capture `090`. - A mixed multi-item .NET 10 x64 compatibility probe with `ShortDesc`, `TestInt`, and `NoSuchObject_999.NoSuchAttr` validated handle routing in one server session: items `1` and `2` produced zero public changes on the current VM state, while item `3` produced exactly one configuration-error data-change. - Compatibility write argument validation now mirrors two x86 error captures: normal `Write` after `AddItem` but before advise returns `0x80070057`, as in captures `008`/`009`, and normal `Write` against an `AddBufferedItem` handle returns `0x80070057`, as in `mxaccess-add-buffered-write-testint-context.log`. - `captures\109-native-post-remove-errors` records stale item-handle parity after `RemoveItem`. Native x86 MXAccess returns `ArgumentException` `0x80070057` for `Advise`, `AdviseSupervisory`, `UnAdvise`, `Write`, `Write2`, `Suspend`, `Activate`, and a second `RemoveItem` against the removed handle. The .NET 10 x64 `--probe-compatibility-post-remove` probe now reports the same `0x80070057` result for all of those operations. - `captures\110-native-invalid-handle-errors` records invalid server-handle and cross-server item-handle parity. Native x86 MXAccess returns `ArgumentException` `0x80070057` for `AddItem`, `AddItem2`, `RemoveItem`, `Advise`, `AdviseSupervisory`, `UnAdvise`, `Write`, `Write2`, `Suspend`, `Activate`, and `Unregister` when the server handle is invalid, and also for `RemoveItem`, `Advise`, and `Write` when the item handle belongs to another server. The .NET 10 x64 `--probe-compatibility-invalid-handles` probe now matches those HRESULTs. - Literal property-reference parity is now covered for the observed buffer property. Captures `085`/`086` show `TestChildObject.TestInt.property(buffer)` resolving to base `TestInt` with property id `0x32` and native handle `01 00 01 00 02 00 05 00 36 d7 02 00 9b 00 32 00 3e da 00 00`. `GalaxyRepositoryTagResolver` now recognizes that suffix, and the .NET 10 x64 compatibility subscribe/write probe for the literal reference reports zero public data-changes and zero public write-completes, matching the native public behavior in those captures. - `AddItem2` context resolution has been broadened in the compatibility layer. The live .NET 10 x64 probe now covers the captured simple context form `TestInt` + `TestChildObject`, a dotted primitive attribute `Alarm.TimeDeadband` + `TestMachine_001.TestAlarm001`, and a context-relative property reference `TestInt.property(buffer)` + `TestChildObject`. All three add and advise successfully; no public data-change is promoted on the current VM state. - A mixed .NET 10 x64 compatibility write probe now validates per-item routing for `TestChildObject.TestInt`, `TestChildObject.ShortDesc`, `TestChildObject.TestInt.property(buffer)`, and `NoSuchObject_999.NoSuchAttr` in one server session. The three valid/literal items report `data_changes=0` and `write_completes=0` on the current VM state, while the invalid reference reports exactly one configuration-error data-change and no write-complete. This confirms the compatibility facade does not misattribute suppressed completion-only writes or invalid-reference callbacks across item handles. - A first post-fix scalar matrix shows more decoder/runtime work remains: `TestChildObject.TestString` receives a `0x05` string record but the current body ends before the declared string payload is complete, so the decoder reports `value=null`; `TestChildObject.TestFloat` receives wire kind `0x03` without enough bytes for a float payload; `TestChildObject.TestBool` receives a 105-byte frame that stops inside the timestamp/kind area and is surfaced as `UnparsedCallbackReceived`. These look like status/incomplete initial-value callbacks on this node rather than handle-generation failures, because `ShortDesc` now matches the native value-bearing path. - Managed recovery lifecycle probe: `MxNativeClient.Probe --probe-session-recover --recover-attempts=2 --recover-delay-ms=100 --tag=TestChildObject.TestInt --value=323` ran from .NET 10 x64. It subscribed successfully, reported one recovery-started event, zero failed-attempt events, one recovery-completed event, preserved one active subscription after recovery, and wrote through the recovered session. - Recovery callback policy is now explicit in the managed API: callbacks are passed through during reconnect/replay and marked with `IsDuringRecovery` instead of being suppressed or buffered. More live evidence is still needed to catch actual replay-window callback delivery on larger subscription sets. - Managed multi-subscription recovery probe: `MxNativeClient.Probe --probe-session-recover-multi --recover-attempts=2 --recover-delay-ms=100` subscribed to `TestChildObject.TestInt`, `TestChildObject.ShortDesc`, `TestMachine_001.ProtectedValue`, and `TestMachine_001.ProtectedValue1`. Recovery replay preserved all four subscriptions. The run observed two data callbacks and four unparsed callbacks overall, but zero data, operation-status, reference-registration, or unparsed callbacks with `IsDuringRecovery=true`. - Managed multi-subscription recovery churn probe: `MxNativeClient.Probe --probe-session-recover-multi --recover-attempts=2 --recover-delay-ms=100 --recover-concurrent-writes --recover-write-start=330 --recover-write-count=5 --recover-write-delay-ms=10` used a separate writer session to write `TestChildObject.TestInt` values `330`-`334` during recovery. Two writes landed before the recovery-completed event. The recovering session preserved all four subscriptions, observed four data callbacks overall, and marked two data callbacks with `IsDuringRecovery=true`; operation-status, reference-registration, and unparsed recovery-window counts remained zero. - OperationComplete trigger analysis update: decompiled interop exposes `IMxCallback2.OperationComplete(int lCallbackId, ref MxStatus, string)` plus the `IDataConsumer.ActivateSuspend` / `ProcessActivateSuspend2` path returning `ItemActiveResponse`. Public `LMXProxyServerClass.Suspend` and `Activate` captures `118`/`119` used direct `IMxScanOnDemand` calls and did not enter the callback. The next useful native trigger search should target DataConsumer activate/suspend completion, not another plain public suspend/activate run. - `aaMxDataConsumer.dll` import/probe: registry CLSID `{85209FB2-0BA1-4594-BBC4-59D3DDAB823D}` maps to `MxDataConsumer Class` in `C:\Program Files (x86)\Common Files\ArchestrA\Services\aaMxDataConsumer.dll`. `tlbimp` generated `analysis\interop\Interop.aaMxDataConsumer.dll`, and `ilspycmd` decompiled it into `analysis\decompiled-interop\Interop.aaMxDataConsumer`. The new `MxDataConsumerProbe` x86 harness can instantiate `DataConsumerClass`, call `RegisterCallback`, resolve namespace strings to ID `1`, and call `ResolveReference`, `subscribe`, `ActivateSuspend`, and `ProcessActivateSuspend2`. Tested namespace strings remain disconnected (`IsConnected=0`), no registration or subscription records are returned, and `ProcessActivateSuspend2` returns `0x8007139F` (`ERROR_INVALID_STATE`). This confirms the COM surface is callable but still missing the bootstrap needed to attach it to the live namespace. - `MxDataConsumerProbe --probe-dataclient` attempted to create `DataClientClass` from the same imported type library. Creation failed with `0x80040154` (`REGDB_E_CLASSNOTREG`) because CLSID `{73BC4121-FF89-4762-901C-206E2BD9FE87}` is not registered on this node. The ASB deployment config shows `ServiceHost1` at `net.tcp://localhost:4000/` and `Default_ZB_MxDataProvider` publishing `IASBIData`, `IASBIDataV2`, and `IDataV3`, but a registered client/factory is still needed before endpoint connection probes can run. - Headless Ghidra plus ILSpy on `C:\Program Files (x86)\Common Files\ArchestrA\Services\aaMxDataConsumer.dll` shows that its DataClient side is a mixed-mode wrapper around managed ASB proxies. `CDataClientCLI.CreateConnection` sets the namespace string on a `DataClientProxy` and starts the auto-connect worker; `DataClientProxy` calls `CIDataVersionAdapterFactory.GetIDataAdapter(accessName)`, which calls `IDataProxySelector.SelectProxyForLatestEndpoint(accessName, new AsbMxDataSettings(), out error)`. - `ASBIDataV2Adapter.dll` from the GAC contains `IDataProxySelector`. It first checks `ASBDataV2Proxy.FindIDataEndpoint(accessName, DiscoveryScope.Global)`; if any endpoints are discovered it returns `ASBDataV2Proxy`, otherwise it falls back to `ASBDataProxy` V1. `ASBDataV2Proxy` searches LDS using scope `domainname//global`. - New x64 `AsbProxyProbe` results: access name `ZB` discovers one `IASBIDataV2` endpoint, `net.tcp://desktop-6jl3kko/ASBService/Default_ZB_MxDataProvider/IDataV2`, with listen URIs on the host name and local IPs. Access names `Default_ZB_MxDataProvider`, `Galaxy`, `localhost`, and `ZB2` discover no IDataV2/IData endpoints. `AsbProxyProbe --access=ZB --connect` successfully opens the ASB proxy from an x64 managed process; `Connect` returns true, channel state is `Opened`, and `PublishWriteComplete` returns success with zero pending writes. - `AsbProxyProbe --access=ZB --connect --register --read --write-int=401 --tag=TestChildObject.TestInt` proves the direct ASB value path. The tag registers with item id `18446462598732840961`, reads as ASB type `4` (`Int32`) value `334`, accepts a write of `401`, and reads back `401`. The immediate write result is global success with per-item `0x0000001F` (`OperationWouldBlock`), then `PublishWriteComplete` returns `0x00000020` (`PublishComplete`) with the submitted write handle `0xA5B20001` and final per-item success. - A follow-up x86 `MxDataConsumerProbe --namespace=ZB` run hung inside the COM wrapper and was stopped. Direct ASB proxy probing is now the preferred path for validating data-service functionality and any future OperationComplete trigger without relying on the standalone mixed-mode DataConsumer COM object. - Pure .NET 10 x64 ASB port update: `MxAsbClient.Probe --tag=TestChildObject.TestInt --write-int=412` now completes the core register/read/write/complete data-service flow without AVEVA assembly references. It connects/authenticates through ASB system auth, retries the observed one-way `AuthenticateMe` startup race in `RegisterItems`, reads the tag, accepts the write with immediate per-item `0x0000001F`, reads back `412`, and decodes `PublishWriteComplete` result `0x00000020`, count `1`, the submitted handle, and final per-item success. The saved evidence is `analysis\proxy\mxasbclient-probe-stage21-register-retry.txt`. - Pure .NET 10 ASB unregister update: `MxAsbClient.Probe --tag=TestChildObject.TestInt` now calls `UnregisterItems` with the registered item identity. The pure client and installed `ASBDataV2Proxy` compare run both return global success `0x00000000` and per-item `0x0000000B` (`OperationFailed`) for this provider. Evidence: `analysis\proxy\mxasbclient-probe-stage23-unregister-id.txt` and `analysis\proxy\asbproxyprobe-unregister-compare.txt`. - Pure .NET 10 ASB multi-item update: `MxAsbClient.Probe --tag=TestChildObject.TestInt --tag=TestMachine_001.TestHistoryValue` registers and reads both tags in two-item requests. Both tags return per-item register/read success; values decode as ASB `TypeInt32` previews `412` and `303`. Evidence: `analysis\proxy\mxasbclient-probe-stage24-multi-read.txt`. ## Next capture steps 1. Decode `NmxSvcps.dll` and `WWProxyStub.dll` to recover interface names, opnum signatures, and NDR stub layouts for the observed DCE/RPC UUIDs. 2. Extract all localhost binary streams, not just port `49704`, and correlate them to harness method/callback timestamps. 3. Trace `LmxProxy.dll`, `Lmx.dll`, `NmxAdptr.dll`, and `NmxSvc.exe` with API Monitor around: `send`, `recv`, `WSASend`, `WSARecv`, `INmx4.PutRequest2`, `INmx4.GetResponse2`, `IDataClient.RegisterItems2`, `IDataClient.Write2`, and `IDataClient.PublishWriteComplete2`. 4. Decide whether a future strict MXAccess COM-compatibility mode should intentionally reproduce the x86 `SAFEARRAY VT_BOOL` value-pairing behavior from capture `098`; the native managed path currently uses direct per-element bool encoding. 5. Build managed encoder/decoder tests from the scalar, array, and write-mode matrix TSVs. 6. Decode DCE/RPC/NDR and mixed-stream records structurally; raw byte searching has confirmed that adapter bodies are not copied verbatim to TCP.