LastValueCache bridges MQTT's subscribe-first push model to the OPC UA
server's polled IReadable.ReadAsync: the subscription path calls Update()
per RawPath, the read path calls Read(). Read never throws; an unseen
RawPath returns BadWaitingForInitialData (0x80320000) rather than an
exception or null, so a batch covering many references degrades per-ref
instead of failing wholesale.
Deviates from the plan's GoodNoData snippet: GoodNoData is reserved
repo-wide for "the historian window held no samples" (NullHistorianDataSource,
OtOpcUaNodeManager HistoryRead paths); BadWaitingForInitialData is the
established convention for "no live value observed yet" (CalculationDriver,
VirtualTagEngine, FOCAS, AddressSpaceApplier). Keyed by RawPath per the v3
driver-reference identity, not a topic/JSON-path-derived key.
Claude-Session: https://claude.ai/code/session_01GASWkNEi68FSCtvr6rLoEW
Review follow-ups on MqttConnection (Task 3):
- Every certificate test asserted rejection, so the accept branch was
unreachable-by-regression. Adds a leaf genuinely issued by the pinned CA and
asserts acceptance.
- ValidateAgainstPinnedCa never seeded ChainPolicy.ExtraStore from the incoming
chain, so a leaf behind an intermediate delivered during the handshake failed
despite a legitimate path to the pinned root. Seeds from both the incoming
chain's elements and its ExtraStore; CustomRootTrust still means only the
pinned roots may terminate the chain.
- A DisposeAsync racing an in-flight connect escaped as an unclassified
exception; it now folds into ObjectDisposedException.
- Promotes the single-caller concurrency invariant into the type remarks, with
the accurate blast radius (a leaked live connection, not a benign throw).
Serialising the lifecycle remains Task 4's job.
- X509Chain.Build can throw; an exception escaping a TLS validation callback is
an opaque handshake crash, so it is caught and refused.
- Adds a connect-retry test (Task 4's reconnect loop reuses the instance) and a
disposed-then-connect test.
Claude-Session: https://claude.ai/code/session_01GASWkNEi68FSCtvr6rLoEW
Task 2 review follow-up. The plan specified MqttEquipmentTagParser.TryParse(reference)
with def.Name = the TagConfig blob, and told us to mirror a type named
ModbusEquipmentTagParser. Both are plan defects:
- EquipmentTagRefResolver documents that a v3 driver reference "is now always a
RawPath" and that the blob-parse fallback is retired. Keying Name by the blob
would make OnDataChange publish under a reference that never matches the
RawPath-keyed fan-out in DriverHostActor - silently dead in production with
every unit test still green.
- ModbusEquipmentTagParser does not exist. The six sibling drivers all use
<Driver>TagDefinitionFactory.FromTagConfig(tagConfig, rawPath, out def).
Changes:
- Rename MqttEquipmentTagParser -> MqttTagDefinitionFactory; TryParse(reference,
out def) -> FromTagConfig(tagConfig, rawPath, out def) setting Name: rawPath,
matching ModbusTagDefinitionFactory's structure, param docs and guard order.
- Pin the identity contract with a dedicated test so a regression to blob-keying
goes red.
- Read qos with the same strictness as the enums: a present-but-invalid qos
("high" / 1.5 / 5 / null) now rejects the tag and is warned by Inspect,
instead of being silently absorbed into the driver-level default and handing
the operator a weaker delivery guarantee than the one they authored.
- No ToTagConfig inverse: the siblings carry one solely for their Driver.<X>.Cli
project, the MQTT plan defines none, and the AdminUI editor template
references no driver factory. Recorded as an explicit YAGNI call in the type
doc rather than added speculatively.
Claude-Session: https://claude.ai/code/session_01GASWkNEi68FSCtvr6rLoEW
Code review of f22db5d8 (approved-with-findings): the existing round-trip
tests only exercised explicit JSON payloads, so nothing failed if UseTls /
AllowUntrustedServerCertificate or the §5.1 numeric defaults were flipped.
Adds a defaults-pinning test plus a partial-JSON test that omits the TLS
knobs entirely (the production case — an operator config that just doesn't
mention TLS must not silently land insecure).
Also overrides the record's PrintMembers so ToString() no longer prints
Password in plaintext (verified RED before the fix: the new test failed
with the raw password in the rendered string). OpcUaClientDriverOptions has
the same unredacted-ToString() shape but is out of scope for this task per
the coordinator's note; flagging as a possible follow-up.
Claude-Session: https://claude.ai/code/session_01GASWkNEi68FSCtvr6rLoEW