docs(grpc): transport matrix + plan reflect ReadEvents + live-verified writes
- README transport matrix: gRPC writes (EnsureTag/DeleteTag/RenameTags/ AddTagExtendedProperties) flip to live-verified; note the async-rename retry and the extended-property read-back parser gap. ReadEvents gRPC -> tooled-but-bounded (StartEventQuery works, GetNext long-polls, throws on no-row pending an event-bearing server). Refresh the closing production-pattern guidance. - grpc-tooling-completion.md: mark items #1 (writes, done) and #2 (ReadEvents, tooled/bounded) with the live outcomes and follow-ups. 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:
@@ -88,10 +88,10 @@ request rides the RPC but the server faults on an unmet precondition) ·
|
||||
| `GetRuntimeParameterAsync` | ✅ | ✅ | tooled + live-verified over gRPC (`StatusService.GetRuntimeParameter`, the 2020 `GETRP` buffers ride unchanged) |
|
||||
| `GetTagExtendedPropertiesAsync` | ✅ | ✅ | tooled + live-verified over gRPC (`RetrievalService.GetTagExtendedPropertiesFromName`, the `GetTepByNm` buffers ride unchanged) |
|
||||
| `ExecuteSqlCommandAsync` | ✅ | ⛔ | gRPC request rides `RetrievalService.ExecuteSqlCommand`, but the server-side `CSrvDbConnection.ExecuteSqlCommand` faults (`IndexOutOfRange`, native err 38) — an unmet DB-connection precondition; bounded behind `ProtocolEvidenceMissingException`. Use WCF |
|
||||
| `ReadEventsAsync` | ✅ | 🔌 | gRPC `StartEventQuery`/`GetNextEventQueryResultBuffer`/`EndEventQuery` recovered, but the read needs the full CM_EVENT registration state machine (RTag2+EnsT2) ported — not yet tooled |
|
||||
| `ReadEventsAsync` | ✅ | ⚠️ | tooled + routed over gRPC: the full CM_EVENT registration replay (`UpdateClientStatus`→`RegisterTags`→`EnsureTags` + discovery probes) runs and `StartEventQuery` succeeds, but `GetNextEventQueryResultBuffer` **long-polls** on no data (it blocks to the deadline rather than returning the synchronous 5-byte code-85 terminal the WCF op gives). The read is **hard-bounded** (≤30s) and throws `ProtocolEvidenceMissingException` on the no-row path rather than assert a false empty. Row-level retrieval is **not yet live-verified** — the dev box holds no events; pending a capture against an event-bearing 2023 R2 server. Use WCF for event reads |
|
||||
| `SendEventAsync` | ✅ | 🔌 | rides `AddStreamValues` family; no distinct event-send RPC, framing uncaptured over gRPC |
|
||||
| `EnsureTagAsync` / `DeleteTagAsync` / `RenameTagsAsync` | ✅ | 🧪 | tooled + routed over gRPC (`HistoryService.EnsureTags` / `DeleteTags` / `StartJob`, write-enabled 0x401 session, WCF serializers reused); sandbox-gated — not yet run destructively against a live box |
|
||||
| `AddTagExtendedPropertiesAsync` | ✅ | 🧪 | tooled + routed over gRPC (`HistoryService.AddTagExtendedProperties`, write-enabled session); sandbox-gated. gRPC also exposes `DeleteTagExtendedProperties` (WCF delete was server-blocked) |
|
||||
| `EnsureTagAsync` / `DeleteTagAsync` / `RenameTagsAsync` | ✅ | ✅ | live-verified 2026-06-22 over gRPC (`HistoryService.EnsureTags` / `DeleteTags` / `StartJob`, write-enabled 0x401 session, WCF serializers reused) via a self-cleaning sandbox-tag lifecycle. Rename is an async StartJob — transiently rejectable right after create, so callers should retry |
|
||||
| `AddTagExtendedPropertiesAsync` | ✅ | ✅ | live-verified 2026-06-22 over gRPC (`HistoryService.AddTagExtendedProperties`, write-enabled session). NOTE: reading a written prop back via `GetTagExtendedPropertiesAsync` can hit a shared-parser evidence gap (value marker `0x01` vs the captured compact-string `0x09`); the write itself is confirmed. gRPC also exposes `DeleteTagExtendedProperties` (WCF delete was server-blocked) |
|
||||
| `GetConnectionStatusAsync` | ✅ | ❌ | synthesized from an authenticated probe — no dedicated RPC on either transport (gRPC `PingServer`/`GetHistorianConsoleStatus` could synthesize it) |
|
||||
| `ReadBlocksAsync` | ❌ | ❌ | `StartBlockRetrievalQuery` never captured on either transport — throws `ProtocolEvidenceMissingException` |
|
||||
|
||||
@@ -105,12 +105,20 @@ confirmed by tooling the read-side config ops (`GetRuntimeParameter`,
|
||||
the server accepts them. Two caveats surfaced when capturing the rest: `ExecuteSqlCommand`
|
||||
is **server-walled** (the front-door `CSrvDbConnection` faults on a DB-connection
|
||||
precondition the managed session doesn't establish — the same *class* of wall as
|
||||
`OpenStorageConnection`), and `ReadEvents` needs the CM_EVENT registration state
|
||||
machine ported. The remaining 🔌 rows are **capture-and-wire** items (route the
|
||||
existing serializer into a gRPC orchestrator + live-capture), not
|
||||
protocol-discovery — but per "capture first, never guess wire bytes" they stay
|
||||
untooled until each is verified live. The natural production pattern today remains
|
||||
WCF for config/writes and `RemoteGrpc` for reads + `AddHistoricalValuesAsync`.
|
||||
`OpenStorageConnection`), and `ReadEvents` is now tooled over gRPC (the CM_EVENT
|
||||
registration state machine is ported and `StartEventQuery` succeeds) but its row
|
||||
retrieval is not yet live-verified: the gRPC server long-polls
|
||||
`GetNextEventQueryResultBuffer` on no data instead of returning the WCF code-85
|
||||
terminal, so on the idle dev box the bounded read throws
|
||||
`ProtocolEvidenceMissingException` rather than fabricate an empty result —
|
||||
confirming rows awaits an event-bearing 2023 R2 server. The remaining 🔌 row
|
||||
(`SendEventAsync`) is a **capture-and-wire** item (route the existing serializer
|
||||
into a gRPC orchestrator + live-capture), not protocol-discovery — but per
|
||||
"capture first, never guess wire bytes" it stays untooled until verified live. The
|
||||
natural production pattern today: `RemoteGrpc` now covers reads,
|
||||
`AddHistoricalValuesAsync`, and the tag-config writes (create/delete/rename/extended
|
||||
properties, live-verified) — use WCF for SQL, events, and reading extended
|
||||
properties back until those gRPC gaps close.
|
||||
|
||||
> A 2023 R2 server reports History interface version 12 (vs. 11 on 2020). The
|
||||
> connect-time version gate accepts both — they are byte-compatible — so gRPC
|
||||
|
||||
@@ -15,6 +15,7 @@ metadata, system-parameter, server time-zone, measured store-forward status,
|
||||
- `GetTagExtendedPropertiesAsync` (read) — ✅ live-verified
|
||||
- `ExecuteSqlCommandAsync` — ⛔ server-walled, bounded behind `ProtocolEvidenceMissingException`
|
||||
- `EnsureTag` / `DeleteTag` / `RenameTags` / `AddTagExtendedProperties` — 🧪 tooled + routed, sandbox-gated, **not yet run destructively live**
|
||||
- `ReadEventsAsync` — ⚠️ tooled + routed 2026-06-22 (item #2 below): chain runs, `StartEventQuery` succeeds, but `GetNextEventQueryResultBuffer` long-polls on no data; hard-bounded (≤30s) and throws `ProtocolEvidenceMissingException` on the no-row path. Row retrieval pending an event-bearing server.
|
||||
|
||||
Test baseline: 317 offline green, 19 gRPC-live green. Relevant memory:
|
||||
`project_grpc_config_ops_tooling`, `project_m0_grpc_parity`,
|
||||
@@ -40,7 +41,20 @@ in `project_grpc_config_ops_tooling` memory and `Grpc/Protos/*.proto`.
|
||||
|
||||
## Remaining items (priority order)
|
||||
|
||||
### 1. Live-verify the write ops (cheapest, highest-confidence-gain)
|
||||
### 1. Live-verify the write ops — ✅ DONE 2026-06-22
|
||||
**Outcome:** ran the gated lifecycle against a synthetic sandbox tag (`ZZ_SdkGrpcWriteProbe`); the
|
||||
writes flip 🧪→✅. `EnsureTags` (create), `AddTagExtendedProperties`, `StartJob` rename, and
|
||||
`DeleteTags` all succeed live over gRPC (write-enabled 0x401 session, WCF serializers reused) — NO
|
||||
priming discovery-dance needed. Two findings: (a) **rename** is an async StartJob that the server can
|
||||
transiently reject right after the create commits and on target-name collision — the test now
|
||||
pre-cleans both names and retries rename (4×); callers should likewise retry. (b) **reading a written
|
||||
extended property back** via `GetTagExtendedPropertiesAsync` hits a shared-parser evidence gap (value
|
||||
marker `0x01` where the parser expects compact-string `0x09`) — a read-side gap, not a write failure;
|
||||
the test tolerates it. Lifecycle test is self-cleaning and asserts no litter remains (verified two
|
||||
consecutive clean passes). Next read-side follow-up: capture the `0x01` extended-property value
|
||||
encoding and extend `HistorianTagExtendedPropertyProtocol.ParseResponse`.
|
||||
|
||||
_Original notes:_
|
||||
- **Goal:** flip the 🧪 writes to ✅ by running the gated lifecycle test against a sandbox tag.
|
||||
- **How:** set `HISTORIAN_GRPC_WRITE_SANDBOX_TAG` to a throwaway name and run
|
||||
`TagWriteLifecycle_OverGrpc_CreatesAddsPropRenamesDeletes` against the live 2023 R2 box.
|
||||
@@ -53,7 +67,23 @@ in `project_grpc_config_ops_tooling` memory and `Grpc/Protos/*.proto`.
|
||||
- **Files:** `tests/.../HistorianGrpcIntegrationTests.cs` (run only),
|
||||
`src/.../Grpc/HistorianGrpcTagWriteOrchestrator.cs` (priming only if rejected).
|
||||
|
||||
### 2. ReadEvents over gRPC (heaviest read op)
|
||||
### 2. ReadEvents over gRPC (heaviest read op) — ✅ TOOLED 2026-06-22 (rows pending event-bearing server)
|
||||
**Outcome:** `ReadEventsAsync` is routed over gRPC (`HistorianGrpcEventOrchestrator`). The CM_EVENT
|
||||
registration replay (`UpdateClientStatus`→6 `GetSystemParameter`→`RegisterTags`→cross-service version
|
||||
probes→`EnsureTags`, captured buffers shared with WCF via `HistorianEventRegistrationProtocol`) runs
|
||||
and **`StartEventQuery` succeeds live**. The blocker that remains is server behavior, not the port:
|
||||
`GetNextEventQueryResultBuffer` **long-polls** when the query has no rows — it blocks to the call
|
||||
deadline instead of returning the synchronous 5-byte type=4 code=85 terminal the 2020 WCF op returns.
|
||||
Per-call gRPC-Web deadlines proved unreliable over the tunnel (a 4s-deadline chain still ran >90s), so
|
||||
the read is hard-bounded by an **overall linked-CTS budget** (≤30s, scaled to `RequestTimeout`); gRPC
|
||||
honors token cancellation. On the no-row path the orchestrator throws `ProtocolEvidenceMissingException`
|
||||
rather than assert a false-empty list. The idle dev box holds no events, so **row-level retrieval is
|
||||
not yet live-verified** — flip the gated test
|
||||
`ReadEventsAsync_OverGrpc_StartsQueryButRowRetrievalIsLongPollBlocked` to assert parsed rows once an
|
||||
event-bearing 2023 R2 server is available (and consider whether the long-poll needs a "fetch historical
|
||||
then stop" request flag the native client may set). README row is ⚠️.
|
||||
|
||||
_Original notes (still the reference for the registration replay):_
|
||||
- **Goal:** route `ReadEventsAsync` over gRPC.
|
||||
- **RPCs (exist):** `RetrievalService.StartEventQuery` (`uiHandle`, `uiQueryRequestType`,
|
||||
`btRequest`) → `{Status, uiQueryHandle, btResonse}`; `GetNextEventQueryResultBuffer`
|
||||
|
||||
Reference in New Issue
Block a user