gRPC M0: probe (R0.4, live-verified) + system-param (R0.3) + shared handshake
Roadmap docs/plans/hcal-roadmap.md, milestone M0 (gRPC parity for the DONE surface). Now unblocked for live verification by a reachable 2023 R2 server. - R0.4 Probe over gRPC: new HistorianGrpcProbe calls History/Retrieval/Status GetInterfaceVersion (unauthenticated). ProbeAsync routes over gRPC when Transport==RemoteGrpc. LIVE-VERIFIED against a real 2023 R2 server — needs no credentials (runs before the auth loop), so it works despite the auth blocker. - R0.3 System parameter over gRPC: new HistorianGrpcStatusClient calls StatusService.GetSystemParameter over the authenticated session; routed in the dialect. Built + unit-tested (request/response field mapping pinned). Live-verification pending an auth fix (see below). - Extracted the proven auth handshake from HistorianGrpcReadOrchestrator into shared Grpc/HistorianGrpcHandshake (reused by read + status + future browse/metadata). Repointed the IL structural guardrail test to it. - Diagnostics: round-failure now decodes the native server error + hex/ASCII preview (HistorianNativeHandshake.DescribeError). This surfaced the live auth blocker as SEC_E_LOGON_DENIED (0x8009030C) at NTLM round 1 — framing is correct, the credential did not validate. Probable cause: stale file password or NAM-domain NTLM restriction (Kerberos/RDP works, NTLM denied; no SPN path over the tunnel). 216 unit tests pass; live gRPC probe passes. Sanitization scan clean. 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:
@@ -9,15 +9,39 @@ HCAL replacement, built on the **2023 R2 gRPC transport**. Derived from
|
||||
> protocol serializer/parser + golden-byte unit test + an env-gated live integration
|
||||
> test against the local Historian.
|
||||
|
||||
## Progress (updated 2026-06-19)
|
||||
## Progress (updated 2026-06-21)
|
||||
|
||||
- ✅ **R0.6 version gate** — `HistorianServerVersionGate` + `HistorianClientOptions.VerifyServerInterfaceVersion`;
|
||||
fail-closed on connect, wired into both WCF and gRPC paths. Supported versions are
|
||||
evidence-based (Hist=11, Retr=4, Trx=2; Status reachability-only), captured from the
|
||||
live server. 10 unit tests.
|
||||
evidence-based (Hist=11/12, Retr=4, Trx=2; Status reachability-only), captured from the
|
||||
live server. History 12 (2023 R2 gRPC) accepted alongside 11 (buffer-compatible).
|
||||
- ✅ **CW-1 capture pipeline** — `ProtocolCaptureSanitizer` + `ProtocolFixtureWriter` +
|
||||
`capture-tag-info` CLI command; produces sanitized `fixtures/protocol/<op>/` golden files.
|
||||
11 unit tests. First fixture: `get-tag-info/analog-*.json`.
|
||||
- ✅ **gRPC auth handshake (read chain)** — LIVE-VERIFIED 2026-06-21 against a real 2023 R2
|
||||
server: `ReadRawAsync` over `RemoteGrpc` returns rows. Token loop routes to
|
||||
`StorageService.ValidateClientCredential`. Shared handshake extracted to
|
||||
`Grpc/HistorianGrpcHandshake` for reuse by the status/browse/metadata paths.
|
||||
- ✅ **R0.4 Probe over gRPC** — `Grpc/HistorianGrpcProbe` (History/Retrieval/Status
|
||||
`GetInterfaceVersion`); `ProbeAsync` routes over gRPC when `Transport==RemoteGrpc`.
|
||||
**LIVE-VERIFIED 2026-06-21** (no credentials required — runs before the auth loop).
|
||||
- 🟡 **R0.3 System parameter over gRPC** — `Grpc/HistorianGrpcStatusClient.GetSystemParameterAsync`
|
||||
(`StatusService.GetSystemParameter`); routed in the dialect. Built + unit-tested
|
||||
(request/response field mapping pinned). **Live-verification pending an auth fix** — see blocker.
|
||||
Code path is the proven handshake + a single string-in/string-out RPC.
|
||||
|
||||
> ⚠️ **Auth blocker (2026-06-21):** live gRPC ops needing a client handle (R0.1/R0.2/R0.3 and the
|
||||
> read chain) fail at NTLM **round 1**. The decoded server error is
|
||||
> `SEC_E_LOGON_DENIED` (0x8009030C) from `aahClientAccessPoint::CServerContext::ProcessClient…` —
|
||||
> round 0 (NEGOTIATE) succeeds, round 1 (the password-bearing AUTHENTICATE) is denied. The token
|
||||
> **framing is correct** (a framing fault would surface as `SEC_E_INVALID_TOKEN`); the server parsed
|
||||
> a valid NTLM message but the credential did not validate. The creds in the gitignored file are
|
||||
> byte-faithfully parsed (verified) and the domain user logs in via **RDP (Kerberos)**, so the
|
||||
> probable cause is one of: (a) the file password is **stale** vs the account, or (b) the NAM domain
|
||||
> **restricts NTLM** ("Network security: Restrict NTLM") — Kerberos/RDP works but NTLM is denied, and
|
||||
> over the SOCKS/SSH tunnel (host→127.0.0.1, no SPN) the client cannot use Kerberos. Probe (R0.4) is
|
||||
> unaffected (unauthenticated). Diagnostic: the round-failure exception now decodes the native error
|
||||
> + a hex/ASCII preview (`HistorianNativeHandshake.DescribeError`).
|
||||
|
||||
> ⚠️ **Live-verification constraint:** the local Historian is **2020** (WCF, port 32568) — the
|
||||
> 2023 R2 gRPC endpoint (32565) is absent. M0's gRPC routing (R0.1–R0.4) can be built and
|
||||
|
||||
Reference in New Issue
Block a user