Files
mxaccess/docs/Current-Sprint-State.md
T
Joseph Doherty fe2a6db786
rust / build / test / clippy / fmt (push) Has been cancelled
Initial project state: .NET reference, design, Rust port (M0+M1), evidence
Layout:
- src/                    .NET 10 x64 reference: MxNativeCodec, MxNativeClient,
                          MxAsbClient, probes, tests, harnesses. Executable spec.
- design/                 Architectural plan for the Rust port (M0–M6), error
                          model, protocol invariants, risks (R1–R16), adversarial
                          review log (review.md).
- rust/                   Rust workspace. M0 skeleton + M1 codec parity.
                          mxaccess-codec: 215 unit tests + 2 cross-implementation
                          parity tests (byte-identical against .NET reference).
                          Other crates are M0 stubs awaiting M2+.
- captures/               Frida + netsh + pcap evidence per CLAUDE.md
                          ("captures are evidence, not throwaway logs").
- analysis/               Decompiled C# (frida/proxy/decompiled-*),
                          Ghidra exports for native DLLs (`exports/` only —
                          working state at `projects/` and AVEVA's input
                          binaries at `input/` are gitignored).
- docs/                   Reverse-engineering reference docs.
- tools/                  Setup-LiveProbeEnv.ps1 (Infisical credential fetcher),
                          Compute-Crc.ps1 (.NET parity helper).
- .github/workflows/      Rust CI: fmt + build + test + clippy on Windows.
- LICENSE                 MIT (Joseph Doherty, 2026).

Verified:
- cargo test --workspace → 217 passed (215 unit + 2 .NET parity), 0 failed
- cargo clippy --workspace -- -D warnings → clean
- cargo fmt --all -- --check → clean
- cargo publish --dry-run -p mxaccess-codec → packages cleanly

Excluded from history (see .gitignore):
- **/bin, **/obj, **/target — build artifacts
- analysis/ghidra/projects/ — Ghidra working state (regenerable)
- analysis/ghidra/input/ — AVEVA proprietary DLLs (vendor IP)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 06:21:00 -04:00

499 lines
32 KiB
Markdown

