Repository navigation
get_pg_server_config counted the page and called it the server: non_default_count is now the snapshot's, computed above the cap, truncated is observed off a limit + 1 fetch, and its three sibling pages stop inferring truncation from a full page (#3653 A3 residue) - #3679
Merged
Merged
Conversation
…efault_count was rows.Count(!IsDefault) over the rows fetched, and truncated was rows.Count >= limit, so a page exactly limit long read as truncated and a limit below the number of chosen settings reported the limit as a fact about the snapshot. The count is now the snapshot's, computed on the row statement above the LIMIT; the page's own count is non_default_returned; the include_defaults filter moved into the statement so truncated is about the population asked for; and the three sibling pages in the same file (extensions, lock stats, config changes) fetch limit + 1 and observe truncation instead of inferring it (#3653)
Closed
39 tasks done
This was referenced Sep 19, 2026
erikdarlingdata
added a commit
that referenced
this pull request
Sep 19, 2026
…le, Opus full tier (#3710) (#3718) * REVIEW-TRAPS.md: the repository's recurring defect shapes, one line each with the PR that bit (#3710) Seven layers (SQL, collectors and deltas, analysis, alerting and notifications, storage and MCP payloads, tests and censuses, workflows and CI, repository mechanics), fifty-odd lines, every one citing the pull request or issue that verified it: the >= limit truncation inference (#3594/#3679/#3699), ORDER BY 3 + 4 folding to a constant (#3613), MAX(max_dop) across plans (#3648), integer division in cntr_value_per_second (#3653 Q9), per-collection alert firing against a shorter cooldown (#3628/#3636), restart zeros in deltas and baselines (#3595/#3698/#3705/#3708), ScoreAll skipping amplifiers on a zero base and the non-virtual GetActiveEdges (#3688), the PgTargetFactKeys censuses (#3688), pg_total_relation_size vs the heap and the two TimescaleDB views that hide materializations and history (#3610/#3585), the -infinity crash backoff (#3629), the Slack block budget and the surrogate-pair cut (#3612/#3618/#3625/#3644), LCK_M_SCH_M folding into LCK so no SCH_M fact exists (#3709), one wait profile per run (#3709), and the CRLF round-trip. Each citation was read before it was written down; a wrong citation is worse than none. The review workflow reads this file before the PR body and must say, per trap the diff is near, whether it applies here and why. * Claude review: blind read before the body, callers rule, structured verdict with a ledger, and an Opus/Sonnet tier by changed path (#3710) The prompt runs in two phases. Phase 1 forbids gh pr view / git log / git show until the reviewer has read the diff, every changed region in context, the callers of every changed public symbol (Grep, both SKUs and both test projects) and .github/REVIEW-TRAPS.md, and written its own account. Phase 2 reads the body and grades every claim VERIFIED / UNVERIFIED / REFUTED, then reports the DIVERGENCE between the two accounts. The verdict is a fixed block -- Claims, Riskiest lines with the pinning test or UNPINNED, Traps near this diff, Divergence, Reviewer checklist -- ending in one machine-readable ledger line, posted through gh pr review --body-file - with a quoted heredoc (no Write tool; the allowlist is unchanged and still read-only). The #3650 verdict check now fetches the newest verdict in the window by id and fails the job when the body lacks the "## Verdict:" header or the ledger, or when the ledger's verdict disagrees with the review's API state (a "CHANGES REQUESTED" body submitted with --comment is the shape the guard would wave through). The guard's own arm is untouched and still keys on the state. A dorny/paths-filter@v4 step (the repo's pin style) routes changes under the shared libraries, Darling Storage/Analysis/Service, Lite Services/Mcp/Database, install/ and every .sql file to the full tier -- claude-opus-5[1m], 60 turns -- and everything else to claude-sonnet-5 at 30 turns; one decision step emits tier/model/max_turns, one review step consumes them (two steps would double the "review posted" accounting the guard does), and the prompt carries the tier and model into the ledger. A filter that dies degrades to FULL, not light. Step name "Claude review" and the ALWAYS POST sentinel are unchanged: both are read by claude-review-guard.yml. * Review tier: Alerting and Notifications read at the full tier (#3710)
This was referenced Sep 19, 2026
erikdarlingdata
added a commit
that referenced
this pull request
Sep 23, 2026
…idance off tools/list (#3898) (#4068) Head/tail split for the pgA family (Darling only, no Lite twins). Heads carry the gating, empty/null and sighting-count guardrails; full original prose moves to each tool's get_tool_guide tail. D2: min_severity, get_pg_index_bloat.limit/answered_only and get_pg_deadlocks.limit were over the 200-char parameter cap, trimmed to a guardrail sentence with the overflow moved verbatim into the tool's tail. Re-points McpPageContractTests' page-contract pin (the #3679 "This is what bounds the page" / truncated wording) to read the tail when the tool is #3898-converted, since the wording moved off the now-shortened limit parameter for get_pg_deadlocks and get_pg_index_bloat. Claude-Session: https://claude.ai/code/session_01M7r1CXmEcBmsrTV34CCLfF Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This was referenced Oct 12, 2026
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.
Part of #3653 (the
get_pg_server_configA3 residue, from #3541). Does not close it.The lie
get_pg_server_configpublished two numbers about the server that were numbers about the page.non_default_countwasrows.Count(r => !r.IsDefault)— a count over the rows the reader had FETCHED, which the caller'slimithad already cut. Atlimit = 25against a server with forty chosen settings it said 25, and an agent that cannot see the code read "25 settings differ from default" as a fact about the snapshot. The number moved with the page size, which is the one thing a count of the server must never do.truncatedwasrows.Count >= limit— the inference #3594 named and every other honest page in the repository has since replaced: a page that happens to be exactlylimitlong cannot tell "that was all of them" from "there was at least one more", and the flag said more either way. On this tool the flag was worse than uninformative, because the default view fetched the WHOLE snapshot atlimitand filtered the defaults out in C# afterwards. A server has several hundred parameters and the default limit is 100, sotruncatedwas true on nearly every default-view call even when every chosen setting was on the page. A flag that is always up says nothing.Three sibling tools in the same file —
get_pg_extensions(~175),get_pg_lock_stats(~423),get_pg_server_config_changes(~716) — carried the same>= limitinference and published it.get_pg_extensionsandget_pg_lock_statsalso WITHHOLD their state totals whentruncatedis true, so on a window holding exactlylimitgroups they withheld figures that were complete.The fix
The reader carries the snapshot's count on the row statement (
DarlingPgServerConfigReader.CurrentConfigSql):COUNT(*) FILTER (WHERE coalesce(c.source, 'default') <> 'default') OVER ()::int AS snapshot_non_default_count— the #3613 idiom. A window aggregate over the filtered result, which PostgreSQL evaluates beforeORDER BYandLIMIT, so it is the same figure atlimit = 5andlimit = 500, and the same figure whether or not defaults are included (the FILTER counts only non-default rows, and every non-default row is in both populations). The FILTER's predicate is the SAME expression the row'sis_defaultcolumn is read from, so the count is provably of the flag the rows report. A newPgConfigPage(Rows, SnapshotNonDefaultCount)record andGetCurrentConfigPageAsynccarry it;GetCurrentConfigAsync(the Viewer's grid) is unchanged in signature and returns.Rowswith every setting, as before.The
include_defaultsfilter moved into the statement ($2::boolean), so the cap cuts the population the caller asked for andtruncatedmeans "that population held more thanlimit". A pending-restart row stays in the default view whatever itssource: it is the row that says the file and the running server disagree, the ordering already put it first for that reason, and the old shape counted it inpending_restart_settingswhile hiding it fromsettings. That is the one visible behaviour change beyond the counts: a pending-restart setting whose running value is still the default now appears in the default-viewsettingslist withis_default = true, pending_restart = true. It is not counted innon_default_count— it is not a chosen setting yet.The tool fetches
limit + 1and observes.truncated = page.Rows.Count > limit, the page isTake(limit), and the payload publishes three counts with their denominator in their names:settings_returned(the page),non_default_count(the snapshot),non_default_returned(the snapshot's chosen settings on THIS page — when it equalsnon_default_count, every chosen setting is here whatevertruncatedsays about the rest). The description states which denominator each uses, in the A7 vocabulary: "THE PAGE IS BOUNDED BY limit … COUNTS ARE OF THE SNAPSHOT, NOT OF THE PAGE".The three siblings fetch
limit + 1and observe. Their readers already bound the cap as a parameter, so it is the tool-side change only; every downstream consumer (the extension tool'sPgCappedRead.Classify, per-database completeness grouping, the lock tool's ungranted count) reads the trimmed page as before.What it does NOT do
extension_count/lock_count/change_countto*_returned. They are page counts under a neutral noun, nottotal_*, andserver-tabs.jsmay read them; renaming is a web-tile change outside this lane.PgCappedRead'sReturnedRows < Limit → Completetest. That classifier is documented as deliberately pessimistic ("the direction to be wrong in") and has its own two test files; a page of exactlylimitrows with nothing behind it still classifiesPartialthere. Noted, not changed.truncated = rows.Count >= limit:DarlingMcpPgDeadlockTools.cs:82,DarlingMcpPgPlanTools.cs:210(insideBuildReadinessJson, which has its own wire-shape tests),DarlingMcpPgIndexTools.cs:150(which also handsrows.Countto the coverage verdict). Each needs its own diff against its own pins; reported on Brains-review campaign: deferred structural residue (from #3538 / #3539 / #3540 / #3541) #3653 rather than folded in here.settings_in_snapshottotal. The FILTERed count is the figure the finding named; a second window count is a separate decision.Tests
McpPageContractTestsgains a Darling-only section (Lite has no PostgreSQL tools, so — like the A7 roster — no twin-parity arm):EveryPgServerStatePage_ObservesTruncation_AndNeverInfersIt— the four bodies never matchTruncationInferred, each matches an observed form and asks forlimit + 1; and the WHOLE file is swept, so a fifth paged tool added to it inherits the rule.EveryPgServerStateRead_BindsItsCapAsAParameter_NeverALiteral— the four reader consts end in a parameterisedLIMIT.PgServerConfig_CountsTheSnapshotOnTheRowStatement_AndFiltersInTheQuery— theCOUNT(*) FILTER … OVER ()column sits above theORDER BY, its predicate is theis_defaultexpression,$2::booleanand the pending-restart keep are in the statement, the body readspage.SnapshotNonDefaultCount, and no.Where(remains over the rows beyond the pending-restart name list.PgServerConfig_NamesItsDenominators_InItsDescription.McpPageContractLivePostgresTests.PgServerConfig_CountsTheSnapshotNotThePage_AgainstDevPostgres— seeds one snapshot (four chosen, two default, one session-scoped row) plus an OLDER snapshot with eight chosen settings the newest-snapshot anchor must ignore. Default view, population 4:limit = 3truncated withnon_default_count = 4(the old shape said 3),limit = 4NOT truncated (the>= limitcase).include_defaults, population 6:limit = 5truncated,limit = 6not,non_default_count = 4on both. A pending-restart default-source row: population 5, snapshot count holds at 4, the row leads the page.Executed on this Mac via the mactest harness (net10.0 xunit.v3,
AssemblyName=Darling.Tests) againsttimescale/timescaledb:2.28.1-pg18:McpPageContractTests,PgServerConfigTests,McpPageContractLivePostgresTests,McpLatestSnapshotStampTests,DarlingPgReadSqlParsesLiveTests(parse analysis of every shipped reader SQL — the$2::booleanbind is what this catches),PgCappedReadSurfaceTests,DarlingCappedReadLivePostgresTests— 92 tests, 0 failed, 0 skipped. Positive control: with the tool'struncatedreverted to>= limitthe live test fails on thelimit = 4page (Assert.False … Actual: True). Service, Storage, Viewer and Darling.Tests build-c Release -p:EnableWindowsTargeting=truewith 0 warnings.