Write path online. GalaxyDriver implements IWritable; routes by
SecurityClassification — SecuredWrite / VerifiedWrite tags go through
MxCommandKind.WriteSecured, everything else through MxGatewaySession.
WriteAsync. Per-tag classifications are captured during ITagDiscovery via a
SecurityCapturingBuilder wrapper that intercepts Variable() calls without
the discoverer needing to know about the driver's internal state.
Files:
- Runtime/MxValueEncoder.cs — boxed CLR value → MxValue. Covers seven Galaxy
scalar types (bool/int8-32/uint8-32 → Int32, int64/uint64 → Int64, float,
double, string, DateTime/DateTimeOffset → Timestamp) and 1-D array
variants. Inverse of MxValueDecoder; round-trip pinned by tests.
DateTime.Local converts to UTC; unsupported types throw ArgumentException.
- Runtime/IGalaxyDataWriter.cs — driver-side seam. Tests inject a fake to
capture routing decisions; production path uses GatewayGalaxyDataWriter.
- Runtime/GatewayGalaxyDataWriter.cs — production. Lazy-AddItem caches
itemHandles, encodes value, routes Write vs WriteSecured, translates
MxCommandReply (ProtocolStatus → BadCommunicationError; first
MxStatusProxy in statuses[] via StatusCodeMap.FromMxStatus). Per-tag
exception isolation: one bad write doesn't fail the batch.
- GalaxyDriver: now implements IWritable. Discovery wraps the supplied
IAddressSpaceBuilder in SecurityCapturingBuilder which records each
attribute's SecurityClass into _securityByFullRef before delegating.
WriteAsync resolves classification per tag (FreeAccess default for
unknown tags — matches the legacy backend), routes through the injected
writer. Throws NotSupportedException with PR 4.4 pointer when no writer
is wired (production path requires GalaxyMxSession.Connect from PR 4.4).
Tests (32 new, 94 Galaxy total):
- MxValueEncoder: every scalar type, narrowing checks (sbyte/short/byte/
ushort fit Int32; uint within Int32 range; ulong within Int64),
DateTime.Local → UTC conversion, array variants for bool/double/string/
DateTime, Dimensions populated, unsupported-type throws ArgumentException,
encoder/decoder round-trip pin.
- GalaxyDriverWriteTests: WriteAsync routes through fake writer with
values intact; theory exercises every SecurityClassification value through
the discovery-then-write path; unknown-tag defaults to FreeAccess; empty-
request short-circuit; no-writer fail-loud; post-dispose throws.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Read path scaffold + the byte→uint quality mapping table that the parity
matrix (PR 5.x) pins. PR 4.4 supplies the production GW-backed reader; this
PR ships the abstraction and the supporting infrastructure so 4.4 just
plugs the implementation in.
Files:
- Runtime/StatusCodeMap.cs — explicit OPC DA quality byte → OPC UA
StatusCode uint mapping. Extends the legacy Galaxy.Host
HistorianQualityMapper with named constants (Good / GoodLocalOverride,
Uncertain + 4 substatuses, Bad + 7 substatuses, BadInternalError) and an
MxStatusProxy → uint helper that honors success flag → detail byte →
detected_by transport-error fallback. Unknown bytes fall back to category
bucket with a once-per-session diagnostic log so field captures can
extend the table.
- Runtime/MxValueDecoder.cs — gateway MxValue → boxed CLR value for the
seven Galaxy data types (Boolean, Int32, Int64, Float32, Float64, String,
DateTime) plus their array variants. Honors MxValue.IsNull and
RawValue passthrough.
- Runtime/IGalaxyDataReader.cs — driver-side seam for one-shot reads. PR
4.4 ships the production wrapper around MxGatewaySession.SubscribeBulk +
StreamEvents + UnsubscribeBulk; this PR exposes the contract so
GalaxyDriver.ReadAsync wires through it.
- Runtime/GalaxyMxSession.cs — wrapper around MxGatewaySession that owns
the Register handle. ConnectAsync opens session + Register; AttachForTests
lets tests bypass real gw construction. PR 4.3/4.4/4.5 add write,
subscribe, and reconnect surfaces.
GalaxyDriver:
- Implements IReadable. ReadAsync routes through the injected
IGalaxyDataReader (test seam) when present; production path throws
NotSupportedException pointing at PR 4.4 — protects deployments running
this PR from silent wrong reads while signaling that the legacy-host
backend (Galaxy:Backend=legacy-host) handles reads in the meantime.
- Internal ctor extended with optional dataReader parameter (default null,
preserves PR 4.0/4.1 callers).
Tests: 42 new — exhaustive byte→uint table for StatusCodeMap (15 known
codes + category-bucket fallback for unknowns + MxStatusProxy precedence
rules + OPC UA top-byte invariants), every MxValue oneof case for the
decoder (bool/int32/int64/float/double/string/timestamp/3 array variants/
raw bytes/null), GalaxyDriver IReadable wiring (route-through, empty-
request, no-reader-throws, post-dispose-throws, status-code preservation).
62 Galaxy tests total pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>