using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
namespace ZB.MOM.WW.OtOpcUa.Commons.Messages.Drivers;
///
/// Snapshot of a single driver instance's health, published to the
/// driver-health DistributedPubSub topic whenever a driver actor
/// transitions state or logs an error. Consumed by
/// DriverStatusSignalRBridge in the AdminUI for live status push.
///
/// Cluster this driver belongs to.
/// Globally-unique driver instance ID.
/// DriverState enum as string (Healthy, Faulted, Reconnecting, etc.).
/// Most recent successful equipment read; null if never.
/// Latest error message; null when none.
/// Number of state-transitions into Faulted in the last 5 minutes.
/// Timestamp this snapshot was published.
///
/// When the driver last raised IRediscoverable.OnRediscoveryNeeded — i.e. it observed that the
/// remote's tag set may have changed (a Galaxy redeploy, a TwinCAT symbol-version bump, an MTConnect
/// agent restart, a Sparkplug rebirth). Null when the driver has never raised it, or does not
/// implement IRediscoverable.
/// Advisory only. The served address space is NOT rebuilt in response — raw tags are
/// authored explicitly through the /raw browse-commit flow. This is a prompt for an operator
/// to re-browse the device and commit whatever changed.
///
///
/// The driver-supplied reason string from the same event (e.g. "deploy-time-changed"), shown
/// to the operator alongside the prompt. Null when is null.
///
///
/// Per-host connectivity as reported by IHostConnectivityProbe.GetHostStatuses(), or null when
/// the driver does not implement that capability. Empty (not null) when it does but knows no hosts yet.
/// Why this rides the health snapshot rather than a table. The DriverHostStatus
/// entity used to own this, with a doc-comment describing a publisher hosted service that upserted rows
/// from each driver node. That publisher was never built, and since per-cluster mesh Phase 4 it is
/// unbuildable as described: Program.cs gates AddOtOpcUaConfigDb on the admin role,
/// so a driver-only node has no ConfigDb connection to write rows to. This channel already reaches
/// /hosts, already survives the mesh split via the Phase 5 gRPC telemetry stream, and already
/// replays a last-value snapshot on every (re)subscribe — so per-host state re-primes after a
/// reconnect without a durable store. The table was dropped.
/// Value-equality caveat. A record's generated Equals compares this list by
/// REFERENCE, so two DriverHealthChanged carrying equal-but-distinct lists are not equal.
/// Nothing dedups on record equality — DriverInstanceActor keeps its own flattened fingerprint
/// precisely because of this — but do not introduce such a comparison without fixing it here first.
///
///
/// What this driver skipped when it last built its tag table, from
/// ISkippedTagReporter.GetSkippedTags(); null when the driver does not implement that
/// capability. All twelve production drivers do.
/// Why this is on the health snapshot. A skip happens at init, is not an
/// error, and does not stop the driver's OTHER tags from reading — so State stays Healthy,
/// ErrorCount5Min stays 0, LastError stays null and LastSuccessfulReadUtc keeps
/// advancing while the driver ignores part or all of its configuration. On the Makino
/// that combination held while 20 of 22 tags were ignored. This field is what contradicts it.
/// Additive, never a state change. See ISkippedTagReporter for why a skipped tag
/// must not latch the driver non-Healthy — it would satisfy both halves of the comms-loss
/// conjunction and reconnect-loop a healthy device.
/// Value-equality caveat — the same one carries: the
/// record's generated Equals compares the sample list by REFERENCE.
///
///
/// Operator-facing driver diagnostics from IDriverDiagnostics.GetDiagnostics() (e.g. S7's
/// negotiated PDU size), bounded and in ordinal key order; null when the driver does not implement the
/// capability. Additive, never a state change. Same by-reference value-equality caveat as
/// .
///
public sealed record DriverHealthChanged(
string ClusterId,
string DriverInstanceId,
string State,
DateTime? LastSuccessfulReadUtc,
string? LastError,
int ErrorCount5Min,
DateTime PublishedUtc,
DateTime? RediscoveryNeededUtc = null,
string? RediscoveryReason = null,
IReadOnlyList? HostStatuses = null,
SkippedTagReport? SkippedTags = null,
IReadOnlyDictionary? Diagnostics = null)
{
///
/// DPS topic name. Both the runtime AkkaDriverHealthPublisher and the AdminUI
/// DriverStatusSignalRBridge reference this single constant so renames can't
/// silently desynchronise publisher and subscriber.
///
public const string TopicName = "driver-health";
}