Skip to content

Route collector cost regression to a daily digest except when it is fleet-wide (#3443) - #3448

Merged
erikdarlingdata merged 3 commits into
devfrom
fix/3443-collector-cost-digest
Sep 15, 2026
Merged

erikdarlingdata merged 3 commits into
devfrom
fix/3443-collector-cost-digest

Conversation

@erikdarlingdata

Copy link
Copy Markdown
Owner

Fixes #3443 — the delivery half of what #3440 raised; #3441 fixed its threshold half.

Collector Cost Regression has needed two gates (#3316's materiality floor, #3441's dispersion bound) to stop it paging on unactionable things, and the issue's diagnosis is that the remaining problem is delivery, not threshold: a seconds-per-day trend in the monitoring tool's own overhead is a report to read, not a condition to interrupt somebody for — except when it is fleet-wide, the one shape that means something changed rather than some server did.

RouteCostRegressions — pure, testable without a host — demotes every selected regression to report-only unless the collector regressed on at least two servers and on more than half the servers it actually ran on in the 24h census (get_collector_cost's own ranked read supplies the denominator; distinct servers, not row count, on both sides of the fraction). The paging half still fires per (server, collector). A census read failure fails safe: nothing routed, nothing paged on a fraction whose denominator is missing.

The digest itself (Collector Cost Digest, daily, fleet-keyed) is inclusive where the paging predicate is exclusive: no materiality floor, no dispersion bound, movers in both directions ranked by |added s/day| — a collector that got cheaper is the half of the series the paging condition structurally cannot report — each line carrying the baseline's own p95 and worst day so the ratio is judgeable, plus the heaviest-first census that would have surfaced #2862. Its severity arm is the map's one deliberate INFO: declared rather than left to the fall-through it renders identically to, so the next #1136/#2090-style sweep does not promote it to WARNING, and pinned in Lite.Tests so that promotion fails red.

Digest tests assert the routing partition (single-server demotion however large, the fleet-wide twin that still pages, the majority boundary at floor two, dedup on both sides of the fraction) and the rendered document's own identities: each line's ratio and s/day reconcile against the figures printed beside it, in the order MoverSql delivers. Three expected values are corrected from the interrupted draft this lane continues — the reconcile fixture arrives pre-ranked because the renderer trusts its reader's ORDER BY and does not sort, -30555.0 s/day is what (456.2 − 1,150.6) × 44,002 actually renders, and 5,489 ms/run is the summary row's integer division (#2862 quoted the rounded 5,490). The #2150 and #2862 figures are reconstructed as fixtures so the worst regression and the census catch this metric exists for stay named.

No migration rung and no schema change. collect.collector_cost and this condition are Darling-only.

…ap lives

CollectorCostDigestTests covers the routing partition (single-server
demotion however large, the fleet-wide twin that still pages, the
majority boundary at floor two), the digest document's own identities
(each line's ratio and s/day reconcile against the figures printed
beside them, ranked as MoverSql delivers), and the #2862 census and
#2150 regression figures reconstructed as fixtures.

Three expected values are corrected from the interrupted draft: the
reconcile fixture arrives pre-ranked because the renderer trusts its
reader's ORDER BY and does not sort; -30555.0 s/day is what
(456.2 - 1,150.6) x 44,002 actually renders; and 5,489 ms/run is the
summary row's integer division (#2862 quoted the rounded 5,490).

The Lite.Tests pin the digest tests' doc comment cites now exists:
CollectorCostDigest_IsInfoBlueOnPurpose holds the one deliberate
INFO arm against the next #2090-style sweep reading it as a miss.
var regressed = regressedServers[regression.CollectorName].Count;
var collected = collectedServers.TryGetValue(regression.CollectorName, out var c) ? c : 0;

if (regressed >= CostRegressionFleetWideMinServers && regressed * 2 > collected)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correctness bug: a collector missing from the census pages instead of failing safe.

When a collector has no entry in census at all (e.g. brand new, or absent from the trailing 24h window), collected is 0. The majority test then becomes regressed * 2 > 0, which is trivially true for any regressed >= 1. Combined with the regressed >= CostRegressionFleetWideMinServers (2) floor, any collector with 2+ regressed servers and zero census rows will page, which is the exact opposite of the documented intent:

"A collector absent from it has no denominator and is report-only" / "this routing failing toward SILENCE"

This also contradicts the PR's own test ACollectorMissingFromTheCensus_IsReportOnly_NotPaged, which uses exactly 2 regressed servers for a collector absent from the census and asserts routing.Paging is empty. Tracing the code by hand: regressedServers["brand_new_collector"].Count == 2, collectedServers has no entry for it so collected == 0, and 2 >= 2 && 4 > 0 evaluates true — so this specific test should fail as written.

Suggested fix: require a known, non-zero denominator before applying the majority rule, e.g.

if (collected > 0 && regressed >= CostRegressionFleetWideMinServers && regressed * 2 > collected)


if (census.Count > 0)
{
sb.Append("\nHeaviest collectors over the same window, every server together - a collector can be"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor accuracy issue: this text says the heaviest-collector census is "over the same window" as the "Moved most" movers section printed just above it, but they're actually computed over two different windows:

  • Movers (GetCostMoversAsync) uses CostRegressionBaselineWindow = 14 days (_utcNow() - CostRegressionBaselineWindow).
  • census reused here is GetTopAsync(postgres, _utcNow() - CollectorCostDenominatorWindow, ...) = a trailing 24 hours (fetched earlier in EvaluateCollectorCostAsync for the routing denominator, then reused for the digest render).

So a reader is told the heaviest-collectors ranking spans the same baseline as the movers, when it's actually a much shorter (24h) window. Worth either wording this more precisely (e.g. "over the trailing 24 hours") or fetching the census over the same 14-day window if that's what was intended for the digest's heaviest-first section.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed still shipped: the census denominator is the trailing 24 hours (CollectorCostDenominatorWindow) while the movers compare against the 14-day baseline, so "over the same window" is wrong in exactly the way an operator would act on. One-string reword riding the same follow-up PR as the severity carve-out above.

@claude

claude Bot commented Sep 15, 2026

Copy link
Copy Markdown

Reviewed the routing/digest change. Overall the design is sound and well-tested, but I found one correctness bug that looks like it should make the PR's own test suite fail, plus a minor doc/text accuracy nit. Left both as inline comments:

  1. Bug — RouteCostRegressions (DarlingSelfAlertEvaluator.cs): when a collector has no entry in the census at all, collected defaults to 0, and the majority check regressed * 2 > collected becomes trivially true for any regressed >= 1. Combined with the 2-server floor, a collector missing from the census with 2+ regressed servers pages instead of going report-only — the opposite of the documented "fails toward silence" behavior, and (by hand-tracing) the opposite of what ACollectorMissingFromTheCensus_IsReportOnly_NotPaged asserts.
  2. Nit — the digest's rendered text claims the heaviest-collector census is "over the same window" as the movers list above it, but the census is fetched over a 24h window (CollectorCostDenominatorWindow, reused from the routing denominator) while movers use the 14-day CostRegressionBaselineWindow.

No Lite/Darling parity concerns — this condition is explicitly Darling-only (no collect.collector_cost in Lite), and the shared-library touches (AlertMetricClassifier, AlertSeverity) are consistently pinned from both Darling.Tests and Lite.Tests. No SQL injection/security issues (parameterized $1/$2 reads), and the new PostgreSQL query follows the existing style in the file (AS on aliases, etc.).

… stop an unknown denominator paging

#3443's evaluator pass grew two swallowed store reads beside the
regression read: the collector-cost census (get_collector_cost's own
ranked read, supplying the fan-out denominator the routing decides on)
and the collector-cost digest read (the daily report's movers). Both
were already instrumented to the census's own discipline - a distinct
read name, a clock started on the line immediately above their try,
the elapsed rendered in the log line and handed to the counter under
the null key - so every scan found them and every inventory total came
up two short. They are COUNTED rather than exempt, because each is the
evidence an alerting decision is judged on: a blind census read means a
tick routes nothing rather than guessing a channel, and a blind digest
read costs a day's report. No exemption category fits - neither is a
write, a delivery, a non-store read, or a block handed its evidence as
parameters.

The registrations, not the numerals, are the change: the evaluator's
whole-file scope goes 5 counted to 7, CountedSites 28 to 30 with the
two sites named in its cross-check note, Lite's fleet-scoped derivation
goes 4 null-key records to 6 with each read asserted apart (a shared
'collector-cost' match would leave last_failure_read ambiguous exactly
when one of the three stages went quiet), and FleetScopedReads names
all three collector-cost reads - which carries them into both SKUs'
get_collection_health descriptions and the WindowNote by the existing
concatenation. Two prose claims that counted the set ('the other two
are Darling store self-alerts') are corrected where they stand.

One routing defect rode along, and it is the whole-tree guards' fifth
failure: RouteCostRegressions folded a census miss into a denominator
of ZERO, which satisfies regressed * 2 > collected for any numerator at
all - paging exactly the collector with the least evidence, against the
routing's own remarks and ACollectorMissingFromTheCensus_IsReportOnly.
The TryGetValue is now part of the paging condition, so an unknown
denominator fails toward the digest, which carries the finding in full.

The suites cannot run on this host; both census test classes and
CollectorCostDigestTests were compiled against their real sources and
executed here through a shim runner - 61 of 61 green, including every
routing pin in both directions - and Darling.Tests and Lite.Tests both
build clean.
@erikdarlingdata
erikdarlingdata marked this pull request as draft September 15, 2026 02:38
"Blocking Detected" or "Deadlocks Detected" or "Failed Agent Job"
or "Custom Alert Rules Unhealthy" or "Stale Mute Rules" => $"{value:F0}",
or "Custom Alert Rules Unhealthy" or "Stale Mute Rules"
or "Collector Cost Digest" => $"{value:F0}",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IsWarning will still render this row as a warning in both Alert History grids.

AlertSeverity.ForMetric("Collector Cost Digest") is deliberately pinned to INFO/blue in this PR (PerformanceMonitor.Notifications/AlertSeverity.cs), with a test (CollectorCostDigest_IsInfoBlueOnPurpose) guarding against a future sweep "promoting" it to WARNING — that's the surface email and webhooks read.

But AlertMetricClassifier.IsWarning (used here, a few lines up) is the complement of IsResolution/IsCritical — anything that isn't a resolution or a deadlock/poison-wait name defaults to true (see Darling/Darling.Tests/AlertMetricClassifierTests.cs's own IsWarning_True_ForOrdinaryActionableAlerts, which documents even null/"" as warning by default). "Collector Cost Digest" doesn't contain "Cleared/Resolved/Restored/.../Reconnected" or "Deadlock/Poison", so IsWarning returns true for it.

That classifier feeds the Alert History grid's row styling in both apps identically:

  • Lite/Controls/AlertsHistoryTab.xaml / Darling/PerformanceMonitor.Darling.Viewer/AlertsHistoryTab.xaml: DataTrigger Binding="{Binding IsWarning}" Value="True" → Background="#33D97706" (amber)
  • via Lite/Services/LocalDataService.AlertHistory.cs and Darling/PerformanceMonitor.Darling.Viewer/ViewerDataService.AlertHistory.cs, both of which derive IsWarning from AlertMetricClassifier.IsWarning(MetricName)

So every digest row a user actually looks at in the desktop history grid will get the same amber "this needs attention" highlight as a real actionable alert — precisely the outcome the extensive commentary and the dedicated AlertSeverityTests pin were written to prevent, just reached through a different, untouched classifier. Since this is the first metric in the codebase meant to be genuinely non-actionable INFO (everything else hitting the INFO fallback is treated as an accident to be fixed), IsWarning/IsCritical may need an explicit carve-out for it (or some other way to keep the grid consistent with the email/webhook treatment).

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed unaddressed on dev — IsWarning is still !IsResolution && !IsCritical with no informational carve-out, so both Alert History grids render the digest amber against its own deliberately-pinned INFO/blue delivery severity. Queued as a small follow-up PR together with the window-text reword below (one commit each, after the #3466 train lands, since the evaluator file is in flight there).

@claude

claude Bot commented Sep 15, 2026

Copy link
Copy Markdown

Reviewed the diff (DarlingSelfAlertEvaluator's cost-regression routing/digest, the new DarlingCollectorCostReader.MoverSql/GetCostMoversAsync, and the shared severity/classifier/read-failure updates).

Overall: the routing logic, SQL, and rendering all check out against the test suite — I traced RouteCostRegressions's majority/floor math, the MoverSql CTEs (ratio, p95/worst-day dispersion, eligible_pairs via count(*) OVER() before LIMIT), and the digest's rendering/reconciliation math by hand and didn't find a discrepancy with what CollectorCostDigestTests pins. The fail-toward-report direction on a missing census denominator, the read-failure counting split into three named sites, and the Lite-side doc/count updates for shared constants all look internally consistent.

One correctness gap (left as an inline comment on PerformanceMonitor.Common/AlertMetricClassifier.cs): AlertMetricClassifier.IsWarning has no "informational, non-actionable" concept — it's true for anything that isn't a resolution or a deadlock/poison-wait name. That classifier drives the Alert History grid's row coloring in both Lite and the Darling Viewer identically, so Collector Cost Digest rows will render with the amber "warning" background there even though AlertSeverity.ForMetric (which email/webhook consume) was carefully pinned to INFO/blue specifically so this metric would not read as actionable. It's the same "promoted to WARNING" failure mode the PR's own CollectorCostDigest_IsInfoBlueOnPurpose test guards against, just via an untouched code path.

No Lite/Darling parity issues otherwise — this feature is correctly Darling-only (per collect.collector_cost not existing in Lite), and the shared-library touches (AlertSeverity, AlertMetricClassifier, AlertReadFailureCounter) are updated and tested on both sides where applicable. No security concerns (no new external input surface; the new SQL is parameterized, not string-built).

@erikdarlingdata
erikdarlingdata marked this pull request as ready for review September 15, 2026 11:40
@erikdarlingdata
erikdarlingdata merged commit 602211f into dev Sep 15, 2026
8 checks passed
@erikdarlingdata
erikdarlingdata deleted the fix/3443-collector-cost-digest branch September 15, 2026 11:41
erikdarlingdata added a commit that referenced this pull request Sep 15, 2026
…#3462) (#3463)

* Give the cost regression an absolute materiality floor, derived from the fleet's own daily-cost distribution, on both delivery surfaces (#3462)

A 5.5x regression of a 122 ms/run collector paged hourly on 2026-09-15
while costing 8.7 seconds of collection time all day, and the operator's
reaction - "it's a few hundred ms. what's the point?" - is the whole
issue: since #3441 the predicate is purely relative, so a regression of
something tiny fires identically to one of something expensive. The same
day's genuine exhibits added 224 and 377 s/day. Both classes clear the
ratio; only one is worth a human.

* Recalibrate CostRegressionAddedMsFloor from the fleet distribution
  (#3316's constant, raised 5,000 -> 15,000 ms/day)

The card that produced this issue broke #3316's calibration honestly:
its 41-hour sample put the truthful-unactionable mass at 0.16-3.6 s/day
and claimed 5 s held the empty band with 4x headroom, and an 8.7 s/day
unactionable firing shows that mass reaches at least 8.7, so 5 s sits
BELOW the band, not inside it. Per the issue, the new value comes from
the measured fleet distribution of per-collector daily cost rather than
from that one card: over 7 days on two stores of one production store
class (42 and 43 servers), the median (server, collector) pair costs
13.7 and 13.1 s/day to exist at all, and the cheapest 17 of 39
collectors each cost under 4 s/day. A regression whose whole added
footprint is under the median collector's daily bill is lost in the
fleet's own operating mass. 15 s sits just above both medians -
independently, at the geometric midpoint (13.8) of the measured empty
band (8.7, 21.8) - and the constant's remarks now state the thinner
headroom instead of the disproved 4x claim. It stays one named constant
with one unit, evaluated in the reads themselves, so every surface
derives one definition of material and nothing recomputes it at
delivery time.

* AND it beside #3441's conjunction, which it cannot loosen

The floor was already a conjunct of RegressionSql ($4), so the paging
predicate's shape is unchanged and #3441's subset property is
undisturbed: a raised floor ANDed beside the ratio-and-dispersion gate
can only shrink the selected set. The fired alert's threshold text now
states the floor beside the per-run bound - #3441's rule extended
across units: the reported threshold is the conjunction that actually
selected the row, or a reader falsifying the arithmetic reconstructs a
looser predicate than the one that fired.

* Narrow the digest's inclusivity, which #3448 shipped as load-bearing
  prose, because #3462 says to and says why

The movers read carried "no materiality floor and no dispersion bound"
as a design claim: ranking by |added s/day| made a threshold
unnecessary because the quantity that would have been thresholded was
the sort key. The row cap is a presentation bound, though, and on a
quiet fleet the list filled its remainder with rows worth a few hundred
milliseconds a day - the issue's words are that a 5.5x-of-nothing row
in a daily digest is the same noise in a quieter channel, and that the
evaluation belongs in the metric so both delivery surfaces inherit it.
So MoverSql now gates on the SAME constant, taken on abs() of the same
expression it ranks by and CostMover.AddedMsPerDay derives, so the
gate, the order and the printed figure cannot disagree: a collector
that got materially CHEAPER keeps its line (the half of the series the
paging condition structurally cannot report), an immaterial move in
either direction loses it, and eligible_pairs becomes the denominator
that survived the floor so the digest's "N of M" still counts from the
answer the rows came out of. What the digest still refuses to carry is
the part that decides - no ratio factor, no dispersion bound - and the
census section keeps no floor at all, because expensive-without-moving
is that section's whole catch (#2862). The demotion's fail-toward-the-
report guarantee is not narrowed: every selected regression clears the
shared floor by construction, so every row the routing receives stays
digest-visible.

* Fixtures: the issue's exhibits bracket the shipped value, and the
  discriminating pair is a ratio-preserving rescale

CollectorCostMaterialityFloorTests runs the SHIPPED constant (now
internal) against the shipped queries on a live store, unlike its
siblings, which pin query mechanics at explicit values: the noise
exhibit (668.5 vs ~123 ms/run over 13 runs, past the mean ratio, the
p95 bound and the old 5,000 ms floor, worth 7,088 ms/day) must not
return, its x32 rescale - same twelve days, same 13 runs, identical
mean and p95 ratios by construction, 226,824 ms/day - must, so the
exclusion is attributable to absolute magnitude and nothing else, and
the recorded plan_correction (223 s/day) and query_stats (263 s/day at
a chosen baseline; 189 s/day at the loosest baseline the ratio admits)
exhibits must keep firing. The movers read gets the same treatment in
both directions, with eligible_pairs counting survivors. A no-store pin
brackets the constant inside the measured band and computes that the
#2150 reconstruction (26,582,400 ms/day) clears it a thousandfold, so
a retune red-lines instead of silently re-admitting the noise or
eating the catches. The dispersion suite keeps its explicit 5,000 ms
floor deliberately - its fixtures' 10,500 ms added cost sits UNDER the
new production value, and passing that would turn a dispersion test
into a materiality one - and its "runs at the PRODUCTION floor" remark
is rewritten to say so, since it stopped being true here. The digest
reconcile fixture's rows are re-scaled to figures the floored read can
actually return.

No migration rung and no schema change: both bounds are computed from
columns collect.collector_cost already carries, and get_collector_cost
serves exactly what it served before - only the alert/digest evaluation
gained a condition, which is the issue's stated scope.

Closes #3462

* State the calibration in the floor's own unit

The recalibration remark measured the noise exhibit's whole-day total
(8.7 s) where the floor gates its ADDED cost - 7,088 ms/day, 1.42x the
old floor - so the band's lower edge, the headroom figures, and the
band pin all moved to the unit the constant actually compares against.
A geometric-midpoint claim that was false even on its own numbers is
gone; the floor's justification is the medians and the band, stated
with the corrected margins (2.1x above noise, 1.45x under the smallest
real catch).
erikdarlingdata added a commit that referenced this pull request Sep 15, 2026
… delivery path, and census the paths from source (#3464) (#3465)

An operator flipped the master switch off on both SQL Server stores at
13:23:40Z and was paged twice in the next hour anyway: an Analysis INFO
card at 38 minutes, a Collector Cost Regression at 60 - while the engine
sweep's own server_alert_passes counters sat byte-identical across
consecutive hourly reads, proving the switch was honored by the sweep
and bypassed by its siblings. Silencing the fleet ultimately took three
independent actions discovered serially, each found only because the
previous one demonstrably failed to silence a live channel. The switch's
own documentation (Darling/README.md, the alerts section) has promised
all along that `enabled: false` turns off all alert evaluation and
scheduled-analysis finding notifications; this change makes the code
keep that promise, in the AND'd idiom the connection-change gate
established (`!_settings.AlertsEnabled || !_notifyConnectionChanges()`).

* Bypass 1, measured: analysis notifications. Both entry points (the
  scheduled tick, analyze_now) handed the notification service
  config.Analysis.NotificationsEnabled ALONE - the D0 split's "delivery
  gated on analysis_notifications_enabled" had quietly REPLACED the
  master consult rather than adding to it. Both now route through
  DarlingWorker.ShouldNotifyAnalysisFindings(config): master AND family,
  one predicate, so a third entry point has one right thing to call. The
  D0 split itself stands, because the contract says it should: the pass
  still runs and persists findings; only the channels go quiet. Lite had
  the identical bypass in RunAnalysisIfDueAsync and gets the identical
  twin (CollectionBackgroundService.ShouldNotifyAnalysisFindings over
  the App statics).

* Bypass 2, measured, and the call site the issue deliberately left
  unpinned: the collector-cost paging path. The fire path is the
  worker's hourly store-metrics tick (DarlingWorker.cs:2098) ->
  DarlingSelfAlertEvaluator.EvaluateCollectorCostAsync ->
  ApplyCostRegressionsAsync(routing.Paging) -> FireAsync ->
  IAlertDeliverer.DeliverAsync -> the shared send core. The ONLY
  AlertsEnabled consult on that method guarded the DIGEST branch, which
  runs AFTER the paging apply - and a comment beside it claimed
  "AlertsEnabled is checked inside the apply, like every sibling" while
  ApplyCostRegressionsAsync alone among the fifteen firing members had
  no such check. The gate now sits in both places the siblings put it:
  the top of the evaluate (before the first store read, the engine's
  early-return shape) and the top of the apply (the direct-test seam).
  Master-off means what the frozen counters prove it means for the
  engine: no evaluation, no history rows, fire/resolve state frozen
  where it stands - chosen over the connection edge's track-always split
  because there is no cross-sweep state machine here to corrupt; the
  edge rebuilds from stored rows the moment the switch returns.

* Two more bypasses the census surfaced, same shape, not yet measured
  only because nothing on the muted stores happened to trip them. The
  PostgreSQL Tier 0 predictors: the #3013 comment had named this gap
  outright ("this path has no master-switch gate at all") and left it as
  an open question about deliberate exemption; the answer arrived as an
  incident, so the group entry now gates on config.Alerts.Enabled before
  RecordPass - the denominator stays truthful by not counting passes
  that never ran, which is the parity with both sibling sites the old
  comment had to disclaim. And the user-authored custom alert rules:
  CustomAlertEvaluator.EvaluateServerAsync fired into the same channels
  with no consult anywhere; it now takes a REQUIRED alertsEnabled seam
  (a construction that cannot say where its master switch lives must not
  compile) and stops evaluating under master-off exactly as the engine
  its rules ride beside does. ReconcileStateAsync stays ungated on
  purpose: state hygiene writes history rows, never a channel, and a
  mute that could orphan rule state would make flipping the switch back
  on unsafe.

* The census (AlertMasterSwitchSurfaceTests), which is the actual fix:
  the read-failure surface discipline applied to delivery. The file set
  is DERIVED - every production .cs whose stripped source calls a
  delivery seam (DeliverAsync, NotifyAsync, TrySendAlertEmailAsync,
  TrySendAsync, SendFindingAlertAsync, SendTest*) must appear in the
  census - and every call site within those files is attributed to its
  enclosing member and classified: consults the master switch inline
  (asserted BEFORE the first send), or is reached only through a
  verified gated caller (the gate asserted, and sole-caller status swept
  across the whole production tree so a second path in fails naming its
  file), or carries a stated exemption (the shared funnels below the
  gates; the settings windows' Send Test buttons, which must work
  mid-mute or they cannot verify the channel an un-mute will need). A
  separate sweep holds every FireAsync-calling member of the self-alert
  evaluator to the same rule, so the sixteenth member cannot ship what
  the fifteenth did. Run against the pre-fix tree, the census goes red
  naming ApplyCostRegressionsAsync, EvaluatePostgresAlertsAsync, the
  custom-rule sweep and both analysis sites; against this tree it is
  green, and the digest path (#3448) proves inside the gate.

* Fixtures both ways: master off suppresses the cost-regression fire,
  the resolution row, and the state advance (re-enabling fires as a
  clean first entry with no phantom Cleared row owed); the analysis
  predicates get their truth tables in both SKUs, pinning that the
  family toggles remain narrower knobs under master-on rather than
  synonyms. The AND'd idiom precedent and the README contract sentence
  are pinned verbatim, so a change to what the gates mean has to land
  where the meaning is stated.

Operationally, master-off is again sufficient: the analysis toggle and
the stopgap fleet-wide mute rules that today's incident required as
compensating controls are now redundant with the switch, and their
removal is the operator's unwind, not this change's.

Verification: full solution build clean (0 errors); tests cannot run on
this workstation, so the census suite plus the gap-2 fixture were
executed out-of-band against the compiled assemblies and the live tree
(all green), and re-executed against the pre-fix sources (census red on
all four bypasses, fixture unable to compile without the predicate).

Closes #3464
erikdarlingdata added a commit that referenced this pull request Sep 16, 2026
…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)
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