Skip to content

Add read-only Repl plan exploration pilot#388

Draft
autocarl wants to merge 16 commits into
erikdarlingdata:devfrom
autocarl:agent/repl-readonly-pilot
Draft

Add read-only Repl plan exploration pilot#388
autocarl wants to merge 16 commits into
erikdarlingdata:devfrom
autocarl:agent/repl-readonly-pilot

Conversation

@autocarl

@autocarl autocarl commented Jul 15, 2026

Copy link
Copy Markdown

Context: unsolicited exploratory proposal

This is an unsolicited exploratory contribution proposing that Performance Studio add Repl. It was not requested, sponsored, or pre-approved by the Performance Studio maintainers, and the proposing contributor is also involved in Repl's development.

The goal is to provide a concrete prototype for evaluation—not to imply that the project has selected Repl, committed to this direction, or placed it on its roadmap. There is no expectation that this proposal be accepted; declining or closing it is a completely valid outcome if the dependency or direction does not fit the project.

What does this PR do?

Adds an experimental Repl surface for loading and exploring SQL Server execution plans without replacing the existing System.CommandLine CLI.

  • pins Repl, Repl.Mcp, and Repl.Testing to the stable 0.11.0 release
  • introduces a bounded, thread-safe shared plan catalog and typed PlanOperations in Core
  • adds interactive and one-shot Repl routes for open/list/close, summary, warnings, expensive operators, and missing indexes
  • exposes an explicit allow-list of the Repl command graph over MCP stdio as server planview
  • delegates the existing Avalonia MCP plan-summary/warnings/operator/index tools to the same operations while preserving their historical tool names, envelopes, warning scope, invalid-severity response, bare object_name, and numeric actual-stat defaults
  • keeps analyze, query-store, credential, and their existing System.CommandLine behavior unchanged
  • documents the pilot commands, architecture, security boundaries, and compatibility scope

Security and resource boundaries

The generated MCP server is stricter than local CLI file access:

  • plan_open is confined to native MCP client roots after canonical path and symbolic-link resolution
  • when roots are unavailable, the server launch directory is the explicit fallback root
  • errors do not disclose rejected absolute paths
  • only seven canonical plan tools are exported; automatic read-only resource promotion is disabled
  • open and close are correctly treated as catalog-mutating tools, not read-only resources
  • streaming reads remain capped at 16 MiB even if a file grows during loading; plans are limited to 10,000 statements and 100,000 operators, concurrent opens to 2, and the catalog to 32 sessions
  • session admission is atomic under concurrency and plan_close provides explicit eviction
  • file loading and analysis honor cancellation checkpoints

Read-only here means no plan file, database, Query Store, or credential is mutated. open/close only mutate the process-local session catalog.

Which component(s) does this affect?

  • Desktop App (PlanViewer.App)
  • Core Library (PlanViewer.Core)
  • CLI Tool (PlanViewer.Cli)
  • SSMS Extension (PlanViewer.Ssms)
  • Tests
  • Documentation

Maintainer-review follow-up

  • The desktop MCP host now passes AnalyzerConfig.Default, so an unrelated malformed planview.config.json cannot prevent startup.
  • Committed App MCP contract tests pin the existing warning scope, invalid-severity response, bare object name, numeric default metrics, and one-snapshot lookup behavior. The implementation deliberately preserves those historical contents rather than accepting silent client-facing changes.
  • Parser errors are surfaced before analysis.
  • The CLI MCP server is named planview.
  • open and list are reserved when deriving session IDs.
  • summary/missing-index/warning/operator errors are translated consistently.
  • The real stdio MCP test has a 20-second linked timeout.
  • Root CLI help now points users toward the no-argument interactive mode; README warns that bare planview waits on stdin.

Repl dependency status

0.11.0 is the current stable Repl release. The APIs used by this pilot are exercised by the one-shot, persistent-session, and real MCP tests.

The Repl repository is MIT licensed, but I confirmed that the 0.11.0 NuGet packages still omit license metadata. Adding PackageLicenseExpression requires a subsequent Repl package publication; this PR documents the issue but does not claim to fix metadata in an external package.

How was this tested?

Platform: Linux, .NET SDK 10.0.301.

