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
6 changes: 5 additions & 1 deletion src/MeshWeaver.Compiler/FrameworkBuildIdentity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,11 @@ public static class FrameworkBuildIdentity
/// </summary>
public static readonly ImmutableArray<string> ContentSurfaceAssemblies =
[
"MeshWeaver.AI",
// MeshWeaver.AI left the content surface with #2276: the engine is a MODULE now, landed
// from the registry and composed into content compiles per-mesh via
// CompileReferences.ComposeWithModules — never part of the framework identity. Content
// referencing AI types compiles wherever the AI module is installed (the bake host lands
// it too), and an image shipping AI.dll in its app closure would 409 the registry module.
"MeshWeaver.Application.Styles",
"MeshWeaver.Compiler",
"MeshWeaver.ContentCollections",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,14 @@
framework build identity — this is the TEST project. -->
<ProjectReference Include="..\..\tools\MeshWeaver.ComboVerifier\MeshWeaver.ComboVerifier.csproj" />
</ItemGroup>

<!-- The gate boots in-process here, and the gate requires the AI engine MODULE beside the
binary (#2276) — modules/ is laid by the closure lane, not copied by a ProjectReference, so
this TEST bin needs its own lane run with the same tester-local single row. Same pattern as
the old StorageModuleLayoutTest: reuse the real worker, never a hand-copied publish. -->
<Import Project="..\..\memex\MeshModulesPublish.targets" />
<ItemGroup>
<MeshModuleClosure Remove="@(MeshModuleClosure)" />
<MeshModuleClosure Include="$([MSBuild]::NormalizePath('$(MSBuildProjectDirectory)', '..', '..', 'src', 'MeshWeaver.AI', 'MeshWeaver.AI.csproj'))" />
</ItemGroup>
</Project>
27 changes: 26 additions & 1 deletion tools/MeshWeaver.PluginTester/MeshWeaver.PluginTester.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,19 @@
<ItemGroup>
<ProjectReference Include="..\..\src\MeshWeaver.Hosting.Monolith\MeshWeaver.Hosting.Monolith.csproj" />
<ProjectReference Include="..\..\src\MeshWeaver.Hosting\MeshWeaver.Hosting.csproj" />
<ProjectReference Include="..\..\src\MeshWeaver.AI\MeshWeaver.AI.csproj" />
<!-- 🚨 MeshWeaver.AI is deliberately NOT a ProjectReference (#2276): the engine left the
content surface and ships as a registry MODULE, so the gate — like a portal — lands it
under modules/MeshWeaver.AI/ (the closure lane below) and activates it through
Modules:Assemblies (PluginGateRunner builds the in-memory configuration). A compiled
reference here would put MeshWeaver.AI back into the framework identity for every bake
host, and an image shipping AI.dll in its app closure 409s the registry module. -->
<!-- These four used to arrive TRANSITIVELY through MeshWeaver.AI and are still part of the
content surface (in-mesh source compiles against them), so the closure keeps them
directly now that the engine reference is gone. -->
<ProjectReference Include="..\..\src\MeshWeaver.Mesh.Operations\MeshWeaver.Mesh.Operations.csproj" />
<ProjectReference Include="..\..\src\MeshWeaver.Application.Styles\MeshWeaver.Application.Styles.csproj" />
<ProjectReference Include="..\..\src\MeshWeaver.Markdown.Collaboration\MeshWeaver.Markdown.Collaboration.csproj" />
<ProjectReference Include="..\..\src\MeshWeaver.ContentCollections.Indexing.Graph\MeshWeaver.ContentCollections.Indexing.Graph.csproj" />
<ProjectReference Include="..\..\src\MeshWeaver.Graph\MeshWeaver.Graph.csproj" />
<ProjectReference Include="..\..\src\MeshWeaver.PluginCatalog\MeshWeaver.PluginCatalog.csproj" />
<ProjectReference Include="..\..\src\MeshWeaver.GitSync\MeshWeaver.GitSync.csproj" />
Expand All @@ -87,4 +99,17 @@
host compiles fine — the gate must judge against the surface content actually gets. -->
<ProjectReference Include="..\..\src\MeshWeaver.Maps\MeshWeaver.Maps.csproj" />
</ItemGroup>

<!-- ═════════ The AI engine rides the MODULE lane, exactly as a portal ships it ═════════
The closure lane lays modules/MeshWeaver.AI/ (entry DLL + its private NuGet closure) into
$(OutDir) after Build and into the publish dir after Publish — the same worker every host
uses, so the gate loads the very layout a portal loads. The row is TESTER-LOCAL: written
AFTER the import, replacing the shared inventory, so nothing here adds MeshWeaver.AI to any
portal image's closure (a closure module and a registry module of the same name are
mutually exclusive — the landing service answers 409). -->
<Import Project="..\..\memex\MeshModulesPublish.targets" />
<ItemGroup>
<MeshModuleClosure Remove="@(MeshModuleClosure)" />
<MeshModuleClosure Include="$([MSBuild]::NormalizePath('$(MSBuildProjectDirectory)', '..', '..', 'src', 'MeshWeaver.AI', 'MeshWeaver.AI.csproj'))" />
</ItemGroup>
</Project>
51 changes: 46 additions & 5 deletions tools/MeshWeaver.PluginTester/PluginGateRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using System.Reactive;
using System.Reactive.Linq;
using System.Text.Json;
using MeshWeaver.AI;
using MeshWeaver.Data;
using MeshWeaver.GitSync;
using MeshWeaver.Graph;
Expand Down Expand Up @@ -589,16 +588,58 @@ public static GateMesh Create(TextWriter output, BakeSeed? seed = null)
() => meshHub ?? throw new InvalidOperationException(
"the gate mesh was asked for prebuilt assemblies before it finished booting"),
seed);
// 🚨 The AI engine is a MODULE (#2276), and the gate activates it the way a portal
// does: the csproj's closure lane laid modules/MeshWeaver.AI/ beside this binary, and
// this in-memory configuration is the gate's Modules:Assemblies. The engine matters
// here because plugin packages ship Agent and Skill nodes — without the AI node types
// those installs are refused "not registered". Features:StaticRepoSync:Partitions
// names ONE AI partition; the module's served-as-a-unit rule
// (AiMeshModuleAttribute.ServeFromPartitions) unions in the rest, so this cannot go
// stale when the engine gains a partition. The DB-served shape is load-bearing, not a
// preference: a statically-served AI partition refuses Agent/Skill package installs
// (StaticShadowedReason — the 2026-08-11 30s-hang-per-package incident).
var gateModules = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string?>
{
["Modules:Assemblies:0"] = "MeshWeaver.AI.dll",
// The .dll suffix is the convention every host uses AND load-bearing: the
// resolver derives the folder with GetFileNameWithoutExtension, so a bare
// "MeshWeaver.AI" would probe modules/MeshWeaver/ and read as absent.
["Modules:Required:0"] = "MeshWeaver.AI.dll",
["Features:StaticRepoSync:Partitions:0"] = "Agent",
})
.Build();
// 🚨 A gate never tests its own inputs (AGENTS.md): a missing engine module fails RED
// here, never a booted mesh without the AI node types that then refuses every install
// for a reason naming the wrong cause.
var missingModules = MeshBuilderModuleActivation.MissingRequired(
gateModules, MeshBuilder.ResolveModulePath, File.Exists);
if (missingModules.Length > 0)
throw new InvalidOperationException(
"the gate's required module(s) did not resolve beside the binary: "
+ string.Join(", ", missingModules)
+ ". The MeshModulesPublish closure lane in MeshWeaver.PluginTester.csproj "
+ "lays modules/MeshWeaver.AI/ into the build and publish output — a run "
+ "whose layout is missing cannot gate.");
var builder = new MeshBuilder(c => c.Invoke(services), AddressExtensions.CreateMeshAddress())
.UseMonolithMesh()
.AddInMemoryPersistence()
.AddRowLevelSecurity()
.AddGraph()
.AddSpaceType()
// The AI node types (Agent / Skill / Model / …) — plugin packages ship Agent
// and Skill nodes (LinkedIn, Feedback, ExplainerVideo), and a portal always
// registers these; without them those installs are refused "not registered".
.AddAI(MeshWeaver.AI.AiContentSources.ContentPartitions)
// The gate runs NO boot static-repo import (its content arrives through package
// installs), so the import-settled signal is registered PRE-SETTLED — the
// documented "nothing to import" state. The AI module's provider-credential seed
// resolves it, and anything sequencing on it proceeds immediately instead of
// waiting for an import that will never run here.
.ConfigureServices(s =>
{
var settled = new StaticRepoImportSettled();
settled.MarkSettled();
return s.AddSingleton(settled);
})
.InstallConfiguredModules(gateModules,
msg => output.WriteLine($"[gate modules] {msg}"))
.AddPluginCatalog()
.AddMeshNodes(RootAdminAccess())
// Per-run isolated assembly store + compilation cache (AddInMemoryPersistence
Expand Down
Loading