Live query re-query cost: one write, one walk of the mesh

The one sentence

A live query in StorageAdapterMeshQueryProvider re-reads its whole scope on every change under that scope, and the security fold keeps three such queries open for as long as the mesh runs. So every write, of anything, re-walked the partition and then the whole mesh. The cost of one write grew with the size of the mesh.

What it looked like

The CD job Plugins: bake + seal failed in 8 of 9 runs after 2026-09-29 09:41Z, with two signatures:

Both come from the same place. The gate installs about 75 packages into one in-memory mesh, one after another. Here is the per-node write time of the installer, taken from the job logs (for each package, ── X: installing N file(s) to Installed node-repo plugin X: N written):

package (install position) run 36543299719 (green) run 36642931001 (red)
Store (1st) 0.003 s/node 0.003 s/node
Edu (23rd) 0.10 0.12
Governance (~57th) 0.67 0.48
Hosting 0.23 (32nd, 397 nodes, 91 s) 1.11 (last, 414 nodes, 459 s)

Plugins #2554 added Essentials@^1.0.0 to the requires of Hosting and Feedback. That moved both packages to the end of the install order. The trigger was that reorder. The cause was that a write at the end of the install costs about 300 times what the first write costs. So Hosting, which is the largest package, was installed at the most expensive point, and the Fleet Console journey ran its OperationalSpaceProvisioning.Ensure (two queries and a few writes) straight after, at that same point.

The repro and the profile

A monolith test mesh that writes Markdown nodes one after another, 150 per batch, took 3.2 ms per write at 150 nodes, 22 ms at 1,200 and 88 ms at 4,500. The growth was linear. A dotnet-trace (dotnet-sampled-thread-time) of the late batches put 92 % of busy time under the provider's scope walk: a ToObservableRecursive<(string, QueryScope)> recursion into InMemoryStorageAdapter.ListChildPaths, reached from the live pipeline's CoalesceWhileRunning(RunQuery). A tally of the re-queries during 450 writes named exactly three queries, with 451 re-runs each:

path:TestData scope:descendants nodeType:AccessAssignment …          (SecurityQueries.PartitionAssignments)
path:TestData scope:descendants id:_Policy nodeType:PartitionAccessPolicy …   (SecurityQueries.PartitionPolicies)
nodeType:GroupMembership partitions:all scope:subtree …              (SecurityQueries.Memberships)

None of these three can ever return a Markdown node. They were re-run anyway, because the live pipeline's relevance test was PathMatcher.ShouldNotify, and that test looks at the path alone.

The fix

A change is irrelevant to a live RAW query when the query confines its rows to a fixed set of node types and neither the type the path now holds nor the type it held before is in that set. That is NodeTypeChangeRelevance, applied in ObserveQueryInternal together with the scope test.

After the fix the same repro holds ~1 ms per write, flat, from 150 nodes to 1,200.

Regression test

LiveQueryIgnoresOtherNodeTypesTest (MeshWeaver.Hosting.Test) counts walks rather than timing them. It uses a pass-through adapter over a real InMemoryStorageAdapter and counts the listings of the query's base path:

What this does not claim