diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Sites/Site.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Sites/Site.cs
index 16f7f0e0..f1e5122e 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Sites/Site.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Sites/Site.cs
@@ -18,6 +18,18 @@ public class Site
public string? GrpcNodeAAddress { get; set; }
/// gRPC endpoint for site node B used by the central SiteStreamGrpcClient.
public string? GrpcNodeBAddress { get; set; }
+ ///
+ /// Revision hash (ArtifactRevisionHashService) of the per-site artifact set last
+ /// deployed to this site successfully; null = artifacts never deployed. Environment-specific
+ /// deploy state — deliberately NOT carried by Transport bundles (like Instance.State).
+ ///
+ public string? ArtifactRevisionHash { get; set; }
+ ///
+ /// UTC instant at which the artifact set identified by was
+ /// last deployed to this site successfully; null = artifacts never deployed. Environment-specific
+ /// deploy state — deliberately NOT carried by Transport bundles (like Instance.State).
+ ///
+ public DateTimeOffset? ArtifactsDeployedAt { get; set; }
///
/// Initializes a new site with the required name and identifier.
diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Repositories/IDeploymentManagerRepository.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Repositories/IDeploymentManagerRepository.cs
index fc261770..78f74d2d 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Repositories/IDeploymentManagerRepository.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Repositories/IDeploymentManagerRepository.cs
@@ -204,6 +204,14 @@ public interface IDeploymentManagerRepository
/// The deployed config snapshot, or null if not found.
Task GetDeployedSnapshotByInstanceIdAsync(int instanceId, CancellationToken cancellationToken = default);
///
+ /// InstanceId → RevisionHash for every DeployedConfigSnapshot. A projection read — never
+ /// materialises ConfigurationJson, so it is safe to call for the whole fleet (the
+ /// pending-updates aggregate does, once per cache TTL).
+ ///
+ /// A cancellation token that can be used to cancel the operation.
+ /// A dictionary keyed by instance ID; instances with no snapshot are absent.
+ Task> GetDeployedRevisionHashesAsync(CancellationToken cancellationToken = default);
+ ///
/// Adds a new deployed config snapshot.
///
/// The deployed config snapshot to add.
diff --git a/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Configurations/SiteConfiguration.cs b/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Configurations/SiteConfiguration.cs
index dfbf3547..d213817a 100644
--- a/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Configurations/SiteConfiguration.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Configurations/SiteConfiguration.cs
@@ -27,6 +27,7 @@ public class SiteConfiguration : IEntityTypeConfiguration
builder.Property(s => s.NodeBAddress).HasMaxLength(500);
builder.Property(s => s.GrpcNodeAAddress).HasMaxLength(500);
builder.Property(s => s.GrpcNodeBAddress).HasMaxLength(500);
+ builder.Property(s => s.ArtifactRevisionHash).HasMaxLength(100);
builder.HasIndex(s => s.Name).IsUnique();
builder.HasIndex(s => s.SiteIdentifier).IsUnique();
diff --git a/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Migrations/20260914173147_AddSiteArtifactRevision.Designer.cs b/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Migrations/20260914173147_AddSiteArtifactRevision.Designer.cs
new file mode 100644
index 00000000..29ab1fbd
--- /dev/null
+++ b/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Migrations/20260914173147_AddSiteArtifactRevision.Designer.cs
@@ -0,0 +1,2149 @@
+//
+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.ScadaBridge.ConfigurationDatabase;
+
+#nullable disable
+
+namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Migrations
+{
+ [DbContext(typeof(ScadaBridgeDbContext))]
+ [Migration("20260914173147_AddSiteArtifactRevision")]
+ partial class AddSiteArtifactRevision
+ {
+ ///
+ 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");
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.ScadaBridge.Commons.Entities.Audit.AuditLogEntry", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("Action")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("AfterStateJson")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("BundleImportId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("EntityId")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("EntityName")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("EntityType")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("Timestamp")
+ .HasColumnType("datetimeoffset");
+
+ b.Property("User")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Action");
+
+ b.HasIndex("BundleImportId")
+ .HasDatabaseName("IX_AuditLogEntries_BundleImportId");
+
+ b.HasIndex("EntityId");
+
+ b.HasIndex("EntityType");
+
+ b.HasIndex("Timestamp");
+
+ b.HasIndex("User");
+
+ b.ToTable("AuditLogEntries");
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.ScadaBridge.Commons.Entities.Audit.SiteCall", b =>
+ {
+ b.Property("TrackedOperationId")
+ .HasMaxLength(36)
+ .IsUnicode(false)
+ .HasColumnType("varchar(36)");
+
+ b.Property("Channel")
+ .IsRequired()
+ .HasMaxLength(32)
+ .IsUnicode(false)
+ .HasColumnType("varchar(32)");
+
+ b.Property("CreatedAtUtc")
+ .HasColumnType("datetime2");
+
+ b.Property("HttpStatus")
+ .HasColumnType("int");
+
+ b.Property("IngestedAtUtc")
+ .HasColumnType("datetime2");
+
+ b.Property("LastError")
+ .HasMaxLength(1024)
+ .HasColumnType("nvarchar(1024)");
+
+ b.Property("RetryCount")
+ .HasColumnType("int");
+
+ b.Property("SourceNode")
+ .HasMaxLength(64)
+ .IsUnicode(false)
+ .HasColumnType("varchar(64)");
+
+ b.Property("SourceSite")
+ .IsRequired()
+ .HasMaxLength(64)
+ .IsUnicode(false)
+ .HasColumnType("varchar(64)");
+
+ b.Property("Status")
+ .IsRequired()
+ .HasMaxLength(32)
+ .IsUnicode(false)
+ .HasColumnType("varchar(32)");
+
+ b.Property("Target")
+ .IsRequired()
+ .HasMaxLength(256)
+ .IsUnicode(false)
+ .HasColumnType("varchar(256)");
+
+ b.Property("TerminalAtUtc")
+ .HasColumnType("datetime2");
+
+ b.Property("UpdatedAtUtc")
+ .HasColumnType("datetime2");
+
+ b.HasKey("TrackedOperationId");
+
+ b.HasIndex("CreatedAtUtc")
+ .HasDatabaseName("IX_SiteCalls_NonTerminal")
+ .HasFilter("[TerminalAtUtc] IS NULL");
+
+ SqlServerIndexBuilderExtensions.IncludeProperties(b.HasIndex("CreatedAtUtc"), new[] { "SourceSite", "SourceNode", "Status" });
+
+ b.HasIndex("TerminalAtUtc")
+ .HasDatabaseName("IX_SiteCalls_Terminal")
+ .HasFilter("[TerminalAtUtc] IS NOT NULL");
+
+ b.HasIndex("CreatedAtUtc", "TrackedOperationId")
+ .IsDescending()
+ .HasDatabaseName("IX_SiteCalls_Created_TrackedOperationId");
+
+ b.HasIndex("SourceSite", "CreatedAtUtc")
+ .IsDescending(false, true)
+ .HasDatabaseName("IX_SiteCalls_Source_Created");
+
+ b.HasIndex("Status", "UpdatedAtUtc")
+ .IsDescending(false, true)
+ .HasDatabaseName("IX_SiteCalls_Status_Updated");
+
+ b.ToTable("SiteCalls", (string)null);
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.ScadaBridge.Commons.Entities.Deployment.DeployedConfigSnapshot", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("ConfigurationJson")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("DeployedAt")
+ .HasColumnType("datetimeoffset");
+
+ b.Property("DeploymentId")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("InstanceId")
+ .HasColumnType("int");
+
+ b.Property("RevisionHash")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DeploymentId");
+
+ b.HasIndex("InstanceId")
+ .IsUnique();
+
+ b.ToTable("DeployedConfigSnapshots");
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.ScadaBridge.Commons.Entities.Deployment.DeploymentRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("CompletedAt")
+ .HasColumnType("datetimeoffset");
+
+ b.Property("DeployedAt")
+ .HasColumnType("datetimeoffset");
+
+ b.Property("DeployedBy")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("DeploymentId")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("ErrorMessage")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("InstanceId")
+ .HasColumnType("int");
+
+ b.Property("RevisionHash")
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .IsRequired()
+ .ValueGeneratedOnAddOrUpdate()
+ .HasColumnType("rowversion");
+
+ b.Property("Status")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("nvarchar(50)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DeploymentId")
+ .IsUnique();
+
+ b.HasIndex("InstanceId");
+
+ b.HasIndex("DeployedAt", "Id")
+ .IsDescending()
+ .HasDatabaseName("IX_DeploymentRecords_DeployedAt_Id");
+
+ b.ToTable("DeploymentRecords");
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.ScadaBridge.Commons.Entities.Deployment.PendingDeployment", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("ConfigurationJson")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("CreatedAtUtc")
+ .HasColumnType("datetimeoffset");
+
+ b.Property("DeploymentId")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("ExpiresAtUtc")
+ .HasColumnType("datetimeoffset");
+
+ b.Property("InstanceId")
+ .HasColumnType("int");
+
+ b.Property("RevisionHash")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("Token")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("nvarchar(128)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DeploymentId")
+ .IsUnique();
+
+ b.HasIndex("ExpiresAtUtc");
+
+ b.HasIndex("InstanceId");
+
+ b.ToTable("PendingDeployments", (string)null);
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.ScadaBridge.Commons.Entities.Deployment.SystemArtifactDeploymentRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("ArtifactType")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("DeployedAt")
+ .HasColumnType("datetimeoffset");
+
+ b.Property("DeployedBy")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("PerSiteStatus")
+ .HasMaxLength(4000)
+ .HasColumnType("nvarchar(4000)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DeployedAt");
+
+ b.ToTable("SystemArtifactDeploymentRecords");
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.ScadaBridge.Commons.Entities.ExternalSystems.DatabaseConnectionDefinition", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("ConnectionString")
+ .IsRequired()
+ .HasMaxLength(8000)
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("MaxRetries")
+ .HasColumnType("int");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("RetryDelay")
+ .HasColumnType("time");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .IsUnique();
+
+ b.ToTable("DatabaseConnectionDefinitions");
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.ScadaBridge.Commons.Entities.ExternalSystems.ExternalSystemDefinition", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("AuthConfiguration")
+ .HasMaxLength(8000)
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("AuthType")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("nvarchar(50)");
+
+ b.Property("EndpointUrl")
+ .IsRequired()
+ .HasMaxLength(2000)
+ .HasColumnType("nvarchar(2000)");
+
+ b.Property("MaxRetries")
+ .HasColumnType("int");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("RetryDelay")
+ .HasColumnType("time");
+
+ b.Property("TimeoutSeconds")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int")
+ .HasDefaultValue(0);
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .IsUnique();
+
+ b.ToTable("ExternalSystemDefinitions");
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.ScadaBridge.Commons.Entities.ExternalSystems.ExternalSystemMethod", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("ExternalSystemDefinitionId")
+ .HasColumnType("int");
+
+ b.Property("HttpMethod")
+ .IsRequired()
+ .HasMaxLength(10)
+ .HasColumnType("nvarchar(10)");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("ParameterDefinitions")
+ .HasMaxLength(4000)
+ .HasColumnType("nvarchar(4000)");
+
+ b.Property("Path")
+ .IsRequired()
+ .HasMaxLength(2000)
+ .HasColumnType("nvarchar(2000)");
+
+ b.Property("ReturnDefinition")
+ .HasMaxLength(4000)
+ .HasColumnType("nvarchar(4000)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ExternalSystemDefinitionId", "Name")
+ .IsUnique();
+
+ b.ToTable("ExternalSystemMethods");
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.ScadaBridge.Commons.Entities.InboundApi.ApiMethod", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("ParameterDefinitions")
+ .HasMaxLength(4000)
+ .HasColumnType("nvarchar(4000)");
+
+ b.Property("ReturnDefinition")
+ .HasMaxLength(4000)
+ .HasColumnType("nvarchar(4000)");
+
+ b.Property("Script")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("TimeoutSeconds")
+ .HasColumnType("int");
+
+ b.Property("TraceAllCalls")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bit")
+ .HasDefaultValue(false);
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .IsUnique();
+
+ b.ToTable("ApiMethods");
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.ScadaBridge.Commons.Entities.Instances.Area", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("ParentAreaId")
+ .HasColumnType("int");
+
+ b.Property("SiteId")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ParentAreaId");
+
+ b.HasIndex("SiteId", "ParentAreaId", "Name")
+ .IsUnique()
+ .HasFilter("[ParentAreaId] IS NOT NULL");
+
+ b.ToTable("Areas");
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.ScadaBridge.Commons.Entities.Instances.Instance", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("AreaId")
+ .HasColumnType("int");
+
+ b.Property("SiteId")
+ .HasColumnType("int");
+
+ b.Property("State")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("nvarchar(50)");
+
+ b.Property("TemplateId")
+ .HasColumnType("int");
+
+ b.Property("UniqueName")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AreaId");
+
+ b.HasIndex("SiteId");
+
+ b.HasIndex("TemplateId");
+
+ b.HasIndex("UniqueName")
+ .IsUnique();
+
+ b.ToTable("Instances");
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.ScadaBridge.Commons.Entities.Instances.InstanceAlarmOverride", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("AlarmCanonicalName")
+ .IsRequired()
+ .HasMaxLength(400)
+ .HasColumnType("nvarchar(400)");
+
+ b.Property("InstanceId")
+ .HasColumnType("int");
+
+ b.Property("IsMesReportableOverride")
+ .HasColumnType("bit");
+
+ b.Property("PriorityLevelOverride")
+ .HasColumnType("int");
+
+ b.Property("TriggerConfigurationOverride")
+ .HasMaxLength(4000)
+ .HasColumnType("nvarchar(4000)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("InstanceId", "AlarmCanonicalName")
+ .IsUnique();
+
+ b.ToTable("InstanceAlarmOverrides");
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.ScadaBridge.Commons.Entities.Instances.InstanceAttributeOverride", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("AttributeName")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("ElementDataType")
+ .HasMaxLength(50)
+ .HasColumnType("nvarchar(50)");
+
+ b.Property("InstanceId")
+ .HasColumnType("int");
+
+ b.Property("OverrideValue")
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("InstanceId", "AttributeName")
+ .IsUnique();
+
+ b.ToTable("InstanceAttributeOverrides");
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.ScadaBridge.Commons.Entities.Instances.InstanceConnectionBinding", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("AttributeName")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("DataConnectionId")
+ .HasColumnType("int");
+
+ b.Property("DataSourceReferenceOverride")
+ .HasMaxLength(512)
+ .HasColumnType("nvarchar(512)");
+
+ b.Property("InstanceId")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DataConnectionId");
+
+ b.HasIndex("InstanceId", "AttributeName")
+ .IsUnique();
+
+ b.ToTable("InstanceConnectionBindings");
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.ScadaBridge.Commons.Entities.Instances.InstanceNativeAlarmSourceOverride", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("ConditionFilterOverride")
+ .HasMaxLength(1000)
+ .HasColumnType("nvarchar(1000)");
+
+ b.Property("ConnectionNameOverride")
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("InstanceId")
+ .HasColumnType("int");
+
+ b.Property("SourceCanonicalName")
+ .IsRequired()
+ .HasMaxLength(400)
+ .HasColumnType("nvarchar(400)");
+
+ b.Property("SourceReferenceOverride")
+ .HasMaxLength(1000)
+ .HasColumnType("nvarchar(1000)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("InstanceId", "SourceCanonicalName")
+ .IsUnique();
+
+ b.ToTable("InstanceNativeAlarmSourceOverrides");
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.ScadaBridge.Commons.Entities.Kpi.KpiRollupHourly", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("HourStartUtc")
+ .HasColumnType("datetime2");
+
+ b.Property("MaxValue")
+ .HasColumnType("float");
+
+ b.Property("Metric")
+ .IsRequired()
+ .HasMaxLength(64)
+ .IsUnicode(false)
+ .HasColumnType("varchar(64)");
+
+ b.Property("MinValue")
+ .HasColumnType("float");
+
+ b.Property("SampleCount")
+ .HasColumnType("int");
+
+ b.Property("Scope")
+ .IsRequired()
+ .HasMaxLength(16)
+ .IsUnicode(false)
+ .HasColumnType("varchar(16)");
+
+ b.Property("ScopeKey")
+ .HasMaxLength(64)
+ .IsUnicode(false)
+ .HasColumnType("varchar(64)");
+
+ b.Property("Source")
+ .IsRequired()
+ .HasMaxLength(64)
+ .IsUnicode(false)
+ .HasColumnType("varchar(64)");
+
+ b.Property("Value")
+ .HasColumnType("float");
+
+ b.HasKey("Id");
+
+ b.HasIndex("HourStartUtc")
+ .HasDatabaseName("IX_KpiRollupHourly_Hour");
+
+ b.HasIndex("Source", "Metric", "Scope", "ScopeKey", "HourStartUtc")
+ .IsUnique()
+ .HasDatabaseName("IX_KpiRollupHourly_Series");
+
+ b.ToTable("KpiRollupHourly", (string)null);
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.ScadaBridge.Commons.Entities.Kpi.KpiSample", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("CapturedAtUtc")
+ .HasColumnType("datetime2");
+
+ b.Property("Metric")
+ .IsRequired()
+ .HasMaxLength(64)
+ .IsUnicode(false)
+ .HasColumnType("varchar(64)");
+
+ b.Property("Scope")
+ .IsRequired()
+ .HasMaxLength(16)
+ .IsUnicode(false)
+ .HasColumnType("varchar(16)");
+
+ b.Property("ScopeKey")
+ .HasMaxLength(64)
+ .IsUnicode(false)
+ .HasColumnType("varchar(64)");
+
+ b.Property("Source")
+ .IsRequired()
+ .HasMaxLength(64)
+ .IsUnicode(false)
+ .HasColumnType("varchar(64)");
+
+ b.Property("Value")
+ .HasColumnType("float");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CapturedAtUtc")
+ .HasDatabaseName("IX_KpiSample_Captured");
+
+ b.HasIndex("Source", "Metric", "Scope", "ScopeKey", "CapturedAtUtc")
+ .HasDatabaseName("IX_KpiSample_Series");
+
+ b.ToTable("KpiSample", (string)null);
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.ScadaBridge.Commons.Entities.Notifications.Notification", b =>
+ {
+ b.Property("NotificationId")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("Body")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetimeoffset");
+
+ b.Property("DeliveredAt")
+ .HasColumnType("datetimeoffset");
+
+ b.Property("LastAttemptAt")
+ .HasColumnType("datetimeoffset");
+
+ b.Property("LastError")
+ .HasMaxLength(4000)
+ .HasColumnType("nvarchar(4000)");
+
+ b.Property("ListName")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("NextAttemptAt")
+ .HasColumnType("datetimeoffset");
+
+ b.Property("OriginExecutionId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("OriginParentExecutionId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("ResolvedTargets")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("RetryCount")
+ .HasColumnType("int");
+
+ b.Property("SiteEnqueuedAt")
+ .HasColumnType("datetimeoffset");
+
+ b.Property("SourceInstanceId")
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("SourceNode")
+ .HasMaxLength(64)
+ .IsUnicode(false)
+ .HasColumnType("varchar(64)");
+
+ b.Property("SourceScript")
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("SourceSiteId")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("Status")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("nvarchar(32)");
+
+ b.Property("Subject")
+ .IsRequired()
+ .HasMaxLength(1000)
+ .HasColumnType("nvarchar(1000)");
+
+ b.Property("Type")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("nvarchar(32)");
+
+ b.Property("TypeData")
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("NotificationId");
+
+ b.HasIndex("DeliveredAt")
+ .HasDatabaseName("IX_Notifications_Delivered")
+ .HasFilter("[Status] = 'Delivered'");
+
+ b.HasIndex("CreatedAt", "NotificationId")
+ .IsDescending()
+ .HasDatabaseName("IX_Notifications_Created_NotificationId");
+
+ b.HasIndex("SourceSiteId", "CreatedAt");
+
+ b.HasIndex("Status", "NextAttemptAt");
+
+ b.ToTable("Notifications");
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.ScadaBridge.Commons.Entities.Notifications.NotificationList", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("Type")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("nvarchar(32)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .IsUnique();
+
+ b.ToTable("NotificationLists");
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.ScadaBridge.Commons.Entities.Notifications.NotificationRecipient", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("EmailAddress")
+ .HasMaxLength(500)
+ .HasColumnType("nvarchar(500)");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("NotificationListId")
+ .HasColumnType("int");
+
+ b.Property("PhoneNumber")
+ .HasMaxLength(32)
+ .HasColumnType("nvarchar(32)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NotificationListId");
+
+ b.ToTable("NotificationRecipients");
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.ScadaBridge.Commons.Entities.Notifications.SmsConfiguration", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("AccountSid")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("ApiBaseUrl")
+ .HasMaxLength(500)
+ .HasColumnType("nvarchar(500)");
+
+ b.Property("AuthToken")
+ .HasMaxLength(8000)
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ConnectionTimeoutSeconds")
+ .HasColumnType("int");
+
+ b.Property("FromNumber")
+ .HasMaxLength(32)
+ .HasColumnType("nvarchar(32)");
+
+ b.Property("MaxRetries")
+ .HasColumnType("int");
+
+ b.Property("MessagingServiceSid")
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("RetryDelay")
+ .HasColumnType("time");
+
+ b.HasKey("Id");
+
+ b.ToTable("SmsConfigurations");
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.ScadaBridge.Commons.Entities.Notifications.SmtpConfiguration", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("AuthType")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("nvarchar(50)");
+
+ b.Property("ConnectionTimeoutSeconds")
+ .HasColumnType("int");
+
+ b.Property("Credentials")
+ .HasMaxLength(8000)
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("FromAddress")
+ .IsRequired()
+ .HasMaxLength(500)
+ .HasColumnType("nvarchar(500)");
+
+ b.Property("Host")
+ .IsRequired()
+ .HasMaxLength(500)
+ .HasColumnType("nvarchar(500)");
+
+ b.Property("MaxConcurrentConnections")
+ .HasColumnType("int");
+
+ b.Property("MaxRetries")
+ .HasColumnType("int");
+
+ b.Property("OAuth2Authority")
+ .HasMaxLength(500)
+ .HasColumnType("nvarchar(500)");
+
+ b.Property("OAuth2Scope")
+ .HasMaxLength(500)
+ .HasColumnType("nvarchar(500)");
+
+ b.Property("Port")
+ .HasColumnType("int");
+
+ b.Property("RetryDelay")
+ .HasColumnType("time");
+
+ b.Property("TlsMode")
+ .HasMaxLength(50)
+ .HasColumnType("nvarchar(50)");
+
+ b.Property("Transport")
+ .HasMaxLength(50)
+ .HasColumnType("nvarchar(50)");
+
+ b.HasKey("Id");
+
+ b.ToTable("SmtpConfigurations");
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.ScadaBridge.Commons.Entities.Schemas.SharedSchema", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("SchemaJson")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Scope")
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .IsUnique();
+
+ b.ToTable("SharedSchemas");
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.ScadaBridge.Commons.Entities.Scripts.SharedScript", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("Code")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("ParameterDefinitions")
+ .HasMaxLength(4000)
+ .HasColumnType("nvarchar(4000)");
+
+ b.Property("ReturnDefinition")
+ .HasMaxLength(4000)
+ .HasColumnType("nvarchar(4000)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .IsUnique();
+
+ b.ToTable("SharedScripts");
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.ScadaBridge.Commons.Entities.SecuredWrites.PendingSecuredWrite", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("ConnectionName")
+ .IsRequired()
+ .HasMaxLength(128)
+ .IsUnicode(false)
+ .HasColumnType("varchar(128)");
+
+ b.Property("DecidedAtUtc")
+ .HasColumnType("datetime2");
+
+ b.Property("ExecutedAtUtc")
+ .HasColumnType("datetime2");
+
+ b.Property("ExecutionError")
+ .HasMaxLength(2048)
+ .IsUnicode(false)
+ .HasColumnType("varchar(2048)");
+
+ b.Property("OperatorComment")
+ .HasMaxLength(1024)
+ .IsUnicode(false)
+ .HasColumnType("varchar(1024)");
+
+ b.Property("OperatorUser")
+ .IsRequired()
+ .HasMaxLength(256)
+ .IsUnicode(false)
+ .HasColumnType("varchar(256)");
+
+ b.Property("SiteId")
+ .IsRequired()
+ .HasMaxLength(128)
+ .IsUnicode(false)
+ .HasColumnType("varchar(128)");
+
+ b.Property("Status")
+ .IsRequired()
+ .HasMaxLength(32)
+ .IsUnicode(false)
+ .HasColumnType("varchar(32)");
+
+ b.Property("SubmittedAtUtc")
+ .HasColumnType("datetime2");
+
+ b.Property("TagPath")
+ .IsRequired()
+ .HasMaxLength(512)
+ .IsUnicode(false)
+ .HasColumnType("varchar(512)");
+
+ b.Property("ValueJson")
+ .IsRequired()
+ .HasMaxLength(4000)
+ .IsUnicode(false)
+ .HasColumnType("varchar(4000)");
+
+ b.Property("ValueType")
+ .IsRequired()
+ .HasMaxLength(128)
+ .IsUnicode(false)
+ .HasColumnType("varchar(128)");
+
+ b.Property("VerifierComment")
+ .HasMaxLength(1024)
+ .IsUnicode(false)
+ .HasColumnType("varchar(1024)");
+
+ b.Property("VerifierUser")
+ .HasMaxLength(256)
+ .IsUnicode(false)
+ .HasColumnType("varchar(256)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("SiteId")
+ .HasDatabaseName("IX_PendingSecuredWrites_Site");
+
+ b.HasIndex("Status", "SubmittedAtUtc")
+ .HasDatabaseName("IX_PendingSecuredWrites_Status_Submitted");
+
+ b.ToTable("PendingSecuredWrites", (string)null);
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.ScadaBridge.Commons.Entities.Security.LdapGroupMapping", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("LdapGroupName")
+ .IsRequired()
+ .HasMaxLength(500)
+ .HasColumnType("nvarchar(500)");
+
+ b.Property("Role")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("LdapGroupName")
+ .IsUnique();
+
+ b.ToTable("LdapGroupMappings");
+
+ b.HasData(
+ new
+ {
+ Id = 1,
+ LdapGroupName = "SCADA-Admins",
+ Role = "Administrator"
+ },
+ new
+ {
+ Id = 2,
+ LdapGroupName = "SCADA-Designers",
+ Role = "Designer"
+ },
+ new
+ {
+ Id = 3,
+ LdapGroupName = "SCADA-Deploy-All",
+ Role = "Deployer"
+ },
+ new
+ {
+ Id = 4,
+ LdapGroupName = "SCADA-Deploy-SiteA",
+ Role = "Deployer"
+ },
+ new
+ {
+ Id = 5,
+ LdapGroupName = "SCADA-Viewers",
+ Role = "Viewer"
+ });
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.ScadaBridge.Commons.Entities.Security.SiteScopeRule", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("LdapGroupMappingId")
+ .HasColumnType("int");
+
+ b.Property("SiteId")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("SiteId");
+
+ b.HasIndex("LdapGroupMappingId", "SiteId")
+ .IsUnique();
+
+ b.ToTable("SiteScopeRules");
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.ScadaBridge.Commons.Entities.Sites.DataConnection", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("BackupConfiguration")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("FailoverRetryCount")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int")
+ .HasDefaultValue(3);
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("PrimaryConfiguration")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Protocol")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("nvarchar(50)");
+
+ b.Property("SiteId")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("SiteId", "Name")
+ .IsUnique();
+
+ b.ToTable("DataConnections");
+ });
+
+ modelBuilder.Entity("ZB.MOM.WW.ScadaBridge.Commons.Entities.Sites.Site", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("ArtifactRevisionHash")
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property