Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -170,24 +170,52 @@ repository on the same night.
| compile-progress overlay | `Ok` ⇒ **redirect** to the page that cannot render, which bounces back | holds, and says why — localized (`ui.compileForeignFramework`) |
| NodeType overview progress line | ✓ **Compiled**, printing the foreign hash beside the green tick as decoration | ⚠ built for another platform build |

## 🚨 What this does NOT reach, measured

**`search 'nodeType:NodeType compilationStatus:Error'` cannot see this, and on some hosts it cannot
see anything.** Two findings, both measured while building this change:

1. **The sweep never returns the value.** `MeshOperations`' search envelope projects
`Path/Name/NodeType/Version/LastModified` only — `compilationStatus` is a WHERE clause and
nothing more. To read the value you must `get` the node or call `get_diagnostics`.
2. 🚨 **On an in-memory or FileSystem-backed host the filter matches NOTHING.**
`QueryEvaluator.GetDirectPropertyValue` resolves a selector by reflection against `MeshNode`,
which has no `compilationStatus` property and no `Content` fallback — so the comparison is
`null == "Error"`, and the sweep returns `count: 0` for a mesh full of broken types. The
translation that makes the sweep work lives in `PostgreSqlSqlGenerator`, which is not in this
repository. **A green sweep on a dev Monolith is evidence of nothing.**

Both are noted here rather than fixed: changing the query evaluator's selector resolution changes
every query in the platform, and the projection is what keeps a search result small. The instrument
that *is* reader-relative is `get_diagnostics`, which now answers `Foreign`.
## 🚨 What this does NOT reach — and a sweep whose two backends disagree

**`search 'nodeType:NodeType compilationStatus:Error'` — AGENTS.md's stated pre-deploy sweep — does
not see a foreign build, and separately, its selector does not mean the same thing on both query
backends.** Two findings, kept here because a deploy is gated on this instrument.

**1. The sweep never returns the value.** `MeshOperations`' search envelope projects
`Path/Name/NodeType/Version/LastModified` only; `compilationStatus` is a WHERE clause and nothing
more. To read a type's status you must `get` the node or call `get_diagnostics`. So even where the
filter works, the sweep answers *which* types are broken, never *what* any type's status is — and
`Foreign` is invisible to it either way, because `Foreign` is derived by the reader and the filter
runs in the store.

