ClusterClient → gRPC migration — CENTRAL TRACKING DOCUMENT
This is the single place to see program state. Update it at every phase boundary, merge,
defect, and decision. Everything else (design, plan, live gate) is referenced from here.
Program goal: all central↔site mesh traffic on gRPC with per-cluster PSK auth;
ClusterClient/ClusterClientReceptionist deleted; Akka remoting never crosses the mesh
boundary. Mirrors ScadaBridge's completed 2026-07-22 migration (same cluster boxes, same end
posture).
Document map
Phase status board
| Phase |
Scope |
Branch |
Status |
PR / merge SHA |
Live gate |
| 0 |
Proto + mappers + goldens; both auth interceptors; IMeshPskProvider; options/validators (+Grpc value, SplitTopology relaxation); rig dark config; stale-comment fixes; FleetStatus issue filed |
feat/grpc-phase0-contract-auth |
NOT STARTED |
— |
PENDING |
| 1A |
Central→node command plane (NodeCommandService on the Telemetry listener; MeshGrpcCommandDialer; third mode branch in CentralCommunicationActor) — worktree A |
feat/grpc-node-command |
NOT STARTED |
— |
PENDING |
| 1B |
Node→central ack plane (CentralCommandService on the ConfigServe listener; GrpcApplyAckSender; third branch in NodeCommunicationActor) — worktree B, rebases on 1A |
feat/grpc-central-ack |
NOT STARTED |
— |
PENDING |
| 2 |
Baseline reproduction on pre-flip build, then full-rig cutover (OTOPCUA_MESH_MODE default → Grpc) + soak + kill drills |
(rig commit) |
NOT STARTED |
— |
PENDING |
| 3 |
Delete ClusterClient (code/HOCON/config/tests); SplitTopology requires Grpc; grep-gates; docs sweep incl. scadaproj index |
feat/grpc-phase3-deletion |
NOT STARTED |
— |
— |
| 4 |
Terminal live gate on the post-deletion image (8 checks, provenance-guarded) |
(on master) |
NOT STARTED |
— |
PENDING |
Execution rules recap: phase order 0 → (1A ∥ 1B) → 2 → 3 → 4; 1A∥1B only in separate git
worktrees, one Opus subagent per worktree, sequential tasks within a worktree; 1A merges
first, 1B rebases (union-resolve Host/Program.cs map block, compose env,
docs/Configuration.md); rig mode flips before Phase 2 are never committed; STOP at every
phase DoD — PR + merge are user-owned.
Decision log
| Date |
Decision |
Where recorded |
| 2026-08-06 |
Reuse existing listeners (Telemetry :4056 node-side, ConfigServe :4055 central-side); rejected dedicated port + ClusterNode column |
design §3.1 |
| 2026-08-06 |
Per-cluster PSK OT-MESH-PSK-<clusterId> from Secrets; rejected fleet-wide shared key (blast radius); MeshTransport:ClusterPsks config map as rig/test override |
design §3.4, plan T0.3/T0.5 |
| 2026-08-06 |
Fire-and-forget semantics preserved exactly — no retries/queues; failures become visible logs, backstops (2-min deadline, boot replay) unchanged |
design §3.3 |
| 2026-08-06 |
Dps mode survives for single-mesh topologies; only ClusterClient is deleted; the three stale "Phase 6 deletes DPS" comments get corrected, not obeyed |
design §3.6/§4.2 |
| 2026-08-06 |
No transport interface — third mode branch inside the two choke-point actors (ScadaBridge §7.1 economy) |
design §4.3 |
| 2026-08-06 |
Coexistence granularity: central's mode governs commands, each node's mode governs its acks (per-pair flips possible — better than ScadaBridge's central-wide flag) |
design §3.6 |
Defect log
Defects found during execution (live-gate or review), with root cause + pinned test.
| # |
Phase |
Defect |
Root cause |
Fix / pin |
| — |
— |
(none yet) |
|
|
Carry-forward register
Checks a gate could not drive, named + carried to the next gate (proportionate-gate rule).
| Item |
Deferred from |
Proven instead by |
Closed in |
| — |
— |
(none yet) |
|
Issues filed by this program
| Issue |
What |
Status |
| (Gitea # TBD in Phase 0, T0.6) |
FleetStatusBroadcaster derives "fleet" from central's own gossip ring — silently central-pair-blind since mesh Phase 6; pre-existing, transport-independent, out of this program's scope |
to file |
Known constraints carried from the research (do not rediscover)
- No Ask crosses the boundary — 4+1 one-way Tells is the whole surface (design §1).
ConfigPublishCoordinator's unconditional deployment-acks DPS subscription is the
Dps-mode ingress — keep it (design §4.4).
- Interceptors: exactly one public constructor, reflection-pinned (gotcha 1).
- Proto enum 0 =
UNSPECIFIED; mapper goldens must cover every member incl. null variants
(gotcha 2, plan T0.2).
- Kestrel block untouchable — no new listeners in this program (gotcha 3).
x-otopcua-cluster is a central-side routing hint; isolation is proven by wrong-key
rejection (gotcha 9).