Phase 6.3 Stream C core - RedundancyStatePublisher + PeerReachability #99
Reference in New Issue
Block a user
Delete Branch "phase-6-3-stream-c-state-publisher"
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?
Wires Stream B pure-logic + Stream A topology loader into one orchestrator. OPC UA variable-node plumbing deferred to a narrower follow-up.
Summary
PeerReachabilityrecord +PeerReachabilityTrackerthread-safe holder. Probe loops (Stream B.1/B.2 runtime follow-up) write; publisher reads.RedundancyStatePublisher.ComputeAndPublish()reads 6 inputs (role, selfHealthy, peer HTTP/UA, apply-in-progress, recovery, topology-valid, operator maintenance) + calls ServiceLevelCalculator. Edge-triggered events:OnStateChangedon byte change,OnServerUriArrayChangedon topology content change.ServiceLevelSnapshotoutput record — OPC UA ServiceLevel Byte subscribes to OnStateChanged; ServerUriArray String[] subscribes to OnServerUriArrayChanged.Test plan
dotnet test: 1186 passing (was 1178, +8).🤖 Generated with Claude Code
Wires the Phase 6.3 Stream B pure-logic pieces (ServiceLevelCalculator, RecoveryStateManager, ApplyLeaseRegistry) + Stream A topology loader (RedundancyCoordinator) into one orchestrator the runtime + OPC UA node surface consume. The actual OPC UA variable-node plumbing (mapping ServiceLevel Byte + ServerUriArray String[] onto the Opc.Ua.Server stack) is narrower follow-up on top of this — the publisher emits change events the OPC UA layer subscribes to. Server.Redundancy additions: - PeerReachability record + PeerReachabilityTracker — thread-safe per-peer-NodeId holder of the latest (HttpHealthy, UaHealthy) tuple. Probe loops (Stream B.1/B.2 runtime follow-up) write via Update; the publisher reads via Get. PeerReachability.FullyHealthy / Unknown sentinels for the two most-common states. - RedundancyStatePublisher — pure orchestrator, no background timer, no OPC UA stack dep. ComputeAndPublish reads the 6 inputs + calls the calculator: * role (from coordinator.Current.SelfRole) * selfHealthy (caller-supplied Func<bool>) * peerHttpHealthy + peerUaHealthy (aggregate across all peers in coordinator.Current.Peers) * applyInProgress (ApplyLeaseRegistry.IsApplyInProgress) * recoveryDwellMet (RecoveryStateManager.IsDwellMet) * topologyValid (coordinator.IsTopologyValid) * operatorMaintenance (caller-supplied Func<bool>) Before-coordinator-init returns NoData=1 so clients never see an authoritative value from an un-bootstrapped server. OnStateChanged event fires edge-triggered when the byte changes; OnServerUriArrayChanged fires edge-triggered when the topology's self-first peer-sorted URI array content changes. - ServiceLevelSnapshot record — per-tick output with Value + Band + Topology. The OPC UA layer's ServiceLevel Byte node subscribes to OnStateChanged; the ServerUriArray node subscribes to OnServerUriArrayChanged. Tests (8 new RedundancyStatePublisherTests, all pass): - Before-init returns NoData (Value=1, Band=NoData). - Authoritative-Primary when healthy + peer fully reachable. - Isolated-Primary (230) retains authority when peer unreachable — matches decision #154 non-promotion semantics. - Mid-apply band dominates: open lease → Value=200 even with peer healthy. - Self-unhealthy → NoData regardless of other inputs. - OnStateChanged fires only on value transitions (edge-triggered). - OnServerUriArrayChanged fires once per topology content change; repeat ticks with same topology don't re-emit. - Standalone cluster treats healthy as AuthoritativePrimary=255. Microsoft.EntityFrameworkCore.InMemory 10.0.0 added to Server.Tests for the coordinator-backed publisher tests. Full solution dotnet test: 1186 passing (was 1178, +8). Pre-existing Client.CLI Subscribe flake unchanged. Closes the core of release blocker #3 — the pure-logic + orchestration layer now exists + is unit-tested. Remaining Stream C surfaces: OPC UA ServiceLevel Byte variable wiring (binds to OnStateChanged), ServerUriArray String[] wiring (binds to OnServerUriArrayChanged), RedundancySupport static from RedundancyMode. Those touch the OPC UA stack directly + land as Stream C.2 follow-up. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>