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

@@ -7,7 +7,9 @@ using System.Text.Json;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using StellaOps.Cli.Commands;
using StellaOps.Cli.Tests.Testing;
using StellaOps.Cli.Services;
using StellaOps.Cli.Services.Models;
using StellaOps.Cli.Services.Models.AdvisoryAi;
using StellaOps.TestKit;
using Xunit;
@@ -150,6 +152,113 @@ public sealed class KnowledgeSearchCommandGroupTests
backend.VerifyAll();
}
[Fact]
public async Task AdvisoryAiSourcesPrepareCommand_GeneratesSeedArtifacts()
{
using var temp = new TempDirectory();
var docsDirectory = Path.Combine(temp.Path, "docs", "runbooks");
Directory.CreateDirectory(docsDirectory);
var markdownPath = Path.Combine(docsDirectory, "network.md");
await File.WriteAllTextAsync(markdownPath, "# Network\n## Retry\nUse retries.");
var docsAllowListPath = Path.Combine(temp.Path, "knowledge-docs-allowlist.json");
await File.WriteAllTextAsync(
docsAllowListPath,
"""
{
"include": [
"docs"
]
}
""");
var doctorSeedPath = Path.Combine(temp.Path, "doctor-search-seed.json");
await File.WriteAllTextAsync(
doctorSeedPath,
"""
[
{
"checkCode": "check.core.db.connectivity",
"title": "PostgreSQL connectivity",
"severity": "high",
"description": "Connectivity issue.",
"remediation": "Fix database connection settings.",
"runCommand": "stella doctor run --check check.core.db.connectivity",
"symptoms": ["connection refused"],
"tags": ["doctor"],
"references": ["docs/INSTALL_GUIDE.md"]
}
]
""");
var docsManifestPath = Path.Combine(temp.Path, "knowledge-docs-manifest.json");
var openApiOutputPath = Path.Combine(temp.Path, "openapi.aggregate.json");
var doctorControlsPath = Path.Combine(temp.Path, "doctor-search-controls.json");
ApiSpecDownloadRequest? capturedApiRequest = null;
var backend = new Mock<IBackendOperationsClient>(MockBehavior.Strict);
backend
.Setup(client => client.DownloadApiSpecAsync(
It.IsAny<ApiSpecDownloadRequest>(),
It.IsAny<CancellationToken>()))
.Callback<ApiSpecDownloadRequest, CancellationToken>((request, _) => capturedApiRequest = request)
.ReturnsAsync(new ApiSpecDownloadResult
{
Success = true,
Path = openApiOutputPath,
FromCache = false,
Checksum = "deadbeef",
ChecksumAlgorithm = "sha256"
});
using var services = new ServiceCollection()
.AddSingleton(backend.Object)
.BuildServiceProvider();
var root = new RootCommand();
root.Add(KnowledgeSearchCommandGroup.BuildAdvisoryAiCommand(
services,
new Option<bool>("--verbose"),
CancellationToken.None));
var invocation = await InvokeWithCapturedConsoleAsync(
root,
$"advisoryai sources prepare --repo-root \"{temp.Path}\" --docs-allowlist \"{docsAllowListPath}\" --docs-manifest-output \"{docsManifestPath}\" --openapi-output \"{openApiOutputPath}\" --doctor-seed \"{doctorSeedPath}\" --doctor-controls-output \"{doctorControlsPath}\" --json");
Assert.Equal(0, invocation.ExitCode);
Assert.NotNull(capturedApiRequest);
Assert.Equal(openApiOutputPath, capturedApiRequest!.OutputPath);
Assert.Equal("openapi-json", capturedApiRequest.Format);
Assert.Null(capturedApiRequest.Service);
Assert.True(File.Exists(docsManifestPath));
Assert.True(File.Exists(doctorControlsPath));
using var manifest = JsonDocument.Parse(await File.ReadAllTextAsync(docsManifestPath));
var include = manifest.RootElement.GetProperty("include");
Assert.True(include.GetArrayLength() >= 1);
Assert.Contains(include.EnumerateArray(), element => element.GetString() == "docs");
var documents = manifest.RootElement.GetProperty("documents");
Assert.Equal(1, documents.GetArrayLength());
Assert.Equal("docs/runbooks/network.md", documents[0].GetProperty("path").GetString());
using var controls = JsonDocument.Parse(await File.ReadAllTextAsync(doctorControlsPath));
Assert.Equal(1, controls.RootElement.GetArrayLength());
Assert.Equal("check.core.db.connectivity", controls.RootElement[0].GetProperty("checkCode").GetString());
Assert.Equal("manual", controls.RootElement[0].GetProperty("control").GetString());
Assert.True(controls.RootElement[0].GetProperty("requiresConfirmation").GetBoolean());
Assert.Equal("PostgreSQL connectivity", controls.RootElement[0].GetProperty("title").GetString());
Assert.Equal("high", controls.RootElement[0].GetProperty("severity").GetString());
Assert.Equal("stella doctor run --check check.core.db.connectivity", controls.RootElement[0].GetProperty("runCommand").GetString());
Assert.Contains(
controls.RootElement[0].GetProperty("tags").EnumerateArray(),
static tag => string.Equals(tag.GetString(), "doctor", StringComparison.Ordinal));
Assert.Contains(
controls.RootElement[0].GetProperty("references").EnumerateArray(),
static reference => string.Equals(reference.GetString(), "docs/INSTALL_GUIDE.md", StringComparison.Ordinal));
backend.VerifyAll();
}
private static AdvisoryKnowledgeSearchResponseModel CreateSearchResponse()
{
return new AdvisoryKnowledgeSearchResponseModel

View File

@@ -0,0 +1,91 @@
using System;
using System.Linq;
using StellaOps.Cli.Services;
using StellaOps.Infrastructure.Postgres.Migrations;
using StellaOps.Platform.Database;
using Xunit;
namespace StellaOps.Cli.Tests.Commands;
public sealed class MigrationCommandServiceTests
{
[Fact]
public void GetMissingLegacyMigrations_WhenConsolidatedOnlyApplied_ReturnsAllLegacyMigrations()
{
var module = MigrationModuleRegistry.FindModule("Scanner");
Assert.NotNull(module);
var artifact = MigrationModuleConsolidation.Build(module!);
var applied = new[]
{
new MigrationInfo(artifact.MigrationName, DateTimeOffset.UtcNow, artifact.Checksum)
};
var missing = MigrationCommandService.GetMissingLegacyMigrations(artifact, applied);
Assert.Equal(artifact.SourceMigrations.Count, missing.Count);
Assert.Equal(
artifact.SourceMigrations.Select(static migration => migration.Name),
missing.Select(static migration => migration.Name));
}
[Fact]
public void GetMissingLegacyMigrations_WhenPartiallyBackfilled_ReturnsOnlyMissingMigrations()
{
var module = MigrationModuleRegistry.FindModule("Scanner");
Assert.NotNull(module);
var artifact = MigrationModuleConsolidation.Build(module!);
var backfilled = artifact.SourceMigrations.Take(2).ToArray();
var applied = backfilled
.Select(static migration => new MigrationInfo(migration.Name, DateTimeOffset.UtcNow, migration.Checksum))
.Concat(new[] { new MigrationInfo(artifact.MigrationName, DateTimeOffset.UtcNow, artifact.Checksum) })
.ToArray();
var missing = MigrationCommandService.GetMissingLegacyMigrations(artifact, applied);
Assert.Equal(artifact.SourceMigrations.Count - backfilled.Length, missing.Count);
Assert.DoesNotContain(missing, migration => backfilled.Any(existing => existing.Name == migration.Name));
}
[Fact]
public void IsConsolidatedInSync_WhenChecksumsMatch_ReturnsTrue()
{
var module = MigrationModuleRegistry.FindModule("Platform");
Assert.NotNull(module);
var artifact = MigrationModuleConsolidation.Build(module!);
var applied = new MigrationInfo(artifact.MigrationName, DateTimeOffset.UtcNow, artifact.Checksum);
var inSync = MigrationCommandService.IsConsolidatedInSync(artifact, applied);
Assert.True(inSync);
}
[Fact]
public void IsConsolidatedInSync_WhenChecksumsDiffer_ReturnsFalse()
{
var module = MigrationModuleRegistry.FindModule("Platform");
Assert.NotNull(module);
var artifact = MigrationModuleConsolidation.Build(module!);
var applied = new MigrationInfo(artifact.MigrationName, DateTimeOffset.UtcNow, "deadbeef");
var inSync = MigrationCommandService.IsConsolidatedInSync(artifact, applied);
Assert.False(inSync);
}
[Fact]
public void IsConsolidatedInSync_WhenConsolidatedNotApplied_ReturnsFalse()
{
var module = MigrationModuleRegistry.FindModule("Platform");
Assert.NotNull(module);
var artifact = MigrationModuleConsolidation.Build(module!);
var inSync = MigrationCommandService.IsConsolidatedInSync(artifact, null);
Assert.False(inSync);
}
}

View File

@@ -0,0 +1,68 @@
using System;
using System.Linq;
using StellaOps.Platform.Database;
using Xunit;
namespace StellaOps.Cli.Tests.Commands;
public sealed class MigrationModuleConsolidationTests
{
[Fact]
public void Build_ForEveryRegisteredModule_ProducesOneUniqueConsolidatedMigration()
{
var modules = MigrationModuleRegistry.GetModules(null).ToArray();
var migrationNames = new HashSet<string>(StringComparer.Ordinal);
foreach (var module in modules)
{
var artifact = MigrationModuleConsolidation.Build(module);
Assert.NotNull(artifact);
Assert.NotEmpty(artifact.Script);
Assert.NotEmpty(artifact.Checksum);
Assert.NotEmpty(artifact.SourceMigrations);
Assert.True(
migrationNames.Add(artifact.MigrationName),
$"Duplicate consolidated migration name '{artifact.MigrationName}' for module '{module.Name}'.");
}
Assert.Equal(modules.Length, migrationNames.Count);
}
[Fact]
public void Build_ForScanner_ProducesSingleConsolidatedMigration()
{
var scanner = MigrationModuleRegistry.FindModule("Scanner");
Assert.NotNull(scanner);
var artifact = MigrationModuleConsolidation.Build(scanner!);
Assert.Equal("100_consolidated_scanner.sql", artifact.MigrationName);
Assert.Equal(36, artifact.SourceMigrations.Count);
Assert.Contains(
artifact.SourceMigrations,
static migration => string.Equals(
migration.Name,
"022a_runtime_observations_compat.sql",
StringComparison.Ordinal));
Assert.Contains(
artifact.SourceMigrations,
static migration => string.Equals(
migration.Name,
"V3700_001__triage_schema.sql",
StringComparison.Ordinal));
}
[Fact]
public void Build_IsDeterministic_ForSameModule()
{
var module = MigrationModuleRegistry.FindModule("Platform");
Assert.NotNull(module);
var first = MigrationModuleConsolidation.Build(module!);
var second = MigrationModuleConsolidation.Build(module!);
Assert.Equal(first.MigrationName, second.MigrationName);
Assert.Equal(first.Checksum, second.Checksum);
Assert.Equal(first.Script, second.Script);
Assert.Equal(first.SourceMigrations.Select(static migration => migration.Name), second.SourceMigrations.Select(static migration => migration.Name));
}
}

View File

@@ -1,3 +1,4 @@
using System;
using System.Linq;
using StellaOps.Platform.Database;
using Xunit;
@@ -10,18 +11,35 @@ public class MigrationModuleRegistryTests
public void Modules_Populated_With_All_Postgres_Modules()
{
var modules = MigrationModuleRegistry.Modules;
Assert.Equal(10, modules.Count);
Assert.True(modules.Count >= 20, $"Expected at least 20 registered modules, found {modules.Count}");
Assert.Contains(modules, m => m.Name == "AdvisoryAI" && m.SchemaName == "advisoryai");
Assert.Contains(modules, m => m.Name == "AirGap" && m.SchemaName == "airgap");
Assert.Contains(modules, m => m.Name == "Authority" && m.SchemaName == "authority");
Assert.Contains(modules, m => m.Name == "Eventing" && m.SchemaName == "timeline");
Assert.Contains(modules, m => m.Name == "Evidence" && m.SchemaName == "evidence");
Assert.Contains(modules, m => m.Name == "Scheduler" && m.SchemaName == "scheduler");
Assert.Contains(modules, m => m.Name == "Concelier" && m.SchemaName == "vuln");
Assert.Contains(modules, m => m.Name == "Policy" && m.SchemaName == "policy");
Assert.Contains(modules, m => m.Name == "Notify" && m.SchemaName == "notify");
Assert.Contains(modules, m => m.Name == "Excititor" && m.SchemaName == "vex");
Assert.Contains(modules, m => m.Name == "PluginRegistry" && m.SchemaName == "platform");
Assert.Contains(modules, m => m.Name == "Platform" && m.SchemaName == "release");
Assert.Contains(modules, m => m.Name == "Scanner" && m.SchemaName == "scanner");
var scanner = Assert.Single(modules, static module => module.Name == "Scanner" && module.SchemaName == "scanner");
Assert.Equal(2, scanner.Sources.Count);
Assert.Contains(
scanner.Sources,
static source => string.Equals(
source.ResourcePrefix,
"StellaOps.Scanner.Triage.Migrations",
StringComparison.Ordinal));
Assert.Contains(modules, m => m.Name == "TimelineIndexer" && m.SchemaName == "timeline");
Assert.Equal(10, MigrationModuleRegistry.ModuleNames.Count());
Assert.Contains(modules, m => m.Name == "VexHub" && m.SchemaName == "vexhub");
Assert.Contains(modules, m => m.Name == "Remediation" && m.SchemaName == "remediation");
Assert.Contains(modules, m => m.Name == "VexLens" && m.SchemaName == "vexlens");
Assert.Contains(modules, m => m.Name == "SbomLineage" && m.SchemaName == "sbom");
Assert.Contains(modules, m => m.Name == "ReachGraph" && m.SchemaName == "reachgraph");
Assert.Contains(modules, m => m.Name == "Verdict" && m.SchemaName == "stellaops");
Assert.True(MigrationModuleRegistry.ModuleNames.Count() >= 20);
}
[Fact]
@@ -60,6 +78,6 @@ public class MigrationModuleRegistryTests
public void GetModules_All_Returns_All()
{
var result = MigrationModuleRegistry.GetModules(null);
Assert.Equal(10, result.Count());
Assert.True(result.Count() >= 20);
}
}

