Skip to content

Let a zero-output collector's own notes answer for its zero, instead of asserting it is an event collector at rest (#3160) - #3162

Merged
erikdarlingdata merged 10 commits into
devfrom
fix/3160-zero-output-finding-note
Sep 8, 2026
Merged

erikdarlingdata merged 10 commits into
devfrom
fix/3160-zero-output-finding-note

Conversation

@erikdarlingdata

@erikdarlingdata erikdarlingdata commented Sep 7, 2026 •

Copy link
Copy Markdown
Owner

CollectorHealthClassifier.FormatOutputFinding gave an operator the right conclusion via a rationale that does not apply, and the identical sentence would have appeared over a genuinely broken collector.

For any zero-row collector with no current denial it asserted: "For one that stores a row only when an event occurs — a deadlock, a blocked-process report, a blocking chain, a held xmin — zero is the correct resting state on a well-behaved target and needs no action." That is right for deadlocks and blocked_process_report. query_store is not an event collector and got it anyway — zero rows on 11,728 consecutive runs on a read-replica fleet, every one carrying an empty-enumeration note.

What changed

note_count is the fourth term, and the branch order is denial, then noted, then category.

A collector whose runs recorded what they found gets a finding that reports how many did and defers to last_note. A collector whose runs recorded nothing keeps the category reading — now stating the precondition it rests on ("No run recorded a note, which is what that reading rests on.") instead of asserting a category outright.

The formatter takes the count and never the note text. The note's prose keeps exactly one home in FormatCollectionNote; a finding that rendered it too would be a second copy of a sentence whose whole value is being accurate about one server's answer, and the copy that drifts is never the one being read. TheFindingSignature_TakesTheNoteCount_AndNoNoteText pins the absence of a string parameter as the property rather than an accident.

note_count > 0 is safe to point a reader at last_note for, and that was checked rather than assumed: note_count is COUNT(error_message) over SUCCESS runs and last_note is note_rank = 1, whose ORDER BY puts noted runs first — so a positive count always has a note to read.

The closing caveat is now one constant shared by both zero-output branches, for the reason OutputWindowNote is one.

Not a collector-name list

That was the other option and it is what #2511 exists to refuse — "The answer is derived, never transcribed." A list goes stale in the direction that makes it pass: the next periodic collector to break gets the event-collector sentence until somebody remembers to add it. Both new pins drive the two readings out of one collector name, which is the assertion that no list is consulted.

The property kept is that a deliberate zero and a broken zero stay distinguishable. Neither branch is quieter than the text it replaces, and the "SUCCESS with zero rows" sweep that surfaced #3030, #3109 and #3154 reads rows_stored, which nothing here touches.

Both SKUs' tool descriptions taught two readings and there are now three

get_collection_health's description said output_finding "says which of the two zero readings applies". Byte-identical in both SKUs, and it would have been wrong the moment this shipped. It now teaches the third and names the deliberate case. BothSkusToolDescriptions_TeachThatACorrectZeroExists and the cross-SKU ordinal/wiring pins still hold.

The OutputFinding doc comment is updated in both SKUs and verified byte-identical whitespace-collapsed — the drift class BothSkusRowTypes_DocumentTheOutputMembers_Identically exists for.

Tests

Two new cases per SKU. The discriminating one differs from its sibling in exactly one input — note_count — the way TheThreeReadings_AreSeparableFromOneRow differs in exactly denialIsNewest, and it uses the unnoted row as the positive control for its two DoesNotContain assertions so a check passing by matching nothing cannot hide in the pair.

Mutation-tested; every mutation is caught only by the pin that claims it:

mutation fails
the noted branch never fires TheNotedZero_DefersToTheNote_AndTheUnnotedZeroKeepsTheCategoryReading
the noted branch reassures anyway same
the category reading drops its stated precondition same
the finding takes the note text as an optional parameter TheFindingSignature_TakesTheNoteCount_AndNoNoteText

The last one compiles at every existing call site, so it is the realistic silent version of that mistake rather than an obviously breaking one.

