using System.Threading.Channels; using ZB.MOM.WW.OtOpcUa.Commons.Messages.Alerts; using ZB.MOM.WW.OtOpcUa.Commons.Messages.Drivers; using ZB.MOM.WW.OtOpcUa.Commons.Messages.Logging; namespace ZB.MOM.WW.OtOpcUa.Runtime.Telemetry; /// /// A single live-telemetry item flowing through the node-local hub. A closed union over the four /// wire-facing telemetry records — carried as the domain records themselves, NOT as proto (the /// gRPC projection happens at the streaming-service seam, one layer out). /// /// /// Two of the four are snapshot-style (a last-value-per-key view: keyed /// by DriverInstanceId, keyed by (DriverInstanceId, HostName)) /// and are cached so a newly-attaching subscriber is primed immediately. The other two /// (, ) are append-style logs and are forwarded live-only. /// public abstract record TelemetryItem { private TelemetryItem() { } /// An append-style alarm transition. Live-forwarded only; never cached/replayed. public sealed record Alarm(AlarmTransitionEvent E) : TelemetryItem; /// An append-style script-log line. Live-forwarded only; never cached/replayed. public sealed record Script(ScriptLogEntry E) : TelemetryItem; /// A snapshot-style driver-health change, cached last-value per DriverInstanceId. public sealed record Health(DriverHealthChanged E) : TelemetryItem; /// A snapshot-style resilience-status change, cached last-value per (DriverInstanceId, HostName). public sealed record Resilience(DriverResilienceStatusChanged E) : TelemetryItem; } /// /// One attached reader on the . Disposing detaches it from the hub /// and completes its channel; the reader first yields the cached snapshots, then live deltas. /// public interface ITelemetrySubscription : IDisposable { /// The bounded reader this subscription drains. Completed on . ChannelReader Reader { get; } } /// /// Process-wide, node-local fan-out hub sitting between this node's own telemetry producers and the /// (Phase-5) gRPC streaming service central dials into. /// /// /// /// CRITICAL INVARIANT — this hub carries ONLY this node's own telemetry. It is fed /// directly, in-process, by the node's producers and MUST NEVER subscribe to cluster /// DistributedPubSub. On the single mesh, DPS delivers every node's events to every node; if the /// hub read DPS it would stream peers' events and central — which dials each node individually — /// would double-count. /// /// /// Fan-out is lossy under backpressure by design: each subscriber owns a bounded channel with /// , so a slow consumer sheds its own oldest items /// and can never block the producer / node-actor threads. /// /// public interface ITelemetryLocalHub { /// /// Fans to every currently-attached subscriber (non-blocking, drop-oldest /// on a full channel) and, for the two snapshot-style items, updates the last-value cache that /// primes future subscribers. /// void Emit(TelemetryItem item); /// /// Attaches a new subscriber. Its reader first yields the cached snapshots (all cached /// , then all cached ), /// then live deltas — with no delta lost across the attach boundary. Dispose to detach. /// /// /// Per-subscriber channel capacity (must be positive). The snapshot prelude is written into this /// same bounded/DropOldest channel before returns, so /// should comfortably exceed this node's live driver-instance + /// (instance, host) count — otherwise the earliest cached snapshots are silently evicted during /// priming before the reader ever drains them. The node-local cache holds only THIS node's /// instances (a handful), so a normal large capacity leaves ample headroom. /// ITelemetrySubscription Subscribe(int boundedCapacity); }