The CHANGELOG was cut at F43 and didn't reflect the work that landed
afterwards on the same V1 milestone. Update the V1 [Unreleased] entry
to cover:
Added (since F43):
- F45 — recovery replay re-issues RegisterReference for buffered subs
- F47 — unsubscribe skips UnAdvise for buffered subs
- F49 / F50 / F51 — live verification + Suspend/Activate captures +
ASB type-matrix expansion with new fixture round-trip tests
- F52.{1,2,3} — codec performance optimisations (BytesMut output,
thread-local name-signature cache, caller-supplied scratch buffer)
- F54 — per-operation correlation + compat OnWriteComplete fan-out
- F55 — DCOM-managed INmxSvcCallback sink (Path A)
- F56 — Connect/AddSubscriberEngine round-trip in subscribe path
- MxStatus synthesizer kernel ported (settles R3/R4)
Known limitations (post-resolution):
- Drop F45 / F46 / R3+R4 — all resolved.
- Add F53 protocol-crate missing-docs deferral.
- F3 entry now links the new docs/F3-cross-domain-ntlm-recipe.md.
Publish-order section keeps the DAG but flags F48 (no crates.io
publish) up front so anyone reading the recipe knows it's hygiene
not release prep.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
9.7 KiB
Changelog
All notable changes to the mxaccess workspace are documented here. The
format follows Keep a Changelog;
the workspace as a whole follows SemVer but the
0.0.x line is pre-release / API-unstable.
[Unreleased] — V1 — 2026-05-07
V1 is the first publishable cut. Closes M0 → M6 from
design/60-roadmap.md. The workspace stays at version = "0.0.0"
indefinitely (F48 — internal usage only, no crates.io publish; consumers
depend via path or git).
Added
mxaccess-codec— pure protocol codec coveringMxReferenceHandle,NmxTransferEnvelope,NmxItemControlMessage,NmxWriteMessage(scalar- array, normal + timestamped),
NmxSecuredWrite2Message,NmxSubscriptionMessage(single + multi-record DataUpdate per F44),NmxReferenceRegistrationMessage,NmxMetadataQueryMessage,NmxOperationStatusMessage,ObservedWriteBodyTemplate, ASB Variant +AsbStatus+RuntimeValue,MxStatus,MxValueKind,MxDataType,MxValue. Counting-allocator bench harness inbenches/alloc_count.rs(F38) reports 1–4 allocs per write across the proven matrix, well under the R12 < 5/write target.
- array, normal + timestamped),
mxaccess-rpc— DCE/RPC PDU codec, NTLMv2 client + server-direction packet-integrity verify (F2 withsubtle::ConstantTimeEq), TCP transport, OBJREF parser + Win32CoMarshalInterfaceemitter (F6),IObjectExporter::ResolveOxid+ResolveOxid2(F10),IRemUnknown::RemQueryInterface+RemAddRef/RemRelease(F11).mxaccess-callback— RPC server hostingINmxSvcCallback+IRemUnknownfor inboundDataReceived/StatusReceivedframes.dcom_sink(F55 Path A, gated bywindows-com) hosts the callback as a DCOM-managed object soRegisterEngine2accepts it on AVEVA installs that do SCM-side OXID resolution against RPCSS; the hand-rolledCallbackExporteris retained for unit tests.mxaccess-nmx—INmxService2client (RegisterEngine2,TransferData,AddSubscriberEngine,SetHeartbeatSendInterval, etc.) plus auto-resolvingNmxClient::createfactory (F12, gated bywindows-com).mxaccess-galaxy—tiberius-backedResolver+UserResolver(F14, gated bygalaxy-resolver).mxaccess-asb-nettcp—[MS-NMF]framing +[MC-NBFX]binary-XML[MC-NBFS]static dictionary + DH/HMAC/AES auth crypto with constant-timemod_expviacrypto-bigint::DynResidue(F27).
mxaccess-asb—IASBIDataV2client (Connect,RegisterItems,Read,Write,PublishWriteComplete,CreateSubscription,AddMonitoredItems,Publish,DeleteMonitoredItems,DeleteSubscription,Disconnect) with canonical-XML HMAC signing for all 13ConnectedRequestshapes (F28) and DataContract field-suffix names on the binaryMonitoredItembody (F34).mxaccess— async Tokio façade exposingSession,AsbSession,Subscription(Stream<Item = Result<DataChange, Error>>),subscribe_bufferedper R2 single-sample-with-cadence-knob semantics (F36),recover_connectionreconnect loop (F16), recovery events (RecoveryEvent::Started/Recovered/Failed), and a typedErrortaxonomy. Recovery replay re-issuesRegisterReference(notAdviseSupervisory) for buffered subscriptions so the.property(buffer)shape survives transport rebuild (F45);unsubscribeskips theUnAdvisewire frame for buffered subscriptions to match the .NET reference'sIsBufferedguard (F47).Session::ensure_publisher_connectedissues theINmxService2::Connect+AddSubscriberEngineround-trip before the first advise against each publishing engine, so0x33DataUpdate frames flow on this AVEVA install (F56). NewWriteHandle { correlation_id }returned by*_with_handlewrite variants for per-operation correlation;OperationStatus.contextcarries the originatingOperationContext(F54). Optionalmetricsfeature emits per-op counters, latency histograms, and connection-state gauges (F40).mxaccess-compat—LMXProxyServer-shaped Rust facade exposing the 18-methodILMXProxyServer5surface as async fns overmxaccess::Session/AsbSessionwith aMutex<HashMap<i32, ItemRef>>handle table andStream-based event channels (F35).LmxClientspawns anoperation_status_drainfan-out task that routesWrite/WriteSecuredevents toon_write_completeand every other op kind toon_operation_complete, dropping events with unknown correlation ids silently (F54).- Examples —
connect-write-read.rs,subscribe.rs,subscribe-buffered.rs,asb-subscribe.rs,multi-tag.rs,recovery.rs,secured-write.rs, plus diagnosticasb-relay.rs. Live-probe DoD verified end-to-end against the AVEVA install. - Tooling —
cargo public-apibaselines underdesign/public-api/{crate}.txtwith CI drift check (F41).design/M6-bench-baseline.mdrecords the alloc-count baseline. - Performance (post-baseline) — F52. Three codec optimisations
measured against the F38 alloc-count harness:
write_message::encode_to_bytes_mut(F52.1) —BytesMutoutput so consumers cansplit_to/freezeand forward to a wire-level sink without copying. Same alloc count asencode.- Thread-local name-signature cache (F52.2) — repeated
MxReferenceHandle::from_namescalls with the same names skip theto_lowercase+ CRC walk.from_namesdrops 2 → 0 allocs/op once warm; bounded at 1024 entries per thread. write_message::encode_into_bytes_mut(F52.3) — caller-suppliedBytesMutscratch buffer; reusing across writes drops fixed-width scalars from 2 → 1 alloc/op and Boolean from 1 → 0. Bench deltas pinned indesign/M6-bench-baseline.md§ F52.{1,2,3}.
- Live evidence — F49 / F50 / F51. F49 step 5 (
LmxClientOnWriteCompleteround-trip) verified live against AVEVA viacargo test -p mxaccess-compat --features live-windows-com --test lmx_write_complete_live. F50 capturedSuspend(NMX opcode0x2D, server-side) +Activate(client-side, no wire traffic) undercaptures/123-frida-suspend-advised-instrumented/+captures/124-frida-activate-advised-instrumented/; R5 settled. F51 provisioned 7 UDAs on$TestMachineviawwtools/graccesscli(TestFloat / TestDouble / TestDateTime / TestDuration + array variants), captured liveAsbVariantwire bytes for each scalar type, and pinned them viacrates/mxaccess-codec/tests/f51_type_matrix_parity.rs. MxStatussynthesizer kernel — Path A fromLmx.dllFUN_10100ce0ported intoMxStatus::from_packed_u32. Settles R3- R4 (
OperationCompletetrigger conditions and completion-only byte mappings: the .NET reference'sWriteCompletedis itself a half-implementation; the Rust port preserves the wire bytes verbatim and routes them through the synthesizer kernel).
- R4 (
Changed (vs the .NET reference)
NmxSubscriptionMessage::parse_data_updateacceptsrecord_count >= 1; the .NET reference hard-throws onrecord_count != 1. F44 evidence walk againstcaptures/094-frida-buffered-separate-writer/documents the multi-record observation that drove the divergence.subscribe_bufferedreturns aStream<Item = DataChange>(single-sample-per-event); per R2 verification the cadence is a server-side delivery rate knob, not a multi-sample payload.
Known limitations
- F3 — cross-domain NTLM Type1/2/3 fixture is permanently
out-of-scope on the dev host (single-domain only). Single-domain
wire parity is verified; cross-domain rounds-trip through the same
shape-agnostic AV-pair codec but no live fixture pins it. Self-
contained provisioning recipe (lab topology, capture procedure,
fixture layout, round-trip test skeleton) at
docs/F3-cross-domain-ntlm-recipe.mdfor whoever has access to a two-forest Windows lab. - F53 (protocol crates only) —
#![warn(missing_docs)]is enabled and warning-clean on the consumer-facingmxaccess+mxaccess-compatlib roots. Protocol crates measure 1883 missing-docs warnings (mostly struct-field-level wire-shape records); enabling the lint there would add per-field one-liners without consumer value. Lint stays off on protocol crates indefinitely. Per-module#![allow(missing_docs)]opt-out is the re-introduction path if a contributor wants per-crate enforcement.
Publish order
Note (2026-05-06, F48): the workspace will not be published to crates.io. Internal usage only; consumers depend via path or git. The dependency DAG below is retained as a workspace-hygiene check (
design/F48-publish-dry-run.mdvalidates each crate'scargo package --listproduces a clean tarball with no accidental captures or large files) and as the publish recipe if the policy ever changes (e.g. an internal contributor wants registry-style versioning via a private cargo registry).
Workspace crates form a dependency DAG; cargo publish requires
already-published deps to exist on crates.io, so the order matters:
mxaccess-codec(no internal deps)mxaccess-rpc(no internal deps)mxaccess-asb-nettcp(no internal deps)mxaccess-galaxy(depends on codec)mxaccess-callback(depends on rpc + codec)mxaccess-asb(depends on codec + asb-nettcp)mxaccess-nmx(depends on codec + galaxy + rpc + callback)mxaccess(depends on all the above)mxaccess-compat(depends on mxaccess)
cargo publish --dry-run validates each crate's metadata + tarball
in isolation; the dependent crates' dry-runs require the leaf crates
to actually exist on crates.io (the registry lookup happens regardless
of --no-verify). For pre-publish verification: leaf crates dry-run
in CI; dependent crates are validated by the public-api baseline +
build-test-clippy matrix.