Declare each collector's PostgreSQL extension dependency and pin the README to it - #3191
Conversation
…README to it The permissions paragraph names the collectors that need an extension installed. Nothing derived that list, so it shipped naming four of the six that have one and nothing failed. Each dependent collector now declares the extension it reads and whether installing it needs shared_preload_libraries and a restart. Darling/README.md is pinned to that declaration in both directions, and a PostgreSQL collector whose query text touches an extension-owned object has to declare it.
…ouches The README pin reports a declared extension the paragraph omits, and the obvious way to clear that is to write the extension into the prose - which lands a dependency the product does not have in the documentation, looking derived. Only extensions the object map knows are checked, so a new extension does not have to be added to the map before it can be declared.
…thing Both facts iterate SetPins(), so emptying it satisfies them vacuously - a pin that cannot fail, which is the shape this file exists to refuse.
|
Reviewed this PR (declaration of Checked in detail:
No correctness issues or parity drift found. This looks solid and unusually well self-audited (the PR description already anticipates and rebuts the two obvious alternative designs). |
…false mechanism The sentence explained the exclusion by saying preload-only modules never appear in pg_available_extensions. tools/pg-verification-rig/seed.sql creates pg_wait_sampling with CREATE EXTENSION, and PgWaitSamplingCollector reads pg_wait_sampling_profile, which only that statement creates - so the mechanism is false for the one case the sentence applied it to. The reason that holds is the one PgExtensionDependency states: the roster is a reportable-absence list rather than a list of collector dependencies, and the two are different sets. The issue references now attach to the fact that pg_wait_sampling was taken off the roster rather than to why.
|
Reviewed. This is a well-scoped, well-tested change — I traced the full contract end to end and it holds together:
Left one inline note: the paragraph correctly retires the false "preload-only modules never appear in 🤖 Generated with Claude Code |
…of the PR bodies The entry text does not only ride the PR description. #3150's own account of the previous batch says 11 of its 66 entries came from a lane's handoff file in `~/Documents/pm-issue-queue-inbox/inbox/` rather than the body, and three files there carry dedicated entry text inside this range: `3164-changelog-entry.md`, `3166-changelog-entry.md` and `3169-changelog-entry.md`. Searching only the bodies missed the first two. - **#3164** (PR #3176) is no longer written from the PR description. Its lane wrote a full entry, and that text is used verbatim: it carries the 1,036 blocks/s upper bound, the reason the decoupling #3153 deferred is unreachable, the declined command-deadline route, the census deliberately not restated at the new ceiling, and the mutation that caught nothing. `[#2997]` joins the definition block at its ascending position, which that text cites and the file did not define. - **#3166** (PR #3168, absorbed and superseded by #3178) had no entry at all. #3178 grouped it into its own citation with a one-line summary, but its lane wrote a separate entry for the census that took `HeaviestHourlyRefreshObservedCeilingSeconds` from 594 s to 896 s and inverted the watch-line ordering. It sits beside #3178's, on #3178's merge. - **#3191's entry** said "#3184 named four of the six". #3184 was squash-merged and the four-of-six state never reached `dev`; it was a commit inside that PR, corrected before merge. "shipped" in this file means merged, so the clause now says a commit in #3184, which is what the #3187 lane's own report establishes. `CHANGELOG.md` only, 60 insertions and no deletions against `origin/dev`: 33 entries (6 Added, 3 Changed, 24 Fixed) and 27 definitions. All 15 definition runs keep the ordering they had, 9 of 15 ascending before and after.
…ix merged pull requests (#3213) * Record the CHANGELOG entries for thirty-one changes across twenty-five merged pull requests Applies the `[Unreleased]` entries for the pull requests merged to `dev` from #3150 (the previous batch) through #3203. Lanes do not edit `CHANGELOG.md` -- every one of them appends to the same block, so a per-PR edit conflicts with whichever sibling merges first -- and the entry text rides each PR's own description instead. This is the periodic commit that applies it. `CHANGELOG.md` only, 56 insertions and no deletions: 31 entries prepended inside `## [Unreleased]` (6 under `### Added`, 2 under `### Changed`, 23 under `### Fixed`), newest merge first within each section, plus the 25 link-reference definitions they need appended at the foot of the file. Nothing already in the block was edited, reworded or reordered. Three of the 28 pull requests in the range earn no entry, each on measured precedent rather than judgement: #3150 is the previous batch pass itself, and no batch pass -- #2260, #2980, #3150 -- is cited anywhere in the file; #3130 and #3131 are routine Dependabot bumps, and none of the six merged Dependabot pull requests in the repository's history is cited either. Two pull requests carried no entry text anywhere, so theirs are written from their descriptions: #3176 (`pg_index_bloat`'s measured block rate) and #3184 (the PostgreSQL target's permissions section). * Record #3205's entry, which arrived on the merged base mid-batch #3205 merged while this batch was assembled, so `origin/dev` was merged in rather than rebased and its entry lands on top. An entry omitted because it arrived mid-batch is how a backlog restarts, and the batch's purpose is to make `[Unreleased]` current at its own merged base. One entry under `### Changed`, newest merge first ahead of #3195's, plus its link-reference definition. #3205 has no other pull request in the range behind it. * Take #3164's and #3166's entries from the lane handoff files instead of the PR bodies The entry text does not only ride the PR description. #3150's own account of the previous batch says 11 of its 66 entries came from a lane's handoff file in `~/Documents/pm-issue-queue-inbox/inbox/` rather than the body, and three files there carry dedicated entry text inside this range: `3164-changelog-entry.md`, `3166-changelog-entry.md` and `3169-changelog-entry.md`. Searching only the bodies missed the first two. - **#3164** (PR #3176) is no longer written from the PR description. Its lane wrote a full entry, and that text is used verbatim: it carries the 1,036 blocks/s upper bound, the reason the decoupling #3153 deferred is unreachable, the declined command-deadline route, the census deliberately not restated at the new ceiling, and the mutation that caught nothing. `[#2997]` joins the definition block at its ascending position, which that text cites and the file did not define. - **#3166** (PR #3168, absorbed and superseded by #3178) had no entry at all. #3178 grouped it into its own citation with a one-line summary, but its lane wrote a separate entry for the census that took `HeaviestHourlyRefreshObservedCeilingSeconds` from 594 s to 896 s and inverted the watch-line ordering. It sits beside #3178's, on #3178's merge. - **#3191's entry** said "#3184 named four of the six". #3184 was squash-merged and the four-of-six state never reached `dev`; it was a commit inside that PR, corrected before merge. "shipped" in this file means merged, so the clause now says a commit in #3184, which is what the #3187 lane's own report establishes. `CHANGELOG.md` only, 60 insertions and no deletions against `origin/dev`: 33 entries (6 Added, 3 Changed, 24 Fixed) and 27 definitions. All 15 definition runs keep the ordering they had, 9 of 15 ascending before and after. * Use the staged #3184 entry text instead of one written from the PR description #3184's entry now exists, staged by the lane that did the work, so the batch no longer invents one. Used verbatim, in `### Fixed` where its author targeted it and for the reason they gave: the section did not merely lack detail, it asserted "One role covers every collector", which is false in the direction that fails silently and which an operator would act on. Their text carries what a description-derived entry could not: the `pg_read_all_data` grant is PostgreSQL 14+ and the pre-14 fallback the section gave was `GRANT SELECT` on a schema, which is not valid PostgreSQL at all; the six collectors that need an extension, four of which additionally need `shared_preload_libraries` and a server restart; and why `pg_index_bloat` is unaffected by any grant. Both figures are live pre-change store reads rather than estimates. The citation is `[#3184]`, the pull request, because the change has no issue behind it, and its definition uses the `/pull/` form. `CHANGELOG.md` only, still 60 insertions and no deletions against `origin/dev`.
…SSIONS (erikdarlingdata#3240) On a PostgreSQL target without optional extensions installed, the collectors that read pg_buffercache, pg_stat_kcache, pg_qualstats, and pg_wait_sampling failed every cycle with SQLSTATE 42P01/42883 and were recorded PERMISSIONS, so Collection Health banded them NO_PERMISSIONS under the hint that pg_monitor covers every collector. A grant fixes none of them; installing the extension would. The classification now consults the collector's DECLARED extension dependency (ICollectorSchemaInfo.RequiredPgExtensions, erikdarlingdata#3191): a missing-object fault on a declaring collector records EXTENSION_MISSING, with a sentence that names the extension, the exact CREATE EXTENSION and the database to run it in, and the shared_preload_libraries restart where the declaration says installing costs one. All five banding reads count the new status, the shared classifier bands it EXTENSION_MISSING ahead of NO_PERMISSIONS (so a window straddling the upgrade lands on the newer name), the band joins NothingReadBands, and the MCP precondition vocabulary carries the new status so an extension-missing read still explains itself. Genuine 42501 denials keep PERMISSIONS, and an undeclared missing object keeps the generic non-fatal skip. pg_index_bloat was in the issue's field table and is deliberately not routed: erikdarlingdata#3235 removed its pgstattuple dependency, so its 42883 cannot occur, and the derived-set pin flips it automatically if it ever declares again. Fixes erikdarlingdata#3240 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AtjWJJ3NnSkDSrPBsGywoZ
Refs #3187.
Darling/README.md's permissions paragraph names the collectors that need a PostgreSQL extension installed. Nothing derived that list, and the concrete cost of that is checkable: #3184's commitbe617669c("Name the four collectors that need an extension") named four of the six, and all eight checks passed on it.a9a2e6c2fcorrected it, because a reviewer read the paragraph — not because anything failed. The squash merge meansdevnever carried the short list, but nothing automated would have stopped it.Each of the six now declares its dependency, and the paragraph is pinned to the declaration.
The authority is a new declaration, not the availability roster
The issue proposed pinning the paragraph to
PgExtensionAvailabilityCollector'sVALUESroster. That roster is the wrong set, in both directions at once. It carries eight entries —hypopg,pg_trgmandpg_cronare on it and no collector reads any of them — and it deliberately omitspg_wait_sampling, which a collector does read. Its own comment says what it is for: it exists ONLY so that absence is reportable. Every consumer ofis_monitoring_relevanttreats it as exactly that and none treats it as a dependency oracle — it orders the reader's result set, rides throughget_pg_extensionsas a reported field, and counts the Viewer's actionable and outdated extensions (ViewerServerTab.Postgres.cs:210-211), which is advice about what an operator could install rather than a statement of what a collector requires. Reportable-absence and collector-dependency overlap; they are not the same question.No structured dependency declaration existed to pin to —
RequiredExtension,RequiresExtension,ExtensionDependencyandRequiredPgExtensionall returned zero matches repo-wide, andCollectorRuntimePreconditiondeliberately carries no extension names ("the remedy text is FRAMED here, not authored here"). So this adds one.ICollectorSchemaInfo.RequiredPgExtensionsdeclares the extension a collector's query cannot run without, plusPgExtensionInstallKind—CreateExtensionfor a plain statement,SharedPreloadLibrariesfor the ones that need a server restart first. It sits on the non-generic surface besideAppliesToandStateKeysso the catalog can enumerate it without the row type, as a default interface member so the test doubles that implement the interface directly need no change.The dependency set was derived from each PostgreSQL definition's own query text rather than inherited: six collectors read an extension-owned object, and three others name one in order to REPORT on it (
pg_extension_availability's roster,pg_table_bloat_stats'pgstattuple_availableflag,pg_plan_capture_readiness' GUC probes). The install kinds match the verification rig's ownshared_preload_librariesline exactly.Two axes are deliberately absent from the declaration because they are already derivable. Whether an extension is needed in every database is
RunsPerDatabase, which is true for exactlypg_index_bloatandpg_predicate_stats— the two the paragraph calls per-database. Andpg_stat_kcache's dependency onpg_stat_statementsis a property of that extension rather than of the collector, so only the extension whose objects the query reads is declared.What the pins hold
ReadmeDerivedCountPinTestsgains a set-pin family alongside its numeric one, compared in both directions — missing catches the defectbe617669ccarried, extra catches its mirror image, a paragraph claiming a dependency nothing has. Three spans are pinned: the collector/extension list as pairs, theshared_preload_librariesset, and the per-database set. Each asserts its pattern matched before comparing, per the file's existing rule, and the new extractions are registered inEveryPin_ReportsAnInjectedDrift— mutated once per MEMBER rather than once per pin, so every name in each sentence is individually shown to be load-bearing. One mutation per pin would prove only that the sentence is read at all, which is not the property that failed.PgExtensionDependencyContractTestsholds what the README pin cannot. The README pin fires when the declaration and the paragraph disagree; it says nothing when a new collector reads an extension and never declares it, because both sides then stay consistent and both stay wrong. So a PostgreSQL collector whose query text touches an extension-owned object has to declare it, the declaration has to be well-formed and PostgreSQL-only, and the verification rig has to be able to load and create everything declared.The reverse direction is checked too, because of what the README pin would otherwise do with a false declaration: it reports the extension as missing from the paragraph, and the obvious way to clear that is to write the extension into the prose — which lands a dependency the product does not have in the documentation, looking derived. So a declared extension whose objects the collector's query never touches fails. Only extensions the object map knows are checked, so declaring a new extension does not first require extending the map; needing to extend it must not become a reason to weaken it.
Three word-numerals over the same set, deleted
The paragraph carried "Six collectors need something installed", "All six degrade identically" and "light up the five that are true extensions" — three uncounted numerals over the set this pin makes authoritative. Deleting a number from prose is the other sanctioned outcome under #3072, and leaving them would have meant shipping a pin that guards the list while three numerals about that same list stayed unguarded.
the five that are true extensionsbecameevery one of them except pg_wait_sampling, which is the same fact stated so it cannot go stale by arithmetic.Scope: this closes the automatable gap, it does not prevent the class
Of the five defects found reviewing #3184, a pin catches one — the missing collectors. The other four were a grant/extension category error, an invalid
GRANT SELECTon a schema, an over-broad claim about the availability roster, and a stale prose count, and every one of them needed a reviewer. Nothing here should be read as making that paragraph safe; it makes exactly one of its claims derived.The install-kind correction is worth calling out as a defect this pin did not catch and would not have. The paragraph named
shared_preload_librariesforpg_stat_statementsandpg_wait_samplingonly, whileDarlingMcpPgKernelStatsToolsandDarlingMcpPgPredicateToolsboth say in shipped operator-facing text thatpg_stat_kcacheandpg_qualstatsneed it too — and the rig'sshared_preload_libraries=pg_stat_statements,pg_wait_sampling,pg_stat_kcache,pg_qualstats,auto_explainsettles it. An operator following the paragraph would have createdpg_stat_kcacheand watched the collector store nothing. That is now stated and pinned, but it was found by reading, not by a test.pg_wait_samplingis not a preload-only module, and the paragraph asserted it twice.tools/pg-verification-rig/seed.sqlrunsCREATE EXTENSION IF NOT EXISTS pg_wait_sampling;, andPgWaitSamplingCollectorreadspg_wait_sampling_profile— a function only that statement creates.auto_explaingenuinely owns no SQL object; the two were lumped together and onlyauto_explainfits.Both assertions are gone from this paragraph. The install-kind sentence no longer says
pg_wait_samplingneeds a preload "rather than aCREATE EXTENSION" — that clause is deleted, and the replacement claims only that a restart is required, which the rig confirms. And the roster-exclusion sentence no longer explains the exclusion by saying preload-only modules never appear inpg_available_extensions: that mechanism is false for the one case it was applied to, so it is replaced by the reasonPgExtensionDependencystates twenty lines away — the roster is a reportable-absence list rather than a list of collector dependencies, and the two are different sets. The issue references now attach to the fact thatpg_wait_samplingwas taken off the roster rather than to why.PgExtensionAvailabilityCollector's own type header and the V89 migration row still carry the preload-only characterization and are left alone: the exclusion rationale living next to it is pinned inLite.Tests/PgExtensionAvailabilityCollectorDefinitionTests.cs, and whether the roster should excludepg_wait_samplingat all is a separate question from whether this paragraph may state a false reason for it.CHANGELOG entry text
Darling/README.md's permissions paragraph named the collectors that need an extension installed, and nothing derived that list - Document the PostgreSQL grant and extensions that decide whether a collector returns anything #3184'sbe617669cnamed four of the six and all eight checks passed on it, corrected only because a reviewer read the paragraph. An enumeration in prose is only better than a count if something breaks when it is wrong.ICollectorSchemaInfo.RequiredPgExtensionsnow carries the extension each collector cannot run without plus what installing it costs -CreateExtensionfor a statement,SharedPreloadLibrariesfor the ones needing a server restart first - and the paragraph is pinned to it in BOTH directions, so a dependency the product has and the documentation omits fails the build, and so does a paragraph claiming one nothing declares. The nearby list that looks like the authority is not it:PgExtensionAvailabilityCollector's roster carries eight entries because it exists so ABSENCE is reportable, three of which no collector reads, and it deliberately omitspg_wait_sampling, which one does. Two axes stay derived rather than declared twice - per-database isRunsPerDatabase, andpg_stat_kcachesitting onpg_stat_statementsis a property of that extension. A second pin closes the case the README pin cannot see, where a new collector reads an extension and never declares it: a PostgreSQL definition whose query text touches an extension-owned object has to declare it, and the verification rig has to be able to load and create everything declared. The paragraph's three word-numerals over the same set are deleted rather than pinned, the Darling/README.md says twelve PostgreSQL read tools and there are thirty-two, which is the third hand-maintained count found stale tonight #3072 alternative. Two corrections found by reading rather than by a pin:pg_stat_kcacheandpg_qualstatsneedshared_preload_librariestoo - which the MCP tools already told operators and the rig's own preload line confirms - so following the paragraph meant creating the extension and watching the collector store nothing. And the paragraph explainedpg_extension_availability's exclusion ofpg_wait_samplingby saying preload-only modules never appear inpg_available_extensions, which is false for that one case: the rig'sseed.sqlcreates it withCREATE EXTENSIONand the collector reads a function only that statement creates. The exclusion now carries the set-membership reason instead - reportable-absence and collector-dependency are different sets - which is the same reason the new type states.