using System.Collections.Concurrent;
using ZB.MOM.WW.ScadaBridge.AuditLog.Payload;
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Central;
///
/// Audit Log (#23) M6 Bundle E (T8, T9) — central singleton implementation of
/// . Owns thread-safe
/// counters for
/// CentralAuditWriteFailures + AuditRedactionFailure and a
/// per-site latched stalled-state map fed by the
/// . Also implements the
/// writer surfaces ( +
/// ) so a single concrete object
/// is the source of truth — DI binds those two interfaces to this same
/// singleton instance on the central composition root.
///
///
///
/// Why one type for read + write. The writer interfaces are tiny
/// (Increment()) and the read surface needs visibility of those
/// counters anyway — having a single class own both means the
/// Interlocked field IS the snapshot value, no extra plumbing needed.
/// Mirrors the
/// pattern where
/// the collector both receives and exposes the metric.
///
///
/// Stalled-state plumbing. The per-site stalled latch lives directly
/// on this snapshot. is the
/// EventStream subscriber that pushes
/// publications in via
/// . Keeping the dictionary on this type (rather
/// than reading the tracker on every access) lets the snapshot be constructed
/// without an dependency — the tracker
/// is wired up later from the Akka bootstrap, once the system is built.
///
///
public sealed class AuditCentralHealthSnapshot
: IAuditCentralHealthSnapshot,
ICentralAuditWriteFailureCounter,
IAuditRedactionFailureCounter,
IAuditInboundCeilingHitsCounter
{
private int _centralAuditWriteFailures;
private int _auditRedactionFailure;
private int _auditInboundCeilingHits;
private readonly ConcurrentDictionary _stalled = new();
///
public int CentralAuditWriteFailures =>
Interlocked.CompareExchange(ref _centralAuditWriteFailures, 0, 0);
///
public int AuditRedactionFailure =>
Interlocked.CompareExchange(ref _auditRedactionFailure, 0, 0);
///
public int AuditInboundCeilingHits =>
Interlocked.CompareExchange(ref _auditInboundCeilingHits, 0, 0);
///
public IReadOnlyDictionary SiteAuditTelemetryStalled =>
new Dictionary(_stalled);
///
/// Apply a publication
/// observed by . Public
/// so the tracker (which lives in the same assembly but is constructed
/// later from the Akka host) can push without a friend reference;
/// readers should call .
///
/// The event carrying the site ID and new stalled state.
public void ApplyStalled(SiteAuditTelemetryStalledChanged evt)
{
if (evt is null) return;
_stalled[evt.SiteId] = evt.Stalled;
}
///
void ICentralAuditWriteFailureCounter.Increment() =>
Interlocked.Increment(ref _centralAuditWriteFailures);
///
void IAuditRedactionFailureCounter.Increment() =>
Interlocked.Increment(ref _auditRedactionFailure);
///
void IAuditInboundCeilingHitsCounter.Increment() =>
Interlocked.Increment(ref _auditInboundCeilingHits);
}