Add starter custom-alert templates + drift-guard + list tool (#3285) - #3329
Conversation
Plan Component 7 / #3282 Q2's answer: templates over new hardcoded evaluators, so the collected-but-unalerted signals become starting points an operator customizes rather than closed C# checks. - CustomAlertTemplates: a code-defined set of nine starter templates, each {key, name, description, definition-JSON} where the definition is a real CustomAlertRuleDefinition (metric + predicate + hysteresis, scope defaulting to all). Every one maps to a REAL MeasureCatalog measure: PostgreSQL (the #3282 signals) - dead-tuple pile-up (pg_autovacuum_stats/pg_av_dead_tuples), replica replay lag (pg_replication_stats/pg_repl_replay_lag_ms), connection count (pg_session_states/pg_sess_total_sessions), table bloat % (pg_table_bloat_stats/pg_tbl_bloat_pct), replication-slot WAL retention (pg_replication_slot_stats/pg_slot_retained_wal_bytes); SQL Server - high signal-wait % (wait_stats/signal_wait_pct ratio), sustained blocking (blocked_process_reports/bpr_wait_time_ms), long-running query (query_stats/query_avg_elapsed_us ratio), tempdb space (tempdb_stats/tempdb_total_reserved_mb). Thresholds are conservative starters (documented per template) that fire only after 3 consecutive breaching evaluations (2 for blocking). - CustomAlertTemplatesTests: the drift-guard (the alert twin of ViewTemplatesTests) - every template's definition TryParse's clean against the live rule-validator + catalog on every build, so a measure that later drifts out of the catalog fails the build rather than shipping a rule that can never fire. Plus unique-key / shape / list-tool coverage. - list_custom_alert_templates MCP tool (read-only, no store): returns the set with each definition embedded as a JSON object, ready to hand to create_custom_alert_rule / test_custom_alert_rule. Deliberately skipped: a PostgreSQL "storage growth" template - the catalog has no PostgreSQL database-SIZE gauge to threshold on (pg_database_stats is counters, not size). The related disk-fill risk is covered by the slot-WAL-retention template. The new MCP tool trips the five cross-project inventory pins (all updated; verified by the passing CrossAppMcpToolInventoryPinTests, not hardcoded): - DarlingMcpInstructions census: 147 total / 86 shared / 61 unique, "seven -> eight custom-alert-rule tools". - KnownLiteMissingMcpTools ratchet += list_custom_alert_templates. - DarlingWebEndpoints.ExcludedToolNames += it. - DarlingWebEndpointsTests pinned sorted array += it. - DarlingMcpCustomAlertToolsSurfaceTests surface (7 -> 8 names + count) + a required-params row. Part of #3285. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WejwpgWF5Xfm3fAmoEbFz4
| { | ||
| var keys = CustomAlertTemplates.All.Select(t => t.Key).ToArray(); | ||
|
|
||
| Assert.True(keys.Length >= 8, "expected the full starter set; found " + keys.Length); |
There was a problem hiding this comment.
Nit: the set currently has 9 templates, but this asserts >= 8. That lets someone silently drop a template (down to 8) without the "full starter set" guard catching it. Either assert == 9 (matching the PR description's "nine starter templates") or use CustomAlertTemplates.All.Count compared against a named constant so this stays a real regression guard rather than a loose floor.
Review summaryReviewed the diff (Darling-only: Correctness — verified against the live catalog
Parity Security / Performance One minor nit posted inline on the test file (a loose |
What & why
Plan Component 7 and #3282's Q2 answer: ship starter custom-alert templates rather than new hardcoded evaluators, so the collected-but-unalerted signals become starting points an operator customizes — and the set generalizes to signals nobody has named yet.
Changes
CustomAlertTemplates— a code-defined set of nine starter templates, each{key, name, description, definition-JSON}where the definition is a genuineCustomAlertRuleDefinition(metric + predicate + hysteresis; scope defaults to all servers). Thresholds are deliberately conservative starters (documented per template) that fire only after 3 consecutive breaching evaluations (2 for the more urgent blocking signal).CustomAlertTemplatesTests— the drift-guard, the alert twin ofViewTemplatesTests: every template's definitionTryParses clean against the live rule-validator +MeasureCatalogon every build, so a measure that later drifts out of the catalog fails the build rather than shipping a rule that can never fire. Plus unique-key / shape / list-tool coverage.list_custom_alert_templatesMCP tool (read-only, touches no store) — returns the set with each definition embedded as a JSON object, ready to hand tocreate_custom_alert_ruleortest_custom_alert_rule.The template set (signal → catalog measure → starter threshold)
PostgreSQL (the #3282 signals):
pg_autovacuum_stats/pg_av_dead_tuples, maxpg_replication_stats/pg_repl_replay_lag_ms, maxpg_session_states/pg_sess_total_sessions, maxpg_table_bloat_stats/pg_tbl_bloat_pct, maxpg_replication_slot_stats/pg_slot_retained_wal_bytes, maxSQL Server (the obvious ones):
wait_stats/signal_wait_pct(ratio)blocked_process_reports/bpr_wait_time_ms, maxquery_stats/query_avg_elapsed_us(ratio)tempdb_stats/tempdb_total_reserved_mb, maxDeliberately skipped: a PostgreSQL "storage growth" template — the catalog has no PostgreSQL database-size gauge to threshold on (
pg_database_statsis transaction/block/temp counters, not size). The related disk-fill risk is covered by the slot-WAL-retention template (retained WAL is the PostgreSQL storage a stuck slot actually grows unbounded); SQL Server storage is already covered by the built-in low-disk / file-growth alerts. Every unit is set explicitly (ms/mb/percent/count) so the threshold scale is unambiguous, and the drift-guard confirms each unit is catalog-valid.Inventory pins (all five)
The new MCP tool trips the cross-project pins; all updated and verified by the passing
CrossAppMcpToolInventoryPinTests, not hardcoded:DarlingMcpInstructionscensus: 146 → 147 total, 86 shared, 60 → 61 unique, "seven → eight custom-alert-rule tools".KnownLiteMissingMcpToolsratchet +=list_custom_alert_templates.DarlingWebEndpoints.ExcludedToolNames+= it.DarlingWebEndpointsTestspinned Ordinal-sorted array += it (betweenlist_custom_alert_rulesandlist_custom_views).DarlingMcpCustomAlertToolsSurfaceTestssurface (7 → 8 names +Assert.Equal(8, …)) + a required-params row.The census numbers are the live values with the tool registered: the pin derives total from the actual Darling tool count and unique = total − shared, and asserts the sentence matches — it passes at 147/86/61.
Tests
CustomAlertTemplatesTests(4): the drift-guard (every template validates against the live catalog), unique keys + says-what-it's-for, Scalar-with-predicate-and-both-tiers, and thelist_custom_alert_templatestool returns every template with a parseable definition object.Full local suites (from a worktree): Darling.Tests 8648 total / 3 failed, Lite.Tests 3699 total / 4 failed — all 7 are the same pre-existing environmental artifacts as the prior slices (source-tree-scan guards that resolve 0 files from a
.claude/worktrees/checkout, plus one TLS-cert platform test); every new + pinned test passes. Build: 0 Warning(s) / 0 Error(s).Part of #3285.
🤖 Generated with Claude Code
https://claude.ai/code/session_01WejwpgWF5Xfm3fAmoEbFz4