M3 R3.1: OpenStorageConnection is a dead end (error 85); precondition is front-door RegisterTags
Live-probed StorageService.OpenStorageConnection against the 2023 R2 server over a
write-enabled (0x401) session. Every attempt — sweeping ConnectionMode (0x401/0x402/0x1),
StorageSessionId-in (Open2-GUID / empty), and FreeDiskSpace — returns the IDENTICAL native
error type=4 code=85 ("session not registered"), so it's a structural refusal, not a bad
field value.
Decode (two corroborating facts):
- Error 85 is the same code the event read returns before RegisterTags2 (see
HistorianWcfEventOrchestrator) — a generic "session not registered for this op".
- The 2023 R2 decompile shows OpenStorageConnection lives on a SEPARATE GrpcStorageClient
(the storage engine's SF/snapshot channel, own port + service identity); HistorianAccess
drives non-streamed writes through the native C++ HistorianClient, never this op.
So the roadmap's mapped "missing console session" step was wrong. The real non-streamed-write
precondition is the front-door HistoryService.RegisterTags (RTag2-family) for the target tag —
which is exactly why the R3.1 batch failed at AddNonStreamValues (no tag registered ->
StoreNonStreamValues had no route). Matches the original 2020-WCF D2 hypothesis.
Remaining (both need a native gRPC capture; do not guess bytes): the regular-tag RegisterTags
btTagInfos (only CM_EVENT's tag-GUID form is known) and the AddNonStreamValues btInput.
- HistorianGrpcStorageConnectionProbe + grpc-open-storage-connection CLI (opens nothing
persistent; CloseStorageConnection on success)
- corrected revision-write-path.md §R3.1 follow-up + hcal-roadmap R3.1/R3.2 rows
- gated regression test pinning the error-85 refusal
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:
@@ -115,8 +115,63 @@ ClientType, ClientVersion, ConnectionMode, ConnectionTimeout, StorageSessionId(i
|
||||
Raw decode artifact: `artifacts/reverse-engineering/grpc-nonstream-decode/batch1-decode.txt`
|
||||
(gitignored). Probe command: `grpc-nonstream-decode`; driver:
|
||||
`HistorianGrpcRevisionProbe.ProbeNonStreamedBuffersAsync` (candidate guess-bytes live in the RE tool,
|
||||
not `src/`). **Status: M3 transaction lifecycle proven; full insert blocked on the
|
||||
OpenStorageConnection handshake + btInput decode — a focused follow-up, each step a live probe.**
|
||||
not `src/`).
|
||||
|
||||
### R3.1 follow-up (2026-06-21): `OpenStorageConnection` is the WRONG precondition — error 85 = "session not registered"
|
||||
|
||||
The mapped sequence above named `StorageService.OpenStorageConnection` as the missing console-session
|
||||
step. **A live probe (`grpc-open-storage-connection` CLI / `HistorianGrpcStorageConnectionProbe`)
|
||||
disproved that.** Against the real 2023 R2 server, over a write-enabled (`0x401`) session, every
|
||||
`OpenStorageConnection` attempt — sweeping `ConnectionMode` (0x401/0x402/0x1), `StorageSessionId`-in
|
||||
(Open2-GUID-upper / empty), and `FreeDiskSpace` — returned the **identical** error
|
||||
`84 55 00 00 00 …09 15 00 "OpenStorageConnection"` = **type 4 (CustomError, 0x80 detail flag), code
|
||||
`0x55` = 85**, independent of all swept values. So it is a *structural* refusal, not a bad field.
|
||||
|
||||
**Decoding the refusal (two corroborating facts):**
|
||||
1. **Error 85 is the generic "session not registered for this op" code.** The event read path hits the
|
||||
*same* `type=4 code=85` from `GetNextEventQueryResultBuffer` when the session hasn't registered its
|
||||
tag first (see `HistorianWcfEventOrchestrator` xmldoc) — the fix there is front-door `RegisterTags2`
|
||||
(RTag2), NOT a storage connection.
|
||||
2. **`OpenStorageConnection` is not a front-door client op.** In the 2023 R2 decompile it lives on a
|
||||
**separate `GrpcStorageClient`** (`Archestra.Historian.GrpcClient`, `GrpcClientBase` with its own
|
||||
`Initialize(target, port, …)` channel) and the managed `HistorianAccess` non-streamed write goes
|
||||
through the **native C++ `<Module>.HistorianClient.AddNonStreamedValueAsync`**, never this gRPC op.
|
||||
The `StorageService` proto is almost entirely snapshots / blocks / SF params / `SendSnapshot` —
|
||||
it is the **storage engine's store-and-forward / snapshot interface** (`HistorianAccess`
|
||||
documents `OpenStorageConnection`/`CloseStorageConnection` as the SF-snapshot *flush*), reached on
|
||||
a distinct channel under a service identity. A normal Historian client never opens it on 32565.
|
||||
|
||||
**Corrected required sequence — the precondition is front-door tag registration, not a storage conn:**
|
||||
|
||||
```
|
||||
HistoryService.OpenConnection (write-enabled 0x401) ✅ have it
|
||||
→ HistoryService.RegisterTags(strHandle, btTagInfos = TARGET tag) ⛔ the real missing step
|
||||
(front door, string handle — the RTag2 family; same op that subscribes the event session)
|
||||
→ TransactionService.AddNonStreamValuesBegin ✅ works
|
||||
→ TransactionService.AddNonStreamValues(btInput) ⛔ R3.1 batch failed here precisely
|
||||
BECAUSE no tag was registered for the session (StoreNonStreamValues had no tag→storage route)
|
||||
→ TransactionService.AddNonStreamValuesEnd(bCommit)
|
||||
```
|
||||
|
||||
This matches the original 2020-WCF D2 hypothesis ("what populates the session's tag working set is
|
||||
likely a `RegisterTags2` call") — the gRPC front door does expose that op (`HistoryService.RegisterTags`,
|
||||
in our `HistoryService.proto`).
|
||||
|
||||
**Remaining blockers (both need a native gRPC capture — no static shortcut, do NOT guess bytes):**
|
||||
1. **`HistoryService.RegisterTags` `btTagInfos` for a *regular analog* tag.** The only known RTag2
|
||||
buffer is CM_EVENT's (a built-in tag identified by a well-known 16-byte *tag*-GUID,
|
||||
`0x6750` v2 + count + GUID). Regular tags expose only a uint `tagKey` + a *type*-id GUID via
|
||||
`GetTagInfo` (see `ParseTagInfoRecord`) — **no per-tag GUID**, so the regular-tag registration
|
||||
framing (tagKey-based vs tag-GUID-based) is uncaptured.
|
||||
2. **`AddNonStreamValues` `btInput`** — still C++-built and absent from every decompile (unchanged).
|
||||
|
||||
Both require capturing the **native 2023 R2 gRPC client** performing a non-streamed write (it would
|
||||
emit the exact `RegisterTags` `btTagInfos` + `btInput`), or decoding the C++ serializer. Probe:
|
||||
`grpc-open-storage-connection` (committed, regression-safe — it opens nothing persistent and
|
||||
CloseStorageConnections on success). **Status: M3 transaction lifecycle proven; the insert precondition
|
||||
is now correctly identified as front-door `RegisterTags` (NOT `OpenStorageConnection`); shipping
|
||||
`AddHistoricalValuesAsync` is blocked on capturing the regular-tag `RegisterTags` `btTagInfos` +
|
||||
the `AddNonStreamValues` `btInput`.**
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user