Repository navigation
The watch-item worklist names its servers, off the whole retained history (#3482) - #3483
Conversation
…tory (#3482) The Fleet Sweeps worklist (and get_sweep_reports' watch_items) rendered raw server_ids while every other table on the page showed display names: the shared builder never emitted the server field sweeps.js has preferred since lane 3, so the client's "server N" fallback was the only rendering. BuildWatchItemsNode now takes a names map and emits ["server"] exactly when it means something - omitted on the fleet-scope sentinel (a name for the fleet would be a fabrication; the fleet_scope flag is what a client renders from) and omitted when the id resolves to no retained verdict row, where the bare-id fallback is the honest degrade. The names come from a new whole-history store read, deliberately unwindowed: the worklist is current-STATE rows, and a carried item can outlive its server's presence in the fleet, so its name may exist only in sweeps older than any span a caller could honestly pick - and the population is bounded by the same base horizon that prunes the worklist itself. The statement carries the span sibling's #3476 dedup-and-determinism contract (newest sighting ordered last, last-write-wins reader), and the read is log-and-degrade: a failed lookup costs the names, never the worklist. Both surfaces share one gate-and-read helper (the ValidateWatchState sharing pattern), skipped when the worklist names nothing - the common case on a healthy fleet under the page's 60-second refresh.
ReviewWent through the full diff ( Correctness — looks right. Both call sites of Security — no issues. The new SQL ( Lite/Darling parity — no drift. Fleet Sweeps ( Style — matches conventions: Minor observation, not a blocker: Test coverage is thorough (shape pins for named/omitted-degrade/fleet-scope-omission, a live-Postgres test proving departed-server naming and rename-determinism, and an MCP shared-builder pin) — nothing further to add there. 🤖 Generated with Claude Code |
|
On the unwindowed-read observation: agreed with your own arithmetic — the GROUP BY runs over the same rows the retention horizon already bounds, so at the shipped hourly cadence and 30-day horizon it stays small even on a fleet that always has something on watch. The watch condition is real though, and it has a natural tripwire already shipped: the store's own query activity is monitored by the product, so a 15-minute-floor fleet where this read grows teeth shows up in its own top-queries surface before an operator feels it. If that day comes, the fix shape is a windowed read with a departed-server fallback arm — recorded here so the next editor inherits the design instead of re-deriving it. |
Closes #3482 — ceremonially; the watcher closes it at merge.
The bug
The watch-item worklist — the one table an operator reads to see what is still standing — rendered
server 412767232where every sibling table on the page saysSQL2025.BuildWatchItemsNodeemittedserver_idand no name; the detail node's verdicts and ledger both join names, so the worklist was the odd one out on both surfaces (/api/sweeps/watch-itemsandget_sweep_reports'watch_items).One correction to the issue's otherwise-exact diagnosis, so the reviewer isn't misled by the line cite:
sweeps.js:227is the would-have-paged ledger, whose feed already sendsserver. The worklist table (~line 367) is the one that never read a name — it branched fleet-scope or raw id, with no name preference to fall back FROM. Both ends needed the fix: the feed now sends the field, and the cell now prefers it.The name source: the whole retained history, not a span and not the latest sweep
Watch items are current-STATE rows, and a carried item can outlive its server's presence in the fleet — departure is a miss, misses are evaluations, and the hysteresis bar is the point. So the latest sweep's verdicts (the detail node's precedent) provably cannot name a departed server, and any span a caller picked would be a guess. The new
GetSweepServerNamesAsyncreads the whole retained verdict history, which is bounded by the same horizon that prunes the worklist itself: a name this read cannot find is a name the store no longer holds anywhere, and that item degrades to the bare id honestly.The read carries #3476's determinism contract verbatim —
GROUP BYwith each pair's newest sighting ordered LAST,sweep_idtiebreak, the paired last-write-wins reader comment — so a renamed server shows its newest name here for the same reason it does in the rollup. Fault posture is presentation-grade: log-and-degrade to an empty map, so a failed lookup costs the names (the client falls back to exactly the pre-fix rendering) and never the worklist. Both surfaces share one gate-and-read helper, and the read is skipped outright when the worklist is empty or all fleet-scope — the common case on a healthy fleet under the page's poll, which should not pay a history scan for nothing.The fleet-scope sentinel, guarded rather than assumed
The sentinel (
server_id0) has no server name, and a name for "the fleet" is a fabrication however it arrives — so the builder refuses to emit one even if a hand-fed names map carries id 0, rather than trusting that no caller ever will. Unresolvable ids omit the field entirely (the degrade shape), and the client renders the sentinel as the fleet label it already had.Tests
The watch-item shape pin grows three arms (named, omitted-degrade, fleet-scope-omission-despite-map); a JS source pin holds the render preference and that "server 0" is unreachable for the sentinel; the MCP shared-builder pin extends to the names read so the two surfaces cannot drift; the rung census gains the new SQL const (no-
now()discipline) and its determinism shape; and the live-Postgres suite proves the departed-server coverage and the two-names-one-id newest-wins arm against a real store. Tests cannot run on macOS; verified by full-solution build and static reasoning — CI is the first execution.