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.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(); } 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}"); + } +}