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
5.3 KiB
GetHistorianInfo over 2020 WCF — GETHI is named-value-only (HCAL R1.4)
Status: ⛔ Bounded out on BOTH 2020 WCF and 2023 R2 gRPC (2026-06-20; gRPC live-confirmed
2026-06-21). GetHistorianInfoAsync is not shipped on any transport: the one field that
motivates it — EventStorageMode — is not on the wire on either transport (it lives only in
the C++ HCAL's in-memory 518-byte struct, filled via a native vtable+648 call — see the §gRPC
conclusion below). The version field GETHI does return is already exposed (ProbeAsync,
GetRuntimeParameterAsync("HistorianVersion")), so there is nothing new to ship. Note: R1.3
(GetServerTimeZone) — once paired with this as "2023R2-only" — diverged: it returns a real
value over gRPC and shipped 2026-06-21 (GetServerTimeZoneAsync); R1.4 did not.
What the capture showed
scripts/Capture-HistorianInfo.ps1 drives the native HistorianAccess.GetHistorianInfo(out HistorianInfo, out error) through the instrumented (instrument-wcf-{write,read}message)
current/aahClientManaged.dll. The native call succeeds and returns
EventStorageMode = Blocks, ServerVersion = 20,0,000,000, no error.
But the wire tells a different story (scripts/decode-historian-info-capture.py):
- The only
GETHIop on the wire isaa/Stat/GETHI(handle, pRequestBuff)withpRequestBuff = 53 67 02 00(sig0x6753+ version2)+ uint charCount(16) + UTF-16 "HistorianVersion"— i.e. the named-value request, identical to the GETRP/version shape. - Its response
pResponseBuffis ~30 bytes:uint charCount(12) + UTF-16 "20,0,000,000"(+ a02 00 01 00trailer). Just the version — not a 518-byte struct. - The post-GETHI ops in the same capture are
Hist/UpdC3+ a run ofStat/GetSystemParameter(AllowOriginals,HistorianPartner,HistorianVersion,MaxCyclicStorageTimeout,RealTimeWindow,FutureTimeThreshold,AllowRenameTags). None carries a storage-mode value. So the native wrapper'sEventStorageModeis derived by the C++ HCAL outside the WCF wire, not fetched over it.
Probe: does GETHI expose storage mode under any name?
StringHandleProbeDiagnosticTests.GETHI_CandidateInfoNames_AgainstLocalHistorian (gated on
HISTORIAN_HOST=localhost) issues GETHI for HistorianVersion plus seven storage-mode name
guesses. Result on the live 2020 server:
| GETHI parameter name | result |
|---|---|
HistorianVersion |
ok=True, respLen=32 (version) |
EventStorageMode, EventStorageType, StorageType, HistorianEventStorageMode, EventStorage, StorageMode, HistorianInfo |
ok=False, errLen=5, empty |
So GETHI on 2020 WCF is a strict named-value lookup with exactly one known-good key
(HistorianVersion). There is no storage-mode key, no full-struct request.
Why the 518-byte struct doesn't apply here
The 2023 R2 decompiled ArchestrA.HistorianAccess.GetHistorianInfo (analysis folder) allocates
a 518-byte HISTORIAN_INFO struct, pre-inits int32 @514 to -1, calls native HCAL
(vtable+648) which fills it, then reads version (UTF-16 @0) + EventStorageMode (@514:
-1=Unsupported, 0=Database, else=Blocks). That is the HCAL-native / 2023R2 gRPC
front-door model (StatusService.GetHistorianInfo returns bytes btHistorianInfo). On 2020
WCF that struct is never marshaled across the wire — only the version named-value is. The
native client's EventStorageMode therefore comes from C++-internal state the managed WCF
replay cannot observe or reproduce.
Conclusion / where it lands
- 2020 WCF:
GetHistorianInfoAsyncwould add nothing over existing surface (version only) and could not report a realEventStorageMode— so it is intentionally not shipped (no hollowUnsupported-returning API; project discipline: don't ship misleading behavior). - 2023 R2 gRPC — LIVE-PROBED 2026-06-21, also bounded out. The earlier expectation that
Status.GetHistorianInforeturns the full 518-bytebtHistorianInfoover gRPC was wrong. On the real 2023 R2 server (History iface 12), the gRPCGetHistorianInfois the same named-value query as 2020 WCF: onlyHistorianVersionresolves (→"23,1,000,000"+02 00 01 00trailer);EventStorageModeand seven name variants returnsuccess=falseon bothGetHistorianInfoandGetSystemParameter. The 518-byte struct is not on the gRPC wire — the 2023 R2 decompile confirms managedHistorianAccess.GetHistorianInfofills it via a native vtable+648 HCAL call (IClientCommon*+ offset 648), not the gRPC op, soEventStorageModeis derived inside the C++ HCAL outside the wire on gRPC exactly as on WCF. Conclusion:GetHistorianInfoAsyncis not shipped on any transport (the only wire-reachable field, version, is already exposed). NoHistorianInfo/HistorianEventStorageModepublic type was added. Probe: the (now-deleted)GrpcStatusInfoProbeTests; raw dump underartifacts/reverse-engineering/grpc-status-info-probe/(gitignored).
Tooling kept as RE aids
tools/AVEVA.Historian.NativeTraceHarnesshistorian-infoscenario (drives the native call).scripts/Capture-HistorianInfo.ps1+scripts/decode-historian-info-capture.py.StringHandleProbeDiagnosticTests.GETHI_CandidateInfoNames_AgainstLocalHistorian(locks the named-value-only finding; gated).