70feb63ea5
Lands the subscribe-path lifecycle: AdviseSupervisory + UnAdvise
round-trip via a Subscription handle. The actual DataChange stream
routing is deferred to F15.
New
- Session::subscribe(reference) -> Result<Subscription, Error> —
resolves the tag, generates a 16-byte correlation_id via
rand::random(), calls NmxClient::advise_supervisory. Mirrors
MxNativeSession.SubscribeAsync (cs:250-270) minus the publisher
Connect dance (will land alongside F15's callback routing).
- Session::unsubscribe(subscription) -> Result<(), Error> — consumes
the handle and calls NmxClient::un_advise. Mirrors
MxNativeSession.Unsubscribe (cs:361-381).
- Subscription { correlation_id, reference, metadata } public type
with accessor methods. Currently a pure lifecycle handle — no
Stream impl yet; the Stream<Item=DataChange> shape lands when F15
wires CallbackExporter routing.
- Removed the old subscribe stub from lib.rs (was Err(Unsupported)).
Drop hazard note
- Subscription deliberately does NOT impl Drop to fire UnAdvise. The
spawn-from-Drop pattern is the R15 hazard tracked in
design/70-risks-and-open-questions.md. Callers must call
Session::unsubscribe(sub).await explicitly. F15's wave-2 long-lived
connection task will support best-effort drop-time cleanup without
the spawn-from-Drop hazard.
Cargo.toml: added rand (for correlation_id generation).
design/followups.md: F15 added (P1, M4 wave 2 callback router).
Open followups now at 11 — slightly over the soft 10-item threshold
but no drift (F13 just resolved last iteration). Next iteration's
Step 0 triage will check whether F15 is actionable.
Tests (4 new in mxaccess; total 30)
- subscribe_then_unsubscribe round-trip via in-memory resolver +
hand-rolled server (2 RPCs: AdviseSupervisory + UnAdvise).
- subscribe propagates non-zero AdviseSupervisory HRESULT.
- subscribe after shutdown returns EngineNotRegistered.
- two_subscribes_produce_distinct_correlation_ids — verifies the
rand::random() correlation id generation differentiates handles.
Test count delta: 494 -> 498 (+4). All four DoD gates green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
mxaccess (Rust port)
Native Rust replacement for AVEVA / Wonderware MXAccess. See ../design/ for
the architectural specification, ../src/ for the .NET reference (the
executable spec), and ../CLAUDE.md for project-wide rules.
Status
M0 — Workspace skeleton. Stub types compile; nothing is implemented yet.
See ../design/60-roadmap.md for the M0–M6 milestone plan.
Layout
rust/
Cargo.toml workspace root
rust-toolchain.toml 1.85 stable
crates/
mxaccess-codec/ pure protocol codec, no I/O
mxaccess-galaxy/ Galaxy SQL resolver (tiberius)
mxaccess-rpc/ DCE/RPC + NTLMv2 + OXID + OBJREF
mxaccess-callback/ INmxSvcCallback RPC server
mxaccess-nmx/ INmxService2 client
mxaccess-asb-nettcp/ net.tcp framing (MC-NMF + MC-NBFX/NBFS)
mxaccess-asb/ IASBIDataV2 client
mxaccess/ async session + Transport trait + public API
mxaccess-compat/ LMXProxyServer-shaped facade
Build
cargo build --workspace
cargo test --workspace
cargo clippy --workspace -- -D warnings
cargo fmt --check
Live probes
. ..\tools\Setup-LiveProbeEnv.ps1
cargo test -p mxaccess --features live -- --ignored
The setup script fetches credentials from Infisical via
wwtools/secrets/Get-Secret.ps1. Never inline plaintext credentials.
License
MIT — see ../LICENSE.