feat(secrets): scaffold ZB.MOM.WW.Secrets.Replicator.Grpc + secrets_hub.v1 pull-only proto

Adds the third replicator sibling next to Replicator.SqlServer and
Replicator.AkkaDotNet: the central-hosted gRPC secrets hub. This commit is
scaffold only -- project, wire contract, solution wiring -- no service or
client implementation.

The proto mirrors exactly the read half of ISecretReplicationHub
(GetManifestAsync, GetManyAsync). ApplyReplicatedAsync is deliberately NOT
mirrored: in this topology every update originates at central and a site node
is a strict follower, so the narrow contract IS the enforcement -- with no
write RPC on the wire, a mis-wired or compromised site node has no way to push
a row into the hub regardless of what its authorization allows.

Messages are hand-written mirrors of StoredSecret and SecretManifestEntry
rather than serializations of the domain types, following the Akka
replicator's SecretRowDto precedent: the trust boundary stays structural, and
a field added to StoredSecret later cannot start crossing the network merely
by existing. Timestamps are ISO-8601 "O" strings (not google.protobuf.
Timestamp, which cannot round-trip a DateTimeOffset's offset) so a relayed row
is byte-identical to the one central holds; nullable .NET fields use proto3
explicit presence so null and empty string stay distinguishable.

Grpc 2.76.0 / Google.Protobuf 3.34.1 match what ScadaBridge, OtOpcUa and the
sibling ZB.MOM.WW.LocalDb libraries already resolve, so a consumer taking both
gets one Grpc rather than a downgrade warning.

Claude-Session: https://claude.ai/code/session_014WNM4vjoVksyyBraTXSZE1
This commit is contained in:
Joseph Doherty
2026-08-07 05:48:27 -04:00
parent ad7df925ae
commit 5fd7bf524a
4 changed files with 211 additions and 0 deletions
@@ -0,0 +1,57 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<RootNamespace>ZB.MOM.WW.Secrets.Replicator.Grpc</RootNamespace>
</PropertyGroup>
<PropertyGroup>
<IsPackable>true</IsPackable>
<PackageId>ZB.MOM.WW.Secrets.Replicator.Grpc</PackageId>
<Authors>ZB.MOM.WW</Authors>
<Description>Central-hosted gRPC secrets hub (pull-only) for the ZB.MOM.WW SCADA family — site nodes follow central, ciphertext-only.</Description>
<PackageProjectUrl>https://gitea.dohertylan.com/dohertj2/zb-mom-ww-secrets</PackageProjectUrl>
<RepositoryUrl>https://gitea.dohertylan.com/dohertj2/zb-mom-ww-secrets</RepositoryUrl>
</PropertyGroup>
<!-- One package carries BOTH halves of the contract: the hub service base class hosted at
central and the follower client dialled from a site node. GrpcServices="Both" is what
makes that possible from a single .proto compile. -->
<ItemGroup>
<Protobuf Include="Protos/secrets_hub.proto" GrpcServices="Both" ProtoRoot="Protos" />
</ItemGroup>
<!-- Grpc.AspNetCore brings the server-side hosting surface (MapGrpcService), which needs the
ASP.NET Core shared framework. That framework reference also supplies the whole
Microsoft.Extensions.* surface the siblings reference explicitly (DI, Options,
Configuration, Hosting, Logging), so this project deliberately carries no PackageReference
for them — NuGet reports each as prunable (NU1510), which is an error here. -->
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ZB.MOM.WW.Secrets\ZB.MOM.WW.Secrets.csproj" />
<ProjectReference Include="..\ZB.MOM.WW.Secrets.Abstractions\ZB.MOM.WW.Secrets.Abstractions.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" />
<PackageReference Include="Grpc.AspNetCore" />
<PackageReference Include="Grpc.Net.Client" />
<PackageReference Include="Grpc.Core.Api" />
<PackageReference Include="Grpc.Tools">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="ZB.MOM.WW.Secrets.Replicator.Grpc.Tests" />
</ItemGroup>
</Project>