Skip to content

Lite MCP tools disclose a truncated query-stats window (#4231) - #4279

Merged
erikdarlingdata merged 11 commits into
devfrom
fix/4231-lite-window-disclosure
Sep 25, 2026
Merged

erikdarlingdata merged 11 commits into
devfrom
fix/4231-lite-window-disclosure

Conversation

@erikdarlingdata

@erikdarlingdata erikdarlingdata commented Sep 25, 2026 •

Copy link
Copy Markdown
Owner

Part of #4231.

Why

At "Last 7 days", three Lite MCP tools read raw-only tables (query_stats, procedure_stats, query_store_stats) with no rollup fallback. Lite keeps these tables 30 days by default, but the retention is set per collector and a user can lower it. A new install also has less history than the window asks for. In each case the read can serve far less than the caller asked for, and nothing in the answer says so.

#2364 fixed the same problem in Darling's get_query_store_top. This PR is the Lite half of #4231: the shared floor probe, the three MCP tools, and the matching WPF grids. The Darling half, #4278, is merged. This PR also makes the three tools' descriptions match in both products, the last step #4278 left.

What changes

  • One shared floor probe, LocalDataService.GetQueryWindowFloorAsync(QueryWindowRelation, serverId, startUtc, endUtc) in Lite/Services/LocalDataService.QueryWindowFloor.cs. It reads MIN(collection_time) from v_query_stats, v_procedure_stats or v_query_store_stats, bounded on both sides of the window. It reads the v_ view, not the bare table. That view is the hot table plus the parquet archive, so the floor covers archived history too. On a seeded store with 500 hot and 500 archived query_stats rows, the probe took 50 to 92 ms locally.
  • get_top_queries_by_cpu, get_top_procedures_by_cpu and get_query_store_top (Lite/Mcp/McpQueryTools.cs) each gain effective_start, effective_hours_back, window_truncated and a truncation_note. Every existing field, default and argument is unchanged. The names and meaning match Darling's. window_truncated describes the window's floor, not a page cut, and top or limit never changes it. The threshold is the existing 90-minute McpQueryTools.TruncationSlack.
  • The Top Queries, Top Procedures and Query Store grids get a note above the grid. When the floor cuts the window short, it shows "Showing since" and the effective start. The time goes through ServerTimeHelper.FormatServerTime, like the comparison notes on the same tabs. Each grid refreshes the note on a sub-tab switch, a full refresh and a slicer drag. The Query Store slicer reads the same view as its grid, so it shares that grid's probe.
  • The note probes the same UTC window the grid read (commit 0ad7d3d9). The probe compares its bounds straight against UTC collection_time. The first version passed server-local times after a slicer drag and under a custom range. On a server not on UTC, the note then probed a window shifted by the server's offset. It then showed a false "Showing since", or missed a real one. Now the slicer handlers pass the slicer's own e.StartUtc and e.EndUtc. The refresh paths call a new LocalDataService.GetQueriesTabWindowUtc, which makes the same GetTimeRange call the three grid reads make.

One description for each tool, in both products (commits f10d80c3, 4226552e, f6e9f53a)

McpToolGuideTests requires each shared tool's served head (the text before <<GUIDE>>) to be the same bytes in both products. So neither PR can change these heads alone. Once #4278 merged, this branch merged dev and changed them in Lite/Mcp/McpQueryTools.cs and Darling's DarlingMcpDataTools.cs together:

  • get_query_store_top: the head said "Darling: window_truncated marks a window floor..." and then "Lite: no such floor; the full requested window is always read." The second line was false once Lite had a floor. Both sentences are now one: "window_truncated marks a window floor, not a page cut — no limit changes it — because stored history can be shorter than asked; effective_start / effective_hours_back give the reach actually served." The head went from 422 to 351 bytes.
  • get_top_queries_by_cpu and get_top_procedures_by_cpu: each head gains "window_truncated marks a window floor, not a page cut; effective_start / effective_hours_back give the reach actually served." The heads went from 474 to 600 bytes and from 406 to 532. Both stay under the 620-character limit.
  • Lite's two CPU tools now end their tails with the CPU topic, as Darling's do. The joins in all four tails had two spaces before the window clause and none after it. Each join now has one space.
  • The merge dropped the note this branch had put in get_query_store_top's tail about the stale head line.

Tests that pinned the old text changed with it:

  • McpPayloadContractCensusTests: the exception MCP top-queries/top-procedures disclose a shortened raw-tier window (#4231) #4278 added for the two CPU tools is gone. Every Darling window-floor tool now checks its head for "window_truncated" and "not a page cut".
  • McpToolGuideHeads.CollectionLog.cs, in both test projects: the test that pinned "Darling: window_truncated" and "Lite: no such floor" now checks the shared head and that neither old line is served.
  • McpToolGuideHeads.Data.cs, in both: guardrail rows for the two CPU tools' new sentence. Lite's CPU-topic test is back to Assert.EndsWith.
  • The tools/list budget, in both: the three tools' lines in DarlingMcpDataTools.txt and McpQueryTools.txt. TotalCeilingBytes goes from 174,373 to 174,554 in Darling and from 92,041 to 92,222 in Lite. Both grew by the same 181 bytes.

Found, not fixed here

The Queries tab's comparison reads, beside the new note, have the same time-basis bug, and it is older than this PR. After a slicer drag or under a custom range, they read a window shifted by the server's UTC offset. That is #4284. Moving them onto UTC also means moving the comparison's own baseline range, so it gets its own change.

Left for #4231

Stage 3: routing the top-N reads to the hourly rollups, after #4186 merges.

Tests

In Lite.Tests/QueryWindowTruncationTests.cs, with its own DuckDbInitializer so a test controls the archive folder:

  • Each of the three tools reports window_truncated: true and the seeded floor as effective_start when the raw table starts after the window's start.
  • A floor 60 minutes after the start, inside the 90-minute slack, gives window_truncated: false and a null truncation_note.
  • The probe finds the archived parquet floor, not only the hot table's, and is timed.
  • QueriesTabGridReads_RouteThroughSharedWindowFloorHelper checks the source. The probe has one call site, and every grid and slicer path calls the shared note helper. No ServerTab*.cs file writes its own MIN(collection_time) query.
  • Two tests check the note's text and visibility, truncated and not, through ServerTab.SetWindowTruncatedBanner on an STA thread.
  • Three tests from commit 0ad7d3d9, at a -240 minute offset: one pins GetQueriesTabWindowUtc under a custom range. One checks that a slicer's UTC range matches the window the grid read. One scans the source so that every note call takes UTC bounds. With the old slicer and refresh files put back, that source test failed. With the fix restored, it passed.

Runs, on f6e9f53a:

  • dotnet build of Lite.Tests and Darling.Tests: 0 warnings, 0 errors.
  • Darling classes McpPayloadContractCensusTests, McpToolGuideTests, McpToolGuideHeadsDataTests, McpToolGuideHeadsCollectionLogTests and McpToolsListBudgetTests: 89 of 89 passed.
  • Lite classes McpToolGuideTests, McpToolGuideHeadsDataTests, McpToolGuideHeadsCollectionLogTests, McpToolsListBudgetTests and QueryWindowTruncationTests: 31 of 31 passed.
  • Full Darling.Tests, once: 14,003 tests, 0 failed, 740 skipped, 1 not run. The skips are live classes. No PostgreSQL rig ran.
  • Full Lite.Tests, once: 5,383 tests, 0 failed.
  • GitHub Actions CI on f6e9f53a: build, Darling PostgreSQL tests and Lite tests all pass.

CHANGELOG entry

SECTION: Fixed
ENTRY:

🤖 Generated with Claude Code

https://claude.ai/code/session_01FVjn4PBJN71NQXdFo6ZxNQ

…cedures_by_cpu, get_query_store_top (#4231)

query_stats, procedure_stats and query_store_stats are raw-only in Lite (no rollup fallback), and the
default 30-day retention_days is per-collector and user-settable, so a window can silently serve less
than asked for. Adds one shared floor probe (LocalDataService.GetQueryWindowFloorAsync) reading the same
v_ view (hot table UNION archived parquet) each grid/tool reads, and wires effective_start /
effective_hours_back / window_truncated into the three MCP tools that read those tables, using
McpQueryTools.TruncationSlack (already defined for the trend tools) as the threshold.

Part 1 of #4231's Lite half (helper + MCP tools + tests). Grids (Lite/Controls/ServerTab.*) are next.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
erikdarlingdata added a commit that referenced this pull request Sep 25, 2026
…er note (#4231)

get_top_queries_by_cpu / get_top_procedures_by_cpu now carry Lite's (#4279) exact
truncation_note sentence, including the "or this server has been monitored for
less time than that" clause Q1a's version was missing.

WPF: Top Queries / Top Procedures / Query Store grids each read their raw table's
floor through the shared RawWindowFloor probe (ViewerDataService.*WindowFloorAsync)
and show "Showing since <effective start>" in the grid header when truncated,
formatted the way Performance Trends already formats a truncated head. Wired into
both the toolbar-window load and each slicer-drag re-read.

Web: the same three MCP tools' truncation_note now renders through panels.js's
existing #3278 noteKey opt-in, on the built-in server tabs and the starter
dashboard template.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
erikdarlingdata and others added 5 commits September 25, 2026 08:33
The Top Queries, Top Procedures and Query Store grids (and their time-range
slicers, which re-read the same grid over a narrower window) now show
"Showing since <effective start>" in the header when the shared
GetQueryWindowFloorAsync probe finds the raw table's floor cutting the
requested window short -- the same words and probe the three MCP tools
added in the previous commit on this branch use, via one shared
RefreshWindowTruncatedBannerAsync helper so the grid and the tool can never
disagree.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
A source pin (QueriesTabGridReads_RouteThroughSharedWindowFloorHelper)
confirms every Queries-tab grid and slicer read of query_stats,
procedure_stats and query_store_stats routes through the ONE shared
GetQueryWindowFloorAsync probe and calls RefreshWindowTruncatedBannerAsync,
never a hand-rolled second copy. Two more tests pin
ServerTab.SetWindowTruncatedBanner's text and visibility for the truncated
and not-truncated cases. Verified the source pin catches a dropped call
site by temporarily removing one and reverting.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
…ext (#4231)

The earlier commits on this branch put the new raw-tier/window_truncated
sentence in the SERVED HEAD (before <<GUIDE>>) for get_top_procedures_by_cpu
and get_query_store_top, pushing them over their tools/list budget pins and,
for get_top_procedures_by_cpu, over the 620-char hard target. Both already
carry the same fact via the shared McpHelpers.WindowTruncatedDescription
appended in the guide tail, so the head text was redundant as well as
oversized.

- get_top_procedures_by_cpu: dropped the redundant head sentence, matching
  get_top_queries_by_cpu's existing (correct) pattern of leaving the
  disclosure to the tail.
- get_query_store_top: trimmed the head sentence to the essential fact
  (raw-tier retention floor, no rollup) instead of restating what the tail
  already says.
- Banked the resulting saving in McpToolsListBudget/McpQueryTools.txt
  (get_query_store_top's ceiling: 422 -> 390).
- Lite.Tests/McpToolGuideHeads.CollectionLog.cs: the "Lite: no such floor"
  guardrail fact and its dedicated test were pinning the PRE-#4231 shape
  (Lite genuinely had no floor then); updated both to the current, true
  fact and renamed the test accordingly.
- Lite.Tests/McpToolGuideHeads.Data.cs: CpuTimeExtremesTopic_RidesOnBothTopByCpuTools
  asserted the CPU-extremes topic ends each tool's tail; it no longer does,
  since WindowTruncatedDescription now trails it on both tools. Switched
  the assertion from EndsWith to Contains.

Full Lite.Tests suite pending in the next commit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
…dinator (#4231)

Darling.Tests' McpToolGuideTests.EverySharedToolName_CarriesTheMarkerOnBothSkus_OrNeither_WithByteIdenticalHeads
requires get_query_store_top's served head to be byte-identical on both
SKUs. Darling's own #4231 PR (#4278) is a separate, still-open lane that
keeps its head as on dev, so this reverts Lite's head to match dev's
Darling head exactly (including its now-stale "Lite: no such floor" line)
and moves the corrected #4231 explanation to the tail instead, where
get_tool_guide serves it -- the payload's own window_truncated /
effective_start / effective_hours_back fields (and their tests) were
never wrong, only this shared head sentence.

Also updates Darling.Tests/McpPayloadContractCensusTests.cs's cross-SKU
rosters (WindowFloorBlocks, WindowFloorTools, CutNoteKeys) for the three
Lite tools that legitimately gained McpHelpers.WindowTruncatedDescription
and truncation_note in this PR -- data-only roster additions, no Darling
product code touched.

Verified: Darling.Tests McpToolGuideTests, McpToolGuideHeadsDataTests,
McpPayloadContractCensusTests all green (81/81); Lite.Tests
McpToolsListBudgetTests, McpToolGuideTests, McpToolGuideHeadsDataTests,
McpToolGuideHeadsCollectionLogTests, QueryWindowTruncationTests,
ServerTabCapabilityPinTests all green (37/37).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
erikdarlingdata added a commit that referenced this pull request Sep 25, 2026
…he twin-pin census check

Three fixes to the checkpoint (fdda3d0) that shrank get_top_queries_by_cpu's
and get_top_procedures_by_cpu's served heads back to dev's byte-identical
values, and the one census assert that shrink left failing:

- DarlingMcpDataTools.cs: swap the tail concatenation order on both tools to
  McpHelpers.WindowTruncatedDescription + McpToolGuideTopics.CpuTimeExtremesAndAttribution,
  so the tail ends with the shared CPU-extremes topic again
  (McpToolGuideHeadsDataTests.CpuTimeExtremesTopic_RidesOnBothTopByCpuTools).
- DarlingMcpDataTools.txt: bank the two tool lines to the now-measured 406/474
  bytes (down from the stale 678/742 sized for the removed head sentence).
- McpToolsListBudgetTests.cs: rewrite the #4231 change-log comments; they
  claimed +550 head bytes that no longer exist now the head matches dev.
- McpPayloadContractCensusTests.cs: EveryWindowFloorTool_CarriesTheSharedClause_AndNoOtherToolDoes
  required window_truncated / "not a page cut" in these two tools' HEAD, which
  is what the checkpoint's shrink had removed to satisfy D6's twin pin
  (McpToolGuideTests.EverySharedToolName_CarriesTheMarkerOnBothSkus_OrNeither_WithByteIdenticalHeads,
  shared with Lite's #4279). The two requirements are unsatisfiable together
  for a tool pair split across two PRs targeting the same tree: a Darling-only
  head sentence fails the twin pin regardless of merge order, so D6 wins.
  Scoped the census check to read both facts from the tail for just these two
  tools, with the reason and a note that a follow-up can move one identical
  sentence into both SKUs' heads once #4279 merges.

Targeted classes (151 total, 0 failed): RepoFileAdoptionTests,
McpToolGuideHeadsDataTests, McpToolGuideTests, McpPayloadContractCensusTests,
McpToolsListBudgetTests, QueryStoreTopWindowTests, QueryStoreClutterTests,
RawWindowFloorSharedHelperSourcePinTests.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
erikdarlingdata and others added 2 commits September 25, 2026 09:22
…4279)

The slicer handlers passed server-local fromServer/toServer (ServerTimeHelper.ToServerTime)
to RefreshWindowTruncatedBannerAsync, and the custom-range refresh path passed server-local
cStart/cEnd, while GetQueryWindowFloorAsync compares straight against UTC collection_time.
On any server not on UTC the banner probed a window shifted by the server's offset.

Slicers now pass e.StartUtc/e.EndUtc directly. Refresh.cs computes the banner's window through
a new internal LocalDataService.GetQueriesTabWindowUtc helper -- the same GetTimeRange call
GetTopQueriesByCpuAsync/GetTopProceduresByCpuAsync/GetQueryStoreTopQueriesAsync already use for
their own window -- so the grid and its banner can never drift onto two different ranges again.

The comparison calls on the same lines (RefreshQueryStatsComparisonAsync and its two twins) are
deliberately untouched: GetComparisonRange() (ServerTab.Comparison.cs:59) is UTC for the default
hoursBack window but server-local for a custom range, the same split as the bug just fixed, so
giving the "current" side a UTC window while the baseline stays server-local would desync them
instead of fixing them. That needs GetComparisonRange() itself rerouted, which is a separate
design decision.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
…isclosure

# Conflicts:
#	Darling/Darling.Tests/McpPayloadContractCensusTests.cs
@erikdarlingdata

Copy link
Copy Markdown
Owner Author

Fix: the banner now probes the same UTC window the grid read

Confirmed the defect as described. LocalDataService.GetQueryWindowFloorAsync (Lite/Services/LocalDataService.QueryWindowFloor.cs) compares startUtc/endUtc straight against UTC collection_time, with no offset conversion of its own. Two paths fed it server-local times instead:

  1. Lite/Controls/ServerTab.Slicers.cs (all three OnXSlicerChanged handlers): passed fromServer/toServer (ServerTimeHelper.ToServerTime(e.StartUtc)) to RefreshWindowTruncatedBannerAsync. Fixed to pass e.StartUtc, e.EndUtc directly — no conversion needed, those are already UTC.
  2. Lite/Controls/ServerTab.Refresh.cs (6 call sites: 3 in the sub-tab switch, 3 in the full refresh): cStart/cEnd(2/3) are server-local under a custom range (GetCurrentWindow converts the pickers with ServerTimeHelper.UtcOffsetMinutes via DisplayTimeToServerTime), UTC otherwise. Rather than reuse those locals for the banner (which would also change what the still-untouched comparison calls receive — see below), each site now computes a dedicated bannerStart/bannerEnd via a new helper.

New helper: LocalDataService.GetQueriesTabWindowUtc(hoursBack, fromDate, toDate, utcOffsetMinutes) (Lite/Services/LocalDataService.cs, right after GetTimeRange), internal static, thin wrapper over the same private GetTimeRange that GetTopQueriesByCpuAsync/GetTopProceduresByCpuAsync/GetQueryStoreTopQueriesAsync already call for their own window. Every Refresh.cs call site passes ServerTimeHelper.UtcOffsetMinutes — the same offset GetCurrentWindow used to produce fromDate/toDate in the first place (confirmed the three grid reads always use SelectedServerTabUtcOffsetMinutes == ServerTimeHelper.UtcOffsetMinutes for their window regardless of what their own utcOffsetMinutes parameter carries — that parameter binds a SQL display column, not the WHERE window), so the round trip can't silently pick up a different offset than the grid used.

Also renamed RefreshWindowTruncatedBannerAsync's parameters start/end → startUtc/endUtc and added a doc paragraph spelling out the UTC requirement, since that was the exact ambiguity that let the bug in.

Comparison calls: left untouched, with the reason

Checked GetComparisonRange() (Lite/Controls/ServerTab.Comparison.cs:59, basis decided at lines 66-77). It is not uniformly UTC: it recomputes its own fromDate/toDate from the pickers independently, converting to server-local via DisplayTimeToServerTime under a custom range (same as the bug just fixed) and staying UTC (DateTime.UtcNow-based) otherwise — a mixed, condition-matched basis, not a fixed one.

RefreshQueryStatsComparisonAsync(cStart, cEnd) and its two twins (same lines as the banner calls, both in ServerTab.Slicers.cs and ServerTab.Refresh.cs) forward cStart/cEnd straight into GetQueryStatsComparisonAsync/twins, which also compare directly against UTC collection_time with no conversion — so yes, they share the same bug shape today, symmetrically: under a custom range, both the "current" argument and GetComparisonRange()'s baseline are consistently server-local (wrong vs. true UTC storage, but shifted by the same amount as each other).

Per the brief's ruling, I did not touch these. Rerouting only the "current" side onto the new UTC helper while GetComparisonRange() stays conditionally server-local would desync current vs. baseline in the custom-range case specifically — a new, different-shaped bug, worse than the existing symmetric one. Fixing it properly means rerouting GetComparisonRange() itself onto the same UTC helper (both current and baseline), which touches every comparison consumer and is a separate design decision, not a mechanical parity copy. Flagging for a follow-up rather than filing an issue myself, since the brief scoped this lane to the banner only.

Tests (Lite.Tests/QueryWindowTruncationTests.cs)

Three new tests, offset -240 minutes (UTC-4) throughout:

  • GetQueriesTabWindowUtc_CustomRange_ConvertsServerLocalPickersBackToUtc — pure arithmetic pin on the new helper.
  • SlicerBannerWindow_MatchesTheGridsUtcWindow_ForANonUtcServer — pins that e.StartUtc/e.EndUtc (now fed to the banner untouched) equals what the grid's own server-local round trip resolves to.
  • WindowTruncatedBannerCallSites_TakeUtcBounds_NotServerLocalOnes — source-scan revert-proof: all 3 slicer banner calls pass e.StartUtc, e.EndUtc; all 6 Refresh.cs banner calls pass bannerStart(2/3)/bannerEnd(2/3), and none pass the raw cStart/cStart2/cStart3 that still feed the (deliberately unchanged) comparison calls.

Revert-proof, actually run: committed the fix, then git checkout 3b8d9e12 -- Lite/Controls/ServerTab.Slicers.cs Lite/Controls/ServerTab.Refresh.cs, rebuilt, ran the class: Total: 11, Failed: 1 — only WindowTruncatedBannerCallSites_TakeUtcBounds_NotServerLocalOnes failed (the two pure-arithmetic tests still passed, as expected, since they pin the helper directly rather than the call sites). Restored the fix with git checkout HEAD -- <both files>, rebuilt clean, all pass again.

Test runs

Pushed to fix/4231-lite-window-disclosure (now at 3f92c92, includes the dev merge commit). Left the PR as a draft, did not touch its body, did not ready it.

erikdarlingdata added a commit that referenced this pull request Sep 25, 2026
…4231) (#4278)

* MCP raw-window disclosure: get_top_queries_by_cpu / get_top_procedures_by_cpu (#4231)

query_stats and procedure_stats are raw-only tables dropped at 4 days when the
rollups are armed, and get_top_queries_by_cpu / get_top_procedures_by_cpu never
reported how far back their window actually reached. Adds a shared RawWindowFloor
probe (generalized from #2364's single-table QueryStoreWindowFloorSql) and wires
both tools to publish effective_start / effective_hours_back / window_truncated,
the same names and meaning get_query_store_top already uses.

Part of #4231. WPF grid headers and the web viewer note are not in this commit;
see the PR body for the handoff.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3

* MCP truncation_note parity, WPF Queries-tab grid disclosure, web viewer note (#4231)

get_top_queries_by_cpu / get_top_procedures_by_cpu now carry Lite's (#4279) exact
truncation_note sentence, including the "or this server has been monitored for
less time than that" clause Q1a's version was missing.

WPF: Top Queries / Top Procedures / Query Store grids each read their raw table's
floor through the shared RawWindowFloor probe (ViewerDataService.*WindowFloorAsync)
and show "Showing since <effective start>" in the grid header when truncated,
formatted the way Performance Trends already formats a truncated head. Wired into
both the toolbar-window load and each slicer-drag re-read.

Web: the same three MCP tools' truncation_note now renders through panels.js's
existing #3278 noteKey opt-in, on the built-in server tabs and the starter
dashboard template.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3

* Checkpoint: LF-read adoption for the #4231 viewer pin, top-by-CPU description heads (#4231)

Q1a2 stopped at its turn limit before re-running the 5 suite failures; unverified.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3

* #4231: fix the CpuExtremes tail order, bank the shrunk heads, scope the twin-pin census check

Three fixes to the checkpoint (fdda3d0) that shrank get_top_queries_by_cpu's
and get_top_procedures_by_cpu's served heads back to dev's byte-identical
values, and the one census assert that shrink left failing:

- DarlingMcpDataTools.cs: swap the tail concatenation order on both tools to
  McpHelpers.WindowTruncatedDescription + McpToolGuideTopics.CpuTimeExtremesAndAttribution,
  so the tail ends with the shared CPU-extremes topic again
  (McpToolGuideHeadsDataTests.CpuTimeExtremesTopic_RidesOnBothTopByCpuTools).
- DarlingMcpDataTools.txt: bank the two tool lines to the now-measured 406/474
  bytes (down from the stale 678/742 sized for the removed head sentence).
- McpToolsListBudgetTests.cs: rewrite the #4231 change-log comments; they
  claimed +550 head bytes that no longer exist now the head matches dev.
- McpPayloadContractCensusTests.cs: EveryWindowFloorTool_CarriesTheSharedClause_AndNoOtherToolDoes
  required window_truncated / "not a page cut" in these two tools' HEAD, which
  is what the checkpoint's shrink had removed to satisfy D6's twin pin
  (McpToolGuideTests.EverySharedToolName_CarriesTheMarkerOnBothSkus_OrNeither_WithByteIdenticalHeads,
  shared with Lite's #4279). The two requirements are unsatisfiable together
  for a tool pair split across two PRs targeting the same tree: a Darling-only
  head sentence fails the twin pin regardless of merge order, so D6 wins.
  Scoped the census check to read both facts from the tail for just these two
  tools, with the reason and a note that a follow-up can move one identical
  sentence into both SKUs' heads once #4279 merges.

Targeted classes (151 total, 0 failed): RepoFileAdoptionTests,
McpToolGuideHeadsDataTests, McpToolGuideTests, McpPayloadContractCensusTests,
McpToolsListBudgetTests, QueryStoreTopWindowTests, QueryStoreClutterTests,
RawWindowFloorSharedHelperSourcePinTests.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
erikdarlingdata and others added 2 commits September 25, 2026 10:15
Resolves the get_query_store_top conflict in Lite/Mcp/McpQueryTools.cs:
keeps dev's QueryTextPreviewLength=400 constant and query_text preview
sentence, keeps this branch's module_name-miss hint clause, and drops
the "#4231 WIRE CHANGE" paragraph now that the next commit unifies the
head text across both products.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
get_query_store_top's head drops the "Darling: "/"Lite: no such floor"
split for one shared sentence: window_truncated is a window floor, not
a page cut, because stored history can be shorter than asked. Both
get_top_queries_by_cpu and get_top_procedures_by_cpu gain the same
sentence in their heads (both stay under the 620-char budget). Lite's
CPU-tool tails now order McpHelpers.WindowTruncatedDescription before
McpToolGuideTopics.CpuTimeExtremesAndAttribution, matching Darling, and
all four now join with exactly one space at each seam.

Updates the pin tests that expected the old split text: the census
carve-out for the two CPU tools, both McpToolGuideHeads.CollectionLog
twin tests (rewritten as one shared-head assertion), and new guardrail
rows in both McpToolGuideHeads.Data files.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
@erikdarlingdata

Copy link
Copy Markdown
Owner Author

Finishing-step lane report (merge dev + shared window-floor head). Context ran out before the full test pass, so this is a checkpoint, not a finish. Everything below is pushed to fix/4231-lite-window-disclosure (now at 4226552e).

1. Merge of origin/dev (commit f10d80c3)

One conflict, in Lite/Mcp/McpQueryTools.cs's get_query_store_top Description(...). Resolved by:

  • keeping dev's QueryTextPreviewLength = 400 constant and its doc comment,
  • keeping this branch's module_name-miss hint clause ("a module_name miss also carries the window read... as hints."),
  • appending dev's query_text-preview sentence right after it, matching the order Darling's dev twin already uses,
  • dropping the #4231 WIRE CHANGE: that head sentence is shared... paragraph (step 2 makes it false).

Darling/Darling.Tests/McpPayloadContractCensusTests.cs auto-merged cleanly. Confirmed both PRs' rows survived in WindowFloorBlocks (DarlingMcpDataTools.cs initializer:4, McpQueryTools.cs envelope:1/initializer:4) and WindowFloorTools (both files carry get_query_store_top, get_top_procedures_by_cpu, get_top_queries_by_cpu).

2. The three shared heads (commit 4226552e)

Verified byte-identical between Lite/Mcp/McpQueryTools.cs and Darling/PerformanceMonitor.Darling.Service/Mcp/DarlingMcpDataTools.cs by extracting and diffing both head strings in a script.

  • get_query_store_top -- 320 characters:
    "Cost-ranked top Query Store queries (heaviest first), not time-ordered. Requires Query Store enabled on target databases. window_truncated marks a window floor, not a page cut -- no limit changes it -- because stored history can be shorter than asked; effective_start / effective_hours_back give the reach actually served."
  • get_top_queries_by_cpu -- 569 characters (was 443; +126 for the appended sentence plus one joining space).
  • get_top_procedures_by_cpu -- 501 characters (was 375; +126).

Both CPU-tool heads gained, before <<GUIDE>>: "window_truncated marks a window floor, not a page cut; effective_start / effective_hours_back give the reach actually served." Both stay well under the 620-character cap, so no shortening was needed.

Tail order/spacing (both products, both CPU tools): Lite's tail order now matches Darling's, McpHelpers.WindowTruncatedDescription before McpToolGuideTopics.CpuTimeExtremesAndAttribution. Fixed the join spacing at both seams in all four occurrences: trimmed the base string literal's own trailing space before " + McpHelpers.WindowTruncatedDescription (was a double space, e.g. "appears. window_truncated"), and inserted an explicit " " + between WindowTruncatedDescription and CpuTimeExtremesAndAttribution (was zero spaces, e.g. "truncated.min/max_cpu_ms"). Confirmed by script: no double spaces and no missing-space joins remain in any of the six edited description lines.

3. Test changes (commit 4226552e)

  • Lite.Tests/McpToolGuideHeads.Data.cs: CpuTimeExtremesTopic_RidesOnBothTopByCpuTools back to Assert.EndsWith (was Assert.Contains), stale "checks presence, not position" doc comment replaced.
  • Darling/Darling.Tests/McpPayloadContractCensusTests.cs: removed the get_top_queries_by_cpu/get_top_procedures_by_cpu carve-out branch in EveryWindowFloorTool_CarriesTheSharedClause_AndNoOtherToolDoes; every Darling window-floor tool now asserts on the head unconditionally.
  • Darling/Darling.Tests/McpToolGuideHeads.CollectionLog.cs and Lite.Tests/McpToolGuideHeads.CollectionLog.cs: dropped the ("get_query_store_top", "Lite: no such floor") guardrail row from each; renamed QueryStoreTop_WindowFloorClause_IsScopedToDarlingInTheHead to QueryStoreTop_WindowFloorClause_IsSharedAcrossBothSkusInTheHead in both, asserting the served head contains "window_truncated" and "not a page cut" and contains neither "Darling: window_truncated" nor "Lite: no such floor". Lite's copy also dropped the old "that head sentence is shared" assertion and its now-false summary comment.
  • Darling/Darling.Tests/McpToolGuideHeads.Data.cs and Lite.Tests/McpToolGuideHeads.Data.cs: added ("window_truncated") / ("not a page cut") guardrail rows for both CPU tools.
  • Grepped both products and both test projects for Lite: no such floor, Darling: window_truncated, WIRE CHANGE: that head, raw retention can be shorter than asked, and #4231 WIRE CHANGE / that head sentence is shared: no stray pins remain outside the intentional Assert.DoesNotContain lines just added.

Verified

  • dotnet build Lite.Tests/Lite.Tests.csproj -c Debug: Build succeeded, 0 Warning(s), 0 Error(s).

Not done -- ran out of context before finishing

I hit the context watchdog's hard limit mid-task and had to checkpoint rather than finish. Still open:

Recommend re-queuing the budget re-measurement plus both full suites as the next step; the text/merge work above should be stable since it was built and cross-checked script-side, but it has not been proven against the actual test pins yet.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3

…#4279)

Lite's #4279 lands the shared window-floor head, so the twin exemption
in Darling's budget no longer applies. Re-measured tools/list for both
SKUs: get_query_store_top's head drops the old split for the shared
sentence (422 -> 351), get_top_queries_by_cpu and get_top_procedures_by_cpu
each gain that same sentence (474 -> 600, 406 -> 532). Net +181 bytes on
both sides. Updated the three per-tool lines and each project's
TotalCeilingBytes (174,373 -> 174,554 Darling; 92,041 -> 92,222 Lite),
with a change-log comment recording the deltas.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FVjn4PBJN71NQXdFo6ZxNQ
@erikdarlingdata

Copy link
Copy Markdown
Owner Author

Budgets and test runs finished (code was already done)

Branch fix/4231-lite-window-disclosure, base commit 4226552 (the previous lane's head-byte-identical fix),
new commit f6e9f53a pushed on top. No product code changed; only the two tools/list budget pins and their
change logs.

Builds

  • dotnet build Darling/Darling.Tests/Darling.Tests.csproj -c Debug: Build succeeded, 0 Warning(s), 0 Error(s).
  • dotnet build Lite.Tests/Lite.Tests.csproj -c Debug: Build succeeded, 0 Warning(s), 0 Error(s).

Measured budgets (old -> new, both projects agree exactly)

Tool old bytes new bytes delta
get_query_store_top 422 351 -71
get_top_procedures_by_cpu 406 532 +126
get_top_queries_by_cpu 474 600 +126

Net per-tool delta is +181, and it matches the total delta on both sides exactly, confirming no other tool moved.

  • Darling/Darling.Tests/McpToolsListBudget/DarlingMcpDataTools.txt: the three tool ... lines updated to 351/532/600.
  • Lite.Tests/McpToolsListBudget/McpQueryTools.txt: same three lines updated to 351/532/600.
  • Darling.Tests.McpToolsListBudgetTests.TotalCeilingBytes: 174,373 -> 174,554.
  • Lite.Tests.McpToolsListBudgetTests.TotalCeilingBytes: 92,041 -> 92,222.
  • Added one change-log comment block above each TotalCeilingBytes recording the Lite MCP tools disclose a truncated query-stats window (#4231) #4279 deltas; every prior change-log line kept as-is.

Targeted classes (both projects, all green)

Darling.Tests (single run, 89/89 pass, then split to confirm no class silently matched zero tests):

  • McpPayloadContractCensusTests: 68/68
  • McpToolGuideTests: 10/10
  • McpToolGuideHeadsDataTests: 3/3
  • McpToolGuideHeadsCollectionLogTests (the McpToolGuideHeads CollectionLog class): 3/3
  • McpToolsListBudgetTests: 5/5

Lite.Tests (single run, 31/31 pass, same per-class confirmation): Lite has no McpPayloadContractCensusTests
class (Darling-only; Lite's nearest analog is the unrelated McpPageContractTests), so it's skipped here per the brief.

  • McpToolGuideTests: 8/8
  • McpToolGuideHeadsDataTests: 3/3
  • McpToolGuideHeadsCollectionLogTests: 4/4
  • McpToolsListBudgetTests: 5/5
  • QueryWindowTruncationTests: 11/11

Full suites (run once each, foreground, no merge from origin/dev since the brief pins HEAD and supplies its own staleness rule)

  • Darling.Tests: Total 14003, Errors 0, Failed 0, Skipped 740, Not Run 1, 121.8s. All 740 skips are the
    usual PG-runtime-gated tests (DARLING_TEST_PGRUNTIME unset per this brief - no rig used). The lone "Not Run: 1"
    has no [FAIL]/[ERROR] line anywhere in the log; it's a benign MTP counter artifact, not a failure. No
    PostgreSQL rig was started, so the CI flake: PgTarget anomaly/blocking worst-tile assertions fail on first attempts unrelated to the change #4274 flake classes (PgTargetAnomalyTests, PgTargetBlockingTests) did not
    run at all here.
  • Lite.Tests: Total 5383, Errors 0, Failed 0, Skipped 0, Not Run 0, 291.9s.

One deviation from the brief, flagged for the coordinator

The brief's "Commit trailers" section supplied Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3,
but that session URL is already on commit 4226552e (the prior lane's own commit already on this branch before I
started) - reusing it on my commit would misattribute it. My own session's attribution reminder gives a different
URL, so I used that instead on commit f6e9f53a:
Claude-Session: https://claude.ai/code/session_01FVjn4PBJN71NQXdFo6ZxNQ. Worth a glance in case the brief's copy
was meant to be a fresh URL for this dispatch rather than a paste of the prior one.

Nothing deferred; no new issues filed. dev's latest CI wasn't consulted since both full suites came back
fully clean (0 failures) - no failure existed to arbitrate.

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