wip: doctor/cli/docs/api to vector db consolidation; api hardening for descriptions, tenant, and scopes; migrations and conversions of all DALs to EF v10

This commit is contained in:
master
2026-02-23 15:30:50 +02:00
parent bd8fee6ed8
commit e746577380
1424 changed files with 81225 additions and 25251 deletions

View File

@@ -0,0 +1,55 @@
// Placeholder for compiled model generated by `dotnet ef dbcontext optimize`.
// This file should be regenerated when DbContext configuration changes.
// Command:
// dotnet ef dbcontext optimize \
// --project src/Plugin/StellaOps.Plugin.Registry/ \
// --output-dir EfCore/CompiledModels \
// --namespace StellaOps.Plugin.Registry.EfCore.CompiledModels
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using StellaOps.Plugin.Registry.EfCore.Context;
#pragma warning disable 219, 612, 618, EF1001
#nullable disable
namespace StellaOps.Plugin.Registry.EfCore.CompiledModels
{
[DbContext(typeof(PluginRegistryDbContext))]
public partial class PluginRegistryDbContextModel : RuntimeModel
{
private static readonly bool _useOldBehavior31751 =
System.AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue31751", out var enabled31751) && enabled31751;
static PluginRegistryDbContextModel()
{
var model = new PluginRegistryDbContextModel();
if (_useOldBehavior31751)
{
model.Initialize();
}
else
{
var thread = new System.Threading.Thread(RunInitialization, 10 * 1024 * 1024);
thread.Start();
thread.Join();
void RunInitialization()
{
model.Initialize();
}
}
model.Customize();
_instance = (PluginRegistryDbContextModel)model.FinalizeModel();
}
private static PluginRegistryDbContextModel _instance;
public static IModel Instance => _instance;
partial void Initialize();
partial void Customize();
}
}

View File

@@ -0,0 +1,40 @@
using Microsoft.EntityFrameworkCore;
using StellaOps.Plugin.Registry.EfCore.Models;
namespace StellaOps.Plugin.Registry.EfCore.Context;
/// <summary>
/// Relationship overlays for PluginRegistryDbContext.
/// </summary>
public partial class PluginRegistryDbContext
{
partial void OnModelCreatingPartial(ModelBuilder modelBuilder)
{
// Plugin -> PluginCapabilities (one-to-many, cascade delete)
modelBuilder.Entity<PluginCapabilityEntity>(entity =>
{
entity.HasOne(e => e.Plugin)
.WithMany(p => p.PluginCapabilities)
.HasForeignKey(e => e.PluginId)
.OnDelete(DeleteBehavior.Cascade);
});
// Plugin -> PluginInstances (one-to-many, cascade delete)
modelBuilder.Entity<PluginInstanceEntity>(entity =>
{
entity.HasOne(e => e.Plugin)
.WithMany(p => p.PluginInstances)
.HasForeignKey(e => e.PluginId)
.OnDelete(DeleteBehavior.Cascade);
});
// Plugin -> PluginHealthHistory (one-to-many, cascade delete)
modelBuilder.Entity<PluginHealthHistoryEntity>(entity =>
{
entity.HasOne(e => e.Plugin)
.WithMany(p => p.HealthHistory)
.HasForeignKey(e => e.PluginId)
.OnDelete(DeleteBehavior.Cascade);
});
}
}

View File

