- HistorianTagExtendedPropertyProtocol.ParseResponse: fix the multi-property/
multi-group response shape captured live from the 2023 R2 server. The server
returns one group per property (the tag name repeats), each propertyCount=1, and
a uint16 searchability-flags trailer per property (0x0003 built-in, 0x0001 user-
added) — NOT the single-byte group trailer the old model assumed, which drifted
one byte per group and threw "expected 0x09 found 0x01" on any buffer with more
than one property. Now reads the per-property uint16 trailer (tolerates a legacy
1-byte tail). Fixes read-back on both WCF and gRPC. Adds GetTagExtendedPropertiesRaw
for future captures.
- HistorianGrpcStatusClient.GetConnectionStatusAsync (plan #5): synthesize connection
status from a measured gRPC handshake (OpenConnection yielding a storage-session
GUID => connected), mirroring the WCF synthesize-from-probe approach. Routed in
Historian2020ProtocolDialect on UseGrpc (the WCF path used the MDAS binding, which
can't reach the gRPC port).
- HistorianGrpcSqlClient: record the negative plan-#4 result — a HistoryService.
RegisterTags prime does NOT clear the server-side CSrvDbConnection fault (tried live
on both 0x402/0x401); the op stays bounded behind ProtocolEvidenceMissingException.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B6mcaT2PjRFKcogzp9UkfC
Wire the config operations that previously only worked over WCF onto RemoteGrpc,
reusing the proven 2020 byte serializers verbatim inside the protobuf bytes fields
(keyed by the Open2 session handle). Live-verified against a real 2023 R2 server
where noted.
Read ops (live-verified):
- GetRuntimeParameterAsync -> StatusService.GetRuntimeParameter (GETRP serializer)
- GetTagExtendedPropertiesAsync -> RetrievalService.GetTagExtendedPropertiesFromName
(GetTepByNm serializer + sequence paging; page-0 FillBufferFromVector is the
benign no-data terminator, matched to the WCF break-and-return-empty semantics)
Server-walled (bounded with captured evidence):
- ExecuteSqlCommandAsync -> RetrievalService.ExecuteSqlCommand. The request rides
the RPC but the server-side CSrvDbConnection.ExecuteSqlCommand faults
(IndexOutOfRange / native err 38) on a DB-connection precondition the pure
managed gRPC session doesn't establish (same class as OpenStorageConnection).
Surfaced as ProtocolEvidenceMissingException.
Write ops (tooled + routed, sandbox-gated — not run destructively live):
- EnsureTagAsync / DeleteTagAsync / RenameTagsAsync / AddTagExtendedPropertiesAsync
via HistoryService.EnsureTags / DeleteTags / StartJob / AddTagExtendedProperties
on a write-enabled (0x401) session, reusing the WCF golden serializers. The WCF
priming discovery-dance is omitted (the M3 gRPC write probe worked without it);
add it first if a live sandbox run is rejected.
Routed in Historian2020ProtocolDialect / HistorianClient on the RemoteGrpc branch.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B6mcaT2PjRFKcogzp9UkfC
Route GetStoreForwardStatusAsync to a gRPC path that actually contacts the
server (StatusService.GetHistorianConsoleStatus) instead of synthesizing an
all-false result blind. On a reachable/normal server it returns the
not-storing baseline but MEASURED; when the server is unreachable or the
console-status call fails it reports ErrorOccurred with the underlying error
(the old synthesis never contacted the server). The active-SF buffer
magnitude (Storing/Pending/DataStored) stays false because it lives behind
the D2 storage-engine console wall.
Non-gRPC transports keep the synthesized fallback. Live-verified against the
2023 R2 server; gated integration test
GetStoreForwardStatusAsync_OverGrpc_ReturnsMeasuredIdleState added. README
operation table updated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B6mcaT2PjRFKcogzp9UkfC
Live-probed both R1.3 and R1.4 against a real 2023 R2 server over the gRPC
StatusService; implemented the one that carries an evidence-backed value.
R1.3 GetServerTimeZoneAsync — SHIPPED:
- StatusService.GetSystemTimeZoneName(uiHandle) returns the real server zone
over RemoteGrpc (the 2020 WCF op is a client-side stub returning empty).
- HistorianGrpcStatusClient.GetSystemTimeZoneNameAsync -> dialect routing ->
public HistorianClient.GetServerTimeZoneAsync. Non-gRPC transports fail
closed with ProtocolEvidenceMissingException (no empty-string lie).
- Golden message-shape unit test + non-gRPC guardrail unit test + gated live
test. 271 unit tests pass.
R1.4 GetHistorianInfoAsync (EventStorageMode) — bounded out on gRPC too:
- gRPC GetHistorianInfo is the same named-value query as 2020 WCF (only
HistorianVersion resolves); EventStorageMode + 7 variants fail on both
GetHistorianInfo and GetSystemParameter. The 518-byte struct is filled by a
native vtable+648 HCAL call, not the gRPC op (per the 2023 R2 decompile), so
the field is never on the wire. Not shipped on any transport. Closes the
roadmap's open "build against a live 2023 R2 server" caveat.
Also correct the stale M3 roadmap section: D2 already proved
Transaction.AddNonStreamValues* rides the storage-engine pipe (STransactPipeClient2
-> aaStorageEngine), not WCF — same wall as R4.2 — so M3-over-WCF is blocked, not
"the path that is NOT the gated cache push".
Docs: hcal-roadmap.md, wcf-historian-info.md, wcf-status-localhost.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B6mcaT2PjRFKcogzp9UkfC
Roadmap docs/plans/hcal-roadmap.md, milestone M0 (gRPC parity for the DONE
surface). Now unblocked for live verification by a reachable 2023 R2 server.
- R0.4 Probe over gRPC: new HistorianGrpcProbe calls History/Retrieval/Status
GetInterfaceVersion (unauthenticated). ProbeAsync routes over gRPC when
Transport==RemoteGrpc. LIVE-VERIFIED against a real 2023 R2 server — needs no
credentials (runs before the auth loop), so it works despite the auth blocker.
- R0.3 System parameter over gRPC: new HistorianGrpcStatusClient calls
StatusService.GetSystemParameter over the authenticated session; routed in the
dialect. Built + unit-tested (request/response field mapping pinned).
Live-verification pending an auth fix (see below).
- Extracted the proven auth handshake from HistorianGrpcReadOrchestrator into
shared Grpc/HistorianGrpcHandshake (reused by read + status + future
browse/metadata). Repointed the IL structural guardrail test to it.
- Diagnostics: round-failure now decodes the native server error + hex/ASCII
preview (HistorianNativeHandshake.DescribeError). This surfaced the live auth
blocker as SEC_E_LOGON_DENIED (0x8009030C) at NTLM round 1 — framing is correct,
the credential did not validate. Probable cause: stale file password or NAM-domain
NTLM restriction (Kerberos/RDP works, NTLM denied; no SPN path over the tunnel).
216 unit tests pass; live gRPC probe passes. Sanitization scan clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B6mcaT2PjRFKcogzp9UkfC