Skip to content

Anomaly engine Stage A: baseline-quality gate, wait-profile redesign, honest no-baseline rendering - #1491

Merged
erikdarlingdata merged 4 commits into
devfrom
feature/recs-remainder-stage-a
Jul 12, 2026
Merged

erikdarlingdata merged 4 commits into
devfrom
feature/recs-remainder-stage-a

Conversation

@erikdarlingdata

Copy link
Copy Markdown
Owner

Stage A of the recs-remainder redesign (analysis/anomaly engine)

Builds on the SAFE subset already merged in #1486 (absolute-magnitude floors + sigma display cap) — this is the remainder's Stage A. Scope is strictly Stage A: Change 2 (quality gate) then Change 1 (wait redesign), plus the 100x honest-rendering fix on the blocking/deadlock detectors. Stage B (incident dedup + CXPACKET severity cap) is deliberately NOT here.

3-provider parity change across the triplicated engine + shared PerformanceMonitor.Analysis.

Change 2 — baseline quality gate + bounded dispersion floors

  • BaselineBucket gains DistinctDays (COUNT(DISTINCT collection_time::DATE) per metric — SUM through the hour-only collapse (exact), MAX proxy through the flat collapse) and IsTrustworthy (tier-aware distinct-day minimums Full >=3 / HourOnly >=10 / Flat >=15, plus real dispersion).
  • Bounded metrics (CPU/memory/IO) gain an absolute dispersion floor (AbsStdDevFloor, folded into EffectiveStdDev) so a variance-collapsed baseline can't manufacture a giant z.
  • New shared AnomalyGate.EvaluateZScore centralizes the interaction trap so the three detectors can't drift: trustworthy → trust z with the Anomaly detector: magnitude floors + sigma display cap to kill young-store noise #1486 magnitude floor as a sanity ceiling; thin/young → fall back to a higher absolute bar (CPU >=90%, mem >=95%, batch >=5000/s, sessions >=500, query-dur >=5s, IO >=50ms), never silence.

Change 1 — wait redesign → one ANOMALY_WAIT_PROFILE

  • Per-type ANOMALY_WAIT_<type> facts (per-hour-vs-per-interval unit inflation; missed a minority-but-real wait) become ONE ANOMALY_WAIT_PROFILE: whole-server all-types wait ms/sec (interval via LAG, never an assumed cadence) vs the WaitMsPerSec baseline, with the top wait types as contrib_<TYPE> metadata.
  • Dashboard SqlServerMetricNames gains WaitMsPerSec + its T-SQL baseline query.
  • FactScorer gains an ANOMALY_WAIT_PROFILE arm; DefaultRatioThreshold + slope retuned to the honest per-second scale (4x → 0.5, 12x → 1.0). FactAdvice composes the profile from its contributors.

100x honest-rendering fix (blocking/deadlock)

  • The no-baseline sentinel now sets an is_new flag; the composer renders a first occurrence ("no baseline yet") instead of a fake "spiked to 100x".

All ratio/slope/fallback constants are labelled tunable — calibrate on SQL2025/HammerDB.

Testing

  • Lite.Tests: 1023 passed / 0 failed / 0 skipped
  • Darling.Tests: 2028 passed / 0 failed / 132 skipped (skipped = gated live-PG)
  • Dashboard.Tests: 690 passed / 3 failed — the 3 failures are pre-existing ThemeParityTests (theme XAML palette drift between Dashboard/Lite; confirmed failing on origin/dev, and this diff touches zero XAML).

Updated tests (intentional, per spec): the two ScenarioTests wait-spike assertions (ANOMALY_WAIT_PAGEIOLATCH_SH → ANOMALY_WAIT_PROFILE + contributor) and the Darling live-PG detector arm (now the is_new profile, since a single-day history is untrustworthy under the gate). New tests added: thin-baseline fallback-fires-but-suppresses-sub-bar-z, healthy-baseline-trusts-z, DistinctDays population + collapse, wait-profile-one-fact + minority capture, and the profile scoring ramp.

Do not merge — Stage B is a separate change.

🤖 Generated with Claude Code

erikdarlingdata and others added 4 commits July 11, 2026 23:45
… + honest no-baseline rendering

