Skip to content

fix(#4296): a negated path is a FILTER, never the anchor it excludes - #4333

Merged
meshweaver-cloud[bot] merged 1 commit into
mainfrom
fix/4296-a-negated-path-is-a-filter
Sep 14, 2026
Merged

meshweaver-cloud[bot] merged 1 commit into
mainfrom
fix/4296-a-negated-path-is-a-filter

Conversation

@rbuergi

@rbuergi rbuergi commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Closes #4296.

Reproduced before fixing

The review finding from #4290 is live. Added the four cases to ParsedQueryAnchoringTest and ran
them against the parser as it stood — all four fail:

Failed ANegatedPathIsNeverAnAnchor(query: "-path:Acme/Secret nodeType:User")
Failed ANegatedPathIsNeverAnAnchor(query: "-path:Acme/Secret|Acme/Draft nodeType:User")
Failed ANegatedPathSurvivesAsAFilterCondition(query: "-path:Acme/Secret namespace:Acme", expected: NotEqual, values: 1)
Failed ANegatedPathSurvivesAsAFilterCondition(query: "-path:Acme/Secret|Acme/Draft namespace:Acme", expected: NotIn, values: 2)
Failed! - Failed: 4, Passed: 23, Total: 27

APositivePathStillAnchors passes in both states — the negative control that says the carve-out is
for negation only.

The defect

  • -path:A|B → NotIn with two values → the In or NotIn arm set Paths = Values and
    Path = Values[0]. The operator was dropped.
  • -path:A → NotEqual → fell through to the plain else path = value. The negation was dropped.

So "everywhere EXCEPT A" reached every consumer as a positive anchor at A:
IsSufficientlySpecified() counted it as anchored, ResolvePinnedPartition pinned the query to A's
schema, and the per-schema route applied path IN (A, B). The query returned rows from exactly the
paths it meant to exclude, or nothing — with no error in either direction. Because a path anchor
also chooses the PARTITION, the exclusion re-routed a mesh-wide read onto the one partition it had
been told to skip.

The fix

NotEqual / NotIn on path join the ordered comparisons that were carved out for the same reason
in #2186: they stay filter tokens. The backend then applies n.path <> 'A' / n.path NOT IN (...) against the real, indexed path column, and the query is unanchored unless something else
anchors it — which is what it actually asked for. A query whose only "anchor" was the exclusion is
now refused by MeshOperations.Search with the specificity message (#4274) instead of being served
from the excluded partition.

Both halves are pinned, because fixing only the first would trade a wrong anchor for a query that
quietly returns the excluded rows: ANegatedPathIsNeverAnAnchor asserts Path/Paths are null and
the query is not specified; ANegatedPathSurvivesAsAFilterCondition asserts the condition survives
in the filter with its operator and value count intact.

Verification

  • MeshWeaver.Hosting.Test ParsedQueryAnchoringTest — 27/27 (was 23/27 before the parser change)
  • MeshWeaver.Hosting.Test whole project — 712/712 passed, Release
  • dotnet build src/MeshWeaver.Mesh.Contract -c Release -warnaserror — 0 Warning(s), 0 Error(s)

Reach outside this repo

The Postgres planner in MeshWeaver.Plugins reads Path/Paths through the same
ParsedQuery.IsSufficientlySpecified (since #4290) — no code change there, and the behaviour it
inherits is the corrected one: a negated-path-only query is now unanchored, so the planner refuses it
rather than pinning to the excluded schema. No in-repo caller writes -path: (swept src/ and
test/: two documentation mentions, both hypothetical), and nothing is removed, so a mesh-stored
caller that used the form gets a correct answer where it previously got the excluded rows or none.

Pairs-with: none — no public type or member leaves src/; the diff changes a parser branch, adds
tests and a What's New entry.

🤖 Generated with Claude Code

`QueryParser` folded a negated `path:` into the path ANCHOR and dropped the
operator, so a query that said "everywhere EXCEPT A" was read as "only A":

  * `-path:A|B` tokenises to NotIn with two values; the `In or NotIn` arm set
    `Paths = Values` AND `Path = Values[0]`;
  * `-path:A` tokenises to NotEqual and fell through to the plain `path = value`
    else-branch.

Every consumer of ParsedQuery then saw a positive anchor: IsSufficientlySpecified()
counted the query as anchored, ResolvePinnedPartition pinned it to A's schema, and
the per-schema route applied `path IN (A, B)` — so the query returned rows from
exactly the paths it meant to exclude, or nothing, with no error in either
direction. A wrong anchor is also a wrong PARTITION, so the exclusion re-routed a
mesh-wide read onto the one partition it had been told to skip.

Negated operators now join the ordered comparisons carved out for the same reason
in #2186: they stay filter tokens, so the backend applies `n.path <> 'A'` /
`n.path NOT IN (...)` against the real, indexed path column and the query is
UNANCHORED unless something else anchors it — which is the truth about what it
asked for. A query whose only "anchor" was the exclusion is now refused by
MeshOperations.Search with the specificity message, instead of being served from
the excluded partition. `path:a|b|c` and `path:a` are untouched.

Watched failing first: the four new cases fail against the parser as it stood —
the two anchoring rows report the excluded path as Path/Paths, the two filter rows
find no path condition in the filter at all — and APositivePathStillAnchors passes
in both states as the negative control. 27/27 in ParsedQueryAnchoringTest and
712/712 in MeshWeaver.Hosting.Test after the change.

Closes #4296.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 14, 2026 17:21

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

The fix, tests, and documentation are complete with no unresolved blocking issues.

Pull request overview

Fixes negated path: queries so they remain filters instead of becoming positive partition anchors.

Changes:

  • Preserves NotEqual and NotIn path conditions as filters.
  • Adds anchoring and filter-preservation tests.
  • Documents the user-visible fix.
File summaries
File Description
test/MeshWeaver.Hosting.Test/ParsedQueryAnchoringTest.cs Tests negated and positive path behavior.
src/MeshWeaver.Mesh.Contract/Query/QueryParser.cs Keeps negated paths out of anchor fields.
src/MeshWeaver.Documentation/Data/WhatsNew/2026-09-14-a-search-that-excludes-a-folder-no-longer-searches-only-that-folder.md Documents the corrected search behavior.
Review details
  • Files reviewed: 3/3 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 1)

516 tests   - 957   516 ✅  - 957   42s ⏱️ - 2m 9s
  1 suites  -   1     0 💤 ±  0 
  1 files    -   1     0 ❌ ±  0 

Results for commit 76ac238. ± Comparison against base commit 9429593.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 0)

262 tests  ±0   262 ✅ ±0   1m 48s ⏱️ -24s
  1 suites ±0     0 💤 ±0 
  1 files   ±0     0 ❌ ±0 

Results for commit 76ac238. ± Comparison against base commit 9429593.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 3)