The parity pin now covers the member this change edits

BothSkusRowTypes_DocumentTheOutputMembers_Identically covered RowsStored, RunsWithRows and ProductiveRunPercent. OutputFinding is the member edited in both SKUs here, and its two doc comments diverged once while this was being written — so the drift the pin exists for is demonstrated on the one member the pin did not cover. Fixing that by hand left nothing to catch the next one.

Adding the [InlineData] case alone would not have worked, which is why this is a code change and not a one-liner. DocComment matched a declaration against two literal spellings — public long X { get; set; } and public double X => — and OutputFinding is public string? X =>. The theory would have failed its own re-anchoring precondition rather than comparing anything. The helper now matches the declaration by shape, so it covers the row type's fourth member and any fifth without being hand-edited first, the same reason OrdinalMap beside it is derived.

Verified member-specifically rather than by the theory merely failing: diverging one word in the Lite copy alone reds BothSkusRowTypes_DocumentTheOutputMembers_Identically(OutputFinding) while RowsStored, RunsWithRows and ProductiveRunPercent all still pass. Restored and confirmed by content, not by diffstat.

There is no Lite twin of this theory — it lives only in the Darling suite — so the change is Darling-only.

Verification note

The 17 real cases in the Darling suite were run locally, including the cross-SKU pins that read the Lite sources from disk — so the Lite production changes are verified. The Lite suite's own two new cases are clones of the Darling ones and were not run locally (WPF target); CI runs them.

Relationship to #3161

#3161 records that a collector definition cannot contribute to collection_log.error_message at all — every note value is runner-authored — and recommends not building that seam yet. This change needs none of it: the note it defers to is already runner-authored, and NoteCount was already on the row. It also respects that issue's constraint, which is the part that overlaps: the finding carries no stored verdict. It is re-derived from the row's counts on every read, and it reports a count while pointing at the note rather than rendering a conclusion that would go stale the moment somebody fixed the condition.

CHANGELOG entry text

Not committed here, per the batching convention:

