From 5de8ec24c91084d8ff8848320617a715dea88615 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20B=C3=BCrgi?= Date: Sun, 16 Aug 2026 18:40:10 +0200 Subject: [PATCH 1/2] =?UTF-8?q?fix(approvals):=20restore=20the=20legacy=20?= =?UTF-8?q?MeshWeaver.Graph=20surface=20=E2=80=94=2011=20NodeTypes=20regre?= =?UTF-8?q?ssed=20on=20the=20extraction=20image?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Approvals extraction (#1654) moved ApprovalExtensions from the MeshWeaver.Graph namespace to MeshWeaver.Approvals and narrowed the public entry point to MeshBuilder. In-mesh sources in the SocialMedia and UWDeepfield partitions compile against the OLD shape — "using MeshWeaver.Graph;" + AddApprovals(this MessageHubConfiguration) + the class short name — and the compiler cannot see those callers (AGENTS.md: in-mesh source is invisible to dotnet build; AddApprovals is the same symbol that caused the #683-era outage). On ci.3961, the first image shipping the extraction, the bake regressed 11 NodeTypes (CS1061 AddApprovals on SocialMedia/Post, CS0103 ApprovalExtensions on UWDeepfield/UWDeepfieldHome + TreatySubmission, 8 UpstreamFailed cascades) and DynamicTypePreWarmer correctly refused readiness on memex and memex-cloud — old pods kept serving, the rollout stalled. GraphLegacySurface.cs (in the Approvals assembly, deliberately declaring the MeshWeaver.Graph namespace) restores the class, the ApprovalPartition const, and AddApprovals(this MessageHubConfiguration) — idempotent via the ApprovalsEnabled marker, delegating to the module's ConfigureHub, so a hub the module already configured is returned unchanged. HasApprovals stays where it lives today (ApprovalsMarkerExtensions) — re-declaring it is CS0121. The pin test compiles a NodeType whose Source uses the exact caller shape and was proven RED without the shim (the prod failure reproduced) and GREEN with it. It is the compiler for the in-mesh callers from here on. Co-Authored-By: Claude Fable 5 --- .../GraphLegacySurface.cs | 38 ++++++++ .../2026-08-16-approvals-legacy-surface.md | 15 +++ .../ApprovalsLegacySurfaceCompileTest.cs | 93 +++++++++++++++++++ 3 files changed, 146 insertions(+) create mode 100644 src/MeshWeaver.Approvals/GraphLegacySurface.cs create mode 100644 src/MeshWeaver.Documentation/Data/WhatsNew/2026-08-16-approvals-legacy-surface.md create mode 100644 test/MeshWeaver.Hosting.Monolith.Test/ApprovalsLegacySurfaceCompileTest.cs diff --git a/src/MeshWeaver.Approvals/GraphLegacySurface.cs b/src/MeshWeaver.Approvals/GraphLegacySurface.cs new file mode 100644 index 0000000000..1b7198f169 --- /dev/null +++ b/src/MeshWeaver.Approvals/GraphLegacySurface.cs @@ -0,0 +1,38 @@ +using MeshWeaver.Messaging; + +// 🚨 Deliberately the GRAPH namespace. This file restores the EXACT public surface in-mesh +// sources compiled against before the Approvals extraction (#1654): `MeshWeaver.Graph. +// ApprovalExtensions` with `AddApprovals(this MessageHubConfiguration)`. The extraction moved +// the class to the MeshWeaver.Approvals namespace and narrowed the public entry point to +// MeshBuilder — deleting a public framework surface that the compiler cannot see the callers +// of (AGENTS.md: in-mesh source is invisible to `dotnet build`). On the first image that +// shipped the extraction, 11 NodeTypes across the SocialMedia and UWDeepfield partitions +// regressed to CompileError (CS1061 AddApprovals / CS0103 ApprovalExtensions) and the bake +// gate refused readiness mesh-wide — the same symbol that caused the #683-era outage. +// Node content lives in OTHER repos and in production databases, so the platform keeps the +// surface; content can migrate to the module registration at its own pace. +namespace MeshWeaver.Graph; + +/// +/// Legacy Approvals surface for in-mesh sources written before the Approvals module extraction +/// (#1654). The real registration lives in and is +/// applied to every per-node hub when the module is installed — so this entry point only fills +/// the gap on hubs the module has not configured, and is a no-op everywhere else. +/// +public static class ApprovalExtensions +{ + /// The sub-partition name where approvals are stored. + public const string ApprovalPartition = Approvals.ApprovalExtensions.ApprovalPartition; + + /// + /// Adds approval support to this hub configuration. Idempotent: when the Approvals module + /// (or an earlier call) already configured the hub — detected via the + /// marker (, + /// which remains the one HasApprovals surface) — the configuration is returned + /// unchanged. + /// + public static MessageHubConfiguration AddApprovals(this MessageHubConfiguration configuration) + => ApprovalsMarkerExtensions.HasApprovals(configuration) + ? configuration + : Approvals.ApprovalExtensions.ConfigureHub(configuration); +} diff --git a/src/MeshWeaver.Documentation/Data/WhatsNew/2026-08-16-approvals-legacy-surface.md b/src/MeshWeaver.Documentation/Data/WhatsNew/2026-08-16-approvals-legacy-surface.md new file mode 100644 index 0000000000..ee2676c362 --- /dev/null +++ b/src/MeshWeaver.Documentation/Data/WhatsNew/2026-08-16-approvals-legacy-surface.md @@ -0,0 +1,15 @@ +--- +Name: Approvals keep working in existing content +Category: Fix +Description: Content that wires up approvals with the original AddApprovals call compiles again — the surface was restored after the Approvals module extraction removed it. +Icon: Sparkle +Order: -20260816 +--- + +# Approvals keep working in existing content + +The Approvals feature recently moved into its own module. That move accidentally removed the +original programming surface that existing content in several workspaces still uses, so those +pages stopped compiling on the newest version and the update was held back by the platform's +safety gate. The original surface is restored (and now covered by a test), so existing content +keeps working unchanged while new content can use the module directly. diff --git a/test/MeshWeaver.Hosting.Monolith.Test/ApprovalsLegacySurfaceCompileTest.cs b/test/MeshWeaver.Hosting.Monolith.Test/ApprovalsLegacySurfaceCompileTest.cs new file mode 100644 index 0000000000..f12dc63c90 --- /dev/null +++ b/test/MeshWeaver.Hosting.Monolith.Test/ApprovalsLegacySurfaceCompileTest.cs @@ -0,0 +1,93 @@ +using System; +using System.Linq; +using System.Reactive.Linq; +using System.Threading.Tasks; +using MeshWeaver.Graph; +using MeshWeaver.Graph.Configuration; +using MeshWeaver.Hosting.Monolith.TestBase; +using MeshWeaver.Mesh; +using MeshWeaver.Mesh.Services; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace MeshWeaver.Hosting.Monolith.Test; + +/// +/// Pins the LEGACY Approvals surface for in-mesh sources: MeshWeaver.Graph.ApprovalExtensions +/// with AddApprovals(this MessageHubConfiguration). The Approvals extraction (#1654) moved +/// the class to the MeshWeaver.Approvals namespace and narrowed the public entry to MeshBuilder — +/// and the first image shipping that regressed 11 NodeTypes across the SocialMedia and UWDeepfield +/// partitions to CompileError (CS1061 AddApprovals / CS0103 ApprovalExtensions), +/// which the bake gate answered by refusing readiness mesh-wide. In-mesh source is invisible to +/// dotnet build, so THIS test is the compiler for those callers: it compiles a NodeType +/// whose Source uses the exact legacy shape and must stay green for as long as any partition's +/// content does. +/// +public class ApprovalsLegacySurfaceCompileTest(ITestOutputHelper output) : MonolithMeshTestBase(output) +{ + private IMeshService MeshService => Mesh.ServiceProvider.GetRequiredService(); + + /// Same shape as CellSurfaceScriptingSeamTest.CreateAndCompile. + private async Task CreateAndCompile( + string nodeTypePath, + NodeTypeDefinition definition, + params (string Name, string Code)[] sources) + { + var typeNode = MeshNode.FromPath(nodeTypePath) with + { + Name = nodeTypePath.Split('/').Last(), + NodeType = MeshNode.NodeTypePath, + Content = definition, + State = MeshNodeState.Active + }; + + await MeshService.CreateNode(typeNode) + .SelectMany(_ => sources + .Select(source => MeshService.CreateNode(new MeshNode(source.Name, $"{nodeTypePath}/Source") + { + NodeType = "Code", + Name = source.Name, + Content = new CodeConfiguration { Code = source.Code, Language = "csharp" }, + State = MeshNodeState.Active + })) + .Aggregate(Observable.Return(null), (chain, next) => + chain.SelectMany(_ => next.Select(n => (MeshNode?)n)))) + .Should().Within(30.Seconds()).Emit(); + + var node = await Mesh.GetMeshNodeStream(nodeTypePath) + .Should().Within(60.Seconds()) + .Match(n => n?.Content is NodeTypeDefinition d + && d.CompilationStatus is CompilationStatus.Ok or CompilationStatus.Error); + return (NodeTypeDefinition)node.Content!; + } + + [Fact(Timeout = 120_000)] + public async Task LegacyApprovalsSurface_StaysCompilableFromNodeSource() + { + // The EXACT caller shape live in the SocialMedia and UWDeepfield partitions: + // `using MeshWeaver.Graph;` + `configuration.AddApprovals()` + the class referenced + // by its short name (the partition-path const). Both failed on the extraction image. + var def = await CreateAndCompile( + "type/LegacyApprovals", + new NodeTypeDefinition + { + Configuration = "config => config" + }, + ("wiring", """ + using MeshWeaver.Graph; + using MeshWeaver.Messaging; + + public static class LegacyApprovalsWiring + { + public const string Partition = ApprovalExtensions.ApprovalPartition; + + public static MessageHubConfiguration Wire(MessageHubConfiguration configuration) + => configuration.AddApprovals(); + } + """)); + + def.CompilationStatus.Should().Be(CompilationStatus.Ok, + "the legacy MeshWeaver.Graph Approvals surface has in-mesh callers the compiler " + + $"cannot see — it must never disappear again; error: {def.CompilationError}"); + } +} From f0a0ff556b402670a7caa90eb29dc4d7c439769b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20B=C3=BCrgi?= Date: Sun, 16 Aug 2026 18:51:10 +0200 Subject: [PATCH 2/2] =?UTF-8?q?test(graph):=20qualify=20the=20module=20Con?= =?UTF-8?q?figureHub=20call=20=E2=80=94=20the=20restored=20legacy=20class?= =?UTF-8?q?=20shadows=20it=20from=20the=20enclosing=20namespace?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- test/MeshWeaver.Graph.Test/ApprovalModuleTest.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/MeshWeaver.Graph.Test/ApprovalModuleTest.cs b/test/MeshWeaver.Graph.Test/ApprovalModuleTest.cs index e1b9fd0076..95f5ee44ad 100644 --- a/test/MeshWeaver.Graph.Test/ApprovalModuleTest.cs +++ b/test/MeshWeaver.Graph.Test/ApprovalModuleTest.cs @@ -54,7 +54,10 @@ public void ConfigureHub_SetsTheApprovalsEnabledMarker_TheOverviewGuardReads() // The markdown overview embeds its Approvals section only when HasApprovals() is true — // the marker stays in Graph, the module sets it. One drifting apart of the two would // silently blank the section, so pin the pair here. - var configuration = ApprovalExtensions.ConfigureHub( + // Fully qualified: the legacy MeshWeaver.Graph.ApprovalExtensions (the restored in-mesh + // surface) shadows the module class from this namespace, and only the module class has + // the internal ConfigureHub. + var configuration = Approvals.ApprovalExtensions.ConfigureHub( new MessageHubConfiguration(null, new Address("test", "approvals"))); configuration.HasApprovals().Should().BeTrue(); }