Repository navigation
Deliver the sweep's memory: the daily rollup ceiling and get_sweep_reports (#3466, lane 4 of 4) - #3473
Conversation
…ports, so a mute's cost reaches the channel once and the record stays whole (#3466) Lane 4 of 4, stacked on the V123 state store, the V124 engine and the web feed - the delivery lane, and the one that closes the issue. Two halves, both over the same rows the engine writes. THE DAILY ROLLUP is the fleet sweep's one channel post, on the collector-cost digest's machinery reused whole: the same daily-interval dedup (the interval is BOTH the ceiling the owner ruled and the covered span, one number so what a post claims to cover and how often one can arrive cannot drift apart), the same empty-sends-nothing gate, the same declared INFO arm (#3443's precedent, pinned in Lite.Tests so the next fall-through sweep cannot promote it), the same shared mute seam, the same FireAsync funnel. Attempted on the worker's hourly store-metrics tick beside the cost evaluation; 23 of every 24 ticks cost one dictionary lookup. It summarizes the trailing day: band transitions rendered chronologically from the persisted documents (the engine's report is the authority on what each sweep SAID - re-deriving from verdict rows would be a second opinion), watch items opened and closed with subjects resolved from the span's own verdict names (the fleet sentinel named as the fleet, a server no verdict named stated as its bare id, never invented), instrument-liveness incidents in the quiet-is-not-clean vocabulary, unreadable documents COUNTED and themselves reportable - and, whenever any covered sweep ran under the alert master switch, the would-have-paged ledger those sweeps carried, counted and named per family and server: what the silence cost, rendered explicitly. A day with nothing to say posts NOTHING, and the choice is argued rather than assumed: the digest precedent is exact, the ruling names the failure mode (report-class content in a paging channel trains operators to ignore the channel), and an honest all-clear would have to carry its instrument-liveness proof - which is a report card, the exact thing the ruling bounds. The affirmative all-clear lives where it can afford its evidence: the web feed, every sweep, at full cadence. Nothing consumes the interval but a delivered post - not a quiet day, not an empty one, not the master gate, not a failed read (which lands on the #3013 swallowed-read counter and skips the tick) - so the first day with something to say is never delayed by the silence evaluated before it. Delivery is master-gated, and here the gate IS the feature's delivery contract: the engine deliberately keeps sweeping under master-off (the muted-mode contract - the report surface is never blinded), so this apply is the one place the sweep feature touches a channel, and master-off means it touches nothing. The catch-up semantics are deliberately narrow and stated so nobody widens them by accident: a rollup only ever covers the trailing day; the first post after re-enable states how many of ITS covered sweeps ran muted, so the operator is told there is history to read; older muted days never get a channel post of their own, because a backlog recap is either unbounded or silently truncated and the re-enable flood is what the operator's mute existed to prevent - the sweep documents stand in the web feed and get_sweep_reports as the permanent record. The apply joins AlertMasterSwitchSurfaceTests' per-family census as an inline-gated firing member (sixteen now; the delivery-call table itself does not move, because FireAsync's one deliver site is unchanged). GET_SWEEP_REPORTS completes the spec's non-goals split - the product publishes evidence, an operator's agent adds judgment - and serves the SAME FleetSweepPresentation builders and store reads the web feed serves, so an agent and a browser cannot disagree about what a sweep looks like. One call returns the timeline for the window (ValidateWindow, the shared authority, refusing rather than clamping), the newest sweep in full with its ledger, and the watch worklist (the web feed's own state validator, so the two surfaces refuse in one vocabulary); sweep_id fetches one sweep, and it is a STRING by #2548's rule - tick-scale ids do not survive a JSON number round trip, and the tool refuses an id it cannot parse exactly rather than querying a rounded neighbor. The description carries the two field contracts an agent must read, pinned as such: alerts_enabled: false means DELIVERY WAS OFF while the sweep ran, with the present-and-empty-versus-absent ledger distinction stated; and instruments_alive: false means the sweep could not prove its own data sources - quiet is not clean, an empty window on such a sweep is unreadable, not healthy. Every census moves: the instructions' tool sentence (151 tools, 65 unique to Darling) with the cross-server table gaining the sweep row and the field-contract paragraph, the CrossAppMcpToolInventoryPinTests ratchet (Darling-only by architecture - Lite has no fleet to sweep and no sweep-state tables), the root README's two Darling figures, llms.txt's range, and the /api/read dispatch (the tool mirror beside the dedicated /api/sweeps routes, the /api/fleet coexistence). ExcludedToolNames is untouched: read tools are mirrored. The store gains the rollup's three reads - throwing, deliberately opposite the presentation posture, because the rollup posts nothing on an empty day and a fault folded into "empty" would convert an unreadable store into a permanently quiet channel - and the two new span statements are rung-pinned like their siblings. Tests: FleetSweepRollupTests (the digest suite's disciplines - every silence pin has a firing twin, and the shipped render is re-parsed with its figures held to each other: header counts against lines, the ledger banner against its family lines, the mute sentence against the sweep count it claims, chronology against a newest-first fixture), DarlingMcpFleetSweepToolsTests (surface, params, the string sweep_id pin, the shared as_of description verbatim, the refusal table on a dead store, the description and instructions contracts, Gemini-clean schema, the dispatch membership), the rung extensions, the Lite INFO pin and the ratchet entry. Verified out-of-band on macOS: the full solution builds clean, and a 70-check harness ran the pure half plus a live TimescaleDB pg17 pass - real planted sweeps, the three new store reads on the wire, EvaluateFleetSweepRollupAsync delivering one rollup off the real store carrying the planted mute's ledger and refusing to repeat inside its day, master-off delivering nothing, and get_sweep_reports plus its /api/read binding served off the same rows.
on the host, describe it to the catalog, and census both from source (#3466) Review round 1 found the lane's headline tool dark. get_sweep_reports existed, was web-dispatched, was counted in the instructions census (151), and was never registered with the MCP host - the registration chain carried 54 .WithGeminiCompatibleTools<T>() lines for 55 tool classes, so a live tools/list answered 150 where the instructions claimed 151, and the one tool the whole feature was designed around was unreachable over the surface it was designed for. That is #2659's exact shape, nearly repeated: every existing name census pins tool NAMES, and the attributes exist whether or not the class is registered. The registration rides beside get_fleet_overview and get_ag_health as the third cross-server read - the one with memory - over the same shared builders the web feed serves. The pin that owns this defect existed and did not save us, which is worth stating plainly: McpToolTypeRegistrationTests (#2659) already derives the registered set from the host SOURCE and the declared set from the assembly, and it would have gone red on the CI that runs it - but the lane's out-of-band harness (tests cannot run on macOS) did not carry the check, so the gap reached review instead of a red build. Two hardenings follow. The declared set now derives BOTH ways the SDK vocabulary can express a tool - the type attribute, unioned with classes carrying [McpServerTool] METHODS, because the registration path scans methods and a class that lost its type attribute in a refactor would be invisible to an attribute-only census. And the assert became SET-EQUALITY: a registration naming a class that declares no tools registers NOTHING silently - a stale line that reads as coverage while delivering none - so it reds in the opposite direction. The lane harness now carries the same check, proven red both ways against the pre-fix source (2 failures naming DarlingMcpFleetSweepTools) before the registration landed. The second critical was the same defect one surface over: get_sweep_reports had a BuildReadDispatch entry and no CatalogDescriptors entry, and BuildCatalogNode indexes CatalogDescriptors[name] for every dispatch key - so /api/catalog answered 500 at runtime, and the two parity tests that pin exactly this (CatalogDescriptors_Keys_EqualTheReadDispatchKeys and the as_of dispatch census) were red. The descriptor's params are the dispatch binder's ACTUAL wire keys - hours (the shared Hours binder, hours_back accepted as the tool-spelling alias), as_of, sweep_id, watch_state - because the catalog is the web surface's only input truth and a key it does not advertise is simply unreachable there. Two honesty repairs in the rollup's extraction, both under the one unreadable counter the render states. ExtractRollupFacts read each band census with GetInt32 inside a JsonException-only catch, so a band count a JSON number carries but an int cannot hold escaped as FormatException/OverflowException and killed the whole rollup tick for up to a day - a silent outage of the exact feature that promises an unreadable day cannot read as a quiet one. TryGetInt32 now treats the corrupt census as unreadable and the fleet-as-of line keeps the newest sweep whose census WAS readable - and says "newest readable covered sweep", so the sentence matches the code's actual selection rule. And AppendWatchEvents dropped a watch entry missing its item or server id silently; it now returns the malformed count onto the same counter, because a watch event the rollup cannot render is still a watch event the operator was owed. UnreadableDocuments became UnreadableItems, and the render sentence says what it counts: whole documents or single entries that did not parse, one counter, stated. New pins: the census overflow arm (the tick survives, the counter states it, the fleet-as-of line falls back readable), the malformed watch entry arm, and the set-equality census both ways. The out-of-band harness grew from 70 to 80 checks - the registration census, the catalog parity twin, BuildCatalogNode served live (the former 500), and both unreadable arms - all green against the live TimescaleDB store.
…ore batches to confirm Review on #3471 caught the drain-loop contract inverting for the two fleet-sweep child purges: neither is time-sliced or row-capped, so under the default batchSize of 1 any day with real work re-ran the full DELETE once more just to observe zero rows. They now dispatch at SingleShotStatement (a cap no real row count reaches, making the one execution terminal), the sliced siblings in the same loop keep their correct batchSize of 1, the sentinel stays out of the row-capped drain report's cap figure, and a source pin holds the call-site shape the compiler cannot.
… new sites they caught The read-failure surface census caught the rollup's three span reads sharing one stopwatch - a fault in the server-names read would have recorded all three reads' elapsed as its own. Restart between awaits; the census counts advance to the eighth evaluator site and 31 tree-wide, with the doc narrating why a swallowed rollup read is counted (a fault folded into 'empty day' converts an unreadable store into a permanently quiet channel). The retention drain-report pin learns the single-shot exclusion the #3471 review response added.
ReviewWent through the full diff (no T-SQL touched — this lane is entirely C#/Darling, so the T-SQL style checklist doesn't apply) against the CONTRIBUTING.md conventions, with a focus on correctness, Lite/Darling parity, security, and performance. Correctness — verified, no bugs found:
Parity: this is correctly scoped as Darling-only — no shared-library code ( Security: all new reads are parameterized; no new file/process/network surface; One very minor, non-blocking observation: No blocking issues found. |
|
On the one observation: agreed it costs nothing, and "why did the ledger section disappear" is a real future question — but it's not worth a CI restart this close to the nightly window. Riding the already-queued review-response PR (with #3448's severity carve-out and window-text reword): the MCP tool's child reads get the host's logger, and the deliberate-choice comment gets retired with it. |
…oes not parse is evidence, not a swallow The swallowed-read census walked past the corrected counts and found the rollup's JsonException arm - unreadable++ with no message - which is neither a counted read nor an exemption it could match. It is a parse arm: the store read above it is the counted site, and the arm converts the fault into the unreadable count the rollup itself reports, so counting it as a read failure would double-book the one read. The catch now carries that reason as the comment the exemption keys on, and the evaluator's scope advances to its tenth exemption.
|
Reviewed the production diff (DarlingRetention.cs, DarlingSelfAlertEvaluator.cs, DarlingWebEndpoints.cs, DarlingWorker.cs, the new DarlingMcpFleetSweepTools.cs, DarlingMcpHostService.cs/Instructions.cs, FleetSweepStore.cs, AlertMetricClassifier.cs, AlertSeverity.cs) plus the associated tests. No blocking issues found. Correctness
Lite/Darling parity
Process note (not a defect): Style: T-SQL conventions don't apply here (this lane touches only the Darling PostgreSQL/C# side); the PostgreSQL-specific rules (schema-qualified objects, naive UTC timestamps via 🤖 Generated with Claude Code |
… re-counted by a person as the pin demands
|
Reviewed the full diff (fc0b399..HEAD): the daily rollup in Correctness — no bugs found. Parameterized queries throughout (no SQL injection surface). Master-gate / mute discipline — Parity — this is a Darling-only feature (fleet-wide sweep state), and that's correctly reflected: Registration bug caught mid-PR — worth noting the second commit fixes a real defect the first commit shipped ( Security — no new file/network/process usage; No blocking findings from this pass. |
The Lite-side census caught what the Darling-side one could not say: a fleet-scoped read the counter records that the FleetScopedReads constant does not name - the operator-prose inventory concatenated into both SKUs' get_collection_health descriptions, where an unnamed member is a nonzero instance total with a documented list that does not name the cause. The constant now carries the fleet-sweep rollup read, the derivation advances to seven with the usual person-counted literal, and the Contains pins hold the name in both directions.
| public const string GetSweepServerNamesBySpanSql = @" | ||
| SELECT DISTINCT v.server_id, v.server_name | ||
| FROM collect.fleet_sweep_server_verdicts v | ||
| JOIN collect.fleet_sweep_runs r ON r.sweep_id = v.sweep_id | ||
| WHERE r.swept_at >= $1 | ||
| AND r.swept_at <= $2;"; |
There was a problem hiding this comment.
Minor determinism gap: every other span read in this file (GetWouldHavePagedBySpanSql, GetRunsBySpanSql, etc.) carries an explicit ORDER BY for exactly this reason, but this one doesn't. If a monitored server is renamed during the covered 24h span, fleet_sweep_server_verdicts can carry two different server_name values for the same server_id within the window. GetSweepServerNamesBySpanAsync builds its map with names[reader.GetInt32(0)] = reader.GetString(1);, so which name wins depends on whatever row order Postgres happens to return for the DISTINCT — not guaranteed stable across calls. The impact is cosmetic (a watch-item/ledger line in the rendered rollup could show the old or new name), but it's a real nondeterminism that the rest of this file is careful to avoid. Consider ORDER BY v.server_id, r.swept_at DESC (or similar) so the newest name for a given server consistently wins.
There was a problem hiding this comment.
Real, and the file's own discipline argues your side — every sibling span read orders for exactly this reason. Cosmetic impact (which spelling of a renamed server the rollup shows), so it rides the queued review-response PR rather than a hotfix: ORDER BY so the newest name wins deterministically, alongside the #3448 pair and the MCP-tool logger from this PR's first review.
Review summaryThis PR delivers the fourth (delivery) lane of the Fleet Sweep Reports feature: the daily channel rollup on the collector-cost digest's machinery, and the Scope check: No T-SQL install scripts are touched, so the T-SQL style rules don't apply here. The new PostgreSQL statements in Lite/Darling parity: This is correctly scoped as Darling-only by architecture (Lite has no fleet to sweep and no sweep-state tables), and that's handled the way One finding posted inline: No correctness, security, or Lite/Darling parity issues found beyond that. |
…ed degrade, and the deterministic name (#3476) * Carve the deliberate INFO reports out of IsWarning, so the history grids agree with the severity map (#3448 review) * Name the census window the digest actually reads: the trailing 24 hours, not the movers' baseline (#3448 review) * Hand get_sweep_reports' child reads the host's logger, so a degraded section leaves a trace (#3473 review) * Order the rollup's names read so a renamed server's newest name wins deterministically (#3473 review)
…to-merge outruns (#3492) * A substantive review finding becomes a failed check, not a comment auto-merge outruns The #3470-#3473 train demonstrated the hole: claude[bot] left six review findings across one night's PRs (one real) and auto-merge outran every one of them, because review output was comments and the merge gate only ever read CI. The guard verified that the review POSTED, but a posted finding and a read finding are different observations, and nothing made a substantive finding cost anything. Now the review prompt carries a VERDICT PROTOCOL: every run ends by submitting exactly one formal GitHub review -- `gh pr review --request-changes` with a numbered summary when any finding is substantive (correctness, security, data integrity, contract violation), or a `--comment` LGTM review otherwise. Style nits, observations the review itself labels non-blocking, and tradeoffs the code or PR already documents as deliberate are explicitly NOT changes-requested, so the red mark keeps meaning something. Bash(gh pr review:*) joins allowedTools; permissions were already pull-requests: write, and nothing else widens. The guard grows the matching VERDICT ARM after its posted-something tally: fetch the PR's reviews, filter to the review bot, take the newest by submitted_at. CHANGES_REQUESTED fails the check, naming the review and the way out; anything else is clean. The semantics are NEWEST-VERDICT-WINS: a re-review after new commits reviews the new diff and submits a fresh verdict, and a clean fresh verdict clears an earlier changes-requested without --approve -- which the review token cannot issue without an org toggle this repo does not enable, and which nothing here needs. A human dismissing the review reads as cleared too (DISMISSED). Staleness needs no SHA bookkeeping: the concurrency groups already cancel both workflows on a new push, and the replacement guard waits for the replacement review run to complete, so the newest verdict at scan time is the current head's. The coupling moves as one commit because the guard's sentinel contract says it must: ALWAYS POST now appears in the prompt, which flips the guard's strict arms on by itself. Silence with the sentinel shipped stays a lost output (exit 1), and comments without the promised verdict review are the same broken-promise class -- and exactly the pre-verdict failure shape, findings living only in comments. Absent the sentinel (an older prompt, or a future one that drops the protocol) the verdict arm stands down, because runs that never promise fresh verdicts could never clear a stale changes-requested and the gate would wedge shut. The #2309 discipline is untouched: the new lookup routes through lookup(), so an API failure marks the review UNCONFIRMED and exits 0 -- a lookup failure is never a verdict. Verified with fixture JSON against the extracted step: newest CHANGES_REQUESTED fails naming the URL; an older changes-requested superseded by a newer COMMENTED clears; a verdict-scan API error is UNCONFIRMED exit 0; comments-without-verdict fails; no sentinel stands down; DISMISSED clears; human reviews are filtered out; pending (null submitted_at) reviews are skipped; both posted-nothing arms behave as before the obligation-read hoist. This PR edits claude-review.yml, so claude-code-action will refuse to review it (guard arm 1 warns and passes, as designed) -- a human must review it, and review stays refused repo-wide until the next release ships dev to main, which the guard header already documents as the cost of touching this file. * Name the guard's check what the enforcement requires: the job id was the check-run name, and the required context was a phantom Branch protection matches required status checks on the check-run name, which is the job's DISPLAY name - and the guard job had none, so it reported as its bare id 'verify' while the required context said 'Claude review guard'. Nothing could ever satisfy it: this PR's own first CI cycle sat all-green and BLOCKED on the phantom. The display name is now the enforcement contract, and the comment beside it says the two move together. --------- Co-authored-by: Erik Darling <erikdarlingdata@gmail.com>
Deliver the sweep's memory: the daily rollup ceiling and
get_sweep_reports(#3466, lane 4 of 4)Closes #3466.
This is the last of the four strictly-sequenced lanes the sizing ruling ordered, and the one that
closes the issue: delivery — the daily channel rollup the cadence ruling bounds, plus the
get_sweep_reportsMCP surface the non-goals section designed the whole feature around. With it, theFleet Sweep Reports feature is complete at the CORE + DELIVERY tier the owner scoped.
What the four lanes built, together
data. Four
collecttables — the immutable run documents with the NOT-NULL instrument-liveness pairand the mute header, the normalized per-server verdicts every later lane joins relationally, the
would-have-paged ledger rows, and the watch-item worklist — written in one throwing transaction, so a
sweep is one indivisible statement about the fleet or it is nothing.
(
fleet_sweepgroup: enabled + interval, 15–1440 minutes, default hourly): per-server verdicts offthe SAME daily-summary scoring every day surface bands with, the diff against sweep N−1's persisted
rows, watch items through the hysteresis state machine (two consecutive sightings to open, two quiet
sweeps to close, last-seen stamps moving on SIGHTINGS only), instrument liveness judged before
anything else is believed, restart awareness as a gate (the measured purge-before-collection-loop
settle window), and the would-have-paged ledger derived honestly from the sweep's own scoring under
master-off — deliberately not an alert-engine replay. The engine makes no delivery call anywhere.
/api/sweepsread surface and the Fleet Sweeps dashboard page:caller-configurable spans (default the last hour, per the ruling) validated through the shared MCP
window authority, the
FleetSweepPresentationbuilders as the ONE wire shape, the mute header onevery sweep with the ledger present-and-empty under mute and absent under alerts-on, and a sweep that
could not prove its instruments rendering LOUDLY — quiet-is-not-clean applied to CSS.
Also riding this lane: the #3471 review's retention finding, fixed at its own commit — the two unsliced fleet-sweep child purges (verdicts, would-have-paged) dispatched through the drain loop's default
batchSize: 1, whose "a full batch means more may remain" contract inverts for a whole-statement DELETE: any purge day with real work ran each statement twice, once for the rows and once to observe none. They now dispatch single-shot (a cap no real row count reaches), the sliced siblings keep their batch size, and a source pin holds the shape.The daily rollup
One channel post per day, a ceiling and not a default, exactly as the delivery ruling ordered —
built on the collector-cost digest's machinery reused whole: the daily-interval dedup, the
empty-sends-nothing gate, the declared INFO arm (#3443's precedent, pinned so the next
fall-through sweep cannot promote it to WARNING), the shared mute seam, the same funnel. The interval
is also the covered span, one number, so what a post claims to cover and how often one can arrive
cannot drift apart.
What it says: the trailing day's band transitions (chronological, parsed from the persisted sweep
documents — the engine's report is the authority on what each sweep said), watch items opened and
closed (subjects resolved from the span's own verdict names; the fleet sentinel named as the fleet; a
server no verdict named stated as its bare id, never invented), instrument-liveness incidents in the
quiet-is-not-clean vocabulary, unreadable documents counted — an unreadable day must not read as a
quiet one — and, whenever any covered sweep ran under the alert master switch, the would-have-paged
ledger those sweeps carried, counted and named per family and server: what the silence cost, rendered
explicitly.
Two decisions argued rather than assumed:
("empty sends nothing"), the ruling names the failure mode (report-class content in a paging channel
trains operators to ignore the channel), and the clincher is quiet-is-not-clean itself: an honest
all-clear would have to carry the instrument-liveness proof behind it, and a daily post carrying
proof-of-quiet is a report card — the exact thing the ruling bounds. The affirmative all-clear lives
where it can afford its evidence: the web feed, every sweep, at full cadence, each document proving
its own instruments. Channel silence is therefore backed by instrument-proved quiet on the record
surface, never by absence. And nothing consumes the interval but a delivered post — not a quiet day,
not an empty one, not the master gate, not a failed read (which lands on the Under store contention the alert pass fails first and silently, so the self-alert that would report it is the first thing lost #3013 swallowed-read
counter and skips the tick) — so the first day with something to say is never delayed.
under master-off (the muted-mode contract; the report surface is never blinded), so this apply is the
one place the sweep feature touches a channel, and master-off means it touches nothing. A rollup only
ever covers the trailing day: the first post after re-enable states how many of ITS covered sweeps
ran muted (with their ledger under that statement), so the operator is told there is history to read;
older muted days never get a channel post of their own, because a backlog recap is either unbounded
or silently truncated, and the re-enable flood is what the operator's mute usually existed to
prevent. The sweep documents stand in the web feed and
get_sweep_reportsas the permanent record.The apply joins
AlertMasterSwitchSurfaceTests' per-family census as the sixteenth firing member,inline-gated (two of the sixteen — the collection-stopped and capture-down applies — are gated at
their verified sole caller rather than inline, which is the census's own recorded distinction); the
delivery-call table itself does not move, because
FireAsync's single deliver site is unchanged.get_sweep_reportsThe narration interface, per the ruling that dropped the narrator tier: any built-in prose pass
needs an LLM from somewhere, and every shape pays — a bundled or cloud-called model collides with the
nothing-phones-home promise, and a BYO-endpoint knob is config surface for a job the architecture
already assigns elsewhere. The product publishes evidence; an agent adds judgment; this tool is the
interface between the two, and it is deliberately the SAME
FleetSweepPresentationbuilders and storereads the web feed serves, so an agent and a browser cannot disagree about what a sweep looks like.
One call returns the sweep timeline for the window (the shared
ValidateWindowauthority, refusingrather than clamping), the newest sweep in full (verdicts with transitions pre-computed, the ledger
whenever the sweep ran muted), and the watch worklist (the web feed's own state validator, one refusal
vocabulary).
sweep_idfetches one sweep — as a string, by #2548's rule: tick-scale ids do notsurvive a JSON number round trip, and the tool refuses an id it cannot parse exactly rather than
silently querying a rounded neighbor. The description carries the two field contracts an agent must
read, and the tests pin them:
alerts_enabled: falseon a sweep means delivery was off while itran (present-and-empty vs absent ledger stated), and
instruments_alive: falsemeans the sweepcould not prove its own data sources — quiet is not clean, and an empty window on such a sweep is
unreadable, not healthy.
Every census moves: the instructions' tool sentence (151 tools, 65 unique to Darling) plus the
cross-server table row and a field-contract paragraph; the
CrossAppMcpToolInventoryPinTestsratchet(Darling-only by architecture — Lite has no fleet to sweep and no sweep-state tables); the root
README's two Darling figures; llms.txt's range; and the
/api/readdispatch gains the mirror besidethe dedicated
/api/sweepsroutes — the/api/fleetcoexistence.ExcludedToolNamesis untouched:read tools are mirrored.
Store
Three new rollup reads with the THROWING posture, deliberately opposite the presentation reads: the
rollup posts nothing on an empty day by design, so a fault folded into "empty" would convert an
unreadable store into a permanently quiet channel with no artifact anywhere — the quiet-is-not-clean
misreading at the delivery end. The two new span statements (the ledger joined to its runs, and the
distinct names map that lets a channel post name servers instead of ids) are rung-pinned beside their
siblings: both bounds the caller's, no
now(), and no read-side restatement of the write-sideguarantee that ledger rows only exist under master-off sweeps.
Tests and verification
FleetSweepRollupTests— the digest suite's disciplines: every silence pin has a firing twin(quiet-day, empty-day, master-gate, and none consuming the interval), the fire shape (own metric, no
severity override, the store label, the count value), the shared mute seam, no resolution edge, and
the shipped render re-parsed with its figures held to each other — header counts against lines
rendered, the ledger banner against the sum of its family lines, the mute sentence against the sweep
count it claims, and chronology against a deliberately newest-first fixture.
DarlingMcpFleetSweepToolsTests— theget_alert_settingssibling pattern: the one-tool surface,the param contract with everything optional, the string
sweep_idpin, the sharedas_ofdescription verbatim, the refusal table proven on a dead data source, the description and
instructions contracts, Gemini-clean advertised schema, and dispatch membership beside the untouched
exclusion list.
Lite.Testsgains the seconddeliberate INFO pin and the ratchet entry.
errors and no new warnings, and a 70-check harness ran the pure half plus a live TimescaleDB
pg17 pass — real planted sweeps through the real store writer, the three new reads on the wire,
EvaluateFleetSweepRollupAsyncdelivering one rollup off the real store carrying the planted mute'sledger and refusing to repeat inside its day, master-off delivering nothing, and
get_sweep_reportsplus its
/api/readbinding served off the same rows.Deployment note
Per the sizing ruling: this feature is dogfooded in a separate environment before the production fleet
sees it — no install accompanies these merges.