Repository navigation
Lite: stop the Job History header calling every Agent-less server "Stopped" - #1723
erikdarlingdata wants to merge 3 commits into
Conversation
…opped" The presentation-side twin of #1719, using the same two gates. The header drew a red "Agent: Stopped" for any row whose agent_running was false. That is a claim the collected data does not support in two ordinary cases: a server where Agent has never run (a container built without it, Express, Azure SQL DB) is not stopped, it simply does not use Agent; and a reading hours or days old says nothing about the service now. Both rendered identically to a genuine outage, so the one state an operator must not learn to ignore was the one they saw constantly. It now judges only on a FRESH reading, and only where Agent has been OBSERVED RUNNING at least once in the retained agent_status history - the evidence rule #1719 established for the Darling alert, reused so the header and the alert cannot disagree about the same row. Never-observed reads a neutral "Agent: never observed running" (deliberately not "not installed", which the data also cannot support); a stale reading reads a neutral "Agent: unknown (reading 3h old)". Fresh + seen-running + stopped is still red, and a running Agent is its own proof of capability so it short-circuits the history probe, exactly as the evaluator does. The roll-up gets the same treatment: never-used and stale servers are reported separately rather than folded into the stopped count, which is what made the fleet line overstate the number of problems. The backing read had no freshness filter at all, so collection_time now rides along with a derived ever_running flag - one window aggregate over the partition the query already scans, not a second round trip. Stale window is 30 minutes, matching Darling's StaleWindow and through it the Dashboard's threshold. Explicitly NOT ServerHealthThresholds.StaleThreshold: that is two minutes derived from the fastest collector's one-minute cadence, and agent_status collects every five, so reusing it would render a healthy Agent as stale most of the time. The decision is a pure function, so it is pinned without standing up WPF: never-seen neutral, stale unknown, seen+fresh+stopped still red, the roll-up counts, plus real-DuckDB round-trips proving ever_running reads the whole retained partition rather than the newest row's own value and does not leak across servers. Lite.Tests 1515 passed / 0 failed, 0 build warnings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The PR came out as 1723, not the 1721 predicted at write time. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…der-honesty # Conflicts: # CHANGELOG.md
|
Closing as superseded by #1725, which shipped the same two fixes to I checked their implementation against mine rather than assuming, and theirs is complete and equivalent:
Merging this would have replaced working shipped code with a parallel implementation of the same thing, which is strictly worse than closing it. Auto-merge disarmed before closing — it was armed, and had the CHANGELOG conflict resolved in the other direction it could have reverted #1725. One genuinely non-redundant piece is being carried forward separately: every test in #1725 runs against constructed model rows, so nothing proves the SQL. The No production code in that follow-up, so it cannot conflict with #1725. |
Lite-only. The presentation-side twin of #1719, using the same two gates.
The problem
The Job History header drew a red
Agent: Stoppedfor any row whoseagent_runningwas false. That is a claim the collected data does not support in two ordinary cases:Both rendered identically to a genuine outage. The result is the familiar one: the single state an operator must not learn to ignore was the state they saw constantly.
The fix
Judge only on a fresh reading, and only where Agent has been observed running at least once in the retained
agent_statushistory — the evidence rule #1719 established for the Darling alert, reused rather than reinvented so the header and the alert cannot disagree about the same row.Agent: Running · Next run: …Agent: Stopped · Next run: …Agent: never observed runningAgent: unknown (reading 3h old)The real alarm is untouched — fresh + seen-running + stopped is still red. A currently-running Agent is its own proof of capability, so it short-circuits the history probe exactly as the evaluator does.
The all-servers roll-up gets the same treatment: never-used and stale servers are reported separately (
2/3 running, 1 stopped, 4 not using Agent) instead of being folded into the stopped count, which is what made the fleet line overstate the number of problems.Notes on two judgment calls
Wording.
never observed running, deliberately not "not installed" / "not present". We can see that Agent has not been observed running; we cannot see whether it is installed. There is a test pinning that the header does not make the stronger claim.Stale window is 30 minutes — matching Darling's
StaleWindowand through it the Dashboard's threshold. Explicitly not the sharedServerHealthThresholds.StaleThreshold: that is two minutes, derived from the fastest collector's one-minute cadence, andagent_statuscollects every five (CollectorScheduleDefaults). Reusing it would have rendered a perfectly healthy Agent as stale most of the time — a pin asserts the window stays longer than the collector cadence.The gate lives in Lite rather than
PerformanceMonitor.Commonto keep this Lite-only and off files other branches are editing. If Darling ever wants the same tri-state header,AgentHeaderStatusis the thing to lift.Implementation
Lite/Services/AgentHeaderStatus.cs(new) — the decision as a pure function, so it is testable without standing up WPF. The code-behind now only maps state to brush.LocalDataService.JobHistory.cs—collection_timeand a derivedever_runningadded to the projection.ever_runningis a window aggregate over the partition the query already scans, not a second round trip;MAXover a 0/1CASErather thanBOOL_ORso it does not depend on which aggregates DuckDB exposes as window functions.JobHistoryTab.xaml.cs— renders whatever the classifier decides.One thing this surfaced: my first draft had a clock-skew branch for a negative age, which is unreachable — a future timestamp is nearer than the stale window, so it classifies Fresh and never reaches the age renderer. Removed rather than left as untestable code, and the test now pins the actual behaviour: a skewed clock counts as fresh, so a genuinely stopped Agent still alerts. Refusing to judge there would silently suppress a true alarm on every server whose clock runs fast, which is the expensive direction to be wrong in.
Test plan
Lite.Testsfull suite: 1515 passed, 0 failed.ever_runningtrue when an earlier row ran though the newest is stopped, false when no retained row ever ran, does not leak across servers, andcollection_timeis projected and drives staleness.Rides the nightly if it lands in time but does not gate it — cosmetic honesty.
🤖 Generated with Claude Code