chore(libs): infrastructure postgres host + attestation slicing + testkit
Shared infrastructure supporting the truthful runtime persistence cutover sprints — no dedicated sprint owner, these libs are consumed by multiple services. - Infrastructure.Postgres: MigrationCategory + StartupMigrationHost + tests (MigrationExecution, Recording, Flags). - AdvisoryAI.Attestation: slice AiAttestationService into partial files (Create/Read/Verify), align IAiAttestationStore + InMemory store, service tests. - TestKit: ValkeyFixture for tests that need a shared valkey instance. - Doctor/AdvisoryAI/IEvidenceSchemaRegistry: shared interface. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -20,7 +20,7 @@ public enum MigrationCategory
|
||||
Release,
|
||||
|
||||
/// <summary>
|
||||
/// Seed data that is inserted once.
|
||||
/// Optional seed/demo data that is inserted only when an operator runs it explicitly.
|
||||
/// Prefix: S001-S999
|
||||
/// </summary>
|
||||
Seed,
|
||||
@@ -83,10 +83,11 @@ public static class MigrationCategoryExtensions
|
||||
/// Returns true if this migration should run automatically at startup.
|
||||
/// </summary>
|
||||
public static bool IsAutomatic(this MigrationCategory category) =>
|
||||
category is MigrationCategory.Startup or MigrationCategory.Seed;
|
||||
category is MigrationCategory.Startup;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this migration requires manual CLI execution.
|
||||
/// Returns true if this migration requires manual CLI execution before startup may proceed.
|
||||
/// Seed migrations are intentionally excluded because they are opt-in/manual only and must not block runtime.
|
||||
/// </summary>
|
||||
public static bool RequiresManualExecution(this MigrationCategory category) =>
|
||||
category is MigrationCategory.Release or MigrationCategory.Data;
|
||||
|
||||
@@ -15,8 +15,8 @@ namespace StellaOps.Infrastructure.Postgres.Migrations;
|
||||
/// This service:
|
||||
/// - Acquires an advisory lock to prevent concurrent migrations
|
||||
/// - Validates checksums of already-applied migrations
|
||||
/// - Blocks startup if pending release migrations exist
|
||||
/// - Runs only Category A (startup) and seed migrations automatically
|
||||
/// - Blocks startup if pending release/data migrations exist
|
||||
/// - Runs only startup migrations automatically; seed migrations remain operator-invoked
|
||||
/// </remarks>
|
||||
public abstract class StartupMigrationHost : IHostedService
|
||||
{
|
||||
@@ -116,37 +116,64 @@ public abstract class StartupMigrationHost : IHostedService
|
||||
}
|
||||
}
|
||||
|
||||
// Step 5: Check for pending release migrations
|
||||
var pendingRelease = allMigrations
|
||||
// Step 5: Check for pending release/data migrations
|
||||
var pendingManual = allMigrations
|
||||
.Where(m => !appliedMigrations.ContainsKey(m.Name))
|
||||
.Where(m => m.Category.RequiresManualExecution())
|
||||
.ToList();
|
||||
|
||||
if (pendingRelease.Count > 0)
|
||||
if (pendingManual.Count > 0)
|
||||
{
|
||||
_logger.LogError(
|
||||
"Migration: {Count} pending release migration(s) require manual execution for {Module}:",
|
||||
pendingRelease.Count, _moduleName);
|
||||
"Migration: {Count} pending manual migration(s) require explicit execution before {Module} startup can converge:",
|
||||
pendingManual.Count,
|
||||
_moduleName);
|
||||
|
||||
foreach (var migration in pendingRelease)
|
||||
foreach (var migration in pendingManual)
|
||||
{
|
||||
_logger.LogError(" - {Migration} (Category: {Category})", migration.Name, migration.Category);
|
||||
}
|
||||
|
||||
_logger.LogError("Run: stellaops db migrate --module {Module} --category release", _moduleName);
|
||||
foreach (var category in pendingManual
|
||||
.Select(static migration => migration.Category)
|
||||
.Distinct()
|
||||
.OrderBy(static category => category.ToString(), StringComparer.Ordinal))
|
||||
{
|
||||
_logger.LogError(
|
||||
"Run: stella system migrations-run --module {Module} --category {Category}",
|
||||
_moduleName,
|
||||
category.ToString().ToLowerInvariant());
|
||||
}
|
||||
|
||||
if (_options.FailOnPendingReleaseMigrations)
|
||||
{
|
||||
_lifetime.StopApplication();
|
||||
throw new InvalidOperationException(
|
||||
$"Pending release migrations block startup for {_moduleName}. Run CLI migration first.");
|
||||
$"Pending manual migrations block startup for {_moduleName}. Run CLI migration first.");
|
||||
}
|
||||
}
|
||||
|
||||
var pendingSeed = allMigrations
|
||||
.Where(m => !appliedMigrations.ContainsKey(m.Name))
|
||||
.Where(static m => m.Category == MigrationCategory.Seed)
|
||||
.OrderBy(m => m.Name)
|
||||
.ToList();
|
||||
|
||||
if (pendingSeed.Count > 0)
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"Migration: {Count} optional seed migration(s) are pending for {Module}. They remain manual-only and will not run at startup.",
|
||||
pendingSeed.Count,
|
||||
_moduleName);
|
||||
_logger.LogInformation(
|
||||
"Run manually when needed: stella system migrations-run --module {Module} --category seed",
|
||||
_moduleName);
|
||||
}
|
||||
|
||||
// Step 6: Execute pending startup migrations
|
||||
var pendingStartup = allMigrations
|
||||
.Where(m => !appliedMigrations.ContainsKey(m.Name))
|
||||
.Where(m => m.Category.IsAutomatic())
|
||||
.Where(static m => m.Category == MigrationCategory.Startup)
|
||||
.OrderBy(m => m.Name)
|
||||
.ToList();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user