feat(mesh): node gRPC artifact fetcher — SHA-verified reassembly, endpoint failover, null-on-any-failure

Phase 3 Task 4. GrpcDeploymentArtifactFetcher streams the artifact from the first
configured central endpoint that answers, reassembles the chunks, and verifies
SHA-256(bytes) lowercase-hex == the dispatched RevisionHash (byte-identical to
ConfigComposer's Convert.ToHexStringLower(SHA256.HashData(blob))). Every per-endpoint
problem — RpcException, zero-length stream, or hash mismatch — is logged and the next
endpoint tried; if none yield verified bytes it returns null (apply failure, keep
last-known-good — the #485 contract), never throwing into the actor loop. A
Func<endpoint, client> seam keeps the gRPC boundary out of the unit tests (that is
Task 8's job); the real path caches one h2c GrpcChannel per endpoint.

Files live under the .DeploymentCache namespace (folder Deployment/) to avoid shadowing
the Configuration.Entities.Deployment type in the test assembly — same convention as
LocalDbDeploymentArtifactCache.

Claude-Session: https://claude.ai/code/session_01GASWkNEi68FSCtvr6rLoEW
This commit is contained in:
Joseph Doherty
2026-07-22 19:28:48 -04:00
parent 4d88f67c0d
commit 7a8fb5f129
4 changed files with 346 additions and 0 deletions
@@ -8,6 +8,9 @@
<ItemGroup>
<PackageReference Include="Akka.Hosting"/>
<PackageReference Include="Akka.Cluster.Tools"/>
<!-- Node-side gRPC client that fetches the deployed-configuration artifact from central
(per-cluster mesh Phase 3, FetchAndCache mode). The generated stub lives in Commons. -->
<PackageReference Include="Grpc.Net.Client"/>
<!-- Core LocalDb only (ILocalDb + the connection/transaction seam) — the deployment-artifact
cache lives here, but the sync engine and its gRPC endpoint are the Host's concern. -->
<PackageReference Include="ZB.MOM.WW.LocalDb" />