fix(hosts): a driver that never built a tag table reports NO ANSWER, not zero skips
Found on the docker-dev live gate, not in tests. Two drivers that faulted during initialise (rtu-gate, opcua1) each rendered "0 tags" on /hosts against two authored tags apiece, because a ledger that had never committed returned SkippedTagReport.Empty. A zero report is a positive claim -- "this driver skipped nothing" -- and making it on behalf of a driver that never got as far as looking is exactly the quiet inaccuracy #541 exists to remove. GetSkippedTags() and SkippedTagLedger.Snapshot() now return null until the first committed build, so the column renders the same em-dash it already used for a driver that cannot report at all. Both are 'no answer'; neither is 'nothing skipped'. Knock-on: a FOCAS driver whose only init threw now reports null rather than a zero report, which is strictly more honest than the assertion the test originally made.
This commit is contained in:
@@ -44,8 +44,15 @@ public interface ISkippedTagReporter
|
||||
/// Must be a pure in-memory read with no I/O — it is called on the health-publish path, is
|
||||
/// deliberately NOT routed through the resilience pipeline, and must never block.
|
||||
/// </remarks>
|
||||
/// <returns>The current report; never null. An all-zero report means "nothing skipped".</returns>
|
||||
SkippedTagReport GetSkippedTags();
|
||||
/// <returns>
|
||||
/// The report from the driver's most recent tag-table build, or <see langword="null"/> when it has
|
||||
/// not built one yet — a driver that faulted during initialise has <b>no answer</b>, which is not
|
||||
/// the same as the answer "nothing skipped". Returning a zero report there would state that a
|
||||
/// driver with a dozen authored tags has none, which is the class of quiet inaccuracy this whole
|
||||
/// capability exists to remove. (Found on the docker-dev rig, where two faulted drivers each
|
||||
/// rendered "0 tags" against two authored ones.)
|
||||
/// </returns>
|
||||
SkippedTagReport? GetSkippedTags();
|
||||
}
|
||||
|
||||
/// <summary>One authored tag a driver could not build into its poll table, and why.</summary>
|
||||
@@ -71,9 +78,6 @@ public sealed record SkippedTag(string RawPath, string Reason);
|
||||
/// </param>
|
||||
public sealed record SkippedTagReport(int AuthoredCount, int SkippedCount, IReadOnlyList<SkippedTag> Sample)
|
||||
{
|
||||
/// <summary>A driver that has authored nothing and skipped nothing.</summary>
|
||||
public static readonly SkippedTagReport Empty = new(0, 0, []);
|
||||
|
||||
/// <summary>Whether any authored tag was skipped — i.e. whether this is worth an operator's attention.</summary>
|
||||
public bool HasSkips => SkippedCount > 0;
|
||||
|
||||
@@ -113,7 +117,9 @@ public sealed class SkippedTagLedger
|
||||
public const int MaxSample = 20;
|
||||
|
||||
private readonly object _gate = new();
|
||||
private SkippedTagReport _visible = SkippedTagReport.Empty;
|
||||
|
||||
/// <summary>Null until the first <see cref="Commit"/> — "no answer yet", never a zero report.</summary>
|
||||
private SkippedTagReport? _visible;
|
||||
|
||||
private int _stagedAuthored;
|
||||
private int _stagedSkipped;
|
||||
@@ -168,8 +174,9 @@ public sealed class SkippedTagLedger
|
||||
}
|
||||
|
||||
/// <summary>The current published snapshot.</summary>
|
||||
/// <returns>The last committed report, or <see cref="SkippedTagReport.Empty"/> before the first commit.</returns>
|
||||
public SkippedTagReport Snapshot()
|
||||
/// <returns>The last committed report, or <see langword="null"/> before the first commit — a driver
|
||||
/// that has not built a tag table has no answer, and a zero report would be a false one.</returns>
|
||||
public SkippedTagReport? Snapshot()
|
||||
{
|
||||
lock (_gate) return _visible;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user