Skip to content

validate_custom_view/run_custom_view_panel move reading guidance off tools/list (#3898) - #4116

Merged
erikdarlingdata merged 5 commits into
devfrom
feature/3898-content-customview
Sep 24, 2026
Merged

erikdarlingdata merged 5 commits into
devfrom
feature/3898-content-customview

Conversation

@erikdarlingdata

@erikdarlingdata erikdarlingdata commented Sep 24, 2026 •

Copy link
Copy Markdown
Owner

Refs #3898.

Why

Issue #3898: a client that loads Darling's whole MCP catalog pays for every tool description before it asks its
first question. validate_custom_view and run_custom_view_panel served 1,576 and 1,084 characters each,
mostly reading guidance rather than facts needed to pick the tool. This moves that guidance off tools/list and
into get_tool_guide, so nothing is deleted, but the catalog gets smaller.

What changes

Both tools are Darling-only (no Lite twin exists for either name). Each description now carries a short head.
tools/list serves the head, plus a Reading guide: get_tool_guide. pointer. The full original text becomes
a tail, served only by get_tool_guide. No tool body, SQL, or parameter was touched.

Tool Darling before Darling after Lite before Lite after
validate_custom_view 1,576 561 N/A, no Lite twin N/A
run_custom_view_panel 1,084 612 N/A, no Lite twin N/A

Corrections

None. Every fact in the original descriptions checked out against DarlingWebEndpoints.ValidateDefinition,
RunComposedPanelAsync, and ComposeStoreAvailability (notice/row-cap logic).

D4 removals

None. Neither description carried an issue reference or an anecdote.

Re-pointed pins

None needed. No existing test pinned a literal sentence from either description. The full suite passed
unmodified after the conversion (see Test plan).

Status routes (the status words the heads state)

validate_custom_view does not use the {status, message} envelope on a normal answer. It returns
{valid, error} instead:

  • valid:true / valid:false, error:"..." - DarlingMcpCustomViewTools.cs:119-120, from
    DarlingWebEndpoints.ValidateDefinition's DefinitionValidation.IsValid/.Error. ValidateDefinition is
    fail-fast. The first failing check returns, so a false answer never lists a second problem.
  • status:"error" - only from the outer catch (Exception ex) at DarlingMcpCustomViewTools.cs:124
    (McpHelpers.FormatError). This is an unhandled exception, not a normal validation miss.

run_custom_view_panel has no status field on a successful answer ({sql, rows, annotations, notice?},
DarlingMcpCustomViewTools.cs:283-284). On failure it uses the local Outcome(status, message) envelope:

  • status:"invalid" - a malformed spec JSON (:274) or a non-object body (:279), both in
    DarlingMcpCustomViewTools.cs. Every ComposeRunOutcome.BadRequest(...) inside RunComposedPanelAsync
    lands here too. Causes: a missing or doubly-nested panel, bad variables, a panel that fails
    ComposeSpec.TryParsePanel, or a malformed or oversized absolute window (DarlingWebEndpoints.cs:956-1028).
    A PostgresException from the bounded query, for example a statement_timeout, also lands here (:1086).
    All of these surface through DarlingMcpCustomViewTools.cs:285
    (outcome.IsServerError ? "error" : "invalid").
  • status:"error" - ComposeRunOutcome.ServerError(...) at DarlingWebEndpoints.cs:1090, an unexpected
    exception during the run rather than a PostgresException. The outer catch at
    DarlingMcpCustomViewTools.cs:289 (McpHelpers.FormatError) also lands here.
  • notice (present only on a successful payload) - DarlingWebEndpoints.cs:1074-1079 sets it when
    ComposeStoreAvailability.CombineNotices(...) (ComposeStoreAvailability.cs:353-357) returns non-null. That
    happens when BuildRetentionNotice (partial retention coverage) or BuildRowCapNotice (row cap hit) fires.
    The key is absent entirely, not null, when neither one does.

D9 traps

  • Q: validate_custom_view answers {valid:false, error:"unknown key 'filter', did you mean 'filters'?"}.
    After renaming the key, is the definition now guaranteed valid?
    Correct: Not necessarily. The error names only the first problem found, so fixing it can surface another
    one underneath.
    Prevents it: "naming only the FIRST problem found; fixing it can reveal more."
  • Q: A definition has one misspelled key, like aggregat instead of aggregate. Does validate_custom_view
    accept the definition and just skip that field as if it were absent?
    Correct: No. Unknown keys are errors at every level (root, panel, cell, filter, overlay). A stray key is
    never silently dropped.
    Prevents it: "Unknown keys are ERRORS at every level (root, panel, cell, filter, overlay), never silently
    dropped."
  • Q: Running validate_custom_view against a view that is already saved in the store. Does it touch the
    stored copy?
    Correct: No. It is a dry run. Nothing is persisted regardless of the result.
    Prevents it: "WITHOUT persisting anything."
  • Q: Building a notebook cell for validate_custom_view. Do its measure fields (source, measure, and so
    on) nest inside a panel key on the cell, the way run_custom_view_panel's top-level spec nests?
    Correct: No. A notebook cell is flat. Its keys sit directly on the cell object. Only
    run_custom_view_panel's own spec nests under panel.
    Prevents it: "A notebook cell is FLAT (carries the panel's own keys directly); only
    run_custom_view_panel's spec nests under 'panel'."
  • Q: run_custom_view_panel returns {"sql": "...", "rows": [...], "annotations": [...]}. Does confirming
    success require checking a status field?
    Correct: No. There is no status field at all on success. status appears only on failure.
    Prevents it: "with no 'status' field on success."
  • Q: A spec's JSON failed to parse. Does run_custom_view_panel's answer come back with status:"error"?
    Correct: No. A bad spec is status:"invalid", not "error". So is a query that fails or times out.
    "error" is only for an unexpected internal fault.
    Prevents it: "Failures return {status, message}: "invalid" for a bad spec or panel, or a failed or
    timed-out query; "error" for an internal fault."
  • Q: A panel already validated fine under validate_custom_view. Can run_custom_view_panel still fail after
    that?
    Correct: Yes. run_custom_view_panel also executes the compiled query, and that can still fail, for
    example on a timeout. That returns status:"invalid", and validation alone never catches it.
    Prevents it: "Failures return {status, message}: "invalid" for a bad spec or panel, or a failed or
    timed-out query."
  • Q: The response has no notice field. Is part of the requested window still hidden by the store's
    retention?
    Correct: No. An absent notice specifically means neither a retention gap nor a row cap applied to this
    result.
    Prevents it: "absent means neither happened."
  • Q: A panel comes back with a notice. Does that mean the query failed?
    Correct: No. notice rides on a successful payload, and the rows are still real data. It flags a partial
    window or a truncated row count, not a failure.
    Prevents it: "with no 'status' field on success" (notice only ever appears on that same success shape).
  • Q: The spec omits hours entirely and gives no window at all. What window does run_custom_view_panel
    use?
    Correct: The last 24 hours ending now, not an unbounded or all-time query.
    Prevents it: "Window ends now: 'hours' (default 24)."
  • Q: The spec sets both hours:6 and windowStart/windowEnd. Which one does run_custom_view_panel
    honor?
    Correct: The absolute windowStart/windowEnd pair, not hours.
    Prevents it: "unless ISO-8601 'windowStart'+'windowEnd' win instead."
  • Q: No server key is sent. Does run_custom_view_panel default to one particular server, or refuse the
    call?
    Correct: Neither. An omitted (or "All") server runs the whole fleet.
    Prevents it: "'server' omitted or "All" runs the whole fleet."

Test plan

  • dotnet build Darling/Darling.Tests/Darling.Tests.csproj -c Debug: Build succeeded, 0 Warning(s), 0
    Error(s).
  • Targeted: McpToolGuide*, McpToolsListBudget*, McpDescription*, McpZeroIsAMeasurement*,
    McpPayloadContractCensus* - Total: 203, Failed: 0, Skipped: 4.
  • Every test class that mentions either tool: DarlingComposeTests and its 4 live-PostgreSQL classes,
    DarlingMcpCustomViewToolsSurfaceTests, DarlingMcpCustomViewToolsLivePostgresTests,
    DarlingWebEndpointsTests, McpPayloadContractCensusTests, and McpReadCommandTimeoutTests. Total: 491,
    Failed: 0, Skipped: 6 (live PostgreSQL, no rig in this lane).
  • Red-watch: broke the "never silently dropped" head phrase, rebuilt, confirmed
    McpToolGuideHeadsCustomViewTests failed, restored, rebuilt, confirmed green again.
  • python .../3898-d9/dropcheck.py origin/dev HEAD validate_custom_view run_custom_view_panel:
    validate_custom_view [Darling] old=1576 new=2117 sentences=8 missing=0.
    run_custom_view_panel [Darling] old=1084 new=1679 sentences=5 missing=0.
  • git merge origin/dev (clean, no conflicts), then full Darling.Tests suite once:
    Total: 13337, Errors: 0, Failed: 0, Skipped: 636, Not Run: 1. No DarlingInstallLocationTests failures (the
    brief's two known ones): DarlingInstallLocationTests: the #4052 checks pass when run non-elevated #4111 landed on dev during this merge and all 28 tests in that class pass now.
  • Lite suite: not run. This lane touched no Lite file (neither tool has a Lite twin).
  • Plain-English checker on this PR body: see below.

Coordinator verification

  • Fixed 1 fact in the run_custom_view_panel head. It said a failure returns "invalid" or "error" but not which
    one applies when. A D9 reader then said a bad spec returns "error". The code returns "invalid" for a bad spec
    or panel and for a query that fails or times out (IsServerError is false for a PostgresException). It
    returns "error" only for an unexpected internal fault. The head now says so. The lane's own D9 answer on this
    trap called a failed query "error", which the fix also corrects.
  • To stay under the cap, the head drops "its" before DATA, and "retention rollups" became "rollups". Served
    size went from 615 to 612. The budget line, the cap constant and the pin in
    McpToolGuideHeads.CustomView.cs match.
  • D9, one round (haiku, one Read): 1 misread (above). validate_custom_view is correct as written.
  • After the fix: build succeeded with 0 warnings. The targeted classes passed (budget, McpToolGuide*,
    DarlingComposeTests, DarlingMcpCustomViewToolsSurfaceTests, DarlingWebEndpointsTests, the censuses,
    McpReadCommandTimeoutTests, McpZeroIsAMeasurementTests): 628 of 628.

Handoff

Nothing deferred. Both tools in this lane's scope (validate_custom_view, run_custom_view_panel) are fully
converted and green. update_custom_view was left alone per the brief (under the served-size floor).

🤖 Generated with Claude Code

https://claude.ai/code/session_01QQh8LczP1HGLXQYAx4oTZC

erikdarlingdata and others added 5 commits September 23, 2026 22:49
…tools/list (#3898)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QQh8LczP1HGLXQYAx4oTZC
…are error (#3898)

A bad spec or panel, and a query that fails or times out, return "invalid".
Only an unexpected internal fault returns "error". The head listed both words
without saying which applies when. Served size goes from 615 to 612.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QQh8LczP1HGLXQYAx4oTZC
@erikdarlingdata
erikdarlingdata marked this pull request as ready for review September 24, 2026 03:29
@erikdarlingdata
erikdarlingdata enabled auto-merge (squash) September 24, 2026 03:29
@erikdarlingdata
erikdarlingdata merged commit 4387fcd into dev Sep 24, 2026
16 of 18 checks passed
@erikdarlingdata
erikdarlingdata deleted the feature/3898-content-customview branch September 24, 2026 04:02
erikdarlingdata added a commit that referenced this pull request Sep 24, 2026
* Add 48 buffered CHANGELOG entries in one splice

Adds 48 entries and 48 link refs (#4057, #4077, #4079, #4081, #4082, #4083, #4084, #4085, #4086, #4087, #4088, #4089, #4090, #4091, #4092, #4093, #4095, #4096, #4099, #4100, #4101, #4103, #4105, #4106, #4107, #4108, #4109, #4110, #4111, #4113, #4114, #4115, #4116, #4117, #4118, #4119, #4120, #4121, #4122, #4123, #4124, #4125, #4126, #4127, #4131, #4133, #4136, #4141). Each PR's entry was buffered, and this lands every entry whose PR was merged on origin/dev when it ran.

Entries found under a bare section line (none unless the old script ran again) move into the matching ### section, below the new entries.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QQh8LczP1HGLXQYAx4oTZC

* Changelog splice: add #4137's entry, which merged before the splice but was missed

#4137 (pg_plan_capture reads csvlog) merged at 05:57Z with a CHANGELOG entry section in its
body. It was neither spliced nor listed as needing no entry. Its entry goes under Fixed, right
after #4136's, and #4136's last sentence now points to it instead of saying plan capture is
unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUU29PuGg9TUFBsACgZg2K

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.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