From a01ec374a4c7956c79aff5b9a90330195c55b144 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Eggert=20Lundsteen=20Olsen?= Date: Fri, 2 Oct 2026 11:33:25 +0200 Subject: [PATCH] Review 1.2: keep SQL Server's error message out of the replication reason When the distribution database could not be read, SQL Server's message (which names the agent's login) was appended to Unavailable. That text is the rules' "not measurable" reason and is returned unredacted by /rule-states and /replication. It is now UnavailableDetail: shown on the local instance page and [udeladt] in the API unless ApiIncludesSensitiveEvidence. The agent list is also capped (TOP 500). Co-Authored-By: Claude Opus 5.5 (1M context) --- src/SQLInsight.Collectors/Instance/ReplicationCollector.cs | 6 ++++-- src/SQLInsight.Core/Data/Replication.cs | 3 +++ src/SQLInsight.Web/Api/ApiV1Endpoints.cs | 1 + src/SQLInsight.Web/Components/Pages/Instance.razor | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/SQLInsight.Collectors/Instance/ReplicationCollector.cs b/src/SQLInsight.Collectors/Instance/ReplicationCollector.cs index 539114f..2a21dcf 100644 --- a/src/SQLInsight.Collectors/Instance/ReplicationCollector.cs +++ b/src/SQLInsight.Collectors/Instance/ReplicationCollector.cs @@ -14,7 +14,7 @@ public sealed class ReplicationCollector : Collector internal const string DistributionDatabaseSql = "SELECT TOP 1 name FROM sys.databases WHERE is_distributor = 1 AND state = 0"; internal const string AgentsSql = """ - SELECT a.name, a.publisher_db, h.runstatus, DATEADD(minute, DATEDIFF(minute, GETDATE(), GETUTCDATE()), h.time), h.delivery_latency, + SELECT TOP 500 a.name, a.publisher_db, h.runstatus, DATEADD(minute, DATEDIFF(minute, GETDATE(), GETUTCDATE()), h.time), h.delivery_latency, CAST(h.comments AS nvarchar(500)) FROM dbo.MSdistribution_agents a OUTER APPLY (SELECT TOP 1 x.runstatus, x.time, x.delivery_latency, x.comments FROM dbo.MSdistribution_history x @@ -58,7 +58,9 @@ public sealed class ReplicationCollector : Collector return new ReplicationSnapshot { CapturedAt = context.Now, IsDistributor = true, DistributionDatabase = distribution, - Unavailable = NoRightsText + " (" + ex.Message.Split('\n')[0].Trim() + ")", + // The error message names the agent's login: kept apart from the reason, which rules and the API show as is. + Unavailable = NoRightsText, + UnavailableDetail = ex.Message.Split('\n')[0].Trim(), }; } finally diff --git a/src/SQLInsight.Core/Data/Replication.cs b/src/SQLInsight.Core/Data/Replication.cs index a27a1ce..d3de913 100644 --- a/src/SQLInsight.Core/Data/Replication.cs +++ b/src/SQLInsight.Core/Data/Replication.cs @@ -32,6 +32,9 @@ public sealed record ReplicationSnapshot /// Why the distribution database could not be read (typically no SELECT rights); null when it could. public string? Unavailable { get; init; } + /// SQL Server's error message when it could not be read. Names the agent's login: sensitive (local UI only). + public string? UnavailableDetail { get; init; } + public IReadOnlyList Agents { get; init; } = []; /// Rows in MSrepl_errors the last 24 hours. diff --git a/src/SQLInsight.Web/Api/ApiV1Endpoints.cs b/src/SQLInsight.Web/Api/ApiV1Endpoints.cs index 97febd0..9b74f66 100644 --- a/src/SQLInsight.Web/Api/ApiV1Endpoints.cs +++ b/src/SQLInsight.Web/Api/ApiV1Endpoints.cs @@ -572,6 +572,7 @@ public static class ApiV1Endpoints IsDistributor = r?.IsDistributor, r?.DistributionDatabase, r?.Unavailable, + UnavailableDetail = redact.Value(r?.UnavailableDetail), r?.ErrorsLastDay, Agents = (r?.Agents ?? []).Select(a => new { a.Name, a.PublisherDatabase, a.RunStatus, a.StatusText, a.LastRunAt, a.DeliveryLatencyMs, Comment = redact.Value(a.Comment) }), }); diff --git a/src/SQLInsight.Web/Components/Pages/Instance.razor b/src/SQLInsight.Web/Components/Pages/Instance.razor index 8a1bb01..2b32168 100644 --- a/src/SQLInsight.Web/Components/Pages/Instance.razor +++ b/src/SQLInsight.Web/Components/Pages/Instance.razor @@ -158,7 +158,7 @@ else

Replikering

distributionsdatabase @repl.DistributionDatabase
@if (repl.Unavailable is { } why) { -

@why

+

@why@(repl.UnavailableDetail is { } detail ? " (" + detail + ")" : "")

} else if (repl.Agents.Count == 0) {