@@ -0,0 +1,274 @@
using Microsoft.EntityFrameworkCore;
using StellaOps.Plugin.Registry.EfCore.Models;
namespace StellaOps.Plugin.Registry.EfCore.Context;
/// <summary>
/// EF Core DbContext for the Plugin Registry schema.
/// </summary>
public partial class PluginRegistryDbContext : DbContext
{
private readonly string _schemaName;
public PluginRegistryDbContext(DbContextOptions<PluginRegistryDbContext> options, string? schemaName = null)
: base(options)
{
_schemaName = string.IsNullOrWhiteSpace(schemaName)
? "platform"
: schemaName.Trim();
}
public virtual DbSet<PluginEntity> Plugins { get; set; }
public virtual DbSet<PluginCapabilityEntity> PluginCapabilities { get; set; }
public virtual DbSet<PluginInstanceEntity> PluginInstances { get; set; }
public virtual DbSet<PluginHealthHistoryEntity> PluginHealthHistory { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var schemaName = _schemaName;
modelBuilder.Entity<PluginEntity>(entity =>
{
entity.HasKey(e => e.Id).HasName("plugins_pkey");
entity.ToTable("plugins", schemaName);
// Unique constraint
entity.HasIndex(e => new { e.PluginId, e.Version }).IsUnique();
// Indexes matching SQL migration
entity.HasIndex(e => e.PluginId, "idx_plugins_plugin_id");
entity.HasIndex(e => e.Status, "idx_plugins_status")
.HasFilter("status != 'active'");
entity.HasIndex(e => e.TrustLevel, "idx_plugins_trust_level");
entity.HasIndex(e => e.HealthStatus, "idx_plugins_health")
.HasFilter("health_status != 'healthy'");
// Column mappings
entity.Property(e => e.Id)
.HasDefaultValueSql("gen_random_uuid()")
.HasColumnName("id");
entity.Property(e => e.PluginId)
.HasMaxLength(255)
.HasColumnName("plugin_id");
entity.Property(e => e.Name)
.HasMaxLength(255)
.HasColumnName("name");
entity.Property(e => e.Version)
.HasMaxLength(50)
.HasColumnName("version");
entity.Property(e => e.Vendor)
.HasMaxLength(255)
.HasColumnName("vendor");
entity.Property(e => e.Description)
.HasColumnName("description");
entity.Property(e => e.LicenseId)
.HasMaxLength(50)
.HasColumnName("license_id");
entity.Property(e => e.TrustLevel)
.HasMaxLength(50)
.HasColumnName("trust_level");
entity.Property(e => e.Signature)
.HasColumnName("signature");
entity.Property(e => e.SigningKeyId)
.HasMaxLength(255)
.HasColumnName("signing_key_id");
entity.Property(e => e.Capabilities)
.HasColumnName("capabilities")
.HasDefaultValueSql("'{}'::text[]");
entity.Property(e => e.CapabilityDetails)
.HasColumnType("jsonb")
.HasDefaultValueSql("'{}'::jsonb")
.HasColumnName("capability_details");
entity.Property(e => e.Source)
.HasMaxLength(50)
.HasColumnName("source");
entity.Property(e => e.AssemblyPath)
.HasMaxLength(500)
.HasColumnName("assembly_path");
entity.Property(e => e.EntryPoint)
.HasMaxLength(255)
.HasColumnName("entry_point");
entity.Property(e => e.Status)
.HasMaxLength(50)
.HasDefaultValue("discovered")
.HasColumnName("status");
entity.Property(e => e.StatusMessage)
.HasColumnName("status_message");
entity.Property(e => e.HealthStatus)
.HasMaxLength(50)
.HasDefaultValue("unknown")
.HasColumnName("health_status");
entity.Property(e => e.LastHealthCheck)
.HasColumnName("last_health_check");
entity.Property(e => e.HealthCheckFailures)
.HasDefaultValue(0)
.HasColumnName("health_check_failures");
entity.Property(e => e.Manifest)
.HasColumnType("jsonb")
.HasColumnName("manifest");
entity.Property(e => e.RuntimeInfo)
.HasColumnType("jsonb")
.HasColumnName("runtime_info");
entity.Property(e => e.CreatedAt)
.HasDefaultValueSql("now()")
.HasColumnName("created_at");
entity.Property(e => e.UpdatedAt)
.HasDefaultValueSql("now()")
.HasColumnName("updated_at");
entity.Property(e => e.LoadedAt)
.HasColumnName("loaded_at");
});
modelBuilder.Entity<PluginCapabilityEntity>(entity =>
{
entity.HasKey(e => e.Id).HasName("plugin_capabilities_pkey");
entity.ToTable("plugin_capabilities", schemaName);
// Unique constraint
entity.HasIndex(e => new { e.PluginId, e.CapabilityType, e.CapabilityId }).IsUnique();
// Indexes matching SQL migration
entity.HasIndex(e => e.CapabilityType, "idx_plugin_capabilities_type");
entity.HasIndex(e => new { e.CapabilityType, e.CapabilityId }, "idx_plugin_capabilities_lookup");
entity.HasIndex(e => e.PluginId, "idx_plugin_capabilities_plugin");
// Column mappings
entity.Property(e => e.Id)
.HasDefaultValueSql("gen_random_uuid()")
.HasColumnName("id");
entity.Property(e => e.PluginId)
.HasColumnName("plugin_id");
entity.Property(e => e.CapabilityType)
.HasMaxLength(100)
.HasColumnName("capability_type");
entity.Property(e => e.CapabilityId)
.HasMaxLength(255)
.HasColumnName("capability_id");
entity.Property(e => e.ConfigSchema)
.HasColumnType("jsonb")
.HasColumnName("config_schema");
entity.Property(e => e.InputSchema)
.HasColumnType("jsonb")
.HasColumnName("input_schema");
entity.Property(e => e.OutputSchema)
.HasColumnType("jsonb")
.HasColumnName("output_schema");
entity.Property(e => e.DisplayName)
.HasMaxLength(255)
.HasColumnName("display_name");
entity.Property(e => e.Description)
.HasColumnName("description");
entity.Property(e => e.DocumentationUrl)
.HasMaxLength(500)
.HasColumnName("documentation_url");
entity.Property(e => e.Metadata)
.HasColumnType("jsonb")
.HasColumnName("metadata");
entity.Property(e => e.IsEnabled)
.HasDefaultValue(true)
.HasColumnName("is_enabled");
entity.Property(e => e.CreatedAt)
.HasDefaultValueSql("now()")
.HasColumnName("created_at");
});
modelBuilder.Entity<PluginInstanceEntity>(entity =>
{
entity.HasKey(e => e.Id).HasName("plugin_instances_pkey");
entity.ToTable("plugin_instances", schemaName);
// Indexes matching SQL migration
entity.HasIndex(e => e.TenantId, "idx_plugin_instances_tenant")
.HasFilter("tenant_id IS NOT NULL");
entity.HasIndex(e => e.PluginId, "idx_plugin_instances_plugin");
entity.HasIndex(e => new { e.PluginId, e.Enabled }, "idx_plugin_instances_enabled")
.HasFilter("enabled = TRUE");
// Column mappings
entity.Property(e => e.Id)
.HasDefaultValueSql("gen_random_uuid()")
.HasColumnName("id");
entity.Property(e => e.PluginId)
.HasColumnName("plugin_id");
entity.Property(e => e.TenantId)
.HasColumnName("tenant_id");
entity.Property(e => e.InstanceName)
.HasMaxLength(255)
.HasColumnName("instance_name");
entity.Property(e => e.Config)
.HasColumnType("jsonb")
.HasDefaultValueSql("'{}'::jsonb")
.HasColumnName("config");
entity.Property(e => e.SecretsPath)
.HasMaxLength(500)
.HasColumnName("secrets_path");
entity.Property(e => e.Enabled)
.HasDefaultValue(true)
.HasColumnName("enabled");
entity.Property(e => e.Status)
.HasMaxLength(50)
.HasDefaultValue("pending")
.HasColumnName("status");
entity.Property(e => e.ResourceLimits)
.HasColumnType("jsonb")
.HasColumnName("resource_limits");
entity.Property(e => e.LastUsedAt)
.HasColumnName("last_used_at");
entity.Property(e => e.InvocationCount)
.HasDefaultValue(0L)
.HasColumnName("invocation_count");
entity.Property(e => e.ErrorCount)
.HasDefaultValue(0L)
.HasColumnName("error_count");
entity.Property(e => e.CreatedAt)
.HasDefaultValueSql("now()")
.HasColumnName("created_at");
entity.Property(e => e.UpdatedAt)
.HasDefaultValueSql("now()")
.HasColumnName("updated_at");
});
modelBuilder.Entity<PluginHealthHistoryEntity>(entity =>
{
entity.HasKey(e => e.Id).HasName("plugin_health_history_pkey");
entity.ToTable("plugin_health_history", schemaName);
// Indexes matching SQL migration
entity.HasIndex(e => new { e.PluginId, e.CheckedAt }, "idx_plugin_health_history_plugin")
.IsDescending(false, true);
entity.HasIndex(e => e.CheckedAt, "idx_plugin_health_history_checked")
.IsDescending(true);
// Column mappings
entity.Property(e => e.Id)
.HasDefaultValueSql("gen_random_uuid()")
.HasColumnName("id");
entity.Property(e => e.PluginId)
.HasColumnName("plugin_id");
entity.Property(e => e.CheckedAt)
.HasDefaultValueSql("now()")
.HasColumnName("checked_at");
entity.Property(e => e.Status)
.HasMaxLength(50)
.HasColumnName("status");
entity.Property(e => e.ResponseTimeMs)
.HasColumnName("response_time_ms");
entity.Property(e => e.Details)
.HasColumnType("jsonb")
.HasColumnName("details");
entity.Property(e => e.ErrorMessage)
.HasColumnName("error_message");
entity.Property(e => e.CreatedAt)
.HasDefaultValueSql("now()")
.HasColumnName("created_at");
});
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}

View File

@@ -0,0 +1,34 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
namespace StellaOps.Plugin.Registry.EfCore.Context;
/// <summary>
/// Design-time DbContext factory for dotnet ef CLI tooling.
/// </summary>
public sealed class PluginRegistryDesignTimeDbContextFactory
: IDesignTimeDbContextFactory<PluginRegistryDbContext>
{
private const string DefaultConnectionString =
"Host=localhost;Port=55433;Database=postgres;Username=postgres;Password=postgres;Search Path=platform,public";
private const string ConnectionStringEnvironmentVariable =
"STELLAOPS_PLUGINREGISTRY_EF_CONNECTION";
public PluginRegistryDbContext CreateDbContext(string[] args)
{
var connectionString = ResolveConnectionString();
var options = new DbContextOptionsBuilder<PluginRegistryDbContext>()
.UseNpgsql(connectionString)
.Options;
return new PluginRegistryDbContext(options);
}
private static string ResolveConnectionString()
{
var fromEnvironment = Environment.GetEnvironmentVariable(ConnectionStringEnvironmentVariable);
return string.IsNullOrWhiteSpace(fromEnvironment)
? DefaultConnectionString
: fromEnvironment;
}
}

View File

@@ -0,0 +1,9 @@
namespace StellaOps.Plugin.Registry.EfCore.Models;
/// <summary>
/// Navigation properties for PluginCapabilityEntity.
/// </summary>
public partial class PluginCapabilityEntity
{
public virtual PluginEntity Plugin { get; set; } = null!;
}

View File

@@ -0,0 +1,21 @@
namespace StellaOps.Plugin.Registry.EfCore.Models;
/// <summary>
/// EF Core entity for the platform.plugin_capabilities table.
/// </summary>
public partial class PluginCapabilityEntity
{
public Guid Id { get; set; }
public Guid PluginId { get; set; }
public string CapabilityType { get; set; } = null!;
public string CapabilityId { get; set; } = null!;
public string? ConfigSchema { get; set; }
public string? InputSchema { get; set; }
public string? OutputSchema { get; set; }
public string? DisplayName { get; set; }
public string? Description { get; set; }
public string? DocumentationUrl { get; set; }
public string? Metadata { get; set; }
public bool IsEnabled { get; set; }
public DateTimeOffset CreatedAt { get; set; }
}

View File

@@ -0,0 +1,11 @@
namespace StellaOps.Plugin.Registry.EfCore.Models;
/// <summary>
/// Navigation properties and collection initializers for PluginEntity.
/// </summary>
public partial class PluginEntity
{
public virtual ICollection<PluginCapabilityEntity> PluginCapabilities { get; set; } = new List<PluginCapabilityEntity>();
public virtual ICollection<PluginInstanceEntity> PluginInstances { get; set; } = new List<PluginInstanceEntity>();
public virtual ICollection<PluginHealthHistoryEntity> HealthHistory { get; set; } = new List<PluginHealthHistoryEntity>();
}

View File

@@ -0,0 +1,33 @@
namespace StellaOps.Plugin.Registry.EfCore.Models;
/// <summary>
/// EF Core entity for the platform.plugins table.
/// </summary>
public partial class PluginEntity
{
public Guid Id { get; set; }
public string PluginId { get; set; } = null!;
public string Name { get; set; } = null!;
public string Version { get; set; } = null!;
public string Vendor { get; set; } = null!;
public string? Description { get; set; }
public string? LicenseId { get; set; }
public string TrustLevel { get; set; } = null!;
public byte[]? Signature { get; set; }
public string? SigningKeyId { get; set; }
public string[] Capabilities { get; set; } = [];
public string CapabilityDetails { get; set; } = "{}";
public string Source { get; set; } = null!;
public string? AssemblyPath { get; set; }
public string? EntryPoint { get; set; }
public string Status { get; set; } = null!;
public string? StatusMessage { get; set; }
public string? HealthStatus { get; set; }
public DateTimeOffset? LastHealthCheck { get; set; }
public int HealthCheckFailures { get; set; }
public string? Manifest { get; set; }
public string? RuntimeInfo { get; set; }
public DateTimeOffset CreatedAt { get; set; }
public DateTimeOffset UpdatedAt { get; set; }
public DateTimeOffset? LoadedAt { get; set; }
}

View File

@@ -0,0 +1,9 @@
namespace StellaOps.Plugin.Registry.EfCore.Models;
/// <summary>
/// Navigation properties for PluginHealthHistoryEntity.
/// </summary>
public partial class PluginHealthHistoryEntity
{
public virtual PluginEntity Plugin { get; set; } = null!;
}

View File

@@ -0,0 +1,16 @@
namespace StellaOps.Plugin.Registry.EfCore.Models;
/// <summary>
/// EF Core entity for the platform.plugin_health_history table.
/// </summary>
public partial class PluginHealthHistoryEntity
{
public Guid Id { get; set; }
public Guid PluginId { get; set; }
public DateTimeOffset CheckedAt { get; set; }
public string Status { get; set; } = null!;
public int? ResponseTimeMs { get; set; }
public string? Details { get; set; }
public string? ErrorMessage { get; set; }
public DateTimeOffset CreatedAt { get; set; }
}

View File

@@ -0,0 +1,9 @@
namespace StellaOps.Plugin.Registry.EfCore.Models;
/// <summary>
/// Navigation properties for PluginInstanceEntity.
/// </summary>
public partial class PluginInstanceEntity
{
public virtual PluginEntity Plugin { get; set; } = null!;
}

View File

@@ -0,0 +1,22 @@
namespace StellaOps.Plugin.Registry.EfCore.Models;
/// <summary>
/// EF Core entity for the platform.plugin_instances table.
/// </summary>
public partial class PluginInstanceEntity
{
public Guid Id { get; set; }
public Guid PluginId { get; set; }
public Guid? TenantId { get; set; }
public string? InstanceName { get; set; }
public string Config { get; set; } = "{}";
public string? SecretsPath { get; set; }
public bool Enabled { get; set; }
public string Status { get; set; } = null!;
public string? ResourceLimits { get; set; }
public DateTimeOffset? LastUsedAt { get; set; }
public long InvocationCount { get; set; }
public long ErrorCount { get; set; }
public DateTimeOffset CreatedAt { get; set; }
public DateTimeOffset UpdatedAt { get; set; }
}

View File

@@ -0,0 +1,35 @@
using Microsoft.EntityFrameworkCore;
using Npgsql;
using StellaOps.Plugin.Registry.EfCore.CompiledModels;
using StellaOps.Plugin.Registry.EfCore.Context;
namespace StellaOps.Plugin.Registry.Postgres;
/// <summary>
/// Runtime factory for PluginRegistryDbContext with compiled model support.
/// </summary>
internal static class PluginRegistryDbContextFactory
{
public const string DefaultSchemaName = "platform";
public static PluginRegistryDbContext Create(
NpgsqlConnection connection,
int commandTimeoutSeconds,
string schemaName)
{
var normalizedSchema = string.IsNullOrWhiteSpace(schemaName)
? DefaultSchemaName
: schemaName.Trim();
var optionsBuilder = new DbContextOptionsBuilder<PluginRegistryDbContext>()
.UseNpgsql(connection, npgsql => npgsql.CommandTimeout(commandTimeoutSeconds));
// Use static compiled model ONLY for default schema path
if (string.Equals(normalizedSchema, DefaultSchemaName, StringComparison.Ordinal))
{
optionsBuilder.UseModel(PluginRegistryDbContextModel.Instance);
}
return new PluginRegistryDbContext(optionsBuilder.Options, normalizedSchema);
}
}

View File

@@ -12,6 +12,17 @@
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Migrations\**\*.sql" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
</ItemGroup>
<ItemGroup>
<!-- Prevent automatic compiled-model binding so non-default schemas can build runtime models. -->
<Compile Remove="EfCore\CompiledModels\PluginRegistryDbContextAssemblyAttributes.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" PrivateAssets="all" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
@@ -19,14 +30,11 @@
<PackageReference Include="Microsoft.Extensions.Options" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" />
<PackageReference Include="Npgsql" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Plugin.Abstractions\StellaOps.Plugin.Abstractions.csproj" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Migrations\*.sql" />
</ItemGroup>
</Project>