v3 batch1 WP1: greenfield DbContext + V3Initial squash + seeds + tests

DbContext (OtOpcUaConfigDbContext):
- Drop Namespace/EquipmentImportBatch/EquipmentImportRow DbSets + Configure methods.
- Add RawFolder/TagGroup/UnsTagReference DbSets + Configure methods with sibling-name
  filtered unique indexes over nullable parents (two-filtered pattern per level).
- Reshape DriverInstance (RawFolderId + UX_DriverInstance_Folder_Name/_ClusterRoot_Name),
  Tag (required DeviceId, nullable TagGroupId, UX_Tag_Group_Name/_Device_Name, IX_Tag_Device),
  Equipment (drop DriverInstanceId/DeviceId + IX_Equipment_Driver), Device (UX_Device_Driver_Name).

Migration squash -> V3Initial:
- Delete the whole Migrations/ folder; scaffold one V3Initial (applies clean to a fresh DB).
- Port the hand-written procs + AuthorizationGrants into V3Initial.StoredProcedures.cs. The v1
  generation-model procs (ConfigGeneration/GenerationId, dead since V2HostingAlignment) are
  rewritten as v3-schema-valid retirement stubs that preserve the two-role EXECUTE-grant surface
  + AuthorizationTests behaviors; sp_ComputeGenerationDiff now references the v3 tables;
  sp_ReleaseExternalIdReservation ported verbatim.

Compile-fixes for the build gate (Wave-C-owned, minimal + TODO(v3 WP4) markers):
- DraftSnapshot/DraftSnapshotFactory: drop the retired Namespaces list.
- DraftValidator: delete namespace-binding + Galaxy-FullName rules; collision rule now VirtualTag-only.

Seeds -> v3 schema:
- seed-clusters.sql summary SELECTs; all 5 scripts/smoke/seed-*.sql rewritten (DriverInstance
  RawFolderId, Device+DeviceConfig endpoint, Tag DeviceId, UnsTagReference; no ConfigGeneration/
  Namespace/sp_PublishGeneration). Verified: all seeds apply clean against a fresh V3Initial DB.

Tests (Configuration.Tests, 107 pass):
- SchemaComplianceTests rewritten to v3 tables + filtered indexes.
- New RawSchemaIntegrityTests: DB-enforced sibling-name uniqueness per raw level + UnsTagReference
  (Equipment,Tag) uniqueness.
- Delete DraftValidatorGalaxyFullNameCorpusTests (WP2/WP6 replaces); fix DraftValidatorTests +
  DraftSnapshotFactoryTests to v3 shapes; repoint scripting-migration existence test to V3Initial.