Plan fixtures:

  • estimated row-goal plan (row_goal_plan.sqlplan)
  • estimated plan with operator warnings and a missing-index suggestion (top_above_scan_plan.sqlplan)

Checks:

  • dotnet build PlanViewer.sln -c Release --nologo succeeds with 0 errors; the only warnings are pre-existing Avalonia/package warnings (upstream/dev reports 8 under the same build)
  • dotnet test PlanViewer.sln -c Release --no-build --nologo — 147/147 tests pass
  • direct CI project command — 147/147 tests pass
  • real MCP stdio smoke validates the exact tool list, annotations, empty resource list, client-root confinement, symlink escape rejection, path redaction, and allowed open/summary round trip
  • interactive Repl smoke covers open, context navigation, summary, close/eviction, and exit
  • one-shot Repl JSON smoke succeeds
  • legacy compact analyze JSON is byte-for-byte identical to upstream/dev for row_goal_plan.sqlplan (SHA-256 0c609fed8e250d9366eb9a6cd5eaf40b661ee30d7ba2546bd7726960592e9d87)
  • scoped dotnet format ... --verify-no-changes succeeds
  • git diff --check succeeds
  • added-line secret/process/XML/deserialization scan reports no findings
  • dotnet list PlanViewer.sln package --vulnerable --include-transitive reports no vulnerable packages

Checklist

  • I have read the contributing guide
  • My code builds with zero warnings (dotnet build -c Debug) — the branch adds no warnings; the same 8 warnings reproduce on unmodified upstream/dev
  • All tests pass (dotnet test)
  • I have not introduced any hardcoded credentials or server names

Scope

This is intentionally a plan-read-only pilot. It does not migrate Query Store or credential commands, mutate plan files or databases, or replace the desktop application's existing HTTP MCP host.

@erikdarlingdata erikdarlingdata left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this — it's a carefully built pilot, and the App MCP delegation was done with more care than these refactors usually get. I verified the details rather than trusting the description: analyze_plan/get_plan_summary delegate to literally the same ResultMapper.Map call; the truncation suffixes/lengths and top-validation ranges match McpHelpers exactly; the expensive-operator ranking is value-equivalent to the old raw-PlanNode path; both existing Register call sites pass keys equal to SessionId, so the new mismatch guard can't fire; the Web project's per-file linked Core sources are unaffected; and the read-only claim holds — nothing in the new surface writes. Nice work.

Requesting changes on three concrete items, plus dependency questions I'd like settled before merge.

The dependency

Repl/Repl.Mcp/Repl.Testing are pinned to 0.11.0-dev.181 — a dev-channel prerelease of your own pre-1.0 framework (last stable is 0.10.0, and dev.189 is already out). The packages restore fine from nuget.org, and the CLI isn't shipped in releases today, so the blast radius is contributor experience rather than installers — but before I take the pin:

  • What's the rough timeline for a stable 0.11 with the MCP bits, and are the APIs used here expected to survive it?
  • The NuGet packages declare no license metadata (the repo is MIT) — can you add PackageLicenseExpression to the packages so consumers aren't looking at "no license" on nuget.org?

Required

  1. Don't let a malformed planview.config.json silently kill the desktop app's MCP server. In src/PlanViewer.App/Mcp/McpHostService.cs, new PlanOperations(_sessionManager) eagerly calls ConfigLoader.Load(), which reads planview.config.json from CWD or the user's home and throws on malformed JSON. That lands in the catch at the bottom of ExecuteAsync, which only Debug.WriteLines — so the entire MCP host silently fails to start over a config file the App path never even uses (_config is only consumed by OpenAsync, which no App tool calls). Pass AnalyzerConfig.Default explicitly there, or make the config load lazy.

  2. Disclose and pin the MCP content changes. "Preserving tool names and JSON envelopes" is true for shapes, but three outputs change content:

    • get_plan_warnings now includes operator-level warnings. The old code only flattened Statements[].Warnings, which ResultMapper populates with statement-level warnings only — so the old tool underreported and disagreed with get_plan_summary totals. That's a fix, but an undisclosed one, and it changes an AI-client-facing contract.
    • get_expensive_operators.object_name now emits FullObjectName ?? ObjectName (three-part names) because it flows through ResultMapper.MapNode (ResultMapper.cs:251), where the old code emitted bare ObjectName.
    • An invalid severity argument now returns an error string (via the new ArgumentException) instead of the old "No {severity} warnings found in this plan."

    Please note these in the PR description and add an App-side test pinning the new get_plan_warnings content — which leads to:

  3. docs/repl-pilot.md claims characterization tests that aren't in the PR. "Existing MCP JSON shape characterization before delegation refactor" — no such test is committed; per the PR body that check was a manual SHA comparison. Either commit the characterization test (it's exactly the test that would have caught item 2) or reword the doc to say the verification was manual.

