Repository navigation
Statement identity for an unknown query hash has one definition (#5758) - #5760
Conversation
Analysis references Common; SameStatementPileupDetector.StatementIdentity calls QueryHashIdentity.Identity and the copied helpers are gone. The parity test becomes QueryHashIdentityTests. Lock files rewritten by the restore are committed.
| <ItemGroup> | ||
| <!-- #5758: the statement-identity rule for an unknown query hash (QueryHashIdentity) has one definition, in Common. | ||
| Common references no project, so this edge cannot make a cycle. --> | ||
| <ProjectReference Include="..\PerformanceMonitor.Common\PerformanceMonitor.Common.csproj" /> |
There was a problem hiding this comment.
Non-blocking, and the counterpart of this edge: PerformanceMonitor.Analysis/WithheldStatementMarker.cs:12-17 still carries the rationale this line refutes —
This is a copy of
SensitiveStatements.PlaceholderTextin PerformanceMonitor.Common, held here because the readers that must recognize it (the same-statement pileup detector, and the mute rule and incident grouper in PerformanceMonitor.Notifications) cannot reference Common without changing thepackages.lock.jsonfiles of every project that references them.
That is now the only place in the tree asserting the constraint (grep -rn "cannot reference Common" → one hit), and it is false as of this diff: Analysis does reference Common, and the lock files changed. Two of its three named readers are now inside the closure that has Common — IncidentGrouping.cs:365 and MuteRule.cs:285 are in Notifications, which reaches Common through Analysis after this edge — so the sentence will mislead the next person who wants to alias the constant.
The body scopes the class itself out of this PR ("Notifications still uses it and StatementMarkerReaderTests pins it"), which I agree with: StatementMarkerReaderTests.cs:413 pins WithheldStatementMarker.Text against SensitiveStatements.PlaceholderText — the copy is pinned to the ORIGINAL, not to a sibling copy, so the #3684 "both must drift" shape does not apply to it. Only the stale reason sentence is worth a line.
| "Microsoft.Data.SqlClient": "[7.1.1, )", | ||
| "PerformanceMonitor.Common": "[3.9.0, )", | ||
| "System.Security.Cryptography.ProtectedData": "[10.0.11, )" | ||
| "PerformanceMonitor.Common": "[3.10.0, )" |
There was a problem hiding this comment.
Non-blocking, but the body under-describes this regeneration. It says only that "Lite's lock drops a stale System.Diagnostics.EventLog entry"; the rewrite actually drops four things the body does not name:
"System.Security.Cryptography.ProtectedData": "[10.0.11, )"out of thisperformancemonitor.plananalysisdependency list (this hunk) — inLite/,deprecated/Dashboard/andDarling/PerformanceMonitor.Darling.Viewer/;- the
System.Security.Cryptography.ProtectedDataCentralTransitiveentry (requested [10.0.12, ),resolved 10.0.11) in those same three; System.Diagnostics.EventLogout ofMicrosoft.Extensions.Logging.EventLog's dependency list and as aTransitiveentry indeprecated/Dashboard/too, not just Lite — and in Lite from thenet10.0-windows7.0/win-x64section as well.
The first two read oddly on their face, because PerformanceMonitor.PlanAnalysis/PerformanceMonitor.PlanAnalysis.csproj:26 still declares that package directly:
<PackageReference Include="System.Security.Cryptography.ProtectedData" VersionOverride="10.0.11" />
I do not think this is a defect: Lite.Tests/packages.lock.json, Darling/Darling.Tests/packages.lock.json, deprecated/Dashboard.Tests/packages.lock.json and tools/ui-smoke-wpf/packages.lock.json ALREADY carry the pruned shape on the base branch (their performancemonitor.plananalysis entries list only Microsoft.Data.SqlClient + PerformanceMonitor.Common, and neither package appears anywhere in them), so the pruning is what the pinned SDK's restore produces today and these three files were simply the stragglers — the same reason they were still on [3.9.0, ) while their siblings were on [3.10.0, ). Worth saying out loud in the body, though, since "drops a stale EventLog entry" reads as one incidental line and it is actually two packages across three files, one of them still declared in a csproj.
Nothing here is checkable from a read-only tree (no SDK in the review environment), so the lock-files job in build.yml:912-920 is the only witness.
There was a problem hiding this comment.
Verdict: LGTM
Claims
- VERIFIED — the
ProjectReferenceand no-cycle claim —PerformanceMonitor.Analysis.csproj:18; Common's csproj has threePackageReferences and noProjectReference. - VERIFIED —
StatementIdentitydelegates,IsUnknownHashdeleted —SameStatementPileupDetector.cs:466-467;grep -rn IsUnknownHashreturns nothing, so no caller was orphaned. - VERIFIED —
NormalizeText/ShortHashstay live —ShortHashat:412(ComputeIncidentId) and:524(StoryPathHash),NormalizeTextat:710(Preview). - VERIFIED — the correction that Common already nulls the withheld marker —
QueryHashIdentity.cs:74-78trim-ordinal-compares toSensitiveStatements.PlaceholderText(SensitiveStatements.cs:97), the same literal asWithheldStatementMarker.cs:22and the same test as itsIsMarker(:25). The removed check was redundant, not dropped. - VERIFIED — "no input differs between the two definitions", read as two implementations rather than over a corpus: the deleted
IsUnknownHashis character-for-characterIsUnknown(sameLength > 2prefix strip, same all-zero scan); the deleted text path is the same trim / collapse /ToLowerInvariantloop, same SHA-256 first-16-hex, sametext:prefix, same marker-before-fingerprint order; andQueryHash!.Trim().ToLowerInvariant()isKnownHash(h)?.ToLowerInvariant(). No input can diverge. - VERIFIED — corpus and case counts — 15 hashes (
QueryHashIdentityTests.cs:38-55) x 21 texts (:61-84) = the 315-pair loop at:109-131; 9 facts + 15 inline + 15 + 21 = 60 against the old 24. Nothing dropped. - VERIFIED, stronger than claimed — the written-out fingerprints are arithmetically right, not just self-consistent: I recomputed all ten distinct normalized texts with
sha256sumand every value at:66-83matches SHA-256's first 16 hex (select 1→822ae07d4783158b,select 2→3cf988cc782b44bc,café→850f7dc43910ff89, and the other seven). Case/whitespace-equal rows share a value because they normalize to one string. - VERIFIED — the rename leaves nothing stale — 62%-similarity rename, identical corpus membership, and
grep -rn QueryHashIdentityParityTestsacross the tree returns nothing. - VERIFIED — lock bookkeeping —
3.9.0→3.10.0matchesDirectory.Build.props:21and no lock file still contains3.9.0; underdeprecated/only the two lock files appear ingh pr diff --name-only; the whole ui-smoke hunk isperformancemonitor.analysisgaining Common. - VERIFIED —
WithheldStatementMarkerout of scope, andENTRY: none—IncidentGrouping.cs:365/MuteRule.cs:285still call it,Darling.Tests/StatementMarkerReaderTests.cs:413pins it to the Common original; and every identity, incident id and story fingerprint is byte-identical before and after, so no release note is owed. Only its stale rationale comment is worth a line (inline on the csproj). - UNVERIFIED — the locked-mode restores all exiting 0 — no .NET SDK here, and the pruned entries are exactly the half of a lock file only a restore confirms.
- UNVERIFIED — "0 warnings, 0 errors" — needs a build. Common's
NoWarncarriesNU1701for CredentialManagement (PerformanceMonitor.Common.csproj:13); Analysis's and Notifications' do not, and both gain that package transitively through this edge. - UNVERIFIED — the test results (247 / 788 / 60 / 41 and the census counts) — a run, not a read; the 60 is verified above as a static count, the pass/fail is not.
- UNVERIFIED — the merge provenance (
4c3a3b44a, "no textual conflicts",17f682c4f) — this checkout is one merge commit, so the branch history is absent.
Riskiest lines
PerformanceMonitor.Analysis.csproj:18— widest blast radius in the diff: seven lock files, plusModelContextProtocol+CredentialManagemententering the restore graph of everything reaching Analysis (Notifications most of all, which reaches Common only through this edge). UNPINNED by anything runnable here; witnesses arebuild.yml:912-920andDarling.Tests/LockedModeRestoreCoverageTests.SameStatementPileupDetector.cs:467— the value every group key, baseline match (:364) andComputeIncidentIdseed (:411) derives from; a divergence re-fingerprints recurring incidents instead of folding them. Pinned byIdentity_AndTheDetectorsRowPath_FollowTheCorpusand the fourPileup_*facts.Lite/packages.lock.json:790and its twins indeprecated/Dashboard/and the Viewer —performancemonitor.plananalysisnow omits a packagePerformanceMonitor.PlanAnalysis.csproj:26still declares directly. UNPINNED from a read-only tree; the four lock files already regenerated on the base branch carry the identical pruned shape, which is the evidence it is right.
Traps near this diff
- "Two copies of one definition pinned EQUAL to each other fail only after BOTH have drifted (#3684). Alias, do not copy." — applies, and this diff is the remedy, not the offence: the copy is gone and
:467calls the one definition. The survivingWithheldStatementMarker.Textis pinned against the ORIGINAL (StatementMarkerReaderTests.cs:413), not a sibling copy, so the both-must-drift shape misses it. - "
InternalsVisibleToon PerformanceMonitor.Analysis names Lite.Tests only — a helper Darling.Tests must call has to be public (#3688)." — does not apply: nothing moved behindinternal;StatementIdentitystays public (:466) andQueryHashIdentityis public (QueryHashIdentity.cs:33), soDarling.Tests/SameStatementPileupSourceCensusTests.cs:189keeps compiling. - "A pin that encodes the lie … Ask what a pin would fail ON (#3699)." — does not apply to the new pins: absolute values, recomputed independently (
QueryHashIdentityTests.cs:66-83). The weaker shape was the OLD test, which compared two implementations to each other. - "A roster-based census does not see the file it does not roster (#3699)." — does not apply: the detector's census derives from the tree and names the changed file by path (
SameStatementPileupSourceCensusTests.cs:48,:161), and its assertions are negative — deleting code cannot add a Query Store token or a table. - "Lite and Darling ship in parity … a fix to one SKU's reader, tool, alert or description without the counterpart's is a finding, not a nit." — does not apply: the edit is in the shared libraries both SKUs compile, so there is no per-SKU copy to miss;
Darling.Tests/LongQueryUnknownHashTests.csreads the same definition Lite's does. - "An expression-bodied member holding a
{ }property pattern is read SHORT by the … member walk (#3667)." — does not apply::466-467is expression-bodied but its body is a single call, no property pattern.
Divergence (blind read vs PR body)
One, an omission rather than a contradiction. The body accounts for the lock rewrite as version refreshes plus "Lite's lock drops a stale System.Diagnostics.EventLog entry". It also drops the System.Security.Cryptography.ProtectedData edge out of performancemonitor.plananalysis AND its CentralTransitive entry in three lock files (Lite, deprecated/Dashboard, Darling Viewer) while PerformanceMonitor.PlanAnalysis.csproj:26 still declares that package directly, and drops System.Diagnostics.EventLog from deprecated/Dashboard's lock too and from Lite's win-x64 section as well as its net10.0-windows7.0 one. My blind read flagged those as the one part of the diff the change itself does not explain; they are almost certainly correct regeneration output, since the base branch's already-regenerated lock files carry the same pruned shape. Detail inline on Lite/packages.lock.json. Everything else matches what I found before reading the body, including the self-reported 0x correction.
Reviewer checklist
- The
lock-filesjob (build.yml:912-920): every locked-mode restore exits 0. Only a real restore under theglobal.jsonSDK pin confirms the prunedProtectedData/EventLogentries — anddeprecated/Installer*'s three lock files still carry both packages, so if the pruning is SDK-driven they are the next stragglers. - Build Analysis and Notifications and look for a NEW
NU1701from CredentialManagement now that Common is in their restore graph. Warning-only, but the body claims 0 warnings and neither project carries Common's suppression. - The full Lite.Tests and Darling.Tests suites, not run locally per the body — especially
SameStatementPileupDetectorTestsand both SKUs'LongQueryUnknownHashTests, which exercise the identity through a real detector/alert path rather than the corpus.
|
Follow-up pass, range This range is a single merge commit ( Checked whether the merge interacts with this PR's own change:
No new authored lines, no new public signatures, no new SQL/collector/alerting code in this range. Per the follow-up-pass instructions, I'm leaving the dev-sourced content alone and filing no findings against it. |
There was a problem hiding this comment.
Verdict: LGTM
Claims
- UNVERIFIED — the PR body's "Dev merge (Q2)" section describes merge
4c3a3b44aand lock-fix commit17f682c4f, both already inside BASE (17f682c4f) and graded by the prior full-pass review on this PR. The body makes no claim about this range's merge (0bfc68b97); there is nothing new to grade.
Riskiest lines
- None — this range adds no authored lines.
0bfc68b97is a merge commit; its only non-dev content is this PR's pre-existing, already-reviewed diff (confirmed viagit diff 3f3b9c1d7 0bfc68b97 --stat, which shows onlyQueryHashIdentity.cs,SameStatementPileupDetector.cs, the renamed test, the.csprojedge, and the lock files — all already graded under head17f682c4f).
Traps near this diff
- No trap applies: the range contributes zero authored SQL, collector, alerting, or analysis code. The five non-merge commits in range (
3f3b9c1d7,378fed75a,b66a39c40,93f7ae606,cdf872759) are pre-existingdevcommits (merged PRs #5705, #5757, #5762, #5751, #5740), not authored on this branch, and none of their changed files overlap this PR's own changed files — so the merge needed no conflict resolution and nothing in this PR's code interacts with what it brought in.
Divergence (blind read vs PR body)
None — the PR body does not describe this range's merge, so there is no claim to diverge from. The blind read confirms the merge is file-disjoint and content-trivial relative to this PR's own (already-reviewed) diff.
Reviewer checklist
- Confirm CI is green on
0bfc68b97for the five dev-originated commits' own build/test coverage (already presumably verified when each landed ondev); nothing here is specific to this PR beyond a clean merge.
Claude posting for Erik Darling.
Closes #5758.
Why
#5721 left two copies of the statement-identity rule for an unknown query hash:
QueryHashIdentityinPerformanceMonitor.Commonand a copy inSameStatementPileupDetector(PerformanceMonitor.Analysis), held equal only by a parity test. Analysis could not reference Common then because the project edge rewrites thepackages.lock.jsonfiles underdeprecated/. Erik has now allowed committing those lock files, so the copy can go.What changes
PerformanceMonitor.Analysis.csprojgets aProjectReferencetoPerformanceMonitor.Common(Common references no project, so there is no cycle).SameStatementPileupDetector.StatementIdentityis nowQueryHashIdentity.Identity(row.QueryHash, row.QueryText). The privateIsUnknownHashis deleted.NormalizeTextandShortHashstay:Previewand the incident/story ids still use them (display only;NormalizeText's comment now says so).StatementFingerprintalready returns null for the withheld marker (it compares the trimmed text toSensitiveStatements.PlaceholderText, the same string asWithheldStatementMarker.Text). So the detector needs no separate marker check;StatementIdentityhas noWithheldStatementMarkercall any more. No input differs between the two definitions: the 15 hash x 21 text corpus (315 pairs) gives the same result both ways, with the expected value for each now written out.StatementIdentityand inQueryHashIdentity.cssay there is one definition.IsUnknownHash's comment went with the method.Lite.Tests/QueryHashIdentityParityTests.csis renamedQueryHashIdentityTests.cs. The corpus is kept whole; the "copy agrees with Common" test now asserts an explicit expected identity per case (throughQueryHashIdentity.Identityand the detector's row path), plus theories forIsUnknown/KnownHashper hash andStatementFingerprintper text.dotnet restore PerformanceMonitor.sln --force-evaluate: Lite, Lite.Tests, Darling.Tests, Darling.Viewer, anddeprecated/Dashboardanddeprecated/Dashboard.Tests. They also refresh the stale[3.9.0, )ranges to[3.10.0, ), and Lite's lock drops a staleSystem.Diagnostics.EventLogentry. Underdeprecated/only those twopackages.lock.jsonfiles changed.Not in scope:
WithheldStatementMarker(Analysis) stays, since Notifications still uses it andStatementMarkerReaderTestspins it.Test plan
[Fact]plus 15[InlineData]) and a 315-pair corpus loop. The new class runs 60 cases: 9 facts, 15 inline, 15 hash-corpus rows, 21 text-corpus rows; the corpus loop is still 315 pairs. Nothing dropped.dotnet restore PerformanceMonitor.sln --locked-modepasses;git diff --name-only origin/dev...HEAD -- deprecated/lists only the twopackages.lock.jsonfiles.*Census*and*Ratchet*: 247 total, 0 failed.*Census*and*Ratchet*: 788 total, 0 failed, 2 skipped.Note for the first test run: my first expectation for hash
0xwas wrong (IsUnknown("0x")is false, since the all-zero test needs a digit after the prefix). That is existing behavior, now pinned as such (identity0x), not a change.Dev merge (Q2)
tools/ui-smoke-wpf/packages.lock.json(dev's Dev CI: UiSmokeWpf lock file pins xunit.v3 4.0.2, and two live test classes skip without DARLING_TEST_PG (#5755) #5756 rewrote it; the project is not in the .sln):dotnet restore tools/ui-smoke-wpf/UiSmokeWpf.csproj --locked-modefailed with NU1004 (performancemonitor.analysis dependencies changed). Regenerated with--force-evaluate; the diff against origin/dev is onlyperformancemonitor.analysisgaining"PerformanceMonitor.Common": "[3.10.0, )". Dev CI: UiSmokeWpf lock file pins xunit.v3 4.0.2, and two live test classes skip without DARLING_TEST_PG (#5755) #5756's pins are untouched.git diff --name-only origin/dev...HEAD -- deprecated/lists only the twopackages.lock.jsonfiles.*Census*67/67; Darling SameStatementPileupSourceCensusTests 5/5,*Census*549 (1 skipped, 0 failed),*Ratchet*15/15 (Lite has no Ratchet class). 0 failures.SECTION: None
ENTRY: none (internal: one statement-identity rule, no behavior change)