diff --git a/deferment.md b/deferment.md
index 9c765c7c..ee539e06 100644
--- a/deferment.md
+++ b/deferment.md
@@ -228,9 +228,12 @@ highest-risk doc error found.
> The table is dropped (`DropDriverHostStatusTable`). Live-gated on docker-dev, **including the cross-mesh
> gRPC path** — site-a first showed `—`, which is also what a broken mapper produces, so it was rebuilt to
> prove the `has_host_statuses` presence flag round-trips rather than assuming version skew.
-> ⏳ **A third table, same defect, found en route:** `DriverInstanceResilienceStatus` also has no writer and
-> no reader — only its DbSet declaration — while the live data rides the `driver-resilience-status` channel.
-> Documented in place, filed as Gitea **#524** rather than widening the schema change beyond the ask.
+> ✅ **A third table, same defect, found en route — now also RESOLVED** (`d979ac69`, Gitea **#524**):
+> `DriverInstanceResilienceStatus` had no writer and no reader either — only its DbSet declaration — while
+> the live data rides the `driver-resilience-status` telemetry channel into `IDriverResilienceStatusStore`
+> (verified consumed: `DriverStatusPanel.razor` injects it, fed by both the DPS bridge and the gRPC dialer).
+> Dropped by migration `DropDriverInstanceResilienceStatusTable`. Filed separately rather than folded into
+> #521 so a second schema change was an explicit decision, not a side effect.
Gitea #518/#507. Nine drivers raise; nothing consumes. `DriverHostStatus` table exists
(`OtOpcUaConfigDbContext.cs:48`, migration `20260715230637_V3Initial.cs:108`) with a doc-comment
@@ -520,6 +523,26 @@ also `--no-ff`) carrying the items §8 did not cover.
| **#521** `IHostConnectivityProbe` | ✅ **Done — resolved differently from how it was filed.** Neither "build the publisher" nor "delete": the publisher as designed was **unbuildable** after Phase 4, so the capability was kept and the transport changed to the Phase 5 telemetry stream. Table dropped. | `30d0697c` |
| **#522** driver stability tiers | ✅ **Done — deleted.** The inertness was broader than either the issue or this register said: nothing constructed `MemoryTracking` either, so "pass real tiers" was never a flag flip. | `cbb88229` |
| Live gate on docker-dev | ✅ **Passed**, one fix (`1 hosts` → `1 host`) | `ce589569` |
+| **#524** `DriverInstanceResilienceStatus` | ✅ **Done — deleted.** Sibling of #521, dead for the same Phase-4 reason. | `d979ac69` |
+| **#525** `IDriver` memory members | ⏳ **OPEN — recommendation withdrawn mid-execution.** See below. | — |
+
+⚠️ **#525 is a worked example of the register's own recurring failure mode, committed by me.** I recommended
+deleting `IDriver.GetMemoryFootprint`/`FlushOptionalCachesAsync` on the stated evidence that *"1 of 12
+actually frees memory"*. That number came from grepping for **two specific cache-clear calls** and treating
+their absence elsewhere as proof of absence. The real inventory: **8 of 12 flushes are no-ops, 3 free real
+memory** (AbCip's UDT template cache, Calculation's compiled scripts, **MTConnect's cached probe model** —
+which is a *designed* path with compare-and-swap discipline and tests asserting the footprint drops), and 1
+is cosmetic. The sweep had already edited 12 drivers, `IDriver` and ~15 test files; it was **reverted**
+rather than completed on a justification that no longer held.
+
+What survives of the argument is narrower: `GetMemoryFootprint` returns `0` in 7 of 12, its computed
+constants were invented and never validated against real memory because nothing ever consumed them, and
+**the two halves are coherent in exactly one driver** (MTConnect) — AbCip and Calculation free real memory
+while reporting footprint `0`; TwinCAT, Galaxy and Mqtt report footprints behind no-op flushes.
+
+**The lesson, which is the same one §6 already records about this file:** a grep for the shapes you expect
+is not an inventory. Enumerate the whole set and read each member before drawing a count — especially when
+the count is the thing your recommendation rests on.
**Both closures produced a residual that was filed rather than folded in** — a second dead table
(#524 `DriverInstanceResilienceStatus`) and two now-consumerless `IDriver` members (#525). Neither was
diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/DriverInstanceResilienceStatus.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/DriverInstanceResilienceStatus.cs
deleted file mode 100644
index 6e93569d..00000000
--- a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/DriverInstanceResilienceStatus.cs
+++ /dev/null
@@ -1,57 +0,0 @@
-namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
-
-///
-/// Runtime resilience counters per (DriverInstanceId, HostName) pair.
-/// ⚠️ This table is DEAD: nothing writes it and nothing reads it. The only reference in
-/// the repo is the DriverInstanceResilienceStatuses DbSet declaration. Do not treat a query
-/// against it as a source of runtime state — it returns empty on every deployment.
-///
-///
-///
-/// The original design (docs/v2/implementation/phase-6-1-resilience-and-observability.md
-/// §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 DriverHostStatus for display. Neither was built:
-/// there is no sampler, the join exists in no razor file, and DriverHostStatus itself was
-/// removed in Gitea #521.
-///
-///
-/// The live data does exist — it just never goes through SQL. Resilience state reaches the
-/// AdminUI as DriverResilienceStatusChanged over the Phase 5 telemetry stream into the
-/// in-memory IDriverResilienceStatusStore, 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.
-///
-///
-public sealed class DriverInstanceResilienceStatus
-{
- /// Gets or sets the driver instance identifier.
- public required string DriverInstanceId { get; set; }
- /// Gets or sets the host name.
- public required string HostName { get; set; }
-
- /// Most recent time the circuit breaker for this (instance, host) opened; null if never.
- public DateTime? LastCircuitBreakerOpenUtc { get; set; }
-
- /// Rolling count of consecutive Polly pipeline failures for this (instance, host).
- public int ConsecutiveFailures { get; set; }
-
- /// 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.
- public int CurrentBulkheadDepth { get; set; }
-
- /// Most recent process recycle time (Tier C only; null for in-process tiers).
- public DateTime? LastRecycleUtc { get; set; }
-
- ///
- /// Post-init memory baseline captured by MemoryTracking (median of first
- /// BaselineWindow samples). Zero while still warming up.
- ///
- public long BaselineFootprintBytes { get; set; }
-
- /// Most recent footprint sample the tracker saw (steady-state read).
- public long CurrentFootprintBytes { get; set; }
-
- /// Row last-write timestamp — advances on every sampling tick.
- public DateTime LastSampledUtc { get; set; }
-}
diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Migrations/20260731073334_DropDriverInstanceResilienceStatusTable.Designer.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Migrations/20260731073334_DropDriverInstanceResilienceStatusTable.Designer.cs
new file mode 100644
index 00000000..f43dd554
--- /dev/null
+++ b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Migrations/20260731073334_DropDriverInstanceResilienceStatusTable.Designer.cs
@@ -0,0 +1,1560 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using ZB.MOM.WW.OtOpcUa.Configuration;
+
+#nullable disable
+
+namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
+{
+ [DbContext(typeof(OtOpcUaConfigDbContext))]
+ [Migration("20260731073334_DropDriverInstanceResilienceStatusTable")]
+ partial class DropDriverInstanceResilienceStatusTable
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "10.0.7")
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
+
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
+
+ modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("FriendlyName")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Xml")
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.ToTable("DataProtectionKeys", (string)null);
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.ClusterNode", b =>
+ {
+ b.Property("NodeId")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("AkkaPort")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int")
+ .HasDefaultValue(4053);
+
+ b.Property("ApplicationUri")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("ClusterId")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("CreatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("datetime2(3)")
+ .HasDefaultValueSql("SYSUTCDATETIME()");
+
+ b.Property("CreatedBy")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("nvarchar(128)");
+
+ b.Property("DashboardPort")
+ .HasColumnType("int");
+
+ b.Property("DriverConfigOverridesJson")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Enabled")
+ .HasColumnType("bit");
+
+ b.Property("GrpcPort")
+ .HasColumnType("int");
+
+ b.Property("Host")
+ .IsRequired()
+ .HasMaxLength(255)
+ .HasColumnType("nvarchar(255)");
+
+ b.Property("LastSeenAt")
+ .HasColumnType("datetime2(3)");
+
+ b.Property("MaintenanceMode")
+ .HasColumnType("bit");
+
+ b.Property("OpcUaPort")
+ .HasColumnType("int");
+
+ b.Property("ServiceLevelBase")
+ .HasColumnType("tinyint");
+
+ b.HasKey("NodeId");
+
+ b.HasIndex("ApplicationUri")
+ .IsUnique()
+ .HasDatabaseName("UX_ClusterNode_ApplicationUri");
+
+ b.HasIndex("ClusterId")
+ .HasDatabaseName("IX_ClusterNode_ClusterId");
+
+ b.ToTable("ClusterNode", (string)null);
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.ClusterNodeCredential", b =>
+ {
+ b.Property("CredentialId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier")
+ .HasDefaultValueSql("NEWSEQUENTIALID()");
+
+ b.Property("CreatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("datetime2(3)")
+ .HasDefaultValueSql("SYSUTCDATETIME()");
+
+ b.Property("CreatedBy")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("nvarchar(128)");
+
+ b.Property("Enabled")
+ .HasColumnType("bit");
+
+ b.Property("Kind")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("nvarchar(32)");
+
+ b.Property("NodeId")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("RotatedAt")
+ .HasColumnType("datetime2(3)");
+
+ b.Property("Value")
+ .IsRequired()
+ .HasMaxLength(512)
+ .HasColumnType("nvarchar(512)");
+
+ b.HasKey("CredentialId");
+
+ b.HasIndex("Kind", "Value")
+ .IsUnique()
+ .HasDatabaseName("UX_ClusterNodeCredential_Value")
+ .HasFilter("[Enabled] = 1");
+
+ b.HasIndex("NodeId", "Enabled")
+ .HasDatabaseName("IX_ClusterNodeCredential_NodeId");
+
+ b.ToTable("ClusterNodeCredential", (string)null);
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.ConfigAuditLog", b =>
+ {
+ b.Property("AuditId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("AuditId"));
+
+ b.Property("ClusterId")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("CorrelationId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("DetailsJson")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("EventId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("EventType")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("GenerationId")
+ .HasColumnType("bigint");
+
+ b.Property("NodeId")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("Outcome")
+ .HasMaxLength(16)
+ .HasColumnType("nvarchar(16)");
+
+ b.Property("Principal")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("nvarchar(128)");
+
+ b.Property("Timestamp")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("datetime2(3)")
+ .HasDefaultValueSql("SYSUTCDATETIME()");
+
+ b.HasKey("AuditId");
+
+ b.HasIndex("EventId")
+ .IsUnique()
+ .HasDatabaseName("UX_ConfigAuditLog_EventId")
+ .HasFilter("[EventId] IS NOT NULL");
+
+ b.HasIndex("GenerationId")
+ .HasDatabaseName("IX_ConfigAuditLog_Generation")
+ .HasFilter("[GenerationId] IS NOT NULL");
+
+ b.HasIndex("ClusterId", "Timestamp")
+ .IsDescending(false, true)
+ .HasDatabaseName("IX_ConfigAuditLog_Cluster_Time");
+
+ b.ToTable("ConfigAuditLog", null, t =>
+ {
+ t.HasCheckConstraint("CK_ConfigAuditLog_DetailsJson_IsJson", "DetailsJson IS NULL OR ISJSON(DetailsJson) = 1");
+ });
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.ConfigEdit", b =>
+ {
+ b.Property("EditId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier")
+ .HasDefaultValueSql("NEWSEQUENTIALID()");
+
+ b.Property("EditedAtUtc")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("datetime2(3)")
+ .HasDefaultValueSql("SYSUTCDATETIME()");
+
+ b.Property("EditedBy")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("nvarchar(128)");
+
+ b.Property("EntityId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("EntityType")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("ExecutionId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("FieldsJson")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("SourceNode")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.HasKey("EditId");
+
+ b.HasIndex("EditedAtUtc")
+ .HasDatabaseName("IX_ConfigEdit_EditedAt");
+
+ b.HasIndex("ExecutionId")
+ .HasDatabaseName("IX_ConfigEdit_Execution")
+ .HasFilter("[ExecutionId] IS NOT NULL");
+
+ b.HasIndex("EntityType", "EntityId")
+ .HasDatabaseName("IX_ConfigEdit_Entity");
+
+ b.ToTable("ConfigEdit", null, t =>
+ {
+ t.HasCheckConstraint("CK_ConfigEdit_FieldsJson_IsJson", "ISJSON(FieldsJson) = 1");
+ });
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.Deployment", b =>
+ {
+ b.Property("DeploymentId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier")
+ .HasDefaultValueSql("NEWSEQUENTIALID()");
+
+ b.Property("ArtifactBlob")
+ .IsRequired()
+ .HasColumnType("varbinary(max)");
+
+ b.Property("CreatedAtUtc")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("datetime2(3)")
+ .HasDefaultValueSql("SYSUTCDATETIME()");
+
+ b.Property("CreatedBy")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("nvarchar(128)");
+
+ b.Property("FailureReason")
+ .HasMaxLength(2048)
+ .HasColumnType("nvarchar(2048)");
+
+ b.Property("RevisionHash")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .IsRequired()
+ .ValueGeneratedOnAddOrUpdate()
+ .HasColumnType("rowversion");
+
+ b.Property("SealedAtUtc")
+ .HasColumnType("datetime2(3)");
+
+ b.Property("Status")
+ .HasColumnType("int");
+
+ b.HasKey("DeploymentId");
+
+ b.HasIndex("CreatedAtUtc")
+ .HasDatabaseName("IX_Deployment_CreatedAt");
+
+ b.HasIndex("Status")
+ .HasDatabaseName("IX_Deployment_Status");
+
+ b.ToTable("Deployment", (string)null);
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.Device", b =>
+ {
+ b.Property("DeviceRowId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier")
+ .HasDefaultValueSql("NEWSEQUENTIALID()");
+
+ b.Property("DeviceConfig")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("DeviceId")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("DriverInstanceId")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("Enabled")
+ .HasColumnType("bit");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("nvarchar(128)");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .IsRequired()
+ .ValueGeneratedOnAddOrUpdate()
+ .HasColumnType("rowversion");
+
+ b.HasKey("DeviceRowId");
+
+ b.HasIndex("DeviceId")
+ .IsUnique()
+ .HasDatabaseName("UX_Device_LogicalId")
+ .HasFilter("[DeviceId] IS NOT NULL");
+
+ b.HasIndex("DriverInstanceId")
+ .HasDatabaseName("IX_Device_Driver");
+
+ b.HasIndex("DriverInstanceId", "Name")
+ .IsUnique()
+ .HasDatabaseName("UX_Device_Driver_Name");
+
+ b.ToTable("Device", null, t =>
+ {
+ t.HasCheckConstraint("CK_Device_DeviceConfig_IsJson", "ISJSON(DeviceConfig) = 1");
+ });
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.DriverInstance", b =>
+ {
+ b.Property("DriverInstanceRowId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier")
+ .HasDefaultValueSql("NEWSEQUENTIALID()");
+
+ b.Property("ClusterId")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("DriverConfig")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("DriverInstanceId")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("DriverType")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("nvarchar(32)");
+
+ b.Property("Enabled")
+ .HasColumnType("bit");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("nvarchar(128)");
+
+ b.Property("RawFolderId")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("ResilienceConfig")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .IsRequired()
+ .ValueGeneratedOnAddOrUpdate()
+ .HasColumnType("rowversion");
+
+ b.HasKey("DriverInstanceRowId");
+
+ b.HasIndex("ClusterId")
+ .HasDatabaseName("IX_DriverInstance_Cluster");
+
+ b.HasIndex("DriverInstanceId")
+ .IsUnique()
+ .HasDatabaseName("UX_DriverInstance_LogicalId")
+ .HasFilter("[DriverInstanceId] IS NOT NULL");
+
+ b.HasIndex("RawFolderId")
+ .HasDatabaseName("IX_DriverInstance_RawFolder");
+
+ b.HasIndex("ClusterId", "Name")
+ .IsUnique()
+ .HasDatabaseName("UX_DriverInstance_ClusterRoot_Name")
+ .HasFilter("[RawFolderId] IS NULL");
+
+ b.HasIndex("ClusterId", "RawFolderId", "Name")
+ .IsUnique()
+ .HasDatabaseName("UX_DriverInstance_Folder_Name")
+ .HasFilter("[RawFolderId] IS NOT NULL");
+
+ b.ToTable("DriverInstance", null, t =>
+ {
+ t.HasCheckConstraint("CK_DriverInstance_DriverConfig_IsJson", "ISJSON(DriverConfig) = 1");
+
+ t.HasCheckConstraint("CK_DriverInstance_ResilienceConfig_IsJson", "ResilienceConfig IS NULL OR ISJSON(ResilienceConfig) = 1");
+ });
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.Equipment", b =>
+ {
+ b.Property("EquipmentRowId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier")
+ .HasDefaultValueSql("NEWSEQUENTIALID()");
+
+ b.Property("AssetLocation")
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("DeviceManualUri")
+ .HasMaxLength(512)
+ .HasColumnType("nvarchar(512)");
+
+ b.Property("Enabled")
+ .HasColumnType("bit");
+
+ b.Property("EquipmentClassRef")
+ .HasMaxLength(128)
+ .HasColumnType("nvarchar(128)");
+
+ b.Property("EquipmentId")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("EquipmentUuid")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("HardwareRevision")
+ .HasMaxLength(32)
+ .HasColumnType("nvarchar(32)");
+
+ b.Property("MachineCode")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("Manufacturer")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("ManufacturerUri")
+ .HasMaxLength(512)
+ .HasColumnType("nvarchar(512)");
+
+ b.Property("Model")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("nvarchar(32)");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .IsRequired()
+ .ValueGeneratedOnAddOrUpdate()
+ .HasColumnType("rowversion");
+
+ b.Property("SAPID")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("SerialNumber")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("SoftwareRevision")
+ .HasMaxLength(32)
+ .HasColumnType("nvarchar(32)");
+
+ b.Property("UnsLineId")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("YearOfConstruction")
+ .HasColumnType("smallint");
+
+ b.Property("ZTag")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.HasKey("EquipmentRowId");
+
+ b.HasIndex("EquipmentId")
+ .IsUnique()
+ .HasDatabaseName("UX_Equipment_LogicalId")
+ .HasFilter("[EquipmentId] IS NOT NULL");
+
+ b.HasIndex("EquipmentUuid")
+ .IsUnique()
+ .HasDatabaseName("UX_Equipment_Uuid");
+
+ b.HasIndex("MachineCode")
+ .HasDatabaseName("IX_Equipment_MachineCode");
+
+ b.HasIndex("SAPID")
+ .HasDatabaseName("IX_Equipment_SAPID")
+ .HasFilter("[SAPID] IS NOT NULL");
+
+ b.HasIndex("UnsLineId")
+ .HasDatabaseName("IX_Equipment_Line");
+
+ b.HasIndex("ZTag")
+ .HasDatabaseName("IX_Equipment_ZTag")
+ .HasFilter("[ZTag] IS NOT NULL");
+
+ b.HasIndex("UnsLineId", "Name")
+ .IsUnique()
+ .HasDatabaseName("UX_Equipment_LinePath");
+
+ b.ToTable("Equipment", (string)null);
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.ExternalIdReservation", b =>
+ {
+ b.Property("ReservationId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier")
+ .HasDefaultValueSql("NEWSEQUENTIALID()");
+
+ b.Property("ClusterId")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("EquipmentUuid")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("FirstPublishedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("datetime2(3)")
+ .HasDefaultValueSql("SYSUTCDATETIME()");
+
+ b.Property("FirstPublishedBy")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("nvarchar(128)");
+
+ b.Property("Kind")
+ .IsRequired()
+ .HasMaxLength(16)
+ .HasColumnType("nvarchar(16)");
+
+ b.Property("LastPublishedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("datetime2(3)")
+ .HasDefaultValueSql("SYSUTCDATETIME()");
+
+ b.Property("ReleaseReason")
+ .HasMaxLength(512)
+ .HasColumnType("nvarchar(512)");
+
+ b.Property("ReleasedAt")
+ .HasColumnType("datetime2(3)");
+
+ b.Property("ReleasedBy")
+ .HasMaxLength(128)
+ .HasColumnType("nvarchar(128)");
+
+ b.Property("Value")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.HasKey("ReservationId");
+
+ b.HasIndex("EquipmentUuid")
+ .HasDatabaseName("IX_ExternalIdReservation_Equipment");
+
+ b.HasIndex("Kind", "Value")
+ .IsUnique()
+ .HasDatabaseName("UX_ExternalIdReservation_KindValue_Active")
+ .HasFilter("[ReleasedAt] IS NULL");
+
+ b.ToTable("ExternalIdReservation", (string)null);
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.LdapGroupRoleMapping", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("ClusterId")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("CreatedAtUtc")
+ .HasColumnType("datetime2(3)");
+
+ b.Property("IsSystemWide")
+ .HasColumnType("bit");
+
+ b.Property("LdapGroup")
+ .IsRequired()
+ .HasMaxLength(512)
+ .HasColumnType("nvarchar(512)");
+
+ b.Property("Notes")
+ .HasMaxLength(512)
+ .HasColumnType("nvarchar(512)");
+
+ b.Property("Role")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("nvarchar(32)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ClusterId");
+
+ b.HasIndex("LdapGroup")
+ .HasDatabaseName("IX_LdapGroupRoleMapping_Group");
+
+ b.HasIndex("LdapGroup", "ClusterId")
+ .IsUnique()
+ .HasDatabaseName("UX_LdapGroupRoleMapping_Group_Cluster")
+ .HasFilter("[ClusterId] IS NOT NULL");
+
+ b.ToTable("LdapGroupRoleMapping", (string)null);
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.NodeAcl", b =>
+ {
+ b.Property("NodeAclRowId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier")
+ .HasDefaultValueSql("NEWSEQUENTIALID()");
+
+ b.Property("ClusterId")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("LdapGroup")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("NodeAclId")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("Notes")
+ .HasMaxLength(512)
+ .HasColumnType("nvarchar(512)");
+
+ b.Property("PermissionFlags")
+ .HasColumnType("int");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .IsRequired()
+ .ValueGeneratedOnAddOrUpdate()
+ .HasColumnType("rowversion");
+
+ b.Property("ScopeId")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("ScopeKind")
+ .IsRequired()
+ .HasMaxLength(16)
+ .HasColumnType("nvarchar(16)");
+
+ b.HasKey("NodeAclRowId");
+
+ b.HasIndex("ClusterId")
+ .HasDatabaseName("IX_NodeAcl_Cluster");
+
+ b.HasIndex("LdapGroup")
+ .HasDatabaseName("IX_NodeAcl_Group");
+
+ b.HasIndex("NodeAclId")
+ .IsUnique()
+ .HasDatabaseName("UX_NodeAcl_LogicalId")
+ .HasFilter("[NodeAclId] IS NOT NULL");
+
+ b.HasIndex("ScopeKind", "ScopeId")
+ .HasDatabaseName("IX_NodeAcl_Scope")
+ .HasFilter("[ScopeId] IS NOT NULL");
+
+ b.HasIndex("ClusterId", "LdapGroup", "ScopeKind", "ScopeId")
+ .IsUnique()
+ .HasDatabaseName("UX_NodeAcl_GroupScope")
+ .HasFilter("[ScopeId] IS NOT NULL");
+
+ b.ToTable("NodeAcl", (string)null);
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.NodeDeploymentState", b =>
+ {
+ b.Property("NodeId")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("DeploymentId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("AppliedAtUtc")
+ .HasColumnType("datetime2(3)");
+
+ b.Property("FailureReason")
+ .HasMaxLength(2048)
+ .HasColumnType("nvarchar(2048)");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .IsRequired()
+ .ValueGeneratedOnAddOrUpdate()
+ .HasColumnType("rowversion");
+
+ b.Property("StartedAtUtc")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("datetime2(3)")
+ .HasDefaultValueSql("SYSUTCDATETIME()");
+
+ b.Property("Status")
+ .HasColumnType("int");
+
+ b.HasKey("NodeId", "DeploymentId");
+
+ b.HasIndex("DeploymentId")
+ .HasDatabaseName("IX_NodeDeploymentState_Deployment");
+
+ b.HasIndex("Status")
+ .HasDatabaseName("IX_NodeDeploymentState_Status");
+
+ b.ToTable("NodeDeploymentState", (string)null);
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.PollGroup", b =>
+ {
+ b.Property("PollGroupRowId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier")
+ .HasDefaultValueSql("NEWSEQUENTIALID()");
+
+ b.Property("DriverInstanceId")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("IntervalMs")
+ .HasColumnType("int");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("nvarchar(128)");
+
+ b.Property("PollGroupId")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .IsRequired()
+ .ValueGeneratedOnAddOrUpdate()
+ .HasColumnType("rowversion");
+
+ b.HasKey("PollGroupRowId");
+
+ b.HasIndex("DriverInstanceId")
+ .HasDatabaseName("IX_PollGroup_Driver");
+
+ b.HasIndex("PollGroupId")
+ .IsUnique()
+ .HasDatabaseName("UX_PollGroup_LogicalId")
+ .HasFilter("[PollGroupId] IS NOT NULL");
+
+ b.ToTable("PollGroup", null, t =>
+ {
+ t.HasCheckConstraint("CK_PollGroup_IntervalMs_Min", "IntervalMs >= 50");
+ });
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.RawFolder", b =>
+ {
+ b.Property("RawFolderRowId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier")
+ .HasDefaultValueSql("NEWSEQUENTIALID()");
+
+ b.Property("ClusterId")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("nvarchar(128)");
+
+ b.Property("ParentRawFolderId")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("RawFolderId")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .IsRequired()
+ .ValueGeneratedOnAddOrUpdate()
+ .HasColumnType("rowversion");
+
+ b.Property("SortOrder")
+ .HasColumnType("int");
+
+ b.HasKey("RawFolderRowId");
+
+ b.HasIndex("ClusterId")
+ .HasDatabaseName("IX_RawFolder_Cluster");
+
+ b.HasIndex("RawFolderId")
+ .IsUnique()
+ .HasDatabaseName("UX_RawFolder_LogicalId")
+ .HasFilter("[RawFolderId] IS NOT NULL");
+
+ b.HasIndex("ClusterId", "Name")
+ .IsUnique()
+ .HasDatabaseName("UX_RawFolder_ClusterRoot_Name")
+ .HasFilter("[ParentRawFolderId] IS NULL");
+
+ b.HasIndex("ClusterId", "ParentRawFolderId", "Name")
+ .IsUnique()
+ .HasDatabaseName("UX_RawFolder_Parent_Name")
+ .HasFilter("[ParentRawFolderId] IS NOT NULL");
+
+ b.ToTable("RawFolder", (string)null);
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.Script", b =>
+ {
+ b.Property("ScriptRowId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier")
+ .HasDefaultValueSql("NEWSEQUENTIALID()");
+
+ b.Property("Language")
+ .IsRequired()
+ .HasMaxLength(16)
+ .HasColumnType("nvarchar(16)");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("nvarchar(128)");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .IsRequired()
+ .ValueGeneratedOnAddOrUpdate()
+ .HasColumnType("rowversion");
+
+ b.Property("ScriptId")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("SourceCode")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("SourceHash")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.HasKey("ScriptRowId");
+
+ b.HasIndex("ScriptId")
+ .IsUnique()
+ .HasDatabaseName("UX_Script_LogicalId")
+ .HasFilter("[ScriptId] IS NOT NULL");
+
+ b.HasIndex("SourceHash")
+ .HasDatabaseName("IX_Script_SourceHash");
+
+ b.ToTable("Script", (string)null);
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.ScriptedAlarm", b =>
+ {
+ b.Property("ScriptedAlarmRowId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier")
+ .HasDefaultValueSql("NEWSEQUENTIALID()");
+
+ b.Property("AlarmType")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("nvarchar(32)");
+
+ b.Property("Enabled")
+ .HasColumnType("bit");
+
+ b.Property("EquipmentId")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("HistorizeToAveva")
+ .HasColumnType("bit");
+
+ b.Property("MessageTemplate")
+ .IsRequired()
+ .HasMaxLength(1024)
+ .HasColumnType("nvarchar(1024)");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("nvarchar(128)");
+
+ b.Property("PredicateScriptId")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("Retain")
+ .HasColumnType("bit");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .IsRequired()
+ .ValueGeneratedOnAddOrUpdate()
+ .HasColumnType("rowversion");
+
+ b.Property("ScriptedAlarmId")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("Severity")
+ .HasColumnType("int");
+
+ b.HasKey("ScriptedAlarmRowId");
+
+ b.HasIndex("PredicateScriptId")
+ .HasDatabaseName("IX_ScriptedAlarm_Script");
+
+ b.HasIndex("ScriptedAlarmId")
+ .IsUnique()
+ .HasDatabaseName("UX_ScriptedAlarm_LogicalId")
+ .HasFilter("[ScriptedAlarmId] IS NOT NULL");
+
+ b.HasIndex("EquipmentId", "Name")
+ .IsUnique()
+ .HasDatabaseName("UX_ScriptedAlarm_EquipmentPath");
+
+ b.ToTable("ScriptedAlarm", null, t =>
+ {
+ t.HasCheckConstraint("CK_ScriptedAlarm_AlarmType", "AlarmType IN ('AlarmCondition','LimitAlarm','OffNormalAlarm','DiscreteAlarm')");
+
+ t.HasCheckConstraint("CK_ScriptedAlarm_Severity_Range", "Severity BETWEEN 1 AND 1000");
+ });
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.ServerCluster", b =>
+ {
+ b.Property("ClusterId")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("CreatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("datetime2(3)")
+ .HasDefaultValueSql("SYSUTCDATETIME()");
+
+ b.Property("CreatedBy")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("nvarchar(128)");
+
+ b.Property("Enabled")
+ .HasColumnType("bit");
+
+ b.Property("Enterprise")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("nvarchar(32)");
+
+ b.Property("ModifiedAt")
+ .HasColumnType("datetime2(3)");
+
+ b.Property("ModifiedBy")
+ .HasMaxLength(128)
+ .HasColumnType("nvarchar(128)");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("nvarchar(128)");
+
+ b.Property("NodeCount")
+ .HasColumnType("tinyint");
+
+ b.Property("Notes")
+ .HasMaxLength(1024)
+ .HasColumnType("nvarchar(1024)");
+
+ b.Property("RedundancyMode")
+ .IsRequired()
+ .HasMaxLength(16)
+ .HasColumnType("nvarchar(16)");
+
+ b.Property("Site")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("nvarchar(32)");
+
+ b.HasKey("ClusterId");
+
+ b.HasIndex("Name")
+ .IsUnique()
+ .HasDatabaseName("UX_ServerCluster_Name");
+
+ b.HasIndex("Site")
+ .HasDatabaseName("IX_ServerCluster_Site");
+
+ b.ToTable("ServerCluster", null, t =>
+ {
+ t.HasCheckConstraint("CK_ServerCluster_RedundancyMode_NodeCount", "((NodeCount = 1 AND RedundancyMode = 'None') OR (NodeCount = 2 AND RedundancyMode IN ('Warm', 'Hot')))");
+ });
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.Tag", b =>
+ {
+ b.Property("TagRowId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier")
+ .HasDefaultValueSql("NEWSEQUENTIALID()");
+
+ b.Property("AccessLevel")
+ .IsRequired()
+ .HasMaxLength(16)
+ .HasColumnType("nvarchar(16)");
+
+ b.Property("DataType")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("nvarchar(32)");
+
+ b.Property("DeviceId")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("nvarchar(128)");
+
+ b.Property("PollGroupId")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .IsRequired()
+ .ValueGeneratedOnAddOrUpdate()
+ .HasColumnType("rowversion");
+
+ b.Property("TagConfig")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("TagGroupId")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("TagId")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("WriteIdempotent")
+ .HasColumnType("bit");
+
+ b.HasKey("TagRowId");
+
+ b.HasIndex("TagId")
+ .IsUnique()
+ .HasDatabaseName("UX_Tag_LogicalId")
+ .HasFilter("[TagId] IS NOT NULL");
+
+ b.HasIndex("DeviceId", "Name")
+ .IsUnique()
+ .HasDatabaseName("UX_Tag_Device_Name")
+ .HasFilter("[TagGroupId] IS NULL");
+
+ b.HasIndex("DeviceId", "TagGroupId")
+ .HasDatabaseName("IX_Tag_Device");
+
+ b.HasIndex("DeviceId", "TagGroupId", "Name")
+ .IsUnique()
+ .HasDatabaseName("UX_Tag_Group_Name")
+ .HasFilter("[TagGroupId] IS NOT NULL");
+
+ b.ToTable("Tag", null, t =>
+ {
+ t.HasCheckConstraint("CK_Tag_TagConfig_IsJson", "ISJSON(TagConfig) = 1");
+ });
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.TagGroup", b =>
+ {
+ b.Property("TagGroupRowId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier")
+ .HasDefaultValueSql("NEWSEQUENTIALID()");
+
+ b.Property("DeviceId")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("nvarchar(128)");
+
+ b.Property("ParentTagGroupId")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .IsRequired()
+ .ValueGeneratedOnAddOrUpdate()
+ .HasColumnType("rowversion");
+
+ b.Property("SortOrder")
+ .HasColumnType("int");
+
+ b.Property("TagGroupId")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.HasKey("TagGroupRowId");
+
+ b.HasIndex("DeviceId")
+ .HasDatabaseName("IX_TagGroup_Device");
+
+ b.HasIndex("TagGroupId")
+ .IsUnique()
+ .HasDatabaseName("UX_TagGroup_LogicalId")
+ .HasFilter("[TagGroupId] IS NOT NULL");
+
+ b.HasIndex("DeviceId", "Name")
+ .IsUnique()
+ .HasDatabaseName("UX_TagGroup_DeviceRoot_Name")
+ .HasFilter("[ParentTagGroupId] IS NULL");
+
+ b.HasIndex("DeviceId", "ParentTagGroupId", "Name")
+ .IsUnique()
+ .HasDatabaseName("UX_TagGroup_Parent_Name")
+ .HasFilter("[ParentTagGroupId] IS NOT NULL");
+
+ b.ToTable("TagGroup", (string)null);
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.UnsArea", b =>
+ {
+ b.Property("UnsAreaRowId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier")
+ .HasDefaultValueSql("NEWSEQUENTIALID()");
+
+ b.Property("ClusterId")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("nvarchar(32)");
+
+ b.Property("Notes")
+ .HasMaxLength(512)
+ .HasColumnType("nvarchar(512)");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .IsRequired()
+ .ValueGeneratedOnAddOrUpdate()
+ .HasColumnType("rowversion");
+
+ b.Property("UnsAreaId")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.HasKey("UnsAreaRowId");
+
+ b.HasIndex("ClusterId")
+ .HasDatabaseName("IX_UnsArea_Cluster");
+
+ b.HasIndex("UnsAreaId")
+ .IsUnique()
+ .HasDatabaseName("UX_UnsArea_LogicalId")
+ .HasFilter("[UnsAreaId] IS NOT NULL");
+
+ b.HasIndex("ClusterId", "Name")
+ .IsUnique()
+ .HasDatabaseName("UX_UnsArea_ClusterName");
+
+ b.ToTable("UnsArea", (string)null);
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.UnsLine", b =>
+ {
+ b.Property("UnsLineRowId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier")
+ .HasDefaultValueSql("NEWSEQUENTIALID()");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("nvarchar(32)");
+
+ b.Property("Notes")
+ .HasMaxLength(512)
+ .HasColumnType("nvarchar(512)");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .IsRequired()
+ .ValueGeneratedOnAddOrUpdate()
+ .HasColumnType("rowversion");
+
+ b.Property("UnsAreaId")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("UnsLineId")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.HasKey("UnsLineRowId");
+
+ b.HasIndex("UnsAreaId")
+ .HasDatabaseName("IX_UnsLine_Area");
+
+ b.HasIndex("UnsLineId")
+ .IsUnique()
+ .HasDatabaseName("UX_UnsLine_LogicalId")
+ .HasFilter("[UnsLineId] IS NOT NULL");
+
+ b.HasIndex("UnsAreaId", "Name")
+ .IsUnique()
+ .HasDatabaseName("UX_UnsLine_AreaName");
+
+ b.ToTable("UnsLine", (string)null);
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.UnsTagReference", b =>
+ {
+ b.Property("UnsTagReferenceRowId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier")
+ .HasDefaultValueSql("NEWSEQUENTIALID()");
+
+ b.Property("DisplayNameOverride")
+ .HasMaxLength(128)
+ .HasColumnType("nvarchar(128)");
+
+ b.Property("EquipmentId")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .IsRequired()
+ .ValueGeneratedOnAddOrUpdate()
+ .HasColumnType("rowversion");
+
+ b.Property("SortOrder")
+ .HasColumnType("int");
+
+ b.Property("TagId")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("UnsTagReferenceId")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.HasKey("UnsTagReferenceRowId");
+
+ b.HasIndex("EquipmentId")
+ .HasDatabaseName("IX_UnsTagReference_Equipment");
+
+ b.HasIndex("TagId")
+ .HasDatabaseName("IX_UnsTagReference_Tag");
+
+ b.HasIndex("UnsTagReferenceId")
+ .IsUnique()
+ .HasDatabaseName("UX_UnsTagReference_LogicalId")
+ .HasFilter("[UnsTagReferenceId] IS NOT NULL");
+
+ b.HasIndex("EquipmentId", "TagId")
+ .IsUnique()
+ .HasDatabaseName("UX_UnsTagReference_Equip_Tag");
+
+ b.ToTable("UnsTagReference", (string)null);
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.VirtualTag", b =>
+ {
+ b.Property("VirtualTagRowId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier")
+ .HasDefaultValueSql("NEWSEQUENTIALID()");
+
+ b.Property("ChangeTriggered")
+ .HasColumnType("bit");
+
+ b.Property("DataType")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("nvarchar(32)");
+
+ b.Property("Enabled")
+ .HasColumnType("bit");
+
+ b.Property("EquipmentId")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("Historize")
+ .HasColumnType("bit");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("nvarchar(128)");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .IsRequired()
+ .ValueGeneratedOnAddOrUpdate()
+ .HasColumnType("rowversion");
+
+ b.Property("ScriptId")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("TimerIntervalMs")
+ .HasColumnType("int");
+
+ b.Property("VirtualTagId")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.HasKey("VirtualTagRowId");
+
+ b.HasIndex("ScriptId")
+ .HasDatabaseName("IX_VirtualTag_Script");
+
+ b.HasIndex("VirtualTagId")
+ .IsUnique()
+ .HasDatabaseName("UX_VirtualTag_LogicalId")
+ .HasFilter("[VirtualTagId] IS NOT NULL");
+
+ b.HasIndex("EquipmentId", "Name")
+ .IsUnique()
+ .HasDatabaseName("UX_VirtualTag_EquipmentPath");
+
+ b.ToTable("VirtualTag", null, t =>
+ {
+ t.HasCheckConstraint("CK_VirtualTag_TimerInterval_Min", "TimerIntervalMs IS NULL OR TimerIntervalMs >= 50");
+
+ t.HasCheckConstraint("CK_VirtualTag_Trigger_AtLeastOne", "ChangeTriggered = 1 OR TimerIntervalMs IS NOT NULL");
+ });
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.ClusterNode", b =>
+ {
+ b.HasOne("ZB.MOM.WW.OtOpcUa.Configuration.Entities.ServerCluster", "Cluster")
+ .WithMany("Nodes")
+ .HasForeignKey("ClusterId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+
+ b.Navigation("Cluster");
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.ClusterNodeCredential", b =>
+ {
+ b.HasOne("ZB.MOM.WW.OtOpcUa.Configuration.Entities.ClusterNode", "Node")
+ .WithMany("Credentials")
+ .HasForeignKey("NodeId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+
+ b.Navigation("Node");
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.DriverInstance", b =>
+ {
+ b.HasOne("ZB.MOM.WW.OtOpcUa.Configuration.Entities.ServerCluster", "Cluster")
+ .WithMany()
+ .HasForeignKey("ClusterId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+
+ b.Navigation("Cluster");
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.LdapGroupRoleMapping", b =>
+ {
+ b.HasOne("ZB.MOM.WW.OtOpcUa.Configuration.Entities.ServerCluster", "Cluster")
+ .WithMany()
+ .HasForeignKey("ClusterId")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.Navigation("Cluster");
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.NodeDeploymentState", b =>
+ {
+ b.HasOne("ZB.MOM.WW.OtOpcUa.Configuration.Entities.Deployment", "Deployment")
+ .WithMany()
+ .HasForeignKey("DeploymentId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("ZB.MOM.WW.OtOpcUa.Configuration.Entities.ClusterNode", "Node")
+ .WithMany()
+ .HasForeignKey("NodeId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+
+ b.Navigation("Deployment");
+
+ b.Navigation("Node");
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.RawFolder", b =>
+ {
+ b.HasOne("ZB.MOM.WW.OtOpcUa.Configuration.Entities.ServerCluster", null)
+ .WithMany("RawFolders")
+ .HasForeignKey("ClusterId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.UnsArea", b =>
+ {
+ b.HasOne("ZB.MOM.WW.OtOpcUa.Configuration.Entities.ServerCluster", "Cluster")
+ .WithMany()
+ .HasForeignKey("ClusterId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+
+ b.Navigation("Cluster");
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.ClusterNode", b =>
+ {
+ b.Navigation("Credentials");
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.ServerCluster", b =>
+ {
+ b.Navigation("Nodes");
+
+ b.Navigation("RawFolders");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Migrations/20260731073334_DropDriverInstanceResilienceStatusTable.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Migrations/20260731073334_DropDriverInstanceResilienceStatusTable.cs
new file mode 100644
index 00000000..2fa1d13b
--- /dev/null
+++ b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Migrations/20260731073334_DropDriverInstanceResilienceStatusTable.cs
@@ -0,0 +1,55 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
+{
+ ///
+ /// 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.
+ ///
+ public partial class DropDriverInstanceResilienceStatusTable : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "DriverInstanceResilienceStatus");
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "DriverInstanceResilienceStatus",
+ columns: table => new
+ {
+ DriverInstanceId = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false),
+ HostName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false),
+ BaselineFootprintBytes = table.Column(type: "bigint", nullable: false),
+ ConsecutiveFailures = table.Column(type: "int", nullable: false),
+ CurrentBulkheadDepth = table.Column(type: "int", nullable: false),
+ CurrentFootprintBytes = table.Column(type: "bigint", nullable: false),
+ LastCircuitBreakerOpenUtc = table.Column(type: "datetime2(3)", nullable: true),
+ LastRecycleUtc = table.Column(type: "datetime2(3)", nullable: true),
+ LastSampledUtc = table.Column(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");
+ }
+ }
+}
diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Migrations/OtOpcUaConfigDbContextModelSnapshot.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Migrations/OtOpcUaConfigDbContextModelSnapshot.cs
index 570dc685..a2823b38 100644
--- a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Migrations/OtOpcUaConfigDbContextModelSnapshot.cs
+++ b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Migrations/OtOpcUaConfigDbContextModelSnapshot.cs
@@ -471,45 +471,6 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
});
});
- modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.DriverInstanceResilienceStatus", b =>
- {
- b.Property("DriverInstanceId")
- .HasMaxLength(64)
- .HasColumnType("nvarchar(64)");
-
- b.Property("HostName")
- .HasMaxLength(256)
- .HasColumnType("nvarchar(256)");
-
- b.Property("BaselineFootprintBytes")
- .HasColumnType("bigint");
-
- b.Property("ConsecutiveFailures")
- .HasColumnType("int");
-
- b.Property("CurrentBulkheadDepth")
- .HasColumnType("int");
-
- b.Property("CurrentFootprintBytes")
- .HasColumnType("bigint");
-
- b.Property("LastCircuitBreakerOpenUtc")
- .HasColumnType("datetime2(3)");
-
- b.Property("LastRecycleUtc")
- .HasColumnType("datetime2(3)");
-
- b.Property("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("EquipmentRowId")
diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/OtOpcUaConfigDbContext.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/OtOpcUaConfigDbContext.cs
index 06841380..1aac7060 100644
--- a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/OtOpcUaConfigDbContext.cs
+++ b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/OtOpcUaConfigDbContext.cs
@@ -44,8 +44,6 @@ public sealed class OtOpcUaConfigDbContext(DbContextOptions ConfigAuditLogs => Set();
/// Gets the DbSet of external ID reservations.
public DbSet ExternalIdReservations => Set();
- /// Gets the DbSet of driver instance resilience statuses.
- public DbSet DriverInstanceResilienceStatuses => Set();
/// Gets the DbSet of LDAP group role mappings.
public DbSet LdapGroupRoleMappings => Set();
/// Gets the DbSet of scripts.
@@ -88,7 +86,6 @@ public sealed class OtOpcUaConfigDbContext(DbContextOptions(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)
{
diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Core/Resilience/DriverResilienceStatusTracker.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Core/Resilience/DriverResilienceStatusTracker.cs
index 8db62bbd..b1134d06 100644
--- a/src/Core/ZB.MOM.WW.OtOpcUa.Core/Resilience/DriverResilienceStatusTracker.cs
+++ b/src/Core/ZB.MOM.WW.OtOpcUa.Core/Resilience/DriverResilienceStatusTracker.cs
@@ -192,9 +192,12 @@ public sealed record ResilienceStatusSnapshot
///
/// In-flight capability calls against this (instance, host). Bumped on call entry +
- /// decremented on completion. Feeds DriverInstanceResilienceStatus.CurrentBulkheadDepth
- /// for Admin /hosts — in-flight capability-call depth until the full
- /// telemetry observer lands.
+ /// decremented on completion. Surfaced to the AdminUI's driver status panel as
+ /// DriverResilienceStatusChanged.CurrentInFlight over the
+ /// driver-resilience-status telemetry channel.
+ /// It used to say this fed DriverInstanceResilienceStatus.CurrentBulkheadDepth "until
+ /// the full telemetry observer lands". The telemetry observer landed (Phase 5); that table never
+ /// had a writer and was dropped in Gitea #524.
///
public int CurrentInFlight { get; init; }
}
diff --git a/tests/Core/ZB.MOM.WW.OtOpcUa.Configuration.Tests/SchemaComplianceTests.cs b/tests/Core/ZB.MOM.WW.OtOpcUa.Configuration.Tests/SchemaComplianceTests.cs
index 4d9cfde6..6f38a15b 100644
--- a/tests/Core/ZB.MOM.WW.OtOpcUa.Configuration.Tests/SchemaComplianceTests.cs
+++ b/tests/Core/ZB.MOM.WW.OtOpcUa.Configuration.Tests/SchemaComplianceTests.cs
@@ -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