From cbc95a468455bb6b522ae5b6cccb23a5550610ac Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Wed, 6 May 2026 01:17:09 -0400 Subject: [PATCH] [F33] design/followups: capture live-subscribe wire gap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Live run of `cargo run -p mxaccess --example asb-subscribe` against the local AVEVA install (with DH params + passphrase loaded from Setup-LiveProbeEnv.ps1 + Get-AsbPassphrase.ps1) surfaced two concrete gaps in the subscription-path response decoders: 1. `CreateSubscriptionResponse` returns subscription_id = 0 — the server almost certainly assigns a real Int64, but decode_create_subscription_response can't locate the `` element. Likely a dict-id our F30 post-pass doesn't resolve for that specific element name. 2. `AddMonitoredItemsResponse` decode fails with MissingField "Status". The wire shape needs a capture-and-diff vs the .NET probe's subscription path. Once subscribe-side ops are issued, the channel desyncs — subsequent read() on the same session fails with the same MissingField error, suggesting NBFX framing state may also be out of sync. The F26 stream API itself (AsbSession::subscribe → Stream>) is complete and unit-tested (commit f2f22df). This followup just captures the live-wire reconciliation work that's still required to make the subscribe path actually return data against MxDataProvider. Once F33 closes, the last M5 live-wire gap is resolved. P2 — not blocking M5 closeout; blocks the Subscribe demo. The asb-subscribe.rs example stays in its working Read-loop form (no regression). When F33 lands, the example can be promoted to demonstrate the full subscribe flow. Co-Authored-By: Claude Opus 4.7 (1M context) --- design/followups.md | 46 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/design/followups.md b/design/followups.md index 1dfbc61..e258419 100644 --- a/design/followups.md +++ b/design/followups.md @@ -146,6 +146,52 @@ move to `## Resolved` with a date + commit hash. F25 (`mxaccess-asb` IASBIDataV2 client) and F26 (`mxaccess::Session` over `AsbTransport`) remain open. With F19-F24 landed, the M5 framing/encoder layer (streams A+B+C+D and the codec stream) is complete; F25 composes them into the `IASBIDataV2` wire client. F22's static dictionary subset is intentionally curated; expand entries as wire captures show new IDs. F27 (constant-time DH) is filed as a separate follow-up below. +### F33 — Live wire reconciliation for the ASB subscription path +**Severity:** P2 — not blocking M5 closeout (the F26 stream API ships with full unit-test coverage), but blocks the `examples/asb-subscribe.rs` Subscribe demo. +**Source:** Live run of `cargo run -p mxaccess --example asb-subscribe` against the local AVEVA install, 2026-05-06. + +**Evidence captured live (with `MX_LIVE` + DH params + passphrase from `tools/Setup-LiveProbeEnv.ps1` + `tools/Get-AsbPassphrase.ps1`):** + +``` +connecting ASB at [fe80::3608:256c:365:cc73%6]:808 ... +connected; lifetime=Some("60000:V2") +registering TestChildObject.TestInt +register status: 0 item(s); first error_code = 0x0000 +creating subscription (max_queue=100, sample=1s) ... +subscription_id = 0 ← suspicious +adding monitored items +add_monitored_items failed: + operation error: response is missing required field Status +``` + +Two distinct symptoms: + +1. **`CreateSubscriptionResponse` returns `subscription_id = 0`** — the + live MxDataProvider almost certainly assigns a real Int64 ID, but + `decode_create_subscription_response` (operations.rs:861) walks + `body_tokens` looking for `` and finds nothing + because the wire emits the element name as a dict-id rather than + inline UTF-8. Likely a dict-id we don't yet resolve in the F30 + post-pass for this specific element. +2. **`AddMonitoredItemsResponse` decode fails with `MissingField "Status"`** — + `decode_add_monitored_items_response` expects a `` array + payload via `collect_asbidata_payloads`. The server's actual + response shape needs a capture-and-diff to compare against the + expected layout. + +Once subscribe-side ops are issued, the channel ends up desynced — +even subsequent `read()` on the same session fails with the same +`MissingField "Status"` error, suggesting NBFX framing state may also +be out of sync after the failed subscription decode. + +**Resolves when:** A side-by-side capture via `examples/asb-relay.rs` +(TCP middleman) running the .NET probe's subscription path reveals +the actual wire bytes for `CreateSubscriptionResponse` + +`AddMonitoredItemsResponse`. Reconcile the dict-id resolution and +response decoder accordingly. The subscribe path then closes the +last live-wire gap on M5 — the F26 stream API itself +(`AsbSession::subscribe`) is already complete and unit-tested. + ### F28 — Canonical XML serialiser for `ConnectedRequest` signing (matches `XmlSerializer.Serialize` byte-for-byte) **Status: PARTIALLY RESOLVED.** The five `[XmlSerializerFormat]` ops (AuthenticateMe, Disconnect, KeepAlive, RegisterItems, UnregisterItems) plus the per-action `ValidatorWireFormat` selector + DH-params-from-registry + dynamic-dict id management all landed in commits `f14580e` / `104efc4`. Live AuthenticateMe + RegisterItems work end-to-end (commit `9063f10`). Read / Write / CreateSubscription / AddMonitoredItems / Publish / DeleteMonitored / DeleteSubscription / PublishWriteComplete still sign over NBFX wire bytes via the legacy fallback; works in practice because the live registry has empty `hashAlgorithm` (no HMAC required for the unforced-MAC path), but will break under any deployment that sets a real algorithm. **Severity now P2** — promote back to P0 if a hashAlgorithm-non-empty environment is in scope. **Severity:** P0 — blocks every signed ASB operation (AuthenticateMe, RegisterItems, all data-plane RPCs).