Builds on the SAFE #1486 floors/sigma-cap. Three coordinated changes across the
triplicated anomaly engine (Lite AnomalyDetector/BaselineProvider, Darling
PgAnomalyDetector/PgBaselineProvider, Dashboard SqlServer*) plus shared
PerformanceMonitor.Analysis (FactScorer, FactAdvice, new AnomalyGate):

Change 2 - baseline quality gate: BaselineBucket gains DistinctDays
(COUNT(DISTINCT date), summed on hour-only collapse, MAX-proxied on flat) and
IsTrustworthy (tier-aware distinct-day mins Full>=3/HourOnly>=10/Flat>=15 + real
dispersion). Bounded metrics (CPU/mem/IO) gain an absolute dispersion floor
(AbsStdDevFloor) folded into EffectiveStdDev. Shared AnomalyGate.EvaluateZScore
centralizes the interaction trap: trustworthy -> trust z with the #1486 magnitude
floor as a sanity ceiling; thin baseline -> fall back to a HIGHER absolute bar
(CPU>=90, mem>=95, batch>=5000, sessions>=500, query-dur>=5s, IO>=50ms), never
silence.

Change 1 - wait redesign: per-type ANOMALY_WAIT_<type> facts (per-hour-vs-
per-interval unit inflation; missed minority waits) become ONE
ANOMALY_WAIT_PROFILE - all-types ms/sec (interval via LAG) vs the WaitMsPerSec
baseline, top types as contrib_<TYPE> metadata. Dashboard SqlServerMetricNames
gains WaitMsPerSec + its T-SQL baseline query. FactScorer gains an
ANOMALY_WAIT_PROFILE arm; DefaultRatioThreshold + slope retuned to the honest
per-second scale (4x->0.5, 12x->1.0). FactAdvice composes the profile.

100x honest-rendering fix: the blocking/deadlock no-baseline sentinel now sets an
is_new flag; the composer renders "first occurrence / no baseline yet" instead of
a fake "spiked to 100x".

Tunable ratio/slope/fallback consts labelled to calibrate on SQL2025/HammerDB.
New tests across all three suites; the 100x/per-type ScenarioTests + Darling
live-PG arm updated to the profile.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Finding 1 (HIGH): the baseline quality-gate fallback fired but FactScorer
silently zeroed it. The 7 bounded z-metric arm gated severity on deviation_sigma
(if deviation < 2.0 return 0.0), but on a thin-baseline fallback fire that stored
sigma is the real (small) z, so a genuine problem (e.g. memory 96% on a young
store) fired the gate yet produced NO finding (InferenceEngine drops severity-0
facts). AnomalyGate.ZDecision now carries FallbackExceedance (peak / the
absolute-fallback bar); FactScorer, when baseline_low_quality=1, grades off that
exceedance (floor 0.5 to clear InferenceEngine's 0.5 entry-point, ramping to 1.0
at 2x the bar) instead of applying the 2-sigma gate. The trustworthy z-path is
byte-for-byte unchanged. Detectors plumb fallback_exceedance at all 7 emit sites
across the 3 providers.

Finding 2 (HIGH): DarlingAnalysisPipelineTests planted a 2 x 100000ms window
(~166.7 ms/sec), below the 250 ms/sec fallback bar, so nothing fired and
Assert.Single threw after change 1 replaced per-type wait facts with one
ANOMALY_WAIT_PROFILE. Reworked to mirror its already-fixed sibling
DarlingAnomalyBaselineTests: 3 x 200000ms at 5-minute spacing (~666.7 ms/sec)
and assert ANOMALY_WAIT_PROFILE. Live-PG gated (skips locally); fixed by
construction.

Finding 4 (MEDIUM): FlatDayMin=15 was structurally unreachable (CollapseToFlat
sets DistinctDays = MAX of per-hour-bucket distinct days, capped near ~5 in a
30-day window), leaving the Flat trust branch permanently dead. Lowered to 3
(byte-identical across all 3 BaselineBucket classes) and corrected the now-wrong
"conservative lower bound" comments (the MAX is a ~5 ceiling).

Findings 3 (blocking/deadlock count-based firing) and 5 (is_new sentinel tuning)
intentionally untouched per instructions (owner design decisions).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@erikdarlingdata
erikdarlingdata merged commit c9c4ef4 into dev Jul 12, 2026
2 checks passed
@erikdarlingdata
erikdarlingdata deleted the feature/recs-remainder-stage-a branch July 12, 2026 05:00
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