View File

@@ -0,0 +1,138 @@
using System.CommandLine;
using System.Net;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Moq.Protected;
using StellaOps.Cli.Commands.Budget;
using StellaOps.TestKit;
using Xunit;
namespace StellaOps.Cli.Tests.Commands;
[Trait("Category", TestCategories.Unit)]
public sealed class RiskBudgetCommandTenantHeaderTests
{
[Fact]
public async Task BudgetStatus_AddsTenantHeader_FromTenantOption()
{
// Arrange
var (services, handlerMock) = CreateServices();
HttpRequestMessage? capturedRequest = null;
handlerMock
.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.Is<HttpRequestMessage>(request =>
request.Method == HttpMethod.Get &&
request.RequestUri != null &&
request.RequestUri.ToString().Contains("/api/v1/policy/risk-budget/status/", StringComparison.Ordinal)),
ItExpr.IsAny<CancellationToken>())
.Callback<HttpRequestMessage, CancellationToken>((request, _) => capturedRequest = request)
.ReturnsAsync(CreateStatusResponse());
var command = RiskBudgetCommandGroup.BuildBudgetCommand(services, new Option<bool>("--verbose"), CancellationToken.None);
var root = new RootCommand { command };
using var writer = new StringWriter();
var originalOut = Console.Out;
int exitCode;
try
{
Console.SetOut(writer);
exitCode = await root.Parse("budget status --service svc-a --output json --tenant Tenant-Bravo").InvokeAsync();
}
finally
{
Console.SetOut(originalOut);
}
// Assert
Assert.Equal(0, exitCode);
Assert.NotNull(capturedRequest);
Assert.True(capturedRequest.Headers.TryGetValues("X-Tenant-Id", out var tenantValues));
Assert.Equal("tenant-bravo", Assert.Single(tenantValues));
}
[Fact]
public async Task BudgetStatus_AddsTenantHeader_FromEnvironmentFallback()
{
// Arrange
var originalTenant = Environment.GetEnvironmentVariable("STELLAOPS_TENANT");
Environment.SetEnvironmentVariable("STELLAOPS_TENANT", "Tenant-Env");
var (services, handlerMock) = CreateServices();
HttpRequestMessage? capturedRequest = null;
handlerMock
.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.Is<HttpRequestMessage>(request =>
request.Method == HttpMethod.Get &&
request.RequestUri != null &&
request.RequestUri.ToString().Contains("/api/v1/policy/risk-budget/status/", StringComparison.Ordinal)),
ItExpr.IsAny<CancellationToken>())
.Callback<HttpRequestMessage, CancellationToken>((request, _) => capturedRequest = request)
.ReturnsAsync(CreateStatusResponse());
var command = RiskBudgetCommandGroup.BuildBudgetCommand(services, new Option<bool>("--verbose"), CancellationToken.None);
var root = new RootCommand { command };
using var writer = new StringWriter();
var originalOut = Console.Out;
int exitCode;
try
{
Console.SetOut(writer);
exitCode = await root.Parse("budget status --service svc-a --output json").InvokeAsync();
}
finally
{
Console.SetOut(originalOut);
Environment.SetEnvironmentVariable("STELLAOPS_TENANT", originalTenant);
}
// Assert
Assert.Equal(0, exitCode);
Assert.NotNull(capturedRequest);
Assert.True(capturedRequest.Headers.TryGetValues("X-Tenant-Id", out var tenantValues));
Assert.Equal("tenant-env", Assert.Single(tenantValues));
}
private static (IServiceProvider Services, Mock<HttpMessageHandler> HandlerMock) CreateServices()
{
var handlerMock = new Mock<HttpMessageHandler>();
var httpClient = new HttpClient(handlerMock.Object)
{
BaseAddress = new Uri("http://localhost:8080"),
};
var factoryMock = new Mock<IHttpClientFactory>();
factoryMock
.Setup(factory => factory.CreateClient("PolicyApi"))
.Returns(httpClient);
var services = new ServiceCollection();
services.AddSingleton(factoryMock.Object);
services.AddSingleton(NullLoggerFactory.Instance);
return (services.BuildServiceProvider(), handlerMock);
}
private static HttpResponseMessage CreateStatusResponse()
{
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(
"""
{
"serviceId": "svc-a",
"window": "2026-02",
"tier": 1,
"allocated": 100,
"consumed": 15,
"remaining": 85,
"percentageUsed": 15.0,
"status": "green"
}
""")
};
}
}

