feat(wcf): C2 spike + ConnectViaAddress/connmode — WCF transport viable, rows server-gated #1

Merged
dohertj2 merged 16 commits from feat/c2-wcf-event-spike into main 2026-06-26 06:48:03 -04:00

16 Commits

Author SHA1 Message Date
Joseph Doherty f2297315b9 docs(c2): correct the WCF finding — transport+auth viable, row-retrieval server-gated
Overturns the earlier wrong "WCF not served on 2023 R2" conclusion (that was a
test error: wrong port/transport for the reverse tunnel). Corrected: the cert
(TLS) transport + NegotiateAuthentication auth reach the 2023 R2 historian
cross-platform; the 0x501 event connection mode makes CM_EVENT RegisterTags
succeed; yet StartEventQuery returns a 0-row buffer + long-polls over a window
that has events. Registration and window ruled out -> the same server-side
per-connection row gate as gRPC. Event reads stay server-gated over BOTH
transports; not client-fixable. Evidence doc rewritten; gRPC + WCF orchestrator
gating messages corrected.

Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
2026-06-26 04:41:21 -04:00
Joseph Doherty de8d5e91ce feat(wcf): EventReadConnectionModeOverride + cross-platform/bounded C2 spike
Live investigation (direct from a VPN host to the 2023 R2 historian's real WCF
port) showed the certificate transport + NegotiateAuthentication auth work
cross-platform, and that the event-read chain needs the 0x501 event connection
mode for CM_EVENT RegisterTags to succeed (0x402/0x401 fail). Even with
registration succeeding over a window that has events, StartEventQuery returns a
0-row header and long-polls — the same server-side per-connection row gate proven
for gRPC. Adds: EventReadConnectionModeOverride (diagnostic), and spike knobs —
cross-platform cert gate, version-check bypass, per-call timeout, overall budget
with phase-diagnostic dump, connection-mode override.

Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
2026-06-26 04:38:58 -04:00
Joseph Doherty 8777c0b816 fix(wcf): set Via via CreateChannel(address, via) — ClientViaBehavior absent in .NET WCF
ClientViaBehavior is a .NET Framework type not present in the System.ServiceModel
client libraries. Use the portable ChannelFactory.CreateChannel(EndpointAddress, Uri)
overload instead, via a CreateChannel helper applied at the history-open and
retrieval-query sites (the critical event path). Fixes the build break in 954b9cc.

Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
2026-06-25 20:37:38 -04:00
Joseph Doherty 954b9cc9cc feat(wcf): add ConnectViaAddress (WCF Via) for tunneled historian access + wire into C2 spike
When the historian is reached through a port-forward whose local port differs
from the server's real service port, WCF's server-side AddressFilter rejects the
message (To = tunnel port != server port). ConnectViaAddress lets the channel
connect to the tunnel while addressing the SOAP To the real Host/Port endpoint.
Applied in HistorianWcfClientCredentialsHelper.Configure (the critical event
factories already call it). The C2 spike reads HISTORIAN_WCF_EVENT_VIA.

Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
2026-06-25 20:35:46 -04:00
Joseph Doherty 7992e43908 test(c2): make WCF spike transport-selectable (integrated|certificate) + opt-in verbose
The first live run used the wrong port (32568 direct vs the 42568 WCF tunnel) and
hardcoded RemoteTcpIntegrated; via the tunnel the error advanced from socket-RST
to ProtocolException (binding/security mismatch). Add HISTORIAN_WCF_EVENT_TRANSPORT
(certificate), _DNSID, _ALLOW_UNTRUSTED, and an opt-in _VERBOSE for live binding
diagnosis. Default output stays sanitized; still Windows-only, never fails the suite.

Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
2026-06-25 20:18:23 -04:00
Joseph Doherty 64c9793b91 docs(c2): correct event-read gating messages — WCF not served on 2023 R2
The gRPC ReadEvents throws no longer advise "use the WCF transport for event
reads": that path is moot on 2023 R2 (net.tcp is reset at the framing layer,
live-disproven 2026-06-25). Messages now state event reads are auth-solved but
server-gated over gRPC and have no WCF fallback on 2023 R2, citing the two
evidence docs. WCF orchestrator remarks scoped to legacy 2020 historians; row
layout noted as decoded. String/comment only; throw behavior unchanged.

Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
2026-06-25 17:11:19 -04:00
Joseph Doherty f1c57f7149 docs(c2): record WCF event-read spike live result (RED — transport not served on 2023 R2)
WCF net.tcp (RemoteTcpIntegrated) against the live 2023 R2 historian is reset
at the socket-write/framing layer before any auth — both the event spike and a
basic Probe/ReadRaw throw the identical CommunicationException/SocketException
("forcibly closed by the remote host"). The 2023 R2 box does not serve the
legacy WCF transport; C2's "route via WCF" unblock is moot on this server class.
Sanitized: counts + native return codes + buffer lengths only.

Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
2026-06-25 16:58:56 -04:00
Joseph Doherty 85f3bd4b4e test(c2): env-gated WCF event-read diagnostic spike (RemoteTcpIntegrated)
Drives HistorianWcfEventOrchestrator over RemoteTcpIntegrated and dumps the
native chain (UpdC3/RTag2/EnsT2 return codes, result-buffer length, row count)
to settle whether WCF event reads return rows on an event-bearing 2023 R2 box.
Windows-only, gated by HISTORIAN_WCF_EVENT_HOST, never fails the suite, inert
off Windows. Sanitized: counts + return codes + buffer lengths + sha256 only.

Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
2026-06-25 16:40:21 -04:00
Joseph Doherty 5a7a28872b Merge feat/c1-numeric-writes: Int8/UInt8 live writes + C3a evidence (UInt1 server-blocked)
Int8/UInt8 analog tag-create + historical-value write encoders un-gated and
live-proven; golden-pinned. UInt1 attempted but re-gated — the historian stores a
degenerate UInt1 analog tag. 2023 R2 gRPC interface-version integers captured (C3a).

Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
2026-06-25 16:07:24 -04:00
Joseph Doherty 100b44a365 docs(write): drop UInt1 from value-layout table; mark Int8/UInt8 live-proven
Removes the stale UInt1 → UInt8(1) entry from the EncodeNativeValue layout
table (UInt1 is re-gated; the prose already said so). Int8/UInt8 layout note
updated from "pending" to live-proven.

Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
2026-06-25 15:42:50 -04:00
Joseph Doherty aa8ca2f6ad docs: Int8/UInt8 analog writes supported (live-proven); UInt1 server-degenerate
EnsureTagAsync + AddHistoricalValues now cover Int8/UInt8 (codes 0x19/0x39, native
LE int64/uint64 value). UInt1 documented as not-supported: the server stores a
degenerate analog tag (GetTagInfo stub, type byte 0x00).

Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
2026-06-25 15:34:34 -04:00
Joseph Doherty 95b924cdbe fix(write): re-gate UInt1 — historian creates degenerate UInt1 analog tags
Live evidence: EnsureTags(UInt1) returns success but the server stores a
degenerate tag (descriptor type byte 0x00, no GUID/name), so GetTagInfo
truncates and writes fail. Not client-fixable on the analog path. Int8/UInt8
stay GREEN (live-proven). UInt1 reverts to ProtocolEvidenceMissingException
(fail-closed); golden rows removed; negative-gate tests added. Removed the
one-off capture diagnostic.

Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
2026-06-25 15:27:04 -04:00
Joseph Doherty aa56d2d81b docs(re): correct Status interface-version comments (4 on gRPC, not 0)
Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
2026-06-25 15:03:22 -04:00
Joseph Doherty dea9107b4b docs(re): capture + record 2023 R2 gRPC interface-version integers (C3a)
Env-gated live evidence test reads History/Retrieval/Transaction/Status
GetInterfaceVersion over gRPC; integers recorded in grpc-interface-versions.md.
Stale not-yet-captured comment fixed; gate XML-doc notes live confirmation.

Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
2026-06-25 14:58:45 -04:00
Joseph Doherty 79bb1d9e06 test(write): golden-pin UInt1/Int8/UInt8 tag-create + value buffers
New InlineData rows derived from the captured Double baselines (type-code /
value bytes swapped). Negative-gate tests retained for still-gated types.

Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
2026-06-25 14:49:21 -04:00
Joseph Doherty 43c2587498 feat(write): un-gate UInt1/Int8/UInt8 tag-create + historical-value encoders
GetAnalogDataTypeCode gains UInt1=0x08/Int8=0x19/UInt8=0x39 (read-side
MapDataType already maps these); EncodeNativeValue gains 1-byte UInt1 +
8-byte LE Int8/UInt8 (mirrors the captured Double value layout). Value API
stays double; 2^53 exact-magnitude ceiling documented. Golden tests + live
round-trip follow.

Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
2026-06-25 14:38:27 -04:00