Replace the 8-line `mxaccess-compat` stub with a real `LmxClient`
struct exposing the 18 `ILMXProxyServer5` methods as Rust async fns
on top of `mxaccess::Session` (NMX) and `mxaccess::AsbSession` (ASB).
Handle-table approach
* `Mutex<HashMap<i32, ItemRef>>` for item handles, populated by
`add_item` / `add_item_2` / `add_buffered_item`, drained by
`remove_item` / `unregister`.
* `Mutex<HashMap<i32, UserRef>>` for user handles allocated by
`authenticate_user` / `archestra_user_to_id`.
* `AtomicI32` monotonic counters for both, matching the .NET
reference's `_nextItemHandle` / `_nextUserHandles` per
`MxNativeCompatibilityServer.cs:62-63`.
Stream-based event surface (per Q4)
* `OnDataChange` / `OnBufferedDataChange` / `OnWriteComplete` /
`OperationComplete` exposed as `EventStream<T>: Stream<Item=T>`,
backed by `tokio::sync::broadcast` channels. Lag silently skips
past `BroadcastStream::Lagged` to keep the public `Item` shape
ergonomic. NOT COM events — that's the post-V1
`mxaccess-compat-com` crate per design/70-risks-and-open-questions.md
Q4. The `OperationComplete` channel is wired but no firing path
is modelled (R3 deferred — no captured byte mapping yet).
* `Advise` / `AdviseSupervisory` spawn a background fan-out task
that drains the `Subscription` stream and routes each
`DataChange` to either `on_data_change` or
`on_buffered_data_change` based on the item's `is_buffered` flag.
`UnAdvise` / `RemoveItem` abort the task.
Pass-through methods
* `Write` / `Write2` -> `Session::write` / `write_with_timestamp`
(`userId` ignored — the underlying surface uses engine identity).
* `WriteSecured2` -> `Session::write_secured_at` with both user ids
always passed (R6: single-user secured = same id twice; never
gated).
* `AdviseSupervisory` collapses onto `Session::subscribe` because
the wire path is `AdviseSupervisory` already (`session.rs:1057`),
matching the .NET reference's `cs:251-259` identical collapse.
* `SetBufferedUpdateInterval` rounds up to nearest 100 ms per
`MxNativeCompatibilityServer.cs:638`.
Stubbed pass-throughs (mirror upstream `Error::Unsupported`)
* `WriteSecured` (no timestamp) — `Session::write_secured` is
stubbed at `crates/mxaccess/src/lib.rs:472` (only
`WriteSecured2`/`0x3A` is ported); workaround documented inline.
* `AddBufferedItem` allocates the handle but `Advise` for buffered
items does not yet drive `Session::subscribe_buffered` cadence
knob — TODO(F36) flagged inline at `add_buffered_item` and
`set_buffered_update_interval`.
Tests (25 new, all green)
* Handle-table lifecycle: Add -> Advise -> UnAdvise -> Remove with
a mocked subscription task.
* Monotonic handle allocation; context-prefix combination.
* `SetBufferedUpdateInterval` rounding (50 -> 100, 101 -> 200, etc.)
+ zero-rejection.
* Compile-time check that all 18 LMX methods are reachable on
`LmxClient`.
* Each event stream yields published items; lag silently dropped.
* GUID-shape validation; server-handle mismatch errors.
Build hygiene
* `cargo build -p mxaccess-compat` clean.
* `cargo test -p mxaccess-compat` -> 25 passed.
* `cargo clippy -p mxaccess-compat --all-targets -- -D warnings` clean.
* `RUSTDOCFLAGS=-D warnings cargo doc -p mxaccess-compat --no-deps` clean.
Deferred / TODOs
* TODO(F36): wire `set_buffered_update_interval` cadence into the
`advise` path for buffered items.
* TODO(R3): plumb a real trigger into `on_operation_complete` once
the byte mapping lands.
* TODO(wave 2): live integration tests against AVEVA.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>