View File

@@ -372,6 +372,94 @@ public class UnknownsGreyQueueCommandTests
Assert.Contains("id,package_id,package_version,band,score", output);
}
[Fact]
public async Task UnknownsList_AddsTenantHeader_FromTenantOption()
{
// Arrange
HttpRequestMessage? capturedRequest = null;
_httpHandlerMock
.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.Is<HttpRequestMessage>(request =>
request.Method == HttpMethod.Get &&
request.RequestUri != null &&
request.RequestUri.ToString().Contains("/api/v1/policy/unknowns", StringComparison.Ordinal)),
ItExpr.IsAny<CancellationToken>())
.Callback<HttpRequestMessage, CancellationToken>((request, _) => capturedRequest = request)
.ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent("""{ "items": [], "totalCount": 0 }""")
});
var command = UnknownsCommandGroup.BuildUnknownsCommand(_services, new Option<bool>("--verbose"), CancellationToken.None);
var root = new RootCommand { command };
using var writer = new StringWriter();
var originalOut = Console.Out;
int exitCode;
try
{
Console.SetOut(writer);
exitCode = await root.Parse("unknowns --tenant Tenant-Bravo list --format json").InvokeAsync();
}
finally
{
Console.SetOut(originalOut);
}
// Assert
Assert.Equal(0, exitCode);
Assert.NotNull(capturedRequest);
Assert.True(capturedRequest.Headers.TryGetValues("X-Tenant-Id", out var tenantValues));
Assert.Equal("tenant-bravo", Assert.Single(tenantValues));
}
[Fact]
public async Task UnknownsList_AddsTenantHeader_FromEnvironmentFallback()
{
// Arrange
var originalTenant = Environment.GetEnvironmentVariable("STELLAOPS_TENANT");
Environment.SetEnvironmentVariable("STELLAOPS_TENANT", "Tenant-Env");
HttpRequestMessage? capturedRequest = null;
_httpHandlerMock
.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.Is<HttpRequestMessage>(request =>
request.Method == HttpMethod.Get &&
request.RequestUri != null &&
request.RequestUri.ToString().Contains("/api/v1/policy/unknowns", StringComparison.Ordinal)),
ItExpr.IsAny<CancellationToken>())
.Callback<HttpRequestMessage, CancellationToken>((request, _) => capturedRequest = request)
.ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent("""{ "items": [], "totalCount": 0 }""")
});
var command = UnknownsCommandGroup.BuildUnknownsCommand(_services, new Option<bool>("--verbose"), CancellationToken.None);
var root = new RootCommand { command };
using var writer = new StringWriter();
var originalOut = Console.Out;
int exitCode;
try
{
Console.SetOut(writer);
exitCode = await root.Parse("unknowns list --format json").InvokeAsync();
}
finally
{
Console.SetOut(originalOut);
Environment.SetEnvironmentVariable("STELLAOPS_TENANT", originalTenant);
}
// Assert
Assert.Equal(0, exitCode);
Assert.NotNull(capturedRequest);
Assert.True(capturedRequest.Headers.TryGetValues("X-Tenant-Id", out var tenantValues));
Assert.Equal("tenant-env", Assert.Single(tenantValues));
}
private void SetupPolicyUnknownsResponse(string json)
{
_httpHandlerMock

View File

@@ -5,7 +5,9 @@ Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229
| Task ID | Status | Notes |
| --- | --- | --- |
| SPRINT_20260222_051-AKS-CLI-TESTS | DONE | Added AKS CLI source-preparation command coverage (`AdvisoryAiSourcesPrepareCommand_GeneratesSeedArtifacts`) including enriched doctor control metadata assertions, and revalidated knowledge-search command group tests (4/4 on 2026-02-22). |
| SPRINT_20260222_051-MGC-04-W1-TESTS | DONE | Updated migration registry/system command tests for platform-owned 10-module coverage and validated with `dotnet test` (1182 passed on 2026-02-22). |
| SPRINT_20260222_051-MGC-04-W1-SOURCES-TESTS | DONE | Extended migration tests to assert per-service source-set flattening metadata, deterministic synthesized consolidated artifact generation (including unique consolidated artifact per registered plugin), and partial-backfill missing-legacy detection behavior; revalidated with `dotnet test` (`1194` passed on 2026-02-22). |
| AUDIT-0143-M | DONE | Revalidated 2026-01-06. |
| AUDIT-0143-T | DONE | Revalidated 2026-01-06. |
| AUDIT-0143-A | DONE | Waived (test project; revalidated 2026-01-06). |

View File

@@ -12,6 +12,14 @@ namespace StellaOps.Policy.Unknowns.Models
public sealed record UnknownPlaceholder;
}
namespace StellaOps.Cli.Services
{
internal static class TenantProfileStore
{
public static string? GetEffectiveTenant(string? commandLineTenant) => commandLineTenant;
}
}
namespace System.CommandLine
{
// Compatibility shims for the System.CommandLine API shape expected by UnknownsCommandGroup.