Record 2023 R2 binary-dive verdicts; fix revision-probe scope comment
Mined the full decompiled stock 2023 R2 managed client as the oracle for every still-pending gRPC item. Governing fact: ArchestrA.HistorianAccess is a C++/CLI shim into native HistorianClient; the managed Grpc*Client wrappers have zero call sites, so buffer-building/dispatch for the pending items is native (absent from the binaries). Sharpened verdicts into handoff.md: - Items 4/5/6 + OpenStorageConnection: hard-confirmed walled, with real reasons (SQL gated out client-side via IsManagedHistorian; no Revision RPC in the gRPC contract; LoadBlocks response is a native blob behind the storage console handle). - Items 3 (SendEvent) and 7 (DeleteTEP): moved from vague to precise, LOCAL-box capturable targets (PackToVtq btValues / DeleteTagExtendedPropertiesByName BtInput with deleteFromServer=true). Also correct the HistorianGrpcRevisionProbe doc comment: it probes the non-streamed ORIGINAL-insert path (AddNonStreamValues), a distinct capability from revision EDITS (native AddRevisionValues trio, no gRPC RPC) — the prior comment conflated them. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B6mcaT2PjRFKcogzp9UkfC
This commit is contained in:
@@ -101,6 +101,74 @@ Live-server gRPC probe recipe: set
|
||||
quotes — `reference_wonder_sql_vd03_credentials`) and run the gated
|
||||
`HistorianGrpcIntegrationTests`.
|
||||
|
||||
### 2023 R2 stock-client binary dive (2026-06-23) — sharpened verdicts
|
||||
|
||||
Re-read the full decompiled stock 2023 R2 managed client
|
||||
(`histsdk-2023r2-analysis/decompiled/`: `Archestra.Historian.GrpcClient`,
|
||||
`ArchestrA.HistorianAccess`, `Archestra.Grpc.Contract`, `HistorianEvent`,
|
||||
`HistorianAccessUtil`) as the oracle for every still-pending item. **Governing
|
||||
fact:** `ArchestrA.HistorianAccess.dll` is a C++/CLI mixed assembly — every
|
||||
data/config/write method is a thin shim into native `<Module>.HistorianClient.*`,
|
||||
and the managed `Grpc*Client` wrappers are instantiated by **nothing** in the
|
||||
decompiled set (`new Grpc*Client(` → zero call sites). So the buffer-building and
|
||||
RPC-dispatch sequencing for these items lives in native C++ not present in the
|
||||
binaries. That confirms the "gated" calls were not from missing managed steps —
|
||||
with these refinements:
|
||||
|
||||
- **Item 1 (gRPC event rows)** — **confirmed native/server-side.** Stock event
|
||||
call graph is provably identical to ours (transport, per-service channels,
|
||||
gzip-only metadata, CM_EVENT registration, v8 ECDH Event-open, `StartEventQuery`
|
||||
request bytes). `EventQuery.StartQuery`/`MoveNext` dispatch straight into native
|
||||
`HistorianClient.StartEventQuery`/`GetNextRow`; the query orchestration that
|
||||
would differ is native and not on the wire. One untested low-effort check
|
||||
remains: byte-diff a captured **Event-connection** EnsureTags/RegisterTags
|
||||
against our replay (the 83-vs-86-byte EnsT gap was never actually compared).
|
||||
- **Item 3 (SendEvent over gRPC)** — **sharpened from "maybe no RPC" to a precise
|
||||
capture.** RPC **confirmed** = `HistoryService.AddStreamValues` (the "no distinct
|
||||
RPC" note is TRUE; an event rides the same RPC as a streamed sample, discriminated
|
||||
inside `btValues`). Public API `HistorianAccess.AddStreamedValue(HistorianEvent)`
|
||||
→ native `AddHistorianValue`; prereqs known (write-enabled Event conn, CM_EVENT
|
||||
tag handle, quality 192); field set/order recovered from `HistorianEvent.PackToVtq`.
|
||||
**Only the `btValues` VTQ byte layout is missing** — built by native
|
||||
`CCommonArchestraEventValue::PackToVtq` and copied out as an opaque `CDataChunk`.
|
||||
Our read parser already decodes the inverse property-bag format. **Capturable
|
||||
against the local Historian** (instrument `PackToVtq` output / the `AddStreamValues`
|
||||
body) → then build `HistorianEventWriteProtocol` and reuse the
|
||||
`HistorianGrpcHistoricalWriteOrchestrator` plumbing.
|
||||
- **Item 4 (ExecuteSql over gRPC)** — **confirmed walled + explained.** The stock
|
||||
client gates SQL **out client-side**: `HistorianAccess.ExecuteSqlCommand` returns
|
||||
`OperationNotSupported` when `IsManagedHistorian(node)` or `!IsProcessConnectionRequested()`
|
||||
(decompile ~:6198/:6214) and never sends the RPC. SQL-over-gRPC is unsupported by
|
||||
design on a managed/gRPC historian; our `ProtocolEvidenceMissingException` is correct.
|
||||
- **Item 5 (R4.2 revision edits)** — **confirmed HARD.** There is **no Revision RPC
|
||||
in the gRPC contract** (zero "Revision" message types); the stock client reaches a
|
||||
revision edit only via the native `HistorianClient.AddRevisionValuesBegin/AddRevisionValue/
|
||||
AddRevisionValuesEnd` transaction trio over the storage-engine channel. NOTE: this is
|
||||
a **distinct capability** from `AddNonStreamValues` (non-streamed original insert) —
|
||||
`HistorianGrpcRevisionProbe` probes the latter; its doc comment was corrected to say so.
|
||||
- **Item 6 (ReadBlocks/LoadBlocks)** — `LoadBlocks` request is a trivial
|
||||
handle+sequence cursor but the `historyBlocks` response is a native blob with no
|
||||
managed decoder, and it needs the D2-blocked `OpenStorageConnection` console handle.
|
||||
Walled.
|
||||
- **Item 7 (DeleteTagExtendedProperties)** — **reframed to a capturable lead.** RPC +
|
||||
string handle are **correct** in our SDK; ADD and DELETE are structurally identical
|
||||
and **neither** routes through `StartJob`. The differentiator is the `deleteFromServer`
|
||||
flag carried inside the native-built `BtInput` plus the native HCAL **cache-sync
|
||||
background worker** that actually propagates the delete server-side (config writes hit
|
||||
the in-process cache first, then sync). **Capturable**: capture native
|
||||
`DeleteTagExtendedPropertiesByName(deleteFromServer=true)`'s `BtInput` to learn whether
|
||||
one well-formed RPC durably deletes (→ shippable) or whether it genuinely depends on
|
||||
the cache-sync worker (→ walled).
|
||||
- **SF/snapshot/shard/ForwardSnapshot ops** — only `Get/SetSFParameter` are managed-built
|
||||
(typed strings); all others carry opaque native buffers and need the storage console
|
||||
handle. Walled / tooling-internal.
|
||||
|
||||
**Net:** 3 items hard-confirmed walled with real explanations (4, 5, 6 + OpenStorageConnection),
|
||||
and **2 moved to a precise, local-box-capturable target**: **SendEvent** (`PackToVtq` output)
|
||||
and **DeleteTEP** (`BtInput` with `deleteFromServer=true`). Both need native instrumentation of
|
||||
`aahClientManaged.dll` (Frida / IL-rewrite — repo tooling exists under
|
||||
`tools/AVEVA.Historian.NativeTraceHarness` + `scripts/frida/`), not a special server.
|
||||
|
||||
## Project Direction
|
||||
|
||||
The project goal is still a fully managed .NET 10 C# AVEVA Historian client.
|
||||
|
||||
@@ -20,6 +20,18 @@ namespace AVEVA.Historian.Client.Grpc;
|
||||
/// write-enabled session and calls <c>AddNonStreamValuesBegin</c>, surfacing whatever the server
|
||||
/// returns. It writes NO data — if Begin succeeds it immediately calls <c>AddNonStreamValuesEnd</c>
|
||||
/// with <c>bCommit=false</c> to discard the transaction.
|
||||
///
|
||||
/// <para><b>Scope note (corrected 2026-06-23 after a 2023 R2 binary re-read).</b> Despite the type
|
||||
/// name, this probes the <i>non-streamed ORIGINAL / backfill insert</i> capability
|
||||
/// (<c>AddNonStreamValues</c>), which is a <b>distinct capability from a revision EDIT</b>
|
||||
/// (overwriting an existing historized value with a new revision). The stock high-level client
|
||||
/// reaches a revision edit via a separate native transaction trio
|
||||
/// <c>HistorianClient.AddRevisionValuesBegin/AddRevisionValue/AddRevisionValuesEnd</c>
|
||||
/// (<c>ArchestrA.HistorianAccess.AddRevisionValues</c>, REVISION_MODE ∈ InsertLatest/UpdateSingle/
|
||||
/// UpdateMultiple). That trio has <b>NO corresponding RPC in the gRPC contract</b> (no "Revision"
|
||||
/// message type exists in <c>Archestra.Grpc.Contract</c>) — it rides the native storage-engine
|
||||
/// transaction channel only. So R4.2 revision edits remain unreachable over gRPC regardless of this
|
||||
/// probe's outcome; this probe neither covers nor unblocks them.</para>
|
||||
/// </summary>
|
||||
internal sealed class HistorianGrpcRevisionProbe
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user