using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Npgsql;
using StellaOps.Infrastructure.Postgres.Migrations;
using StellaOps.JobEngine.Core.Backfill;
using StellaOps.JobEngine.Core.DeadLetter;
using StellaOps.JobEngine.Core.Observability;
using StellaOps.JobEngine.Core.Repositories;
using StellaOps.JobEngine.Core.Services;
using StellaOps.JobEngine.Infrastructure.Caching;
using StellaOps.JobEngine.Infrastructure.Ledger;
using StellaOps.JobEngine.Infrastructure.Observability;
using StellaOps.JobEngine.Infrastructure.Options;
using StellaOps.JobEngine.Infrastructure.Postgres;
using StellaOps.JobEngine.Infrastructure.Repositories;
using StellaOps.JobEngine.Infrastructure.Services;
using System;
using System.Linq;
namespace StellaOps.JobEngine.Infrastructure;
///
/// Extension methods for registering JobEngine infrastructure services.
///
public static class ServiceCollectionExtensions
{
///
/// Adds JobEngine infrastructure services to the service collection.
///
/// The service collection.
/// The configuration.
/// The service collection for chaining.
public static IServiceCollection AddJobEngineInfrastructure(
this IServiceCollection services,
IConfiguration configuration)
{
// Register configuration options
services.AddOptions()
.Bind(configuration.GetSection(JobEngineServiceOptions.SectionName))
.PostConfigure(options =>
{
var explicitConnection = configuration[$"{JobEngineServiceOptions.SectionName}:Database:ConnectionString"];
if (!string.IsNullOrWhiteSpace(explicitConnection))
{
options.Database.ConnectionString = explicitConnection;
return;
}
var orchestratorConnection = configuration["Orchestrator:Database:ConnectionString"];
if (ShouldReplaceConnectionString(options.Database.ConnectionString)
&& !string.IsNullOrWhiteSpace(orchestratorConnection))
{
options.Database.ConnectionString = orchestratorConnection;
}
var fallbackConnection =
configuration.GetConnectionString("Default")
?? configuration["ConnectionStrings:Default"];
if (string.IsNullOrWhiteSpace(fallbackConnection))
{
return;
}
if (ShouldReplaceConnectionString(options.Database.ConnectionString))
{
options.Database.ConnectionString = fallbackConnection;
}
});
services.AddStartupMigrations(
schemaName: "orchestrator",
moduleName: "JobEngine",
migrationsAssembly: typeof(JobEngineDataSource).Assembly,
connectionStringSelector: options => options.Database.ConnectionString);
// Register data source
services.AddSingleton();
// Register repositories
services.AddScoped();
services.AddScoped();
services.AddScoped();
services.AddScoped();
services.AddScoped();
services.AddScoped();
services.AddScoped();
services.AddScoped();
services.AddScoped();
services.AddScoped();
services.AddScoped();
services.AddScoped();
services.AddScoped();
// Register audit and ledger repositories
services.AddScoped();
services.AddScoped();
services.AddScoped();
services.AddScoped();
// Register ledger exporter service
services.AddScoped();
// Register duplicate suppression factory
services.AddSingleton();
// Register golden signals metrics (per ORCH-OBS-51-001)
services.AddSingleton();
// Register incident mode hooks (per ORCH-OBS-55-001)
var incidentModeOptions = configuration
.GetSection(IncidentModeHooksOptions.SectionName)
.Get() ?? new IncidentModeHooksOptions();
services.AddSingleton(incidentModeOptions);
services.AddSingleton();
// First signal (TTFS) services
services.Configure(configuration.GetSection(FirstSignalOptions.SectionName));
services.AddHttpClient();
services.AddSingleton();
services.AddScoped();
// Circuit breaker and quota governance services (per SPRINT_20260208_042)
services.AddScoped();
services.AddScoped();
return services;
}
private static bool ShouldReplaceConnectionString(string? configuredConnectionString)
{
if (string.IsNullOrWhiteSpace(configuredConnectionString))
{
return true;
}
try
{
var builder = new NpgsqlConnectionStringBuilder(configuredConnectionString);
var host = builder.Host?.Trim();
if (string.IsNullOrWhiteSpace(host))
{
return true;
}
return host.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.All(IsLoopbackHost);
}
catch
{
return false;
}
}
private static bool IsLoopbackHost(string host)
{
return host.Equals("localhost", StringComparison.OrdinalIgnoreCase)
|| host.Equals("127.0.0.1", StringComparison.OrdinalIgnoreCase)
|| host.Equals("::1", StringComparison.OrdinalIgnoreCase);
}
}