[F53 partial] enable #![warn(missing_docs)] on consumer crates

mxaccess + mxaccess-compat now carry #![warn(missing_docs)] at the
crate root. Every public item has at least a one-line doc comment
(struct fields, enum variants, trait methods all covered).

Touched items:
- mxaccess::lib: DataChange fields, SecurityContext fields,
  TransportKind variants, TransportCapabilities fields,
  RecoveryEvent variants + their inner fields, SessionOptions
  fields, the full Error / ConnectionError / AuthError /
  ProtocolError / ConfigError / SecurityError taxonomy + nested
  fields, Transport trait method docs.
- mxaccess-compat::lib: DataChangeEvent / BufferedDataChangeEvent /
  WriteCompleteEvent / OperationCompleteEvent fields.

Protocol crates (codec, rpc, galaxy, nmx, callback, asb,
asb-nettcp) deliberately left without the lint per F53's strategy
paragraph — their consumers (mxaccess + mxaccess-compat) already
document the surfaces they re-export, and forcing one-liners on
every transport-internal item adds noise without consumer value.

Verification:
- `RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps` clean.
- `cargo test --workspace` (824 tests) green.
- `cargo clippy --workspace --all-targets -- -D warnings` clean.

design/followups.md F53 marked partially resolved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-05-06 12:20:47 -04:00
parent d149143535
commit c606736ec3
3 changed files with 157 additions and 39 deletions
+4 -34
View File
@@ -25,39 +25,6 @@ Between each publish: wait for the crate to be indexed before the next one's `ca
**Resolves when:** crates.io shows all 9 crates published + the V1 tag is pushed.
### F49 — Live verification sweep for the M6 features
**Status:** **Resolved 2026-05-06.** All five steps pass live against the local AVEVA install (`docs/M6-live-verification.md`):
- Step 1 (F36 buffered subscribe) — `tests/buffered_subscribe_live.rs`
- Step 2 (F45 buffered recovery replay) — `tests/buffered_recovery_replay_live.rs`
- Step 3 (F47 buffered unsubscribe skip) — `tests/buffered_unsubscribe_skip_live.rs`
- Step 4 (F40 metrics smoke) — `tests/metrics_smoke_live.rs`
- Step 5 (F54 OnWriteComplete) — `tests/lmx_write_complete_live.rs`
F55 (DCOM-managed `INmxSvcCallback`) and F56 (missing `EnsurePublisherConnected` + AdviseSupervisory after RegisterReference for buffered) were the two real Rust-port bugs uncovered along the way; both are resolved.
**Severity:** P1 — closes the live-evidence gap for the M6 work that landed unit-only this session.
**Source:** F36, F40, F45, F47, F54 closeouts — each ships with unit tests but most were not exercised against the live AVEVA install in this session.
**Blocked-by:** F12 hardening (`Session::connect_nmx_auto` returns `RPC_S_SERVER_UNAVAILABLE` (1722) under `cargo test`'s tokio multi-thread runtime — see "Live attempt 2026-05-06" below). The COM-activation path itself works in isolation (`cargo run -p mxaccess-rpc --example com-marshal-probe --features windows-com` succeeds), so the failure is downstream — likely a COM apartment threading issue when CoInitializeEx runs on a tokio worker thread.
**Scope.** Run the following against the live AVEVA host with `MX_LIVE=1`:
1. **F36 buffered subscribe**`cargo run -p mxaccess --example subscribe-buffered -- --tag TestChildObject.TestInt`. Confirm `OnBufferedDataChange`-rate updates flow at the configured cadence; capture wire bytes via `analysis/frida/mx-nmx-trace.js` and confirm exactly one `RegisterReference` (`0x10`) frame with `.property(buffer)` suffix, no separate `SetBufferedUpdateInterval` RPC, and no separate `AdviseSupervisory` follow-up.
2. **F45 recovery replay for buffered** — start the `subscribe-buffered` example, force a `Session::recover_connection` mid-flight (e.g. via a `wwtools` helper that bumps the NMX TCP socket), assert the post-recovery NMX traffic carries an `RegisterReference` (NOT `AdviseSupervisory`) with the same correlation id and `.property(buffer)` suffix.
3. **F47 buffered unsubscribe skip** — instrument `Session::unsubscribe` with a `tracing::debug` log line on the buffered branch, run the example to completion + drop, confirm no `UnAdvise` frame in the wire trace.
4. **F40 metrics** — install a `metrics` exporter (`metrics-exporter-prometheus` is the lightest), run `connect-write-read` + `subscribe` examples with `--features metrics`, confirm at least one counter increment and one histogram observation per metric name in the registered set.
5. **F54 OnWriteComplete (LmxClient round-trip)** — scaffold lives at `crates/mxaccess-compat/tests/lmx_write_complete_live.rs`. Run `cargo test -p mxaccess-compat --features live-windows-com --test lmx_write_complete_live -- --ignored --nocapture` to drive `LmxClient::write` → drain `client.on_write_complete()` and assert the `WriteCompleteEvent { server_handle, item_handle, statuses, is_during_recovery }` shape matches `LMX_OnWriteComplete(int hLMXServerHandle, int phItemHandle, ref MXSTATUS_PROXY[] pVars)`.
**Live attempt 2026-05-06.** Steps 1-4 not run yet. Step 5 attempted; the test compiled and ran past Frida-style `--probe-resolve-oxid-managed-ntlm-integrity` resolution + `--probe-remqi-managed` IPID extraction, but `connect_nmx_auto` (preferred path) and `connect_nmx` (fallback with probe-resolved IPID) both fail with `Status { detail: 1722 }` (RPC_S_SERVER_UNAVAILABLE). The .NET `MxNativeClient.Probe --probe-session-write` runs the same scenario successfully end-to-end against the same AVEVA install, so the wire is functional and the failure is Rust-port specific. Root-caused as F55 (hand-rolled callback exporter rejected by NmxSvc's SCM-side OXID resolution); not a tokio-runtime COM-activation issue.
**Step 5 unblocked 2026-05-06 by F55 / Path A.** `cargo test -p mxaccess-compat --features live-windows-com --test lmx_write_complete_live -- --ignored --nocapture` passes against the live AVEVA install: RegisterEngine2 OK, write round-trips, OnWriteComplete fires with the expected `WriteCompleteEvent { server_handle, item_handle, statuses, is_during_recovery }` shape. Steps 1-4 still pending.
**Step 1 attempted 2026-05-06 — blocked by F56.** Added `crates/mxaccess-compat/tests/buffered_subscribe_live.rs` driving `Session::subscribe_buffered` via `Session::connect_nmx_auto`. RegisterReference completes successfully against the live engine, but no `0x33` DataUpdate frames are ever received — only op-status frames per write and the `0x11` registration-result. The Rust port's wire frame for RegisterReference must differ from the .NET reference's in some field. The codec router was also fixed during this attempt (envelope-peeling for `NmxSubscriptionMessage` and the `0x11` `NmxReferenceRegistrationResultMessage` path were both missing); those are real bugs that would have hidden any DataUpdate had one arrived. F56 captures the open work.
**Definition of done:**
1. Per-feature evidence summary in `docs/M6-live-verification.md` (one paragraph per feature with the wire-trace excerpt or metrics-exporter snapshot).
2. If any feature fails live: file a sub-followup with the captured failure and link it from the evidence doc.
3. F12's tokio-runtime COM activation issue resolved (the `connect_nmx_auto` 1722 error above) so the live tests can actually run.
**Resolves when:** all five features have a live evidence row + no sub-followups remain unresolved.
### F50 — Run the F46 Suspend/Activate Frida capture live
**Severity:** P3 — residual from F46 (script ready, capture not yet run).
**Source:** F46 closeout (`design/followups.md`) + `analysis/frida/mx-nmx-trace.js` header procedure.
@@ -98,6 +65,7 @@ F55 (DCOM-managed `INmxSvcCallback`) and F56 (missing `EnsurePublisherConnected`
**Resolves when:** all three optimisations land or are deliberately rejected with a note in the baseline doc.
### F53 — Enable `#![warn(missing_docs)]` workspace-wide
**Status:** Consumer crates resolved 2026-05-06: `#![warn(missing_docs)]` enabled on `mxaccess` and `mxaccess-compat` lib roots, every public item now carries at least a one-line doc comment, `RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps` clean. Protocol crates (`mxaccess-codec`, `mxaccess-rpc`, `mxaccess-galaxy`, `mxaccess-nmx`, `mxaccess-callback`, `mxaccess-asb`, `mxaccess-asb-nettcp`) deliberately deferred per the strategy paragraph below — their consumers (`mxaccess` + `mxaccess-compat`) already document the surfaces they re-export, and forcing one-liners on every transport-internal item adds noise without consumer value.
**Severity:** P3 — doc-coverage tightening; not a correctness or release blocker.
**Source:** F42 closeout — the missing-docs lint was deferred because enabling it surfaces hundreds of low-priority public-item gaps that are out of scope for that F-number.
@@ -111,7 +79,9 @@ F55 (DCOM-managed `INmxSvcCallback`) and F56 (missing `EnsurePublisherConnected`
**Resolves when:** the lint is on and the workspace doc build is warning-clean with it.
### F56 — `subscribe` / `subscribe_buffered` complete on the wire but never receive `0x33` DataUpdate frames
**Status:** **Resolved 2026-05-06.** Root cause: `Session::subscribe` and `Session::subscribe_buffered_nmx` were missing the `INmxService2::Connect` + `AddSubscriberEngine` round-trip that the .NET reference's `MxNativeSession.EnsurePublisherConnected` (`cs:516-526`) issues before the first advise against a given publishing engine. Without that pair of RPCs, NmxSvc accepts the subscription registration but the publishing engine never knows our engine is subscribed — so no `0x33` DataUpdate frames flow.
**Status:** **Resolved 2026-05-06.** See Resolved section below for the full closeout.
Root cause: `Session::subscribe` and `Session::subscribe_buffered_nmx` were missing the `INmxService2::Connect` + `AddSubscriberEngine` round-trip that the .NET reference's `MxNativeSession.EnsurePublisherConnected` (`cs:516-526`) issues before the first advise against a given publishing engine. Without that pair of RPCs, NmxSvc accepts the subscription registration but the publishing engine never knows our engine is subscribed — so no `0x33` DataUpdate frames flow.
Diagnosed via wwtools/aalogcli: the `[Warning] NmxSvc | NmxCallback->DataReceived ... failed with error 0x{N}` log lines turned out to be NmxSvc's normal log spam where N is the bufferSize, NOT an actual error — the .NET reference's own probe triggers identical entries while still receiving `0x33` DataUpdate frames successfully. The real issue was that those frames never started being sent in the first place.
+25
View File
@@ -50,6 +50,7 @@
//! live-trigger work.
#![forbid(unsafe_code)]
#![warn(missing_docs)]
use std::collections::{HashMap, VecDeque};
use std::pin::Pin;
@@ -73,12 +74,20 @@ use tokio_stream::wrappers::BroadcastStream;
/// `MxNativeDataChangeEvent` (`MxNativeCompatibilityServer.cs:6-13`).
#[derive(Debug, Clone)]
pub struct DataChangeEvent {
/// LMX server handle that produced this event.
pub server_handle: i32,
/// Item handle within `server_handle` whose value changed.
pub item_handle: i32,
/// Decoded value payload.
pub value: MxValue,
/// Legacy 16-bit OPC quality.
pub quality: u16,
/// Wire-recorded timestamp (Windows FILETIME-derived).
pub timestamp: SystemTime,
/// Richer category-model status (complements `quality`).
pub status: MxStatus,
/// `true` when the event was emitted while a `recover_connection`
/// attempt was in flight.
pub is_during_recovery: bool,
}
@@ -90,13 +99,21 @@ pub struct DataChangeEvent {
/// capture proves multi-sample bodies real.
#[derive(Debug, Clone)]
pub struct BufferedDataChangeEvent {
/// LMX server handle that produced this event.
pub server_handle: i32,
/// Item handle within `server_handle`.
pub item_handle: i32,
/// `MxDataType` discriminator for the carried values.
pub mx_data_type: i16,
/// Sample values — length 1 per R2's single-sample verdict.
pub values: Vec<MxValue>,
/// Per-sample legacy 16-bit OPC qualities. Same length as `values`.
pub qualities: Vec<u16>,
/// Per-sample timestamps. Same length as `values`.
pub timestamps: Vec<SystemTime>,
/// Per-sample richer-category status. Same length as `values`.
pub statuses: Vec<MxStatus>,
/// `true` when the event was emitted during recovery.
pub is_during_recovery: bool,
}
@@ -104,9 +121,13 @@ pub struct BufferedDataChangeEvent {
/// `MxNativeWriteCompleteEvent` (`cs:15-19`).
#[derive(Debug, Clone)]
pub struct WriteCompleteEvent {
/// LMX server handle that issued the original write.
pub server_handle: i32,
/// Item handle the write was targeted at.
pub item_handle: i32,
/// Per-write completion statuses (one per `MXSTATUS_PROXY` slot).
pub statuses: Vec<MxStatus>,
/// `true` when the write completed while recovery was in flight.
pub is_during_recovery: bool,
}
@@ -117,9 +138,13 @@ pub struct WriteCompleteEvent {
/// once the trigger is captured.
#[derive(Debug, Clone)]
pub struct OperationCompleteEvent {
/// LMX server handle the operation belongs to.
pub server_handle: i32,
/// Item handle the operation was targeted at.
pub item_handle: i32,
/// Per-operation statuses.
pub statuses: Vec<MxStatus>,
/// `true` when the event was emitted during recovery.
pub is_during_recovery: bool,
}
+128 -5
View File
@@ -18,6 +18,7 @@
//! deferred work tracker.
#![forbid(unsafe_code)]
#![warn(missing_docs)]
use std::borrow::Cow;
use std::sync::Arc;
@@ -60,11 +61,16 @@ pub struct Session {
/// `wwtools/mxaccesscli/docs/api-notes.md:104-105`.
#[derive(Debug, Clone)]
pub struct DataChange {
/// Tag reference the update applies to.
pub reference: Arc<str>,
/// Decoded value payload.
pub value: MxValue,
/// Legacy 16-bit OPC quality. Distinct from `status: MxStatus`.
pub quality: u16,
/// Wire-recorded timestamp (Windows FILETIME-derived) for the update.
pub timestamp: SystemTime,
/// Richer category-model status. Complements `quality` for callers
/// that want the modern MxStatus taxonomy.
pub status: MxStatus,
}
@@ -125,9 +131,16 @@ impl BufferedOptions {
}
}
/// Caller identity for secured-write operations. Mirrors the .NET
/// reference's two-user-id pattern (`current_user_id` is the actor;
/// `verifier_user_id` is the supervisor approving the change). Both
/// fields are required by the wire op even when the same user fills
/// both roles — pass the same id twice for single-user secured writes.
#[derive(Debug, Clone)]
pub struct SecurityContext {
/// User id of the caller initiating the write.
pub current_user_id: i32,
/// User id of the verifier (supervisor) approving the write.
pub verifier_user_id: i32,
}
@@ -135,17 +148,32 @@ pub struct SecurityContext {
#[derive(Debug, Clone)]
pub struct ConnectionOptions;
/// Discriminator for which transport produced (or should consume) a
/// given session, error, or capability set. Surfaces in
/// [`Error::Unsupported`] to identify the transport that rejected an
/// operation and in [`TransportCapabilities`] indirectly.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum TransportKind {
/// NMX (DCE/RPC over `INmxService2` to NmxSvc.exe).
Nmx,
/// ASB (`IASBIDataV2` over net.tcp to MxDataProvider).
Asb,
}
/// Per-transport capability flags — used by feature-detection helpers
/// to query what an open session supports without spelunking through
/// transport-specific docs.
#[derive(Debug, Clone, Copy)]
pub struct TransportCapabilities {
/// `true` if the transport supports a server-side buffered
/// delivery cadence (NMX yes; ASB no — gated at API level).
pub buffered_subscribe: bool,
/// `true` if the transport supports the `Activate` / `Suspend`
/// pair on a subscribed item (NMX yes; ASB no).
pub activate_suspend: bool,
/// `true` if the transport surfaces an OperationComplete frame
/// post-write (NMX yes via `OnWriteComplete`; ASB no).
pub operation_complete_frame: bool,
}
@@ -196,23 +224,38 @@ impl Default for RecoveryPolicy {
}
}
/// Recovery-attempt lifecycle event broadcast on
/// [`Session::recovery_events`].
///
/// Not `Clone` — `Error` is not `Clone`-able (thiserror chains an
/// `io::Error` source which is not `Clone`). Consumers that need to clone an
/// event should wrap it in `Arc`.
#[derive(Debug)]
#[non_exhaustive]
pub enum RecoveryEvent {
/// `recover_connection` started a new attempt. `attempt` is
/// 1-indexed and counts only attempts driven by the current
/// `recover_connection` invocation (resets on a fresh call).
Started {
/// 1-indexed attempt counter.
attempt: u32,
},
/// An attempt failed. `error` is the underlying cause; `will_retry`
/// is `true` when the configured [`RecoveryPolicy`] still has
/// retry budget.
Failed {
/// 1-indexed attempt counter.
attempt: u32,
/// Underlying error that caused the failure.
error: Error,
/// Whether the configured policy will retry. Mirrors
/// `MxNativeRecoveryFailureEvent.WillRetry` (`MxNativeSession.cs:47-51`).
will_retry: bool,
},
/// Recovery completed successfully. `attempt` is the index of the
/// successful attempt.
Recovered {
/// 1-indexed attempt counter.
attempt: u32,
},
}
@@ -242,12 +285,20 @@ pub enum RecoveryEvent {
/// `RegisterEngine2`.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SessionOptions {
/// Local engine id advertised to NmxSvc. Default: `0x7000 + (pid & 0x0FFF)`.
pub local_engine_id: i32,
/// Engine name string sent to `RegisterEngine2`. Default: `mxaccess.<pid>`.
pub engine_name: String,
/// Partner-version negotiated with NmxSvc. Default: `6`.
pub partner_version: i32,
/// Galaxy id for routing. Default: `1`.
pub galaxy_id: u8,
/// Source-platform id for outbound NMX envelopes. Default: `1`.
pub source_platform_id: i32,
/// `Some(n)` enables `SetHeartbeatSendInterval(n, ...)` after register;
/// `None` skips the heartbeat config call entirely (default).
pub heartbeat_ticks_per_beat: Option<i32>,
/// Heartbeat tolerance — only used when `heartbeat_ticks_per_beat` is `Some`.
pub heartbeat_max_missed_ticks: i32,
}
@@ -290,43 +341,63 @@ impl Default for SessionOptions {
// ---- Error taxonomy ------------------------------------------------------
/// Top-level error returned by every fallible `mxaccess` API. The
/// variants partition errors into stable categories so consumers can
/// match on shape without spelunking nested error types.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
/// Transport bring-up or runtime connection-state failure.
#[error("connection: {0}")]
Connection(#[from] ConnectionError),
/// NTLM or other authentication failure.
#[error("authentication: {0}")]
Auth(#[from] AuthError),
/// Wire protocol / codec violation (decode failure, size mismatch).
#[error("protocol: {0}")]
Protocol(#[from] ProtocolError),
/// Caller-supplied options or arguments rejected pre-flight.
#[error("configuration: {0}")]
Configuration(#[from] ConfigError),
/// `MxValue` kind doesn't match what the resolver / engine expects
/// for the named tag.
#[error("type mismatch on {reference}: expected {expected:?}, got {actual:?}")]
TypeMismatch {
/// Tag reference whose write/read triggered the mismatch.
reference: Arc<str>,
/// Kind the engine / resolver expected.
expected: MxValueKind,
/// Kind the caller supplied (or the wire returned).
actual: MxValueKind,
},
/// Galaxy- or session-level security check rejected the operation.
#[error("security: {0}")]
Security(#[from] SecurityError),
/// Operation isn't supported on the chosen transport
/// (e.g. `subscribe_buffered` on ASB).
#[error("unsupported on {transport:?} transport: {operation}")]
Unsupported {
/// Human-readable name of the operation that was rejected.
operation: Cow<'static, str>,
/// Transport that rejected the operation.
transport: TransportKind,
},
/// Operation didn't complete within its timeout budget.
#[error("operation timed out after {0:?}")]
Timeout(Duration),
/// Operation was cancelled (e.g. via cancellation token / `drop`).
#[error("operation cancelled")]
Cancelled,
/// Server-reported MxStatus (decoded category + detail).
// Field is named `detected_by` (not `source`) to match the codec's
// `MxStatus.detected_by` and to avoid thiserror's `#[source]` attribute
// semantics (which would require `MxStatusSource: std::error::Error`).
@@ -334,23 +405,37 @@ pub enum Error {
"status: success={success} category={category:?} detected_by={detected_by:?} detail={detail}"
)]
Status {
/// `0` = success, non-zero = failure with the rest of the
/// fields populated.
success: i16,
/// Status category as decoded from the wire (`MxStatusCategory`).
category: MxStatusCategory,
/// Layer that originally detected the failure.
detected_by: MxStatusSource,
/// Detail code carrying the specific reason.
detail: i16,
},
/// Underlying I/O error from the transport socket.
#[error("io: {0}")]
Io(#[from] std::io::Error),
}
/// Connection / transport-bring-up failure modes.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum ConnectionError {
/// RPC server (NmxSvc / MxDataProvider) unreachable or refused
/// the connection.
#[error("RPC server unavailable")]
ServerUnavailable,
/// COM proxy/stub for the callback interface isn't registered on
/// the box (`REGDB_E_CLASSNOTREG` from the SCM).
#[error("callback proxy/stub not registered (REGDB_E_CLASSNOTREG)")]
CallbackProxyMissing,
/// `RegisterEngine2` returned non-zero, OR an operation was
/// attempted on a session whose engine wasn't registered (e.g.
/// post-shutdown).
#[error("engine not registered (UninitializedObject / ERROR_INVALID_STATE)")]
EngineNotRegistered,
/// Transport bring-up failed during preamble exchange or
@@ -359,38 +444,68 @@ pub enum ConnectionError {
/// keep the public taxonomy small. ASB-specific (F26 step 2);
/// `EngineNotRegistered` covers the analogous NMX failure mode.
#[error("transport bring-up failed: {detail}")]
TransportFailure { detail: String },
TransportFailure {
/// Stringified underlying-error detail.
detail: String,
},
}
/// Authentication-side failures.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum AuthError {
/// NTLM handshake rejected by the peer.
#[error("NTLM rejected: {reason}")]
Ntlm { reason: String },
Ntlm {
/// Human-readable reason from the underlying NTLM stack.
reason: String,
},
}
/// Wire-protocol violations surfaced by the codec layer.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum ProtocolError {
/// Decode failure at a specific buffer offset.
#[error("decode at offset {offset} ({reason}); buffer len {buffer_len}")]
Decode {
/// Byte offset within the buffer where decoding failed.
offset: usize,
/// Static description of the violation.
reason: &'static str,
/// Total buffer length (for context in error messages).
buffer_len: usize,
},
/// Outer envelope's declared inner length doesn't match the actual body size.
#[error("inner length {declared} does not match body length {actual}")]
InnerLengthMismatch { declared: i32, actual: usize },
InnerLengthMismatch {
/// Inner length declared in the envelope header.
declared: i32,
/// Actual remaining body length on the wire.
actual: usize,
},
/// First-byte command opcode wasn't recognised by the parser.
#[error("unexpected opcode {0:#x}")]
UnexpectedOpcode(u8),
}
/// Caller-side configuration / argument errors.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum ConfigError {
/// Argument failed pre-flight validation. `detail` carries the
/// specific reason (which arg, what was wrong).
#[error("invalid argument: {detail}")]
InvalidArgument { detail: String },
InvalidArgument {
/// Human-readable description of the rejected argument.
detail: String,
},
/// Galaxy resolver returned an error during tag resolution.
#[error("galaxy resolver: {reason}")]
Galaxy { reason: String },
Galaxy {
/// Underlying resolver error message.
reason: String,
},
/// `Session::recover_connection` was called without a
/// [`crate::RebuildFactory`] installed via
/// [`crate::Session::set_recovery_factory`]. F16.
@@ -400,11 +515,15 @@ pub enum ConfigError {
RecoveryNotConfigured,
}
/// Security-related operation rejection.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum SecurityError {
/// NmxSvc rejected our callback OBJREF (`HRESULT 0x8001011D`).
#[error("callback OBJREF rejected (HRESULT 0x8001011D)")]
CallbackObjRefRejected,
/// Caller invoked a secured-write op without supplying the
/// verifier user id.
#[error("verifier user token required for secured write")]
VerifierRequired,
}
@@ -414,10 +533,14 @@ pub enum SecurityError {
/// Generic-only trait — `dyn Transport` is intentionally unsupported (see
/// design/20-async-layer.md L53 fix). Consumers parameterise on `<T: Transport>`.
pub trait Transport: Send + Sync + 'static {
/// Reports per-transport capability flags so callers can
/// feature-gate without hard-coding transport identity.
fn capabilities(&self) -> TransportCapabilities;
/// Reports which [`TransportKind`] this implementation produces.
fn kind(&self) -> TransportKind;
}
// ---- Session API surface -------------------------------------------------
//
// The `*_value` family in `session.rs` takes `WriteValue` (the codec's