diff --git a/docs/migration/ravendb-to-sql-migration-instructions.md b/docs/migration/ravendb-to-sql-migration-instructions.md
index 7b3fc8c7ae..75da2f16f2 100644
--- a/docs/migration/ravendb-to-sql-migration-instructions.md
+++ b/docs/migration/ravendb-to-sql-migration-instructions.md
@@ -44,10 +44,11 @@ The report prints the RavenDB server version, whether the source is embedded or
## If the report fails
-The error names the setting to fix:
+The error says what to fix:
- **"has no database named ..."**: the database name setting it quotes is wrong.
- **"refused its client certificate access ..."**: grant that certificate Read access to the database, or supply a certificate that has it.
+- **"could not start a server for the embedded database ..."**: a ServiceControl instance is still running against that data directory and holds it. Stop the instance, run the report, then start it again.
## Not available yet
diff --git a/docs/migration/ravendb-to-sql-migration-overview.md b/docs/migration/ravendb-to-sql-migration-overview.md
index de2c322b14..33b22c8dc3 100644
--- a/docs/migration/ravendb-to-sql-migration-overview.md
+++ b/docs/migration/ravendb-to-sql-migration-overview.md
@@ -75,12 +75,12 @@ The copier runs inside the ServiceControl host, so every row and every message b
4. Run the [dry run](#dry-run). It reports what it resolved as a source, what each category holds, and an estimate of how long ServiceControl will be closed. Read [what the dry run reports](#dry-run) before booking an outage around its estimate.
5. Start ServiceControl (`MigrationMode=true`).
6. Every check runs before a single row moves. If one fails the host does not start and names which, having copied nothing, so a wrong database name or unconfigured body storage costs a restart rather than a half-finished migration.
-7. The copying of [required data](#required) starts, with ServiceControl still closed. This is assumed to be a small amount of data.
-8. ServiceControl opens, and whatever [optional data](#optional) they asked for is copied in the background while the instance runs normally. They can watch it from ServicePulse custom checks and events, but not steer it.
+7. The copying of [required data](#required) starts, with ServiceControl still closed: the copy runs inside that same start, before the API begins listening and before any background service runs. This is assumed to be a small amount of data.
+8. ServiceControl opens by itself the moment the required copy finishes, with no second restart to perform, and whatever [optional data](#optional) they asked for is copied in the background while the instance runs normally. They can watch it from ServicePulse custom checks and events, but not steer it.
9. They run the verification pass once the background job has completed, which reports row counts on both sides category by category, accounting for deliberate skips so a difference is explained rather than reported as a fault, then set `MigrationMode=false` and restart. It tolerates more rows in SQL than in RavenDB, because RavenDB keeps expiring rows the copier already took.
10. RavenDB data can be removed.
-- If `MigrationMode=false` is set while a selected category is still incomplete, the host refuses to start and names exactly what is outstanding.
+- If `MigrationMode=false` is set while a selected category is still incomplete, the startup is gated: it refuses and names exactly what is outstanding, or, where the [free abort](#the-one-point-you-can-go-back) is still open, starts with a warning that says so. See [turning migration mode off is a gated startup too](#turning-migration-mode-off-is-a-gated-startup-too).
- A category that ended *complete with errors* counts as complete and does not block, though its skipped count is printed so the loss is stated rather than silent.
- An explicit override exists for a customer who has changed their mind and accepts leaving data behind. It marks the outstanding categories as abandoned, which is a deliberate end state rather than a failure, so the progress check settles and the guard stays armed for any later migration.
- **Steps 5 to 7 are the abort window**, which is not the override above: see [the one point you can go back](#the-one-point-you-can-go-back).
@@ -122,17 +122,24 @@ flowchart TB
```mermaid
flowchart TB
- A["Restart with MigrationMode = true"] --> B["Open the SQL target, exactly as today"]
+ A["Restart"] --> M{"MigrationMode?"}
+
+ M -->|"On"| B["Open the SQL target, exactly as today"]
B --> C["Open the old RavenDB, read only"]
C --> D{"All checks pass?"}
D -->|"No"| E["Host does not start.
Says which check failed.
Nothing has been copied."]
- D -->|"Yes"| F["Copy what cannot be recreated.
Minutes. ServiceControl still closed."]
- F --> G["ServiceControl opens.
New failed messages go straight to SQL."]
+ D -->|"Yes"| F["Copy what cannot be recreated.
Minutes. The API is not listening yet
and no hosted service has started."]
+ F -->|"no restart: the same start carries on"| G["ServiceControl opens.
New failed messages go straight to SQL."]
G --> H["Copy the selected history in the background,
throttled behind normal ingestion"]
- H --> I["Verify row counts on both sides,
category by category"]
- I --> J{"MigrationMode = false,
everything complete?"}
- J -->|"No"| K["Host does not start.
Names what is outstanding.
An override exists."]
- J -->|"Yes"| L["RavenDB is never opened again"]
+ H --> I["Verify row counts on both sides,
category by category,
then set MigrationMode = false and restart,
which comes back through this same gate"]
+
+ M -->|"Off"| N{"Any checkpoint row
still outstanding?"}
+ N -->|"No, or no checkpoint table at all"| L["ServiceControl opens.
RavenDB is never opened again."]
+ N -->|"Yes"| O{"Override set?"}
+ O -->|"Yes"| P["Records each outstanding category as abandoned,
logs what each one leaves behind,
and opens."]
+ O -->|"No"| Q{"Has this instance
ever opened on SQL?"}
+ Q -->|"No, so the abort is still free"| R["Opens, with a warning naming the two moves:
stop now and point PersistenceType back at RavenDB,
or carry on and lose the way back."]
+ Q -->|"Yes"| S["Host does not start.
Names every outstanding category,
its counts, and every route out."]
```
**Checked before a single row moves:**
@@ -145,6 +152,20 @@ flowchart TB
- The selected categories are valid
- `RetryHistoryDepth` is greater than zero. At zero or less, the first completed retry after the migration deletes the entire copied retry history, and no row count would ever show it
+### Turning migration mode off is a gated startup too
+
+*The right-hand branch above is the half a customer meets last and expects least, so it is worth reading before the migration starts rather than at the end of one.*
+
+Every startup on a SQL Server or PostgreSQL instance looks at the checkpoint table before ServiceControl opens, whether `MigrationMode` is on or off. That is what stops a migration ending by accident, and it costs nothing on an instance that has never migrated: a RavenDB instance has no checkpoint table at all, a SQL instance whose schema predates this feature says it holds no checkpoint state, and a SQL instance whose categories all finished has nothing outstanding. All three start exactly as they do today.
+
+With `MigrationMode` off and at least one category still outstanding, one of three things happens, and each is said out loud at startup rather than discovered weeks later:
+
+- **The override is set.** Every outstanding category is recorded as abandoned, with its copied and skipped counts left as they are, and the host starts. Each one is logged saying what state it was in, how much it had copied, and that whatever it had not copied stays only in RavenDB. Abandoning is final: selecting that category in a later migration does not copy it again.
+- **The override is not set, and this instance has never opened on SQL.** This is the [free abort](#the-one-point-you-can-go-back), so the host starts and warns rather than refusing. The warning names the two moves: stop now and point `PersistenceType` back at RavenDB, which discards the partial copy and costs nothing else, or carry on, which opens ServiceControl on a partly copied database and ends the free abort. It deliberately does not mention the override, because at that moment nothing is lost yet.
+- **The override is not set, and this instance has already opened on SQL.** The host does not start. The error names every outstanding category, its state, its copied and skipped counts and its last error, and then the three routes out: restart with `MigrationMode=true` to let the copy finish or to resume a halted category once its cause is fixed, set the override to abandon what is outstanding and start without it, or, if RavenDB is already gone, abandon, because that is the only exit left.
+
+**Which is why the source stays until verification passes.** A customer who decommissions RavenDB while a category is outstanding has both doors shut: `MigrationMode=true` cannot start, because it opens the source before it copies anything, and `MigrationMode=false` refuses. Abandoning is then the only way to start the instance, and it is a real loss whose size is the counts in that message.
+
## Data to be migrated (Categories)
### Required
diff --git a/src/ServiceControl.Persistence.EFCore.PostgreSql/Migrations/20260916125150_AddMigrationCheckpoints.Designer.cs b/src/ServiceControl.Persistence.EFCore.PostgreSql/Migrations/20260916125150_AddMigrationCheckpoints.Designer.cs
new file mode 100644
index 0000000000..a8a3dd5b3b
--- /dev/null
+++ b/src/ServiceControl.Persistence.EFCore.PostgreSql/Migrations/20260916125150_AddMigrationCheckpoints.Designer.cs
@@ -0,0 +1,976 @@
+//
+using System;
+using System.Collections.Generic;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+using ServiceControl.Persistence.EFCore.PostgreSql;
+
+#nullable disable
+
+namespace ServiceControl.Persistence.EFCore.PostgreSql.Migrations
+{
+ [DbContext(typeof(PostgreSqlServiceControlDbContext))]
+ [Migration("20260916125150_AddMigrationCheckpoints")]
+ partial class AddMigrationCheckpoints
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "10.0.11")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.ArchiveOperationEntity", b =>
+ {
+ b.Property("RequestId")
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)")
+ .HasColumnName("request_id");
+
+ b.Property("ArchiveType")
+ .HasColumnType("integer")
+ .HasColumnName("archive_type");
+
+ b.Property("OperationType")
+ .HasColumnType("integer")
+ .HasColumnName("operation_type");
+
+ b.Property("CurrentBatch")
+ .HasColumnType("integer")
+ .HasColumnName("current_batch");
+
+ b.Property("GroupName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("group_name");
+
+ b.Property("InitiatedById")
+ .HasMaxLength(450)
+ .HasColumnType("character varying(450)")
+ .HasColumnName("initiated_by_id");
+
+ b.Property("InitiatedByName")
+ .HasMaxLength(450)
+ .HasColumnType("character varying(450)")
+ .HasColumnName("initiated_by_name");
+
+ b.Property("NumberOfBatches")
+ .HasColumnType("integer")
+ .HasColumnName("number_of_batches");
+
+ b.Property("NumberOfMessagesProcessed")
+ .HasColumnType("integer")
+ .HasColumnName("number_of_messages_processed");
+
+ b.Property("OperationId")
+ .HasMaxLength(450)
+ .HasColumnType("character varying(450)")
+ .HasColumnName("operation_id");
+
+ b.Property("Started")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("started");
+
+ b.Property("TotalNumberOfMessages")
+ .HasColumnType("integer")
+ .HasColumnName("total_number_of_messages");
+
+ b.HasKey("RequestId", "ArchiveType", "OperationType")
+ .HasName("pk_archive_operations");
+
+ b.HasIndex("Started")
+ .HasDatabaseName("ix_archive_operations_started");
+
+ b.ToTable("archive_operations", (string)null);
+ });
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.CustomCheckEntity", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property("Category")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("category");
+
+ b.Property("CustomCheckId")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("custom_check_id");
+
+ b.Property("FailureReason")
+ .HasColumnType("text")
+ .HasColumnName("failure_reason");
+
+ b.Property("OriginatingEndpointHost")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("originating_endpoint_host");
+
+ b.Property("OriginatingEndpointHostId")
+ .HasColumnType("uuid")
+ .HasColumnName("originating_endpoint_host_id");
+
+ b.Property("OriginatingEndpointName")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("originating_endpoint_name");
+
+ b.Property("ReportedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("reported_at");
+
+ b.Property("Status")
+ .HasColumnType("integer")
+ .HasColumnName("status");
+
+ b.HasKey("Id")
+ .HasName("pk_custom_checks");
+
+ b.HasIndex("ReportedAt")
+ .HasDatabaseName("ix_custom_checks_reported_at");
+
+ b.HasIndex("Status", "ReportedAt")
+ .HasDatabaseName("ix_custom_checks_status_reported_at");
+
+ b.ToTable("custom_checks", (string)null);
+ });
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.EndpointSettingsEntity", b =>
+ {
+ b.Property("Name")
+ .HasMaxLength(450)
+ .HasColumnType("character varying(450)")
+ .HasColumnName("name");
+
+ b.Property("TrackInstances")
+ .HasColumnType("boolean")
+ .HasColumnName("track_instances");
+
+ b.HasKey("Name")
+ .HasName("pk_endpoint_settings");
+
+ b.ToTable("endpoint_settings", (string)null);
+ });
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.EventLogItemEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasColumnName("id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Category")
+ .IsRequired()
+ .HasMaxLength(450)
+ .HasColumnType("character varying(450)")
+ .HasColumnName("category");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("description");
+
+ b.Property("EventType")
+ .IsRequired()
+ .HasMaxLength(450)
+ .HasColumnType("character varying(450)")
+ .HasColumnName("event_type");
+
+ b.Property("RaisedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("raised_at");
+
+ b.PrimitiveCollection>("RelatedTo")
+ .IsRequired()
+ .HasColumnType("text[]")
+ .HasColumnName("related_to");
+
+ b.Property("Severity")
+ .HasColumnType("integer")
+ .HasColumnName("severity");
+
+ b.HasKey("Id")
+ .HasName("pk_event_log_items");
+
+ b.HasIndex("RaisedAt", "Id")
+ .IsDescending()
+ .HasDatabaseName("ix_event_log_items_raised_at_id");
+
+ b.ToTable("event_log_items", (string)null);
+ });
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.ExternalIntegrationDispatchRequestEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasColumnName("id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("DispatchContextJson")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("dispatch_context_json");
+
+ b.Property("DispatchContextTypeName")
+ .IsRequired()
+ .HasMaxLength(450)
+ .HasColumnType("character varying(450)")
+ .HasColumnName("dispatch_context_type_name");
+
+ b.HasKey("Id")
+ .HasName("pk_external_integration_dispatch_requests");
+
+ b.ToTable("external_integration_dispatch_requests", (string)null);
+ });
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.FailedErrorImportEntity", b =>
+ {
+ b.Property("UniqueMessageId")
+ .HasColumnType("uuid")
+ .HasColumnName("unique_message_id");
+
+ b.Property("Body")
+ .IsRequired()
+ .HasColumnType("bytea")
+ .HasColumnName("body");
+
+ b.Property("BodyStoredExternally")
+ .HasColumnType("boolean")
+ .HasColumnName("body_stored_externally");
+
+ b.Property("ExceptionInfo")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("exception_info");
+
+ b.Property("FailedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("failed_at");
+
+ b.Property("HeadersJson")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("headers_json");
+
+ b.Property("MessageId")
+ .IsRequired()
+ .HasMaxLength(450)
+ .HasColumnType("character varying(450)")
+ .HasColumnName("message_id");
+
+ b.HasKey("UniqueMessageId")
+ .HasName("pk_failed_error_imports");
+
+ b.HasIndex("FailedAt")
+ .HasDatabaseName("ix_failed_error_imports_failed_at");
+
+ b.ToTable("failed_error_imports", (string)null);
+ });
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.FailedMessageEditEntity", b =>
+ {
+ b.Property("UniqueMessageId")
+ .HasColumnType("uuid")
+ .HasColumnName("unique_message_id");
+
+ b.Property("EditId")
+ .IsRequired()
+ .HasMaxLength(450)
+ .HasColumnType("character varying(450)")
+ .HasColumnName("edit_id");
+
+ b.HasKey("UniqueMessageId")
+ .HasName("pk_failed_message_edits");
+
+ b.HasIndex("EditId")
+ .HasDatabaseName("ix_failed_message_edits_edit_id");
+
+ b.ToTable("failed_message_edits", (string)null);
+ });
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.FailedMessageEntity", b =>
+ {
+ b.Property("UniqueMessageId")
+ .HasColumnType("uuid")
+ .HasColumnName("unique_message_id");
+
+ b.Property("BodyContentType")
+ .HasMaxLength(450)
+ .HasColumnType("character varying(450)")
+ .HasColumnName("body_content_type");
+
+ b.Property("BodySize")
+ .HasColumnType("integer")
+ .HasColumnName("body_size");
+
+ b.Property("BodyStoredExternally")
+ .HasColumnType("boolean")
+ .HasColumnName("body_stored_externally");
+
+ b.Property("BodyText")
+ .HasColumnType("text")
+ .HasColumnName("body_text");
+
+ b.Property("ConversationId")
+ .HasMaxLength(450)
+ .HasColumnType("character varying(450)")
+ .HasColumnName("conversation_id");
+
+ b.Property("ExceptionMessage")
+ .HasColumnType("text")
+ .HasColumnName("exception_message");
+
+ b.Property("ExceptionType")
+ .HasColumnType("text")
+ .HasColumnName("exception_type");
+
+ b.Property("FailingEndpointAddress")
+ .IsRequired()
+ .HasMaxLength(450)
+ .HasColumnType("character varying(450)")
+ .HasColumnName("failing_endpoint_address");
+
+ b.Property("FirstTimeOfFailure")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("first_time_of_failure");
+
+ b.Property("HeadersJson")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("headers_json");
+
+ b.Property("IsSystemMessage")
+ .HasColumnType("boolean")
+ .HasColumnName("is_system_message");
+
+ b.Property("LastAttemptedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_attempted_at");
+
+ b.Property("LastModified")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_modified");
+
+ b.Property("LastTimeOfFailure")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_time_of_failure");
+
+ b.Property("MessageId")
+ .HasMaxLength(450)
+ .HasColumnType("character varying(450)")
+ .HasColumnName("message_id");
+
+ b.Property("MessageType")
+ .HasColumnType("text")
+ .HasColumnName("message_type");
+
+ b.Property("NumberOfProcessingAttempts")
+ .HasColumnType("integer")
+ .HasColumnName("number_of_processing_attempts");
+
+ b.Property("ReceivingEndpointHost")
+ .HasMaxLength(450)
+ .HasColumnType("character varying(450)")
+ .HasColumnName("receiving_endpoint_host");
+
+ b.Property("ReceivingEndpointHostId")
+ .HasColumnType("uuid")
+ .HasColumnName("receiving_endpoint_host_id");
+
+ b.Property("ReceivingEndpointName")
+ .HasMaxLength(450)
+ .HasColumnType("character varying(450)")
+ .HasColumnName("receiving_endpoint_name");
+
+ b.Property("SendingEndpointHost")
+ .HasMaxLength(450)
+ .HasColumnType("character varying(450)")
+ .HasColumnName("sending_endpoint_host");
+
+ b.Property("SendingEndpointHostId")
+ .HasColumnType("uuid")
+ .HasColumnName("sending_endpoint_host_id");
+
+ b.Property("SendingEndpointName")
+ .HasMaxLength(450)
+ .HasColumnType("character varying(450)")
+ .HasColumnName("sending_endpoint_name");
+
+ b.Property("Status")
+ .HasColumnType("integer")
+ .HasColumnName("status");
+
+ b.Property("StatusChangedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("status_changed_at");
+
+ b.Property("TimeSent")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("time_sent");
+
+ b.HasKey("UniqueMessageId")
+ .HasName("pk_failed_messages");
+
+ b.HasIndex("ConversationId")
+ .HasDatabaseName("ix_failed_messages_conversation_id");
+
+ b.HasIndex("FailingEndpointAddress")
+ .HasDatabaseName("ix_failed_messages_failing_endpoint_address");
+
+ b.HasIndex("ReceivingEndpointName")
+ .HasDatabaseName("ix_failed_messages_receiving_endpoint_name");
+
+ b.HasIndex("StatusChangedAt")
+ .HasDatabaseName("ix_failed_messages_status_changed_at")
+ .HasFilter("status IN (2, 4)");
+
+ b.HasIndex("TimeSent")
+ .HasDatabaseName("ix_failed_messages_time_sent");
+
+ b.HasIndex("Status", "LastModified")
+ .HasDatabaseName("ix_failed_messages_status_last_modified");
+
+ b.ToTable("failed_messages", (string)null);
+ });
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.FailedMessageGroupEntity", b =>
+ {
+ b.Property("FailedMessageUniqueId")
+ .HasColumnType("uuid")
+ .HasColumnName("failed_message_unique_id");
+
+ b.Property("GroupId")
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)")
+ .HasColumnName("group_id");
+
+ b.Property("Title")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("title");
+
+ b.Property("Type")
+ .IsRequired()
+ .HasMaxLength(255)
+ .HasColumnType("character varying(255)")
+ .HasColumnName("type");
+
+ b.HasKey("FailedMessageUniqueId", "GroupId")
+ .HasName("pk_failed_message_groups");
+
+ b.HasIndex("GroupId")
+ .HasDatabaseName("ix_failed_message_groups_group_id");
+
+ b.HasIndex("Type", "GroupId")
+ .HasDatabaseName("ix_failed_message_groups_type_group_id");
+
+ b.ToTable("failed_message_groups", (string)null);
+ });
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.FailedMessageRetryEntity", b =>
+ {
+ b.Property("UniqueMessageId")
+ .HasColumnType("uuid")
+ .HasColumnName("unique_message_id");
+
+ b.Property("RetryBatchId")
+ .HasColumnType("uuid")
+ .HasColumnName("retry_batch_id");
+
+ b.Property("StageAttempts")
+ .HasColumnType("integer")
+ .HasColumnName("stage_attempts");
+
+ b.HasKey("UniqueMessageId")
+ .HasName("pk_failed_message_retries");
+
+ b.HasIndex("RetryBatchId")
+ .HasDatabaseName("ix_failed_message_retries_retry_batch_id");
+
+ b.ToTable("failed_message_retries", (string)null);
+ });
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.GroupCommentEntity", b =>
+ {
+ b.Property("GroupId")
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)")
+ .HasColumnName("group_id");
+
+ b.Property("Comment")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("comment");
+
+ b.HasKey("GroupId")
+ .HasName("pk_group_comments");
+
+ b.ToTable("group_comments", (string)null);
+ });
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.HistoricRetryOperationEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasColumnName("id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CompletionTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("completion_time");
+
+ b.Property("Failed")
+ .HasColumnType("boolean")
+ .HasColumnName("failed");
+
+ b.Property("NumberOfMessagesProcessed")
+ .HasColumnType("integer")
+ .HasColumnName("number_of_messages_processed");
+
+ b.Property("Originator")
+ .HasColumnType("text")
+ .HasColumnName("originator");
+
+ b.Property("RequestId")
+ .IsRequired()
+ .HasMaxLength(400)
+ .HasColumnType("character varying(400)")
+ .HasColumnName("request_id");
+
+ b.Property("RetryType")
+ .HasColumnType("integer")
+ .HasColumnName("retry_type");
+
+ b.Property("StartTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("start_time");
+
+ b.HasKey("Id")
+ .HasName("pk_historic_retry_operations");
+
+ b.HasIndex("CompletionTime", "Id")
+ .IsDescending()
+ .HasDatabaseName("ix_historic_retry_operations_completion_time_id");
+
+ b.ToTable("historic_retry_operations", (string)null);
+ });
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.KnownEndpointEntity", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property("Host")
+ .IsRequired()
+ .HasMaxLength(450)
+ .HasColumnType("character varying(450)")
+ .HasColumnName("host");
+
+ b.Property("HostId")
+ .HasColumnType("uuid")
+ .HasColumnName("host_id");
+
+ b.Property("Monitored")
+ .HasColumnType("boolean")
+ .HasColumnName("monitored");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(450)
+ .HasColumnType("character varying(450)")
+ .HasColumnName("name");
+
+ b.HasKey("Id")
+ .HasName("pk_known_endpoints");
+
+ b.ToTable("known_endpoints", (string)null);
+ });
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.LicensingEndpointEntity", b =>
+ {
+ b.Property("NormalizedName")
+ .HasMaxLength(300)
+ .HasColumnType("character varying(300)")
+ .HasColumnName("normalized_name");
+
+ b.Property("ThroughputSource")
+ .HasColumnType("integer")
+ .HasColumnName("throughput_source");
+
+ b.PrimitiveCollection>("EndpointIndicators")
+ .IsRequired()
+ .HasColumnType("text[]")
+ .HasColumnName("endpoint_indicators");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(300)
+ .HasColumnType("character varying(300)")
+ .HasColumnName("name");
+
+ b.Property("NormalizedSanitizedName")
+ .IsRequired()
+ .HasMaxLength(450)
+ .HasColumnType("character varying(450)")
+ .HasColumnName("normalized_sanitized_name");
+
+ b.Property("SanitizedName")
+ .IsRequired()
+ .HasMaxLength(450)
+ .HasColumnType("character varying(450)")
+ .HasColumnName("sanitized_name");
+
+ b.Property("Scope")
+ .HasMaxLength(450)
+ .HasColumnType("character varying(450)")
+ .HasColumnName("scope");
+
+ b.Property("UserIndicator")
+ .IsRequired()
+ .HasMaxLength(450)
+ .HasColumnType("character varying(450)")
+ .HasColumnName("user_indicator");
+
+ b.HasKey("NormalizedName", "ThroughputSource")
+ .HasName("pk_licensing_endpoints");
+
+ b.HasIndex("NormalizedSanitizedName")
+ .HasDatabaseName("ix_licensing_endpoints_normalized_sanitized_name");
+
+ b.ToTable("licensing_endpoints", (string)null);
+ });
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.LicensingEndpointThroughputEntity", b =>
+ {
+ b.Property("NormalizedName")
+ .HasMaxLength(300)
+ .HasColumnType("character varying(300)")
+ .HasColumnName("normalized_name");
+
+ b.Property("ThroughputSource")
+ .HasColumnType("integer")
+ .HasColumnName("throughput_source");
+
+ b.Property("DateUtc")
+ .HasColumnType("date")
+ .HasColumnName("date_utc");
+
+ b.Property("MessageCount")
+ .HasColumnType("bigint")
+ .HasColumnName("message_count");
+
+ b.HasKey("NormalizedName", "ThroughputSource", "DateUtc")
+ .HasName("pk_licensing_endpoint_throughput");
+
+ b.HasIndex("DateUtc")
+ .HasDatabaseName("ix_licensing_endpoint_throughput_date_utc");
+
+ b.ToTable("licensing_endpoint_throughput", (string)null);
+ });
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.MessageRedirectEntity", b =>
+ {
+ b.Property("FromPhysicalAddress")
+ .HasMaxLength(450)
+ .HasColumnType("character varying(450)")
+ .HasColumnName("from_physical_address");
+
+ b.Property("LastModified")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_modified");
+
+ b.Property("ToPhysicalAddress")
+ .IsRequired()
+ .HasMaxLength(450)
+ .HasColumnType("character varying(450)")
+ .HasColumnName("to_physical_address");
+
+ b.HasKey("FromPhysicalAddress")
+ .HasName("pk_message_redirects");
+
+ b.ToTable("message_redirects", (string)null);
+ });
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.MigrationCheckpointEntity", b =>
+ {
+ b.Property("CategoryId")
+ .HasMaxLength(450)
+ .HasColumnType("character varying(450)")
+ .HasColumnName("category_id");
+
+ b.Property("AlreadyPresentCount")
+ .HasColumnType("bigint")
+ .HasColumnName("already_present_count");
+
+ b.Property("CopiedCount")
+ .HasColumnType("bigint")
+ .HasColumnName("copied_count");
+
+ b.Property("Cursor")
+ .HasColumnType("text")
+ .HasColumnName("cursor");
+
+ b.Property("LastError")
+ .HasColumnType("text")
+ .HasColumnName("last_error");
+
+ b.Property("LastProgressAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_progress_at");
+
+ b.Property("SettledAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("settled_at");
+
+ b.Property("SkipReasons")
+ .HasColumnType("text")
+ .HasColumnName("skip_reasons");
+
+ b.Property("SkippedCount")
+ .HasColumnType("bigint")
+ .HasColumnName("skipped_count");
+
+ b.Property("SourceTotal")
+ .HasColumnType("bigint")
+ .HasColumnName("source_total");
+
+ b.Property("StartedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("started_at");
+
+ b.Property("State")
+ .HasColumnType("integer")
+ .HasColumnName("state");
+
+ b.Property("Version")
+ .IsConcurrencyToken()
+ .HasColumnType("bigint")
+ .HasColumnName("version");
+
+ b.HasKey("CategoryId")
+ .HasName("pk_migration_checkpoints");
+
+ b.ToTable("migration_checkpoints", (string)null);
+ });
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.RetryBatchEntity", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property("Classifier")
+ .HasColumnType("text")
+ .HasColumnName("classifier");
+
+ b.Property("Context")
+ .HasColumnType("text")
+ .HasColumnName("context");
+
+ b.Property("InitialBatchSize")
+ .HasColumnType("integer")
+ .HasColumnName("initial_batch_size");
+
+ b.Property("InitiatedById")
+ .HasMaxLength(450)
+ .HasColumnType("character varying(450)")
+ .HasColumnName("initiated_by_id");
+
+ b.Property("InitiatedByName")
+ .HasColumnType("text")
+ .HasColumnName("initiated_by_name");
+
+ b.Property("Last")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last");
+
+ b.Property("OperationId")
+ .HasMaxLength(450)
+ .HasColumnType("character varying(450)")
+ .HasColumnName("operation_id");
+
+ b.Property("Originator")
+ .HasColumnType("text")
+ .HasColumnName("originator");
+
+ b.Property("RequestId")
+ .IsRequired()
+ .HasMaxLength(450)
+ .HasColumnType("character varying(450)")
+ .HasColumnName("request_id");
+
+ b.Property("RetrySessionId")
+ .IsRequired()
+ .HasMaxLength(450)
+ .HasColumnType("character varying(450)")
+ .HasColumnName("retry_session_id");
+
+ b.Property("RetryType")
+ .HasColumnType("integer")
+ .HasColumnName("retry_type");
+
+ b.Property("StagingId")
+ .HasMaxLength(450)
+ .HasColumnType("character varying(450)")
+ .HasColumnName("staging_id");
+
+ b.Property("StartTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("start_time");
+
+ b.Property("Status")
+ .HasColumnType("integer")
+ .HasColumnName("status");
+
+ b.HasKey("Id")
+ .HasName("pk_retry_batches");
+
+ b.HasIndex("Status", "RetrySessionId")
+ .HasDatabaseName("ix_retry_batches_status_retry_session_id");
+
+ b.ToTable("retry_batches", (string)null);
+ });
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.RetryBatchNowForwardingEntity", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("integer")
+ .HasColumnName("id");
+
+ b.Property("RetryBatchId")
+ .HasColumnType("uuid")
+ .HasColumnName("retry_batch_id");
+
+ b.HasKey("Id")
+ .HasName("pk_retry_batch_now_forwarding");
+
+ b.ToTable("retry_batch_now_forwarding", (string)null);
+ });
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.SettingEntity", b =>
+ {
+ b.Property("Key")
+ .HasMaxLength(450)
+ .HasColumnType("character varying(450)")
+ .HasColumnName("key");
+
+ b.Property("Value")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("value");
+
+ b.HasKey("Key")
+ .HasName("pk_settings");
+
+ b.ToTable("settings", (string)null);
+ });
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.SubscriptionEntity", b =>
+ {
+ b.Property("MessageType")
+ .HasMaxLength(200)
+ .HasColumnType("character varying(200)")
+ .HasColumnName("message_type");
+
+ b.Property("TransportAddress")
+ .HasMaxLength(200)
+ .HasColumnType("character varying(200)")
+ .HasColumnName("transport_address");
+
+ b.Property("Endpoint")
+ .IsRequired()
+ .HasMaxLength(450)
+ .HasColumnType("character varying(450)")
+ .HasColumnName("endpoint");
+
+ b.HasKey("MessageType", "TransportAddress")
+ .HasName("pk_subscriptions");
+
+ b.ToTable("subscriptions", (string)null);
+ });
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.UnacknowledgedRetryOperationEntity", b =>
+ {
+ b.Property("RequestId")
+ .HasMaxLength(400)
+ .HasColumnType("character varying(400)")
+ .HasColumnName("request_id");
+
+ b.Property("RetryType")
+ .HasColumnType("integer")
+ .HasColumnName("retry_type");
+
+ b.Property("Classifier")
+ .HasMaxLength(450)
+ .HasColumnType("character varying(450)")
+ .HasColumnName("classifier");
+
+ b.Property("CompletionTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("completion_time");
+
+ b.Property("Failed")
+ .HasColumnType("boolean")
+ .HasColumnName("failed");
+
+ b.Property("Last")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last");
+
+ b.Property("NumberOfMessagesProcessed")
+ .HasColumnType("integer")
+ .HasColumnName("number_of_messages_processed");
+
+ b.Property("Originator")
+ .HasColumnType("text")
+ .HasColumnName("originator");
+
+ b.Property("StartTime")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("start_time");
+
+ b.HasKey("RequestId", "RetryType")
+ .HasName("pk_unacknowledged_retry_operations");
+
+ b.ToTable("unacknowledged_retry_operations", (string)null);
+ });
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.FailedMessageGroupEntity", b =>
+ {
+ b.HasOne("ServiceControl.Persistence.EFCore.Entities.FailedMessageEntity", null)
+ .WithMany()
+ .HasForeignKey("FailedMessageUniqueId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("fk_failed_message_groups_failed_messages_failed_message_unique");
+ });
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.LicensingEndpointThroughputEntity", b =>
+ {
+ b.HasOne("ServiceControl.Persistence.EFCore.Entities.LicensingEndpointEntity", null)
+ .WithMany()
+ .HasForeignKey("NormalizedName", "ThroughputSource")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("fk_licensing_endpoint_throughput_licensing_endpoints_normalize");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/ServiceControl.Persistence.EFCore.PostgreSql/Migrations/20260916125150_AddMigrationCheckpoints.cs b/src/ServiceControl.Persistence.EFCore.PostgreSql/Migrations/20260916125150_AddMigrationCheckpoints.cs
new file mode 100644
index 0000000000..b2b97c730a
--- /dev/null
+++ b/src/ServiceControl.Persistence.EFCore.PostgreSql/Migrations/20260916125150_AddMigrationCheckpoints.cs
@@ -0,0 +1,45 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace ServiceControl.Persistence.EFCore.PostgreSql.Migrations
+{
+ ///
+ public partial class AddMigrationCheckpoints : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "migration_checkpoints",
+ columns: table => new
+ {
+ category_id = table.Column(type: "character varying(450)", maxLength: 450, nullable: false),
+ state = table.Column(type: "integer", nullable: false),
+ cursor = table.Column(type: "text", nullable: true),
+ copied_count = table.Column(type: "bigint", nullable: false),
+ skipped_count = table.Column(type: "bigint", nullable: false),
+ source_total = table.Column(type: "bigint", nullable: true),
+ skip_reasons = table.Column(type: "text", nullable: true),
+ started_at = table.Column(type: "timestamp with time zone", nullable: true),
+ last_progress_at = table.Column(type: "timestamp with time zone", nullable: true),
+ settled_at = table.Column(type: "timestamp with time zone", nullable: true),
+ last_error = table.Column(type: "text", nullable: true),
+ already_present_count = table.Column(type: "bigint", nullable: false),
+ version = table.Column(type: "bigint", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("pk_migration_checkpoints", x => x.category_id);
+ });
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "migration_checkpoints");
+ }
+ }
+}
diff --git a/src/ServiceControl.Persistence.EFCore.PostgreSql/Migrations/PostgreSqlServiceControlDbContextModelSnapshot.cs b/src/ServiceControl.Persistence.EFCore.PostgreSql/Migrations/PostgreSqlServiceControlDbContextModelSnapshot.cs
index af482619d5..86dfd249ca 100644
--- a/src/ServiceControl.Persistence.EFCore.PostgreSql/Migrations/PostgreSqlServiceControlDbContextModelSnapshot.cs
+++ b/src/ServiceControl.Persistence.EFCore.PostgreSql/Migrations/PostgreSqlServiceControlDbContextModelSnapshot.cs
@@ -704,6 +704,68 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.ToTable("message_redirects", (string)null);
});
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.MigrationCheckpointEntity", b =>
+ {
+ b.Property("CategoryId")
+ .HasMaxLength(450)
+ .HasColumnType("character varying(450)")
+ .HasColumnName("category_id");
+
+ b.Property("AlreadyPresentCount")
+ .HasColumnType("bigint")
+ .HasColumnName("already_present_count");
+
+ b.Property("CopiedCount")
+ .HasColumnType("bigint")
+ .HasColumnName("copied_count");
+
+ b.Property("Cursor")
+ .HasColumnType("text")
+ .HasColumnName("cursor");
+
+ b.Property("LastError")
+ .HasColumnType("text")
+ .HasColumnName("last_error");
+
+ b.Property("LastProgressAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("last_progress_at");
+
+ b.Property("SettledAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("settled_at");
+
+ b.Property("SkipReasons")
+ .HasColumnType("text")
+ .HasColumnName("skip_reasons");
+
+ b.Property("SkippedCount")
+ .HasColumnType("bigint")
+ .HasColumnName("skipped_count");
+
+ b.Property("SourceTotal")
+ .HasColumnType("bigint")
+ .HasColumnName("source_total");
+
+ b.Property("StartedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("started_at");
+
+ b.Property("State")
+ .HasColumnType("integer")
+ .HasColumnName("state");
+
+ b.Property("Version")
+ .IsConcurrencyToken()
+ .HasColumnType("bigint")
+ .HasColumnName("version");
+
+ b.HasKey("CategoryId")
+ .HasName("pk_migration_checkpoints");
+
+ b.ToTable("migration_checkpoints", (string)null);
+ });
+
modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.RetryBatchEntity", b =>
{
b.Property("Id")
diff --git a/src/ServiceControl.Persistence.EFCore.SqlServer/Migrations/20260916125144_AddMigrationCheckpoints.Designer.cs b/src/ServiceControl.Persistence.EFCore.SqlServer/Migrations/20260916125144_AddMigrationCheckpoints.Designer.cs
new file mode 100644
index 0000000000..5dfcc173b9
--- /dev/null
+++ b/src/ServiceControl.Persistence.EFCore.SqlServer/Migrations/20260916125144_AddMigrationCheckpoints.Designer.cs
@@ -0,0 +1,782 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using ServiceControl.Persistence.EFCore.SqlServer;
+
+#nullable disable
+
+namespace ServiceControl.Persistence.EFCore.SqlServer.Migrations
+{
+ [DbContext(typeof(SqlServerServiceControlDbContext))]
+ [Migration("20260916125144_AddMigrationCheckpoints")]
+ partial class AddMigrationCheckpoints
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "10.0.11")
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
+
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.ArchiveOperationEntity", b =>
+ {
+ b.Property("RequestId")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("ArchiveType")
+ .HasColumnType("int");
+
+ b.Property("OperationType")
+ .HasColumnType("int");
+
+ b.Property("CurrentBatch")
+ .HasColumnType("int");
+
+ b.Property("GroupName")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("InitiatedById")
+ .HasMaxLength(450)
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("InitiatedByName")
+ .HasMaxLength(450)
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("NumberOfBatches")
+ .HasColumnType("int");
+
+ b.Property("NumberOfMessagesProcessed")
+ .HasColumnType("int");
+
+ b.Property("OperationId")
+ .HasMaxLength(450)
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("Started")
+ .HasColumnType("datetime2");
+
+ b.Property("TotalNumberOfMessages")
+ .HasColumnType("int");
+
+ b.HasKey("RequestId", "ArchiveType", "OperationType");
+
+ b.HasIndex("Started");
+
+ b.ToTable("ArchiveOperations");
+ });
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.CustomCheckEntity", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("Category")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("CustomCheckId")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("FailureReason")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("OriginatingEndpointHost")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("OriginatingEndpointHostId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("OriginatingEndpointName")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ReportedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("Status")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ReportedAt");
+
+ b.HasIndex("Status", "ReportedAt");
+
+ b.ToTable("CustomChecks");
+ });
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.EndpointSettingsEntity", b =>
+ {
+ b.Property("Name")
+ .HasMaxLength(450)
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("TrackInstances")
+ .HasColumnType("bit");
+
+ b.HasKey("Name");
+
+ b.ToTable("EndpointSettings");
+ });
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.EventLogItemEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("Category")
+ .IsRequired()
+ .HasMaxLength(450)
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("EventType")
+ .IsRequired()
+ .HasMaxLength(450)
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("RaisedAt")
+ .HasColumnType("datetime2");
+
+ b.PrimitiveCollection("RelatedTo")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Severity")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RaisedAt", "Id")
+ .IsDescending();
+
+ b.ToTable("EventLogItems");
+ });
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.ExternalIntegrationDispatchRequestEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("DispatchContextJson")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("DispatchContextTypeName")
+ .IsRequired()
+ .HasMaxLength(450)
+ .HasColumnType("nvarchar(450)");
+
+ b.HasKey("Id");
+
+ b.ToTable("ExternalIntegrationDispatchRequests");
+ });
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.FailedErrorImportEntity", b =>
+ {
+ b.Property("UniqueMessageId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("Body")
+ .IsRequired()
+ .HasColumnType("varbinary(max)");
+
+ b.Property("BodyStoredExternally")
+ .HasColumnType("bit");
+
+ b.Property("ExceptionInfo")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("FailedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("HeadersJson")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("MessageId")
+ .IsRequired()
+ .HasMaxLength(450)
+ .HasColumnType("nvarchar(450)");
+
+ b.HasKey("UniqueMessageId");
+
+ b.HasIndex("FailedAt");
+
+ b.ToTable("FailedErrorImports");
+ });
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.FailedMessageEditEntity", b =>
+ {
+ b.Property("UniqueMessageId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("EditId")
+ .IsRequired()
+ .HasMaxLength(450)
+ .HasColumnType("nvarchar(450)");
+
+ b.HasKey("UniqueMessageId");
+
+ b.HasIndex("EditId");
+
+ b.ToTable("FailedMessageEdits");
+ });
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.FailedMessageEntity", b =>
+ {
+ b.Property("UniqueMessageId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("BodyContentType")
+ .HasMaxLength(450)
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("BodySize")
+ .HasColumnType("int");
+
+ b.Property("BodyStoredExternally")
+ .HasColumnType("bit");
+
+ b.Property("BodyText")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ConversationId")
+ .HasMaxLength(450)
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("ExceptionMessage")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ExceptionType")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("FailingEndpointAddress")
+ .IsRequired()
+ .HasMaxLength(450)
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("FirstTimeOfFailure")
+ .HasColumnType("datetime2");
+
+ b.Property("HeadersJson")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("IsSystemMessage")
+ .HasColumnType("bit");
+
+ b.Property("LastAttemptedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("LastModified")
+ .HasColumnType("datetime2");
+
+ b.Property("LastTimeOfFailure")
+ .HasColumnType("datetime2");
+
+ b.Property("MessageId")
+ .HasMaxLength(450)
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("MessageType")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("NumberOfProcessingAttempts")
+ .HasColumnType("int");
+
+ b.Property("ReceivingEndpointHost")
+ .HasMaxLength(450)
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("ReceivingEndpointHostId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("ReceivingEndpointName")
+ .HasMaxLength(450)
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("SendingEndpointHost")
+ .HasMaxLength(450)
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("SendingEndpointHostId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("SendingEndpointName")
+ .HasMaxLength(450)
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("Status")
+ .HasColumnType("int");
+
+ b.Property("StatusChangedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("TimeSent")
+ .HasColumnType("datetime2");
+
+ b.HasKey("UniqueMessageId");
+
+ b.HasIndex("ConversationId");
+
+ b.HasIndex("FailingEndpointAddress");
+
+ b.HasIndex("ReceivingEndpointName");
+
+ b.HasIndex("StatusChangedAt")
+ .HasFilter("[Status] IN (2, 4)");
+
+ b.HasIndex("TimeSent");
+
+ b.HasIndex("Status", "LastModified");
+
+ b.ToTable("FailedMessages");
+ });
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.FailedMessageGroupEntity", b =>
+ {
+ b.Property("FailedMessageUniqueId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("GroupId")
+ .HasMaxLength(64)
+ .HasColumnType("nvarchar(64)");
+
+ b.Property("Title")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property