From 8b50c0fd438a3eff914eaf61b7fe3e2d5311f9b2 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Thu, 7 May 2026 04:27:59 -0400 Subject: [PATCH] CHANGELOG: curate post-F43 work into V1 entry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- CHANGELOG.md | 102 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 82 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4785ea0..345d783 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,10 +5,12 @@ format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); the workspace as a whole follows [SemVer](https://semver.org/) but the 0.0.x line is pre-release / API-unstable. -## [Unreleased] — V1 — 2026-05-06 +## [Unreleased] — V1 — 2026-05-07 V1 is the first publishable cut. Closes M0 → M6 from -`design/60-roadmap.md`. +`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 @@ -29,6 +31,10 @@ V1 is the first publishable cut. Closes M0 → M6 from `IRemUnknown::RemQueryInterface` + `RemAddRef`/`RemRelease` (F11). - **`mxaccess-callback`** — RPC server hosting `INmxSvcCallback` + `IRemUnknown` for inbound `DataReceived` / `StatusReceived` frames. + `dcom_sink` (F55 Path A, gated by `windows-com`) hosts the callback + as a DCOM-managed object so `RegisterEngine2` accepts it on AVEVA + installs that do SCM-side OXID resolution against RPCSS; the + hand-rolled `CallbackExporter` is retained for unit tests. - **`mxaccess-nmx`** — `INmxService2` client (`RegisterEngine2`, `TransferData`, `AddSubscriberEngine`, `SetHeartbeatSendInterval`, etc.) plus auto-resolving `NmxClient::create` factory (F12, gated by @@ -49,12 +55,28 @@ V1 is the first publishable cut. Closes M0 → M6 from `subscribe_buffered` per R2 single-sample-with-cadence-knob semantics (F36), `recover_connection` reconnect loop (F16), recovery events (`RecoveryEvent::Started/Recovered/Failed`), and a typed - `Error` taxonomy. Optional `metrics` feature emits per-op counters, - latency histograms, and connection-state gauges (F40). + `Error` taxonomy. Recovery replay re-issues `RegisterReference` (not + `AdviseSupervisory`) for buffered subscriptions so the + `.property(buffer)` shape survives transport rebuild (F45); + `unsubscribe` skips the `UnAdvise` wire frame for buffered + subscriptions to match the .NET reference's `IsBuffered` guard + (F47). `Session::ensure_publisher_connected` issues the + `INmxService2::Connect` + `AddSubscriberEngine` round-trip before + the first advise against each publishing engine, so `0x33` + DataUpdate frames flow on this AVEVA install (F56). New + `WriteHandle { correlation_id }` returned by `*_with_handle` write + variants for per-operation correlation; `OperationStatus.context` + carries the originating `OperationContext` (F54). Optional + `metrics` feature emits per-op counters, latency histograms, and + connection-state gauges (F40). - **`mxaccess-compat`** — `LMXProxyServer`-shaped Rust facade exposing the 18-method `ILMXProxyServer5` surface as async fns over `mxaccess::Session` / `AsbSession` with a `Mutex>` handle table and `Stream`-based event channels (F35). + `LmxClient` spawns an `operation_status_drain` fan-out task that + routes `Write` / `WriteSecured` events to `on_write_complete` and + every other op kind to `on_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 diagnostic @@ -63,6 +85,37 @@ V1 is the first publishable cut. Closes M0 → M6 from - **Tooling** — `cargo public-api` baselines under `design/public-api/{crate}.txt` with CI drift check (F41). `design/M6-bench-baseline.md` records 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) — `BytesMut` output + so consumers can `split_to` / `freeze` and forward to a wire-level + sink without copying. Same alloc count as `encode`. + - Thread-local name-signature cache (F52.2) — repeated + `MxReferenceHandle::from_names` calls with the same names skip the + `to_lowercase` + CRC walk. `from_names` drops 2 → 0 allocs/op once + warm; bounded at 1024 entries per thread. + - `write_message::encode_into_bytes_mut` (F52.3) — caller-supplied + `BytesMut` scratch buffer; reusing across writes drops fixed-width + scalars from 2 → 1 alloc/op and Boolean from 1 → 0. + Bench deltas pinned in `design/M6-bench-baseline.md` § F52.{1,2,3}. +- **Live evidence — F49 / F50 / F51.** F49 step 5 (`LmxClient` + `OnWriteComplete` round-trip) verified live against AVEVA via + `cargo test -p mxaccess-compat --features live-windows-com --test + lmx_write_complete_live`. F50 captured `Suspend` (NMX opcode `0x2D`, + server-side) + `Activate` (client-side, no wire traffic) under + `captures/123-frida-suspend-advised-instrumented/` + + `captures/124-frida-activate-advised-instrumented/`; R5 settled. + F51 provisioned 7 UDAs on `$TestMachine` via `wwtools/graccesscli` + (TestFloat / TestDouble / TestDateTime / TestDuration + array + variants), captured live `AsbVariant` wire bytes for each scalar + type, and pinned them via + `crates/mxaccess-codec/tests/f51_type_matrix_parity.rs`. +- **`MxStatus` synthesizer kernel** — Path A from `Lmx.dll` + `FUN_10100ce0` ported into `MxStatus::from_packed_u32`. Settles R3 + + R4 (`OperationComplete` trigger conditions and completion-only + byte mappings: the .NET reference's `WriteCompleted` is itself a + half-implementation; the Rust port preserves the wire bytes + verbatim and routes them through the synthesizer kernel). ### Changed (vs the .NET reference) @@ -77,26 +130,35 @@ V1 is the first publishable cut. Closes M0 → M6 from ### Known limitations - **F3** — cross-domain NTLM Type1/2/3 fixture is permanently - out-of-scope on the dev host (single-domain). Single-domain wire - parity is verified; cross-domain is documented but not regression- - tested. -- **F45** — recovery replay for buffered subscriptions falls through - to plain `AdviseSupervisory`, losing the `.property(buffer)` - registration. Filed as a follow-up. -- **F46** — `LmxProxy.dll!CLMXProxyServer.Suspend`/`.Activate` wire - emission was not instrumented; the compatibility-server's - client-side gating is documented but the underlying ORPC call - shape is unconfirmed. -- **R3 / R4** — `OperationComplete` trigger conditions and - completion-only byte mappings are unmapped in both the .NET - reference and the Rust port. Frame bytes are preserved verbatim - via `Session::operation_status_events()`. + 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.md` for whoever has access to a + two-forest Windows lab. +- **F53 (protocol crates only)** — `#![warn(missing_docs)]` is + enabled and warning-clean on the consumer-facing `mxaccess` + + `mxaccess-compat` lib 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.md` validates each crate's `cargo +> package --list` produces 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. -For V1 cut: +already-published deps to exist on crates.io, so the order matters: 1. `mxaccess-codec` (no internal deps) 2. `mxaccess-rpc` (no internal deps)