Skip to content

[pull] main from erikdarlingdata:main - #15

Merged
pull[bot] merged 927 commits into
ehtick:mainfrom
erikdarlingdata:main
Jul 22, 2026
Merged

pull[bot] merged 927 commits into
ehtick:mainfrom
erikdarlingdata:main

Conversation

@pull

@pull pull Bot commented Jul 22, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

erikdarlingdata and others added 30 commits July 13, 2026 11:54
…er-latch

Fix: Azure SQL DB outage permanently wedged database-scoped collection (#1506)
… Changes tab

Part 1 (collapse, behavior-preserving): the config-change diff (server /
database / trace-flag DRIFT computed from append-only config snapshots) was
hand-duplicated in the Darling viewer (ViewerDataService.ConfigChanges Diff*)
and the headless MCP host (DarlingConfigHistoryReader Compute*). Hoist the one
source into PerformanceMonitor.Common.ConfigChangeDiff: the three pure diff
algorithms, the snapshot INPUT records, the change OUTPUT records carrying the
pure-derived columns (requires_restart / change_description / scope / status
text), and the 27 database-config setting names. The viewer's ...Row view-models
become thin display wrappers (adding only the ViewerTimeHelper timezone render);
the service reader deletes its diff/records/setting-names and calls the shared
class, with the MCP tools passing DateTime.MaxValue as the no-op upper edge so
the shared both-edges diff reproduces their prior unbounded-read behavior. No
third copy. Same one-source pattern as the #1507 significance collapse. The
Darling config-history tests are repointed to the shared class and stay green.

Part 2 (Lite tab): new LAST top-level Configuration Changes tab (index 19, after
System Events, nothing renumbered; RefreshVisibleTabAsync case 19 == XAML index
19) with three sub-tab grids (Server / Database / Trace Flag config changes)
consuming the shared ConfigChangeDiff over the DuckDB snapshot views
v_server_config / v_database_config / v_trace_flags. The read upper-bounds at the
window end (keeps the pre-window baseline), capture_time renders through
ServerTimeHelper.FormatServerTime, the #1319 database filter is honored, and every
Darling grid column is reproduced. Real-DuckDB round-trip reader tests plant two
captures encoding a known change and assert the diff.

Common + Darling + Lite only; no Full Dashboard, no install/*.sql.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-alerts

Lite connection alerts + generic webhook channel (#1506)
…nges-tab

Collapse config-diff twin to shared Common + Lite Configuration Changes tab (Tier-1 parity)
…er-1 parity)

Port the Darling viewer's Expensive Queries view to Lite as the LAST sub-tab of
the Queries tab (sub-index 6, after Query Heatmap — nothing else reindexed; the
Queries inner switch gains case 6 while the top-level Queries tab stays index 2).

One grid UNIONs the three query engines Lite already collects — query_stats
(Query Stats), procedure_stats (Stored Procedure/Trigger/Function), and
query_store_stats (Query Store) — each grouped and TOP-20-per-source ranked by
AVERAGE worker (CPU) time, then merged, re-ranked by average worker time and
capped at 20, mirroring the Dashboard's report.expensive_queries_today and
Darling's GetUnifiedExpensiveQueriesAsync.

Reader LocalDataService.ExpensiveQueries.cs adapts the Postgres/T-SQL UNION to
DuckDB against the v_query_stats / v_procedure_stats / v_query_store_stats
archive views: the query_stats/query_store arms SUM the collected deltas / weight
per-interval averages by execution_count, a LATERAL fetches each group's latest
text + stored plan, every arm is both-sides window-bound on collection_time and
honors the #1319 database filter, and the us->ms/sec + KB/page->MB conversions
run in SQL like the Dashboard view's outer projection. Named UnifiedExpensiveQueryRow
/ GetUnifiedExpensiveQueriesAsync to avoid collision with Lite's FinOps-cost
ExpensiveQueryRow.

The stored plan rides IN-ROW, so View Plan / Copy Repro Script open it with zero
extra reads via the shared DataGridContextMenu — mirroring the Top Queries sub-tab
(View Plan on the in-row plan; Get Actual Plan re-executes the runnable statement
sources; both no-op cleanly for module sources and plan-less rows).

Store-driven deviations, each faithful to Lite's store: Query Stats objects are
the constant 'Adhoc', all functions collapse to one 'Function' label, the Query
Store grant is pages->MB (x8/1024), and the Query Store arm carries no stored plan
and no plan_handle (Lite's query_store_stats stores neither) so its plan actions
gate off. Every Darling grid column reproduced.

Real-DuckDB round-trip tests (ExpensiveQueriesReaderTests) assert the cross-source
ranking by avg worker time, the unit conversions, the source labels + object-name
shapes, the in-row plan (and the Query Store no-plan/no-handle deviation), delta
summing across snapshots, the #1319 filter, and window bounding.

Full Lite.Tests green (1231 passed, 0 failed); Lite build clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…queries-tab

Lite: Expensive Queries sub-tab (unified cross-source ranked list, Tier-1 parity)
Port Darling's Blocking Stats sub-tab to Lite: a 2x2 chart grid + summary
strip showing blocking AND deadlock SEVERITY over the window (the "getting
WORSE, not just more frequent" signal), the severity companion to Lite's
existing count-only Blocking Trends sub-tab. Appended as the LAST Blocking
sub-tab (sub-index 4, after Deadlocks) so nothing reindexes; RefreshBlocking
Async's subTabOnly switch gains case 4 and the full-refresh path renders it.
Blocking stays top-level index 8.

Reader (LocalDataService.BlockingStats.cs) adapts Darling's aggregates to
DuckDB: block-duration SUM/MAX/AVG(wait_time_ms) per minute over the same
XE-preferred + DMV-fallback (WHERE NOT EXISTS) union the count trend uses
(honoring the #1319 DB filter on both arms), and a deadlock-severity aggregate
over deadlock_graph_xml on the same v_deadlocks/collection_time window as the
count (uncapped), parsed off-thread with Lite's own DeadlockProcessDetail
.ParseFromRows and bucketed by minute (victim_count + total/max/avg wait).

Collapse check: Darling's deadlock-severity computation is NOT a viewer<->
service twin -- the service/MCP side hosts only count trends and deliberately
omits a blocking_deadlock_stats aggregate, and the Darling viewer already
parses via shared Common.DeadlockGraphParser -- so per the brief Lite reuses
its own existing ParseFromRows (internally consistent with its Deadlocks grid)
rather than adding a third copy. Nothing on the Darling side touched.

Four charts (ServerTab.BlockingStats.cs) mirror Darling with Lite's chart
idioms (ChartStyle/ChartHoverHelper, SeriesColors/ChartPalette, UtcOffset
Minutes axis, Y-floor-at-0, window-padded scatter), wired into the Blocking
TabControl + ServerTab.xaml.cs init/dispose. Real-DuckDB round-trip tests
(BlockingStatsReaderTests) assert the duration aggregate, the XE->DMV fallback,
the #1319 filter, window bounding, and the deadlock-severity aggregate
reconciling with the deadlock count.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tats-tab

Lite: Blocking Stats severity sub-tab (Tier-1 parity)
Adds a LIVE, on-demand "what is running right now" DMV snapshot to the
Darling viewer, delivered through the Stage-2 command plane exactly like
execute_actual_plan / fetch_plan: the store-only viewer enqueues a new
fetch_active_queries command, the service runs the query on the target
server's live connection and returns the rows in result_json, and the
viewer renders them in a new "Current Active Queries" sub-tab.

Command plane (mirrors fetch_plan):
- New CommandKind.FetchActiveQueries + "fetch_active_queries" case in
  DarlingCommandExecutor.ResolvePlan (requires target_server_id, no args)
  and the RunAsync dispatch to IDarlingCommandHost.FetchActiveQueriesLiveAsync.
- WorkerCommandHost + DarlingWorker.RunFetchActiveQueriesLiveAsync resolve
  the runtime under the servers lock, return legible not-monitored /
  not-connected / timeout / permission / sql-error outcomes, and cap the
  SQL read at 30s (under the viewer's 60s poll budget).
- Read-only DMV read (not consent-class like execute_actual_plan), but it
  rides the same read-write-seat-only enqueue as every other command.

DMV query REUSED (not reinvented):
- DarlingCollectorRunner.FetchRowsAsync runs the shared QuerySnapshotsCollector
  (BuildQuery + ReadAsync) against the live connection and returns the rows
  WITHOUT writing to the store, building the SAME CollectorContext the
  scheduled sweep builds, so the live snapshot carries the same columns as
  the stored one.

Viewer:
- ViewerDataService.RequestActiveQueriesLiveAsync enqueues, polls, deletes
  the (large) row, and parses result_json into ViewerQuerySnapshotRow (the
  stored grid's model), with read-only-seat / timeout / failure handling.
- New "Current Active Queries" sub-tab (index 2, right after the stored
  Active Queries tab) with a Refresh button; on-demand only (never
  auto-fetched). Estimated/Actual plan buttons reuse the stored handlers.

Tests (Darling.Tests green; build clean):
- Pins the new command constant viewer<->executor, the ResolvePlan case,
  and the full service-serialize -> viewer-parse wire contract (no live
  SQL Server needed), plus timeout/failure mappings and the poll-budget
  ordering. Live fetch_active_queries executor round-trip gated on
  DARLING_TEST_PG like the fetch_plan one.

Darling-only: no Lite, Dashboard, or install/*.sql changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tive-queries

Darling: live Current Active Queries via service-mediated command
Port the Dashboard's Query Store Regressions grid to the Darling viewer as a new
Queries sub-tab, inserted at index 6 (after Query Store by Duration, matching the
Dashboard's Query Store -> Query Store Regressions adjacency). Every sub-tab index
uses a named constant, so Expensive Queries/Query Heatmap only shift.

- ViewerDataService.QueryStoreRegressions.cs: the Postgres port of the Dashboard's
  report.query_store_regressions TVF against query_store_stats -- baseline (before
  the window) vs recent (in-window) averages split on collection_time, CPU-regression
  > 25% gate, execution-count-weighted added-duration ranking, TOP 50, duration-driven
  severity bands; honors the #1319 database filter; row VM reproduces every column.
- Loader + switch case + constant (Queries.cs), FilterManager (Filters.cs), double-click
  history reusing QueryStoreHistoryWindow with plan_id 0 (History.cs), plan-less Copy
  Repro from the query-text sample (CopyExport.cs), and the XAML sub-tab (QueryGridRowStyle,
  no slicer/comparison -- matching the Dashboard grid and the sibling grid-only tabs).
- Tests: SQL string pins (base table, baseline/recent split, CPU gate, ranking/cap,
  severity, casts, DB filter, PG dialect), row display, key mapper, Copy Repro, and a
  gated live round-trip (baseline-vs-recent detection).

Darling-only: no Lite, Full Dashboard, or install/*.sql changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…essions

Darling viewer: Query Store Regressions sub-tab (Dashboard parity)
Restore three classifications the deprecated Dashboard's report.* /
config.critical_issues SQL made but the ported shared analysis engine had
dropped. All three are pure FactScorer additions over facts the collectors
already emit (no collector/schema/install change), so they light up in Lite,
the Dashboard, and Darling recommendations at once; each cites its source
SQL line in a code comment.

- Query Store disabled: INFO advisory folded into DB_CONFIG, detected as
  query_store_on_count < database_count (install/50:83).
- Security-cache (TokenAndPermUserStore) growth: flat WARNING when the
  USERSTORE_TOKENPERM clerk >= 1 GB, off the context-only MEMORY_CLERKS fact
  (install/50:562,583).
- Runnable-queue / worker pressure: standalone ScoreCpuFact arm on
  total_runnable_tasks_count tiered > 50/20/10 plus the small-box
  runnable_tasks_warning fallback (install/47:1839-1844); additive to and
  distinct from the #1494 THREADPOOL amplifier, which is unchanged.

Advice wired for the new/expanded root keys (MEMORY_CLERKS + RUNNABLE_TASKS
blocks; Query Store folded into ComposeDbConfig) so the cards are not
contentless, sourced from the Dashboard's own message/recommendation text.

FactScorer tests added in both Lite.Tests and Dashboard.Tests (tiers fire at
the right band, stay silent below the bar, existing arms unchanged). The Lite
clean-server seeder now defaults Query Store ON (the healthy value) so the
QS-off arm stays dormant in fixtures not exercising it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wraps the 18 MCP tools Lite lacked vs Darling as [McpServerTool] wrappers over
readers Lite already has: get_latch_stats / get_spinlock_stats / get_plan_cache_bloat /
get_cpu_scheduler_pressure / get_resource_semaphore / get_default_trace_events /
get_daily_summary / get_server_config_changes / get_database_config_changes /
get_trace_flag_changes, and the 8 get_health_parser_* tools. io_issues/cpu_tasks got
thin reader adds off the shared Common.SystemHealthParser (parse reused, not reinvented).
KnownLiteMissingMcpTools drained to empty (the CI inventory-pin ratchet). The get_blocking
rename is deliberately NOT done here (breaking; held for explicit sign-off).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…orts

Port 18 MCP tools to Lite (Tier-2 Lite<->Darling MCP parity)
…arms-batch1

Add 3 Tier-2 issue-classification arms to the shared FactScorer
Two sourced pure-scorer arms (land in Darling + Lite; no collector change --
both read data already in existing fact metadata):

- TempDB version-store pressure by ABSOLUTE size (max_version_store_mb):
  >5000 CRITICAL / >2000 HIGH / >1000 MEDIUM, Math.Max'd into TEMPDB_USAGE
  alongside the existing space-fraction score. Source: report.tempdb_pressure
  (install/47:1431-1433).
- TempDB PAGELATCH_UP allocation contention: absolute wait_time_ms > 10000 ->
  MEDIUM. Source: report.tempdb_contention_analysis (install/47:2515). The
  CRITICAL allocation_contention_warning is a tempdb-scoped flag not carried in
  the server-wide wait fact, so this is scoped to the PAGELATCH_UP MEDIUM tier.

Base caps at 1.0 (WARNING); CRITICAL earned via corroboration. Sourced advice
added; FactScorerTests in Lite + Dashboard (67 Lite FactScorer tests green).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Tier-2 batch-2a: tempdb-deep analysis arms (version-store size + PAGELATCH)
erikdarlingdata and others added 27 commits July 20, 2026 19:19
CI: run the gated-live Darling PostgreSQL suite on PRs, not only nightly (#1587)
…1599)

The build job built Lite.Tests + Installer.Tests + Darling and published
all five apps on EVERY non-doc PR, regardless of what changed - the bulk
of its wall-clock. Gate each product's build/test/publish with a
paths-filter that encodes the real ProjectReference graph:

- product dirs (Lite/ Dashboard/ Installer/ Darling/) never reference
  each other, so a change confined to one builds only that product
- a shared PerformanceMonitor.* core lib fans out to Lite + Dashboard +
  Darling (NOT the CLI Installer - it references only Installer.Core)
- Installer.Core fans out to Installer + Dashboard
- a root build file (solution, global.json, restore config, this
  workflow) rebuilds everything
- markdown-only changes under a product dir short-circuit to a green
  no-op (the filters exclude **/*.md, so a docs PR never trips a build
  with .NET setup skipped)

