Repository navigation
A server dark past collection_log's retention reads Offline everywhere, not 'Awaiting first collection' (#3967) - #3975
Merged
Conversation
…e, not "Awaiting first collection" (#3967) #3966 fixed the fleet card's 48-hour read. The surfaces that read a server's newest collection with no window still called a null "never collected": list_servers, the WPF sidebar dot and Overview card, and Lite's Overview card. Retention bounds what they can see (collection_log 60 days, Lite's archive 3 months), so a server dark for longer turned amber. The rule now lives once in PerformanceMonitor.Common: ServerHealthClassifier.ClassifyFreshness(lastCollection, registeredAt, searchedFrom, now). A null on a server registered before what the read could see is Offline; otherwise the ladder decides. The fleet card delegates with its 48 h window start. The unbounded Darling surfaces pass DarlingRetentionHorizons.CollectionLogHorizon(now). Lite passes RetentionService.OldestRetainedInstant(now), the first month start the archive still holds whole. Registration comes from servers.created_date: on ServerListSql, and on both ServersSql and ManagedServersSql via DarlingServer.RegisteredAt, which the Overview loader stamps onto the card. In Lite it comes from ServerConnection.CreatedDate, a required parameter on GetServerSummaryAsync. The "Never" / "No data collected" texts now say "None retained" for that case. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018Z4T51J4E6L8fjriPHpsif
This was referenced Sep 23, 2026
erikdarlingdata
added a commit
that referenced
this pull request
Sep 23, 2026
…h the archive cleanup keeps, not at the cutoff day (#3984) The cleanup deletes an archive file whole once the month it is named for falls before the cutoff, so the days between the cutoff and the next month start are gone with it. DailySummaryRetentionHorizon returned the cutoff day, which judged those days as retained with no data instead of purged, and published a retention_horizon up to a month earlier than Lite's history. It now returns RetentionService.OldestRetainedInstant, the instant #3975 gave the Overview freshness band. Claude-Session: https://claude.ai/code/session_01GdmA4ND1wLSqA91ax1m4xv Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
erikdarlingdata
added a commit
that referenced
this pull request
Sep 23, 2026
…3920, #3927, #3931, #3932, #3940, #3942, #3946, #3947, #3950, #3952, #3955, #3956, #3957, #3964, #3965, #3966, #3968, #3972, #3975, #3979, #3980, #3981, #3983, #3984, #3985) (#3989) The wave's fix PRs deliberately carried no CHANGELOG edits (parallel-agent hot-spot protocol); each agent reported its entry and this commit lands them together, byte-verified against origin/dev. Claude-Session: https://claude.ai/code/session_01GdmA4ND1wLSqA91ax1m4xv Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #3967.
Why
#3966 (#3935) taught the fleet card that "nothing found" is not "never collected". Every other surface that reads a server's newest collection still made the same mistake, at a longer horizon.
list_servers, the WPF sidebar dot and the WPF Overview card read that collection with no window. Butcollection_logkeeps onlyCollectionLogRetentionDays(60) days. A server dark for longer than that has no row left, andClassifyFreshness(null)calls it never collected. So a decommissioned server left enabled read Offline for two months, then turned amber: "Awaiting first collection — registered, but the service has not reached it yet".Lite has the same case. Its Overview card reads hot, then the archive view, and the archive files are deleted after
ArchiveRetentionMonths(3). A server dark past that read "Never", with an amber "No collection has ever landed for this server".What changes
One rule, in
PerformanceMonitor.Common. New overload:searchedFromUtc(the earliest instant the read could see) is Offline.The callers are the surfaces that previously called the ladder with a null last collection:
ClassifyWindowedFreshness, now a delegate)servers.created_dateon the probe rowlist_servers(ServerListSql+created_date,FreshnessStatus)servers.created_dateDarlingRetentionHorizons.CollectionLogHorizon(now)= now − 60 dDarlingServer.ApplyFreshness)DarlingServer.RegisteredAt, fromcreated_dateon BOTHServersSqlandManagedServersSqlServerSummaryItem.ApplyFreshness)IsPostgresGetServerSummaryAsync→ApplyCollectionFreshness)ServerConnection.CreatedDate→RegisteredAtUtc(not persisted)RetentionService.OldestRetainedInstant(now)Lite's horizon is a month start, not the cleanup's cutoff. Archive files are named for the month (or day) they were written, and
CleanupOldArchivesdeletes a file once that date is before the cutoff, so a month's file goes whole. A row lands in a file of its own month or later, so it is certain to survive only from the first month start at or after the cutoff. A test runs the real cleanup against that month and the month before it, so the two cannot drift.Lite needs the registration passed in, so it is now required.
GetServerSummaryAsync(id, name, registeredAtUtc)takes it as a required parameter, and all three callers pass one:list_servers;get_server_summary, through a newServerResolver.RegisteredAtUtc.So the band never depends on which caller asked.
Text that assumed the old meaning of an empty read:
Never→None retainedwhen the card is Offline with nothing to show.Never→None retained (stopped).Never→None retained. The dialog now reads the registry once for the registration instants.no data collected yet→no collection retained.list_serversline:No data collected→none retained.No token or payload shape changes. The
list_serversstatus token for such a server moves fromAwaitingFirstCollectiontoOffline, and nothing else changes. No new fields, no description change. The MCP payload'slast_collectionstays null for the case.Not changed: no migration, and no new reads anywhere except the Manage Servers dialog's one registry read.
DarlingServer's new constructor parameter is defaulted, so the test fakes compile unchanged.Where two surfaces can still differ, deliberately. A server that connected more than 48 hours ago but inside the retention, and has never written a single
collection_logrow, reads Offline on the fleet card and "Awaiting first collection" on the unbounded surfaces. The fleet read cannot see far enough back to prove it never collected; the others can. It is rare, because every collector run writes a row whatever its outcome. Every server that ever collected now reads the same on every surface: the live test asserts one answer per server acrosslist_servers, the dot, the card and the fleet card.Test plan
ServerListSqlbefore and after, warm: 0.79 ms / 0.81 ms execution, 29 buffers each, same plan.created_daterides the registry row it already reads.MAXplanning every chunk, a cost this PR does not touch.ManagedServersSqlafter: 0.48 ms.DarkPastRetentionReadsOfflineTests(Darling, 11 pure):list_serversthroughRenderServerList;created_dateon all three registry reads;DarkPastRetentionReadsOfflineLivePostgresTests(rig 55501, TimescaleDB). Four registered servers: history aged out, registered 10 min ago, dark 5 days inside retention, and collecting. They are read throughlist_servers, both viewer registry reads (config seeded, soManagedServersSqlreally runs), the sidebar freshness read, the Overview summary read, andGetFleetOverviewAsync. Each surface gets the expected word, and all four surfaces agree for every server.DarkPastRetentionReadsOfflineToolTests(Lite, 7):CleanupOldArchives;ServerResolver.RegisteredAtUtc;list_serverstext for both cases.EveryCardIsStampedWithItsBandWhereItIsBuiltsource pin, which now pins the stamp WITH the registration.list_serversdropping it: 2;ManagedServersSql's reader dropping the column: 1 (live);Darling.TestswithDARLING_TEST_PGon the rig: 12,712 total, 1 failed, 24 skipped. The failure isCollectionHealthAggregateTests.InstallHole_FirstPolicyRunAfterEnsure_StrandsNoHistory_AgainstDevPostgres:55P03 could not refresh continuous aggregate "collection_health_hourly" due to a concurrent refresh, a race with the rig's background refresh job on a class this PR does not touch. Re-run alone, the class passes 13/13. Every skip is environment-gated: managed runtime or upgrade fixtures, a live SQL Server, symlink privilege, or CI sizing.Lite.Tests: 5,139 total, 0 failed, 0 skipped.0 Warning(s).🤖 Generated with Claude Code
https://claude.ai/code/session_018Z4T51J4E6L8fjriPHpsif