feat(health): report permanently rejected site audit rows on the site health report (ScadaBridge#69)

SiteAuditBacklogSnapshot gains an optional trailing RejectedCount (gauge of
Rejected forward-state rows, read from the site store), carried on the wire as
SiteAuditBacklogSnapshotDto.rejected_count = 4 (additive; old sites read as 0,
old central ignores it). Health page shows an 'Audit rows rejected' badge.

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
This commit is contained in:
Joseph Doherty
2026-11-09 12:46:34 -05:00
parent 3ae3f1bcef
commit 0eac956d25
10 changed files with 263 additions and 100 deletions
@@ -1389,6 +1389,7 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, ISiteAuditPushLi
public Task<SiteAuditBacklogSnapshot> GetBacklogStatsAsync(CancellationToken ct = default)
{
int pendingCount;
int rejectedCount;
DateTime? oldestPending;
// Read via the dedicated _readConnection (under
@@ -1409,7 +1410,8 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, ISiteAuditPushLi
// (PushAbandoned / Rejected) still await the reconciliation pull and are never
// purged, so they count toward the backlog and its oldest-row age.
cmd.CommandText = """
SELECT COUNT(*), MIN(OccurredAtUtc)
SELECT COUNT(*), MIN(OccurredAtUtc),
COALESCE(SUM(CASE WHEN ForwardState = $rejected THEN 1 ELSE 0 END), 0)
FROM audit_forward_state
WHERE ForwardState IN ($pending, $pushAbandoned, $rejected);
""";
@@ -1418,6 +1420,7 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, ISiteAuditPushLi
using var reader = cmd.ExecuteReader();
reader.Read();
pendingCount = reader.GetInt32(0);
rejectedCount = reader.GetInt32(2);
oldestPending = reader.IsDBNull(1)
? null
: DateTime.Parse(reader.GetString(1),
@@ -1453,7 +1456,8 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, ISiteAuditPushLi
return Task.FromResult(new SiteAuditBacklogSnapshot(
PendingCount: pendingCount,
OldestPendingUtc: oldestPending,
OnDiskBytes: onDiskBytes));
OnDiskBytes: onDiskBytes,
RejectedCount: rejectedCount));
}
/// <inheritdoc />
@@ -285,6 +285,14 @@
Secret mirror failing
</span>
}
@if (HasAuditRejectedRows(state))
{
<span class="badge bg-warning text-dark"
data-test="audit-rejected-badge"
title="Central permanently refused @state.LatestReport!.SiteAuditBacklog!.RejectedCount site audit row(s) as un-insertable. They are still served by the reconciliation pull, but they indicate a row-intrinsic insert failure at central — check the central audit ingest logs.">
Audit rows rejected
</span>
}
@if (!state.IsOnline && state.LastStatusChangeAt is { } changedAt)
{
<small class="text-muted">offline since @changedAt.ToString("u")</small>
@@ -629,6 +637,13 @@
&& state.LatestReport?.SecretMirrorStatuses is { } statuses
&& statuses.Any(n => n.Failures.Count > 0);
// Gitea #69: warn when central has permanently refused site audit rows. Null backlog
// ("no data": central self-report, pre-#69 site) and zero are healthy; offline sites are
// skipped like the other warning badges.
private static bool HasAuditRejectedRows(SiteHealthState state) =>
state.IsOnline
&& state.LatestReport?.SiteAuditBacklog is { RejectedCount: > 0 };
// Online central nodes, from the same ClusterNodes list the Nodes column renders.
// Fewer than two means no standby, which disables the manual-failover control. This is
// a display-side guard only — AkkaManualFailoverService re-checks against live cluster
@@ -26,7 +26,19 @@ namespace ZB.MOM.WW.ScadaBridge.Commons.Types;
/// invariant (alog.md §10) — a steady file-size growth past the retention
/// window points at a stuck purge or a stuck forwarder.
/// </param>
/// <param name="RejectedCount">
/// Number of rows currently in the <c>Rejected</c> forward state — rows central permanently
/// refused (listed in the ack's <c>rejected_event_ids</c>) on repeated pushes and that the
/// site moved out of the push queue. They are still owed to central via the reconciliation
/// pull (so they are ALSO included in <paramref name="PendingCount"/>), but a non-zero value
/// means central is rejecting rows as un-insertable, which is a data-quality fault worth an
/// operator's attention rather than a drain-lag symptom. A point-in-time gauge read from the
/// site store (survives restarts), unlike the process-local
/// <c>scadabridge.site.audit.rejected_rows</c> counter. Trailing, defaulted member: an older
/// site omits it (reads as 0) and the wire field is optional (Gitea #69).
/// </param>
public sealed record SiteAuditBacklogSnapshot(
int PendingCount,
DateTime? OldestPendingUtc,
long OnDiskBytes);
long OnDiskBytes,
int RejectedCount = 0);
@@ -59,102 +59,103 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
"ChVzdWNjZXNzZnVsbHlfcmVzb2x2ZWQYAiABKAUiQwoTVGFnUXVhbGl0eUNv",
"dW50c0R0bxIMCgRnb29kGAEgASgFEgsKA2JhZBgCIAEoBRIRCgl1bmNlcnRh",
"aW4YAyABKAUiQgoNTm9kZVN0YXR1c0R0bxIQCghob3N0bmFtZRgBIAEoCRIR",
"Cglpc19vbmxpbmUYAiABKAgSDAoEcm9sZRgDIAEoCSKDAQobU2l0ZUF1ZGl0",
"Cglpc19vbmxpbmUYAiABKAgSDAoEcm9sZRgDIAEoCSKbAQobU2l0ZUF1ZGl0",
"QmFja2xvZ1NuYXBzaG90RHRvEhUKDXBlbmRpbmdfY291bnQYASABKAUSNgoS",
"b2xkZXN0X3BlbmRpbmdfdXRjGAIgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRp",
"bWVzdGFtcBIVCg1vbl9kaXNrX2J5dGVzGAMgASgDIqEBChhDb25uZWN0aW9u",
"RW5kcG9pbnRNYXBEdG8SVQoHZW50cmllcxgBIAMoCzJELnNjYWRhYnJpZGdl",
"LmNlbnRyYWxjb250cm9sLnYxLkNvbm5lY3Rpb25FbmRwb2ludE1hcER0by5F",
"bnRyaWVzRW50cnkaLgoMRW50cmllc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2",
"YWx1ZRgCIAEoCToCOAEixQEKEFRhZ1F1YWxpdHlNYXBEdG8STQoHZW50cmll",
"cxgBIAMoCzI8LnNjYWRhYnJpZGdlLmNlbnRyYWxjb250cm9sLnYxLlRhZ1F1",
"YWxpdHlNYXBEdG8uRW50cmllc0VudHJ5GmIKDEVudHJpZXNFbnRyeRILCgNr",
"ZXkYASABKAkSQQoFdmFsdWUYAiABKAsyMi5zY2FkYWJyaWRnZS5jZW50cmFs",
"Y29udHJvbC52MS5UYWdRdWFsaXR5Q291bnRzRHRvOgI4ASJQChFOb2RlU3Rh",
"dHVzTGlzdER0bxI7CgVub2RlcxgBIAMoCzIsLnNjYWRhYnJpZGdlLmNlbnRy",
"YWxjb250cm9sLnYxLk5vZGVTdGF0dXNEdG8iPgoWU2VjcmV0TWlycm9yRmFp",
"bHVyZUR0bxITCgtzZWNyZXRfbmFtZRgBIAEoCRIPCgdtZXNzYWdlGAIgASgJ",
"IqsBChlTZWNyZXRNaXJyb3JOb2RlU3RhdHVzRHRvEhEKCW5vZGVfbmFtZRgB",
"IAEoCRIyCg5jaGVja2VkX2F0X3V0YxgCIAEoCzIaLmdvb2dsZS5wcm90b2J1",
"Zi5UaW1lc3RhbXASRwoIZmFpbHVyZXMYAyADKAsyNS5zY2FkYWJyaWRnZS5j",
"ZW50cmFsY29udHJvbC52MS5TZWNyZXRNaXJyb3JGYWlsdXJlRHRvImQKGVNl",
"Y3JldE1pcnJvclN0YXR1c0xpc3REdG8SRwoFbm9kZXMYASADKAsyOC5zY2Fk",
"YWJyaWRnZS5jZW50cmFsY29udHJvbC52MS5TZWNyZXRNaXJyb3JOb2RlU3Rh",
"dHVzRHRvIokQChNTaXRlSGVhbHRoUmVwb3J0RHRvEg8KB3NpdGVfaWQYASAB",
"KAkSFwoPc2VxdWVuY2VfbnVtYmVyGAIgASgDEjQKEHJlcG9ydF90aW1lc3Rh",
"bXAYAyABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEnAKGGRhdGFf",
"Y29ubmVjdGlvbl9zdGF0dXNlcxgEIAMoCzJOLnNjYWRhYnJpZGdlLmNlbnRy",
"YWxjb250cm9sLnYxLlNpdGVIZWFsdGhSZXBvcnREdG8uRGF0YUNvbm5lY3Rp",
"b25TdGF0dXNlc0VudHJ5EmoKFXRhZ19yZXNvbHV0aW9uX2NvdW50cxgFIAMo",
"CzJLLnNjYWRhYnJpZGdlLmNlbnRyYWxjb250cm9sLnYxLlNpdGVIZWFsdGhS",
"ZXBvcnREdG8uVGFnUmVzb2x1dGlvbkNvdW50c0VudHJ5EhoKEnNjcmlwdF9l",
"cnJvcl9jb3VudBgGIAEoBRIkChxhbGFybV9ldmFsdWF0aW9uX2Vycm9yX2Nv",
"dW50GAcgASgFEnwKH3N0b3JlX2FuZF9mb3J3YXJkX2J1ZmZlcl9kZXB0aHMY",
"CCADKAsyUy5zY2FkYWJyaWRnZS5jZW50cmFsY29udHJvbC52MS5TaXRlSGVh",
"bHRoUmVwb3J0RHRvLlN0b3JlQW5kRm9yd2FyZEJ1ZmZlckRlcHRoc0VudHJ5",
"EhkKEWRlYWRfbGV0dGVyX2NvdW50GAkgASgFEh8KF2RlcGxveWVkX2luc3Rh",
"bmNlX2NvdW50GAogASgFEh4KFmVuYWJsZWRfaW5zdGFuY2VfY291bnQYCyAB",
"KAUSHwoXZGlzYWJsZWRfaW5zdGFuY2VfY291bnQYDCABKAUSEQoJbm9kZV9y",
"b2xlGA0gASgJEhUKDW5vZGVfaG9zdG5hbWUYDiABKAkSWgoZZGF0YV9jb25u",
"ZWN0aW9uX2VuZHBvaW50cxgPIAEoCzI3LnNjYWRhYnJpZGdlLmNlbnRyYWxj",
"b250cm9sLnYxLkNvbm5lY3Rpb25FbmRwb2ludE1hcER0bxJUChtkYXRhX2Nv",
"bm5lY3Rpb25fdGFnX3F1YWxpdHkYECABKAsyLy5zY2FkYWJyaWRnZS5jZW50",
"cmFsY29udHJvbC52MS5UYWdRdWFsaXR5TWFwRHRvEhwKFHBhcmtlZF9tZXNz",
"YWdlX2NvdW50GBEgASgFEkcKDWNsdXN0ZXJfbm9kZXMYEiABKAsyMC5zY2Fk",
"YWJyaWRnZS5jZW50cmFsY29udHJvbC52MS5Ob2RlU3RhdHVzTGlzdER0bxIh",
"ChlzaXRlX2F1ZGl0X3dyaXRlX2ZhaWx1cmVzGBMgASgFEh8KF2F1ZGl0X3Jl",
"ZGFjdGlvbl9mYWlsdXJlGBQgASgFElYKEnNpdGVfYXVkaXRfYmFja2xvZxgV",
"IAEoCzI6LnNjYWRhYnJpZGdlLmNlbnRyYWxjb250cm9sLnYxLlNpdGVBdWRp",
"dEJhY2tsb2dTbmFwc2hvdER0bxIlCh1zaXRlX2V2ZW50X2xvZ193cml0ZV9m",
"YWlsdXJlcxgWIAEoAxJHCiFvbGRlc3RfcGFya2VkX21lc3NhZ2VfYWdlX3Nl",
"Y29uZHMYFyABKAsyHC5nb29nbGUucHJvdG9idWYuRG91YmxlVmFsdWUSGgoS",
"c2NyaXB0X3F1ZXVlX2RlcHRoGBggASgFEhsKE3NjcmlwdF9idXN5X3RocmVh",
"ZHMYGSABKAUSRAoec2NyaXB0X29sZGVzdF9idXN5X2FnZV9zZWNvbmRzGBog",
"ASgLMhwuZ29vZ2xlLnByb3RvYnVmLkRvdWJsZVZhbHVlEkIKHmxvY2FsX2Ri",
"X3JlcGxpY2F0aW9uX2Nvbm5lY3RlZBgbIAEoCzIaLmdvb2dsZS5wcm90b2J1",
"Zi5Cb29sVmFsdWUSOwoWbG9jYWxfZGJfb3Bsb2dfYmFja2xvZxgcIAEoCzIb",
"Lmdvb2dsZS5wcm90b2J1Zi5JbnQ2NFZhbHVlEj0KGXN0YXJ0dXBfcmVjb25j",
"aWxlcl9hY3RpdmUYHSABKAsyGi5nb29nbGUucHJvdG9idWYuQm9vbFZhbHVl",
"Eh8KF2RldGFjaGVkX3NjcmlwdF90aHJlYWRzGB4gASgFEh0KFXNjcmlwdF9y",
"dW5fc2hlZF9jb3VudBgfIAEoBRIkChxzaXRlX3N0cmVhbV9hbGFybV9kcm9w",
"X2NvdW50GCAgASgDElgKFnNlY3JldF9taXJyb3Jfc3RhdHVzZXMYISABKAsy",
"OC5zY2FkYWJyaWRnZS5jZW50cmFsY29udHJvbC52MS5TZWNyZXRNaXJyb3JT",
"dGF0dXNMaXN0RHRvGnIKG0RhdGFDb25uZWN0aW9uU3RhdHVzZXNFbnRyeRIL",
"CgNrZXkYASABKAkSQgoFdmFsdWUYAiABKA4yMy5zY2FkYWJyaWRnZS5jZW50",
"cmFsY29udHJvbC52MS5Db25uZWN0aW9uSGVhbHRoRW51bToCOAEacQoYVGFn",
"UmVzb2x1dGlvbkNvdW50c0VudHJ5EgsKA2tleRgBIAEoCRJECgV2YWx1ZRgC",
"IAEoCzI1LnNjYWRhYnJpZGdlLmNlbnRyYWxjb250cm9sLnYxLlRhZ1Jlc29s",
"dXRpb25TdGF0dXNEdG86AjgBGkIKIFN0b3JlQW5kRm9yd2FyZEJ1ZmZlckRl",
"cHRoc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoBToCOAEiYwoW",
"U2l0ZUhlYWx0aFJlcG9ydEFja0R0bxIPCgdzaXRlX2lkGAEgASgJEhcKD3Nl",
"cXVlbmNlX251bWJlchgCIAEoAxIQCghhY2NlcHRlZBgDIAEoCBINCgVlcnJv",
"chgEIAEoCSKLAQoMSGVhcnRiZWF0RHRvEg8KB3NpdGVfaWQYASABKAkSFQoN",
"bm9kZV9ob3N0bmFtZRgCIAEoCRIRCglpc19hY3RpdmUYAyABKAgSLQoJdGlt",
"ZXN0YW1wGAQgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIRCglz",
"eW50aGV0aWMYBSABKAgqvQEKFENvbm5lY3Rpb25IZWFsdGhFbnVtEiEKHUNP",
"Tk5FQ1RJT05fSEVBTFRIX1VOU1BFQ0lGSUVEEAASHwobQ09OTkVDVElPTl9I",
"RUFMVEhfQ09OTkVDVEVEEAESIgoeQ09OTkVDVElPTl9IRUFMVEhfRElTQ09O",
"TkVDVEVEEAISIAocQ09OTkVDVElPTl9IRUFMVEhfQ09OTkVDVElORxADEhsK",
"F0NPTk5FQ1RJT05fSEVBTFRIX0VSUk9SEAQyoQYKFUNlbnRyYWxDb250cm9s",
"U2VydmljZRKDAQoSU3VibWl0Tm90aWZpY2F0aW9uEjQuc2NhZGFicmlkZ2Uu",
"Y2VudHJhbGNvbnRyb2wudjEuTm90aWZpY2F0aW9uU3VibWl0RHRvGjcuc2Nh",
"ZGFicmlkZ2UuY2VudHJhbGNvbnRyb2wudjEuTm90aWZpY2F0aW9uU3VibWl0",
"QWNrRHRvEpIBChdRdWVyeU5vdGlmaWNhdGlvblN0YXR1cxI5LnNjYWRhYnJp",
"ZGdlLmNlbnRyYWxjb250cm9sLnYxLk5vdGlmaWNhdGlvblN0YXR1c1F1ZXJ5",
"RHRvGjwuc2NhZGFicmlkZ2UuY2VudHJhbGNvbnRyb2wudjEuTm90aWZpY2F0",
"aW9uU3RhdHVzUmVzcG9uc2VEdG8SRwoRSW5nZXN0QXVkaXRFdmVudHMSGy5z",
"aXRlc3RyZWFtLkF1ZGl0RXZlbnRCYXRjaBoVLnNpdGVzdHJlYW0uSW5nZXN0",
"QWNrElAKFUluZ2VzdENhY2hlZFRlbGVtZXRyeRIgLnNpdGVzdHJlYW0uQ2Fj",
"aGVkVGVsZW1ldHJ5QmF0Y2gaFS5zaXRlc3RyZWFtLkluZ2VzdEFjaxKAAQoN",
"UmVjb25jaWxlU2l0ZRI2LnNjYWRhYnJpZGdlLmNlbnRyYWxjb250cm9sLnYx",
"LlJlY29uY2lsZVNpdGVSZXF1ZXN0RHRvGjcuc2NhZGFicmlkZ2UuY2VudHJh",
"bGNvbnRyb2wudjEuUmVjb25jaWxlU2l0ZVJlc3BvbnNlRHRvEn0KEFJlcG9y",
"dFNpdGVIZWFsdGgSMi5zY2FkYWJyaWRnZS5jZW50cmFsY29udHJvbC52MS5T",
"aXRlSGVhbHRoUmVwb3J0RHRvGjUuc2NhZGFicmlkZ2UuY2VudHJhbGNvbnRy",
"b2wudjEuU2l0ZUhlYWx0aFJlcG9ydEFja0R0bxJQCglIZWFydGJlYXQSKy5z",
"Y2FkYWJyaWRnZS5jZW50cmFsY29udHJvbC52MS5IZWFydGJlYXREdG8aFi5n",
"b29nbGUucHJvdG9idWYuRW1wdHlCK6oCKFpCLk1PTS5XVy5TY2FkYUJyaWRn",
"ZS5Db21tdW5pY2F0aW9uLkdycGNiBnByb3RvMw=="));
"bWVzdGFtcBIVCg1vbl9kaXNrX2J5dGVzGAMgASgDEhYKDnJlamVjdGVkX2Nv",
"dW50GAQgASgFIqEBChhDb25uZWN0aW9uRW5kcG9pbnRNYXBEdG8SVQoHZW50",
"cmllcxgBIAMoCzJELnNjYWRhYnJpZGdlLmNlbnRyYWxjb250cm9sLnYxLkNv",
"bm5lY3Rpb25FbmRwb2ludE1hcER0by5FbnRyaWVzRW50cnkaLgoMRW50cmll",
"c0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEixQEKEFRh",
"Z1F1YWxpdHlNYXBEdG8STQoHZW50cmllcxgBIAMoCzI8LnNjYWRhYnJpZGdl",
"LmNlbnRyYWxjb250cm9sLnYxLlRhZ1F1YWxpdHlNYXBEdG8uRW50cmllc0Vu",
"dHJ5GmIKDEVudHJpZXNFbnRyeRILCgNrZXkYASABKAkSQQoFdmFsdWUYAiAB",
"KAsyMi5zY2FkYWJyaWRnZS5jZW50cmFsY29udHJvbC52MS5UYWdRdWFsaXR5",
"Q291bnRzRHRvOgI4ASJQChFOb2RlU3RhdHVzTGlzdER0bxI7CgVub2RlcxgB",
"IAMoCzIsLnNjYWRhYnJpZGdlLmNlbnRyYWxjb250cm9sLnYxLk5vZGVTdGF0",
"dXNEdG8iPgoWU2VjcmV0TWlycm9yRmFpbHVyZUR0bxITCgtzZWNyZXRfbmFt",
"ZRgBIAEoCRIPCgdtZXNzYWdlGAIgASgJIqsBChlTZWNyZXRNaXJyb3JOb2Rl",
"U3RhdHVzRHRvEhEKCW5vZGVfbmFtZRgBIAEoCRIyCg5jaGVja2VkX2F0X3V0",
"YxgCIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASRwoIZmFpbHVy",
"ZXMYAyADKAsyNS5zY2FkYWJyaWRnZS5jZW50cmFsY29udHJvbC52MS5TZWNy",
"ZXRNaXJyb3JGYWlsdXJlRHRvImQKGVNlY3JldE1pcnJvclN0YXR1c0xpc3RE",
"dG8SRwoFbm9kZXMYASADKAsyOC5zY2FkYWJyaWRnZS5jZW50cmFsY29udHJv",
"bC52MS5TZWNyZXRNaXJyb3JOb2RlU3RhdHVzRHRvIokQChNTaXRlSGVhbHRo",
"UmVwb3J0RHRvEg8KB3NpdGVfaWQYASABKAkSFwoPc2VxdWVuY2VfbnVtYmVy",
"GAIgASgDEjQKEHJlcG9ydF90aW1lc3RhbXAYAyABKAsyGi5nb29nbGUucHJv",
"dG9idWYuVGltZXN0YW1wEnAKGGRhdGFfY29ubmVjdGlvbl9zdGF0dXNlcxgE",
"IAMoCzJOLnNjYWRhYnJpZGdlLmNlbnRyYWxjb250cm9sLnYxLlNpdGVIZWFs",
"dGhSZXBvcnREdG8uRGF0YUNvbm5lY3Rpb25TdGF0dXNlc0VudHJ5EmoKFXRh",
"Z19yZXNvbHV0aW9uX2NvdW50cxgFIAMoCzJLLnNjYWRhYnJpZGdlLmNlbnRy",
"YWxjb250cm9sLnYxLlNpdGVIZWFsdGhSZXBvcnREdG8uVGFnUmVzb2x1dGlv",
"bkNvdW50c0VudHJ5EhoKEnNjcmlwdF9lcnJvcl9jb3VudBgGIAEoBRIkChxh",
"bGFybV9ldmFsdWF0aW9uX2Vycm9yX2NvdW50GAcgASgFEnwKH3N0b3JlX2Fu",
"ZF9mb3J3YXJkX2J1ZmZlcl9kZXB0aHMYCCADKAsyUy5zY2FkYWJyaWRnZS5j",
"ZW50cmFsY29udHJvbC52MS5TaXRlSGVhbHRoUmVwb3J0RHRvLlN0b3JlQW5k",
"Rm9yd2FyZEJ1ZmZlckRlcHRoc0VudHJ5EhkKEWRlYWRfbGV0dGVyX2NvdW50",
"GAkgASgFEh8KF2RlcGxveWVkX2luc3RhbmNlX2NvdW50GAogASgFEh4KFmVu",
"YWJsZWRfaW5zdGFuY2VfY291bnQYCyABKAUSHwoXZGlzYWJsZWRfaW5zdGFu",
"Y2VfY291bnQYDCABKAUSEQoJbm9kZV9yb2xlGA0gASgJEhUKDW5vZGVfaG9z",
"dG5hbWUYDiABKAkSWgoZZGF0YV9jb25uZWN0aW9uX2VuZHBvaW50cxgPIAEo",
"CzI3LnNjYWRhYnJpZGdlLmNlbnRyYWxjb250cm9sLnYxLkNvbm5lY3Rpb25F",
"bmRwb2ludE1hcER0bxJUChtkYXRhX2Nvbm5lY3Rpb25fdGFnX3F1YWxpdHkY",
"ECABKAsyLy5zY2FkYWJyaWRnZS5jZW50cmFsY29udHJvbC52MS5UYWdRdWFs",
"aXR5TWFwRHRvEhwKFHBhcmtlZF9tZXNzYWdlX2NvdW50GBEgASgFEkcKDWNs",
"dXN0ZXJfbm9kZXMYEiABKAsyMC5zY2FkYWJyaWRnZS5jZW50cmFsY29udHJv",
"bC52MS5Ob2RlU3RhdHVzTGlzdER0bxIhChlzaXRlX2F1ZGl0X3dyaXRlX2Zh",
"aWx1cmVzGBMgASgFEh8KF2F1ZGl0X3JlZGFjdGlvbl9mYWlsdXJlGBQgASgF",
"ElYKEnNpdGVfYXVkaXRfYmFja2xvZxgVIAEoCzI6LnNjYWRhYnJpZGdlLmNl",
"bnRyYWxjb250cm9sLnYxLlNpdGVBdWRpdEJhY2tsb2dTbmFwc2hvdER0bxIl",
"Ch1zaXRlX2V2ZW50X2xvZ193cml0ZV9mYWlsdXJlcxgWIAEoAxJHCiFvbGRl",
"c3RfcGFya2VkX21lc3NhZ2VfYWdlX3NlY29uZHMYFyABKAsyHC5nb29nbGUu",
"cHJvdG9idWYuRG91YmxlVmFsdWUSGgoSc2NyaXB0X3F1ZXVlX2RlcHRoGBgg",
"ASgFEhsKE3NjcmlwdF9idXN5X3RocmVhZHMYGSABKAUSRAoec2NyaXB0X29s",
"ZGVzdF9idXN5X2FnZV9zZWNvbmRzGBogASgLMhwuZ29vZ2xlLnByb3RvYnVm",
"LkRvdWJsZVZhbHVlEkIKHmxvY2FsX2RiX3JlcGxpY2F0aW9uX2Nvbm5lY3Rl",
"ZBgbIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5Cb29sVmFsdWUSOwoWbG9jYWxf",
"ZGJfb3Bsb2dfYmFja2xvZxgcIAEoCzIbLmdvb2dsZS5wcm90b2J1Zi5JbnQ2",
"NFZhbHVlEj0KGXN0YXJ0dXBfcmVjb25jaWxlcl9hY3RpdmUYHSABKAsyGi5n",
"b29nbGUucHJvdG9idWYuQm9vbFZhbHVlEh8KF2RldGFjaGVkX3NjcmlwdF90",
"aHJlYWRzGB4gASgFEh0KFXNjcmlwdF9ydW5fc2hlZF9jb3VudBgfIAEoBRIk",
"ChxzaXRlX3N0cmVhbV9hbGFybV9kcm9wX2NvdW50GCAgASgDElgKFnNlY3Jl",
"dF9taXJyb3Jfc3RhdHVzZXMYISABKAsyOC5zY2FkYWJyaWRnZS5jZW50cmFs",
"Y29udHJvbC52MS5TZWNyZXRNaXJyb3JTdGF0dXNMaXN0RHRvGnIKG0RhdGFD",
"b25uZWN0aW9uU3RhdHVzZXNFbnRyeRILCgNrZXkYASABKAkSQgoFdmFsdWUY",
"AiABKA4yMy5zY2FkYWJyaWRnZS5jZW50cmFsY29udHJvbC52MS5Db25uZWN0",
"aW9uSGVhbHRoRW51bToCOAEacQoYVGFnUmVzb2x1dGlvbkNvdW50c0VudHJ5",
"EgsKA2tleRgBIAEoCRJECgV2YWx1ZRgCIAEoCzI1LnNjYWRhYnJpZGdlLmNl",
"bnRyYWxjb250cm9sLnYxLlRhZ1Jlc29sdXRpb25TdGF0dXNEdG86AjgBGkIK",
"IFN0b3JlQW5kRm9yd2FyZEJ1ZmZlckRlcHRoc0VudHJ5EgsKA2tleRgBIAEo",
"CRINCgV2YWx1ZRgCIAEoBToCOAEiYwoWU2l0ZUhlYWx0aFJlcG9ydEFja0R0",
"bxIPCgdzaXRlX2lkGAEgASgJEhcKD3NlcXVlbmNlX251bWJlchgCIAEoAxIQ",
"CghhY2NlcHRlZBgDIAEoCBINCgVlcnJvchgEIAEoCSKLAQoMSGVhcnRiZWF0",
"RHRvEg8KB3NpdGVfaWQYASABKAkSFQoNbm9kZV9ob3N0bmFtZRgCIAEoCRIR",
"Cglpc19hY3RpdmUYAyABKAgSLQoJdGltZXN0YW1wGAQgASgLMhouZ29vZ2xl",
"LnByb3RvYnVmLlRpbWVzdGFtcBIRCglzeW50aGV0aWMYBSABKAgqvQEKFENv",
"bm5lY3Rpb25IZWFsdGhFbnVtEiEKHUNPTk5FQ1RJT05fSEVBTFRIX1VOU1BF",
"Q0lGSUVEEAASHwobQ09OTkVDVElPTl9IRUFMVEhfQ09OTkVDVEVEEAESIgoe",
"Q09OTkVDVElPTl9IRUFMVEhfRElTQ09OTkVDVEVEEAISIAocQ09OTkVDVElP",
"Tl9IRUFMVEhfQ09OTkVDVElORxADEhsKF0NPTk5FQ1RJT05fSEVBTFRIX0VS",
"Uk9SEAQyoQYKFUNlbnRyYWxDb250cm9sU2VydmljZRKDAQoSU3VibWl0Tm90",
"aWZpY2F0aW9uEjQuc2NhZGFicmlkZ2UuY2VudHJhbGNvbnRyb2wudjEuTm90",
"aWZpY2F0aW9uU3VibWl0RHRvGjcuc2NhZGFicmlkZ2UuY2VudHJhbGNvbnRy",
"b2wudjEuTm90aWZpY2F0aW9uU3VibWl0QWNrRHRvEpIBChdRdWVyeU5vdGlm",
"aWNhdGlvblN0YXR1cxI5LnNjYWRhYnJpZGdlLmNlbnRyYWxjb250cm9sLnYx",
"Lk5vdGlmaWNhdGlvblN0YXR1c1F1ZXJ5RHRvGjwuc2NhZGFicmlkZ2UuY2Vu",
"dHJhbGNvbnRyb2wudjEuTm90aWZpY2F0aW9uU3RhdHVzUmVzcG9uc2VEdG8S",
"RwoRSW5nZXN0QXVkaXRFdmVudHMSGy5zaXRlc3RyZWFtLkF1ZGl0RXZlbnRC",
"YXRjaBoVLnNpdGVzdHJlYW0uSW5nZXN0QWNrElAKFUluZ2VzdENhY2hlZFRl",
"bGVtZXRyeRIgLnNpdGVzdHJlYW0uQ2FjaGVkVGVsZW1ldHJ5QmF0Y2gaFS5z",
"aXRlc3RyZWFtLkluZ2VzdEFjaxKAAQoNUmVjb25jaWxlU2l0ZRI2LnNjYWRh",
"YnJpZGdlLmNlbnRyYWxjb250cm9sLnYxLlJlY29uY2lsZVNpdGVSZXF1ZXN0",
"RHRvGjcuc2NhZGFicmlkZ2UuY2VudHJhbGNvbnRyb2wudjEuUmVjb25jaWxl",
"U2l0ZVJlc3BvbnNlRHRvEn0KEFJlcG9ydFNpdGVIZWFsdGgSMi5zY2FkYWJy",
"aWRnZS5jZW50cmFsY29udHJvbC52MS5TaXRlSGVhbHRoUmVwb3J0RHRvGjUu",
"c2NhZGFicmlkZ2UuY2VudHJhbGNvbnRyb2wudjEuU2l0ZUhlYWx0aFJlcG9y",
"dEFja0R0bxJQCglIZWFydGJlYXQSKy5zY2FkYWJyaWRnZS5jZW50cmFsY29u",
"dHJvbC52MS5IZWFydGJlYXREdG8aFi5nb29nbGUucHJvdG9idWYuRW1wdHlC",
"K6oCKFpCLk1PTS5XVy5TY2FkYUJyaWRnZS5Db21tdW5pY2F0aW9uLkdycGNi",
"BnByb3RvMw=="));
descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
new pbr::FileDescriptor[] { global::Google.Protobuf.WellKnownTypes.EmptyReflection.Descriptor, global::Google.Protobuf.WellKnownTypes.TimestampReflection.Descriptor, global::Google.Protobuf.WellKnownTypes.WrappersReflection.Descriptor, global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.SitestreamReflection.Descriptor, },
new pbr::GeneratedClrTypeInfo(new[] {typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.ConnectionHealthEnum), }, null, new pbr::GeneratedClrTypeInfo[] {
@@ -168,7 +169,7 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.TagResolutionStatusDto), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.TagResolutionStatusDto.Parser, new[]{ "TotalSubscribed", "SuccessfullyResolved" }, null, null, null, null),
new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.TagQualityCountsDto), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.TagQualityCountsDto.Parser, new[]{ "Good", "Bad", "Uncertain" }, null, null, null, null),
new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.NodeStatusDto), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.NodeStatusDto.Parser, new[]{ "Hostname", "IsOnline", "Role" }, null, null, null, null),
new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.SiteAuditBacklogSnapshotDto), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.SiteAuditBacklogSnapshotDto.Parser, new[]{ "PendingCount", "OldestPendingUtc", "OnDiskBytes" }, null, null, null, null),
new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.SiteAuditBacklogSnapshotDto), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.SiteAuditBacklogSnapshotDto.Parser, new[]{ "PendingCount", "OldestPendingUtc", "OnDiskBytes", "RejectedCount" }, null, null, null, null),
new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.ConnectionEndpointMapDto), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.ConnectionEndpointMapDto.Parser, new[]{ "Entries" }, null, null, null, new pbr::GeneratedClrTypeInfo[] { null, }),
new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.TagQualityMapDto), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.TagQualityMapDto.Parser, new[]{ "Entries" }, null, null, null, new pbr::GeneratedClrTypeInfo[] { null, }),
new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.NodeStatusListDto), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.NodeStatusListDto.Parser, new[]{ "Nodes" }, null, null, null, null),
@@ -3408,6 +3409,7 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
pendingCount_ = other.pendingCount_;
oldestPendingUtc_ = other.oldestPendingUtc_ != null ? other.oldestPendingUtc_.Clone() : null;
onDiskBytes_ = other.onDiskBytes_;
rejectedCount_ = other.rejectedCount_;
_unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
}
@@ -3456,6 +3458,23 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
}
}
/// <summary>Field number for the "rejected_count" field.</summary>
public const int RejectedCountFieldNumber = 4;
private int rejectedCount_;
/// <summary>
/// Rows central permanently refused and the site moved to the Rejected forward
/// state (a subset of pending_count). Additive; an older site omits it (reads 0)
/// and an older central ignores it (Gitea #69).
/// </summary>
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public int RejectedCount {
get { return rejectedCount_; }
set {
rejectedCount_ = value;
}
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public override bool Equals(object other) {
@@ -3474,6 +3493,7 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
if (PendingCount != other.PendingCount) return false;
if (!object.Equals(OldestPendingUtc, other.OldestPendingUtc)) return false;
if (OnDiskBytes != other.OnDiskBytes) return false;
if (RejectedCount != other.RejectedCount) return false;
return Equals(_unknownFields, other._unknownFields);
}
@@ -3484,6 +3504,7 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
if (PendingCount != 0) hash ^= PendingCount.GetHashCode();
if (oldestPendingUtc_ != null) hash ^= OldestPendingUtc.GetHashCode();
if (OnDiskBytes != 0L) hash ^= OnDiskBytes.GetHashCode();
if (RejectedCount != 0) hash ^= RejectedCount.GetHashCode();
if (_unknownFields != null) {
hash ^= _unknownFields.GetHashCode();
}
@@ -3514,6 +3535,10 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
output.WriteRawTag(24);
output.WriteInt64(OnDiskBytes);
}
if (RejectedCount != 0) {
output.WriteRawTag(32);
output.WriteInt32(RejectedCount);
}
if (_unknownFields != null) {
_unknownFields.WriteTo(output);
}
@@ -3536,6 +3561,10 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
output.WriteRawTag(24);
output.WriteInt64(OnDiskBytes);
}
if (RejectedCount != 0) {
output.WriteRawTag(32);
output.WriteInt32(RejectedCount);
}
if (_unknownFields != null) {
_unknownFields.WriteTo(ref output);
}
@@ -3555,6 +3584,9 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
if (OnDiskBytes != 0L) {
size += 1 + pb::CodedOutputStream.ComputeInt64Size(OnDiskBytes);
}
if (RejectedCount != 0) {
size += 1 + pb::CodedOutputStream.ComputeInt32Size(RejectedCount);
}
if (_unknownFields != null) {
size += _unknownFields.CalculateSize();
}
@@ -3579,6 +3611,9 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
if (other.OnDiskBytes != 0L) {
OnDiskBytes = other.OnDiskBytes;
}
if (other.RejectedCount != 0) {
RejectedCount = other.RejectedCount;
}
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
}
@@ -3613,6 +3648,10 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
OnDiskBytes = input.ReadInt64();
break;
}
case 32: {
RejectedCount = input.ReadInt32();
break;
}
}
}
#endif
@@ -3647,6 +3686,10 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
OnDiskBytes = input.ReadInt64();
break;
}
case 32: {
RejectedCount = input.ReadInt32();
break;
}
}
}
}
@@ -581,6 +581,7 @@ public static class CentralControlDtoMapper
{
PendingCount = backlog.PendingCount,
OnDiskBytes = backlog.OnDiskBytes,
RejectedCount = backlog.RejectedCount,
};
if (backlog.OldestPendingUtc.HasValue)
@@ -675,7 +676,8 @@ public static class CentralControlDtoMapper
OldestPendingUtc: snapshot.OldestPendingUtc is null
? null
: DateTime.SpecifyKind(snapshot.OldestPendingUtc.ToDateTime(), DateTimeKind.Utc),
OnDiskBytes: snapshot.OnDiskBytes);
OnDiskBytes: snapshot.OnDiskBytes,
RejectedCount: snapshot.RejectedCount);
}
List<SecretMirrorNodeStatus>? mirrorStatuses = null;
@@ -170,6 +170,10 @@ message SiteAuditBacklogSnapshotDto {
int32 pending_count = 1;
google.protobuf.Timestamp oldest_pending_utc = 2; // absent when the queue is empty
int64 on_disk_bytes = 3;
// Rows central permanently refused and the site moved to the Rejected forward
// state (a subset of pending_count). Additive; an older site omits it (reads 0)
// and an older central ignores it (Gitea #69).
int32 rejected_count = 4;
}
// The three collection wrappers below exist so null and empty stay