Skip to content

audit_config answers a PostgreSQL target with its own settings: the CONFIG_PG_* facts projected into the same recommendations shape, engine in place of edition, and the two checkpointing knobs marked not applicable on Aurora (#3691 line 70) - #3867

Merged
erikdarlingdata merged 5 commits into
devfrom
feat/3691-audit-config-pg-projection
Sep 22, 2026

Conversation

@erikdarlingdata

Copy link
Copy Markdown
Owner

What a PostgreSQL operator now learns

audit_config on a PostgreSQL target used to answer an honest refusal: this tool evaluates SQL Server settings, go call get_analysis_facts with source pg_config. True, and useless — the caller had already asked the right question of the right server, and was sent to a different tool to ask it again. This lane projects the analysis pass's CONFIG_PG_* facts into the same recommendations shape the SQL Server arm returns, so one call answers "how is this server configured, and what about it is worth looking at."

PARTIAL for #3691 — do not close. The checklist line completed, verbatim:

audit_config for a PostgreSQL target answers the honest not_collected envelope (plumbing); a CONFIG_PG_* projection into the same tool is a description-contract change — decide.

Erik's ruling (2026-09-22 02:23Z): YES, with two riders, both landed here:

  1. The projection inherits the calibrated Aurora honesty. CONFIG_PG_CHECKPOINT_TIMEOUT and CONFIG_PG_MAX_WAL_SIZE render status = not_applicable on aurora-postgres, counted on their own summary line and excluded from settings_checked — a knob the engine does not consult was not checked, and folding it into that count would have the audit overstate what it looked at. Neither is ever a finding.
  2. The residual edition-awareness drift is fixed in the same lane, so the description contract changes ONCE. The Brains-review campaign: deferred structural residue (from #3538 / #3539 / #3540 / #3541) #3653 description fix had already landed; what remained were three doc comments still claiming edition-awareness (PgFactCollector.Resources.cs, Lite's DuckDbFactCollector.Resources.cs, AnalysisSchema.cs v2) — all three now say resource-based / the server-metadata columns the config audit reads, which is what those facts and columns actually enable.

The description, before and after

The first four sentences are unchanged (including "NO check branches on it"). Only the trailing sentence moved:

Before: SQL Server only: for a PostgreSQL target this tool refuses with status not_collected and points at get_pg_server_config, get_pg_logging_audit and the CONFIG_PG_* facts instead of pretending the collector failed.

After: For a PostgreSQL target it projects the analysis pass's CONFIG_PG_* setting facts into the same recommendations shape (setting, current_value with unit, status, recommendation; no suggested_value - the recommendation sentence carries the evidence), reports engine in place of edition, and renders checkpoint_timeout / max_wal_size as not_applicable on Aurora, where the engine manages checkpointing. get_analysis_facts has each setting's full investigation; get_pg_server_config has the raw snapshot.

The instruction table row (DarlingMcpInstructions.cs:99) and the step-7 mention (:340) were updated to match in one sentence each.

What the payload says, and why each difference from the SQL Server arm exists

Every difference is a difference of the engine, and each is stated in the description so the contract changes once:

  • engine in place of edition — PostgreSQL ships no editions. The token is the registry's (postgres / aurora-postgres), resolved per call off servers.engine_kind through MonitoredEngineKind, never inferred from a column's presence (PostgreSQL targets have no UI on either SKU, and both UIs render SQL Server tabs at them #2530). edition is absent, not null: a PostgreSQL payload carrying "edition": null would be the same false edition-awareness Brains-review campaign: deferred structural residue (from #3538 / #3539 / #3540 / #3541) #3653 took out of the description.
  • current_value is a STRING with its unit — the SQL Server arm's int cannot carry random_page_cost 1.1, shared_buffers 128 MB, checkpoint_timeout 5 min or track_io_timing on. Formatted off the FACT KEY, never sniffed from the magnitude (4 could be a cost ratio or four megabytes; 300 could be seconds or connections), in the same shapes PgTargetAdvice's private Knob* formatters render inside an advice sentence — so one value does not read two ways across two surfaces.
  • No suggested_value, at any depth — the right value for a PostgreSQL knob is host- and workload-specific. The advice's remediation sentence carries the evidence and the counter-objective a bare number would strip, which is precisely what the evidence-first discipline forbids shipping. recommendation is the advice headline + remediation; investigation stays in get_analysis_facts, and the description says so.
  • threshold_lineage is null, not 0 — the config family stamps no lineage because its bars ARE PostgreSQL's own shipped defaults; there is no chosen number to disclose (PgTargetScorer.Config says exactly this in its class summary). 0 would claim a bar had been chosen unmeasured.
  • object_name is OMITTED on a server-level row, present only where the fact carries one (CONFIG_PG_AUTOVACUUM_DISABLED is a per-table reloption). A null table name on a shared_buffers row invites a reader to wonder which table it meant.

The tool grades nothing: severity is the pass's, read off the fact, and this arm only spells it ok / review / warning / not_applicable. Facts are filtered by KEY PREFIX rather than source — the family lives on pg_config, pg_memory (CONFIG_PG_MEMORY_OVERCOMMIT) and pg_vacuum (CONFIG_PG_AUTOVACUUM_DISABLED), so a source filter would silently drop the two checks that are not settings snapshots — and iterated as an ordered LIST, because the reloption check repeats its key per table. Zero facts keeps its own no_config_data envelope naming the health read: no facts is not a refusal.

PgTargetFactKeys.ConfigPgSettingName maps every declared CONFIG_PG_* key to the pg_settings row an operator would go set, beside the constants so a renamed key fails to compile in both places at once. The three that are not one setting are spelled so a reader cannot mistake them for a row they could go read: shared_preload_libraries (pg_stat_statements), shared_buffers + work_mem × max_connections (composite), autovacuum_enabled (table reloption). A bare autovacuum on the reloption row would send an operator to ALTER SYSTEM for something only ALTER TABLE can reach.

Rider 1's second knob

The calibration stamped not_applicable on CONFIG_PG_MAX_WAL_SIZE (lane 15, #3728 §A4) but not on CONFIG_PG_CHECKPOINT_TIMEOUT — correctly, because that fact is base-0 context there and nothing was grading it. It is graded by nothing here either, but this tool renders it as a setting with a recommendation, and the composed context sentence says tune it with max_wal_size, which on Aurora points an operator at a knob the storage layer ignores. Rider 1 names both keys for exactly that reason, so the engine-side arm lives in this tool with the reason written down. Stamping the fact itself (so get_analysis_facts said it too) is the collector's change and lane 15's file — reported, not folded.

Byte-identity

  • The SQL Server arm is untouched. Its int current_value, its suggested_value, its edition = editionName echo and every check are byte-identical; the projection row is a separate PgConfigRecommendation record rather than a widening of ConfigRecommendation, so nothing about the new shape can reach it. DarlingMcpToolsTests's existing audit_config body-scan assertions (the only edition reads are the fact lookup and the payload echo) run unchanged.
  • Lite is untouched — its audit_config is SQL Server-only by product (no PostgreSQL targets). Worth stating precisely: Lite's description never carried the trailing refusal sentence at all, so there was nothing there to leave in place. Its body and description are unmodified; only the two doc comments under rider 2 changed in that tree.

Tests

Class What it holds Executed
PgTargetAuditConfigTests (new, 2 tests) LIVE, both arms. Planted pg_server_config snapshot → AuditConfig → JSON read: engine present / edition absent, one row per fact, unit-carrying string values (128 MB, 1 GB, 8 GB, 1.1, on, 5 min), statuses off the pass's severities, no suggested_value anywhere in the payload text, threshold_lineage null, object_name omitted, summary arithmetic. Aurora arm: both checkpointing knobs not_applicable with lane 15's verbatim sentence and the engine-side sentence, out of settings_checked, shared_buffers still graded ✅ live, green
PgTargetSharedSwitchRoutingTests (+1 test) Setting-name census, reflected off the constants: every declared CONFIG_PG_* returns non-null, lower-case, and the three composites say what they are; unknown key and null → null ✅ 74 green
PgTargetMcpSurfaceTests (reworked) The arm's STRUCTURE: engine read → fact read → StartsWith(PgTargetFactKeys.ConfigPrefix (the CONSTANT, not the literal), no source filter, and not_collected / get_pg_logging_audit gone from the whole tool body ✅ green
DarlingMcpToolsTests (renamed) …_AndNamesThePostgresProjection: the four stated differences in the tool's own words, plus the refusal no longer promised; "NO check branches" and the body-scan edition assertions unchanged ✅ 16 green
McpPayloadContractCensusTests not_applicable added to audit_config's status-vocabulary roster line with its reason ✅ 67 green
TsqlConventionGuardTests The projection row's expression-bodied ToPayload registered in KnownTruncatedRanges with what falls outside the range and why no census reads it there ✅ 14 green
DocCommentHygieneTests / RepoFileAdoptionTests / ServerPageTabsTests / ViewTemplatesTests / PgTargetKnobsTests Pre-checked, unmoved ✅ 77 / 2 / 19 / 7 / green

Harness: net10.0, AssemblyName=Darling.Tests, inside the worktree, against a throwaway timescale/timescaledb:2.28.1-pg18 (Docker context desktop-linux); torn down, harness deleted before commit. The full Darling.Tests project builds with EnableWindowsTargeting=true (no new warnings).

Self-review — three riskiest hunks

  1. The engine-side not_applicable arm (DarlingMcpTools.cs) — it makes this tool say something the FACT does not, which is the shape of an inconsistency between two surfaces. Held by the live Aurora arm asserting the appended sentence's exact text and the stock arm asserting checkpoint_timeout reads ok there; the comment states that the fact-side stamp is lane 15's file, not a fold-in.
  2. FormatConfigPgValue's two inversions — CONFIG_PG_AUTOVACUUM_OFF is a finding FLAG (value 1 = the setting is off), and CONFIG_PG_AUTOVACUUM_DISABLED's value is the table's backlog ratio, not its reloption. Both are documented at the method and the shapes a reader would get backwards are pinned by the live stock arm's six rendered values.
  3. The ToPayload ternary — the only place a key can silently go missing (two anonymous initializers, one per arm). The live pins read both arms' keys from serialized JSON, and the member-scan roster entry names exactly what falls outside the walker's range.

Reviewer checklist

  • The SQL Server arm's payload is byte-identical (separate record; ConfigRecommendation untouched).
  • settings_checked excludes not_applicable; not_applicable is its own summary line.
  • threshold_lineage is null (absent bar), never 0, on config rows.
  • No suggested_value reaches the wire on the PostgreSQL arm.
  • Every CONFIG_PG_* constant names a setting (census fails on a new key).

What it does not do

No change to which CONFIG_PG_* facts exist or to any bar; no migration rung; no get_pg_server_config change; Lite's audit_config body untouched; no Viewer change; CHANGELOG buffered, not edited.

Out-of-lane (issue-class, reported not folded)

  • CONFIG_PG_CHECKPOINT_TIMEOUT is not stamped not_applicable on Aurora at the COLLECTOR, so get_analysis_facts still shows it unqualified there. Crossing into lane 15's collector file + its calibration pins is a cross-lane edit; this lane made the operator-facing surface true and left the fact where it is.

audit_config's PostgreSQL arm has to tell an operator WHICH setting a fact is
about, and "CONFIG_PG_RANDOM_PAGE_COST" is not a thing anyone types into ALTER
SYSTEM. ConfigPgSettingName maps every declared CONFIG_PG_* constant to its
pg_settings name, beside the constants rather than inside the tool, so a second
reader of the same facts inherits the map instead of inventing it and a renamed
key fails to compile in both places at once.

The three keys that are not one setting are spelled so the reader cannot mistake
them for a pg_settings row to go read: the extension's absence is
shared_preload_libraries (pg_stat_statements), the composition check is the
arithmetic it performs, and the per-table reloption says reloption (the TABLE
rides the fact's ObjectName). An unknown key returns null and the caller renders
the key itself: a vocabulary a reader can look up beats a guessed instruction.
… redirect

#3542 stopped this tool telling a PostgreSQL operator that the config collector
may not have run yet, by refusing honestly and naming where the checks live. It
left the reader one hop short: the CONFIG_PG_* facts it pointed at are emitted by
the very pass audit_config already runs, so a reader who asked the right question
was sent to ask it again somewhere else. Erik ruled the projection in on
2026-09-22; this is it.

Every difference from the SQL Server envelope is a difference of the engine, and
each is now in the tool's description so the contract changes once: engine in
place of edition (PostgreSQL ships no editions), a STRING current_value because
'random_page_cost 4.0', 'shared_buffers 4 GB' and 'autovacuum off' are not
integers, and no suggested_value at all - the right value for a PostgreSQL knob is
host- and workload-specific, so the advice's remediation sentence carries the
evidence and the counter-objective a bare number would strip.

The tool grades nothing. Severity is the pass's, read off the fact and spelled
ok / review / warning, plus not_applicable for a fact the collector stamped:
checkpoint_timeout and max_wal_size on Aurora, where the storage layer owns
checkpointing. Those rows report the engine's own sentence - lane 15 composed it
into the advice block, so no special case is needed here - are counted on their
own summary line, and are excluded from settings_checked, because a knob the
engine does not consult was not checked and counting it would inflate the audit's
idea of how much it looked at.

Facts are filtered by KEY prefix, not by source: the family lives on pg_config,
pg_memory (the composition check) and pg_vacuum (the per-table reloption), so a
source filter would silently drop the two checks that are not settings snapshots.
PgConfigRecommendation is a separate record from ConfigRecommendation rather than
a widening of it, so the SQL Server arm's payload stays byte-identical.
Rider 2 of Erik's ruling: the description contract changes ONCE, so the residual
edition-awareness drift goes out with the projection rather than in a later pass.
The A15/A16 fix landed in #3653 and its pin passes; what was left were three doc
comments that had not moved.

Both SKUs' memory collectors claimed their facts 'enable edition-aware memory
recommendations in the config audit'. They enable RESOURCE-based ones: max server
memory is sized against the host's physical RAM, and no check in that audit
branches on the edition - the payload reports it for context, which is the exact
distinction the tool's own description has made since #3653. Lite's schema
comment called the v2 server-metadata columns 'edition-aware analysis' and now
names what they are: the columns the config audit reads, reported for context.

The instruction table's audit_config row and the step-7 mention now describe the
PostgreSQL arm as a projection rather than a refusal, one sentence each, matching
the tool description word for word on the three things that differ (engine in
place of edition, unit-carrying current_value with no suggested_value, and
not_applicable on Aurora).

Lite's audit_config body is untouched and needs no description change: it has no
PostgreSQL targets by product and its description never carried the refusal
sentence, so there was nothing there to keep or correct.
@erikdarlingdata
erikdarlingdata merged commit 4db5c04 into dev Sep 22, 2026
7 of 8 checks passed
@erikdarlingdata
erikdarlingdata deleted the feat/3691-audit-config-pg-projection branch September 22, 2026 03:21
erikdarlingdata added a commit that referenced this pull request Sep 22, 2026
…t at the tool that renders it (#3868) (#3874)

Lane 15 of #3691 (#3728) taught the write family to tell Aurora the truth,
and it stamped one knob. CONFIG_PG_MAX_WAL_SIZE carries not_applicable /
not_applicable_on_aurora on an aurora-postgres target because the calibration
read fifty clusters over fourteen days and found sixty timed "checkpoints" an
hour with a requested share of 0 on every one: Aurora's storage layer owns
checkpointing, so max_wal_size at its shipped default is not a convention
finding there, it is the parameter group's value for a knob the engine does
not consult. Its sibling, CONFIG_PG_CHECKPOINT_TIMEOUT, came out of the same
method in the same pass unqualified.

That is what #3867 found while pinning rider 1 of Erik's ruling, and it fixed
the symptom it could reach: audit_config renders both checkpointing knobs
not_applicable on Aurora, the second through an engine-side arm in the tool.
The rider is satisfied for that one surface. Everything else that reads the
fact -- get_analysis_facts, the story walk, the drill-downs -- still read a
graded, unqualified fact, and its composed advice still ended with "tune it
with max_wal_size, not instead of it," which on Aurora sends an operator to a
knob the storage layer ignores. Belt, not braces: the fact is the truth the
rest of the engine reads, and it disagreed with the tool.

The stamp is mirrored, not invented. PgTargetFactCollector.Config.cs now
stamps checkpoint_timeout the way it stamps max_wal_size, in the same method,
off the same single read: the is_aurora flag on the SERVER_MAJOR_VERSION
registry fact emitted just before this method, which comes from
MonitoredEngineKind and never from a column's presence (#2530). The value is
still emitted -- what a parameter group holds for a knob nothing reads is a
fact about the parameter group and worth seeing, and the write partial reads
checkpoint_timeout_s off max_wal_size's metadata regardless.
PgTargetScorer.Config.cs needed no change and got none: its not_applicable
read already returns 0 for a stamped fact, as it has for the sibling since
lane 15, so no advisory roots and FactScorer.ScoreAll skips amplifiers at
base 0. This knob was base 0 anyway; the stamp is about what the fact SAYS.

One line had to move. audit_config keeps its engine-side arm, now honestly
commented as belt and braces -- one read of a flag that is already true, kept
because this tool is where rider 1's guarantee is made to a reader. But the
tool also APPENDS the Aurora sentence to checkpoint_timeout's recommendation,
and that append was gated on the fact being UNSTAMPED. That gate was correct
when only the engine knew. Stamping the fact would have silently stopped
appending the one sentence the rider exists to guarantee -- a fix that quietly
regressed #3867's own acceptance, with a green build. The gate now
discriminates by KEY: max_wal_size's composed block carries lane 15's
not-applicable sentence inline and needs no append, while checkpoint_timeout's
block is the base-0 context one with no Aurora arm, so the sentence is
appended there. Found by reading #3867's live pins before running them.

The write family's Aurora e2e now pins the pair where it pinned one: through
the real get_analysis_facts, checkpoint_timeout reads 300 s with base 0 and
both metadata flags set, beside the existing max_wal_size assertions, so a
future edit that stamps only one knob again fails a test instead of shipping.
PgTargetAuditConfigTests (#3867, two live arms) is untouched and green, which
is the evidence that the surface did not move while the fact underneath it
changed. Both config-family lineage censuses were run for lane 15's
not_applicable-stamps-no-lineage contract and needed no exemption: they walk
scorer SOURCE for lineage markers rather than counting facts per family, and
this lane adds no bar and stamps no threshold_lineage.

Verified against a live timescaledb-pg18 store: PgTargetAuditConfigTests 2/2,
PgTargetWriteTests 29/29, PgTargetKnobsTests 43 (1 skipped),
PgTargetThresholdLineageTests 4/4, PgTargetMeasuredLineageTests 4/4, plus
PgTargetFactCollectorTests 8/8, DarlingMcpToolsTests 16/16,
McpPayloadContractCensusTests 67/67, PgTargetSharedSwitchRoutingTests 28/28
and RepoFileAdoptionTests 2/2. Zero warnings.

Closes #3868.

Co-authored-by: erikdarlingdata <erik@erikdarling.com>
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