DelTep (extended-property delete) — wire format captured + serializer golden-proven, but live delete is server-blocked and NOT exposed publicly: - Captured the DelTep inBuff via a cross-session trick (harness add-tep gains --tep-skip-add + read-for-sync before --tep-delete; Capture-DeleteTagExtended Properties.ps1 / decode-del-tep-capture.py). Layout = same group framing as AddTEx but property-name-only (no 0x43 value) + 0x00 group trailer. - SerializeDeleteRequest + 4 golden tests pin the server-accepted buffer. - A decisive experiment shows SDK-added properties ARE deletable (the native client read-syncs and deletes one), so SDK-add is complete; the SDK's own DelTep is rejected by CHistStorage::DeleteTagExtendedProperties even with byte-identical inBuff, matching mode/handle, GetTgByNm+GetTepByNm prime, open channel, and 60s retries. Root cause: the native multiplexes services over one connection (per-connection working set); the SDK's per-service WCF channels don't reproduce it. Kept as documented-but-blocked internal orchestrator path; no public HistorianClient delete API. Bounded out with evidence (no code; docs + roadmap + probe): - R1.12 localized-property write — no op on 2020 (mirror of R1.6); no *LocalizedPropert*/TagLocalized* symbol in any current/*.dll. - R1.13 non-analog tag create — GATED; native AddTag rejects every non-analog type client-side (ValidationFailed, before any WCF op): SingleByteString, DoubleByteString, Int1 all fail, Float works. No Discrete type in the native enum, no TagType setter. No wire request to capture. - R1.3 timezone + R1.4 EventStorageMode — re-confirmed 2023R2/gRPC-only from the Runtime DB schema (no timezone param, no EventStorageMode anywhere) and a parameter-op probe (GetSystemParameter + GETRP return null/throw for every candidate; only HistorianVersion works). 238 unit tests pass; full solution builds with 0 warnings. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B6mcaT2PjRFKcogzp9UkfC
6.0 KiB
WCF Status Evidence
Commands:
dotnet run --no-build --project tools\AVEVA.Historian.ReverseEngineering -- wcf-status localhost 32568
dotnet run --no-build --project tools\AVEVA.Historian.ReverseEngineering -- wcf-status localhost 32568 Version
Confirmed:
- The local status endpoint is
net.tcp://localhost:32568/Stat. IStatusServiceContract2is a static WCF contract namedStatin namespaceaa. The managed definitions now includeGetSystemParameter,GETHI,PNGS, andPNGP.GetInterfaceVersionreturns code0, version0on the local 2020 install.- The decompiled
CStatusConnectionWCF.GetServerTimeimplementation is a WCF-path stub that returns success without calling theStatservice. The managed direct call likewise returns code0with size0and no buffer.
Observed sanitized localhost results:
GetSystemTimeZoneName(handle: 0)returns code4and no value.IsDBCaseSensitive(handle: 0)returns code4.GetSystemParameter(handle: 0, "Version")returnsfalsewith no error buffer.
Re-tested 2026-06-20 with a real authenticated client handle (full Open2 auth
chain), not handle: 0:
GetSystemParameter(handle, "HistorianVersion")→ real version string (works; shipped asGetSystemParameterAsync).GetSystemTimeZoneName(handle)→ return code0x00000000(success) but an empty value string. Same channel/handle that makesGetSystemParameterreturn real data, so this is the op's own behavior, not an auth/marshalling gap.GetSystemTimeZoneNameis a member of theGetServerTimestub family: the 2020 WCF path returns success without producing a value (the native client computes the zone locally). It only becomes a real round-trip on the 2023 R2 gRPC front door (Status.GetSystemTimeZoneName), which is absent on this box.
Interpretation:
Statendpoint routing is confirmed, but status operations that require a real client handle are not usable until managed session open is solved.GetServerTimeshould not be promoted into the public SDK as a real server time call from this WCF path; native evidence shows it is a no-op stub here.GetServerTimeZoneAsync(roadmap R1.3) is NOT a trivial WCF op on 2020 — it is a stub returning empty. Do not ship it over the 2020 WCF transport. Deliver it only against a live 2023 R2 gRPC server. Reclassified indocs/plans/hcal-roadmap.md.
GETRP / GetRuntimeParameter (roadmap R1.2) — DONE, live-verified 2026-06-20
Captured the native HistorianAccess.GetRuntimeParameter(List<string>, out List<object>)
WCF traffic with scripts/Capture-RuntimeParam.ps1 (instrument-wcf-{write,read}message).
Findings:
- The WCF op is
aa/Stat/GETRP—bool GETRP(string handle, byte[] pRequestBuff, out byte[] pResponseBuff, out byte[] errorBuffer), i.e. the same string-handle + request/response-buffer shape as GETHI, not the simpleGetSystemParameter(uint, string)shape the roadmap originally assumed. - The
string handleis the Open2 storage-session GUID (the valueParseOpenConnectionResponsereads fromoutBuff[5..21]), sent UPPERCASE, dash-separated, no braces (ToString("D").ToUpperInvariant()). - Unlike GETHI (which the earlier probe found blocked), GETRP succeeds from the pure-managed
client with that handle:
GetRuntimeParameter("HistorianVersion")→20,0,000,000. pRequestBuff=54 67 01 00(sig+version) + uint nameCount + per name(uint charCount + UTF-16LE).pResponseBuff= version(1) + uint resultCount + CRetVariant(0x43VT_BSTR + uint16 payloadLen + uint16 charCount + UTF-16LE).
Shipped as HistorianClient.GetRuntimeParameterAsync(name). See
HistorianRuntimeParameterProtocol, golden WcfRuntimeParameterProtocolTests, and the
handle-format lead in wcf-string-handle-wall.md §Update (retry GETHI/ExeC uppercased).
R1.3 timezone + R1.4 EventStorageMode — re-confirmed bounded out (2026-06-21)
Both were already classified 2023R2/gRPC-only; re-verified from two fresh angles that corroborate it more strongly than the original op-level probes:
- Runtime DB schema (
Runtime.dbo, the server's own source of truth): theSystemParametertable has no timezone parameter and noEventStorageMode(onlyEventStorageDuration/EventStorageLogPath). The server timezone exists only as per-block storage artifacts (HistoryBlock.TimeZoneOffset= e.g. 240 min,wwTimeZone= e.g. "Eastern Daylight Time") and aTimeZonereference/lookup table;StorageShard.TimeZoneIdis NULL. So the timezone is a DST-specific, SQL-only, OS-derived value, not a clean server-config field exposed by any op. - Parameter-op probe (
StringHandleProbeDiagnosticTests.TimezoneAndStorageMode_ParameterProbe):GetSystemParameterandGetRuntimeParameter(GETRP) were asked for every timezone candidate (TimeZone/ServerTimeZone/SystemTimeZone/TimeZoneName/SystemTimeZoneName/TimeStampRule/ServerTime) and every storage-mode candidate (EventStorageMode/StorageMode/EventStorage/EventStorageDuration). All returned null (GetSystemParameter) or threwProtocolEvidenceMissingException(GETRP — non-string/empty response); only theHistorianVersioncontrol returned a value (20,0,000,000). Note:TimeStampRule/EventStorageDurationdo exist in theSystemParametertable yetGetSystemParameterAsyncreturns null for them — the shipped op only surfaces a whitelisted subset (a possible future widening, unrelated to R1.3/R1.4).
Conclusion: R1.3 GetServerTimeZoneAsync and R1.4 GetHistorianInfoAsync (EventStorageMode) are not
deliverable as server ops on 2020. The only 2020 route to the timezone is a SQL read of
HistoryBlock/TimeZone via ExecuteSqlCommand (R1.1) — a DST-specific value over a different
mechanism than the roadmap's Status.GetSystemTimeZoneName. EventStorageMode has no 2020
representation at all (it is a 2023 R2 event-storage-architecture field). Deliver both only against a
live 2023 R2 gRPC server.