Repository navigation
Two query providers disagree about which selectors exist: 'compilationStatus' resolves on Postgres and is blind in QueryEvaluator, with no test comparing themΒ #3511
Description
π¨ AGENTS.md's prescribed pre-prod NodeType sweep cannot match anything β the selector is not a property of the queried type
AGENTS.md, under "Green CI does NOT mean the mesh compiles", names one instrument and argues for it
by construction:
The instrument is
search 'nodeType:NodeType compilationStatus:Error'β ONE non-truncated call,
and it reads the SAME field the readiness gate reads (ClassifyDetailedbranches on
definition.CompilationStatus == CompilationStatus.Error), so the sweep agrees with the gate BY
CONSTRUCTION rather than by coincidence.
The two cannot agree by construction, because the search cannot reach that field at all.
The chain, read on origin/main
-
QueryEvaluator.GetDirectPropertyValue(src/MeshWeaver.Hosting/Persistence/Query/QueryEvaluator.cs:139)
resolves a selector by reflecting against the CLR object:var property = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); return property?.GetValue(obj);
property?.β an unknown selector yields null. There is noContentfallback on this path. -
The queried object is
MeshNode(src/MeshWeaver.Mesh.Contract/MeshNode.cs:77). It has no
CompilationStatusproperty. The field lives onNodeTypeDefinition
(src/MeshWeaver.Graph.Contract/NodeTypeDefinition.cs) β i.e. inside the node's Content. -
compilationStatustherefore evaluates tonull, the conditionnull == "Error"does not match,
and the query returns zero rows β indistinguishable from "no NodeType is in Error". -
π¨ This is not scoped to in-memory or FileSystem hosts.
StorageAdapterMeshQueryProviderβ the storage-backed provider, Postgres included β holds
private readonly QueryEvaluator _evaluator = new();and matches through it (its own comment at
~1430 saysQueryEvaluator.Matches). Conditions are evaluated in process, not translated to
SQL against the JSONB content. So the selector is unreachable on every backend. -
No aliasing rescues it:
QueryParsermaps only two legacy scope aliases, and nothing anywhere maps
compilationStatusonto a content path.
The readiness gate is fine β it reads definition.CompilationStatus off a materialised
NodeTypeDefinition, which is a real CLR property. Only the sweep cannot see it.
Why this is the worst possible instance of the shape
The same AGENTS.md section warns, three paragraphs earlier, that "a search_chunks answer carrying
searched: false is a FAILED sweep, not a clean one", and the document as a whole is built around
absence of a match must never read as absence of a subject. This is that defect in the document's
own prescribed instrument, for the check that gates a production deploy β and it returns the
reassuring answer, every time, on every backend.
It also silently outranks the alternatives the same section demotes. get_diagnostics and
LspDiagnosticsForNode are described as less reliable (measured wrong in 3 of 5 contested cases,
in both directions) β but they at least look. An agent following the document exactly uses the one
instrument that cannot.
π¨ What would falsify this, and it should be run before anyone acts
This is a code read, not an execution. I have not run the query against a live mesh. The decisive
check is one command on a mesh known to hold a NodeType in Error:
search 'nodeType:NodeType compilationStatus:Error'
- returns that node β this issue is wrong, and the resolution path is elsewhere (say so on the issue);
- returns nothing while
get @<that node>showscompilationStatus: Errorβ confirmed.
π¨ Do not run it on a mesh with no Error node and read the empty result as a pass β that is the
defect itself.
What a fix has to do
- Make the selector reachable β a
Contentfallback inGetDirectPropertyValue, or an explicit
selectorβcontent-path mapping. AContentfallback is a behaviour change for every query and
needs its own thought, not a one-liner. - And publish a denominator, because the whole point is that a zero must be distinguishable from
a blind zero: the sweep should report how manynodeType:NodeTypenodes it examined.0 of 0and
0 of 412are different answers and currently spell the same. - Then correct the AGENTS.md paragraph, which is a shared rule block and changes in every repo of the
fleet together.
Found by an agent working #3472; verified here through the code path. Related: #3396 (the measurement
that demoted get_diagnostics and LSP), #2741 (searched: false), and
Doc/Architecture/NodeTypeCompilation.
Activity
rbuergi commentedon Sep 7, 2026
π¨ Correcting my own filing: the defect is real, the fix I proposed is wrong β and the right one is a single string
I said this needed a
Contentfallback inGetDirectPropertyValueor a selectorβcontent-path mapping,and called that "a behaviour change for every query [that] needs its own thought". That is not
required. The instrument is simply written with the wrong selector.
QueryEvaluator.GetPropertyValuesplits the selector on.and walks it:ContentIS a public property ofMeshNode(one of 24 reachable selectors:Category, Content, CreatedBy, CreatedDate, Description, DesiredId, ExcludeFromContext, HasExplicitMainNode, HubConfiguration, Icon, Id, IsDefinitionOnly, IsSatelliteType, LastModified, LastModifiedBy, MainNode, Name, Namespace, NodeType, Order, PreRenderedHtml, State, SyncBehavior, Version). So a dottedselector reaches straight into the content β and the second hop works on both content shapes:
typed, via reflection with
IgnoreCase; untypedJsonElement, viaGetJsonPropertyValue, which hasits own case-insensitive fallback.
QueryParseraccepts.in a selector token (QueryParser.cs:868).And this is the established idiom, not a discovery β it is what production code already does:
So the fix is:
One string in AGENTS.md. π¨ It is a shared rule block, so it changes in every repo of the fleet
together β that is the only reason this is not a two-minute change.
This makes the issue more urgent, not less. The defect is unchanged β the documented instrument
returns nothing, on every backend, and reads as a clean mesh β but the remedy is cheap and safe
rather than a query-engine change nobody wanted to make during a release.
The falsification now has two arms, and both should be run together
On a mesh known to hold a NodeType in
Error:search 'nodeType:NodeType compilationStatus:Error'search 'nodeType:NodeType content.compilationStatus:Error'Both arms matter: the first alone could be explained by there being no Error node, which is exactly
the ambiguity this issue is about. π¨ Still a code read, not an execution β I have not run either.
One consequence worth stating
Every agent that has followed AGENTS.md's sweep instruction has run a query that cannot match, and
had no way to tell that from a clean mesh β the denominator recommendation stands for that reason
alone: the sweep should report how many
nodeType:NodeTypenodes it examined, so0 of 0and0 of 412stop spelling the same thing.rbuergi commentedon Sep 7, 2026
Measured on the live Postgres-backed mesh (
memexMCP), 2026-09-07 00:3xZ β the sweep DOES discriminate therenodeType:NodeType compilationStatus:ErrornodeType:NodeType content.compilationStatus:ErrornodeType:NodeType compilationStatus:OknodeType:NodeTypeSo on the production path the bare selector selects the Error set exactly: the Postgres provider must be mapping a field that is not a
MeshNodecolumn into the content JSONB β the parser passescompilationStatusthrough unprefixed (checked at155b4fa17: the term becomes the selector as written), and the in-processQueryEvaluator.GetDirectPropertyValuehas no Content fallback, as the body says. The claim that holds is narrower than filed: the in-memory evaluator is blind to Content fields; the Postgres SQL path is not. That leaves the sweep valid on the AKS portals (PG) and invalid on anything the in-memory evaluator serves β a Monolith on the file adapter, static nodes, tests β which is worth fixing with a Content fallback in the evaluator, and worth a test that runs the same query through both providers and demands the same answer.Note what the working sweep found tonight: four
Store/*NodeTypes inErrorsince 22:39:32Z on this instance β a separate issue follows.rbuergi commentedon Sep 7, 2026
π¨ CORRECTION β "reaches nothing, every backend" is FALSE. The bare form works on Postgres.
Measured on the live Postgres mesh by another session, and it settles it:
nodeType:NodeType compilationStatus:ErrornodeType:NodeType content.compilationStatus:ErrornodeType:NodeType compilationStatus:OknodeType:NodeTypeThe bare predicate discriminates on the production path β the Postgres provider maps a non-column
field into the content JSONB. So the sweep AGENTS.md prescribes is not blind on an AKS portal,
which is where it is actually run before a deploy. My headline claim was wrong and would have misled
the next reader in the most consequential place.
Where my code read was right, and it is a narrower thing: the in-memory
QueryEvaluatorhasno
Contentfallback, soGetDirectPropertyValuereturns null forcompilationStatusagainst aMeshNode. That makes the bare form blind on the Monolith/file adapter, static nodes and tests βa real gap, and the reason a test written against those hosts cannot catch a regression in the other.
What I got wrong, and why, since the mechanism is the reusable part: I saw
StorageAdapterMeshQueryProviderholdprivate readonly QueryEvaluator _evaluator = new();andconcluded every storage-backed path β Postgres included β matched through it. It does not; there is a
Postgres-specific query path that pushes the predicate into SQL. π¨ The falsifying artefact was in my
own terminal minutes earlier: a doc line naming
PostgreSqlPartitionedMeshQuery.EnumerateFanOutAsync.I read it and did not apply it. "A field held is not a field used on every path" β the same shape as
reading a gate's presence as its coverage.
What still stands
content.compilationStatus:Errorβ it is theestablished production idiom, it works on every path, and it removes the split entirely.
QueryEvaluatorshould get theContentfallback, so the two providers answer the samequestion the same way.
defect this issue should carry: two implementations of one query language, disagreeing about which
selectors exist, with no test that compares them. Every consequence here β including my wrong
conclusion β follows from that.
0 should say
0 of 412, so a blind path and a clean mesh stop spelling the same thing.The title is now wrong too and should be re-scoped to the evaluator/provider divergence rather than
the sweep.
[-]The prescribed pre-prod NodeType sweep matches nothing: 'compilationStatus' is not a property of MeshNode and QueryEvaluator has no Content fallback[/-][+]Two query providers disagree about which selectors exist: 'compilationStatus' resolves on Postgres and is blind in QueryEvaluator, with no test comparing them[/+]Make the sort comparator total β the fallback made its partiality reaβ¦