Suggestions (non-blocking)

  • PlanOperations.OpenAsync re-implements parse → analyze → score, which already exists as PlanAnalysisRunner.Analyze — but that helper lives in the Cli project where Core can't reach it. Consider moving the runner to Core and sharing it, so this path can't diverge from analyze when the pipeline grows a step. While there: surface plan.ParseError in the InvalidDataException message instead of the generic "Could not parse any statements" (the legacy path has the same gap; cheap to fix here).
  • The CLI stdio server and the desktop app's HTTP MCP server are both named PerformanceStudio. A client configured with both sees two identically named servers with different tool sets — name the CLI one planview.
  • Session IDs derive from filenames, so list.sqlplan / open.sqlplan produce IDs that collide with the literal plan list / plan open routes and become unaddressable. Reserve those words in CreateBaseSessionId.
  • PlanReplModule: warnings/operators wrap ArgumentException via Execute(), but summary/missing-indexes/list are bare — a session evicted between context validation and the handler throws a raw KeyNotFoundException. Wrap them uniformly.
  • README: worth calling out that bare planview now blocks on stdin (scripts that shelled out to planview expecting help text will hang), and that root --help doesn't mention plan/open/repl/mcp at all — a one-line pointer in the legacy root description would help discoverability.
  • McpSmokeTests spawns a child dotnet process with no explicit timeout — a hung MCP handshake hangs CI rather than failing. Consider a linked CancellationTokenSource with a ceiling.
  • Test placement: the routing/REPL/MCP tests make PlanViewer.Core.Tests reference the Cli project. Fine for the pilot; a PlanViewer.Cli.Tests project would keep the boundary honest.
  • Nits: (char)34\" in PlanReplTests; plan.Batches.SelectMany(...).Count() == 0!....Any(), and the emptiness check could run before Analyze/Score; InMemoryPlanCatalog is the only unsealed new class; Register(session) skips the SessionId validation that TryRegister has.

🤖 Review drafted with Claude Code

@autocarl

Copy link
Copy Markdown
Author

Thanks for the detailed review. I pushed the follow-up as two commits:

  • 4c4678b — code, contract tests, MCP/path hardening, and stable Repl packages
  • d8f90c3 — documentation and pilot guarantees

Dependency questions

  • All three packages are now pinned to the stable 0.11.0 release: Repl, Repl.Mcp, and Repl.Testing.
  • I verified that the published 0.11.0 nupkgs still omit NuGet license/repository metadata. NuGet package versions are immutable, so that remaining packaging issue requires a corrected Repl release; this PR cannot repair metadata on the already-published packages.

Required changes

  1. Malformed analyzer config: the desktop MCP host now constructs PlanOperations with AnalyzerConfig.Default, so analysis does not fail because of an empty default config.
  2. MCP response contracts: instead of documenting silent behavior changes, I restored the historical contracts for the existing desktop MCP tools:
    • warnings remain statement-scoped;
    • expensive operators retain the historical bare object_name;
    • an unknown severity retains the historical no-match response.
  3. Characterization coverage: McpPlanToolsContractTests now locks those behaviors down and verifies that tools continue using the session snapshot they already looked up.

Suggestions addressed

  • parser failures now surface the actual ParseError;
  • the generated MCP server identifies itself as planview;
  • literal open and list session IDs are reserved;
  • disappearing sessions return a consistent not-found result;
  • the README now states that bare planview starts the persistent REPL and waits on stdin;
  • MCP smoke tests have a 20-second deadline;
  • path access is confined to client roots (with canonical handle validation), and advertised empty roots fail closed;
  • file, session, statement, and operator budgets bound untrusted plan input.

I kept the two non-blocking structural suggestions (moving the analysis runner into Core and splitting CLI tests into a separate test project) out of this already-large pilot follow-up.

Verification

  • Release build: 0 errors
  • Tests: 147 passed, 0 failed
  • Focused MCP/contract tests: 15 passed
  • Legacy compact CLI output SHA-256 unchanged: 0c609fed8e250d9366eb9a6cd5eaf40b661ee30d7ba2546bd7726960592e9d87
  • NuGet vulnerability audit: no vulnerable packages reported

When convenient, could you please take another look?

@erikdarlingdata

Copy link
Copy Markdown
Owner

Thanks for the thorough follow-up — this addressed the review properly. I verified rather than skimmed:

  • AnalyzerConfig.Default in McpHostService — fixed as asked.
  • Restoring the historical desktop MCP contracts instead of documenting the changes is a better resolution than what I asked for, and McpPlanToolsContractTests pins exactly the behaviors in question — including the ThrowingCatalog proof that tools reuse the already-resolved session snapshot. The intentional desktop-vs-CLI divergence is documented in the pilot doc.
  • ParseError surfacing, the planview server name, reserved session IDs, uniform not-found handling, the README stdin note, the 20-second smoke deadlines, and the stable pin all check out. I confirmed 0.11.0 is live on nuget.org; the upstream license-metadata gap is honestly disclosed as not fixable from this repo.
  • The MCP path policy is well-engineered: lexical containment before any filesystem probe, open-once, kernel-final-path validation (GetFinalPathNameByHandle / /proc/self/fd / F_GETPATH), parsing the same handle so there is no swap window, fail-closed empty roots, no path disclosure in errors, and real end-to-end stdio tests covering all of it. I went looking for escapes — traversal, symlinks, rename swaps, 8.3 aliases, alternate data streams — and did not find one.

One regression to fix, and three optional notes.

Fix: the README screenshot link is now broken. Line 60 references screenshots/performance_studio_query-store_analysis_top_cpu_by_query_hash.png, but the file in the repo is performance_studio_querystore_analysis_top_cpu_by_query_hash.png. The prose corrections to query-store are right — that is the real command name — but the image filename should not have been renamed. Revert the filename in the link (or rename the file in this PR, either way).

Optional:

  • Session IDs now always carry a 32-hex suffix. The no-reuse property makes sense for MCP clients, but interactively plan list followed by plan row_goal_plan-<32 hex chars> summary is a lot of typing. A 6-8 character suffix keeps the property in practice at a 32-session cap, and the registration loop already retries on collision.
  • PublicApiCompatibilityTests.InMemoryPlanCatalog_RemainsUnsealed pins the class as unsealed, but nothing derives from it — the test enforces a non-contract and permanently blocks sealing. I would drop that one test.
  • Local REPL open now rejects non-.sqlplan extensions while legacy analyze reads any file — fine as a pilot constraint, just noting the divergence is user-visible.

CI is approved and running on this head.

🤖 Review drafted with Claude Code

@autocarl

Copy link
Copy Markdown
Author

@erikdarlingdata Update pushed in 03026e3947d50275241b819d22351162e2d30e12.

This includes the README link correction and 12-hex session ID suffixes you requested, plus the follow-up fixes from independent review:

  • streaming XML preflight now covers alternate statement forms, cursor operations, total elements, and total attributes;
  • retained-analysis admission accounts for those structural counts and is shared across operation facades;
  • warning/index queries use bounded collectors, operator ranking uses bounded top-K, and expensive reads have shared fail-fast admission plus cancellation;
  • Windows ADS paths are rejected and root containment uses the on-disk directory casing;
  • preflight and decoding are bound to identical bytes with streaming SHA-256 before XDocument materialization;
  • native MCP roots negotiation has a server-side timeout;
  • the desktop path uses the shared analysis/scoring pipeline while preserving historical plan_source values;
  • Query Store sessions release the parser graph but retain the minimal raw/database data required by the historical parameter, XML, comparison, and repro tools. A contract test compares all four outputs exactly against a graph-backed session;
  • bare planview remains on the legacy System.CommandLine route; interactive mode is explicitly planview repl.

Validation on this head:

  • Release build: 0 errors;
  • tests: 182/182 passed;
  • historical compact CLI SHA-256 remains 0c609fed8e250d9366eb9a6cd5eaf40b661ee30d7ba2546bd7726960592e9d87;
  • historical App source and binary consumer probes both returned APP_API_OK;
  • NuGet audit reported no vulnerable packages;
  • the 128-call stdio admission probe observed at most 2 live .sqlplan descriptors and 0 residual descriptors after close.

I am still running the final independent review panel against the updated head, so this is a progress update rather than a claim that the PR is merge-ready. I will follow up with the final panel result and any remaining fixes.

@autocarl

Copy link
Copy Markdown
Author

@erikdarlingdata Thanks — I applied the requested convergence in 1372214187a5bd116459457c7b6bf43b720d7a14:

  • plan_open now uses case-insensitive containment on Windows/macOS and ordinal containment on Linux. Windows segment resolution accepts client casing, and the pilot docs explicitly mark case-sensitive Windows/macOS configurations out of scope.
  • The shared two-open gate now queues with cancellable WaitAsync before path/file acquisition instead of failing fast.
  • The desktop App MCP host is explicitly exempt from the new query-admission cap; the generated CLI/MCP pilot retains its four-query limit.
  • The UI file-open path keeps Analysis = null and maps lazily on MCP demand.
  • Removed the App-local session/summary copies, converters, implicit operator, key/payload registration wart, dead MCP/Query Store overloads, and reflection compatibility tests. UI and Query Store now share Core.Models.PlanSession directly; host-only Core orchestration is internal again.

Validation on this exact SHA:

  • Release build: 0 errors
  • tests: 187/187
  • focused path/budget/App-MCP tests: 25/25
  • historical compact CLI SHA-256 unchanged: 0c609fed8e250d9366eb9a6cd5eaf40b661ee30d7ba2546bd7726960592e9d87
  • NuGet vulnerability audit clean; targeted format and git diff --check clean
  • 128-call stdio pressure probe: maximum 2 open .sqlplan descriptors, 0 residual descriptors

I am freezing this PR here as requested; any unrelated hardening findings will go to separate follow-up PRs.

@autocarl
autocarl marked this pull request as draft July 16, 2026 15:34

@erikdarlingdata erikdarlingdata left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed the full delta since d8f90c3 (+2,502/−452 across 37 files). Both items I asked for are fixed — README image link reverted, 12-hex session suffixes — and several things in this round are genuinely excellent: HistoricalCliContractTests running the real CLI and pinning the compact-output SHA is exactly the characterization I asked for originally; the Query Store capture contract test comparing all four raw tools against a graph-backed session is thorough; the bare-planview routing revert is the right call and consistently documented; and the legacy analyze path staying entirely outside the new preflight/budget machinery is the right separation.

That said, this round introduces three regressions that need fixing, and I want to talk about scope.

