refactor(config): drop the dead DriverInstanceResilienceStatus table (#524)

The sibling of #521's DriverHostStatus, dead for exactly the same reason. Before this change the
only reference in the entire repo was the DriverInstanceResilienceStatuses DbSet declaration: no
writer, no reader, and the table empty on every deployment.

Its entity doc described two components that were never built — a HostedService sampling the
tracker every ~5 s into this table, and an AdminUI that left-joined it on DriverHostStatus for
display. There is no sampler, that join exists in no razor file, and DriverHostStatus itself was
removed in #521.

Per-cluster mesh Phase 4 also makes the design unbuildable now: AddOtOpcUaConfigDb is gated on the
admin role, so a driver-only node has no ConfigDb connection to write rows with. Any per-node
runtime-status table has this problem.

The live data was never missing — it just does not go through SQL. Resilience counters reach the
AdminUI's DriverStatusPanel as DriverResilienceStatusChanged over the driver-resilience-status
telemetry channel into the in-memory IDriverResilienceStatusStore. Verified consumed: the panel
injects the store, fed by both DriverResilienceStatusBridge (DPS) and TelemetryDialSupervisor
(gRPC).

If breaker HISTORY across a central restart is ever wanted — the one thing the last-value-only
store cannot give — it needs a CENTRAL-side writer fed by that stream, never a driver-node writer.
Keeping an empty table now bought nothing toward that.

Migration DropDriverInstanceResilienceStatusTable; the scaffolder's data-loss warning cannot bite
on a table nothing ever inserted into, and Down() recreates it exactly.

Also corrected: DriverResilienceStatusTracker's doc-comment claimed CurrentInFlight fed
DriverInstanceResilienceStatus.CurrentBulkheadDepth "until the full telemetry observer lands". The
observer landed in Phase 5; the table never had a writer.

Claude-Session: https://claude.ai/code/session_015p7wGqy3YpZNCpDzTpGMKo
This commit is contained in:
Joseph Doherty
2026-07-31 03:37:10 -04:00
parent 820abd1fa3
commit d979ac69eb
7 changed files with 1632 additions and 120 deletions
@@ -1,57 +0,0 @@
namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
/// <summary>
/// Runtime resilience counters per <c>(DriverInstanceId, HostName)</c> pair.
/// <para><b>⚠️ This table is DEAD: nothing writes it and nothing reads it.</b> The only reference in
/// the repo is the <c>DriverInstanceResilienceStatuses</c> DbSet declaration. Do not treat a query
/// against it as a source of runtime state — it returns empty on every deployment.</para>
/// </summary>
/// <remarks>
/// <para>
/// The original design (<c>docs/v2/implementation/phase-6-1-resilience-and-observability.md</c>
/// §Stream E.1) called for a HostedService sampling the tracker every ~5 s into this table, and
/// an Admin UI that left-joined it on <c>DriverHostStatus</c> for display. Neither was built:
/// there is no sampler, the join exists in no razor file, and <c>DriverHostStatus</c> itself was
/// removed in Gitea #521.
/// </para>
/// <para>
/// The live data does exist — it just never goes through SQL. Resilience state reaches the
/// AdminUI as <c>DriverResilienceStatusChanged</c> over the Phase 5 telemetry stream into the
/// in-memory <c>IDriverResilienceStatusStore</c>, the same shape #521 adopted for per-host
/// connectivity, and for the same reason: per-cluster mesh Phase 4 leaves a driver-only node with
/// no ConfigDb connection to write rows with. Keep-or-delete is tracked as Gitea #524.
/// </para>
/// </remarks>
public sealed class DriverInstanceResilienceStatus
{
/// <summary>Gets or sets the driver instance identifier.</summary>
public required string DriverInstanceId { get; set; }
/// <summary>Gets or sets the host name.</summary>
public required string HostName { get; set; }
/// <summary>Most recent time the circuit breaker for this (instance, host) opened; null if never.</summary>
public DateTime? LastCircuitBreakerOpenUtc { get; set; }
/// <summary>Rolling count of consecutive Polly pipeline failures for this (instance, host).</summary>
public int ConsecutiveFailures { get; set; }
/// <summary>In-flight capability-call depth for this (instance, host). Formerly fed a Polly
/// concurrency-limiter strategy that was removed (R2-02/01/U-6); the column name is retained to avoid a
/// migration on a reader-less entity — it now carries the tracker's in-flight-call count.</summary>
public int CurrentBulkheadDepth { get; set; }
/// <summary>Most recent process recycle time (Tier C only; null for in-process tiers).</summary>
public DateTime? LastRecycleUtc { get; set; }
/// <summary>
/// Post-init memory baseline captured by <c>MemoryTracking</c> (median of first
/// BaselineWindow samples). Zero while still warming up.
/// </summary>
public long BaselineFootprintBytes { get; set; }
/// <summary>Most recent footprint sample the tracker saw (steady-state read).</summary>
public long CurrentFootprintBytes { get; set; }
/// <summary>Row last-write timestamp — advances on every sampling tick.</summary>
public DateTime LastSampledUtc { get; set; }
}
@@ -0,0 +1,55 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
{
/// <summary>
/// Drops the DriverInstanceResilienceStatus table (Gitea #524) — the sibling of #521's
/// DriverHostStatus, dead for the same reason. No code ever inserted a row: the HostedService
/// sampler its entity doc described was never written, and per-cluster mesh Phase 4 made it
/// unbuildable as designed, since a driver-only node has no ConfigDb connection. Every
/// deployment's copy of this table is empty, so the scaffolder's data-loss warning cannot bite.
/// Live breaker / in-flight counters reach the AdminUI's driver status panel as
/// DriverResilienceStatusChanged over the driver-resilience-status telemetry channel.
/// Down() recreates the table exactly, so the rollback is lossless too.
/// </summary>
public partial class DropDriverInstanceResilienceStatusTable : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "DriverInstanceResilienceStatus");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "DriverInstanceResilienceStatus",
columns: table => new
{
DriverInstanceId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
HostName = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
BaselineFootprintBytes = table.Column<long>(type: "bigint", nullable: false),
ConsecutiveFailures = table.Column<int>(type: "int", nullable: false),
CurrentBulkheadDepth = table.Column<int>(type: "int", nullable: false),
CurrentFootprintBytes = table.Column<long>(type: "bigint", nullable: false),
LastCircuitBreakerOpenUtc = table.Column<DateTime>(type: "datetime2(3)", nullable: true),
LastRecycleUtc = table.Column<DateTime>(type: "datetime2(3)", nullable: true),
LastSampledUtc = table.Column<DateTime>(type: "datetime2(3)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_DriverInstanceResilienceStatus", x => new { x.DriverInstanceId, x.HostName });
});
migrationBuilder.CreateIndex(
name: "IX_DriverResilience_LastSampled",
table: "DriverInstanceResilienceStatus",
column: "LastSampledUtc");
}
}
}
@@ -471,45 +471,6 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
});
});
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.DriverInstanceResilienceStatus", b =>
{
b.Property<string>("DriverInstanceId")
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<string>("HostName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<long>("BaselineFootprintBytes")
.HasColumnType("bigint");
b.Property<int>("ConsecutiveFailures")
.HasColumnType("int");
b.Property<int>("CurrentBulkheadDepth")
.HasColumnType("int");
b.Property<long>("CurrentFootprintBytes")
.HasColumnType("bigint");
b.Property<DateTime?>("LastCircuitBreakerOpenUtc")
.HasColumnType("datetime2(3)");
b.Property<DateTime?>("LastRecycleUtc")
.HasColumnType("datetime2(3)");
b.Property<DateTime>("LastSampledUtc")
.HasColumnType("datetime2(3)");
b.HasKey("DriverInstanceId", "HostName");
b.HasIndex("LastSampledUtc")
.HasDatabaseName("IX_DriverResilience_LastSampled");
b.ToTable("DriverInstanceResilienceStatus", (string)null);
});
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.Equipment", b =>
{
b.Property<Guid>("EquipmentRowId")
@@ -44,8 +44,6 @@ public sealed class OtOpcUaConfigDbContext(DbContextOptions<OtOpcUaConfigDbConte
public DbSet<ConfigAuditLog> ConfigAuditLogs => Set<ConfigAuditLog>();
/// <summary>Gets the DbSet of external ID reservations.</summary>
public DbSet<ExternalIdReservation> ExternalIdReservations => Set<ExternalIdReservation>();
/// <summary>Gets the DbSet of driver instance resilience statuses.</summary>
public DbSet<DriverInstanceResilienceStatus> DriverInstanceResilienceStatuses => Set<DriverInstanceResilienceStatus>();
/// <summary>Gets the DbSet of LDAP group role mappings.</summary>
public DbSet<LdapGroupRoleMapping> LdapGroupRoleMappings => Set<LdapGroupRoleMapping>();
/// <summary>Gets the DbSet of scripts.</summary>
@@ -88,7 +86,6 @@ public sealed class OtOpcUaConfigDbContext(DbContextOptions<OtOpcUaConfigDbConte
ConfigureNodeAcl(modelBuilder);
ConfigureConfigAuditLog(modelBuilder);
ConfigureExternalIdReservation(modelBuilder);
ConfigureDriverInstanceResilienceStatus(modelBuilder);
ConfigureLdapGroupRoleMapping(modelBuilder);
ConfigureScript(modelBuilder);
ConfigureVirtualTag(modelBuilder);
@@ -545,23 +542,14 @@ public sealed class OtOpcUaConfigDbContext(DbContextOptions<OtOpcUaConfigDbConte
// is registered only on the admin role, so a driver-only node has no connection to write rows with.
// Per-host connectivity now rides DriverHealthChanged.HostStatuses to /hosts over the Phase 5
// telemetry stream, which needs no DB and survives the mesh split.
private static void ConfigureDriverInstanceResilienceStatus(ModelBuilder modelBuilder)
{
modelBuilder.Entity<DriverInstanceResilienceStatus>(e =>
{
e.ToTable("DriverInstanceResilienceStatus");
e.HasKey(x => new { x.DriverInstanceId, x.HostName });
e.Property(x => x.DriverInstanceId).HasMaxLength(64);
e.Property(x => x.HostName).HasMaxLength(256);
e.Property(x => x.LastCircuitBreakerOpenUtc).HasColumnType("datetime2(3)");
e.Property(x => x.LastRecycleUtc).HasColumnType("datetime2(3)");
e.Property(x => x.LastSampledUtc).HasColumnType("datetime2(3)");
// LastSampledUtc drives the Admin UI's stale-sample filter same way DriverHostStatus's
// LastSeenUtc index does for connectivity rows.
e.HasIndex(x => x.LastSampledUtc).HasDatabaseName("IX_DriverResilience_LastSampled");
});
}
//
// ConfigureDriverInstanceResilienceStatus is GONE for the same reason (Gitea #524). That table held
// per-(instance, host) breaker/in-flight counters and was equally never written: its doc described a
// HostedService sampler and an AdminUI left-join on DriverHostStatus, neither of which was built. The
// live data reaches the AdminUI's DriverStatusPanel as DriverResilienceStatusChanged over the
// driver-resilience-status telemetry channel into the in-memory IDriverResilienceStatusStore.
// If breaker HISTORY across a central restart is ever wanted, it needs a CENTRAL-side writer fed by
// that stream — never a driver-node writer, which Phase 4 rules out.
private static void ConfigureLdapGroupRoleMapping(ModelBuilder modelBuilder)
{
@@ -192,9 +192,12 @@ public sealed record ResilienceStatusSnapshot
/// <summary>
/// In-flight capability calls against this (instance, host). Bumped on call entry +
/// decremented on completion. Feeds <c>DriverInstanceResilienceStatus.CurrentBulkheadDepth</c>
/// for Admin <c>/hosts</c> — in-flight capability-call depth until the full
/// telemetry observer lands.
/// decremented on completion. Surfaced to the AdminUI's driver status panel as
/// <c>DriverResilienceStatusChanged.CurrentInFlight</c> over the
/// <c>driver-resilience-status</c> telemetry channel.
/// <para>It used to say this fed <c>DriverInstanceResilienceStatus.CurrentBulkheadDepth</c> "until
/// the full telemetry observer lands". The telemetry observer landed (Phase 5); that table never
/// had a writer and was dropped in Gitea #524.</para>
/// </summary>
public int CurrentInFlight { get; init; }
}
@@ -36,7 +36,9 @@ public sealed class SchemaComplianceTests
// DriverHostStatus was dropped in Gitea #521 — nothing ever wrote a row, and per-cluster
// mesh Phase 4 made the publisher its entity doc described unbuildable (a driver-only node
// has no ConfigDb connection). Per-host connectivity now rides DriverHealthChanged to /hosts.
"DriverInstanceResilienceStatus",
// DriverInstanceResilienceStatus dropped in Gitea #524 — same defect as DriverHostStatus
// above: no writer, no reader, and Phase 4 leaves a driver-only node with no ConfigDb to
// write from. Live resilience counters ride the driver-resilience-status telemetry channel.
"LdapGroupRoleMapping",
// v3 dropped the orphaned EquipmentImportBatch / EquipmentImportRow tables.
// ScriptedAlarmState was dropped in per-cluster mesh Phase 4 (Task 9) — scripted-alarm