A Darling-only PR - the common case - no longer builds/tests Lite or the
Installer or publishes the four other apps, and it shrinks further as
Full/CLI move to maintenance. No coverage lost: products can't break
each other; shared changes trigger the fan-out. darling-pg (#1598)
untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…oducts

CI: gate PR build/test/publish per product via the dependency graph (#1599)
Adds seven MCP tools to the Darling server so an MCP client can build and
manage Custom Views (CV2) programmatically instead of only via the web
editor: list_custom_views, get_custom_view, validate_custom_view,
create_custom_view, update_custom_view, delete_custom_view, and
run_custom_view_panel (compile + run a single composed panel and return
{sql, rows, annotations} for a self-test loop).

No divergent second implementation: every tool routes through the existing
authority. Persistence is the same CustomViewStore; structural validation is
the same DarlingWebEndpoints.ValidateDefinition (through ComposeSpec.TryParsePanel
and the hand-authored measure catalog); and the panel run is the shared
RunComposedPanelAsync extracted from the web /api/compose/run endpoint, which
now backs both surfaces. create/update always validate before persisting.
The returned view shape reuses the web BuildFullViewNode/BuildSummariesNode.

Security (scoped, deliberate): the mcp role is granted INSERT/UPDATE/DELETE on
only config.custom_views via role provisioning (not a migration), mirroring the
viewer role's single-table grant for the web composer. A token-holder can author
custom views but still cannot reach the config_command pivot or the carved secret
columns, and a composed query names only collect.* tables. The README MCP
blast-radius section and the MCP instructions are updated to state the widened write.

Tests: provisioning-grant pins (unit) + validate/validate-before-persist envelopes
(ungated) + a gated-live round-trip through the tools against a real PostgreSQL, and
the security-split live test extended to prove the mcp role can write config.custom_views.
The cross-app Lite<->Darling MCP tool-inventory ratchet (Lite.Tests
CrossAppMcpToolInventoryPinTests) records the seven new tools as Darling-only (CV2 is a
central-store feature Lite has no twin for). Full Darling.Tests: 2767 passed / 0 failed /
143 gated-live skipped (local; the gated-live suite runs on CI's darling-pg PR job).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… rows, live create-invalid, search_path fix

Strengthens DarlingMcpCustomViewToolsLivePostgresTests per review, no downscoping:
- run_custom_view_panel now runs against SEEDED collect.wait_stats and asserts NON-EMPTY
  {sql, rows} come back (delta_wait_time_ms summed into hourly buckets), proving the composed
  panel's data flows end-to-end, not just that the SQL compiles.
- Adds a LIVE create-with-invalid-definition assertion: the tool returns 'invalid' AND no row
  is stored (validate-before-persist proven on the real store, alongside the ungated dead-store test).
- Opens the seed connection with the explicit collect/config search_path (like the tool data source)
  so the helper's bare `servers` / `wait_stats` resolve regardless of pool state — a raw cs connection
  can reuse a stale pre-migration session whose search_path is only `public` (surfaced only in a full run).

Verified against a real throwaway PostgreSQL + TimescaleDB stood up from the bundled pg-runtime
(exactly as CI's darling-pg job does, DARLING_TEST_PG set): the gated-live round-trip RAN (not skipped)
and PASSED, and the full Darling.Tests suite is 2904 passed / 0 failed / 6 skipped with the gated-live
tests INCLUDED (the 6 skips need DARLING_TEST_SQL / DARLING_TEST_PGRUNTIME, none of them mine).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Darling MCP: create and manage Custom Views (CV2) over MCP
…up + firewall)

Four one-shot verbs let a headless operator toggle the MCP + web-dashboard
endpoints and open/close their firewall rule in one elevated action:
--enable-mcp / --disable-mcp / --enable-web / --disable-web.

Closes two headless-deployment gaps:
- No supported way to enable/disable an endpoint on a box without the WPF
  Viewer's Settings. After first run darling.json's mcp.enabled/web.enabled are
  only a SEED; the store (config.config_service.mcp_enabled/web_enabled) is the
  live switch. Each verb writes the store via a targeted UPDATE whose
  BEFORE-UPDATE self-bump trigger increments config_version, so the worker
  hot-reloads within one sweep (no restart). Never touches config_version,
  paused, or the other endpoint's flag.
- The service runs as a virtual service account that cannot modify Windows
  Firewall, so its best-effort self-reconcile silently fails. When the endpoint's
  darling.json network block opts into LAN exposure, each verb (run elevated)
  opens/removes the SAME scoped, idempotent-by-DisplayName rule the host
  reconciles; not elevated -> prints the exact elevated command as a handoff
  (the store toggle already succeeded, so a non-elevated shell is never a
  failure). A firewall failure is non-fatal.

Managed-mode only (BYO governs its own config_service + exposure); Windows-only
(DPAPI credential decrypt + WindowsPrincipal + firewall), same guard shape as
--print-viewer-connection. Wired into IsKnownVerb, UsageText, and the Program
dispatch (allow-list + dispatch kept in sync). The two host firewall-rule-name
builders are now internal so the CLI reconciles the exact same DisplayName.

Tests (Darling.Tests): pin the four targeted store-write SQL strings (right flag,
updated_by='cli', WHERE id=1, never config_version/paused/other flag), verb
recognition + classify wiring, the pure firewall-step classifier, the shared rule
names, and one gated-live (DARLING_TEST_PG) test proving enable/disable flip the
flag AND self-bump config_version (transaction rolled back). Gated-live skips in
the normal unit run.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…N-exposure section

Adds a "Headless enable/disable + firewall" subsection beside --configure-network:
the four verbs flip the live config_service store flag (the first-run darling.json
enabled is only a seed) and reconcile the same scoped firewall rule, elevated or
as a printed handoff.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…M view authoring

#1600 added the seven Custom Views authoring tools but gave an MCP client no
way to DISCOVER the compose vocabulary. The tool descriptions give the panel
shape and say "iterate until valid", but no tool enumerates the legal
source/measure/dimension/aggregate/unit/viz identifiers, and validate errors
name the first problem without listing valid values ("unknown source 'X'"
never says what sources exist). An assistant asked to build a view had to
guess names blind - the wall hit while dogfooding, cleared only by reading
MeasureCatalog.cs source a customer's assistant cannot.

Add a read-only describe_custom_view_catalog tool that returns the SAME
BuildComposeCatalogNode the web composer's /api/catalog picker binds to
(measures with source/kind/validAggregates/allowedDimensions/units/appliesTo,
dimensions, unit families, aggregates, timeBuckets, filterOps, viz). The MCP
server instructions now direct clients to call it FIRST before authoring; it
joins the /api/read exclusion set like the other Custom Views tools (served by
its own endpoint, not the 1:1 read mirror). No divergent catalog - one source
of truth. Static reference data: no store, no server, not a write.

Tests: updated tool-surface / Gemini-schema / required-param pins (now eight
Custom Views tools), the /api/read parity reflection, and a new pin that the
tool surfaces a known measure with its composable fields.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ews write

The startup "Least-privilege roles ready" log still described the mcp role as
"viewer's reads + INSERT on analysis_findings/analysis_muted" — omitting the
config.custom_views write #1600 granted it (the same grant the custom-view MCP
tools, and describe_custom_view_catalog's neighbors, rely on). One-line
log-accuracy leftover from #1600, folded in here since it's the same surface.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… as Lite-missing

Lite.Tests CrossAppMcpToolInventoryPinTests asserts Darling's MCP tool set is a
superset of Lite's, with a ratchet allow-list of Darling-only tools Lite has no
twin for. describe_custom_view_catalog is Darling-only by the same architecture
as the #1600 CV2 tools (Lite has no web composer or config.custom_views), so it
joins KnownLiteMissingMcpTools. This CI Lite.Tests check is not in the Darling
build, which is why the Darling-only local run didn't surface it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…covery-tool

Darling MCP: describe_custom_view_catalog (compose vocabulary for LLM view authoring)
…-cli-verbs

Darling: --enable/disable-mcp/web CLI verbs (headless endpoint bring-up + firewall)
…t's healthy-job assertion

The CompressionJobSelfHeal gated-live test asserts a freshly-applied compression
policy job is NOT flagged as stuck. But a just-added TimescaleDB job can
momentarily read next_start = '-infinity' in job_stats before its background
scheduler assigns the first real next run - and the stuck-job detector is CORRECT
to flag -infinity - so asserting immediately after ApplyCompressionPolicy raced
that window and intermittently false-failed on a slow CI runner.

Fix: arm the job with a real future next_start (via the same alter_job the
self-heal uses), then wait for the catalog to reflect a non-(-infinity)
next_start before the "not flagged" assertion. Bounded to 30s, failing loudly if
it never settles (a job that stays -infinity IS genuinely stuck and must not
silently pass). Test-only; the detector and self-heal are unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ion-selfheal-flake

Darling.Tests: settle the compression self-heal healthy-job assertion (gated-live flake)
…delete_mute_rule)

Add three Darling-only MCP write tools to DarlingMcpAlertTools so an MCP client
can tune a user's alerting conversationally, the sibling of the #1600 Custom
Views write tools:

- update_alert_settings: a PARTIAL update of the singleton config_alert_settings
  row, validated against the SAME ranges/enums the Viewer's Settings window
  enforces (SettingsWindow.BuildAlertRowFromControls). Validate-before-write: an
  out-of-range value or an unknown field (top-level or nested) returns
  {status:"invalid"} and writes nothing. Applies ONLY the provided columns via a
  targeted parameterized UPDATE ... WHERE id = 1; the config-table trigger
  self-bumps config_version so the service hot-reloads within one sweep.
- create_mute_rule / delete_mute_rule: reuse the SAME PgMuteRuleStore
  get_mute_rules reads through, with the Viewer's GUID id-generation.

Grant (least-privilege mcp role, in role provisioning, NOT a migration):
INSERT/UPDATE/DELETE on config.config_mute_rules and UPDATE on the singleton
config.config_alert_settings, mirroring how #1600 granted config.custom_views.
Plus a COLUMN-level UPDATE on config_service (config_version, updated_at): the
config_alert_settings bump trigger (config_bump_version) is SECURITY INVOKER and
UPDATEs config_service AS the writing role, so without this every settings write
would fail 42501 in production - and the superuser-run gated-live tests would
never catch it. The column grant lets mcp bump the reload beacon but NOT flip
paused / capture_plans / mcp_enabled / mcp_port.

Also: the MCP instructions document the three tools + the read-modify-write flow
and recount the tool prose (82 -> 85); the cross-app tool-inventory ratchet lists
them as Darling-only (Lite has no central alert store); the three write tools
join the /api/read exclusion set (writes, no read endpoint); the README MCP
blast-radius + roles table are updated.

Tests: six-tool surface pin, Gemini-clean schema + required-params, and
validate-before-write (a bad/unknown partial update returns invalid WITHOUT
opening a connection); a DarlingManagedRoles grant pin; the live security test
now proves the mcp role can UPDATE config_alert_settings (the beacon fires) but
cannot flip paused; and one gated-live round-trip proves update_alert_settings
flips a threshold + self-bumps config_version and create_mute_rule ->
get_mute_rules -> delete_mute_rule round-trips (skips in the normal unit run).

Verified: Darling service + Darling.Tests build clean (0 errors, 0 warnings in
the changed source); Darling.Tests 2821 passed / 0 failed / 145 gated-live
skipped; Lite.Tests CrossAppMcpToolInventoryPinTests 2 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Darling MCP: alert-tuning write tools (update_alert_settings, create/delete_mute_rule)
…erver)

Add the server-onboarding MCP write tools so an MCP client can stand up or
tear down FLEET monitoring conversationally - the service-side twin of the
Viewer's Add / Manage Servers dialogs, and the sibling of the #1600 Custom
Views and #1608 alert-tuning MCP write tools.

- add_servers (BULK): a JSON array of server objects, processed sequentially -
  validate -> case-folded dedupe (shared ServerIdHelper.BuildStorageName gate,
  #1549) -> in-process DarlingServerConnector.ProbeAsync (the service holds the
  network path + credentials, so no test_connect command plane) -> DarlingSecrets
  DPAPI-encrypt (round-trips at collection) -> INSERT mirroring
  SeedMonitoredServersAsync. Per-server added/duplicate/connection_failed/invalid;
  a connection failure does not abort the batch; Entra/MFA rejected; encrypt_mode
  + trust_server_certificate exposed. Returns {added, skipped, failed, results}.
- remove_server: resolve via the same resolver the read tools use, DELETE the
  config_monitored_servers row.
- Grant: mcp role gets INSERT/UPDATE/DELETE on config.config_monitored_servers
  (provisioning, not migration); the encrypted_password column stays SELECT-carved
  (write a credential, never read one back). The #1608 config_service beacon
  column-grant already covers the monitored-servers bump trigger.
- MCP instructions (85 -> 87 tools), cross-app ratchet (Darling-only), /api/read
  write-exclusion, and the README MCP blast-radius (credential-on-the-wire note).

Verified: Darling service + Darling.Tests build clean; Darling.Tests 2847 passed
/ 0 failed / 146 gated-live skipped (DARLING_TEST_PG cleared); Lite.Tests
CrossAppMcpToolInventoryPinTests 2 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Darling MCP: bulk add/remove monitored servers (add_servers, remove_server)
- Bump all 6 csproj files 3.1.0 -> 3.2.0 (Version/AssemblyVersion/FileVersion; InformationalVersion on the 4 non-Darling)
- CHANGELOG: stamp [3.2.0] - 2026-07-21, consolidate duplicate Added/Changed/Fixed sections (lossless), add #1128/#1581 link refs
- README sync: Lite collectors 26 -> 36 (+10 rows, fix session_stats cadence), Lite MCP 55 -> 74 (+21 tool rows), ignored-waits 144 -> 124; correct stale Darling-only collector/tab/MCP-tool claims (now shared)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@pull pull Bot locked and limited conversation to collaborators Jul 22, 2026
@pull pull Bot added the ⤵️ pull label Jul 22, 2026
@pull
pull Bot merged commit 8b44776 into ehtick:main Jul 22, 2026
2 of 3 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant