Repository navigation
Three PostgreSQL pages observe truncation off limit + 1 through one shared helper, the census sweeps the inference class instead of a roster, and four payload rules become facts and inventories, so three tools stop guessing truncation from a full page and four of the eleven payload rules stop being sentences (partial #3653) - #3699
Merged
Merged
Conversation
Closed
39 tasks done
…hared helper, the census sweeps the inference class instead of a roster, and four payload rules become facts and inventories (partial #3653) get_pg_deadlocks, get_pg_plan_capture_readiness and get_pg_index_bloat published truncated = rows.Count >= limit, the #3594 inference; the readiness and index tools withheld their summaries on the strength of it for pages that were the whole set, and get_pg_plans beside them Take(limit)'d a read already cut at limit and said nothing. McpHelpers.BoundPage turns (fetched at limit + 1, limit) into (page, truncated) in one place; the four tools are its first consumers and every count downstream is still a count of the page. McpPageContractTests missed the three because EveryPagedTool_ObservesTruncation walks the ten tools #3541 A3 named and its #3653 sibling walks one file - a roster gap, not a regex gap; the matcher catches both spellings and now witnesses the object-initializer one. NoTool_InfersTruncationFromItsCap_OnEitherSku sweeps every tool body on both SKUs with a population floor and fails on the three at dev. McpPayloadContractCensusTests pins the four unpinned rules as far as a census can honestly go: every catch returns through a shared shape (one ad-hoc site rostered) and the two shared shapes - a sentence and a JSON envelope - are an inventory for the vocabulary lane; every bounded parameter reaches a shared validator or one of five inline days_back refusals, and nothing clamps a parameter; every total_* assigned from a Count names a whole set (rostered), every latest-anchored reader projects its anchor (two known exceptions); truncation-key dialects, severity-word spellings and statement-terminal literal LIMITs are exact inventories. The DarlingMcpPgPlanToolsTests pin that handed the builder two rows at limit 2 and expected truncated encoded the inference as its own proof and becomes the boundary pair. Executed here through the built Darling.Tests.dll and against PostgreSQL 18.4 / TimescaleDB 2.28.1; positive controls: the three files at dev state fail the sweep by name, and BoundPage flipped to >= fails the live boundary, the builder pins and the helper theory.
erikdarlingdata
force-pushed
the
fix/3653-payload-census
branch
from
September 19, 2026 06:33
8ab6650 to
177a0b7
Compare
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
This was referenced Oct 2, 2026
This was referenced Oct 11, 2026
Manage Servers shows the service's version, not the viewer's, as the installed version (#5655)
#5668
Merged
Merged
Merged
This was referenced Oct 12, 2026
Open
Open
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 "11-rule payload contract as census" line, from #3541's checklist, and the three
>= limitresidue sites PR #3679 named). Does not close it.The lie
Three PostgreSQL tools still guessed truncation from a full page, and four of the eleven payload rules were sentences, not tests.
get_pg_deadlocks,get_pg_plan_capture_readinessandget_pg_index_bloateach publishedtruncated = rows.Count >= limit— the inference #3594 named and every other honest page in the repository had replaced. A page that is exactlylimitlong cannot tell "that was all of them" from "there was at least one more", and the flag said more either way. Two of the three made it worse by acting on the flag: the readiness tool WITHHOLDSunsatisfied_facetswhen truncated, and the collector emits exactly six facets, so a caller asking forlimit = 6— the whole set — was told the page was cut and had the one figure the tool exists to produce withheld. The index tool withholdsanswered_count/estimated_count/exactly_measured_countthe same way, for a candidate census that happened to be exactlylimitlong. Beside the readiness site,get_pg_planshad the quieter cousin: aTake(limit)over a read already cut atlimit, and no word on the wire about the cut at all.The census that pins this rule —
McpPageContractTests.EveryPagedTool_ObservesTruncation_AndNeverInfersIt_OnEitherSku— was green the whole time. Why it missed them is the first thing this PR answers: it walksPagedTools, the ten tools #3541 A3 named, and its #3653 sibling walks one file (DarlingMcpPgServerStateTools). The three tools were in neither roster, so their bodies were never read. The regex was never the gap:TruncationInferredmatches both spellings they used, the local (var truncated = rows.Count >= limit;) and the object-initializer member (truncated = rows.Count >= limit,), and the discriminator test now witnesses both against the shipped lines. A roster is the right instrument for the POSITIVE contract (which tools must observe, bind and publish their bounds — it names what a paged tool is) and the wrong instrument for the anti-pattern, which is a defect wherever it appears.The fix
One helper, four first consumers.
McpHelpers.BoundPage<T>(fetched, limit)is the one place(rows fetched at limit + 1, limit)becomes(page, truncated):truncated = fetched.Count > limit, the page is the firstlimitrows in the reader's order (the same list when nothing was cut), and every count a caller takes off the page is a count of the page. The four tools fetchlimit + 1and bind through it. On the index tool the page is bound BEFORE the empty branch, the coverage verdict and the reach classifier, sorows.Countkeeps meaning what it meant at every downstream site;PgCappedRead.Classifystill receives the page count and the cap, and its documented-pessimisticReturnedRows < Limit → Completetest is not this change's to soften. The two plan tools' wire-shape builders (BuildPlansJson,BuildReadinessJson) receive thelimit + 1fetch and bind inside, so their existing wire-shape tests keep exercising the real projection.get_pg_plansnow publishestruncatedand a note that says the window holds more shapes, ranked below these by total duration, and thatquery_idpins one statement wherever it ranks. Everylimitparameter description says what it bounds and names the flag, in #3679's wording. The readers were already parameterised (LIMIT $4/$5/$4/$4), so no Storage file changes.The census now catches the class.
NoTool_InfersTruncationFromItsCap_OnEitherSkusweeps every tool body on both SKUs (244 methods; floor 150, the same population control the A13Math.Abssweep uses) and the whole file below the last tool, where two of the three lived. Run against the three files at their dev state it fails naming exactlyget_pg_deadlocks,get_pg_index_bloat,get_pg_plan_capture_readiness— the positive control. It does NOT flaglimit_reached = x.Count >= limit(five PostgreSQL tools): that key says the page is as long as the cap, which is true by construction, and its prose says "more MAY exist" — a weaker dialect rather than an inference, and whether it collapses into an observedtruncatedis the vocabulary lane's call. It is held in the truncation-key inventory instead, and the discriminator test witnesses the non-match. A Darling-only roster (PgPagedToolsThroughTheHelper) pins the four first consumers:limit + 1in the body,McpHelpers.BoundPagein the body or the named builder,truncatedpublished, the reader's cap parameterised, the description naming the bound. The census accepts EITHER the helper OR the proven inline spelling (var truncated = x.Count > limit;) and says so — forty-odd inline sites are not swept onto the helper tonight, other lanes hold those files.The pin that encoded the lie.
DarlingMcpPgPlanToolsTests.ACappedResult_WithholdsTheUnsatisfiedSummary_RatherThanDescribingThePagehanded the builder two facets atlimit = 2and assertedtruncated— the>= limitinference as its own proof. It now hands three (a truncated page of two, third row the signal) and a new fact asserts the other boundary: two atlimit = 2is complete, andunsatisfied_facetsis an empty ARRAY, not null — absent-because-withheld and absent-because-none are different answers. This is the one test file outside the brief's list I touched; the pin was falsified by the fix and a load-bearing pin is a granted boundary.The four rules, as census
McpPayloadContractCensusTests(new; one region per rule, each fact stating its population, its floor and its roster; rosters are EQUALITIES so a hit that vanishes without leaving the roster is as red as a new one):catchin every tool method returns through a SHARED shape":McpHelpers.FormatError(directly or viaTask.FromResulton the synchronous tools),McpHelpers.Status("error", …), the write tools'Outcome("invalid", …), or a gate computed above the catch. 254 catch returns swept; one ad-hoc site rostered shrink-only (list_serversreturns an interpolated sentence — another lane's file). The rule itself is not met: the two shared shapes are a bare STRING and a JSON envelope, executed side by side inTheTwoSharedErrorShapes_AreASentenceAndAnEnvelope— 200-odd tools answer an exception with prose an agent keyed onstatusreads as successful text, 30-odd with{"status":"error"}. The split is pinned as a file-grain inventory (21 files return the envelope: every PostgreSQL tool file plus the collector-cost tool; every Lite file and every SQL Server-family Darling file returns the sentence) for the vocabulary lane to collapse.hours_back/limit/top/days_back/as_of(156 methods) reaches aMcpHelpersvalidator in its own span or a same-file helper it calls (the nine health-parser tools shareCollectAsync), or is one of five rostered inlinedays_backrefusals (noValidateDaysBackexists; they refuse, they do not clamp — three with a bare sentence, the errors split one layer down). No tool clamps a bounded parameter against a ceiling or literal — zero hits, so the fact carries the population floor and a witness that the matcher fires onMath.Clamp(limit, 1, MaxTop)/Math.Min(hours_back, 168)/top = Math.Max(1, top)and passes the page-count idiomMath.Min(rows.Count, limit). TheMath.Absarm stays where 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 put it. The timestamp arm: no tool source parses a timestamp under the host culture; the two general (non-Exact)DateTime.TryParsecalls that remain (create_mute_rule'sexpires_at,update_mute_rule'sBuildMuteRuleUpdate) carryInvariantCulture+AdjustToUniversal | AssumeUniversaland are rostered shrink-only — they accept every shape the framework accepts rather than theAsOfFormatsallowlistResolveAsOfrefuses against, and a control shows the allowlist refusing08/18/2026 14:30while the general parser under the same styles accepts it.total_*assigned from a.Count/.Lengthin a tool body (10 sites, both SKUs) is rostered with the WHOLE set it counts (get_mute_rules' every rule, the default-trace and health-parser tools' every event in an uncapped window,get_analysis_facts' every collected fact) — a new one must prove its set is whole or rename*_returned. Every reader constant whose OUTER statement anchors on= (SELECT MAX(collection_time | capture_time) …)projects the anchor column so a stamp is possible (15 anchored constants on both SKUs; a depth-zero walker finds the owning SELECT list; anchors inside a CTE or subquery pick a SET and are not snapshots) — two rostered exceptions, both known:IndexUsageSql(the A10 object-stats residualMcpLatestSnapshotStampTests.UnstampedLatestReadsPendingA10already holds) andIndexUsageMatchCountSql(aCOUNT(*)with no row to stamp).truncated,shown,limit_reached,scan_truncated,truncation_note,is_partial,partial_count,history_capped,capture_was_truncated,chain_may_be_truncated,chain_truncation_note,query_text_may_be_truncated,answered_rows_withheld,created_rows_withheld) — the doc says which are dialects of one fact and which are different facts. Severity words: 8 spellings in three casings (Healthy/Warning/Critical/Unknown,warning/unknown,CRITICAL/HEALTHY) with the files that spell each; theNoData/No Datapair is pinned by its named instance —get_daily_summaryon both SKUs publisheshealth_band = HealthBand.ToString()besideoverall_health = DailyHealthBandCalculator.Label(…), the same band under two spellings in one object, executed against the shared calculator. Statement-terminal literalLIMIT n(n > 1): seven, all in Lite's service layer, none behind a tool that takeslimit(six viewer-only reads;GetPlanCacheSnapshotAsync'sLIMIT 30is a ceiling over a dozen cache types behind a tool with no cap) — zero on Darling.DarlingMcpServerAdminToolsSurfaceTests.EveryAddStatus_HasExactlyOneSummaryCounter_AndNoCounterIsUnnamed,Aggregate_CountsEveryResultOnce_AndTheCountersSumToRequested); a cross-write census is deferred until the envelope lane's mute-write rewrite lands tonight, and the file says so.Files whose names carry
ForcePlanare outside every sweep by standing decision, not by allowance.What it does NOT do
deadlock_count/facet_count/plan_shapes/index_countto*_returned. They are page counts under a neutral noun, nottotal_*; the vocabulary lane owns renames.var truncated = x.Count > limit;sites ontoBoundPage. Both spellings are the observation; the census accepts both.limit_reached(five tools) orhistory_capped(runs.Count >= RunsReadPerTable, the same shape against a collector constant). Both are inventoried, not flagged.PgCappedRead.Classify's pessimisticCompletetest,CHANGELOG.md, any otherMcp/*Tools.cs,server-tabs.js,DarlingWebEndpoints.cs, or anyPgTarget*file.Tests
All executed on this Mac through the built
Darling.Tests.dll(xunit v3 in-process runner,-p:EnableWindowsTargeting=true, WindowsDesktop framework entry stripped from the runtimeconfig), then first executed in CI:McpPayloadContractCensusTests— 23 facts, 23 green. Every roster above was first written from a survey, then run, and the census corrected the survey in three places (a fifth inlinedays_backrefusal inget_collector_stall_probes; five truncation keys emitted from builders below the last tool; three UPPER-case severity spellings in the Darling service's own readers).McpPageContractTests— 40 green (+NoTool_InfersTruncationFromItsCap_OnEitherSku,EveryPgPagedToolThroughTheHelper_…,EveryPgPagedReadThroughTheHelper_BindsItsCapAsAParameter_NeverALiteral, a seven-rowBoundPagetheory, the widened discriminator witness). Positive control: with the three tool files at dev state on disk the sweep fails naming exactly the three sites.McpPageContractLivePostgresTests.PgPagedTools_FlipTruncatedExactlyAtTheBoundary_AgainstDevPostgres— againsttimescale/timescaledb:2.28.1-pg18(PostgreSQL 18.4) through the real tool methods: five distinct deadlock reports (one seen twice) atlimit = 4truncated /5complete; six readiness facets at5truncated withunsatisfied_facetsnull /6complete with the three unsatisfied named in causal order; four plan shapes at3truncated with the note /4complete; three answered indexes at2truncated withanswered_countwithheld /3complete withanswered_count = 3, coverage'scandidate_index_count3 on both pages. Positive control: withBoundPageflipped to>=the live test fails on the firstlimit = Narm, the two new builder pins fail, and two of the seven theory rows fail.DarlingMcpPgPlanToolsTests— 14 green (the reshaped pin plus two boundary facts).DarlingCappedReadLivePostgresTests(6),DarlingPgIndexBloatNanLivePostgresTests(2),DarlingPgIndexBloatCoverageLivePostgresTests,DarlingPgIndexBloatRotationReadLivePostgresTests,DarlingMcpPgPlanQueryIdLiveTests,DarlingPgReadSqlParsesLiveTests— the pre-existing suites that call the changed tools against live Postgres — all green through thelimit + 1fetch.PgCappedReadSurfaceTests(12),PgIndexBloatCoverageTests(16),PgLoggingCollectorOffTests,RepoFileAdoptionTests,CommentFilterAdoptionTests,McpLatestSnapshotStampTests(30),AsOfWindowAnchorTests(29) — green.PerformanceMonitor.Common,Darling.Storage,Darling.Service,Darling.Viewer,Darling.Tests,PerformanceMonitorLite,Lite.Tests,deprecated/Dashboard.Testsbuild-c Release -p:EnableWindowsTargeting=truewith 0 warnings from this change (Lite.Tests carries one pre-existing xUnit2000 on dev atLogTailOverlapThresholdPinTests.cs:95).Which component(s) does this affect?
PerformanceMonitor.Common/Mcp/McpHelpers.csis shared, compiled into both SKUs, and gains one helper nothing on Lite calls yet)Checklist
-c Release -p:EnableWindowsTargeting=true, every touched project and every consumer ofPerformanceMonitor.Common)