[F56] subscribe / subscribe_buffered: split-form wire body + diagnose Galaxy fixture gap
Three real fixes + one architectural diagnosis:
1. Session::subscribe_buffered_nmx now sends the .NET-reference split
form on the wire:
item_definition = "<attr>.property(buffer)" (was: full reference)
item_context = "<object_tag_name>" (was: empty)
item_handle = SessionInner::next_item_handle.fetch_add(1)
(was: hardcoded 0)
Verified byte-identical against captures/082 + 094 by the existing
buffered_register_reference_parity unit tests. The
item_handle counter mirrors MxNativeCompatibilityServer's
_nextItemHandle++ at MxNativeSession.cs:613.
2. New live tests:
- tests/buffered_subscribe_live.rs (F49 step 1) — uses real Galaxy
metadata via SqlTagResolver + connect_nmx_auto, drives a
background writer at 500ms cadence to force value-changes,
drains DataChange events from Subscription.
- tests/plain_subscribe_live.rs — same harness over plain
Session::subscribe (NOT buffered), used to isolate whether
"no DataUpdate" is buffered-specific (it's not — both fail).
Both pull tracing-subscriber as a dev-dep so `RUST_LOG=trace`
surfaces dcom_sink + router activity.
3. mxaccess-galaxy/sql_resolver.rs: drop the inner-attribute
`#![cfg(feature = "galaxy-resolver")]` — the module-level cfg on
`pub mod sql_resolver` in lib.rs already handles this and Rust
1.85's clippy::duplicated_attributes lint flagged the duplicate
once mxaccess-compat dev-deps activated the feature.
4. F56 finding (diagnosis, NOT a bug fix): the engine on this Galaxy
install does not have an active value for TestChildObject.TestInt.
Confirmed by running the .NET reference's own probe:
dotnet run --project src/MxNativeClient.Probe -c Release \
-- --probe-session-subscribe --tag=TestChildObject.TestInt \
--subscribe-hold-seconds=10
...returns ONE 0x32 SubscriptionStatus (status=3 detail=3
quality=0x00C0 Uncertain value=null) and zero 0x33 DataUpdates —
matching the Rust port's symptom exactly. Not a Rust port bug,
not a wire-byte gap. F49 steps 1-3 need either an actively-
scanned tag or local Galaxy reconfiguration to scan
TestChildObject.TestInt.
Workspace tests + clippy clean under both feature configurations.
F56 entry in design/followups.md updated with the full diagnostic
chain so future-me / future-collaborators can pick it up without
re-tracing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -32,56 +32,10 @@ mod live {
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use futures_util::StreamExt;
|
||||
use mxaccess::{
|
||||
BufferedOptions, GalaxyTagMetadata, MxValue, RecoveryPolicy, Resolver, ResolverError,
|
||||
Session, SessionOptions,
|
||||
};
|
||||
use mxaccess::{BufferedOptions, MxValue, RecoveryPolicy, Session, SessionOptions};
|
||||
use mxaccess_galaxy::SqlTagResolver;
|
||||
use mxaccess_rpc::ntlm::NtlmClientContext;
|
||||
|
||||
struct StaticResolver {
|
||||
tag_reference: String,
|
||||
metadata: GalaxyTagMetadata,
|
||||
}
|
||||
|
||||
impl StaticResolver {
|
||||
fn new(tag_reference: &str) -> Self {
|
||||
let (object, attribute) = tag_reference
|
||||
.split_once('.')
|
||||
.unwrap_or((tag_reference, "TestInt"));
|
||||
Self {
|
||||
tag_reference: tag_reference.to_string(),
|
||||
metadata: GalaxyTagMetadata {
|
||||
object_tag_name: object.to_string(),
|
||||
attribute_name: attribute.to_string(),
|
||||
primitive_name: None,
|
||||
platform_id: 1,
|
||||
engine_id: 2,
|
||||
object_id: 3,
|
||||
primitive_id: 0,
|
||||
attribute_id: 7,
|
||||
property_id: GalaxyTagMetadata::VALUE_PROPERTY_ID,
|
||||
mx_data_type: 2,
|
||||
is_array: false,
|
||||
security_classification: 0,
|
||||
attribute_source: "dynamic".into(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl Resolver for StaticResolver {
|
||||
async fn resolve(&self, tag: &str) -> Result<GalaxyTagMetadata, ResolverError> {
|
||||
if tag == self.tag_reference {
|
||||
Ok(self.metadata.clone())
|
||||
} else {
|
||||
Err(ResolverError::NotFound {
|
||||
tag_reference: tag.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ntlm_from_test_env() -> NtlmClientContext {
|
||||
let user = std::env::var("MX_TEST_USER").expect("MX_TEST_USER");
|
||||
let password = std::env::var("MX_TEST_PASSWORD").expect("MX_TEST_PASSWORD");
|
||||
@@ -111,11 +65,42 @@ mod live {
|
||||
.with_test_writer()
|
||||
.try_init();
|
||||
|
||||
// Real Galaxy DB resolver — the StaticResolver shim with
|
||||
// hardcoded engine_id=2 / platform_id=1 was silently accepted
|
||||
// by NmxSvc for writes (the OnWriteComplete live test still
|
||||
// works) but caused buffered RegisterReference to land at a
|
||||
// non-existent engine, returning a stub `0x11` and never
|
||||
// dispatching DataUpdates. F56 root cause.
|
||||
let galaxy_db = std::env::var("MX_GALAXY_DB")
|
||||
.expect("MX_GALAXY_DB (set via tools/Setup-LiveProbeEnv.ps1)");
|
||||
let resolver = Arc::new(
|
||||
SqlTagResolver::from_ado_string(&galaxy_db).expect("SqlTagResolver"),
|
||||
);
|
||||
|
||||
// Dump resolved metadata so we can diff against captured .NET wire bytes.
|
||||
{
|
||||
use mxaccess_galaxy::Resolver as _;
|
||||
let m = resolver.resolve(&tag).await.expect("resolve test tag");
|
||||
eprintln!(
|
||||
"resolved {tag}: object_tag={:?} attribute={:?} primitive={:?} platform={} engine={} object={} attribute_id={} property_id={} mx_type={} is_array={}",
|
||||
m.object_tag_name,
|
||||
m.attribute_name,
|
||||
m.primitive_name,
|
||||
m.platform_id,
|
||||
m.engine_id,
|
||||
m.object_id,
|
||||
m.attribute_id,
|
||||
m.property_id,
|
||||
m.mx_data_type,
|
||||
m.is_array,
|
||||
);
|
||||
}
|
||||
|
||||
eprintln!("connecting via Session::connect_nmx_auto");
|
||||
let session = Session::connect_nmx_auto(
|
||||
ntlm_from_test_env,
|
||||
SessionOptions::default(),
|
||||
Arc::new(StaticResolver::new(&tag)),
|
||||
resolver,
|
||||
RecoveryPolicy::default(),
|
||||
)
|
||||
.await
|
||||
|
||||
Reference in New Issue
Block a user