Required fixes

  1. The Ordinal path-containment switch breaks plan_open on Windows and default macOS volumes. McpPlanPathPolicy now compares containment with StringComparison.Ordinal on every platform, and ResolveDirectoryEntry requires the advertised root's per-segment casing to match on-disk casing exactly. Two concrete failures on case-insensitive filesystems:

    • Clients commonly advertise lowercased paths (VS Code famously sends file:///c:/...). A root advertised as c:\plans against on-disk C:\Plans gets dropped in ResolveDirectoryEntry → all roots dropped → every open denied.
    • Even with per-segment casing matching, the drive letter alone kills it: Directory.EnumerateDirectories("c:\") preserves the lowercase c:\ prefix, so the resolved root is c:\plans\..., while GetFinalPathNameByHandle returns canonical C:\Plans\... — the Ordinal StartsWith fails and the opened stream is discarded. Same class of failure on macOS: F_GETPATH returns on-disk casing, non-Windows roots aren't casing-canonicalized at all, and APFS defaults to case-insensitive.
    • The no-roots CWD fallback can also hard-fail: ResolveExistingDirectory(Directory.GetCurrentDirectory()) throws (uncaught) when the shell's CWD casing differs from disk (cd c:\github\... in cmd).

    None of this is exercised by CI, which is ubuntu-only. The threat being defended against — case-sensitive sibling directories on NTFS — requires a per-directory fsutil opt-in that essentially nobody enables, and it can be defended without breaking everyone: canonicalize both sides through the kernel (you already resolve the opened file's final path — resolve each root directory the same way, e.g. open a directory handle and GetFinalPathNameByHandle/realpath it) and then Ordinal-compare the two kernel-final strings. Alternatively, revert to OrdinalIgnoreCase on Windows/macOS and document the fsutil edge as out of scope. Either restores function; as written, the feature is broken for the majority of real clients on the two OSes this product ships desktop builds for.

  2. The fail-fast query cap is a regression for the desktop app's MCP server. Pre-PR, the App served any number of concurrent read tools; now AcquireQuerySlot is Wait(0) fail-fast with 4 slots shared per catalog, applied to analyze_plan, get_plan_summary, get_plan_warnings, get_missing_indexes, and get_expensive_operators. AI clients routinely issue parallel tool calls — Claude fanning out analyze_plan across five sessions now gets errors where it used to get answers. These are pure CPU-bound reads on a single-user local machine; please make the slots queue (a real WaitAsync(cancellationToken), optionally with a bounded wait) rather than fail fast — or exempt the desktop App path from query slots entirely and keep them CLI-side. Same question for the 2-slot open gate: fail-fast turns parallel plan_open calls into retry churn for the client.

  3. Eager ResultMapper.Map on the desktop file-open path is pure cost. PlanViewerControl now builds the full analysis DTO tree at registration time for every opened plan — on the load path, whether or not any MCP tool is ever called — roughly doubling retained memory for large plans (parser graph for the UI plus a full snapshot). The session.Analysis ?? ResultMapper.Map(...) fallback already handles a null snapshot lazily, which is exactly the historical behavior. The eager capture is justified for Query Store sessions (the graph is released there — that trade makes sense); for UI file sessions, please pass Analysis = null (or make it lazy) and keep map-on-demand.

Simplification request

The compatibility layer has crossed into invention. PlanViewer.App is an executable with no external consumers, but this round adds: a second App-local PlanSession/PlanSessionSummary pair with ToCore/FromCore converters and an implicit conversion operator; resurrected "historical overloads" on McpPlanTools that nothing calls (each constructing a fresh PlanOperations); reflection-based signature tests; and a test pinning the Register(key, session) key/payload-mismatch semantics — a wart no caller exercises. Meanwhile InMemoryPlanCatalog, which last round gained a test asserting it stays unsealed, is now internal sealed and that test is deleted — which rather proves these contracts are being invented per round. Please delete the dead overloads, the implicit operator, and the reflection tests, and keep one session type unless something real consumes the second. Complexity that defends no actual consumer is just future maintenance.

Scope

This is the third self-review wave, the PR has roughly quadrupled from the original pilot, and your comment says the panel is still running. The core of the pilot — REPL, one-shot, MCP surface, shared catalog/operations — was solid two rounds ago and is what this PR is for. Each hardening wave since has touched progressively more shipping surface (parser, analyzer, scorer, Query Store tools, the desktop load path) and this one introduced the regressions above. Please land the three fixes plus the simplification and then freeze this PR — no further panel-driven hardening waves here. If the panel surfaces more items, bring them as small follow-up PRs where each can be judged on its own.

CI is approved on the current head; note it only builds ubuntu, so item 1 will not be caught by checks — it needs the fix, not a green run, to be considered resolved.

🤖 Review drafted with Claude Code

@autocarl

Copy link
Copy Markdown
Author

@erikdarlingdata Thanks. This review appears to have been drafted against the pre-convergence tree and then submitted after 1372214 became the head: its body describes code that is no longer present on the SHA attached to the review.

The requested items are already resolved on this exact head:

  1. Path containment is OrdinalIgnoreCase on Windows/macOS and Ordinal on Linux, with the case-sensitive Windows/macOS edge explicitly documented out of scope.
  2. The desktop App host constructs PlanOperations with enforceQueryAdmission: false, while the two-open gate now queues with cancellable WaitAsync.
  3. The desktop file-open registration now stores the Core session without setting Analysis, so mapping remains lazy.
  4. The App-local session/summary types, converters, implicit operator, dead MCP/Query Store overloads, key/payload overload, and reflection compatibility tests were removed in the convergence commit. PlanSessionManager now stores Core.Models.PlanSession directly.

That convergence commit is a net simplification (151 insertions / 368 deletions), and the detailed validation was posted in the comment immediately before this review: #388 (comment)

Could you please re-review the current tree or dismiss this stale changes-request state? I am keeping the PR frozen as requested.

@erikdarlingdata
erikdarlingdata dismissed stale reviews from themself July 16, 2026 19:42

Dismissing: this review was drafted against dc03dc1 and submitted after 1372214 had already landed; every item it requests was resolved in that push. See the follow-up comment for verification.

@erikdarlingdata

Copy link
Copy Markdown
Owner

You're right about the sequencing, and that one's on me: the request-changes review was drafted against dc03dc1b and submitted at 15:59 UTC without re-checking the head, 76 minutes after 1372214 had already landed with the fixes. Sorry for the churn.

I've now verified the current head directly rather than taking the summary on faith:

  • Path containment is OrdinalIgnoreCase on Windows/macOS with Ordinal on Linux, segment resolution accepts client casing, and the case-sensitive-volume edge is documented out of scope — this resolves both the lowercase-drive-letter and advertised-casing denials.
  • The open gate queues on a cancellable WaitAsync instead of failing fast.
  • The desktop host constructs PlanOperations with enforceQueryAdmission: false, and AcquireQuerySlot is a no-op on that path, so the App MCP server is back to unbounded concurrent reads.
  • The desktop file-open registration no longer builds an eager analysis snapshot; mapping is lazy via the session.Analysis ?? ResultMapper.Map(...) fallback.
  • The App-local session/summary duplicates, converters, implicit operator, dead overloads, and reflection tests are gone; RawPlanXml/DatabaseName moved onto the Core PlanSession, and the GetRawPlanXml fallback chain keeps the Query Store tools working against gutted graphs (the four-tool exact-equality contract test survives).

I also checked the parts of 91f8617 that were outside the requested fixes — the TextFormatter and ShowPlanParser.Costs changes are mechanical cancellation-token threading with every emitted line unchanged, so neither the legacy text output nor the App summary text is affected.

I've dismissed both stale changes-requested reviews and approved CI on this head. No outstanding code requests from review; final disposition is a maintainer decision from here. Thanks for holding the freeze.

🤖 Review drafted with Claude Code

@erikdarlingdata erikdarlingdata left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First, an apology: I owe you one for the last round. I posted a request-changes review against a head you had already fixed — the review was drafted against dc03dc1b and submitted more than an hour after 1372214 landed, without re-checking the tree. That cost you a rebuttal you shouldn't have had to write and left a stale gate on the PR. That was my process failure, not yours, and I'm sorry for the churn.

With that owned: I've now done a final full-pass read of the complete PR as it stands (1372214, +4,794/−366 across 46 files), including every file from the last two commits I had previously only spot-checked. Findings:

  • All four regression fixes are verified in the final code, not just the summary: case-insensitive containment on Windows/macOS with ordinal Linux (and the platform-comparison logic unit-tested), queued cancellable open slots, enforceQueryAdmission: false resolving to a true no-op for the desktop host, and lazy analysis mapping on the desktop file-open path.
  • The simplification is real: the App-local session/summary duplicates, converters, implicit operator, dead overloads, and reflection tests are gone. One Core.Models.PlanSession (now carrying RawPlanXml/DatabaseName) serves UI, Query Store, CLI, and MCP, and PlanSessionManager is back to a straightforward catalog.
  • The Query Store path improved beyond what was asked: AdmitSnapshot gives QS sessions bounded admission with per-plan load_error context instead of the old silent catch-all, and the four-tool exact-equality contract test still passes against gutted graphs.
  • The remaining unreviewed churn (TextFormatter, ShowPlanParser.Costs, QS cancellation threading) is output-preserving — every emitted line unchanged — and the committed analyze --compact SHA-256 characterization test pins the legacy contract for good.

For the record, two accepted behavior changes to be aware of: Query Store bulk loads are now bounded by the shared 32-session / 64 MiB budget (previously unbounded; overflow reports a clear load_error instead of loading), and the get_query_store_top / missing-index envelopes gained additive load_error / columns_truncated fields. Both are documented and reasonable.

CI is green on this head with 187/187 tests. Approving. Thanks for the quality of the convergence work, for holding the freeze, and for the patience with my sequencing mistake — any further hardening findings from your panel are welcome as separate follow-up PRs.

🤖 Review drafted with Claude Code

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.

2 participants