**2. 🚨 The two query providers disagree about which selectors exist, and nothing compares
them** ([#3511](https://github.com/Systemorph/MeshWeaver/issues/3511)). This is the finding; "the
sweep is broken" would be the wrong summary and was the wrong filing.

| backend | `compilationStatus:Error` | `content.compilationStatus:Error` |
|---|---|---|
| in-memory / FileSystem (`QueryEvaluator`) | **matches nothing** — resolves to `null` | reaches the field |
| Postgres (`PostgreSqlSqlGenerator`, out of repo) | **discriminates** — measured on a live mesh: 5 for `Error`, 195 for `Ok`, disjoint | not measured here |

`QueryEvaluator.GetDirectPropertyValue` resolves a selector by reflection against the object it is
handed — a `MeshNode` — which has no `compilationStatus` property and no `Content` fallback, so the
comparison is `null == "Error"`: false for every node, and a mesh full of broken types answers
`count: 0`. `GetPropertyValue` splits on `.` and walks, so `content.compilationStatus` resolves
`MeshNode.Content` and then `NodeTypeDefinition.CompilationStatus` case-insensitively, and
`CompareEqual` compares on `ToString()` so the enum name matches the query's literal.
`SweepSelectorReachesTheCompileStatusTest` pins all three rows of that — the dotted selector answers
`Error`, the bare one answers `null`, and a healthy type answers `Ok` rather than everything
answering `Error`.

**The consequence is not "the sweep lies" but "the sweep means different things in different
places".** On the portal a deploy is actually gated on, the bare form discriminates. On a dev
Monolith, a disposable CI mesh, or any FileSystem-backed host, the same query is green by
construction — which is the CI-gate-that-skips-on-missing-input shape: *it never ran* and *it
passed* paint the same colour. A rehearsal of the sweep on a local mesh therefore proves nothing
about the sweep, and that is exactly what makes the disagreement dangerous rather than merely
untidy.

The evaluator is **pinned rather than changed** here: giving it a `Content` fallback would alter the
meaning of every selector in every query in the platform, and closing the gap properly means one
test exercising both providers against the same records — #3511's scope, of which the pin above is
the first half. What this page fixes is the INSTRUCTION. And the instrument that is genuinely
reader-relative — the one that answers the question this page is about — is `get_diagnostics`, which
now answers `Foreign`.

## What this does not fix

Expand Down
12 changes: 10 additions & 2 deletions src/MeshWeaver.Graph/NodeTypeLayoutAreas.cs
Original file line number Diff line number Diff line change
Expand Up @@ -469,10 +469,18 @@ private static (string Icon, string Header, string Body) RenderProgressLines(
}

/// <summary>First eight characters of a framework build identity — the same width the
/// assembly-store filename tag carries, so a page and a DLL name compare by eye.</summary>
/// assembly-store filename tag carries, so a page and a DLL name compare by eye.
///
/// <para>🚨 An absent identity renders as an EM DASH, never "(none)". This value is
/// substituted into <c>ui.compileForeignFrameworkBody</c>, so an English literal here would
/// appear untranslated in the middle of a German sentence — and the house preference is
/// exactly this: a language-neutral glyph beats a translated word (AGENTS.md, i18n). The
/// English "(none)" that <c>NodeTypeBuildIdentity.Short</c> uses is correct THERE, because
/// that one feeds log lines, which stay English by house rule.</para>
/// </summary>
private static string Short(string? identity)
=> string.IsNullOrEmpty(identity)
? "(none)"
? "—"
: identity[..Math.Min(8, identity.Length)];

/// <summary>
Expand Down
20 changes: 14 additions & 6 deletions src/MeshWeaver.Mesh.Operations/MeshOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2810,16 +2810,24 @@ private IObservable<Func<MessageHubConfiguration, MessageHubConfiguration>?>
return Observable.Return<Func<MessageHubConfiguration, MessageHubConfiguration>?>(null);

return hub.GetWorkspace().GetMeshNodeStream(nodeType)
.Where(n => n?.Content is NodeTypeDefinition def
&& def.CompilationStatus == CompilationStatus.Ok
&& !string.IsNullOrEmpty(def.LatestAssemblyCollection)
&& !string.IsNullOrEmpty(def.LatestAssemblyPath))
// 🚨 ContentAs in the FILTER, not only below it. A NodeType node whose Content
// arrived un-materialized — the per-node hub's TypeRegistry lacked the $type, which is
// the ordinary shape on a cross-hub sync stream — IS a NodeType node, and
// `is NodeTypeDefinition` answers "no" for it. Such a node was filtered out here, so
// the stream never emitted, the read hung out its whole 5 s budget, and the schema
// then reported no configuration for a type that has one. A ContentAs further down
// could never have rescued that: nothing reached it.
.Where(n => n.ContentAs<NodeTypeDefinition>(hub.JsonSerializerOptions)
is { CompilationStatus: CompilationStatus.Ok } d
&& !string.IsNullOrEmpty(d.LatestAssemblyCollection)
&& !string.IsNullOrEmpty(d.LatestAssemblyPath))
.Take(1)
.Timeout(TimeSpan.FromSeconds(5))
.SelectMany(node =>
Comment on lines +2820 to 2826
{
// ContentAs, never a cast: the Where above already accepted this node, but the
// value can still arrive as raw JSON on a hub that did not register the type.
// Re-read rather than carry the filter's value: the predicate above ran on an
// earlier emission of the same stream in principle, and ContentAs is the only
// reading of Content this method makes.
var def = node.ContentAs<NodeTypeDefinition>(hub.JsonSerializerOptions);
if (def is null)
return Observable.Return<NodeCompilationResult?>(null);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
using MeshWeaver.Graph.Configuration;
using MeshWeaver.Hosting.Persistence.Query;
using MeshWeaver.Mesh;
using MeshWeaver.Mesh.Services;
using Xunit;

namespace MeshWeaver.Hosting.Test;

/// <summary>
/// 🔴 <b>The pre-deploy sweep's SELECTOR, pinned — Systemorph/MeshWeaver#3472.</b>
///
/// <para>AGENTS.md names one instrument for "is this mesh safe to deploy":
/// <c>search 'nodeType:NodeType compilationStatus:Error'</c>, ONE non-truncated call. A sweep is
/// only worth anything if a broken type makes it answer — and a sweep that returns <c>count: 0</c>
/// because its selector resolves to <c>null</c> is indistinguishable from a clean mesh. That is the
/// same defect shape as a CI gate that skips on a missing input: "it never ran" and "it passed"
/// paint the same colour.</para>
///
/// <para>🚨 <b>The finding is NOT "the sweep is broken" — it is that the two query
/// providers disagree about which selectors exist, and nothing compares them</b>
/// (Systemorph/MeshWeaver#3511). Both halves are measured, and they disagree:</para>
///
/// <list type="bullet">
/// <item><b>In-memory / FileSystem</b> (<see cref="QueryEvaluator"/>, what a dev Monolith and
/// most test meshes run): the BARE selector reaches nothing.
/// <see cref="QueryEvaluator.GetPropertyValue"/> resolves by reflection against the object it
/// is handed — a <see cref="MeshNode"/> — which has no <c>compilationStatus</c> property and
/// no <c>Content</c> fallback, so the comparison is <c>null == "Error"</c>: false for every
/// node. The DOTTED selector splits on <c>.</c>, walks into <c>Content</c>, and does reach it.
/// That is what the three cases below pin.</item>
/// <item><b>Postgres</b> (<c>PostgreSqlSqlGenerator</c>, not in this repository): the bare
/// selector DISCRIMINATES. Measured on a live mesh by a peer, 2026-09-07 —
/// <c>compilationStatus:Error</c> returned 5, <c>compilationStatus:Ok</c> returned 195, and
/// none of the five were among the 195.</item>
/// </list>
///
/// <para><b>So the sweep means different things in different places</b>, and that is worse than a
/// uniformly broken one: on the portal a deploy is actually gated on it works, while a rehearsal
/// on a local or CI mesh is green by construction and proves nothing about it. Closing the gap
/// properly is one test exercising BOTH providers against the same records — #3511's scope, of
/// which this file is the first half. It deliberately claims nothing about the Postgres provider
/// it cannot execute.</para>
/// </summary>
public class SweepSelectorReachesTheCompileStatusTest
{
private static MeshNode BrokenType() =>
new("Offer", "Crm")
{
NodeType = MeshNode.NodeTypePath,
Content = new NodeTypeDefinition
{
Configuration = "config => config",
CompilationStatus = CompilationStatus.Error,
CompilationError = "CS0103: the name 'x' does not exist in the current context",
},
};

[Fact]
public void TheDottedSelectorReachesTheCompileStatus()
=> new QueryEvaluator().GetPropertyValue(BrokenType(), "content.compilationStatus")
?.ToString()
.Should().Be("Error",
"the sweep has to be able to SEE a broken type. The evaluator walks a dotted "
+ "selector part by part, so 'content' resolves MeshNode.Content and "
+ "'compilationStatus' then resolves NodeTypeDefinition.CompilationStatus "
+ "case-insensitively — and the comparison is done on ToString(), so the enum name "
+ "matches the query's literal");

[Fact]
public void TheBareSelectorDoesNotReachIt_WhichIsWhyAGreenSweepHereIsNotEvidence()
=> new QueryEvaluator().GetPropertyValue(BrokenType(), "compilationStatus")
.Should().BeNull(
"🚨 MeshNode has no such property and this evaluator has no Content fallback, so "
+ "the bare selector compares null against 'Error' and matches NOTHING — a mesh "
+ "full of broken types answers count: 0, which reads exactly like a clean one. "
+ "On Postgres the same query DISCRIMINATES (5 Error / 195 Ok, disjoint, measured "
+ "on a live mesh), so this is a provider DISAGREEMENT, not a broken sweep — and "
+ "the disagreement is the dangerous part, because it makes a green rehearsal on "
+ "a local mesh look like a green sweep on the portal. Pinned rather than fixed: "
+ "giving the evaluator a Content fallback would change the meaning of every "
+ "selector in every query in the platform, and comparing the two providers needs "
+ "a test that can run both (#3511)");

[Fact]
public void AHealthyTypeIsNotReportedBroken()
=> new QueryEvaluator()
.GetPropertyValue(
new MeshNode("Client", "Crm")
{
NodeType = MeshNode.NodeTypePath,
Content = new NodeTypeDefinition
{
Configuration = "config => config",
CompilationStatus = CompilationStatus.Ok,
},
},
"content.compilationStatus")
?.ToString()
.Should().Be("Ok",
"THE CONTROL — a selector that answered 'Error' for everything would satisfy the "
+ "first assertion and make the sweep useless in the other direction");
}
Loading