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
38 changes: 38 additions & 0 deletions src/MeshWeaver.Approvals/GraphLegacySurface.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Legacy Approvals surface for in-mesh sources written before the Approvals module extraction
/// (#1654). The real registration lives in <see cref="Approvals.ApprovalExtensions"/> 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.
/// </summary>
public static class ApprovalExtensions
{
/// <summary>The sub-partition name where approvals are stored.</summary>
public const string ApprovalPartition = Approvals.ApprovalExtensions.ApprovalPartition;

/// <summary>
/// Adds approval support to this hub configuration. Idempotent: when the Approvals module
/// (or an earlier call) already configured the hub — detected via the
/// <see cref="ApprovalsEnabled"/> marker (<see cref="ApprovalsMarkerExtensions.HasApprovals"/>,
/// which remains the one <c>HasApprovals</c> surface) — the configuration is returned
/// unchanged.
/// </summary>
public static MessageHubConfiguration AddApprovals(this MessageHubConfiguration configuration)
=> ApprovalsMarkerExtensions.HasApprovals(configuration)
? configuration
: Approvals.ApprovalExtensions.ConfigureHub(configuration);
}
Original file line number Diff line number Diff line change
@@ -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.
5 changes: 4 additions & 1 deletion test/MeshWeaver.Graph.Test/ApprovalModuleTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Pins the LEGACY Approvals surface for in-mesh sources: <c>MeshWeaver.Graph.ApprovalExtensions</c>
/// with <c>AddApprovals(this MessageHubConfiguration)</c>. 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 <c>AddApprovals</c> / CS0103 <c>ApprovalExtensions</c>),
/// which the bake gate answered by refusing readiness mesh-wide. In-mesh source is invisible to
/// <c>dotnet build</c>, 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.
/// </summary>
public class ApprovalsLegacySurfaceCompileTest(ITestOutputHelper output) : MonolithMeshTestBase(output)
{
private IMeshService MeshService => Mesh.ServiceProvider.GetRequiredService<IMeshService>();

/// <summary>Same shape as <c>CellSurfaceScriptingSeamTest.CreateAndCompile</c>.</summary>
private async Task<NodeTypeDefinition> 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<MeshNode?>(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}");
}
}
Loading