1 319 tests  +227   1 319 ✅ +229   2m 56s ⏱️ - 3m 25s
    2 suites  -   1       0 💤  -   2 
    2 files    -   1       0 ❌ ±  0 

Results for commit 76ac238. ± Comparison against base commit 9429593.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 4)

1 626 tests   - 235   1 626 ✅  -  43   4m 25s ⏱️ -2s
    2 suites  -   2       0 💤  - 192 
    2 files    -   2       0 ❌ ±  0 

Results for commit 76ac238. ± Comparison against base commit 9429593.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 5)

    4 files   - 2      4 suites   - 2   5m 26s ⏱️ +11s
2 190 tests +7  2 190 ✅ +7  0 💤 ±0  0 ❌ ±0 
2 194 runs  +7  2 194 ✅ +7  0 💤 ±0  0 ❌ ±0 

Results for commit 76ac238. ± Comparison against base commit 9429593.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 2)

1 625 tests  +712   1 625 ✅ +712   9m 4s ⏱️ + 5m 39s
    3 suites +  1       0 💤 ±  0 
    3 files   +  1       0 ❌ ±  0 

Results for commit 76ac238. ± Comparison against base commit 9429593.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results

   13 files   -   5     13 suites   - 5   24m 24s ⏱️ -8s
7 538 tests  - 246  7 538 ✅  - 52  0 💤  - 194  0 ❌ ±0 
7 542 runs   - 246  7 542 ✅  - 52  0 💤  - 194  0 ❌ ±0 

Results for commit 76ac238. ± Comparison against base commit 9429593.

@meshweaver-cloud
meshweaver-cloud Bot added this pull request to the merge queue Sep 14, 2026
Merged via the queue into main with commit 7e54f3d Sep 14, 2026
37 checks passed
@rbuergi
rbuergi deleted the fix/4296-a-negated-path-is-a-filter branch October 10, 2026 13:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants