SendEvent over gRPC: implement + live-validate (was capture-gated)

Captured the native 2023 R2 client's gRPC event send (new capture-send-event
harness scenario): it rides HistoryService.AddStreamValues with the SAME "OS"
(0x534F) storage-sample buffer the WCF path already uses (HistorianEventWrite-
Protocol) — confirming "no distinct RPC", and that it is NOT the historical
write's "ON" buffer. Diffed the write-enabled vs read-only v8 Event open: byte-
identical apart from per-session crypto, so the existing OpenSession event path
is reused unchanged.

So SendEvent-over-gRPC was pure assembly of proven parts:
- HistorianGrpcEventWriteOrchestrator = v8 Event open + CM_EVENT registration
  (UpdC3/RegisterTags/EnsureTags) + AddStreamValues(OS buffer).
- HistorianClient.SendEventAsync now routes to it for RemoteGrpc (WCF otherwise).

Live-validated end-to-end against the 2023 R2 server: pure-managed SDK send →
AddStreamValues BSuccess=true → the event reads back from the server (markers
confirmed in returned event rows). The native gRPC RegisterTags(24B) +
EnsureTags(86B) byte-match our serializers (new GrpcEventSendProtocolTests
golden, closing the 83-vs-86 EnsureTags question). Gated live test
SendEventAsync_OverGrpc_AcceptsEvent (opt-in HISTORIAN_GRPC_EVENT_SEND=1).
331 offline tests pass.

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-23 15:37:22 -04:00
parent ae536bb4b8
commit afc7c4bf96
6 changed files with 384 additions and 21 deletions
@@ -233,6 +233,37 @@ public sealed class HistorianGrpcIntegrationTests
Assert.Contains(samples, s => s.NumericValue is { } v && Math.Abs(v - expected) < 0.01);
}
[Fact]
public async Task SendEventAsync_OverGrpc_AcceptsEvent()
{
string? host = Environment.GetEnvironmentVariable("HISTORIAN_GRPC_HOST");
// Gated additionally on a dedicated opt-in so this WRITE test never runs by accident — it
// appends a clearly-marked test event to the server's event history. Captured 2026-06-23:
// the gRPC event send rides HistoryService.AddStreamValues with the same "OS" buffer the WCF
// path uses (HistorianEventWriteProtocol), on a v8 Event session + CM_EVENT registration.
if (string.IsNullOrWhiteSpace(host)
|| string.IsNullOrEmpty(Environment.GetEnvironmentVariable("HISTORIAN_USER"))
|| Environment.GetEnvironmentVariable("HISTORIAN_GRPC_EVENT_SEND") is null)
{
return;
}
HistorianClient client = new(BuildOptions(host));
var evt = new HistorianEvent(
Id: Guid.NewGuid(),
EventTimeUtc: DateTime.UtcNow,
ReceivedTimeUtc: DateTime.UtcNow,
Type: "SdkSendProbe",
SourceName: "SdkSendProbe",
Namespace: "SdkCapture",
RevisionVersion: 0,
Properties: new Dictionary<string, object?> { ["SdkProbeProp"] = "SdkProbeValue" });
bool sent = await client.SendEventAsync(evt, CancellationToken.None);
Assert.True(sent, "gRPC SendEvent should be accepted by the server (AddStreamValues BSuccess).");
}
[Fact]
public async Task ReadAggregateAsync_OverGrpc_ReturnsTimeWeightedAverageRows()
{