This commit is contained in:
Joseph Doherty
2026-07-15 19:19:21 -04:00
parent cb720bb8c3
commit 4d39b98564
54 changed files with 2523 additions and 30115 deletions
@@ -373,6 +373,10 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
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");
@@ -452,8 +456,7 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<string>("NamespaceId")
.IsRequired()
b.Property<string>("RawFolderId")
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
@@ -476,8 +479,18 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
.HasDatabaseName("UX_DriverInstance_LogicalId")
.HasFilter("[DriverInstanceId] IS NOT NULL");
b.HasIndex("NamespaceId")
.HasDatabaseName("IX_DriverInstance_Namespace");
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 =>
{
@@ -537,18 +550,10 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("DeviceId")
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<string>("DeviceManualUri")
.HasMaxLength(512)
.HasColumnType("nvarchar(512)");
b.Property<string>("DriverInstanceId")
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<bool>("Enabled")
.HasColumnType("bit");
@@ -621,9 +626,6 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
b.HasKey("EquipmentRowId");
b.HasIndex("DriverInstanceId")
.HasDatabaseName("IX_Equipment_Driver");
b.HasIndex("EquipmentId")
.IsUnique()
.HasDatabaseName("UX_Equipment_LogicalId")
@@ -654,148 +656,6 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
b.ToTable("Equipment", (string)null);
});
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.EquipmentImportBatch", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<string>("ClusterId")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<DateTime>("CreatedAtUtc")
.HasColumnType("datetime2(3)");
b.Property<string>("CreatedBy")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<DateTime?>("FinalisedAtUtc")
.HasColumnType("datetime2(3)");
b.Property<int>("RowsAccepted")
.HasColumnType("int");
b.Property<int>("RowsRejected")
.HasColumnType("int");
b.Property<int>("RowsStaged")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("CreatedBy", "FinalisedAtUtc")
.HasDatabaseName("IX_EquipmentImportBatch_Creator_Finalised");
b.ToTable("EquipmentImportBatch", (string)null);
});
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.EquipmentImportRow", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<string>("AssetLocation")
.HasMaxLength(512)
.HasColumnType("nvarchar(512)");
b.Property<Guid>("BatchId")
.HasColumnType("uniqueidentifier");
b.Property<string>("DeviceManualUri")
.HasMaxLength(512)
.HasColumnType("nvarchar(512)");
b.Property<string>("EquipmentId")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<string>("EquipmentUuid")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<string>("HardwareRevision")
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<bool>("IsAccepted")
.HasColumnType("bit");
b.Property<int>("LineNumberInFile")
.HasColumnType("int");
b.Property<string>("MachineCode")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<string>("Manufacturer")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("ManufacturerUri")
.HasMaxLength(512)
.HasColumnType("nvarchar(512)");
b.Property<string>("Model")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<string>("RejectReason")
.HasMaxLength(512)
.HasColumnType("nvarchar(512)");
b.Property<string>("SAPID")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<string>("SerialNumber")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("SoftwareRevision")
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<string>("UnsAreaName")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<string>("UnsLineName")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<string>("YearOfConstruction")
.HasMaxLength(8)
.HasColumnType("nvarchar(8)");
b.Property<string>("ZTag")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.HasKey("Id");
b.HasIndex("BatchId")
.HasDatabaseName("IX_EquipmentImportRow_Batch");
b.ToTable("EquipmentImportRow", (string)null);
});
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.ExternalIdReservation", b =>
{
b.Property<Guid>("ReservationId")
@@ -905,66 +765,6 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
b.ToTable("LdapGroupRoleMapping", (string)null);
});
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.Namespace", b =>
{
b.Property<Guid>("NamespaceRowId")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier")
.HasDefaultValueSql("NEWSEQUENTIALID()");
b.Property<string>("ClusterId")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<bool>("Enabled")
.HasColumnType("bit");
b.Property<string>("Kind")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("nvarchar(32)");
b.Property<string>("NamespaceId")
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<string>("NamespaceUri")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("Notes")
.HasMaxLength(1024)
.HasColumnType("nvarchar(1024)");
b.Property<byte[]>("RowVersion")
.IsConcurrencyToken()
.IsRequired()
.ValueGeneratedOnAddOrUpdate()
.HasColumnType("rowversion");
b.HasKey("NamespaceRowId");
b.HasIndex("ClusterId")
.HasDatabaseName("IX_Namespace_Cluster");
b.HasIndex("NamespaceId")
.IsUnique()
.HasDatabaseName("UX_Namespace_LogicalId")
.HasFilter("[NamespaceId] IS NOT NULL");
b.HasIndex("NamespaceUri")
.IsUnique()
.HasDatabaseName("UX_Namespace_NamespaceUri");
b.HasIndex("ClusterId", "Kind")
.IsUnique()
.HasDatabaseName("UX_Namespace_Cluster_Kind");
b.ToTable("Namespace", (string)null);
});
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.NodeAcl", b =>
{
b.Property<Guid>("NodeAclRowId")
@@ -1120,6 +920,63 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
});
});
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.RawFolder", b =>
{
b.Property<Guid>("RawFolderRowId")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier")
.HasDefaultValueSql("NEWSEQUENTIALID()");
b.Property<string>("ClusterId")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<string>("ParentRawFolderId")
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<string>("RawFolderId")
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<byte[]>("RowVersion")
.IsConcurrencyToken()
.IsRequired()
.ValueGeneratedOnAddOrUpdate()
.HasColumnType("rowversion");
b.Property<int>("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<Guid>("ScriptRowId")
@@ -1399,22 +1256,10 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
.HasColumnType("nvarchar(32)");
b.Property<string>("DeviceId")
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<string>("DriverInstanceId")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<string>("EquipmentId")
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<string>("FolderPath")
.HasMaxLength(512)
.HasColumnType("nvarchar(512)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(128)
@@ -1434,6 +1279,10 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("TagGroupId")
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<string>("TagId")
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
@@ -1443,27 +1292,23 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
b.HasKey("TagRowId");
b.HasIndex("EquipmentId")
.HasDatabaseName("IX_Tag_Equipment")
.HasFilter("[EquipmentId] IS NOT NULL");
b.HasIndex("TagId")
.IsUnique()
.HasDatabaseName("UX_Tag_LogicalId")
.HasFilter("[TagId] IS NOT NULL");
b.HasIndex("DriverInstanceId", "DeviceId")
.HasDatabaseName("IX_Tag_Driver_Device");
b.HasIndex("EquipmentId", "Name")
b.HasIndex("DeviceId", "Name")
.IsUnique()
.HasDatabaseName("UX_Tag_EquipmentPath")
.HasFilter("[EquipmentId] IS NOT NULL");
.HasDatabaseName("UX_Tag_Device_Name")
.HasFilter("[TagGroupId] IS NULL");
b.HasIndex("DriverInstanceId", "FolderPath", "Name")
b.HasIndex("DeviceId", "TagGroupId")
.HasDatabaseName("IX_Tag_Device");
b.HasIndex("DeviceId", "TagGroupId", "Name")
.IsUnique()
.HasDatabaseName("UX_Tag_FolderPath")
.HasFilter("[EquipmentId] IS NULL");
.HasDatabaseName("UX_Tag_Group_Name")
.HasFilter("[TagGroupId] IS NOT NULL");
b.ToTable("Tag", null, t =>
{
@@ -1471,6 +1316,63 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
});
});
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.TagGroup", b =>
{
b.Property<Guid>("TagGroupRowId")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier")
.HasDefaultValueSql("NEWSEQUENTIALID()");
b.Property<string>("DeviceId")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<string>("ParentTagGroupId")
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<byte[]>("RowVersion")
.IsConcurrencyToken()
.IsRequired()
.ValueGeneratedOnAddOrUpdate()
.HasColumnType("rowversion");
b.Property<int>("SortOrder")
.HasColumnType("int");
b.Property<string>("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<Guid>("UnsAreaRowId")
@@ -1567,6 +1469,60 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
b.ToTable("UnsLine", (string)null);
});
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.UnsTagReference", b =>
{
b.Property<Guid>("UnsTagReferenceRowId")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier")
.HasDefaultValueSql("NEWSEQUENTIALID()");
b.Property<string>("DisplayNameOverride")
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<string>("EquipmentId")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<byte[]>("RowVersion")
.IsConcurrencyToken()
.IsRequired()
.ValueGeneratedOnAddOrUpdate()
.HasColumnType("rowversion");
b.Property<int>("SortOrder")
.HasColumnType("int");
b.Property<string>("TagId")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<string>("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<Guid>("VirtualTagRowId")
@@ -1671,17 +1627,6 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
b.Navigation("Cluster");
});
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.EquipmentImportRow", b =>
{
b.HasOne("ZB.MOM.WW.OtOpcUa.Configuration.Entities.EquipmentImportBatch", "Batch")
.WithMany("Rows")
.HasForeignKey("BatchId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Batch");
});
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.LdapGroupRoleMapping", b =>
{
b.HasOne("ZB.MOM.WW.OtOpcUa.Configuration.Entities.ServerCluster", "Cluster")
@@ -1692,17 +1637,6 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
b.Navigation("Cluster");
});
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.Namespace", b =>
{
b.HasOne("ZB.MOM.WW.OtOpcUa.Configuration.Entities.ServerCluster", "Cluster")
.WithMany("Namespaces")
.HasForeignKey("ClusterId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("Cluster");
});
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.NodeDeploymentState", b =>
{
b.HasOne("ZB.MOM.WW.OtOpcUa.Configuration.Entities.Deployment", "Deployment")
@@ -1722,6 +1656,15 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
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")
@@ -1738,16 +1681,11 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Migrations
b.Navigation("Credentials");
});
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.EquipmentImportBatch", b =>
{
b.Navigation("Rows");
});
modelBuilder.Entity("ZB.MOM.WW.OtOpcUa.Configuration.Entities.ServerCluster", b =>
{
b.Navigation("Namespaces");
b.Navigation("Nodes");
b.Navigation("RawFolders");
});
#pragma warning restore 612, 618
}