# Current Sprint State
Last updated: 2026-04-26 local VM time.
## Build Status
- `MxNativeCodec.Tests`: passed.
- `MxNativeClient.Tests`: passed, including compatibility surface and recovery
lifecycle event checks.
- `MxTraceHarness`: Release build passed.
- `MxNativeClient.Probe`: Release build passed, including
`--probe-session-recover-multi`.
- `MxDataConsumerProbe`: Release build passed. It is an x86 .NET Framework
probe for `aaMxDataConsumer.dll` / `IDataConsumer`.
- `AsbProxyProbe`: Release x64 .NET Framework build passed. It directly loads
the MSIL ASB IData proxy/contract assemblies and proves x64 managed
discovery, connection, register, read, write, readback, and published write
completion against the live MxDataProvider. It also has an unregister compare
probe for installed-proxy parity.
- `MxAsbClient.Probe`: Release .NET 10 x64 build passed. It is a pure managed
ASB client slice with no AVEVA assembly references. It reads the ASB solution
shared secret through DPAPI, performs the ASB system-auth handshake, opens the
net.tcp `IASBIDataV2` channel, and live validates register, read, write,
readback, published write completion, unregister parity, and multi-item
register/read for `TestChildObject.TestInt` plus
`TestMachine_001.TestHistoryValue`.
- `MxAsbClient.Probe`: clean Release build with zero warnings after adding an
explicit `System.Security.Cryptography.Xml` `10.0.7` package reference and
replacing the obsolete PBKDF2 constructor with `Rfc2898DeriveBytes.Pbkdf2`.
- `MxAsbClient.Tests`: passed. The test project covers ASB `Variant` factory
and decode/display round trips for bool, int, float, double, string,
datetime, duration, and the matching bool/int/float/double/string/
datetime/duration array wire payloads, `MonitoredItemValue` binary
serializer round trips for publish responses, and ASB status payload decoding
for publish quality/status mapping. It also covers the stable ASB connection
options overload, endpoint validation, and friend-only debug payload helper
visibility, plus write-completion option validation and cancellation-token
surface, subscription/monitored-item option overloads, and publish result
summary helpers. Latest focused non-live shaping tests also assert exact
defaults and validation boundaries for `AsbConnectionOptions`,
`AsbWriteOptions`, `AsbWriteCompletionOptions`, `AsbSubscriptionOptions`,
and `AsbMonitoredItemOptions`, DTO shape for write-completion/readback
results, null collection argument guards, empty/non-empty `AsbPublishResult`
helpers, derived status-summary helpers, item-status array mapping, and both
compatibility `Advise` overload signatures.
- Credential scan over `analysis`, `docs`, `src`, `captures`, and `README.md`:
no matches for the protected password patterns.
## Implemented Surface
- Public MXAccess method surface is accounted for: `Register`, `Unregister`,
`AddItem`, `RemoveItem`, `Advise`, `UnAdvise`, `Write`, `WriteSecured`,
`AuthenticateUser`, `ArchestrAUserToId`, `AddItem2`, `Write2`,
`WriteSecured2`, `Suspend`, `Activate`, `AdviseSupervisory`,
`AddBufferedItem`, and `SetBufferedUpdateInterval`.
- Public event families are represented: `OnDataChange`, `OnWriteComplete`,
`OperationComplete`, and `OnBufferedDataChange`.
- `WriteSecured2` is implemented and live validated from .NET 10 x64 for the
observed authenticated secured-write body shape.
- `WriteSecured` remains intentionally unsupported because installed MXAccess
returns `0x80004021` for the observed secured/verified paths before emitting a
value-bearing NMX body.
- Object/VARIANT timestamp overloads exist for `Write2` and `WriteSecured2`,
matching the installed interop assembly's public API shape.
- `SetHeartbeatSendInterval` is exposed and live validated through
`MxNativeClient.Probe --probe-session-heartbeat --heartbeat-ticks=5
--heartbeat-max-missed=3`.
- Explicit recovery is implemented as `MxNativeSession.RecoverConnection()` and
`MxNativeCompatibilityServer.RecoverConnection(serverHandle)`. The probe
`--probe-session-recover --tag=TestChildObject.TestInt --value=321` live
validated subscribe, recover, preserved subscription count, and write-through
against local NmxSvc.
- `RecoverConnectionAsync` adds a caller-controlled retry loop via
`MxNativeRecoveryPolicy`. Automatic write retry remains intentionally absent
because it can duplicate writes.
- Recovery lifecycle is observable through `RecoveryAttemptStarted`,
`RecoveryAttemptFailed`, and `RecoveryCompleted` on both `MxNativeSession`
and the managed compatibility facade. A live recovery probe with
`--recover-attempts=2 --recover-delay-ms=100 --value=323` observed one
started event, zero failed events, and one completed event.
- Callback, operation-status, reference-registration, unparsed-callback,
compatibility data-change, buffered-data-change, and write-complete payloads
now carry `IsDuringRecovery`. The current policy is pass-through with an
explicit marker rather than suppression or buffering.
- `MxNativeClient.Probe --probe-session-recover-multi` replays several active
subscriptions and counts recovery-window callbacks by callback family.
## Runtime Evidence Added In Latest Sprints
- GR identified `TestMachine_001.TestHistoryValue` as a deployed, historized
integer dynamic attribute.
- Captures `121` and `122` proved context-bearing buffered registration/result
bodies for `TestHistoryValue.property(buffer)` in context `TestMachine_001`.
- Both supervisory and plain advise buffered captures still produced no native
`Fire_OnBufferedDataChange` entry on this VM.
- Reflection over `ArchestrA.MXAccess.dll` confirmed the 18-method public
method surface, four event families, `MxStatus` layout, `MxStatusCategory`,
`MxStatusSource`, and full `MxDataType` enum values.
- Installed `Lmx.aaDCT` detail text has been folded into `MxStatusDetails` for
details `16`-`61`, `541`, `542`, and `8017`.
- Explicit recovery event reporting is implemented and live validated. The
events report attempt number, maximum attempts, exception, and retry intent
for failed attempts.
- A live multi-subscription recovery probe replayed four subscriptions and
preserved all four. It observed two data callbacks and four unparsed callbacks
overall, but zero callbacks with `IsDuringRecovery=true` on this VM.
- A follow-up churn run with `--recover-concurrent-writes` wrote
`TestChildObject.TestInt` values `330`-`334` from a separate managed session
during recovery. It preserved all four subscriptions and observed four data
callbacks overall, including two with `IsDuringRecovery=true`. This validates
the pass-through-with-marker callback policy under live traffic.
- `aaMxDataConsumer.dll` was imported with `tlbimp` into
`analysis\interop\Interop.aaMxDataConsumer.dll` and decompiled under
`analysis\decompiled-interop\Interop.aaMxDataConsumer`. Registry CLSID
`{85209FB2-0BA1-4594-BBC4-59D3DDAB823D}` maps to `MxDataConsumer Class`.
- `MxDataConsumerProbe` can instantiate `DataConsumerClass`, register
`IDataConsumerCallback`, resolve namespace strings to namespace ID `1`, and
call `ResolveReference`, `subscribe`, `ActivateSuspend`, and
`ProcessActivateSuspend2`. However, `IsConnected(namespaceId)` remains `0`
for tested namespace strings (`Galaxy`, `ArchestrA`, `Lmx`, `localhost`,
`ZB`), no registration/subscription responses are produced, and
`ProcessActivateSuspend2` returns `0x8007139F` (`ERROR_INVALID_STATE`).
- `DataClientClass` appears in the imported `aaMxDataConsumer` type library,
but its CLSID `{73BC4121-FF89-4762-901C-206E2BD9FE87}` is not registered on
this node; `MxDataConsumerProbe --probe-dataclient` reports
`0x80040154` (`REGDB_E_CLASSNOTREG`) before endpoint tests can run.
- Headless/ILSpy analysis of `aaMxDataConsumer.dll` shows the COM consumer is a
mixed-mode wrapper around managed ASB data proxies. `CDataClientCLI` creates a
`DataClientProxy`, stores the namespace string, starts an auto-connect
worker, and `DataClientProxy.Initialize` passes that same string as
`AccessName` to `IDataProxySelector.SelectProxyForLatestEndpoint`.
- `ASBIDataV2Adapter.dll` from the GAC contains `IDataProxySelector`. It first
calls `ASBDataV2Proxy.FindIDataEndpoint(accessName, DiscoveryScope.Global)`;
if that returns endpoints it constructs `ASBDataV2Proxy`, otherwise it falls
back to V1. The discovery scope is
`domainname/<accessName>/global`.
- `AsbProxyProbe` live x64 discovery found one `IASBIDataV2` endpoint for
access name `ZB`:
`net.tcp://desktop-6jl3kko/ASBService/Default_ZB_MxDataProvider/IDataV2`.
It found no endpoint for `Default_ZB_MxDataProvider`, `Galaxy`,
`localhost`, or `ZB2`.
- `AsbProxyProbe --access=ZB --connect` opened the ASB data proxy from an x64
managed process. `ASBDataV2Proxy.Connect` returned `true`, channel state was
`Opened`, and `PublishWriteComplete` returned `0x00000000` with no pending
writes. This proves a managed x64 ASB route to LMX/NMX data services exists
on this node.
- `AsbProxyProbe --access=ZB --connect --register --read --tag=TestChildObject.TestInt`
registered and read the test integer through `IASBIDataV2`. `RegisterItems`
and `Read` both returned `0x00000000`; the item id was
`18446462598732840961`; the read value was ASB `DataType.TypeInt32` (`4`)
with payload `4E010000`, decoded as `334`.
- `AsbProxyProbe --access=ZB --connect --register --read --write-int=401 --tag=TestChildObject.TestInt`
accepted a basic ASB write and proved readback. The immediate `Write` result
was globally successful, the per-item synchronous status was `0x0000001F`
(`ArchestrAError.OperationWouldBlock`), the next read returned integer value
`401`, and `PublishWriteComplete` then returned `0x00000020`
(`ArchestrAError.PublishComplete`) with the original write handle
`0xA5B20001` and per-item completion status `0x00000000`. This is the first
direct evidence that ASB write completion polling can supply the missing
operation-completion source without the unstable DataConsumer COM wrapper.
- Forcing the x86 `MxDataConsumerProbe` to `--namespace=ZB` hung in the COM
wrapper path and was stopped. The direct ASB route is currently the cleaner
path for x64 managed implementation and for any future `OperationComplete`
trigger testing.
- The pure .NET 10 ASB port in `src\MxAsbClient` now proves the live data path
without loading AVEVA assemblies. Key auth details recovered during the port:
this VM's ASB solution registry key is `Archestra_DESKTOP-6JL3KKO`,
`HashAlgorthim=None`, `keySize=256`, and the solution overrides the DH prime
with the installed 768-bit value. The remaining compatibility fixes were the
AVEVA CLR data-contract namespaces/field ordering for `ConnectionValidator`,
`WriteValue`, `ItemStatus`, `ItemIdentity`, and `ItemWriteComplete`, plus
mutable-struct-safe binary decode for nested `Variant` and `ASBStatus`
values.
- `MxAsbClient.Probe --tag=TestChildObject.TestInt --write-int=412` completed
from .NET 10 x64. The run read the previous value `411`, accepted write
`412` with immediate per-item `0x0000001F` (`OperationWouldBlock`), read back
`412`, and decoded `PublishWriteComplete` result `0x00000020`, count `1`,
handle `0xA5B21001`, and final per-item status `0x00000000`.
- `RegisterItems` parity is resolved for the observed startup race. ASB
`AuthenticateMe` is a one-way WCF call; the first immediate `RegisterItems`
can arrive before `ASBIDataV2Shim.AuthenticateMe` adds the connection
implementation. The client now matches installed proxy signing for normal
data calls and retries `RegisterItems` briefly when the immediate result is
`0x00000001` (`InvalidConnectionId`). Stage21 evidence shows first register
message `2` returned `InvalidConnectionId`, retry message `3` returned
`0x00000000` with item id `18446462598732840961`, and later calls on the
same connection succeeded.
- `UnregisterItems` is implemented in the pure .NET 10 ASB port and compared
against the installed AVEVA `ASBDataV2Proxy`. Both clients return global
success `0x00000000` and the same per-item `0x0000000B`
(`OperationFailed`) for the registered `TestChildObject.TestInt` identity on
this provider. Evidence:
`analysis\proxy\mxasbclient-probe-stage23-unregister-id.txt` and
`analysis\proxy\asbproxyprobe-unregister-compare.txt`.
- Multi-item ASB register/read is implemented and live validated in the pure
.NET 10 port. `analysis\proxy\mxasbclient-probe-stage24-multi-read.txt`
registers and reads `TestChildObject.TestInt` and
`TestMachine_001.TestHistoryValue` in single two-item requests. Both register
and read return global `0x00000000` and per-item `0x00000000`; the read values
decode as ASB `TypeInt32` with previews `412` and `303`.
- The pure .NET 10 ASB port now has non-live ASB `Variant` construction and
decode/display coverage for the core scalar and array type matrix:
`TypeBool`, `TypeInt32`, `TypeFloat`, `TypeDouble`, `TypeString`,
`TypeDateTime`, `TypeDuration`, and their matching array forms. The public
client also exposes a generic `Write(tag, Variant, writeHandle)` path so the
next live write probes can reuse the same encoder instead of being hard-coded
to `Int32`.
- A live ASB register/read probe validated the deployed scalar and array read
type matrix from .NET 10 x64:
`TestChildObject.TestBool` -> `TypeBool` (`17`),
`TestChildObject.TestInt` -> `TypeInt32` (`4`),
`TestChildObject.TestFloat` -> `TypeFloat` (`8`),
`TestChildObject.TestDouble` -> `TypeDouble` (`9`),
`TestChildObject.TestString` -> `TypeString` (`10`),
`TestChildObject.TestDateTime` -> `TypeDateTime` (`11`), and the matching
`TestBoolArray[]`, `TestIntArray[]`, `TestFloatArray[]`,
`TestDoubleArray[]`, `TestStringArray[]`, and `TestDateTimeArray[]` returning
ASB array types `57`, `44`, `48`, `49`, `50`, and `51`. All register/read
per-item statuses were `0x00000000`, and previews decoded correctly.
- `MxAsbClient.Probe` now keeps SOAP envelopes and custom serializer byte dumps
opt-in through `--dump-messages`; normal traced runs still show stages,
statuses, type IDs, payload lengths, timestamps, and decoded previews without
dumping request/response bodies.
- Pure .NET 10 ASB scalar writes are live validated beyond `Int32`:
`TestBool` accepted a `TypeBool` write and later read back `False` then
`True`; `TestFloat` wrote/read `13.25`; `TestDouble` wrote/read `13.75`;
`TestString` accepted `asb-scalar-write` and read it back on a delayed
follow-up read; `TestDateTime` wrote/read `2026-04-26T14:33:00Z`.
These runs confirm the scalar factory payloads are accepted by the live
provider. Immediate `PublishWriteComplete` often returned count `0`, and
bool/string immediate readback could be stale or time out, so completion
polling/readback timing remains part of the production timeout policy gap.
- Pure .NET 10 ASB array writes are live validated for the deployed dynamic
array tags. `TestIntArray[]` accepted/read back `11`-`20`;
`TestBoolArray[]` accepted/read back alternating false/true values;
`TestFloatArray[]` and `TestDoubleArray[]` accepted/read back
`11.1`-`20.1`; `TestStringArray[]` accepted/read back `K11`-`T20`; and
`TestDateTimeArray[]` accepted/read back ten UTC timestamps from
`2026-04-26T14:40:00Z` through `2026-04-26T14:49:00Z`. Int, bool, and string
array readback needed a delayed follow-up read, matching the scalar timing
behavior noted above.
- Production cleanup removed the active ASB build warnings: the vulnerable
transitive `System.Security.Cryptography.Xml` `10.0.0` package is overridden
with `10.0.7`, `dotnet list package --include-transitive --vulnerable` now
reports no vulnerable packages for `MxAsbClient`, and the system-auth
PBKDF2 derivation uses the non-obsolete static API while preserving the
installed proxy's 1000-iteration SHA1 derivation inputs. A live ASB read of
`TestChildObject.TestInt` still succeeds after the authenticator change.
- Pure .NET 10 ASB subscription/publish is now live-proven for the basic
lifecycle. `MxAsbClient.Probe --subscribe --publish-count=3` created
subscription id `3`, added monitored items for `TestChildObject.TestInt` and
`TestChildObject.TestString` with per-item status `0x00000000`, received both
values on the second publish poll (`TypeInt32` preview `412` and `TypeString`
preview `asb-scalar-write`), deleted the subscription with global success,
and then completed the existing unregister cleanup path. Publish responses
returned global `0x00000020`, matching the previously observed completion
queue success signal, so exact result/status mapping remains open.
- The ASB publish path now maps raw `MonitoredItemValue` responses into
callback-ready values with item-id-to-tag-name resolution, decoded value,
UTC timestamp, `MxQuality`, and parsed ASB status elements. A live publish
run for `TestChildObject.TestInt` and `TestChildObject.TestString` decoded
quality `0x00C0` and status elements
`OpcUaStatus:0|OpcUaVendorStatus:0|MxStatusCategory:0|MxStatusDetail:0|MxQuality:192`
for both values. The same run added `DeleteMonitoredItems`, returned global
success and per-item `0x00000000` for both monitored identities, then deleted
the subscription successfully.
- `MxAsbDataClient` now exposes `PublishedValueReceived` plus `PublishValues`.
`PublishValues` keeps the raw `PublishResponse`, maps values through the
client's monitored-item id cache, and raises one event per mapped value. A
live probe observed `published_event[0]` for `TestChildObject.TestInt` and
`published_event[1]` for `TestChildObject.TestString`, both with quality
`0x00C0` and expected previews.
- `MxAsbCompatibilityServer` now adapts the pure ASB stream into an MXAccess-like
server/item-handle data-change facade. A live
`MxAsbClient.Probe --compat-subscribe --publish-count=3` run registered ASB
server handle `1`, added/advised item handles `1` and `2`, polled publish,
raised two `compat_data_change` events with values `412` and
`asb-scalar-write`, quality `0x00C0`, decoded ASB status elements, removed
both monitored items, and unregistered cleanly.
- The compatibility facade now has an `Advise` overload that accepts
`AsbSubscriptionOptions` plus `AsbMonitoredItemOptions`, matching the public
ASB subscription shaping instead of forcing positional primitives. The probe
compatibility subscribe path now exercises this route, and a live
compatibility subscribe run through the options overload registered/advised
item handles, published mapped `compat_data_change` events, removed monitored
items, and unregistered cleanly.
- Initial non-success ASB item-status mapping is implemented. A live probe for
`DefinitelyMissingObject.DefinitelyMissingAttribute` showed global register
and read success but per-item read error `0x000A`
(`InvalidMonitoredItems`), an empty value with value-status payload decoded
as `OpcUaStatus:32905|OpcUaVendorStatus:0|MxStatusCategory:750|MxStatusDetail:0|MxQuality:0`,
and unregister per-item error `0x000B` (`OperationFailed`). Unknown payloads
are still preserved rather than guessed.
- `MxAsbClient.Probe --probe-error-cases` now keeps broader live ASB
non-success evidence opt-in without changing production client behavior.
Evidence in `analysis\proxy\mxasbclient-probe-error-cases-v2.txt` covers:
invalid read target global success with per-item `0x000A`
(`InvalidMonitoredItems`) plus bad value status; invalid-target async write
immediate per-item `0x001F` (`OperationWouldBlock`) followed by
`PublishWriteComplete` global `0x0020` (`PublishComplete`) and completion
per-item `0x0006` (`MonitoredItemsNotFound`) with status payload
`OpcUaStatus:6|OpcUaVendorStatus:0|MxStatusCategory:4|MxStatusDetail:6`;
wrong-type string write to `TestChildObject.TestInt` immediate per-item
`0x001F`, completion per-item `0x0013`
(`WriteFailedBadTypeMismatch`) with status payload
`OpcUaStatus:19|OpcUaVendorStatus:0|MxStatusCategory:4|MxStatusDetail:8001`,
and unchanged readback value `412`; invalid monitored-item cleanup global
success with per-item `0x000B` (`OperationFailed`); invalid subscription
delete global `0x000C`, status `0x0020`, specific `0x80020000`; and empty
subscription publish returning global `0x0020` with no values.
- ASB result/status summaries now name the full installed `ArchestrAError`
code set, classify publish `0x0020` (`PublishComplete`) as success-like for
publish polling, summarize `MxQuality` as good/uncertain/bad/unknown, and map
known MX status details `16` and `17` to `RequestTimedOut` and
`BadNoCommunication` while preserving raw category/detail/quality values.
- Async ASB write completion now has a production-oriented polling helper.
`WaitForWriteComplete` polls `PublishWriteComplete` for a target write
handle until completion or timeout, preserving every raw poll response and
completion record. `WaitForWriteCompleteAndRead` adds optional delayed
readback without retrying or duplicating the write. A live probe using
`--write-int=412 --wait-write-complete --write-complete-timeout-ms=5000
--write-complete-poll-ms=250 --write-readback-delay-ms=500` saw the first
completion poll return count `0`, the second poll return the matching
handle `0xA5B21001`, and readback return `412`.
- ASB client disposal now has explicit cleanup reporting. `Dispose()` delegates
to idempotent `Cleanup()`, sends signed `Disconnect` best-effort, closes the
channel and factory independently, aborts safely after close/fault failures,
and records `LastCleanupResult` with `Completed`, `Succeeded`,
`UsedAbortFallback`, and `RequiresNewConnection`. A live read probe ended
with `asb.stage=cleanup`, `asb.stage=disconnect`, and
`asb.cleanup.succeeded=True`.
- ASB reconnect now has an explicit opt-in API. `MxAsbDataClient.Reconnect`
validates caller retry options, optionally cleans up the current connection,
returns a new client on success, and preserves attempt plus cleanup evidence
without silently replaying reads or writes. A live
`MxAsbClient.Probe --probe-reconnect --reconnect-attempts=2
--reconnect-delay-ms=250 --cleanup-disconnect-timeout-ms=5000
--cleanup-close-timeout-ms=5000` run read `TestChildObject.TestInt` as
`412`, cleaned up and disconnected the original connection with caller
cleanup timeouts, connected successfully on attempt `1`, re-registered the
tag on the new client, read back `412`, and completed final cleanup
successfully.
- ASB cleanup partial-failure behavior is now covered in focused local tests
without forcing live provider faults. The close-or-abort policy is factored
into `AsbCommunicationCleanup`, and tests cover already-closed objects,
faulted objects that skip close and abort directly, close failure followed by
abort fallback, abort failure reporting, and propagation of the configured
close timeout.
- ASB cleanup cancellation now has explicit policy. `AsbClientCleanupOptions`
accepts a `CancellationToken`; when cancellation is already requested, cleanup
skips graceful disconnect/close attempts and uses local abort cleanup for
non-closed channel/factory objects. Existing disconnect and close timeouts
still bound synchronous WCF calls that have already started. A live
`MxAsbClient.Probe --probe-canceled-cleanup` run connected, registered, and
read `TestChildObject.TestInt` as `412`, then skipped disconnect and aborted
both opened communication objects locally. The cleanup result completed with
`succeeded=False`, `abort=True`, `requires_new=True`, and an
`OperationCanceledException` disconnect marker.
- Safe connection-failure evidence is now captured without destabilizing the
live ASB provider. `MxAsbClient.Probe --probe-connect-failure
--endpoint=net.tcp://localhost:1/ASBService/Default_ZB_MxDataProvider/IDataV2`
reports `connect_failure_observed=True`, an `EndpointNotFoundException`, and
inner `SocketException` `10061` for a refused local TCP endpoint. `Connect`
now also closes or aborts any WCF channel/factory objects created before a
connection setup failure; the refused-endpoint probe reports
`asb.stage=connect-cleanup` before surfacing the exception.
- OperationComplete candidate evidence was narrowed on the direct ASB route.
The `IASBIDataV2` contract exposed by the live endpoint has no activate,
suspend, or generic operation-complete operation; only `PublishWriteComplete`
exists, and it is write-specific. A live
`MxAsbClient.Probe --probe-operation-complete-candidates` run against
`TestChildObject.TestInt` and `TestChildObject.TestString` polled
`PublishWriteComplete` before and after create-subscription, add-monitored,
publish, delete-monitored, and delete-subscription operations. Every poll
returned count `0`, while the non-write operations themselves succeeded.
- ASB buffered-subscription probing is now explicit. `MxAsbDataClient` exposes
the `MonitoredItem.Buffered` flag through `AddMonitoredItems`, and
`MxAsbClient.Probe --subscribe-buffered` sets it. A live buffered ASB publish
probe against the historized `TestMachine_001.TestHistoryValue` succeeded,
but returned the same single current `MonitoredItemValue` shape as the normal
publish path: value `303`, quality `0x00C0`, timestamp
`2026-04-26T02:33:45.4260000Z`, and no multi-sample buffered batch.
- ASB status mapping now includes installed MX detail `33`
(`WriteAccessDenied`) and maps it to `WriteFailedAccessDenied`. Tests cover
success, invalid monitored item, wrong-type write completion, invalid cleanup
evidence inputs, request timeout detail `16`, platform/no-communication
detail `17`, and access-denied detail `33`.
- Safe access-denied/no-communication candidate probes did not produce those
source conditions on this VM. Same-value direct ASB writes to secured
`TestMachine_001.ProtectedValue` and verified
`TestMachine_001.ProtectedValue1` both completed successfully and read back
unchanged, so direct system-auth ASB writes do not reproduce the MXAccess
public secured-write rejection path here. A read-only sweep of
`TestMachine_001.ProtectedValue` through `TestMachine_020.ProtectedValue`
registered and read all 20 values with good quality `0x00C0`; no
no-communication status was observed.
- Native NMX completion-only operation-status handling is now stricter. One-byte
completion-only frames remain preserved as raw, unpromoted completion statuses
even when the byte is `0x00`; only the observed 5-byte status-word frame
`00 00 50 80 00` maps to `MxStatus.WriteCompleteOk` and can raise the
MXAccess-compatible write-complete event. Tests cover completion-only
`0x00`, `0x41`, and `0xEF`, plus the promoted `0x8050/0x00` status-word
frame. A search of the available Ghidra/decompiled outputs did not expose a
completion-byte-to-`MXSTATUS_PROXY[]` mapping table beyond this public-event
evidence.
- Public ASB connection shaping has started. `AsbConnectionOptions` is now the
stable options object for `MxAsbDataClient.Connect`, the previous string
overload delegates to it, and `MxAsbCompatibilityServer.Register` has a
matching options overload. Endpoint validation now happens before registry or
WCF setup work. `AsbPayloadDebug` is no longer part of the public surface and
is friend-only for the test/probe assemblies. A clean Release probe build,
non-live ASB contract test run, and live read of `TestChildObject.TestInt`
all passed after this change.
- ASB write-completion options now validate their own polling/readback policy
and expose a `CancellationToken`. Cancellation is checked before each
`PublishWriteComplete` poll and during poll/readback delays; already-running
synchronous ASB calls remain bounded by the existing WCF operation behavior.
A live same-value write-completion probe still completed on the second poll
for handle `0xA5B21001` and read back `412`.
- ASB subscription API shaping now has `AsbSubscriptionOptions` and
`AsbMonitoredItemOptions` overloads while preserving the existing positional
overloads. A live subscribe/publish probe created subscription `15`, added
monitored items for `TestChildObject.TestInt` and
`TestChildObject.TestString`, published both mapped values/events, deleted
the monitored items, deleted the subscription, and cleaned up the ASB
connection successfully.
- `AsbPublishResult` now exposes a derived `Result` summary and `HasValues`
helper so callers do not have to remap raw `PublishResponse.Result` on every
publish poll. The existing raw response and mapped values remain preserved.
- Public API/request-shaping tests were expanded without live ASB dependency.
They now pin defaults, validation, overload visibility, and helper DTO shape
for connection, write completion/readback, subscription, monitored-item,
compatibility `Advise`, and publish-result surfaces.
- Basic writes now also have an `AsbWriteOptions` overload carrying the write
handle and optional comment. The existing positional write overload and
`WriteInt32` path delegate through it, preserving behavior; a live same-value
write-completion probe still completed on the second poll and read back
`412`.
- Public multi-item APIs now explicitly reject null collection arguments before
touching client state: `RegisterMany`, `UnregisterMany`, `ReadMany`,
option-based `AddMonitoredItems`, and `DeleteMonitoredItems`. Empty
collections and tag/item contents keep their prior behavior.
- Published values and compatibility data-change events now expose derived
`StatusSummary` helpers, and `AsbResultMapper.ToItemSummaries` maps nullable
item-status arrays into raw-preserving summaries with null/empty as an empty
result.
- `docs\ASB-Variant-Wire-Format.md` now captures the supported ASB `Variant`
wire format: type IDs, scalar and array payload layouts, timestamp/duration
handling, string encoding, runtime value wrapping, quality/status payload
parsing, and intentionally raw-preserved unsupported types.
- `docs\ASB-Native-Integration-Decision.md` now records the recommended
integration boundary: prefer `MxAsbClient` for the regular tag data plane
behind a higher-level compatibility routing facade, keep `MxNativeClient` for
native callback-only semantics and not-yet-proven MXAccess behaviors, and
avoid merging ASB WCF lifecycle internals into the NMX DCE/RPC session.
## Remaining Highest-Value Gaps
1. Additional live faulted-channel and source-condition evidence that can be
gathered safely. Current safe secured/verified-write and protected-value
read sweeps did not produce access-denied or no-communication statuses.
2. Broaden pure .NET 10 ASB only where safe source conditions expose new
behavior. Local ASB `Variant` encode/decode, live reads/writes,
subscription/create/add/publish/delete-monitored/delete-subscription,
mapped publish events, compatibility data-change facade, options-shaped
connection/write/subscription/advise/write-completion APIs, cleanup, and
reconnect are now covered. Remaining work is mainly external failure modes
such as access denied or no-communication when a safe source condition is
available.
3. A native runtime trigger for `OperationComplete`. The direct ASB path now
proves a write-completion queue (`PublishWriteComplete`) carrying write
handles and per-item statuses. Direct `IASBIDataV2` does not expose
activate/suspend or a generic operation-complete contract, and subscription
create/add/publish/delete operations did not enqueue write-completion
records. Map to MXAccess event ID `3` only if a separate native trigger is
found.
4. A native runtime/source condition that emits `OnBufferedDataChange` sample
batches. Direct ASB buffered monitored items are accepted by this provider,
but the observed publish response still carries one current value rather
than a native MXAccess buffered sample batch.
5. Exact mapping for completion-only operation-status bytes into
`MXSTATUS_PROXY[]`; current code now preserves all completion-only bytes as
unpromoted raw statuses instead of guessing, including completion-only
`0x00`.
6. Cleanup behavior around live faulted/partial failure cases when a safe
provider condition is available.
7. Less-common ASB variant types only when real deployed tags expose them; the
currently supported/proven variant layout is documented and unsupported
declared types remain raw-preserved.
## Recommended Next Sprint
Treat the public ASB API-shaping sprint as closed unless a concrete caller gap
appears. The remaining local documentation gaps are also closed. Return to exact
completion-only operation-status mapping into `MXSTATUS_PROXY[]` only if a new
safe native public-event capture or deeper targeted decompile can provide
evidence. Keep live faulted-channel, access-denied, and source
no-communication probes opportunistic unless a safe provider condition appears.