Skip to content

Two query providers disagree about which selectors exist: 'compilationStatus' resolves on Postgres and is blind in QueryEvaluator, with no test comparing themΒ #3511

Closed

Description

@rbuergi

🚨 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 (ClassifyDetailed branches 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

  1. 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 no Content fallback on this path.

  2. The queried object is MeshNode (src/MeshWeaver.Mesh.Contract/MeshNode.cs:77). It has no
    CompilationStatus property.
    The field lives on NodeTypeDefinition
    (src/MeshWeaver.Graph.Contract/NodeTypeDefinition.cs) β€” i.e. inside the node's Content.

  3. compilationStatus therefore evaluates to null, the condition null == "Error" does not match,
    and the query returns zero rows β€” indistinguishable from "no NodeType is in Error".

  4. 🚨 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 says QueryEvaluator.Matches). Conditions are evaluated in process, not translated to
    SQL against the JSONB content. So the selector is unreachable on every backend.

  5. No aliasing rescues it: QueryParser maps only two legacy scope aliases, and nothing anywhere maps
    compilationStatus onto 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> shows compilationStatus: 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 Content fallback in GetDirectPropertyValue, or an explicit
    selector→content-path mapping. A Content fallback 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 many nodeType:NodeType nodes it examined. 0 of 0 and
    0 of 412 are 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

  1. rbuergi commented on Sep 7, 2026

    @rbuergi
    ContributorAuthor

    🚨 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 Content fallback in GetDirectPropertyValue or 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.GetPropertyValue splits the selector on . and walks it:

    var parts = selector.Split('.');
    foreach (var part in parts) { … current = GetDirectPropertyValue(current, part); }

    Content IS a public property of MeshNode (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 dotted
    selector reaches straight into the content β€” and the second hop works on both content shapes:
    typed, via reflection with IgnoreCase; untyped JsonElement, via GetJsonPropertyValue, which has
    its own case-insensitive fallback. QueryParser accepts . in a selector token (QueryParser.cs:868).

    And this is the established idiom, not a discovery β€” it is what production code already does:

    src/MeshWeaver.Graph/UserActivityLayoutAreas.cs:550   nodeType:AccessAssignment content.accessObject:{ownerId}
    src/MeshWeaver.Graph/UserActivityLayoutAreas.cs:668   nodeType:Thread -content.status:Done sort:LastModified-desc
    src/MeshWeaver.Graph/SpaceInviteService.cs:66         nodeType:User content.email:{normalizedEmail}
    src/MeshWeaver.Graph/GroupInviteExtensions.cs:93      nodeType:User content.email:{normalizedEmail}
    Doc/Architecture/NoStaticState.md:181                 nodeType:ApiToken content.tokenHash:{hash} limit:1
    

    So the fix is:

    - search 'nodeType:NodeType compilationStatus:Error'
    + search 'nodeType:NodeType content.compilationStatus:Error'

    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:

    expected if this issue is right
    search 'nodeType:NodeType compilationStatus:Error' returns nothing
    search 'nodeType:NodeType content.compilationStatus:Error' returns the node

    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:NodeType nodes it examined, so 0 of 0 and
    0 of 412 stop spelling the same thing.

  2. rbuergi commented on Sep 7, 2026

    @rbuergi
    ContributorAuthor

    Measured on the live Postgres-backed mesh (memex MCP), 2026-09-07 00:3xZ β€” the sweep DOES discriminate there

    query count
    nodeType:NodeType compilationStatus:Error 5 β€” LinkedIn/Skill, Store/Catalog, Store/Maintenance, Store/Order, Store/Plugin
    nodeType:NodeType content.compilationStatus:Error 5 β€” the same five
    nodeType:NodeType compilationStatus:Ok 195, and none of the five is among them
    nodeType:NodeType >200 (truncated at the limit)

    So on the production path the bare selector selects the Error set exactly: the Postgres provider must be mapping a field that is not a MeshNode column into the content JSONB β€” the parser passes compilationStatus through unprefixed (checked at 155b4fa17: the term becomes the selector as written), and the in-process QueryEvaluator.GetDirectPropertyValue has 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 in Error since 22:39:32Z on this instance β€” a separate issue follows.

  3. rbuergi commented on Sep 7, 2026

    @rbuergi
    ContributorAuthor

    🚨 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:

    query result
    nodeType:NodeType compilationStatus:Error 5 (LinkedIn/Skill, Store/Catalog, Store/Maintenance, Store/Order, Store/Plugin)
    nodeType:NodeType content.compilationStatus:Error the same 5
    nodeType:NodeType compilationStatus:Ok 195, none of the five among them
    nodeType:NodeType >200

    The 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 QueryEvaluator has
    no Content fallback, so GetDirectPropertyValue returns null for compilationStatus against a
    MeshNode. 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
    StorageAdapterMeshQueryProvider hold private readonly QueryEvaluator _evaluator = new(); and
    concluded 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

    • The AGENTS.md string should still become content.compilationStatus:Error β€” it is the
      established production idiom, it works on every path, and it removes the split entirely.
    • QueryEvaluator should get the Content fallback, so the two providers answer the same
      question the same way.
    • 🚨 And the test that proves it must run ONE query through BOTH providers. That is the actual
      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.
    • The denominator recommendation is unchanged and is now better motivated: a sweep that returns
      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.

  4. changed the title [-]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[/+] on Sep 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions