feat(grpc): tool the WCF-only config ops over the gRPC transport

Wire the config operations that previously only worked over WCF onto RemoteGrpc,
reusing the proven 2020 byte serializers verbatim inside the protobuf bytes fields
(keyed by the Open2 session handle). Live-verified against a real 2023 R2 server
where noted.

Read ops (live-verified):
- GetRuntimeParameterAsync -> StatusService.GetRuntimeParameter (GETRP serializer)
- GetTagExtendedPropertiesAsync -> RetrievalService.GetTagExtendedPropertiesFromName
  (GetTepByNm serializer + sequence paging; page-0 FillBufferFromVector is the
  benign no-data terminator, matched to the WCF break-and-return-empty semantics)

Server-walled (bounded with captured evidence):
- ExecuteSqlCommandAsync -> RetrievalService.ExecuteSqlCommand. The request rides
  the RPC but the server-side CSrvDbConnection.ExecuteSqlCommand faults
  (IndexOutOfRange / native err 38) on a DB-connection precondition the pure
  managed gRPC session doesn't establish (same class as OpenStorageConnection).
  Surfaced as ProtocolEvidenceMissingException.

Write ops (tooled + routed, sandbox-gated — not run destructively live):
- EnsureTagAsync / DeleteTagAsync / RenameTagsAsync / AddTagExtendedPropertiesAsync
  via HistoryService.EnsureTags / DeleteTags / StartJob / AddTagExtendedProperties
  on a write-enabled (0x401) session, reusing the WCF golden serializers. The WCF
  priming discovery-dance is omitted (the M3 gRPC write probe worked without it);
  add it first if a live sandbox run is rejected.

Routed in Historian2020ProtocolDialect / HistorianClient on the RemoteGrpc branch.

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-22 01:26:33 -04:00
parent 035d8a92f2
commit ef68016c7a
6 changed files with 448 additions and 7 deletions
@@ -95,21 +95,27 @@ internal sealed class Historian2020ProtocolDialect
{
cancellationToken.ThrowIfCancellationRequested();
ArgumentException.ThrowIfNullOrWhiteSpace(name);
return Wcf.HistorianWcfStatusClient.GetRuntimeParameterAsync(_options, name, cancellationToken);
return UseGrpc
? HistorianGrpcStatusClient.GetRuntimeParameterAsync(_options, name, cancellationToken)
: Wcf.HistorianWcfStatusClient.GetRuntimeParameterAsync(_options, name, cancellationToken);
}
public Task<IReadOnlyList<Models.HistorianTagExtendedProperty>> GetTagExtendedPropertiesAsync(string tag, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
ArgumentException.ThrowIfNullOrWhiteSpace(tag);
return Wcf.HistorianWcfTagExtendedPropertyClient.GetTagExtendedPropertiesAsync(_options, tag, cancellationToken);
return UseGrpc
? Grpc.HistorianGrpcTagClient.GetTagExtendedPropertiesAsync(_options, tag, cancellationToken)
: Wcf.HistorianWcfTagExtendedPropertyClient.GetTagExtendedPropertiesAsync(_options, tag, cancellationToken);
}
public Task<HistorianSqlResult> ExecuteSqlCommandAsync(string command, HistorianSqlExecuteOption option, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
ArgumentException.ThrowIfNullOrWhiteSpace(command);
return Wcf.HistorianWcfSqlClient.ExecuteSqlCommandAsync(_options, command, option, cancellationToken);
return UseGrpc
? Grpc.HistorianGrpcSqlClient.ExecuteSqlCommandAsync(_options, command, option, cancellationToken)
: Wcf.HistorianWcfSqlClient.ExecuteSqlCommandAsync(_options, command, option, cancellationToken);
}
private static async IAsyncEnumerable<T> Missing<T>(