M3 R3.1: document the captured + validated AddStreamValues "ON" write path

revision-write-path.md §"R3.1 CAPTURED" + roadmap R3.1/R3.2/one-glance now record the validated
finding: the historical write is HistoryService.AddStreamValues ("ON" storage-sample buffer, AddS2
"OS" family) + EnsureTags, not AddNonStreamValues/TransactionService. Includes the decoded 56-byte
"ON" buffer layout, the working priming/batch sequence, the tag-GUID keying, and that the D2 cache
gate does not block the primed 2023 R2 client. Remaining work to ship AddHistoricalValuesAsync is
the managed "ON" serializer (adapt HistorianEventWriteProtocol) + gRPC orchestrator wiring.

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:
Joseph Doherty
2026-06-21 21:04:17 -04:00
parent 9bcfffb365
commit 0e78d638d0
2 changed files with 40 additions and 3 deletions
+37
View File
@@ -213,6 +213,43 @@ IL-rewrite `Archestra.Historian.GrpcClient.dll`; (3) write-enabled run → captu
`tagInfos` (+ `btInput` if the gate passes); (4) build golden serializer(s) in `src/`; (5) real
`bCommit=true` write + SQL read-back on a sandbox tag → ship `AddHistoricalValuesAsync`.
### R3.1 CAPTURED + VALIDATED (2026-06-21): the write rides `HistoryService.AddStreamValues` ("ON" buffer)
The capture ran end-to-end against the live server (`AVEVA.Historian.Grpc2023CaptureHarness`,
`capture-write` scenario, sandbox tag created by the harness, IL-rewritten `GrpcClient` dumping every
`byte[]`). The committed write **persisted and read back over gRPC** (SDK `ReadRawAsync` returned the
sample) — fully validated.
**The roadmap's assumption was wrong.** The native non-streamed (historical backfill) write does **not**
use `AddNonStreamValues` / the TransactionService at all. The native `HistorianAccess.AddNonStreamedValue
→ SendValues` routes over gRPC as **`HistoryService.AddStreamValues`** carrying an **"ON"
storage-sample buffer** (structurally the AddS2 **"OS"** family — same serializer pattern the SDK already
has in `HistorianEventWriteProtocol`), preceded by **`EnsureTags`** to register the tag:
```
EnsureTags.tagInfos (144B) = the analog CTagMetadata the SDK's EnsureTagAsync already builds
(0x4E marker … fe 00 trailer)
AddStreamValues.values (56B) = "ON" (0x4E4F) + u16 sampleCount(1) + u32 totalLen(56)
+ u16 payloadLen(46) + 16B tag GUID + FILETIME(sample)
+ u16 OpcQuality(192=Good) + u32 type/descriptor
+ FILETIME(received/version) + 8B double value
```
The full priming/write sequence that works from the native client (write-enabled session): `OpenConnection`
`UpdateClientStatus` ×N → `EnsureTags``GetTagInfosFromName` (resolve identity) → `AddStreamValues`
("ON" buffer). Notes: (a) the **D2 cache gate (err 129) does NOT block** the primed 2023 R2 client —
`AddNonStreamedValue` returned success once the session was primed (via `AddTag`/`GetTagInfoByName`) and
the server had assigned the tag key; (b) the value is keyed by a **16-byte tag GUID**, not the uint
`tagKey` (so the SDK serializer needs the tag's GUID, available from EnsureTags/GetTagInfo, not just
`HistorianTagMetadata.Key`); (c) batch lifecycle is `NonStreamedValuesBegin → AddNonStreamedValue →
SendValues → AddNonStreamedValuesEnd` (End-before-Send returns err 160 InvalidBatchId).
**Remaining to ship `AddHistoricalValuesAsync`:** build the managed "ON" `AddStreamValues` serializer in
`src/` (adapt `HistorianEventWriteProtocol`'s "OS" builder), resolve the tag GUID, reuse the existing
`EnsureTagAsync` CTagMetadata, wire `HistoryService.AddStreamValues` over the gRPC orchestrator, golden-test
the buffer, then a real write + read-back on a sandbox tag. Capture artifacts (gitignored):
`artifacts/reverse-engineering/grpc-nonstream-capture/captureB4.ndjson`.
---
## Legacy WCF analysis (preserved — still accurate for the 2020 WCF transport)