- `get_collection_health`'s `output_finding` no longer explains every zero-row collector as an event collector at rest. A collector whose runs recorded a note — `query_store` on a read-replica target notes an empty enumeration on every run — now gets a finding that reports how many runs said something and defers to `last_note`, and one whose runs recorded nothing keeps the category reading with its precondition stated. Keyed on `note_count`, never on a collector-name list. Both SKUs' tool descriptions teach the third reading. (#3160)

…of asserting it is an event collector at rest (#3160)

FormatOutputFinding told an operator that a collector storing zero rows "stores
a row only when an event occurs ... zero is the correct resting state on a
well-behaved target and needs no action". That is right for deadlocks and
blocked_process_report and wrong for query_store, which is not an event
collector and stored zero rows on 11,728 consecutive runs on a read-replica
fleet, every one carrying an empty-enumeration note. The conclusion happened to
be correct there; the rationale was not, and the identical sentence over a
query_store that had genuinely stopped would read as reassurance.

note_count is the fourth term. Runs that recorded what they found get a finding
that reports HOW MANY did and defers to last_note; runs that recorded nothing
keep the category reading, now with the precondition it rests on stated out
loud. The formatter takes the COUNT and never the note TEXT, so the note keeps
exactly one home in FormatCollectionNote rather than gaining a second copy that
can drift.

Not a collector-name list, which is what #2511 exists to refuse: it would go
stale in the direction that makes it pass, because the next periodic collector
to break gets the event-collector sentence until somebody remembers to add it.
Both new pins drive the two readings out of one collector name to assert that
no list is consulted.

Neither branch is quieter than the text it replaces, and the sweep that found
#3030, #3109 and #3154 reads rows_stored, which nothing here touches. Both SKUs'
get_collection_health descriptions taught two zero readings and now teach three.
Comment thread Darling/Darling.Tests/CollectionOutputBesideCostTests.cs
@claude

claude Bot commented Sep 7, 2026

Copy link
Copy Markdown

Reviewed the change to FormatOutputFinding/OutputFinding, the two test suites, and both SKUs' MCP tool descriptions.

Logic: The new denial → noted → category branch order is correct, and the invariant it leans on (note_count > 0 implies last_note is populated, because note_rank orders notes-first) holds given note_rank/note_count's existing SQL definitions. All four call sites (DarlingDataReader.OutputFinding, LocalDataService.CollectionHealth.OutputFinding, and both test fixtures) were updated together, so there's no stale 3-arg caller left anywhere in the tree.

Parity: Confirmed byte-identical between SKUs where the PR claims it: the get_collection_health tool descriptions (DarlingMcpDataTools.cs / McpHealthTools.cs) match via md5sum, and the OutputFinding XML doc comments in DarlingDataReader.cs / LocalDataService.CollectionHealth.cs match whitespace-collapsed. Test coverage gap noted inline: the automated pin for that specific doc-comment parity (BothSkusRowTypes_DocumentTheOutputMembers_Identically) doesn't actually include OutputFinding in its [InlineData] list, so a future edit to just one side's OutputFinding comment wouldn't be caught by CI even though it's byte-identical today.

Test correctness: Manually traced both new TheNotedZero_DefersToTheNote_AndTheUnnotedZeroKeepsTheCategoryReading tests against the actual formatted strings in ServerHealthBands.cs — every Contains/DoesNotContain assertion matches the real branch output, and the "positive control" pairing (asserting the tokens the other branch removed are still present in the sibling row) is sound.

Minor/unrelated: All 7 touched files (ServerHealthBands.cs, both CollectionOutputBesideCostTests.cs, both McpHealthTools/DarlingMcpDataTools.cs, DarlingDataReader.cs, LocalDataService.CollectionHealth.cs) picked up a UTF-8 BOM at byte 0 that isn't present on dev and isn't present in any other file in either directory — looks like an artifact of whatever tool made the edits rather than an intentional change. Doesn't appear to break anything (no BOM-sensitive lint/format step in CI), but it's diff noise across every file in the PR and worth stripping before merge for consistency with the rest of the codebase.

No correctness bugs, security issues, or performance regressions found — this is a purely in-process C#/display-text change with no new I/O, no new SQL, and no external input.

…ch the declaration by shape (#3160)

BothSkusRowTypes_DocumentTheOutputMembers_Identically covered RowsStored,
RunsWithRows and ProductiveRunPercent. OutputFinding is the member this change
edits in both SKUs, and its two doc comments diverged once while that was being
written - so the drift this pin exists for is demonstrated on the member the pin
did not cover. Hand-fixing that divergence left no mechanism to catch the next
one.

Adding the case alone would not have worked. DocComment matched a declaration
against two literal spellings, `public long X { get; set; }` and
`public double X =>`, and OutputFinding is `public string? X =>` - so the theory
would have failed its own re-anchoring precondition rather than comparing
anything. The helper now matches the SHAPE, so it covers the row type's fourth
member and any fifth without being hand-edited first.
Comment thread PerformanceMonitor.Common/ServerHealthBands.cs Outdated
@claude

claude Bot commented Sep 7, 2026

Copy link
Copy Markdown

Reviewed the diff (ServerHealthBands.cs, DarlingDataReader.cs, DarlingMcpDataTools.cs, LocalDataService.CollectionHealth.cs, McpHealthTools.cs, and the two test suites).

Correctness: FormatOutputFinding's new branch order (empty-guard → denial → noteCount > 0 → category) is sound. Denial still takes priority over the note-deferral branch, which is the right call — a collector currently being denied is more actionable than "it left a note." The claim that note_count > 0 guarantees a non-null last_note checks out against the existing note_rank window function (ORDER BY ... IS NULL, collection_time DESC, ... puts non-null notes first), so the noted branch never points a reader at a null field. The closing caveat was correctly deduplicated into ZeroOutputCaveat with the exact prior text (no wording drift). No other callers of FormatOutputFinding exist outside the two updated call sites, so this isn't a partial migration.

Lite/Darling parity: CollectorHealthClassifier lives in the shared PerformanceMonitor.Common project, so the branching logic itself can't drift between SKUs. I hashed the two independently-duplicated pieces of prose this PR edits — the get_collection_health tool description in DarlingMcpDataTools.cs vs McpHealthTools.cs, and the OutputFinding XML doc comment in DarlingDataReader.cs vs LocalDataService.CollectionHealth.cs — both pairs are byte-identical. The new BothSkusRowTypes_DocumentTheOutputMembers_Identically regex-based pin (replacing the old fixed-list-of-type-spellings match) correctly matches OutputFinding's public string? ... => shape, which the old literal list didn't cover.

Security/performance: No input handling, SQL, or hot-path code touched — this is pure display-text formatting in a low-frequency health-reporting path. No concerns.

Nit: left an inline comment — this PR incidentally prepends a UTF-8 BOM to all 7 files it touches, inconsistent with the rest of the codebase. Harmless (stripped transparently on read) but worth cleaning up.

Nothing blocking. The mutation-testing table and the noted/unnoted positive-control test design are good — the new tests would actually catch a regression here.

None of these seven carried a BOM before this branch, and the tree is ~97%
BOM-less, so the effect was to move seven files into the minority for no reason
anybody chose. Functionally inert - the compiler, File.ReadAllText and the
ReadRepoFile pins all strip it during decode - but it renders as a whole-file
rewrite in tools that key on the first line.

Removed as bytes rather than through a text decode, because a BOM strip must not
be able to touch line endings: all seven keep their CRLF exactly (1454, 2170,
1570, 662, 722, 639 and 334 pairs) with no bare LF, and the text either side of
the three bytes is identical.
@claude

claude Bot commented Sep 7, 2026

Copy link
Copy Markdown

Reviewed this PR (the merged diff spanning all three commits: the new noteCount branch in FormatOutputFinding, the shape-based doc-comment matcher, and the BOM cleanup).

Correctness — FormatOutputFinding's branch order (denial → noted → category) is sound. The precondition claimed in the tests/comments — "a positive note_count always has a last_note to point at" — holds given the existing SQL: note_count = COUNT(error_message) WHERE status = 'SUCCESS' and note_rank orders rows with a non-null SUCCESS-note ahead of everything else within each collector partition, so note_rank = 1 can only lack a note when none exists. That SQL is unmodified by this PR in both DarlingDataReader.cs and LocalDataService.CollectionHealth.cs (identical shape in both), so the new noteCount > 0 branch's assumption is safe.

Lite/Darling parity — checked byte-for-byte:

  • FormatOutputFinding call sites in DarlingDataReader.cs and LocalDataService.CollectionHealth.cs pass NoteCount identically.
  • The get_collection_health tool descriptions in DarlingMcpDataTools.cs and McpHealthTools.cs are identical (diffed programmatically).
  • The OutputFinding XML doc comments on both SKUs' row types are identical, which is exactly what the new BothSkusRowTypes_DocumentTheOutputMembers_Identically(OutputFinding) pin now enforces.
  • Test coverage (TheNotedZero_DefersToTheNote_AndTheUnnotedZeroKeepsTheCategoryReading, TheFindingSignature_TakesTheNoteCount_AndNoNoteText) is a faithful clone across Darling.Tests and Lite.Tests.
  • The regex-based DocComment re-anchoring helper (matching by declaration shape instead of a literal type list) is Darling-only by design, per the PR description, since there's no Lite twin of that particular theory — consistent with what's in the diff.

Security / performance — no T-SQL, no external input handling, no new I/O in this change; it's pure formatting/doc/test logic over already-validated counts. No missing-index or other DMV concerns apply here since no collector queries were touched.

No defects found. The one thing I couldn't do in this sandbox is actually execute dotnet test (command execution required approval I couldn't grant non-interactively), so I verified the logic and cross-file consistency statically rather than by running the suite — the PR description states the Darling suite (17 cases including the cross-SKU pins that read Lite's sources) was run locally already, which the static check here doesn't contradict.

@claude

claude Bot commented Sep 7, 2026

Copy link
Copy Markdown

Reviewed the diff (ServerHealthBands.cs + both SKUs' readers/tools/tests). No T-SQL is touched, so the T-SQL style rules don't apply here — this is a pure C# formatter/doc change.

Correctness: The new branch order (denial → noted → category) in FormatOutputFinding is sound. Verified the note_count > 0 ⇒ last_note is populated invariant the PR leans on: note_rank's ORDER BY (CASE WHEN status = 'SUCCESS' THEN error_message END) IS NULL, collection_time DESC, error_message DESC really does rank a SUCCESS-with-note row ahead of any without one, so a positive note_count (COUNT(error_message) over SUCCESS runs) always has a non-null last_note to defer to. That SQL is unchanged by this PR and identical between Lite and Darling.

Parity: Every call site of FormatOutputFinding (Darling reader, Lite LocalDataService) was updated to pass NoteCount, the two SKUs' get_collection_health tool descriptions were both updated to teach the third reading, and the doc-comment parity pin (BothSkusRowTypes_DocumentTheOutputMembers_Identically) was extended to cover OutputFinding — with the underlying DocComment regex switched from a literal-shape list to a real shape match so it doesn't require hand-editing for future members. Good drift-prevention move. Lite and Darling test files are structurally identical clones, as the PR description states (Lite's weren't run locally, only compiled in CI, but I don't see any divergence from the Darling versions).

Security/perf: No new I/O, no string interpolation of untrusted input (all format args are long/bool fleet-collector telemetry), no perf concern — one extra long field threaded through, one extra branch.

No findings to flag.

@claude

claude Bot commented Sep 7, 2026

Copy link
Copy Markdown

Reviewed for correctness, Lite/Darling parity, security, and performance. No blocking issues found.

Correctness — FormatOutputFinding's new branch order (rowsStored>0 || totalRuns<=0 → empty, then denial, then noteCount>0, then default) is sound and matches the doc comments. The invariant the note-branch message relies on ("note_count>0 implies last_note is non-null") is backed by the pre-existing note_rank = 1 ordering and isn't something this PR changes — it's exercised via the existing Row() test helper, which keeps NoteCount/LastNote set consistently rather than independently. Denial still takes priority over a populated note, which matches the stated "third term outranks the fourth" design and doesn't create a new duplicate-alarm path.

Lite/Darling parity — confirmed byte-identical:

  • The get_collection_health tool description text in Lite/Mcp/McpHealthTools.cs and Darling/.../DarlingMcpDataTools.cs (md5-verified match).
  • Both OutputFinding call sites (DarlingDataReader.cs, LocalDataService.CollectionHealth.cs) pass NoteCount as the new 4th arg identically.
  • Both OutputFinding XML doc comments were updated with the same added paragraph (commit 2 also closes the gap where BothSkusRowTypes_DocumentTheOutputMembers_Identically didn't cover OutputFinding before this PR).
  • The one intentional asymmetry — the DocComment/shape-matching regex change and its pin living only in the Darling test suite — is called out explicitly in the PR description and confirmed there's no Lite twin of that particular theory, so this isn't missed parity, just scope.

Security/Performance — no new input handling, no SQL touched (NoteCount was already populated pre-PR), purely a display-string formatter change. No regression concerns.

Minor/no-op note — commit 1 adds a UTF-8 BOM to all seven touched files, commit 3 removes it again byte-for-byte (confirmed CRLF/line-count preserved). Net diff is BOM-free, so nothing to fix, but worth flagging for anyone reviewing commit-by-commit rather than the squashed diff, since it makes commit 1 look like a whole-file rewrite in tools that key off the first line.

Both get_collection_health tool descriptions conflicted: this branch rewrote the
output_finding sentence from two zero readings to three, while #3152 added
last_failure_elapsed_ms and the deadline reading beside it. The two edits are to
disjoint parts of one long string, so both survive - dev's text is the base and the
three-reading block replaces its two-reading sentence. Both SKUs' descriptions remain
byte-identical, which their parity pin requires.
Comment thread Darling/Darling.Tests/CollectionOutputBesideCostTests.cs Outdated
@claude

claude Bot commented Sep 7, 2026

Copy link
Copy Markdown

Reviewed. This is a well-scoped, thoroughly self-documented change — I verified the substantive claims rather than taking them on faith:

  • Core logic (ServerHealthBands.FormatOutputFinding): branch order is denial → noted → category, matching the PR description. The noteCount > 0 ⟹ last_note non-null invariant it relies on is real — I traced the note_rank/note_count SQL in both LocalDataService.CollectionHealth.cs (DuckDB) and DarlingDataReader.cs (Postgres): note_rank's ORDER BY puts non-null SUCCESS error_message rows first, so a positive COUNT(error_message) always has a note_rank = 1 row to read. Identical on both stores.
  • Lite/Darling parity: confirmed independently rather than trusting the PR text. The two SKUs' get_collection_health MCP tool description strings hash byte-identical (md5sum match). The OutputFinding XML doc paragraphs in DarlingDataReader.cs and LocalDataService.CollectionHealth.cs are also identical. Both call sites (Darling, Lite) pass (RowsStored, TotalRuns, DeniedSinceLastSuccess, NoteCount) in the same order, and FormatOutputFinding has exactly these two callers, so no stale 3-arg call site was left behind.
  • DocComment regex rewrite (matching by shape instead of a hardcoded type-spelling list): correctly handles the new public string? OutputFinding => shape that the old list-based matcher couldn't, without over-matching across lines ([^\r\n]* + RegexOptions.Multiline keeps it single-line).
  • Security/perf: no new I/O, no dynamic SQL, no user input on this path — this is pure string-formatting/branch logic plus doc comments and MCP tool descriptions.

Left one non-blocking nit inline on a test fixture magic number. No correctness, parity, or security issues found otherwise.

… from

note_count is COUNT(error_message) over SUCCESS runs, so a fixture passing the total
run count asserted a row with more notes than successes - a state no query can return.
FormatOutputFinding does not validate the count, so the test passed while describing an
unreachable input. The factory now rejects such a count, because it is the one place
every fixture passes through.
Comment thread Lite.Tests/CollectionOutputBesideCostTests.cs Outdated
@claude

claude Bot commented Sep 7, 2026

Copy link
Copy Markdown

Reviewed the diff. No .sql files change here — this is purely FormatOutputFinding's new fourth term (note_count) plus the doc/description parity work around it, so the T-SQL style conventions don't apply.

Found one real bug (posted inline): Lite's new TheNotedZero_DefersToTheNote_AndTheUnnotedZeroKeepsTheCategoryReading test passes noteCount: 79_333 — that's the fixture's TotalRuns, not its SuccessCount (63_448). Since note_count is COUNT(error_message) over SUCCESS runs only, this describes a row the real query can never return. The Darling sibling test gets this right (noteCount: MeasuredSuccesses) and even added a runtime guard in its Row helper specifically to catch this mistake ("a fixture claiming more notes than successes describes a row no query can return"). Lite's Row helper has no equivalent guard, so this exact class of bug — which the PR explicitly says it's defending against — slipped into the Lite counterpart of the same test. Worth fixing before merge since it's the discriminating assertion in the very test meant to pin the new behavior.

Everything else looks solid:

  • FormatOutputFinding's branch order (denied → noted → category) matches the documented precedence and is exercised by the new test.
  • The noteCount > 0 branch correctly defers to last_note/note_count rather than re-rendering note text, keeping FormatCollectionNote the single source for that prose — good call given the drift risk called out in the PR description.
  • Both SKUs' tool descriptions and doc comments were updated identically, and the new BothSkusRowTypes_DocumentTheOutputMembers_Identically(OutputFinding) case plus the shape-based DocComment regex look correct for public string? X => members.
  • No other call sites of FormatOutputFinding were missed (confirmed by grep) — the compiler would've caught it anyway since the new parameter isn't optional.

Minor nit, not blocking: Darling/Darling.Tests/CollectionOutputBesideCostTests.cs's Row helper has two back-to-back comment blocks (a /* */ block and a // block) both explaining note_count's SUCCESS-run semantics — looks like leftover duplication from editing.

…ng's

The previous change fixed the Darling twin only, which left the two test files
disagreeing about whether a note count above the success count is representable -
the drift this file's parity discipline exists to prevent. Lite's fixture now
passes the success count and its Row helper rejects a larger one, as Darling's does.
Comment thread Darling/Darling.Tests/CollectionOutputBesideCostTests.cs Outdated
Comment thread Lite.Tests/CollectionOutputBesideCostTests.cs Outdated
@claude

claude Bot commented Sep 8, 2026

Copy link
Copy Markdown

Reviewed the diff. This is a well-tested, narrowly-scoped fix (note_count as the fourth term in FormatOutputFinding, both SKUs updated in lockstep with the shared cross-SKU parity pins asserting byte-identical tool descriptions and doc comments). No correctness, security, or SQL-related issues found — no T-SQL is touched, no external input handling changes, and the classifier logic (denial → noted → category, in that order) checks out against the stated invariant that note_count is bounded by SuccessCount.

Two minor nits left as inline comments, both in the test fixtures added by the fixup commits:

  1. Darling & Lite CollectionOutputBesideCostTests.cs: a // line-comment block was added directly under an existing /* ... */ block comment that says almost exactly the same thing about note_count/last_note consistency — looks like a fixup-commit leftover rather than intentional, and it's the only plain // comment in either file (everything else is /* */ or ///, consistent with the block-comment convention).
  2. Lite/Darling parity: Darling extracted the repeated 63,448 into a MeasuredSuccesses constant; Lite's twin still hardcodes 63_448 in three places. Functionally fine (values match), but a small drift in a file whose whole point is mirroring its sibling.

Neither blocks merge — both are cleanup nits in test-only code.

…Lite's counts

The added line comments restated the block comment directly above them and were the
only // comments in either file, which CONTRIBUTING asks against and neither file
otherwise contains. The new information is one clause, so it belongs in that block.

Lite's fixture counts are now named as Darling's are, since the two files are meant to
match line-for-line and the guard referring to a bare 63_448 was the asymmetry left by
adding the guard to Lite in its own style rather than the twin's.
Comment thread Darling/Darling.Tests/CollectionOutputBesideCostTests.cs Outdated
@claude

claude Bot commented Sep 8, 2026

Copy link
Copy Markdown

Reviewed the FormatOutputFinding change (new noteCount 4th term) and its Lite/Darling parity surface.

Correctness. Branch order is sound: early return on rowsStored > 0 || totalRuns <= 0, then a current denial takes priority, then noteCount > 0 defers to last_note, then the unnoted category reading. Traced the actual production SQL (note_count = COUNT(CASE WHEN status = 'SUCCESS' THEN error_message END), last_note ranked notes-first via note_rank) to confirm the doc comments' claims hold: note_count > 0 ⇒ last_note IS NOT NULL, and note_count <= total_runs (same GROUP BY/window). Left one inline note on a related test-fixture assumption that's slightly tighter than what the SQL actually guarantees — low severity, doesn't affect the production formatter.

Lite/Darling parity — checked byte-for-byte, all consistent:

  • get_collection_health tool description strings in Lite/Mcp/McpHealthTools.cs and Darling/.../DarlingMcpDataTools.cs (md5-identical)
  • The OutputFinding XML doc comments in LocalDataService.CollectionHealth.cs / DarlingDataReader.cs
  • The new TheNotedZero_DefersToTheNote_AndTheUnnotedZeroKeepsTheCategoryReading and TheFindingSignature_TakesTheNoteCount_AndNoNoteText tests, identical across Lite.Tests and Darling.Tests
  • The FormatOutputFinding(RowsStored, TotalRuns, DeniedSinceLastSuccess, NoteCount) call sites in both SKUs

The BothSkusRowTypes_DocumentTheOutputMembers_Identically regex re-anchor (matching by declaration shape instead of a literal type-spelling list) correctly finds both OutputFinding declarations (public string? OutputFinding =>), and I didn't find an earlier false-positive match in either file.

Other checks: no T-SQL touched by this PR, so the collector-query style rules don't apply here. No new input/network/file/process handling, so no security surface. No perf concern — this is pure string formatting with no added queries. No stray // comments introduced (codebase's block-comment convention preserved throughout).

Nothing blocking. Nice, precise fix.

…tually holds

note_count above success_count is producible, not unrepresentable: success_count also
excludes legacy abandoned-by-note rows, which carry a non-null error_message on a
SUCCESS status and are therefore counted by note_count. Those rows having aged out of
retention makes the tighter bound true of current data rather than structurally true,
so the guard as written rejected a shape production can emit. note_count <= total_runs
is what FormatOutputFinding rests on, both aggregates coming from one GROUP BY.
@claude

claude Bot commented Sep 8, 2026

Copy link
Copy Markdown

Reviewed the diff (ServerHealthBands.cs, both SKUs' MCP tool/reader files, both test suites) against CONTRIBUTING.md's C# and Lite/Darling parity conventions. No correctness, parity, or security issues found.

What I checked specifically:

  • FormatOutputFinding branching (denial → noted → category, in that order) is correct: the denial branch is unchanged and still takes precedence over the new noteCount branch, so a currently-denied+noted collector still gets the grant message rather than a confusing blend. The category branch's caveat text is correctly deduped into the shared ZeroOutputCaveat constant.
  • The note_count > 0 ⇒ last_note is non-null invariant the tests and doc comments lean on actually holds in both SQL sources: note_rank orders (error_message IS NULL, collection_time DESC, error_message DESC) on SUCCESS rows, so a positive COUNT(CASE WHEN status='SUCCESS' THEN error_message END) guarantees note_rank = 1 lands on a noted row. Verified this in DarlingDataReader.cs's SQL and confirmed LocalDataService.CollectionHealth.cs's DuckDB query uses the identical shape.
  • Lite/Darling parity: FormatOutputFinding call sites, the OutputFinding XML doc paragraphs, and the entire get_collection_health tool Description(...) string are byte-identical between DarlingMcpDataTools.cs/DarlingDataReader.cs and McpHealthTools.cs/LocalDataService.CollectionHealth.cs (checked via md5sum on the extracted description strings). The new BothSkusRowTypes_DocumentTheOutputMembers_Identically(OutputFinding) pin and its shape-based DocComment regex look correctly anchored — no false-positive matches against XML <see cref> mentions on non-declaration lines.
  • Test fixture guard: the final NoteCount = noteCount <= MeasuredRuns ? noteCount : throw ... bound is the right invariant (I confirmed success_count excludes AbandonedByNotePredicateSql rows that note_count still includes, so bounding by success_count instead of total_runs would have rejected a real producible shape — good that this got corrected before merge in the commit history).
  • No missed call sites: grepped for FormatOutputFinding repo-wide; both production call sites (Darling, Lite) and both test reflection pins were updated for the new 4-parameter signature. Full Dashboard doesn't use CollectorHealthClassifier at all, so no third copy to update there.
  • BOM churn: the transient UTF-8 BOM added to 7 files earlier in the branch history is fully removed in the final diff — verified all 7 touched files start with 2f 2a (/*) or 75 73 (using), no ef bb bf.
  • Security: no new user-input handling, no note text is duplicated into the new code path (only the count), which keeps the note's prose in its one existing home (FormatCollectionNote) as the PR intends.

Nothing to flag — this is a clean, well-tested fix.

@erikdarlingdata
erikdarlingdata merged commit e6c2503 into dev Sep 8, 2026
8 checks passed
@erikdarlingdata
erikdarlingdata deleted the fix/3160-zero-output-finding-note branch September 8, 2026 00:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant