Skip to content

One payload said NoData and No Data, eight spellings of four severity words were two bands and six other vocabularies, and the page cut had five names: the daily summary speaks one band token on both SKUs, six inferred cuts become observed truncated + *_returned, and the census classifies every cut key and severity literal by what it is (partial #3653) - #3703

Merged
erikdarlingdata merged 2 commits into
devfrom
fix/3653-vocabulary
Sep 19, 2026

Conversation

@erikdarlingdata

Copy link
Copy Markdown
Owner

Part of #3653 (the A15/A16 "one vocabulary" clause: 7 truncation dialects / 5 severity vocabularies ("NoData" and "No Data" in one payload)). PARTIAL — does not close it. #3699 measured the clause exactly and left the rosters (TruncationKeyDialects, 14 keys; SeverityWordSpellings, 8 spellings; the named NoData/No Data pair) for this lane to collapse. This PR collapses what is a dialect, classifies what is not, and says which is which in a census that fails on the next spelling.

The lie, read from the source

Three lies of different sizes were hiding under one sentence.

One payload, two spellings of one band. get_daily_summary and get_daily_summary_range, on both SKUs, published overall_health = DailyHealthBandCalculator.Label(band) beside health_band = band.ToString(). Those agree on three of four values and disagree on the fourth: "No Data" beside "NoData". The label is the desktop viewers' tooltip text; the token is what every other band on the wire is spelled as (HealthSeverity, FleetHealthBand, DailyHealthBand — PascalCase, one word) and what the web client keys its CSS classes on (band-Warning, sev-Critical; sweeps.js had to special-case "No Data" because a space is not a class name). Both keys now publish the token. overall_health stays as a key because it is the older of the two (the Dashboard's daily-summary column) and removing it is a wire-shape change, not a spelling. The row types keep OverallHealth as the human label for the viewers.

The page cut had five names, and three of them were inferences. Four PostgreSQL tools (get_pg_autovacuum_health, get_pg_session_states, get_pg_index_usage, get_pg_table_bloat) said limit_reached = x.Count >= limit — the >= cap shape #3594 named, which cannot tell a population of exactly limit from a busier one; its own prose said "more MAY exist". The autovacuum tool's run history said history_capped = runs.Count >= RunsReadPerTable, the same inference one layer down and against a collector constant: a table with exactly 25 logged runs read as capped. get_analysis_findings on both SKUs inferred its truncation_note from findings.Count >= WindowCoveringLimit — a window of exactly 10,000 occurrences read as cut. All six now fetch one row past their cap and bind through McpHelpers.BoundPage (#3699's helper), publish truncated, and the four caller-capped ones publish their page count as tables_returned / sessions_returned / indexes_returned (the web tiles already labelled those keys "Tables returned" / "Sessions returned" / "Indexes returned" — the label knew what the key did not say). Each limit description names the flag in #3679's wording. No SQL changed: all four readers already bound LIMIT $4, and the run-history read's per-relation cap is $5.

