Merge rows 80+73+71: nullable LocalDb health fields on unreplicated sites (Host supplies ReplicationIsConfigured), StartupReconcilerActive on the health report (proto field 29) with central No-self-heal badge, standby active-node ERR downgraded via narrowly-keyed Serilog filter

# Conflicts:
#	CLAUDE.md
This commit is contained in:
Joseph Doherty
2026-08-16 04:17:40 -04:00
22 changed files with 1208 additions and 69 deletions
@@ -103,12 +103,23 @@ public record SiteHealthReport(
/// or <see langword="null"/> when replication is not wired on this node.
/// </summary>
/// <remarks>
/// <para>
/// Nullable on purpose, and <see langword="false"/> is NOT the same as null.
/// Replication ships default-OFF, so a site node with no peer configured reports
/// <see langword="false"/> — that is a healthy, expected state, not an outage. Only
/// a node that was configured with a peer and is reporting <see langword="false"/>
/// is degraded, and distinguishing those two cases is the operator's job, not this
/// field's.
/// <b>Null means "no reading"</b> — replication is not configured on this node
/// (the default-OFF posture), or the reporter has not run yet.
/// <b><see langword="false"/> means "configured, and the peer session is down"</b>,
/// which is a genuine fault worth investigating.
/// </para>
/// <para>
/// The site side must never publish <see langword="false"/> for an unconfigured node
/// (deferred-work register row 80): the replication engine is registered
/// unconditionally and answers <c>Connected=false</c> while inert, so passing that
/// through made every deliberately-unreplicated site look like a broken replicated
/// one, and starved central's null branches (conditional KPI emission,
/// sustained-badge anchor exclusion) of the null they were built for.
/// <c>LocalDbReplicationStatusReporter</c> gates on the Host-supplied
/// <c>SiteLocalDbSetup.ReplicationIsConfigured</c> to keep that distinction honest.
/// </para>
/// </remarks>
public bool? LocalDbReplicationConnected { get; init; }
@@ -134,6 +145,34 @@ public record SiteHealthReport(
/// 0 so existing producers/tests that don't populate it stay valid.
/// </summary>
public long SiteStreamAlarmDropCount { get; init; }
/// <summary>
/// Whether this node spawned its <c>SiteReconciliationActor</c> at boot — the per-node
/// startup self-heal that lets a standby which was DOWN during a deploy recover its
/// deployed inventory (and, since register row 67, restart the Instance Actors a
/// boot-time load missed).
/// </summary>
/// <remarks>
/// <para>
/// Tri-state, and the distinction is the whole point (deferred-work register row 73).
/// <b>Null = not reported</b>: a central self-report, where the notion does not apply,
/// or a site node running a pre-row-73 build. <b><see langword="true"/></b> = the
/// reconciler is running. <b><see langword="false"/></b> = the node booted WITHOUT it,
/// because no <c>IDeploymentConfigFetcher</c> was resolvable.
/// </para>
/// <para>
/// That false case used to be invisible: <c>AkkaHostedService</c> logged a single boot
/// Warning and spawned nothing, so a misconfigured site reported itself perfectly
/// healthy forever while being unable to self-heal. Central badges it rather than
/// failing the site — replication remains the primary path, so the node is degraded,
/// not down.
/// </para>
/// <para>
/// Stamped once at boot by the Host (which owns the spawn decision) via
/// <c>ISiteHealthCollector.SetStartupReconcilerActive</c>; it is not polled.
/// </para>
/// </remarks>
public bool? StartupReconcilerActive { get; init; }
}
/// <summary>