Skip to content

MCP payload-contract campaign: the agent-facing API tells the truth (~30 defects + an 11-rule contract) #3541

Description

@erikdarlingdata

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):

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.

Activity

  1. erikdarlingdata commented on Sep 18, 2026

    @erikdarlingdata
    OwnerAuthor

    A3 landed in #3594 (both SKUs, one pattern, census-pinned by McpPageContractTests on 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_alerts are page counts (rows.Count after Take(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_history dispatch and catalog don't pass or advertise the new include_dismissed parameter, so a web/API caller cannot lift the dismissed filter (it does receive dismissed_excluded_count). One-line wire-through + catalog entry; rider for the next Darling MCP lane.
    • get_active_queries (DarlingMcpSessionTools.cs:156) publishes a genuine total_snapshots from an unbounded window read, but its C#-side blocking_only / database_name filters run after the read while total_snapshots stays 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.cs is the natural home when the 11-rule contract is pinned.
  2. erikdarlingdata commented on Sep 18, 2026

    @erikdarlingdata
    OwnerAuthor

    A2 landed in #3590 (+ #3599, the Lite get_query_trend disclosure that raced the merge): the trio routes through the same age → availability → coverage ladder get_query_trend uses, onto the hourly rollups with the bucket width as denominator, and every sibling publishes source / effective_start / effective_hours_back / truncated / bucket / aggregate_note on the data path AND the empty one; the empty branch names an unserved head and --backfill-rollups where widening cannot help. get_query_trend gained 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 accepted hours_back routes the trio and get_query_trend identically.

    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 has GetRollupAvailabilityAsync for 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:1959 describes get_query_duration_trend as "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.cs QueryDurationTrendSql (:383) / ProcedureDurationTrendSql (:421) still LAG-recompute the RAW denominator: query_stats.sample_interval_seconds is per-key delta age (0 when unknowable), not the per-collection gap; procedure_stats has 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.
  3. erikdarlingdata commented on Sep 18, 2026

    @erikdarlingdata
    OwnerAuthor

    A7 landed in #3613 — the five PostgreSQL percent tools compute the window total on the same statement as their rows (SUM(...) OVER () above the parameterised LIMIT), publish total_* (window), returned_* (page sum) and returned_pct_of_total, fetch limit + 1 for truncated, 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_stats had never been ranked by CPU. Its ORDER BY 3 + 4 DESC is 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_deadlocks are page sums under total_* names, with limit_reached = databases.Count >= limit (the A3 inference). Disclosed today by limit_reached, the note, and the web tile labels ("Databases returned", "(of returned)"). The SUM(SUM(...)) OVER () idiom in DarlingPgDatabaseReader.PgDatabaseSql makes them true totals — but the tile labels live in wwwroot/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_stats keeps combination_count (a page count) instead of the dialect's combinations_returned because PG_IO_SUMMARY_STATS in server-tabs.js:3173 reads it by key; truncated sits 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. PREPARE does not catch it (the statement is valid). A single addition to DarlingPgReadSqlParsesLiveTests grepping every DarlingPg*Reader const for ORDER BY\s+\d+\s*[-+*/]\s*\d+ would close the class; the A7 census pins it only for the five readers it touched.
  4. erikdarlingdata commented on Sep 18, 2026

    @erikdarlingdata
    OwnerAuthor

    A14 landed in #3615 (+ the /api/read/get_alert_history include_dismissed rider from A3's residuals): every add_servers status maps to exactly one counter and the counters sum to requested; remove_server refuses an ambiguous partial with the candidates listed; the two update tools share one vocabulary (omitted = unchanged, "" = cleared); mute_analysis_finding reports registered / matched_now / muted_unmatched on 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 > 0 as 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's mute_analysis_finding returns "muted" for any hash. Goes on the end-of-wave Dashboard-mirror list (smallest faithful diff: a count read + matched_now in its idiom).
    • remove_server resolves against the servers registry, which is populated on first successful connect — a server added via add_servers that has never connected cannot be removed by the tool (not_found with the listing). Pre-existing. If it matters, the write should also match config_monitored_servers by name/host. A16 (refuse what you cannot honor) candidate.
    • mute_analysis_finding writes story_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.
  5. erikdarlingdata commented on Sep 18, 2026

    @erikdarlingdata
    OwnerAuthor

    Status 17:45Z — A2, A3, A7, A14 merged (#3590/#3599, #3594, #3613, #3615). Not in flight. Remaining: A9 daily-summary retention ghosts (collection_runs from #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.

  6. erikdarlingdata commented on Sep 18, 2026

    @erikdarlingdata
    OwnerAuthor

    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 dev on 2026-09-18):

    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 the GetCurrentConfigAsync census gap, the Lite latch/spinlock LIMIT 20, the viewer Performance Trends raw-only read, the ORDER-BY-integer-expression sweep, the six unpinned contract rules, and the deprecated/Dashboard mirror batch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions