Harden remaining runtime transport lifecycles

This commit is contained in:
master
2026-04-06 00:24:16 +03:00
parent 751546084e
commit fc798a1573
29 changed files with 311 additions and 107 deletions

View File

@@ -19,4 +19,29 @@ public sealed partial class EgressPolicyTests
Assert.True(recordingPolicy.EnsureAllowedCalled);
Assert.NotNull(client);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void EgressHttpClientFactory_Create_UsingFallbackFactory_ReturnsIsolatedClients()
{
var recordingPolicy = new RecordingPolicy();
var request = new EgressRequest("Component", new Uri("https://allowed.internal"), "mirror-sync");
using var first = EgressHttpClientFactory.Create(
recordingPolicy,
request,
client =>
{
client.BaseAddress = new Uri("https://first.internal/");
client.DefaultRequestHeaders.Add("X-Test", "one");
});
using var second = EgressHttpClientFactory.Create(recordingPolicy, request);
Assert.True(recordingPolicy.EnsureAllowedCalled);
Assert.Equal(new Uri("https://first.internal/"), first.BaseAddress);
Assert.Null(second.BaseAddress);
Assert.True(first.DefaultRequestHeaders.Contains("X-Test"));
Assert.False(second.DefaultRequestHeaders.Contains("X-Test"));
Assert.NotEmpty(second.DefaultRequestHeaders.UserAgent);
}
}

View File

@@ -1,5 +1,7 @@
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
namespace StellaOps.AirGap.Policy;
@@ -8,6 +10,13 @@ namespace StellaOps.AirGap.Policy;
/// </summary>
public static class EgressHttpClientFactory
{
private static readonly SocketsHttpHandler SharedHandler = new()
{
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
PooledConnectionIdleTimeout = TimeSpan.FromMinutes(2),
PooledConnectionLifetime = TimeSpan.FromMinutes(15),
};
/// <summary>
/// Creates an <see cref="HttpClient"/> after validating the supplied egress request against the policy.
/// </summary>
@@ -19,11 +28,7 @@ public static class EgressHttpClientFactory
{
ArgumentNullException.ThrowIfNull(egressPolicy);
egressPolicy.EnsureAllowed(request);
var client = new HttpClient();
configure?.Invoke(client);
return client;
return Create(egressPolicy, request, CreateFallbackClient, configure);
}
/// <summary>
@@ -90,4 +95,11 @@ public static class EgressHttpClientFactory
Func<HttpClient> clientFactory,
Action<HttpClient>? configure = null)
=> Create(egressPolicy, new EgressRequest(component, destination, intent), clientFactory, configure);
private static HttpClient CreateFallbackClient()
{
var client = new HttpClient(SharedHandler, disposeHandler: false);
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("StellaOpsAirGapPolicy", "1.0"));
return client;
}
}

View File

@@ -7,6 +7,6 @@ Source of truth: `docs/implplan/SPRINT_20260130_002_Tools_csproj_remediation_sol
| --- | --- | --- |
| AUDIT-0030-M | DONE | Revalidated 2026-01-06; new findings recorded in audit report. |
| AUDIT-0030-T | DONE | Revalidated 2026-01-06; test coverage tracked in AUDIT-0033. |
| AUDIT-0030-A | TODO | Replace direct new HttpClient usage in EgressHttpClientFactory. |
| AUDIT-0030-A | DONE | `docs/implplan/SPRINT_20260405_011___Libraries_transport_pooling_and_attribution_hardening.md`: `EgressHttpClientFactory` now uses a shared-handler fallback instead of raw default `new HttpClient()` allocation. |
| REMED-05 | DONE | Remediation checklist: docs/implplan/audits/csproj-standards/remediation/checklists/src/AirGap/StellaOps.AirGap.Policy/StellaOps.AirGap.Policy/StellaOps.AirGap.Policy.md. |
| REMED-06 | DONE | SOLID review notes updated for SPRINT_20260130_002. |

View File

@@ -362,10 +362,7 @@ public sealed class GenericOciConnector : IRegistryConnectorCapability, IDisposa
}
}
_httpClient = new HttpClient
{
BaseAddress = new Uri(_registryUrl + "/")
};
_httpClient = ConnectorHttpClients.CreateClient(new Uri(_registryUrl + "/"));
if (!string.IsNullOrEmpty(_username))
{
@@ -375,9 +372,6 @@ public sealed class GenericOciConnector : IRegistryConnectorCapability, IDisposa
new AuthenticationHeaderValue("Basic", credentials);
}
_httpClient.DefaultRequestHeaders.UserAgent.Add(
new ProductInfoHeaderValue("StellaOps", "1.0"));
return _httpClient;
}

View File

@@ -375,10 +375,7 @@ public sealed class HarborConnector : IRegistryConnectorCapability, IDisposable
}
}
_httpClient = new HttpClient
{
BaseAddress = new Uri(_harborUrl + "/")
};
_httpClient = ConnectorHttpClients.CreateClient(new Uri(_harborUrl + "/"));
if (!string.IsNullOrEmpty(_username))
{
@@ -388,9 +385,6 @@ public sealed class HarborConnector : IRegistryConnectorCapability, IDisposable
new AuthenticationHeaderValue("Basic", credentials);
}
_httpClient.DefaultRequestHeaders.UserAgent.Add(
new ProductInfoHeaderValue("StellaOps", "1.0"));
return _httpClient;
}

View File

@@ -523,10 +523,7 @@ public sealed class JfrogArtifactoryConnector : IRegistryConnectorCapability, ID
}
}
_httpClient = new HttpClient
{
BaseAddress = new Uri(_artifactoryUrl + "/")
};
_httpClient = ConnectorHttpClients.CreateClient(new Uri(_artifactoryUrl + "/"));
// Set authorization header based on available auth
if (!string.IsNullOrEmpty(_accessToken))
@@ -546,9 +543,6 @@ public sealed class JfrogArtifactoryConnector : IRegistryConnectorCapability, ID
new AuthenticationHeaderValue("Basic", credentials);
}
_httpClient.DefaultRequestHeaders.UserAgent.Add(
new ProductInfoHeaderValue("StellaOps", "1.0"));
return _httpClient;
}

View File

@@ -417,10 +417,7 @@ public sealed class QuayConnector : IRegistryConnectorCapability, IDisposable
}
}
_httpClient = new HttpClient
{
BaseAddress = new Uri(_quayUrl + "/")
};
_httpClient = ConnectorHttpClients.CreateClient(new Uri(_quayUrl + "/"));
// Set authorization header
if (!string.IsNullOrEmpty(_oauth2Token))
@@ -436,9 +433,6 @@ public sealed class QuayConnector : IRegistryConnectorCapability, IDisposable
new AuthenticationHeaderValue("Basic", credentials);
}
_httpClient.DefaultRequestHeaders.UserAgent.Add(
new ProductInfoHeaderValue("StellaOps", "1.0"));
return _httpClient;
}

View File

@@ -350,16 +350,11 @@ public sealed class AzureDevOpsConnector : IScmConnectorCapability, IDisposable
var authValue = Convert.ToBase64String(Encoding.UTF8.GetBytes($":{pat}"));
_httpClient = new HttpClient
{
BaseAddress = new Uri(baseUrl)
};
_httpClient = ConnectorHttpClients.CreateClient(new Uri(baseUrl));
_httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Basic", authValue);
_httpClient.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
_httpClient.DefaultRequestHeaders.UserAgent.Add(
new ProductInfoHeaderValue("StellaOps", "1.0"));
return _httpClient;
}

View File

@@ -285,16 +285,11 @@ public sealed class GitHubConnector : IScmConnectorCapability, IDisposable
}
}
_httpClient = new HttpClient
{
BaseAddress = new Uri(baseUrl)
};
_httpClient = ConnectorHttpClients.CreateClient(new Uri(baseUrl));
_httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
_httpClient.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/vnd.github+json"));
_httpClient.DefaultRequestHeaders.UserAgent.Add(
new ProductInfoHeaderValue("StellaOps", "1.0"));
_httpClient.DefaultRequestHeaders.Add("X-GitHub-Api-Version", "2022-11-28");
return _httpClient;

View File

@@ -283,15 +283,10 @@ public sealed class GitLabConnector : IScmConnectorCapability, IDisposable
}
}
_httpClient = new HttpClient
{
BaseAddress = new Uri(baseUrl)
};
_httpClient = ConnectorHttpClients.CreateClient(new Uri(baseUrl));
_httpClient.DefaultRequestHeaders.Add("PRIVATE-TOKEN", token);
_httpClient.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
_httpClient.DefaultRequestHeaders.UserAgent.Add(
new ProductInfoHeaderValue("StellaOps", "1.0"));
return _httpClient;
}

View File

@@ -292,16 +292,11 @@ public sealed class GiteaConnector : IScmConnectorCapability, IDisposable
_baseUrl = baseUrlProp.GetString()!.TrimEnd('/');
var apiUrl = _baseUrl + "/api/v1/";
_httpClient = new HttpClient
{
BaseAddress = new Uri(apiUrl)
};
_httpClient = ConnectorHttpClients.CreateClient(new Uri(apiUrl));
_httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("token", token);
_httpClient.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
_httpClient.DefaultRequestHeaders.UserAgent.Add(
new ProductInfoHeaderValue("StellaOps", "1.0"));
return _httpClient;
}

View File

@@ -454,7 +454,7 @@ public sealed class AwsAppConfigConnector : ISettingsStoreConnectorCapability, I
// Use the AppConfig Data API for configuration retrieval
var dataEndpoint = $"https://appconfigdata.{_region}.amazonaws.com";
using var dataClient = new HttpClient { BaseAddress = new Uri(dataEndpoint) };
using var dataClient = ConnectorHttpClients.CreateClient(new Uri(dataEndpoint));
// Start a configuration session
var sessionRequest = CreateAppConfigDataRequest(
@@ -561,14 +561,8 @@ public sealed class AwsAppConfigConnector : ISettingsStoreConnectorCapability, I
var endpoint = $"https://appconfig.{_region}.amazonaws.com";
_httpClient = new HttpClient
{
BaseAddress = new Uri(endpoint + "/"),
Timeout = TimeSpan.FromSeconds(30)
};
_httpClient.DefaultRequestHeaders.UserAgent.Add(
new ProductInfoHeaderValue("StellaOps", "1.0"));
_httpClient = ConnectorHttpClients.CreateClient(new Uri(endpoint + "/"));
_httpClient.Timeout = TimeSpan.FromSeconds(30);
}
private HttpRequestMessage CreateAppConfigRequest(HttpMethod method, string path, string? payload)

View File

@@ -480,14 +480,8 @@ public sealed class AwsParameterStoreConnector : ISettingsStoreConnectorCapabili
var endpoint = $"https://ssm.{_region}.amazonaws.com";
_httpClient = new HttpClient
{
BaseAddress = new Uri(endpoint + "/"),
Timeout = TimeSpan.FromSeconds(30)
};
_httpClient.DefaultRequestHeaders.UserAgent.Add(
new ProductInfoHeaderValue("StellaOps", "1.0"));
_httpClient = ConnectorHttpClients.CreateClient(new Uri(endpoint + "/"));
_httpClient.Timeout = TimeSpan.FromSeconds(30);
}
private HttpRequestMessage CreateSsmRequest(string action, string payload)

View File

@@ -528,16 +528,11 @@ public sealed class AzureAppConfigConnector : ISettingsStoreConnectorCapability,
_label = labelProp.GetString();
}
_httpClient = new HttpClient
{
BaseAddress = new Uri(_endpoint + "/"),
Timeout = TimeSpan.FromSeconds(30)
};
_httpClient = ConnectorHttpClients.CreateClient(new Uri(_endpoint + "/"));
_httpClient.Timeout = TimeSpan.FromSeconds(30);
_httpClient.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/vnd.microsoft.appconfig.kv+json"));
_httpClient.DefaultRequestHeaders.UserAgent.Add(
new ProductInfoHeaderValue("StellaOps", "1.0"));
return _httpClient;
}

View File

@@ -462,20 +462,14 @@ public sealed class ConsulKvConnector : ISettingsStoreConnectorCapability, IDisp
}
}
_httpClient = new HttpClient
{
BaseAddress = new Uri(_consulAddress + "/"),
Timeout = TimeSpan.FromMinutes(6) // Allow for blocking queries
};
_httpClient = ConnectorHttpClients.CreateClient(new Uri(_consulAddress + "/"));
_httpClient.Timeout = TimeSpan.FromMinutes(6); // Allow for blocking queries
if (!string.IsNullOrEmpty(_token))
{
_httpClient.DefaultRequestHeaders.Add("X-Consul-Token", _token);
}
_httpClient.DefaultRequestHeaders.UserAgent.Add(
new ProductInfoHeaderValue("StellaOps", "1.0"));
return _httpClient;
}

View File

@@ -473,11 +473,8 @@ public sealed class EtcdConnector : ISettingsStoreConnectorCapability, IDisposab
}
}
_httpClient = new HttpClient
{
BaseAddress = new Uri(_etcdAddress + "/"),
Timeout = TimeSpan.FromMinutes(6)
};
_httpClient = ConnectorHttpClients.CreateClient(new Uri(_etcdAddress + "/"));
_httpClient.Timeout = TimeSpan.FromMinutes(6);
// Authenticate if credentials provided
if (!string.IsNullOrEmpty(_username) && !string.IsNullOrEmpty(_password))
@@ -485,9 +482,6 @@ public sealed class EtcdConnector : ISettingsStoreConnectorCapability, IDisposab
await AuthenticateAsync(ct);
}
_httpClient.DefaultRequestHeaders.UserAgent.Add(
new ProductInfoHeaderValue("StellaOps", "1.0"));
return _httpClient;
}

View File

@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("StellaOps.ReleaseOrchestrator.IntegrationHub.Tests")]

View File

@@ -5,5 +5,6 @@ Source of truth: `docs/implplan/SPRINT_20260130_002_Tools_csproj_remediation_sol
| Task ID | Status | Notes |
| --- | --- | --- |
| SPRINT_20260405_011-XPORT-HTTP | DONE | `docs/implplan/SPRINT_20260405_011___Libraries_transport_pooling_and_attribution_hardening.md`: legacy vault/registry connectors now use shared-handler compatibility HTTP clients instead of raw temporary `HttpClient` allocation. |
| SPRINT_20260406_011-XPORT-HTTP | DONE | `docs/implplan/SPRINT_20260405_011___Libraries_transport_pooling_and_attribution_hardening.md`: SCM, settings-store, and remaining registry connectors now source their per-connector `HttpClient` instances from `ConnectorHttpClients.CreateClient(...)` so they reuse the shared pooled handler without leaking auth headers across integrations. |
| REMED-05 | TODO | Remediation checklist: docs/implplan/audits/csproj-standards/remediation/checklists/src/ReleaseOrchestrator/__Libraries/StellaOps.ReleaseOrchestrator.IntegrationHub/StellaOps.ReleaseOrchestrator.IntegrationHub.md. |
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |

View File

@@ -0,0 +1,32 @@
using System;
using System.Linq;
using StellaOps.ReleaseOrchestrator.IntegrationHub.Connectors;
namespace StellaOps.ReleaseOrchestrator.IntegrationHub.Tests;
public sealed class ConnectorHttpClientsTests
{
[Fact]
public void CreateClient_AssignsBaseAddressAndSharedUserAgent()
{
using var client = ConnectorHttpClients.CreateClient(new Uri("https://connector.example/api/"));
Assert.Equal(new Uri("https://connector.example/api/"), client.BaseAddress);
Assert.Contains(client.DefaultRequestHeaders.UserAgent, agent => agent.Product?.Name == "StellaOps");
}
[Fact]
public void CreateClient_ReturnsIsolatedClientInstances()
{
using var first = ConnectorHttpClients.CreateClient();
using var second = ConnectorHttpClients.CreateClient();
first.DefaultRequestHeaders.Add("X-Test-Header", "one");
Assert.NotSame(first, second);
Assert.True(first.DefaultRequestHeaders.Contains("X-Test-Header"));
Assert.False(second.DefaultRequestHeaders.Contains("X-Test-Header"));
Assert.True(second.DefaultRequestHeaders.UserAgent.Any());
}
}

View File

@@ -4,5 +4,6 @@ Source of truth: `docs/implplan/SPRINT_20260130_002_Tools_csproj_remediation_sol
| Task ID | Status | Notes |
| --- | --- | --- |
| SPRINT_20260406_011-XPORT-HTTP | DONE | `docs/implplan/SPRINT_20260405_011___Libraries_transport_pooling_and_attribution_hardening.md`: added `ConnectorHttpClients` regression coverage for isolated per-connector clients on the shared pooled handler path. |
| REMED-05 | TODO | Remediation checklist: docs/implplan/audits/csproj-standards/remediation/checklists/src/ReleaseOrchestrator/__Tests/StellaOps.ReleaseOrchestrator.IntegrationHub.Tests/StellaOps.ReleaseOrchestrator.IntegrationHub.Tests.md. |
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |

23
src/Workflow/AGENTS.md Normal file
View File

@@ -0,0 +1,23 @@
# AGENTS - Workflow Module
## Working Directory
- `src/Workflow/**` (workflow abstractions, engine, storage backends, signaling, and tests).
## Required Reading
- `docs/README.md`
- `docs/07_HIGH_LEVEL_ARCHITECTURE.md`
- `docs/modules/platform/architecture-overview.md`
- `docs/modules/release-orchestrator/modules/workflow-engine.md`
## Engineering Rules
- Preserve deterministic workflow state transitions, event ordering, and projection behavior across storage backends.
- Keep PostgreSQL, signaling, and engine changes compatible at the contract level unless the sprint explicitly scopes a cross-backend divergence.
- Runtime storage code must follow the shared transport attribution rules: stable client identity, pooled connections, and no anonymous long-lived transports.
- Avoid cross-module edits unless the active sprint explicitly allows them.
## Testing & Verification
- Workflow backend/storage changes must run the targeted backend test project, not just the broader module solution.
- Prefer focused integration coverage for persistence, signaling, wake-outbox, and projection flows when touching storage or transport behavior.
## Sprint Discipline
- Track task state in the active sprint file and record blockers or contract changes in `Decisions & Risks`.

View File

@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("StellaOps.Workflow.DataStore.PostgreSQL.Tests")]

View File

@@ -7,6 +7,11 @@ public sealed class PostgresWorkflowBackendOptions
public const string SectionName = $"{WorkflowBackendOptions.SectionName}:Postgres";
public string ConnectionStringName { get; set; } = "WorkflowPostgres";
public string? ApplicationName { get; set; }
public bool Pooling { get; set; } = true;
public int MinPoolSize { get; set; } = 1;
public int MaxPoolSize { get; set; } = 100;
public int? ConnectionIdleLifetimeSeconds { get; set; } = 300;
public string SchemaName { get; set; } = "srd_wfklw";
public string RuntimeStatesTableName { get; set; } = "wf_runtime_states";
public string HostedJobLocksTableName { get; set; } = "wf_host_locks";

View File

@@ -18,6 +18,7 @@ public sealed class PostgresWorkflowDatabase(
PostgresWorkflowMutationSessionAccessor sessionAccessor,
IWorkflowMutationScopeAccessor scopeAccessor)
{
private const string DefaultApplicationName = "stellaops-workflow";
private readonly PostgresWorkflowBackendOptions postgres = options.Value;
internal PostgresWorkflowBackendOptions Options => postgres;
@@ -86,14 +87,36 @@ public sealed class PostgresWorkflowDatabase(
}
internal async Task<NpgsqlConnection> OpenConnectionAsync(CancellationToken cancellationToken = default)
{
var connection = new NpgsqlConnection(BuildConnectionString());
await connection.OpenAsync(cancellationToken);
return connection;
}
internal string BuildConnectionString()
{
var connectionString = configuration.GetConnectionString(postgres.ConnectionStringName)
?? throw new InvalidOperationException(
$"PostgreSQL workflow backend requires connection string '{postgres.ConnectionStringName}'.");
var connection = new NpgsqlConnection(connectionString);
await connection.OpenAsync(cancellationToken);
return connection;
var normalizedMinPoolSize = Math.Max(postgres.MinPoolSize, 0);
var normalizedMaxPoolSize = Math.Max(postgres.MaxPoolSize, normalizedMinPoolSize);
var builder = new NpgsqlConnectionStringBuilder(connectionString)
{
ApplicationName = string.IsNullOrWhiteSpace(postgres.ApplicationName)
? DefaultApplicationName
: postgres.ApplicationName.Trim(),
Pooling = postgres.Pooling,
MinPoolSize = normalizedMinPoolSize,
MaxPoolSize = normalizedMaxPoolSize,
};
if (postgres.ConnectionIdleLifetimeSeconds.HasValue && postgres.ConnectionIdleLifetimeSeconds.Value > 0)
{
builder.ConnectionIdleLifetime = postgres.ConnectionIdleLifetimeSeconds.Value;
}
return builder.ToString();
}
}

View File

@@ -0,0 +1,78 @@
using System.Collections.Generic;
using StellaOps.Workflow.DataStore.PostgreSQL;
using StellaOps.Workflow.Engine.Services;
using FluentAssertions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using Npgsql;
using NUnit.Framework;
namespace StellaOps.Workflow.DataStore.PostgreSQL.Tests;
[TestFixture]
public class PostgresWorkflowDatabaseTests
{
[Test]
public void BuildConnectionString_WhenApplicationNameIsOmitted_UsesWorkflowDefaults()
{
var options = new PostgresWorkflowBackendOptions
{
ConnectionStringName = "WorkflowPostgres",
MinPoolSize = 2,
MaxPoolSize = 9,
ConnectionIdleLifetimeSeconds = 123,
};
var connectionString = CreateDatabase(options).BuildConnectionString();
var builder = new NpgsqlConnectionStringBuilder(connectionString);
builder.ApplicationName.Should().Be("stellaops-workflow");
builder.Pooling.Should().BeTrue();
builder.MinPoolSize.Should().Be(2);
builder.MaxPoolSize.Should().Be(9);
builder.ConnectionIdleLifetime.Should().Be(123);
}
[Test]
public void BuildConnectionString_WhenExplicitSettingsAreProvided_AppliesNormalizedValues()
{
var options = new PostgresWorkflowBackendOptions
{
ConnectionStringName = "WorkflowPostgres",
ApplicationName = "workflow-projection-tests",
Pooling = false,
MinPoolSize = 8,
MaxPoolSize = 3,
};
var connectionString = CreateDatabase(options).BuildConnectionString();
var builder = new NpgsqlConnectionStringBuilder(connectionString);
builder.ApplicationName.Should().Be("workflow-projection-tests");
builder.Pooling.Should().BeFalse();
builder.MinPoolSize.Should().Be(8);
builder.MaxPoolSize.Should().Be(8);
}
private static PostgresWorkflowDatabase CreateDatabase(PostgresWorkflowBackendOptions options)
{
var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string?>
{
[$"ConnectionStrings:{options.ConnectionStringName}"] =
"Host=localhost;Database=workflow;Username=workflow;Password=workflow",
})
.Build();
return new PostgresWorkflowDatabase(
configuration,
Options.Create(options),
new PostgresWorkflowMutationSessionAccessor(),
new WorkflowMutationScopeAccessor());
}
}

View File

@@ -17,7 +17,6 @@ public sealed class RuntimePostgresConstructionConventionTests
"src/Doctor/__Plugins/StellaOps.Doctor.Plugin.Postgres/Checks/PostgresConnectivityCheck.cs",
"src/Doctor/__Plugins/StellaOps.Doctor.Plugin.Postgres/Checks/PostgresMigrationStatusCheck.cs",
"src/Platform/StellaOps.Platform.WebService/Services/PlatformMigrationAdminService.cs",
"src/Workflow/__Libraries/StellaOps.Workflow.DataStore.PostgreSQL/PostgresWorkflowDatabase.cs",
"src/__Libraries/StellaOps.Doctor.Plugins.Database/Checks/DatabaseCheckBase.cs",
"src/__Libraries/StellaOps.Infrastructure.Postgres/Migrations/MigrationRunner.cs",
"src/__Libraries/StellaOps.Infrastructure.Postgres/Migrations/StartupMigrationHost.cs",
@@ -36,10 +35,23 @@ public sealed class RuntimePostgresConstructionConventionTests
"src/Integrations/StellaOps.Integrations.WebService/FeedMirrorConnectorPlugins.cs",
"src/Integrations/StellaOps.Integrations.WebService/ObjectStorageConnectorPlugins.cs",
"src/Platform/StellaOps.Platform.WebService/Services/IdentityProviderManagementService.cs",
"src/ReleaseOrchestrator/__Libraries/StellaOps.ReleaseOrchestrator.IntegrationHub/Connectors/Registry/GenericOciConnector.cs",
"src/ReleaseOrchestrator/__Libraries/StellaOps.ReleaseOrchestrator.IntegrationHub/Connectors/Registry/HarborConnector.cs",
"src/ReleaseOrchestrator/__Libraries/StellaOps.ReleaseOrchestrator.IntegrationHub/Connectors/Registry/JfrogArtifactoryConnector.cs",
"src/ReleaseOrchestrator/__Libraries/StellaOps.ReleaseOrchestrator.IntegrationHub/Connectors/Registry/QuayConnector.cs",
"src/ReleaseOrchestrator/__Libraries/StellaOps.ReleaseOrchestrator.IntegrationHub/Connectors/Registry/AcrConnector.cs",
"src/ReleaseOrchestrator/__Libraries/StellaOps.ReleaseOrchestrator.IntegrationHub/Connectors/Registry/DockerHubConnector.cs",
"src/ReleaseOrchestrator/__Libraries/StellaOps.ReleaseOrchestrator.IntegrationHub/Connectors/Registry/EcrConnector.cs",
"src/ReleaseOrchestrator/__Libraries/StellaOps.ReleaseOrchestrator.IntegrationHub/Connectors/Registry/GcrConnector.cs",
"src/ReleaseOrchestrator/__Libraries/StellaOps.ReleaseOrchestrator.IntegrationHub/Connectors/Scm/AzureDevOpsConnector.cs",
"src/ReleaseOrchestrator/__Libraries/StellaOps.ReleaseOrchestrator.IntegrationHub/Connectors/Scm/GiteaConnector.cs",
"src/ReleaseOrchestrator/__Libraries/StellaOps.ReleaseOrchestrator.IntegrationHub/Connectors/Scm/GitHubConnector.cs",
"src/ReleaseOrchestrator/__Libraries/StellaOps.ReleaseOrchestrator.IntegrationHub/Connectors/Scm/GitLabConnector.cs",
"src/ReleaseOrchestrator/__Libraries/StellaOps.ReleaseOrchestrator.IntegrationHub/Connectors/SettingsStore/AwsAppConfigConnector.cs",
"src/ReleaseOrchestrator/__Libraries/StellaOps.ReleaseOrchestrator.IntegrationHub/Connectors/SettingsStore/AwsParameterStoreConnector.cs",
"src/ReleaseOrchestrator/__Libraries/StellaOps.ReleaseOrchestrator.IntegrationHub/Connectors/SettingsStore/AzureAppConfigConnector.cs",
"src/ReleaseOrchestrator/__Libraries/StellaOps.ReleaseOrchestrator.IntegrationHub/Connectors/SettingsStore/ConsulKvConnector.cs",
"src/ReleaseOrchestrator/__Libraries/StellaOps.ReleaseOrchestrator.IntegrationHub/Connectors/SettingsStore/EtcdConnector.cs",
"src/ReleaseOrchestrator/__Libraries/StellaOps.ReleaseOrchestrator.IntegrationHub/Connectors/Vault/AwsSecretsManagerConnector.cs",
"src/ReleaseOrchestrator/__Libraries/StellaOps.ReleaseOrchestrator.IntegrationHub/Connectors/Vault/AzureKeyVaultConnector.cs",
"src/ReleaseOrchestrator/__Libraries/StellaOps.ReleaseOrchestrator.IntegrationHub/Connectors/Vault/HashiCorpVaultConnector.cs",