Repository navigation
get_pg_logging_audit judges a PostgreSQL target's logging settings facet by facet from the stored config, so a target with everything off stops looking identical to an instrumented one (#3607) - #3643
Conversation
| own boot value at a positive threshold is on that default, and the default is the shape worth | ||
| naming: it sees only the outlier runs. */ | ||
| var atDefault = row is not null && threshold > 0 | ||
| && string.Equals(row.Setting, row.BootValue, StringComparison.Ordinal); |
There was a problem hiding this comment.
Non-blocking: atDefault decides "PostgreSQL's own default since 15" wording by comparing row.Setting to row.BootValue as text, rather than checking row.Source == "default".
This is exactly the anti-pattern DarlingPgServerConfigReader.CurrentConfigSql documents and deliberately avoids a few files over (lines 88-97 there): "PostgreSQL's OWN verdict, not a text comparison against boot_val. Comparing the strings looks equivalent and is not... source = 'default' is PostgreSQL saying plainly that nobody set them."
Concretely: an admin who writes log_autovacuum_min_duration = 600000 explicitly in postgresql.conf (coincidentally matching the PG15+ boot value) would get source = 'configuration file', but this code would still say "This is PostgreSQL's own default since 15" — attributing a deliberate choice to inaction. Using row.Source == "default" instead would match the established, documented convention and avoid the false positive.
There was a problem hiding this comment.
Fixed in 2b48da1 — atDefault now reads source = 'default', PostgreSQL's own verdict, with a comment citing CurrentConfigSql's reasoning. Test TheTenMinuteAutovacuumDefault_IsNamedAsTheDefault_AndADeliberateThresholdIsNot gained your exact case: an explicit 600000 from configuration file is NOT called the default.
| /// unit with no suffix (<c>600000</c> with <c>unit = ms</c>), so this is a plain parse; anything else is | ||
| /// null and the caller says <c>unknown</c> rather than guessing. | ||
| /// </summary> | ||
| private static long? Threshold(DarlingPgLoggingAuditReader.PgLoggingSettingRow? row) => |
There was a problem hiding this comment.
Non-blocking: Threshold/Bool return null for both "row is missing" and "row is present but the value doesn't parse" (e.g. log_temp_files = "lots"). Both collapse to the Unknown verdict, and every caller's _ => NotInSnapshot / verdict == Unknown ? NotInSnapshot : ... branch (e.g. lines 250, 299-300, 336, 398, 439, 500) then emits "This setting is not in the stored snapshot, so nothing is claimed about it" — which is false when the row exists but is unparseable. Remedy (line ~623) has the same conflation: "No remedy is offered for a setting the snapshot does not hold".
This contradicts the tool's own documented contract ("unknown means the setting is not in the snapshot") and is tested only for the verdict (AnUnparseableValue_IsUnknown), not the message. In practice this is close to unreachable since PostgreSQL only ever renders well-formed values for boolean/integer GUCs, so I'd call it low-severity — but worth distinguishing "absent" from "present but unparseable" in the prose if the collector or a future setting ever produces the latter.
There was a problem hiding this comment.
Fixed in 2b48da1 — a shared UnknownNote(row) tells the two apart: a null row says "not in the stored snapshot", a present-but-unreadable row says "IS in the stored snapshot but its value '…' is not one this audit can read", and Remedy's unknown arm splits the same way. AnUnparseableValue_IsUnknown_AndSaysItIsInTheSnapshot pins the message, not just the verdict — you are right that nothing else would ever exercise it.
There was a problem hiding this comment.
LGTM — reviewed the new get_pg_logging_audit read (reader SQL, pure-function judgment, MCP wire, JS panel, docs/instructions/README counts, and the Lite/Darling parity pin). The reader correctly excludes session-scoped rows via the same inline list convention as DarlingPgServerConfigReader, the parameterized query has no injection surface, the four-verdict logic (instrumented/partial/off/unknown) and hosting-flavour detection from rds.* evidence check out against the extensive test suite (including the live-Postgres end-to-end test), and the Lite exclusion-list pin plus doc/tool counts (151→152, 86→86 shared, PostgreSQL reads 33→34) are internally consistent. Left two non-blocking inline notes: (1) the autovacuum-default wording compares setting text to boot_val text instead of checking source == 'default', which is the exact anti-pattern the sibling DarlingPgServerConfigReader documents avoiding; (2) the unknown verdict's prose always says "not in the stored snapshot," which is inaccurate for the (currently unreachable in practice) case of a present-but-unparseable value. Neither affects verdict correctness or produces an unsafe remedy.
…cet by facet from the stored config, so a target with everything off stops looking identical to an instrumented one (#3607) Plan-capture readiness established the shape - a facet per setting, the remedy per finding, the hosting flavour's own syntax - and covered only auto_explain's three preconditions. The rest of the logging surface (log_lock_waits, log_temp_files, log_autovacuum_min_duration, log_checkpoints, log_connections / log_disconnections, log_min_duration_statement) had no audit at all, and a target with all of them off answered every counter-based read exactly like a fully instrumented one. This is a READ over pg_server_config's newest snapshot, not a collector: every judged setting is a core GUC the config collector already stores hourly with its value, source, unit and context, so the judgment is a pure function computed on request and the snapshot's collection_time is the audit's as_of. Verdicts are instrumented / partial / off / unknown; partial names what a threshold hides and is the recommended posture for log_min_duration_statement. Consumers are named honestly as PLANNED (#3601/#3602/#3603) beside the counter read that exists today. Hosting flavour comes from rds.* parameters in the same snapshot, which is the one fact the registry's engine token cannot supply. Plan capture's own settings are listed with the readiness facet that owns each, never judged twice. Registered beside the plan tools, dispatched on the web with a Configuration-tab-adjacent panel under readiness, censused in the instructions / README / llms.txt / runbook, ratcheted in the cross-app inventory pin.
…LATEST IS A TIME in the description, and a Darling-only allowance in the stamp roster dev moved under the lane: #3637 landed McpLatestSnapshotStampTests, whose reader-call sweep caught GetNewestSnapshotAsync and asked for a Shape. The roster is a roster of PAIRS (every entry names its Lite twin's file), so a Darling-only tool gets a DarlingOnlyStamped list held to the same three Stamped assertions, and the new reader's SQL joins the stamp-column theory. Threshold() became a block body so TsqlConventionGuardTests' member scan reads it whole rather than growing KnownTruncatedRanges.
6a2f100 to
8a800ed
Compare
| public sealed record Facet( | ||
| string Setting, | ||
| string? Value, | ||
| string? Unit, | ||
| string? DefaultValue, | ||
| string? Source, | ||
| string? ChangeNeeds, | ||
| string Verdict, | ||
| string Unlocks, | ||
| string Consumer, | ||
| string Recommended, | ||
| string CostNote, | ||
| string Remedy, | ||
| string? ScopeNote); |
There was a problem hiding this comment.
Facet (and every builder below it) drops PgLoggingSettingRow.PendingRestart on the floor — the reader fetches it and documents it as "The file and the running server disagree about this one" (DarlingPgLoggingAuditReader.cs:63), but it's never read anywhere in this file, never appears on Facet, and never reaches the JSON in DarlingMcpPgLoggingAuditTools.BuildAuditJson.
That's a real gap given this tool's own stated bar for honest disclosure (the ScopeNote machinery exists for exactly this kind of "the value shown isn't the whole truth" caveat). The sibling get_pg_server_config tool treats this field as important enough to report "loudly" (DarlingMcpPgServerStateTools.cs:595): if postgresql.conf was edited and reloaded but the running server hasn't restarted yet, the file value differs from what pg_settings.setting currently reports. For a judged setting sitting on pending_restart = true, this audit will confidently report today's verdict/remedy with no indication that the setting is about to flip on the next restart — which is exactly the kind of silent staleness this PR's design otherwise goes out of its way to avoid.
Worth surfacing pending_restart per facet (or at least noting it in scope_note) the same way the config reader does. Also worth noting: no test in DarlingMcpPgLoggingAuditToolsTests.cs ever sets PendingRestart = true (the Row() helper hardcodes false), so this path is untested as well as unused.
There was a problem hiding this comment.
Fixed in 2b48da1 — real gap, thank you. Facet carries PendingRestart + a RestartNote (the running value is what was judged, the file already holds another, pg_settings cannot say which way it moves, so read the file / get_pg_server_config's pending_restart_settings before acting on the remedy). On the wire per row as pending_restart / restart_note, named at the top as pending_restart_count / pending_restart_settings the way get_pg_server_config does it, and the response note points at it. APendingRestart_IsCarriedOnTheRow_AndNamedInTheSummary covers the judgment and the wire; the live test now seeds one pending_restart = true row on the managed server and asserts it lands in both places.
| catch (Exception ex) | ||
| { | ||
| return McpHelpers.Status("error", $"Reading the PostgreSQL logging audit failed: {ex.Message}"); | ||
| } | ||
| } |
There was a problem hiding this comment.
This catch block doesn't check DarlingEngineCapability.NotCollectedStatusAsync the way its siblings do. DarlingMcpPgPlanTools.GetPgPlans and GetPgPlanCaptureReadiness both re-check the engine gate inside their catch (not just on the empty-rows path) so a read that throws for an engine-mismatch or schema reason still comes back as a friendly not_collected status rather than a raw error with ex.Message. Here any exception from GetNewestSnapshotAsync (e.g. hitting an un-migrated store, a transient timeout, etc.) just surfaces as McpHelpers.Status("error", ...), which is a step down in message quality from the pattern this file otherwise mirrors ("BuildAuditJson split out the way BuildReadinessJson is"). Minor compared to the PendingRestart gap, but worth aligning for consistency.
There was a problem hiding this comment.
Fixed in 2b48da1 — the catch asks NotCollectedStatusAsync("pg_server_config") before answering error, mirroring GetPgPlanCaptureReadiness, with a comment saying why the gate is the more specific fact.
There was a problem hiding this comment.
Reviewed the diff against origin/dev. The Darling-only scope is well justified (Lite has no PostgreSQL target — CrossAppMcpToolInventoryPinTests, McpLatestSnapshotStampTests's new DarlingOnlyStamped roster, and ServerPageTabsTests are all updated consistently), tool counts in README/llms.txt/docs match the registration change, and the T-SQL/PostgreSQL-store style conventions (schema-qualified reads, naive-UTC collection_time, session-scoped source exclusion spelled inline and identically to DarlingPgServerConfigReader) are followed correctly.
Two findings from the review, left as inline comments:
-
Correctness/completeness:
DarlingPgLoggingAuditReader.PgLoggingSettingRow.PendingRestartis read from the store and documented as significant ("the file and the running server disagree about this one"), butDarlingPgLoggingAudit'sFacetnever carries it and it never reaches the JSON response inDarlingMcpPgLoggingAuditTools.BuildAuditJson. The siblingget_pg_server_configtool reports this "loudly" for exactly the scenario where it matters: a judged setting sitting onpending_restart = truemeans the value this audit reports (and the remedy it offers) may be about to change on the next restart with no signal to the reader. No test exercisesPendingRestart = trueeither. -
Minor consistency: the
catchblock inGetPgLoggingAuditdoesn't re-check the engine gate (DarlingEngineCapability.NotCollectedStatusAsync) the wayDarlingMcpPgPlanTools.GetPgPlans/GetPgPlanCaptureReadinessdo in their catch blocks, so an exception during the read surfaces as a raw error instead of the establishednot_collectedmessage.
Neither is a blocker on architecture or parity — the PostgreSQL/RDS/Aurora remedy branching, the threshold judgment logic, the log_connections 17/18 boundary handling, and the readiness cross-reference all check out against the described design. Requesting changes mainly for finding #1, which weakens the audit's core promise of full, honest disclosure.
…catch re-checks the engine gate, the ten-minute default is PostgreSQL's own verdict, and the two unknowns are told apart pending_restart was fetched, documented as load-bearing, and dropped before the Facet - on exactly the row whose remedy needs qualifying, because the value judged is the RUNNING one and the file already holds another. It is now on every facet with a restart_note, named at the top as pending_restart_settings the way get_pg_server_config names them, and seeded in the live test. The catch block asks the engine gate before answering error, as the plan tools do. atDefault reads source = 'default' rather than comparing setting text to boot_val - an administrator who writes 600000 into the file has made a choice, which is the anti-pattern DarlingPgServerConfigReader's own comment names. An unparseable value now says it IS in the snapshot rather than that it is not.
Both findings remediated in 2b48da1 (pending_restart on the row and in the summary with a live-seeded case; catch re-checks the engine gate) plus the two non-blocking notes from the first pass (source='default' verdict; unknown-vs-unparseable wording). Replied inline to each.
There was a problem hiding this comment.
LGTM — reviewed the audit judgment logic (DarlingPgLoggingAudit.cs), the parameterized Postgres reader SQL, the MCP wire/JSON shape, and the doc/registration/parity updates (README, llms.txt, MCP instructions, runbook, CrossAppMcpToolInventoryPinTests, McpLatestSnapshotStampTests). No correctness bugs, injection risks, or Lite/Darling parity gaps found; Lite's lack of a PostgreSQL target is explicitly and correctly documented as the reason there's no counterpart.
…spliced once Fifty-nine PRs merged to dev today across the coordinator's lanes and the wave-2 worker's; each lane returned its entry to a buffer instead of touching this file, so that fifty-plus PRs did not each rebase the same twenty lines. This is the one splice. Every entry is one line (the archiver's compact() and the pins read them that way); riders fold into their parent's entry (#3599 under #3590, #3619 under #3611, #3623 under #3616, #3640 under #3633; #3617 test-only and #3661 re-cut as #3666 carry none); #3657's entry is in because it MERGED to dev - the twin to main is what is still pending. [Unreleased] gains a `### Added` above `### Fixed` (Keep-a-Changelog order) for the six new capabilities: per-user theme colours (#3606 / #3577 arm B), routed alert families (#3668 / #3598), the PostgreSQL logging audit tool (#3643 / #3607), the service-side wait sampler (#3645 / #3604), and the log-event classifier with its temp-file / autovacuum parser families (#3646 / #3601, #3664 / #3602 #3603). The other forty-eight are honesty fixes to existing surfaces and append to `### Fixed` after the wave-1 bullets, in PR-number order. Thirty-six reference definitions added for the issues the new entries cite and the index did not yet define; the [Unreleased] group is one ascending run again, which moves [#3557] into its slot (the one deleted line). Nothing under ## [3.8.0] or older is touched; the archive script was not run. One editorial touch: the #3585 entry ended in a dangling "Darling" and now reads "Darling only." (the tool exists only in the Darling MCP host). tools/changelog/changelog_archive.py verify: all PASS (1420 bold entries, floor 1,329; 1,358 distinct refs resolve; CRLF throughout; 356,539 bytes under the 750 KiB ceiling). ChangelogIndexAndArchiveTests: 5/5 pass via a net10.0 harness.
…spliced once (#3672) Fifty-nine PRs merged to dev today across the coordinator's lanes and the wave-2 worker's; each lane returned its entry to a buffer instead of touching this file, so that fifty-plus PRs did not each rebase the same twenty lines. This is the one splice. Every entry is one line (the archiver's compact() and the pins read them that way); riders fold into their parent's entry (#3599 under #3590, #3619 under #3611, #3623 under #3616, #3640 under #3633; #3617 test-only and #3661 re-cut as #3666 carry none); #3657's entry is in because it MERGED to dev - the twin to main is what is still pending. [Unreleased] gains a `### Added` above `### Fixed` (Keep-a-Changelog order) for the six new capabilities: per-user theme colours (#3606 / #3577 arm B), routed alert families (#3668 / #3598), the PostgreSQL logging audit tool (#3643 / #3607), the service-side wait sampler (#3645 / #3604), and the log-event classifier with its temp-file / autovacuum parser families (#3646 / #3601, #3664 / #3602 #3603). The other forty-eight are honesty fixes to existing surfaces and append to `### Fixed` after the wave-1 bullets, in PR-number order. Thirty-six reference definitions added for the issues the new entries cite and the index did not yet define; the [Unreleased] group is one ascending run again, which moves [#3557] into its slot (the one deleted line). Nothing under ## [3.8.0] or older is touched; the archive script was not run. One editorial touch: the #3585 entry ended in a dangling "Darling" and now reads "Darling only." (the tool exists only in the Darling MCP host). tools/changelog/changelog_archive.py verify: all PASS (1420 bold entries, floor 1,329; 1,358 distinct refs resolve; CRLF throughout; 356,539 bytes under the 750 KiB ceiling). ChangelogIndexAndArchiveTests: 5/5 pass via a net10.0 harness.
The lie
A PostgreSQL target with
log_lock_waits,log_temp_files,log_autovacuum_min_duration,log_checkpoints,log_connections,log_disconnectionsandlog_min_duration_statementall off looks identical to a fully instrumented one from every read this product has. The counters are the same, the sampled views are the same,get_pg_server_configlists 400 settings non-default-first and nobody scrolls to the logging block. The operator finds out at incident time, when the log they reach for holds nothing — which is exactly the silence plan-capture readiness (#2564/#3070) fixed forauto_explain, over a different setting list.Mechanism
get_pg_logging_audit(Darling-only; Lite has no PostgreSQL target) judges the seven settings from the newest storedpg_server_configsnapshot (itscollection_timeis the response'scaptured_at) and answers per setting:verdict, thevalue/source/unit/default_valueverbatim, what the lines it writes unlock and what this product has instead today, the Darlingconsumerthat would read the lines, therecommendedvalue, itscost_note, and theremedyin the hosting flavour's syntax. The summary counts every facet (there is no page — the list is fixed) and names theoff_settingsandunknown_settings, because those are the actionable ones.Three pieces:
Darling/PerformanceMonitor.Darling.Storage/DarlingPgLoggingAuditReader.cs— the SQL: newestcollection_timeper server, every non-session row at it. NamedDarlingPg*Readerdeliberately soDarlingPgReadSqlParsesLiveTestsparse-checks it with the rest (it did, on this lane's rig).Darling/PerformanceMonitor.Darling.Service/Mcp/DarlingPgLoggingAudit.cs— the judgment, a pure function over the rows. Tests hit it with no store.Darling/PerformanceMonitor.Darling.Service/Mcp/DarlingMcpPgLoggingAuditTools.cs— the wire, withBuildAuditJsonsplit out the wayBuildReadinessJsonis.Where this copies readiness, and where it differs — and why
Copies: a facet per setting rather than one verdict; every facet reported whether satisfied or not (a list of only the failures cannot show that a target is instrumented); the remedy per row worded for the flavour; the readiness collector's exact phrasing for managed hosting ("parameter-group change that applies WITHOUT a reboot"); the
captured_at= snapshot stamp dialect #3541 A10 (#3637) established for every latest-snapshot read, selected on the row statement, with LATEST IS A TIME in the description; thenot_collected→emptymiss ladderget_pg_server_configuses, with the empty message saying plainly it is the absence of evidence, not a verdict.Differs — it is a read, not a collector, and takes no rung. The readiness collector persists because judging its facets means probing the target: is the library in
shared_preload_libraries, does anauto_explain.*GUC exist at all. Every setting here is a plain core GUCPgServerConfigCollector(#2658) already stores hourly with value, source, unit and context, so a second collector would write the same rows under a second name and the two would drift. Settings change rarely; the config collector's hourly snapshot is the history. (The storage ladder is also spoken for tonight, which is a reason to prefer this shape but not the reason it is right.)Differs — four verdicts instead of a boolean, and
partialis not a lesserinstrumented.instrumentedwrites every line the setting can;offwrites none;unknownis not in the snapshot and nothing is inferred — not from the default, not from the major;partialmeans a threshold is filtering and the row says what falls under it. Forlog_min_duration_statementthe threshold is the recommended posture: 0 logs every statement the server runs, and #2565 measured the capture-everything shape of that mechanism at 31% of throughput and 772 MB of log in 20 seconds. So a 1000 ms row's remedy is "no change needed", itscost_notecalls it the recommendation, and the 0 row isinstrumentedwith the measured cost and a remedy that moves it to a threshold. Sending an agent to "fix" a sane threshold to 0 is the worst outcome this read could produce, and the shape is built to refuse it.Differs — hosting flavour is decided from evidence in the same rows. The registry's engine token cannot do it:
MonitoredEngineKind.Postgresis both self-hosted and RDS for PostgreSQL, and they need opposite instructions (ALTER SYSTEM SET …; SELECT pg_reload_conf();on one; a parameter group on the other, whereALTER SYSTEMis refused). Anyrds.*GUC in the snapshot — RDS and Aurora both carry them — flips every remedy, andhosting_evidencesays how many there were or that there were none. The reload-vs-restart clause comes from each setting's owncontextcolumn, sosuperuser-backendsettings say "applies to connections opened after it" and apostmasterone would say restart — no table of contexts here to rot.Differs — the cross-reference. Plan capture's own settings (
shared_preload_libraries,auto_explain.log_min_duration,log_line_prefix,lc_messages) appear underjudged_by_readinessas observed values with the readiness facet that owns each, never as a second verdict —get_pg_plan_capture_readinessholds their traps (loaded-at-−1, placeholder GUC, translated catalogue) and a second judgment here is a second place for those to drift.lc_messagesis in the list because it governs whether any of the seven settings' lines are written in the English the parsers match.Honesty about consumers
The issue's sequencing note says this audit earns its keep once #3601's log pipeline and its first parser families (#3602 temp files, #3603 autovacuum) consume the lines, and none of those ships. Every facet's
consumertherefore begins PLANNED, names the issue, and names the counter read that exists today with what it cannot see (get_pg_top_queriesaverages a shape, never says one execution took 40 s at 03:07;get_pg_blockingsamples and misses a wait that starts and ends between samples;get_pg_autovacuum_healthsays whether, never what it cost). A test pins that every consumer says PLANNED and is written to go red when one ships — so the prose an agent plans against becomes a deliberate edit rather than stale. The setting is still worth turning on first: the log it fills is the one somebody opens at incident time, whichever tool reads it.Per-setting facts, verified on the lane's PostgreSQL 18.4 rig
pg_settings.settingrenders integer GUCs in the base unit with no suffix (600000/ms,-1/kB), so the threshold parse is a plainlong.TryParse; anything else isunknown.log_connectionsis a boolean through 17 and a string list in 18 (receipt,authentication,authorization,setup_durations,all;on/true/1still mean all; empty is off). Measured: 18.4 storeson,true,1,all,receipt,authenticationeach verbatim as written. The audit reads any non-empty non-false value as producing lines and shows the value.log_autovacuum_min_duration's default moved from −1 to 600000 in 15; a server withsource = 'default'at a positive threshold gets the "PostgreSQL's own default since 15 — sees only the outlier runs" wording, a deliberate threshold (any other source — including an explicit600000written into the file) gets neutral wording. PostgreSQL's own verdict, not a text comparison againstboot_val, for the reasonCurrentConfigSql's comment gives (review round 1).log_checkpointsdefault moved to on in 15;log_lock_waitsfires atdeadlock_timeout, whose value is quoted from the same snapshot (or "not in the snapshot" — never the compiled-in 1 s as if it were this server's).log_lock_waits/log_temp_files/log_min_duration_statementaresuperuser,log_autovacuum_min_duration/log_checkpointsaresighup,log_connections/log_disconnectionsaresuperuser-backend. All dynamic — every self-hosted remedy is a reload.pending_restarttravels: a judged row whose file value already differs from the running one carriespending_restart = trueand arestart_note(the running value is what was judged;pg_settingscannot say which way the file moves it; read the file orget_pg_server_config'spending_restart_settingsbefore acting on the remedy), and the summary namespending_restart_settingsthe wayget_pg_server_configdoes. Review round 2 caught this dropped on the floor between the reader and theFacet— a real gap on exactly the row whose remedy needs qualifying.client-sourced row (SET log_min_duration_statement = 0in the monitoring session) is excluded at the SQL, spelled inline as the config reader spells it so the constant stays parse-checkable; auser/database-sourced row is judged but carries ascope_notesaying the server-wide value may differ — the limit the readiness collector states forlc_messages, applied to every GUC here.What this does NOT do
source) and in the description. Itscatchre-checks the engine gate before answeringerror, as the plan tools do (review round 2).logging_collector,log_destination,log_min_messagesorlog_statement— the transport's and the pipeline's concerns, not "what can this target tell you".hours_backoras_ofparameters — the Stamped latest-read shape (MCP payload-contract campaign: the agent-facing API tells the truth (~30 defects + an 11-rule contract) #3541 A10) for a state, not a window;AsOfWindowAnchorTests' derived pins hold the catalog to that, andMcpLatestSnapshotStampTestsholds the stamp. dev moved under the lane (Every latest-snapshot MCP read says when it was captured, and no tool accepts a window it does not read (#3541 A10) #3637 landed the stamp roster while this was open): the roster is a roster of pairs — every entry names its Lite twin's file — so a Darling-only tool cannot sit in it; it gets aDarlingOnlyStampedallowance held to the same three Stamped assertions (stamp on the payload, LATEST IS A TIME, neither knob), with the set-equality control the other allowances have, and the new reader's SQL joins the stamp-column theory.TsqlConventionGuardTestsflaggedThreshold()'s expression body (a{ }property pattern reads as a member body to its scan); it became a block body rather than aKnownTruncatedRangesentry.EveryPostgresRead_IsReachableFromThePostgresRegistryrequires everyget_pg_*read to land.Surface
Registered beside the plan tools in
DarlingMcpHostService(McpToolTypeRegistrationTestsis derived and now counts it); dispatched at/api/read/get_pg_logging_auditwith a server-only catalog entry; censused inDarlingMcpInstructions(152 / 86 / 66, thirty-four PostgreSQL reads, the onboarding pair named),README.md(both sites),llms.txt(87–152), anddocs/postgres-first-target-runbook.md(step 8 table row, "34get_pg_*tools", the §12 plan-capture bullet gains the sibling); ratcheted inCrossAppMcpToolInventoryPinTests.KnownLiteMissingMcpToolswith its near-twin (get_server_config, a listing not a judgment) named; mapped inServerPageTabsTests.CollectorForReadtopg_server_config.Tests
Darling/Darling.Tests/DarlingMcpPgLoggingAuditToolsTests.cs, executed on this Mac via the mactest harness against atimescale/timescaledb:2.28.1-pg18rig (316 tests in the harness green, includingMcpLatestSnapshotStampTests,TsqlConventionGuardTests,ServerPageTabsTests,AsOfWindowAnchorTests,EngineCapabilityMissTests,DarlingWebEndpointsTests,DarlingCustomViewsTests,DarlingPeerDisclosureTestsand the liveDarlingPgReadSqlParsesLiveTestscensus that now includes the new reader;McpToolTypeRegistrationTestscannot run out-of-tree and was replayed in Python — missing ∅, stale ∅):off, all named, every remedy theALTER SYSTEM … pg_reload_conf()form;log_connectionsacross the 17/18 boundary in the spellings 18.4 stores;unknown("not in the stored snapshot"), kept out ofoff_settings; unparseable →unknownwith a message that says the row IS in the snapshot and quotes the value — the two unknowns told apart (review round 1);pending_restarton the row,restart_note, andpending_restart_settingsin the summary; plain rows carry false/null (review round 2);rds.*row → every remedy the parameter-group form, nopg_reload_conf,rds.*rows are evidence not facets;scope_note, server setting → null;deadlock_timeoutquoted from the snapshot or declared missing;auto_explain;captured_at(and noas_ofkey), counts summing tototal, named off/unknown, every prose column non-empty, nohours_back;captured_at, readiness pointer,partialdefined, PostgreSQL-only) and the no-window/no-anchor signature;client-sourced 0 that must not become the server's setting, a per-role override with itsscope_note, an unseededdeadlock_timeoutdeclared missing, and a third server with no snapshot →emptywith the not-a-verdict wording.Built
Darling.Storage,Darling.Service,Darling.Tests,Lite.Tests-c Release -p:EnableWindowsTargeting=true— 0 warnings.Closes #3607