Repository navigation
CrossAppGuardCiGateTests anchors on Lite/, so the two Darling.Tests reads of Lite.Tests/ are invisible to it #3067
Description
CrossAppGuardCiGateTests requires that when one SKU's test project reads the other's source, that read is reachable by the CI path filter gating its suite. Two such reads exist on dev today and it sees neither, because the otherApp anchor is the app directory and Lite's test project is a sibling of it rather than a child.
The two references
Darling/Darling.Tests/QueryHighDopStaleMaxDopParityTests.cs:479—const string twin = "Lite.Tests/QueryHighDopStaleMaxDopParityTests.cs";, thenFile.Exists(RepoPath(twin))and a parse of the file's declaredExpectedGuardCopies.Darling/Darling.Tests/ParameterSensitivityFiringSignatureParityTests.cs:336— the same shape againstLite.Tests/ParameterSensitivityFiringSignatureParityTests.csand itsExpectedSignatureCopies.
Both read real files that exist on disk. Both are exactly the cross-app source read this guard exists to require be filter-reachable.
The mechanism
For the Darling.Tests arm the guard passes otherApp: "Lite", and both matchers anchor on otherApp followed by a separator:
Regex.Matches(text, "\"(" + Regex.Escape(otherApp) + "[/\\\\][^\"]+)\"")Lite.Tests/… does not match Lite/…, so neither reference enters the found set. The reverse arm does not have this problem: otherApp is Darling and Darling's test project is at Darling/Darling.Tests, inside Darling/.
This is a different cause from #3063. That one was the *.cs-only population — project XML was never opened — and it had no instance on dev until #3059 merged. This one is the anchor, and it is live now. #3065 fixed the population and left this deliberately, because it is a decision rather than a repair.
Why this is not a one-line widening
The darling filter names Lite/**/*.cs and Lite/**/*.xaml but not Lite.Tests/**. So the moment the anchor sees these two references, the guard goes red, and the exits are:
(a) Add Lite.Tests/**/!(*.md) to the darling filter. #3059 weighed and rejected exactly this: the darling filter also gates the live-PostgreSQL darling-pg job, so naming Lite's test tree there would stand up a throwaway TimescaleDB cluster for a change to any of ~300 Lite test files. That cost is the reason the entry was declined once already.
(b) Teach the guard that darling-tree-guards coverage counts. That job runs Darling.Tests whenever the build job's Run Darling tests step reports skipped, which is precisely when the darling filter did not fire. So for the Darling.Tests arm, coverage is already total — and an exemption on that basis would make the entire Darling.Tests arm of this guard vacuous, not just these two entries. That is a real argument for it and a real argument against it, which is why it needs a call.
(c) Something narrower — e.g. a per-reference allow-list carrying the darling-tree-guards reasoning as its stated bound, in the shape CommentFilterAdoptionTests (#3059) uses for its own legitimate exceptions. Keeps the arm non-vacuous and keeps the two entries honest, at the cost of a list somebody has to maintain.
Nothing is unguarded today
A Lite.Tests-only change leaves darling-tests reporting skipped, so darling-tree-guards runs the whole Darling suite and both twin guards execute. A change touching Darling/** fires darling and they execute there. Either way they run.
As with #3063, what is missing is the guard's ability to know that, and the failure direction is the same one: this guard fails toward not noticing, and a missed reference is silent.
The measurement that should decide
Between (a) and (c): how often would a Lite.Tests-only change be forced to stand up the darling-pg cluster. That is countable off recent merge history — the darling-tree-guards comment already records that 92 of 100 PRs report there without spinning up .NET at all, so the same window can say how many touched Lite.Tests without touching Darling/**. If the answer is a large fraction, (a) is as expensive as #3059 judged it and (c) wins.
Provenance
Found while fixing #3063 (PR #3065), by measuring the framing #3059's adjacent finding had proposed — that the detector "keys on Lite/ and Darling/ while Lite.Tests is a sibling of Lite". That was not the cause of #3059's own invisibility, which was the population. It is, however, a correct description of a second and currently-live gap, so it is recorded here on its own rather than folded into a population fix.
Activity
erikdarlingdata commentedon Sep 6, 2026
Claude posting for Erik Darling
Fixed by #3075, merged to
devas0f888e90c13. Closing by hand — a closing keyword does not fire when the PR targetsdevrather than the default branch.Resolved via exit (c): a four-entry allow-list compared for set equality, honoured only while
WholeTreeBackstopIsIntact. Two corrections to this issue's own record are worth carrying forward:CommentFilterAdoptionTests.cs:99and:106are the ones this issue missed, and they are the strongest instances — that class enumerates the whole tree.build.ymldeclares three separatedarling:filter blocks (lines 162build, 673darling-pg, 854darling-linux), anddarling-pgreads its own; nothing added to the build job's filter can stand up its cluster.darling-tree-guardshas no paths filter at all. The same false claim was committed to the tree as Fixes #3052 #3059's stated reason and is corrected in Fixes #3067 #3075.The measurement inverted the basis for the decision: over 100 PRs merged to
dev(#2919–#3073), six touchedLite.Tests/**withoutDarling/**and all six already ran the Darling suite — so exit (a) buys zero additional guard execution, paid for by compiling the Darling Viewer on a Lite test-file edit.A third distinct spelling is filed as #3076: a cross-app read written as a bare directory name carries no separator and is invisible to the matcher.
Floor the two-SKU loop in the bare-directory pin
Fixes #3076 (#3079)
Derive the injected-gap arms instead of naming a restore line
Make every locked-mode restore CI runs able to fail its step, and rea…