Repository navigation
MCP payload-contract campaign: the agent-facing API tells the truth (~30 defects + an 11-rule contract) #3541
Description
Activity
A3 landed in #3594 (both SKUs, one pattern, census-pinned by
McpPageContractTestson each side). Residuals found in that lane, parked here for the remaining A-items rather than filed separately — each is in this issue's scope:deprecated/Dashboard/Mcp/McpBlockingTools.cs:68,133,McpAlertTools.cs:104—total_events/total_deadlocks/total_alertsare page counts (rows.CountafterTake(limit)), the A3 class on the frozen twin. Its caps live in the Dashboard's own SQL-Server-side readers, so a rename-only mirror is half a fix; the frozen tier gets the smallest faithful diff (renames +truncated) when the A7/A12 normalization lane touches the Dashboard, or nothing.DarlingWebEndpoints.cs:1872,2554— the/api/read/get_alert_historydispatch and catalog don't pass or advertise the newinclude_dismissedparameter, so a web/API caller cannot lift the dismissed filter (it does receivedismissed_excluded_count). One-line wire-through + catalog entry; rider for the next Darling MCP lane.get_active_queries(DarlingMcpSessionTools.cs:156) publishes a genuinetotal_snapshotsfrom an unbounded window read, but its C#-sideblocking_only/database_namefilters run after the read whiletotal_snapshotsstays pre-filter — that is A13's item, noted here so A13's lane starts with the site.- The contract item: the page dialect is currently held by census tests, not code. A shared page-bounds helper in
PerformanceMonitor.Common/Mcp/McpHelpers.csis the natural home when the 11-rule contract is pinned.
A2 landed in #3590 (+ #3599, the Lite
get_query_trenddisclosure that raced the merge): the trio routes through the same age → availability → coverage ladderget_query_trenduses, onto the hourly rollups with the bucket width as denominator, and every sibling publishessource/effective_start/effective_hours_back/truncated/bucket/aggregate_noteon the data path AND the empty one; the empty branch names an unserved head and--backfill-rollupswhere widening cannot help.get_query_trendgained the availability and coverage rungs (it answered 42P01 on plain PostgreSQL past four days). Lite twins publish the same block with Lite's truth. Census: every acceptedhours_backroutes the trio andget_query_trendidentically.Residuals from that lane, parked here:
- The viewer's Performance Trends tab has the same raw-only read —
Darling/PerformanceMonitor.Darling.Viewer/ViewerDataService.QueryTrends.cs:239-246(GetQueryDurationTrendAsync/GetProcedureDurationTrendAsync): a 7-day chart on a TimescaleDB store silently plots 4 days. The port is the MCP fix's hourly twins (DarlingTrendReader.*HourlySql) +ResolveTier; the viewer already hasGetRollupAvailabilityAsyncfor the inputs. Same class as Darling viewer: built-in tabs silently lose history past 4 days - CAGG read-routing only covers Custom Views #1661's built-in tabs — a viewer lane. DarlingWebEndpoints.cs:1959describesget_query_duration_trendas "Query-duration percentiles over time" — it is elapsed ms/sec + executions/sec; no percentiles exist in the read. A15 (description drift) item on the web catalogue.- Measurement A11a residual (not a one-liner) —
DarlingTrendReader.csQueryDurationTrendSql(:383) /:421) still LAG-recompute the RAW denominator:ProcedureDurationTrendSql(query_stats.sample_interval_secondsis per-key delta age (0 when unknowable), not the per-collection gap;procedure_statshas no interval column (one of Measurement-layer campaign: the delta honesty contract (11 findings, one keystone) #3540's four still-naked families); and the viewer (ViewerDataService.QueryTrends.cs:48/72) and Lite (LocalDataService.QueryStats.cs:1116/1193) LAG too, so changing Darling's raw estimator alone would make the same chart disagree across surfaces. Lands with the procedure_stats rung. - A12 pre-existing: the raw LAG idiom's first point (and the QS rollup route's first bucket) is a fabricated 0 — visible in both live fixtures. A12's "trend first-points fabricate 0" starts there.
- The viewer's Performance Trends tab has the same raw-only read —
- added 3 commits that reference this issue
on Sep 18, 2026 A7 landed in #3613 — the five PostgreSQL percent tools compute the window total on the same statement as their rows (
SUM(...) OVER ()above the parameterisedLIMIT), publishtotal_*(window),returned_*(page sum) andreturned_pct_of_total, fetchlimit + 1fortruncated, and say in their descriptions which denominator their shares use. A cross-SKU census over all 82 paged tool bodies pins the rule.Found by executing the SQL, not reading it:
get_pg_kernel_statshad never been ranked by CPU. ItsORDER BY 3 + 4 DESCis an integer EXPRESSION, which PostgreSQL folds to a constant and drops (EXPLAIN showed a single sort key), so every page was in(database_name, query_id)order while the description said "ranked by CPU". Fixed in-lane (named differenced columns); the Viewer's kernel grid inherits it via the shared SQL. A dozen string pins passed over it — only the live container caught it.Residuals from that lane, parked here:
DarlingMcpPgDatabaseTools.cs:~232-235—total_temp_files/total_temp_bytes/total_deadlocksare page sums undertotal_*names, withlimit_reached = databases.Count >= limit(the A3 inference). Disclosed today bylimit_reached, the note, and the web tile labels ("Databases returned", "(of returned)"). TheSUM(SUM(...)) OVER ()idiom inDarlingPgDatabaseReader.PgDatabaseSqlmakes them true totals — but the tile labels live inwwwroot/js/pages/server-tabs.js:3112-3121. The census carries these three keys as its ONE stated allowance with a control that fails when the allowance is unused, so whoever fixes it deletes the allowance. Pairs with the wwwroot part of A15/A16.get_pg_io_statskeepscombination_count(a page count) instead of the dialect'scombinations_returnedbecausePG_IO_SUMMARY_STATSinserver-tabs.js:3173reads it by key;truncatedsits beside it and the description says it is a page count. One-line JS + payload rename finishes the dialect — same wwwroot boundary.- Repo-wide sweep worth one fact: the
ORDER BY <int> <op> <int>folding class.PREPAREdoes not catch it (the statement is valid). A single addition toDarlingPgReadSqlParsesLiveTestsgrepping everyDarlingPg*Readerconst forORDER BY\s+\d+\s*[-+*/]\s*\d+would close the class; the A7 census pins it only for the five readers it touched.
- added a commit that references this issue
on Sep 18, 2026 A14 landed in #3615 (+ the
/api/read/get_alert_history include_dismissedrider from A3's residuals): everyadd_serversstatus maps to exactly one counter and the counters sum torequested;remove_serverrefuses an ambiguous partial with the candidates listed; the two update tools share one vocabulary (omitted = unchanged,""= cleared);mute_analysis_findingreportsregistered/matched_now/muted_unmatchedon both SKUs and surfaces a swallowed store failure; the instruction table stops denying the Entra modes #3484 accepted. The pre-push harness caught a shipping defect (serverId > 0as a scope test — server ids are FNV hashes, half negative).Residuals from that lane, parked here:
deprecated/Dashboard/Mcp/McpAnalysisTools.cs~:429 — the frozen twin'smute_analysis_findingreturns"muted"for any hash. Goes on the end-of-wave Dashboard-mirror list (smallest faithful diff: a count read +matched_nowin its idiom).remove_serverresolves against theserversregistry, which is populated on first successful connect — a server added viaadd_serversthat has never connected cannot be removed by the tool (not_foundwith the listing). Pre-existing. If it matters, the write should also matchconfig_monitored_serversby name/host. A16 (refuse what you cannot honor) candidate.mute_analysis_findingwritesstory_path = story_path_hash(the path column holds the hash) and registers duplicate mutes for a repeated hash on both SKUs — pre-existing, neither a lie in the response; noted for A15/A16.
Status 17:45Z — A2, A3, A7, A14 merged (#3590/#3599, #3594, #3613, #3615). Not in flight. Remaining: A9 daily-summary retention ghosts (
collection_runsfrom #3596 now distinguishes a purged day — half the fix), A10 latest-snapshot stamps, A12 zero-vs-unknown (start at the trend first-point fabricated 0 noted above), A13 silent filter semantics (start at get_active_queries), A15/A16 description drift + envelope vocabulary (including the wwwroot tile labels and the web catalogue's 'percentiles' line noted above), the 11-rule contract census (the page dialect is pinned; the rest are not). Frozen-twin mirrors (Dashboard page counts, Dashboard mute) batch into one end-of-wave lane. Issue stays open by design.- added 11 commits that reference this issue
on Sep 18, 2026 Closing — every checklist item that was a payload-contract FIX is landed on both SKUs; the description-drift normalisation sprawl, the wwwroot twins and the frozen-twin mirrors move to #3653 #3653.
Shipped from this issue (all merged to
devon 2026-09-18):- A3 hidden caps → honest pages (six tool groups, one dialect,
McpPageContractTestscensus both SKUs) — MCP pages now say what bounded them: caps bind to the caller's limit, truncation is detected not inferred, and no page count is called a total (#3541 A3) #3594 - A2 duration-trend trio routed by retention tier with source/effective_start/truncated disclosure — The duration-trend trio routes by retention tier and discloses its source like get_query_trend already does, so a 7-day request no longer returns 4 days labelled quiet (#3541 A2) #3590, Lite's get_query_trend carries the same source/effective_start/truncated block its Darling twin has had since #2353, so the last cross-SKU envelope mismatch in the trend family closes (#3541 A2 follow-up) #3599
- A7 percents name their denominator (
SUM(...) OVER ()on the same statement;get_pg_kernel_statshad never been ranked by CPU —ORDER BY 3 + 4folds to a constant) — MCP percents name their denominator: shares are of the window or say they are of the page, so a three-row page stops summing to 100% of everything (#3541 A7) #3613 - A14 write tools report what happened (
add_serverscounters sum torequested,remove_serverrefuses ambiguity, one update vocabulary,mute_analysis_findingmatched_now) +/api/read/get_alert_history include_dismissed— MCP write tools report what happened: every server you add lands in a counter, a partial name cannot delete the wrong server, an omitted field is never a clear, and a mute that matched nothing says so (#3541 A14) #3615 - A10 every latest-snapshot read says when it was captured; no tool accepts a window it does not read;
get_cpu_scheduler_pressureone contract — Every latest-snapshot MCP read says when it was captured, and no tool accepts a window it does not read (#3541 A10) #3637 - A9 daily-summary retention ghosts (purged day ≠ Healthy,
retention_horizon) + A13 filters are part of the query (parallel_only/min_dop/blocking_onlyin SQL, negativehours_backrefused, unknownsourcenames the accepted set) — The daily summary stops painting purged months green, and every MCP filter is part of the query: parallel_only/min_dop/blocking_only cut before the page, a negative window is refused, an unknown source names the accepted set (#3541 A9/A13) #3641 - A12 zero is a measurement (health parsers
source_observed, NULL regression percents stay null, differenced-trend first point null, xmin denominator = all captures,pvs_measured, growth over available history) — Zero is a measurement: health parsers say whether their source was ever seen, a regression with no baseline stays null, and the first point of a differenced trend is no longer a fabricated 0 (#3541 A12) #3642 - Individually filed and fixed in wave 1: the CPU ranking (get_top_queries_by_cpu and get_top_procedures_by_cpu rank by elapsed time, not CPU #3523), granted_mb, pg_plans filter, autovacuum axis, slots worst-pick, pg_io timing.
Deferred to #3653: A15/A16 description drift + envelope vocabulary (enumerated), the wwwroot twins (page-summed tile labels,
combination_count, the memory-grants line chart), the A10 object-stats trio and theGetCurrentConfigAsynccensus gap, the Lite latch/spinlockLIMIT 20, the viewer Performance Trends raw-only read, the ORDER-BY-integer-expression sweep, the six unpinned contract rules, and thedeprecated/Dashboardmirror batch.- A3 hidden caps → honest pages (six tool groups, one dialect,
- added 5 commits that reference this issue
on Sep 18, 2026
Adversarial sweep of all MCP tool payloads and descriptions on both SKUs (55 Darling + 27 Lite tool files), hunting siblings of two shipped exemplars: #3502 (a published verdict field that wasn't the decision variable) and #3287 (a limited page indistinguishable from a complete answer). Both exemplar fixes verified intact. The thesis: these payloads are consumed by reasoning agents that cannot see the code — a wrong semantic silently poisons every downstream conclusion. Individually filed: the CPU ranking, granted_mb, pg_plans filter, autovacuum axis, slots worst-pick, pg_io timing.
Remaining findings (checklist; path:line-cited in the campaign; re-verify at pickup):
value= elapsed-ms-per-second in duration trends.Clean (patterns to copy): get_query_heatmap ("the model citizen"), get_query_trend tier routing, QS regressions 4-way empty, pg trend null discipline, the capability->precondition->honest-miss ladders.
From the 2026-09 brains-review campaign.