From 62e8f54f71777d51262c25625418ba777757c38d Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Wed, 22 Jul 2026 19:04:58 -0400 Subject: [PATCH] feat(mesh): deployment_artifact.v1 proto + first in-repo Grpc.Tools codegen The repo has consumed packaged gRPC clients (LocalDb.Replication, HistorianGateway.Client) but never compiled a proto locally. This adds the Grpc.Tools toolchain to Commons and a deployment_artifact.proto with a single server-streaming Fetch(deployment_id) -> stream ArtifactChunk RPC, generating both the server base and the client stub (GrpcServices="Both") so central (Host/AdminUI) and the node (Runtime) share one reference. A compile-touch test pins the codegen: it stops compiling if generation regresses. Claude-Session: https://claude.ai/code/session_01GASWkNEi68FSCtvr6rLoEW --- Directory.Packages.props | 1 + .../Protos/deployment_artifact.proto | 26 +++++++++++++ .../ZB.MOM.WW.OtOpcUa.Commons.csproj | 18 +++++++++ .../DeploymentArtifactProtoTests.cs | 38 +++++++++++++++++++ 4 files changed, 83 insertions(+) create mode 100644 src/Core/ZB.MOM.WW.OtOpcUa.Commons/Protos/deployment_artifact.proto create mode 100644 tests/Core/ZB.MOM.WW.OtOpcUa.Commons.Tests/DeploymentArtifactProtoTests.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 39dd734e..8590388c 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -32,6 +32,7 @@ + diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Protos/deployment_artifact.proto b/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Protos/deployment_artifact.proto new file mode 100644 index 00000000..a097ca2c --- /dev/null +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Protos/deployment_artifact.proto @@ -0,0 +1,26 @@ +syntax = "proto3"; + +package deployment_artifact.v1; + +option csharp_namespace = "ZB.MOM.WW.OtOpcUa.Commons.Protos.DeploymentArtifact.V1"; + +// Central serves the deployed-configuration artifact bytes to a driver node (per-cluster mesh +// Phase 3). Only admin-role nodes — which hold the central SQL connection — serve; driver nodes in +// FetchAndCache mode are the clients. The bytes travel here, out-of-band, precisely so they never +// ride an Akka message and hit the frame-size limit. +service DeploymentArtifactService { + // Streams the artifact for a sealed deployment as ordered chunks. NotFound when the id is + // unknown, the deployment is not sealed, or the blob is empty (existence-hiding + the #485 + // "empty bytes are never an empty config" rule). The client verifies + // SHA-256(reassembled) == the RevisionHash it already holds from DispatchDeployment, so no hash + // is carried here. + rpc Fetch(FetchRequest) returns (stream ArtifactChunk); +} + +message FetchRequest { + string deployment_id = 1; +} + +message ArtifactChunk { + bytes data = 1; +} diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Commons/ZB.MOM.WW.OtOpcUa.Commons.csproj b/src/Core/ZB.MOM.WW.OtOpcUa.Commons/ZB.MOM.WW.OtOpcUa.Commons.csproj index 686d3f42..acad5d43 100644 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Commons/ZB.MOM.WW.OtOpcUa.Commons.csproj +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Commons/ZB.MOM.WW.OtOpcUa.Commons.csproj @@ -11,6 +11,24 @@ + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + diff --git a/tests/Core/ZB.MOM.WW.OtOpcUa.Commons.Tests/DeploymentArtifactProtoTests.cs b/tests/Core/ZB.MOM.WW.OtOpcUa.Commons.Tests/DeploymentArtifactProtoTests.cs new file mode 100644 index 00000000..50f28bf0 --- /dev/null +++ b/tests/Core/ZB.MOM.WW.OtOpcUa.Commons.Tests/DeploymentArtifactProtoTests.cs @@ -0,0 +1,38 @@ +using Shouldly; +using Xunit; +using ZB.MOM.WW.OtOpcUa.Commons.Protos.DeploymentArtifact.V1; + +namespace ZB.MOM.WW.OtOpcUa.Commons.Tests; + +/// +/// A compile-touch test: it exists so that the repo's first locally-compiled proto +/// (deployment_artifact.proto) is proven to generate both the client stub and the server +/// base and the message types. If Grpc.Tools codegen regresses, this file stops compiling — +/// which, under TreatWarningsAsErrors, breaks the build loudly rather than silently. +/// +public class DeploymentArtifactProtoTests +{ + [Fact] + public void The_request_message_carries_the_deployment_id() + { + var request = new FetchRequest { DeploymentId = "abc" }; + + request.DeploymentId.ShouldBe("abc"); + } + + [Fact] + public void The_chunk_message_carries_bytes() + { + var chunk = new ArtifactChunk { Data = Google.Protobuf.ByteString.CopyFromUtf8("x") }; + + chunk.Data.Length.ShouldBe(1); + } + + [Fact] + public void The_service_base_and_client_types_generate() + { + // Referencing these types is the whole assertion — GrpcServices="Both" must emit both. + typeof(DeploymentArtifactService.DeploymentArtifactServiceBase).ShouldNotBeNull(); + typeof(DeploymentArtifactService.DeploymentArtifactServiceClient).ShouldNotBeNull(); + } +}