Repository navigation
get_ag_health/get_store_query_stats move reading guidance off tools/list (#3898) - #4114
Merged
Merged
Conversation
…ist (#3898) Refs #3898. Moves both Darling-only tools' guardrail-heavy descriptions to a 620-character head plus a get_tool_guide tail, adding two guardrail facts verified against source that the original prose never stated: get_ag_health's scoped not_collected route, and get_store_query_stats' zero-match payload shape. Adds Darling/Darling.Tests/McpToolGuideHeads.AgStore.cs and updates both tools' budget lines. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QQh8LczP1HGLXQYAx4oTZC
The precondition clause named 3 of the 5 routes in PreconditionReason. It now also names the reader-not-built route and the no-grant route. The pin checks the new clause. Served size stays 614. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QQh8LczP1HGLXQYAx4oTZC
erikdarlingdata
marked this pull request as ready for review
September 24, 2026 03:25
erikdarlingdata
enabled auto-merge (squash)
September 24, 2026 03:25
erikdarlingdata
added a commit
that referenced
this pull request
Sep 24, 2026
* Add 48 buffered CHANGELOG entries in one splice Adds 48 entries and 48 link refs (#4057, #4077, #4079, #4081, #4082, #4083, #4084, #4085, #4086, #4087, #4088, #4089, #4090, #4091, #4092, #4093, #4095, #4096, #4099, #4100, #4101, #4103, #4105, #4106, #4107, #4108, #4109, #4110, #4111, #4113, #4114, #4115, #4116, #4117, #4118, #4119, #4120, #4121, #4122, #4123, #4124, #4125, #4126, #4127, #4131, #4133, #4136, #4141). Each PR's entry was buffered, and this lands every entry whose PR was merged on origin/dev when it ran. Entries found under a bare section line (none unless the old script ran again) move into the matching ### section, below the new entries. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QQh8LczP1HGLXQYAx4oTZC * Changelog splice: add #4137's entry, which merged before the splice but was missed #4137 (pg_plan_capture reads csvlog) merged at 05:57Z with a CHANGELOG entry section in its body. It was neither spliced nor listed as needing no entry. Its entry goes under Fixed, right after #4136's, and #4136's last sentence now points to it instead of saying plan capture is unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NUU29PuGg9TUFBsACgZg2K --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #3898.
Why
get_ag_health(1,529 served characters) andget_store_query_stats(984 served characters) each put a long,guardrail-heavy description on every
tools/listcall. Both are Darling-only reads with no Lite twin. This PRmoves the reading guidance off the wire, into a short head plus a
get_tool_guidetail, per #3898.What changes
Darling/PerformanceMonitor.Darling.Service/Mcp/DarlingMcpAgTools.cs:get_ag_healthgets a 585-character head(616 served) followed by
<<GUIDE>>and the original description, kept verbatim, as the tail.Darling/PerformanceMonitor.Darling.Service/Mcp/DarlingMcpStoreQueryStatsTools.cs:get_store_query_statsgetsa 583-character head (614 served) followed by
<<GUIDE>>and the original description, kept verbatim, as thetail.
Darling/Darling.Tests/McpToolGuideHeads.AgStore.cs(new): head pins for both tools.Darling/Darling.Tests/McpToolsListBudget/DarlingMcpAgTools.txtand.../DarlingMcpStoreQueryStatsTools.txtupdated to the new served sizes.order_byis thelongest, at 165).
wrong, and I verified both against the tool bodies:
get_ag_health's head now says a call scoped to one server returnsnot_collectedwhen that server's enginecan never run AG collection (
DarlingEngineCapability.NotCollectedStatusAsync,DarlingMcpAgTools.cs:86).The original description covered only the fleet-wide
emptycase.get_store_query_stats's head now says a zero-match result is a normal JSON payload with empty arrays, nota
status: "empty"envelope. The tool has no empty or unavailable branch at all. I checked every return pathin
GetStoreQueryStatsto confirm this.Served characters (Darling only, no Lite twin)
Corrections
None. Both original descriptions were accurate. The two additions above are new guardrail facts, not fixes to
wrong ones.
D4 removals
None. Neither served description contained an issue reference or an anecdote to remove.
Re-pointed pins
Darling/Darling.Tests/DarlingMcpAgToolsTests.cshas one existing pin on this tool's description text:Description_SurfacesTheReadersTwoDocumentedTraps. It reads the rawDescriptionAttribute.Description, which isthe head, the marker and the tail joined together, not the served head alone. Every phrase it checks for is still
present, verbatim, in the tail. That list is
NO row for a server with no AGs,last non-empty snapshot,lag and queue depth are NOT banded,secondary_lag_seconds,is_suspended,0 lag while data movement is suspended, andpopulated only for the LOCAL replica. No change was needed. I ranthis test and confirmed it still passes.
I found no other test, in Darling.Tests or Lite.Tests, that pins either tool's description text. Other files that
mention
get_ag_healthorget_store_query_statsonly check tool-name presence, parameter names, or catalog anddispatch membership. Those files are
DarlingMcpStoreMetricsToolsTests.cs,DarlingWebEndpointsTests.cs,EngineCapabilityMissTests.cs,PgTargetMcpSurfaceTests.cs, andLite.Tests/CrossAppMcpToolInventoryPinTests.cs.D9 traps
Correct: Not necessarily. Each row is one monitored server's VIEW of the AG, so a multi-replica AG appears
once per monitored replica.
Prevents it: "One row per REPLICA's view: a multi-replica AG appears once per replica, not merged."
Correct: No. Severities restate the DMVs' own state and health strings only. Lag and queue depth are never
folded in, so a badly lagging secondary can still carry a healthy severity.
Prevents it: "Severities restate DMV verdicts only, un-banded on lag/queue depth"
Correct: Not necessarily. The DMV reports 0 lag while data movement is suspended, so a 0 must be read
together with is_suspended.
Prevents it: "lag reads 0 while suspended, so check secondary_lag_seconds and is_suspended yourself."
Correct: Not necessarily. A server whose AGs were dropped keeps returning its last non-empty snapshot, so
an old collection_time can be that case instead of a live reading.
Prevents it: "collection_time can be stale after an AG is dropped."
everywhere?
Correct: No. Empty on a fleet-wide call is real: no Availability Groups exist on any monitored server.
That is a normal topology, not a collection gap.
Prevents it: "Empty: none collected fleet-wide or on the server."
reported its AGs yet?
Correct: No. not_collected here means that server's engine can never run AG collection at all, for example
Azure SQL Database or a PostgreSQL target. That is a permanent gap, not a timing gap.
Prevents it: "Scoped to a server whose engine never runs AG collection: not_collected."
Correct: No. not_collected is checked only when the caller scoped to one server. A fleet-wide miss always
reads as empty, because no single engine speaks for the whole fleet.
Prevents it: "Empty: none collected fleet-wide or on the server. Scoped to a server whose engine never runs
AG collection: not_collected."
Correct: No. There is no server_name parameter. The tool has no monitored-server scope at all, because the
store itself, not a monitored server, is the subject.
Prevents it: "No server_name: the store is the subject."
Correct: No. It ranks the monitoring STORE's own SQL statements, from pg_stat_statements on the store's
PostgreSQL backend, not a monitored server's queries.
Prevents it: "Ranks the STORE's own SQL statements by server-side cost, from pg_stat_statements: not a
monitored server's queries"
Correct: Not necessarily. by_role is each role's share of the total recorded time. A role can lead by
running many cheap calls rather than slow ones.
Prevents it: "by_role is each role's TIME SHARE, not how slow it felt."
Correct: No. This tool has no empty status. Zero matching statements is a normal JSON payload with empty
by_role and statements arrays.
Prevents it: "Zero matches: a normal payload with empty arrays, not status empty."
Correct: No. precondition is a setup gap, not an activity gap. pg_stat_statements is missing, not loaded
or too old, its reader is not built yet, or this role has no grant.
Prevents it: "Gated: status precondition, with the remedy, when pg_stat_statements is missing, not loaded
or too old, its reader isn't built yet, or this role has no grant."
Status routes
Test plan
dotnet build Darling/Darling.Tests/Darling.Tests.csproj -c Debug: Build succeeded, 0 Warning(s), 0 Error(s).dropcheck.py origin/dev HEAD get_ag_health get_store_query_stats: bothmissing=0.McpToolGuideHeadsAgStoreTests(new file), 5/5 pass.McpToolGuideTests,McpToolsListBudgetTests,McpZeroIsAMeasurementTests,McpPayloadContractCensusTests,DarlingMcpAgToolsTests,StoreStatementStatsTests,DarlingMcpStoreMetricsToolsTests,DarlingWebEndpointsTests,EngineCapabilityMissTests,PgTargetMcpSurfaceTests: 246/246 pass, 0 failed.origin/dev(through DarlingInstallLocationTests: the #4052 checks pass when run non-elevated #4111), clean merge, no conflicts.Darling.Tests.exe. Total 13,340, errors 0, failed 0, skipped 636(live PostgreSQL classes, no rig). Not Run: 1, a pre-existing runner artifact unrelated to either tool.
No
[FAIL]lines appear in the run. The twoDarlingInstallLocationTeststhis wave's brief calls out asknown local failures did not fail here: DarlingInstallLocationTests: the #4052 checks pass when run non-elevated #4111, merged in, fixes them.
Lite twin), so the wave rules do not call for it.
check.pyfinal pass found 25 hit(s). All are the allowed D9 labels(
**Correct:**,**Prevents it:**) or a semicolon inside a table cell.Coordinator verification
was collected. It returns not_collected only when scoped to one server whose engine never runs AG collection
(DarlingMcpAgTools.cs:79-101).
PreconditionReason. It now names all five: missing, not loaded, too old, reader not built yet, and no grant for
this role. To make room, the head drops "answering which one is slow" (the tail still says it) and says
"carries its role" instead of "is attributed to its role". Served size stays 614, under the 620 cap, so the
budget line is unchanged. The pin in McpToolGuideHeads.AgStore.cs checks the new clause.
addresses it. One other answer was "not stated", which does not count as a misread.
Handoff
Nothing deferred. Both of this lane's tools are fully converted and green.