feat(grpc): Phase 1A — site→central control plane over gRPC (default Akka) #26
Reference in New Issue
Block a user
Delete Branch "feat/grpc-central-control"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Phase 1A of the ClusterClient→gRPC migration: the site→central control plane now has a gRPC implementation behind a flag. Default is still
Akka— nothing moves off ClusterClient by default. Rollback = flipScadaBridge:Communication:CentralTransportback.What changed
central_control.proto(scadabridge.centralcontrol.v1, 7 RPCs) +CentralControlDtoMapperwith 32 round-trip goldens (verified to have teeth by mutation). Checked-in codegen (no active<Protobuf>item — the arm64 Docker build stays intact).CentralControlService— decodes →Asks the existingCentralCommunicationActor(zero handler changes) → encodes. Gated by a newCentralControlAuthInterceptorthat verifies theBearerPSK against the per-site key (ISitePskProvider, keyed by the requiredx-scadabridge-siteheader), fail-closed. Own single public constructor, pinned by a reflection test.ICentralTransportseam —AkkaCentralTransport(today's ClusterClient path, extracted verbatim) andGrpcCentralTransport(new:CentralChannelProviderwith sticky failover/failback, PSK, per-call deadlines matched to today's Ask timeouts, no cross-node retry onDeadlineExceeded). Selected byCentralTransport(defaultAkka).NotificationForwarderTests,HealthReportSenderTests,SiteAuditTelemetryActorTests) pass unmodified — the plan's own signal the seam is placed right.Live gate — PASS, and it caught a real regression
Full writeup in
docs/plans/2026-07-22-clusterclient-to-grpc-live-gate.md. With site-a flipped to gRPC and site-b/c left on Akka: heartbeat drives the active flag, health lands (site-a online, advancing sequence), reconcile-after-restart works, 0 auth failures, and Akka + gRPC sites coexist.The rig caught a central-down defect the whole 6,872-test suite missed (
0e162cb2): T1A.2'sConfigureKestrel(ListenAnyIP(8083))put Kestrel into explicit-endpoints mode, which suppressesASPNETCORE_URLS— so binding only the gRPC port silently deleted central's entire:5000HTTP surface (Central UI, Management/Inbound API,/health/*for Traefik +IActiveNodeGate), with no startup error. TestServer never binds real Kestrel, so no test could see it. Fixed by re-declaring the configured HTTP port(s) alongside the gRPC port (Program.ParseHttpBindPorts+ 14 test cases).Deferred (honest)
Notification-e2e and audit-ingest were not driven live — the rig has no deployed instance to emit them. Same transport + auth + actor path as the three proven RPCs, 32 goldens on the payloads; Phase 2's central-kill S&F soak exercises them for real.
Verification
dotnet build0/0. Host.Tests 405, Communication.Tests 371 — full runs, green.Merged into
mainvia merge commitaa60f438(branch had diverged from main by docs-tracking commits, so a non-ff merge). Merged tree builds 0/0; Host.Tests 405 + Communication.Tests 371 green. Closing.Pull request closed