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: 6 additions & 0 deletions memex/Memex.Portal.Shared/MemexConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,12 @@ public TBuilder ConfigureMemexMesh(IConfiguration configuration, bool isDevelopm
// import — no regression. Default Helm sets ["Doc","Agent","Provider","Harness","Skill"].
var syncPartitions = features.StaticRepoSync.Partitions
.ToHashSet(StringComparer.OrdinalIgnoreCase);
// AI content is served as a UNIT: if the config names ANY AI partition, serve them ALL
// (Agent/Provider/Harness/Skill), so an incomplete list can't leave Skill (or a future AI
// content type) in-memory while the rest go to the DB — and AddAI's per-type serve-from-DB
// gating stays consistent with the static-repo import. See MeshWeaver.AI/AiContentSources.
if (syncPartitions.Overlaps(AiContentSources.ContentPartitions))
syncPartitions.UnionWith(AiContentSources.ContentPartitions);
IReadOnlySet<string> serveFromPartition = syncPartitions;

MeshBuilder mb = builder
Expand Down
20 changes: 10 additions & 10 deletions memex/Memex.Portal.Shared/StaticRepoSyncExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,16 @@ public static TBuilder AddStaticRepoSync<TBuilder>(this TBuilder builder,
{
if (serveFromPartition.Contains("Doc"))
services.AddSingleton<IStaticRepoSource, DocumentationStaticRepoSource>();
if (serveFromPartition.Contains("Agent"))
services.AddSingleton<IStaticRepoSource, AgentStaticRepoSource>();
// The model catalog (providers + models + policy) imports into the top-level "Provider"
// partition. Honour the legacy "Model" partition name too for backwards-compatible configs.
if (serveFromPartition.Contains("Provider") || serveFromPartition.Contains("Model"))
services.AddSingleton<IStaticRepoSource, ModelStaticRepoSource>();
if (serveFromPartition.Contains("Harness"))
services.AddSingleton<IStaticRepoSource, HarnessStaticRepoSource>();
if (serveFromPartition.Contains("Skill"))
services.AddSingleton<IStaticRepoSource, SkillStaticRepoSource>();

// AI content (Agent / Provider / Harness / Skill) is ONE bundle: register every built-in
// AI source together whenever any AI partition is served, so a config that names only some
// of them can NEVER silently drop one (the recurring "Skill was never imported" bug). The
// set + the bundle live next to the sources in MeshWeaver.AI — adding a new AI content type
// there auto-joins the import; there is no per-partition allow-list here to forget. (The
// expand-to-the-whole-bundle also happens in MemexConfiguration so AddAI's serve-from-DB
// gating stays consistent with the import.)
if (serveFromPartition.Overlaps(AiContentSources.ContentPartitions))
services.AddBuiltInAiContentSources();

// Runs after the PG schema-provisioning hosted service (registered earlier by
// AddPartitionedPostgreSqlPersistence) — hosted services start in registration order.
Expand Down
1 change: 1 addition & 0 deletions src/MeshWeaver.AI/AgentStaticRepoSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public IReadOnlyList<MeshNode> EnumerateSourceNodes() =>
public MeshNode? PartitionRoot => new("Agent")
{
Name = "Agents",
Icon = "/static/NodeTypeIcons/sparkle.svg",
NodeType = "Space",
State = MeshNodeState.Active,
Content = new MarkdownContent
Expand Down
46 changes: 46 additions & 0 deletions src/MeshWeaver.AI/AiContentSources.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using System.Collections.Immutable;
using MeshWeaver.Mesh;
using Microsoft.Extensions.DependencyInjection;

namespace MeshWeaver.AI;

/// <summary>
/// The built-in AI content partitions and their static-repo import sources, treated as ONE bundle.
/// AI content — Agents, model Providers, Harnesses, Skills — is seeded from these sources on boot.
/// Bundling them is the cure for the recurring "Skill partition was never imported" bug: there is no
/// per-partition allow-list to hand-maintain, so a new built-in AI content type can't be silently
/// left un-imported by forgetting to name its partition. This is the single source of truth shared by
/// <c>AddAI</c>'s serve-from-DB gating and the portal's static-repo import wiring.
///
/// <para>Pinned by <c>AiContentSourcesTest</c>: every <see cref="IStaticRepoSource"/> defined in
/// <c>MeshWeaver.AI</c> MUST be in <see cref="AddBuiltInAiContentSources"/> — adding a fifth source
/// without bundling it fails the test.</para>
/// </summary>
public static class AiContentSources
{
/// <summary>
/// The partitions whose content is the built-in AI catalog: <c>Agent</c>, <c>Provider</c>,
/// <c>Harness</c>, <c>Skill</c>. Served from the DB (and imported) as a UNIT — never partially.
/// </summary>
public static readonly ImmutableHashSet<string> ContentPartitions =
ImmutableHashSet.Create(
StringComparer.OrdinalIgnoreCase,
"Agent", // AgentStaticRepoSource.Partition
ModelProviderNodeType.RootNamespace, // "Provider"
HarnessNodeType.RootNamespace, // "Harness"
SkillNodeType.RootNamespace); // "Skill"

/// <summary>
/// Registers EVERY built-in AI <see cref="IStaticRepoSource"/> (Agent, Model/Provider, Harness,
/// Skill) as one bundle. The portal calls this whenever AI content is served from the DB, so the
/// import set can never drift from <see cref="ContentPartitions"/> or silently drop a partition.
/// </summary>
public static IServiceCollection AddBuiltInAiContentSources(this IServiceCollection services)
{
services.AddSingleton<IStaticRepoSource, AgentStaticRepoSource>();
services.AddSingleton<IStaticRepoSource, ModelStaticRepoSource>();
services.AddSingleton<IStaticRepoSource, HarnessStaticRepoSource>();
services.AddSingleton<IStaticRepoSource, SkillStaticRepoSource>();
return services;
}
}
1 change: 1 addition & 0 deletions src/MeshWeaver.AI/ModelStaticRepoSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public IReadOnlyList<MeshNode> EnumerateSourceNodes() =>
public MeshNode? PartitionRoot => new(ModelProviderNodeType.RootNamespace)
{
Name = "Providers",
Icon = "/static/NodeTypeIcons/database.svg",
NodeType = "Space",
State = MeshNodeState.Active,
Content = new MarkdownContent
Expand Down
1 change: 1 addition & 0 deletions src/MeshWeaver.AI/SkillStaticRepoSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public IReadOnlyList<MeshNode> EnumerateSourceNodes() =>
public MeshNode? PartitionRoot => new(SkillNodeType.RootNamespace)
{
Name = "Skills",
Icon = "/static/NodeTypeIcons/sparkle.svg",
NodeType = "Space",
State = MeshNodeState.Active,
Content = new MarkdownContent
Expand Down
45 changes: 45 additions & 0 deletions test/MeshWeaver.AI.Test/AiContentSourcesTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System.Linq;
using MeshWeaver.Mesh;
using Microsoft.Extensions.DependencyInjection;
using Xunit;

namespace MeshWeaver.AI.Test;

/// <summary>
/// The ratchet behind the recurring "Skill partition was never imported" bug: every built-in AI
/// <see cref="IStaticRepoSource"/> defined in <c>MeshWeaver.AI</c> MUST be in the
/// <see cref="AiContentSources.AddBuiltInAiContentSources"/> bundle. Adding a new AI content type
/// (a fifth partition) without bundling it fails this test — so it can never again be silently left
/// un-imported by an incomplete per-partition list.
/// </summary>
public class AiContentSourcesTest
{
[Fact]
public void AddBuiltInAiContentSources_bundles_every_AI_static_repo_source()
{
// Reflect over EVERY concrete IStaticRepoSource in the MeshWeaver.AI assembly.
var allAiSources = typeof(AiContentSources).Assembly.GetTypes()
.Where(t => t is { IsAbstract: false, IsClass: true }
&& typeof(IStaticRepoSource).IsAssignableFrom(t))
.ToHashSet();
allAiSources.Should().NotBeEmpty("MeshWeaver.AI defines built-in static-repo content sources");

var registered = new ServiceCollection()
.AddBuiltInAiContentSources()
.Where(sd => sd.ServiceType == typeof(IStaticRepoSource))
.Select(sd => sd.ImplementationType!)
.ToHashSet();

var unbundled = allAiSources.Except(registered).OrderBy(t => t.Name).ToList();
unbundled.Should().BeEmpty(
"AddBuiltInAiContentSources must register EVERY IStaticRepoSource in MeshWeaver.AI — " +
"bundling them is what stops a new AI partition (Skill, …) from being silently un-imported");
}

[Fact]
public void ContentPartitions_cover_the_four_AI_content_partitions()
{
AiContentSources.ContentPartitions.OrderBy(p => p, System.StringComparer.Ordinal)
.Should().Equal("Agent", "Harness", "Provider", "Skill");
}
}
Loading