Eight spellings of four severity words — of which two were bands. The census counted Critical/Healthy/Warning/Unknown, warning, unknown, CRITICAL, HEALTHY. Read at each site: the PascalCase four are the band canon (HealthSeverity written out by the three PostgreSQL object tools and the AG reader's label switch). Every other spelling is another vocabulary that happens to use the same word, or the word on the way IN:

spelling file what it actually is
CRITICAL DarlingPlanCacheSchedulerReader bloat_level's top rung of the Dashboard's NORMAL / MEDIUM / HIGH / CRITICAL level scale — spelled identically by Lite's LocalDataService.ClassifyPlanCacheBloat and coloured by both viewers' BloatLevelBrush. Moving one rung would leave Critical / HIGH / MEDIUM / NORMAL.
HEALTHY DarlingAgReader SQL Server's own synchronization_health_desc, parsed on the way in and banded to HealthSeverity. Input, never emitted.
HEALTHY DarlingFleetReader the collector-health row status (NEVER_RUN / … / WARNING / HEALTHY), compared to count healthy collectors. Input.
Unknown DarlingMcpTools, McpAnalysisTools audit_config's edition-NAME fallback. A name, canon-spelled by coincidence.
unknown DarlingMcpPgWaitSamplingTools the wait-instrument token for an arm this build does not know (service_sampled / pg_wait_sampling / …).
unknown DarlingPgLoggingAudit the logging audit's verdict vocabulary (instrumented / partial / off / unknown).
unknown McpHealthTools get_collection_health's version-string fallback.
warning DarlingMcpPgWraparoundTools the bare tier of the PostgreSQL severity-TOKEN ladder (ok / info_* / warning[_*] / critical_*, shared by the autovacuum, slot and wraparound tools, rendered untranslated by server-tabs.js by design).
warning DarlingMcpTools, McpAnalysisTools audit_config's per-recommendation status (ok / warning / review) — the lower-case status vocabulary every status key speaks.

None of these moved, and the PR says so rather than counting them as collapsed. The one that is a severity by any reading — the token ladder — is a different vocabulary, not a case variant: the tier is a prefix and the reason is the rest of the token (critical_far_past_threshold). Folding it into the band canon means splitting severity into a band plus a reason on three tools, which is a payload reshape and a maintainer's call; it is stated as residue below, not done quietly.

The classification (14 keys → 5 classes + 2 homonyms)

key class why it keeps (or loses) its name
truncated page cut THE spelling. Observed off cap + 1 through BoundPage, beside *_returned.
limit_reached page cut → retired >= limit inference on four PG tools; now truncated + *_returned.
history_capped page cut → retired >= RunsReadPerTable inference in RecentRuns; now truncated off a + 1 per-relation read.
truncation_note the prose beside the flag *_note is the house idiom for a disclosure; on get_analysis_findings (both SKUs) it now rides beside an observed truncated; on get_query_store_top it explains the #2364 window floor (see residue).
scan_truncated a second bound in the same payload get_blocking / get_deadlocks under dedup_key scan the window to a stated ceiling BEFORE the page is cut; two cuts a caller acts on differently need two names, and the second is <bound>_truncated, observed the same way.
capture_was_truncated source-side the pg_session_states collector's per-capture row cap bit at COLLECTION.
chain_may_be_truncated, chain_truncation_note source-side the pg_blocking collector's chain-walk depth cap at collection.
query_text_may_be_truncated source-side statement text cut at collection to the collector's text cap.
answered_rows_withheld, created_rows_withheld #3594's withheld summary a reach verdict that withholds a figure rather than publishing a page count under a whole's name.
shown page count under a neutral noun (survives) beside an honest WHOLE total_* on the health-parser, default-trace and analysis-facts tools; the cut is exact (total − shown). The *_returned + truncated rename is fenced tonight: four PgTarget* test files read shown off get_analysis_facts, and that fence is a standing decision.
is_partial homonym, not a cut a PARTIAL INDEX (CREATE INDEX … WHERE).
partial_count homonym, not a cut logging-audit facets whose verdict is partial.

Source-side keys keep their names because they are true and different: a caller can do nothing about them by re-paging, and folding them into truncated would tell that caller to raise a limit that changes nothing.

The census now asserts the classification

McpPayloadContractCensusTests (49 facts, all executed here): the flat TruncationKeyDialects roster is replaced by six classified rosters (SecondBoundCutKeys, CutNoteKeys, SourceSideCutKeys, WithheldSummaryKeys, PageCountsUnderANeutralNoun, CutHomonyms) each carrying files and a reason, plus RetiredCutSpellings. EveryCutKey_IsThePageDialect_OrClassified_OnBothSkus sweeps both SKUs' tool sources and fails a retired spelling BY NAME, an unclassified spelling with the class it should have joined, a rostered key that moved files, and a rostered key that vanished. The cut matcher is widened beyond the tree (*_reached, *_capped, cap_hit/limit_hit, has_more, more_available, omitted) and witnessed both ways — including that shared_blks_hit is a buffer-cache hit, not a cap hit (the first run of the widened matcher caught exactly that, and the arm is anchored to the cap words). TheSixMovedSites_FetchOnePastTheCap_AndObserveThroughBoundPage reads the six tools back; TheAutovacuumRunHistory_BindsToItsCapAndObserves the block one layer down.

Severity: SeverityWordSpellings becomes BandCanonSpellings (PascalCase by construction, asserted) plus SeverityHomonyms — (spelling, file, what it is) for every non-band literal — and EverySeverityWordLiteral_IsTheBandCanon_OrARosteredHomonym_OnBothSkus fails a new lower- or upper-case severity literal in any unrostered file as a band spelled wrong. Executed controls: TheBandCanon_IsWhatTheSharedClassifiersSpell (the enum tokens and both label helpers agree); ThePlanCacheBloatLevel_IsOneScaleOnBothSkus_AndNotABand (Darling's classifier at every boundary, Lite's source spelling all four rungs); TheLadderTier_IsAPrefixOfATokenFamily_NotABand (the wraparound, autovacuum and slot ladders around the bare warning); TheDailySummary_PublishesOneBandSpelling_OnBothSkus replaces the two-spellings fact — both keys read row.HealthBand.ToString() on all four tool spans and OverallHealth appears in none.

The four moved PG tools join McpPageContractTests.PgPagedToolsThroughTheHelper (now eight), so the page census holds their full contract (limit + 1, BoundPage, truncated published, LIMIT $4 parameterised, the description naming the bound).

Errors one shape: what this does and refuses

Refused: making McpHelpers.FormatError return the JSON envelope. That is one helper change with a fleet-wide wire effect (~212 tools' failures become {status:"error"}), and it is ruling Q11 on #3653. FormatError's shape is untouched and TheTwoSharedErrorShapes_AreASentenceAndAnEnvelope still executes the split.

Done inside that line: the one ad-hoc catch (list_servers returned an interpolated sentence of its own) goes through FormatError with an operation that names what was being read, so AdHocErrorReturns is empty; and McpHelpers.ValidateDaysBack(daysBack, maxDaysBack) adopts the five inline days_back refusals (get_collector_cost, get_collector_stall_probes, get_store_metrics, and get_daily_summary_range on both SKUs) in ValidateHoursBack's first sentence, each tool keeping its own ceiling (60 / 90 / 366 / 400 — the retention of the series it reads), so InlineDaysBackRefusals is empty and the bounded-parameter census recognises the new validator. Executed at its boundaries in ValidateDaysBack_RefusesOutsideTheCeilingItIsHanded_InTheSharedSentence; the two range-tool tests that pin Invalid days_back value '367' on the wire pass unchanged.

Consumers moved in the same PR

  • wwwroot/js/pages/server-tabs.js: the autovacuum / bloat / session-state / index-usage stat tiles read tables_returned / sessions_returned / indexes_returned and truncated. node --check clean; zero hits for limit_reached, "table_count", "session_count" across wwwroot/js.
  • wwwroot/js/util.js: bandClass / sevClass build their CSS class through canonicalBand, a case-insensitive map onto the PascalCase vocabulary — the belt to the census's brace, so a band arriving as "warning" from a surface the census does not sweep still colours. Executed under node against nine inputs.
  • Descriptions: both get_daily_summary descriptions say health_band=NoData where they said "No Data"; both McpInstructions already said NoData. READMEs and the web catalogue name none of the changed keys.
  • Tests quoting a changed spelling: McpFilterSemanticsLivePostgresTests and Lite's McpPageContractTests (overall_health → NoData), DarlingPgAutovacuumVerdictLiveTests (truncated / tables_returned, plus the exactly-limit complete boundary), PgLogEventMetricsTests (RecentRuns: exactly 25 is complete, 26 is truncated with runs_counted 25 and the 25th-newest as oldest_counted_run_at — the old pin encoded the >= inference as its own proof).

What it does NOT do (residue, stated)

Tests

Executed on this Mac through the built Darling.Tests.dll (xunit v3 in-process runner, -c Release -p:EnableWindowsTargeting=true, WindowsDesktop framework entry stripped from the runtimeconfig): McpPayloadContractCensusTests 49/49, McpPageContractTests 42/42, McpLatestSnapshotStampTests 34, ServerPageTabsTests 17, AsOfWindowAnchorTests 29, FleetIdentifierScrubTests 2, DocCommentHygieneTests 77, PgLogEventMetricsParserTests 11, DailySummaryHealthFormatTests 2, RepoFileAdoptionTests 2, CommentFilterAdoptionTests 4, DarlingMcpHealthToolsSurfaceAndSqlTests 26, DarlingPgWraparoundReaderTests 11, DarlingMcpPgPlanToolsTests 14, PgCappedReadSurfaceTests 12, ReadmeDerivedCountPinTests 5 — all green. Lite through a throwaway net10.0-windows harness Compile-Including the classes: DailySummaryRangeToolTests and CrossAppMcpToolInventoryPinTests green; McpPageContractTests 26/28 with the two reds (GetWaitStats_…, GetWaitingTasks_…) reproduced identically on dev before this change — a harness artefact, not this PR's. The two Darling live tests edited here (DarlingPgAutovacuumVerdictLiveTests, McpFilterSemanticsLivePostgresTests) are first executed by the Darling PostgreSQL CI job: no SQL text changed and every cap is bound as a parameter, so the + 1 is a bigger number through the same statement.

PerformanceMonitor.Common, Darling.Service, Darling.Viewer, PerformanceMonitorLite, Darling.Tests, Lite.Tests, deprecated/Dashboard.Tests build -c Release -p:EnableWindowsTargeting=true with 0 warnings from this change (Lite.Tests carries dev's one pre-existing xUnit2000 at LogTailOverlapThresholdPinTests.cs:95).

Which component(s) does this affect?

  • Lite (get_daily_summary / get_daily_summary_range band spelling; get_analysis_findings observed cut; ValidateDaysBack adoption)
  • Darling (four PostgreSQL tools + the autovacuum run history onto the page dialect; the daily-summary pair; list_servers; four ValidateDaysBack adoptions; web tiles and the band-class normaliser)
  • Lite Tests
  • Darling Tests
  • SQL collection scripts
  • Documentation
  • Full Dashboard (deprecated)
  • CLI Installer (deprecated)

Checklist

  • I have read the contributing guide
  • My code builds with zero warnings (-c Release -p:EnableWindowsTargeting=true, every touched project and every consumer of PerformanceMonitor.Common)
  • I have tested my changes (censuses and per-tool pins executed in-process on this Mac; the two edited live tests run first in CI's PostgreSQL job — no SQL changed)
  • I have not introduced any hardcoded credentials or server names

… words were two bands and six other vocabularies, and the page cut had five names (partial #3653 A15/A16)

get_daily_summary / get_daily_summary_range on both SKUs publish overall_health and
health_band from the same enum token (NoData, not "No Data"); the four PostgreSQL
tools that inferred limit_reached from a full page, the autovacuum run history's
history_capped and get_analysis_findings' truncation_note (both SKUs) fetch one past
their cap through McpHelpers.BoundPage and publish truncated (+ *_returned); the
census classifies the fourteen cut keys and the eight severity spellings by what
each actually is and fails a retired or unclassified spelling by name.
McpHelpers.ValidateDaysBack adopts the five inline days_back refusals and
list_servers goes through FormatError; FormatError's shape is untouched (Q11).
…and token (overall_health = NoData beside health_band), and asserts the two keys agree
@erikdarlingdata
erikdarlingdata enabled auto-merge (squash) September 19, 2026 08:14
@erikdarlingdata
erikdarlingdata merged commit 13c76a5 into dev Sep 19, 2026
7 of 8 checks passed
@erikdarlingdata
erikdarlingdata deleted the fix/3653-vocabulary branch September 19, 2026 08:15
This was referenced Sep 19, 2026
erikdarlingdata added a commit that referenced this pull request Sep 20, 2026
… the page dialect's truncated: get_query_trend, the duration-trend trio and get_query_store_top publish the store's reach under its own key, every consumer and description follows, and the vocabulary census holds the two facts apart (partial #3653 — item 17) (#3793)

`truncated` carried two facts on the wire. On twenty-odd paged tools it is the caller's `limit` biting — observed off a `cap + 1` fetch, beside `*_returned`, remedied by a bigger limit. On the #2364 / #2353 trend family and on get_query_store_top it was the store's REACH: the served series begins later than the requested start because the tier that answered no longer holds the window's head, beside `effective_start` / `effective_hours_back`, and no limit changes it. #3703 classified the homonym and left it as stated residue; #3706 counted the blast radius (~15) and stopped. This is that rename.

Emitters (4): DarlingMcpTrendTools get_query_trend initializer + TrendDisclosure.WriteTo (the trio, data and empty envelopes); DarlingMcpDataTools get_query_store_top (truncation_note keeps its name — it is the prose for this flag); Lite McpQueryTools.WriteDisclosure (the trio + get_query_trend). Consumers: server-tabs.js drawQueryTrend reads window_truncated (undefined is falsy — the old key would have silently dropped the notice). Descriptions: McpHelpers.WindowTruncatedDescription, one shared clause on the nine publishing tools (5 Darling, 4 Lite), naming the key, the reach beside it, and the WIRE CHANGE; nine instructions rows and five web-catalog rows say window_truncated. C# members (Truncated, TruncationSlack, DescribeCoverage) keep their names — the wire key renamed.

Census: McpPayloadContractCensusTests classifies window_truncated as the second bound it is (SecondBoundCutKeys), reads the ordered-envelope idiom (`envelope["key"] =`) the disclosure block uses so Lite's writer is in the roster, and adds TheWindowFloor_IsSpelledWindowTruncated_BesideItsReach_AndNeverBareTruncated_OnBothSkus: every block on both SKUs that writes effective_hours_back must write window_truncated and effective_start and never bare truncated — brace-walked over strings-blanked code, rostered as an equality on (file, idiom, count), checked first on a synthetic offender and a synthetic page cut. The discriminator is the reach vocabulary, not window_start: get_query_heatmap publishes window_start beside a bare truncated that IS a page cut. EveryWindowFloorTool_CarriesTheSharedClause_AndNoOtherToolDoes is the description half. The residue sentence is gone.
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