Centralize Postgres connection string policy across all modules

Extract connection string building into PostgresConnectionStringPolicy so all
services use consistent pooling, application_name, and timeout settings.
Adopt the new policy in 20+ module DataSource/ServiceCollectionExtensions classes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
master
2026-04-06 08:51:04 +03:00
parent 517fa0a92d
commit ccdfd41e4f
64 changed files with 625 additions and 178 deletions

View File

@@ -38,7 +38,12 @@ public sealed class EvidenceLockerDataSource : IAsyncDisposable
private static NpgsqlDataSource CreateDataSource(string connectionString)
{
var builder = new NpgsqlDataSourceBuilder(connectionString);
var connectionStringBuilder = new NpgsqlConnectionStringBuilder(connectionString)
{
ApplicationName = "stellaops-evidence-locker",
};
var builder = new NpgsqlDataSourceBuilder(connectionStringBuilder.ConnectionString);
builder.EnableDynamicJson();
return builder.Build();
}

View File

@@ -83,10 +83,10 @@ public static class EvidenceLockerInfrastructureServiceCollectionExtensions
// Verdict attestation repository
services.AddScoped<StellaOps.EvidenceLocker.Storage.IVerdictRepository>(provider =>
{
var options = provider.GetRequiredService<IOptions<EvidenceLockerOptions>>().Value;
var dataSource = provider.GetRequiredService<EvidenceLockerDataSource>();
var logger = provider.GetRequiredService<ILogger<StellaOps.EvidenceLocker.Storage.PostgresVerdictRepository>>();
return new StellaOps.EvidenceLocker.Storage.PostgresVerdictRepository(
options.Database.ConnectionString,
cancellationToken => dataSource.OpenConnectionAsync(cancellationToken),
logger);
});
@@ -94,10 +94,10 @@ public static class EvidenceLockerInfrastructureServiceCollectionExtensions
// Sprint: SPRINT_20260219_009 (CID-04)
services.AddScoped<StellaOps.EvidenceLocker.Storage.IEvidenceThreadRepository>(provider =>
{
var options = provider.GetRequiredService<IOptions<EvidenceLockerOptions>>().Value;
var dataSource = provider.GetRequiredService<EvidenceLockerDataSource>();
var logger = provider.GetRequiredService<ILogger<StellaOps.EvidenceLocker.Storage.PostgresEvidenceThreadRepository>>();
return new StellaOps.EvidenceLocker.Storage.PostgresEvidenceThreadRepository(
options.Database.ConnectionString,
cancellationToken => dataSource.OpenConnectionAsync(cancellationToken),
logger);
});

View File

@@ -5,6 +5,7 @@ Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229
| Task ID | Status | Notes |
| --- | --- | --- |
| SPRINT_20260405_011-XPORT | DONE | `docs/implplan/SPRINT_20260405_011___Libraries_transport_pooling_and_attribution_hardening.md`: named the Evidence Locker PostgreSQL datasource and switched runtime repositories onto the shared open-connection path. |
| AUDIT-0289-M | DONE | Revalidated 2026-01-07; open findings tracked in audit report. |
| AUDIT-0289-T | DONE | Revalidated 2026-01-07; open findings tracked in audit report. |
| AUDIT-0289-A | TODO | Revalidated 2026-01-07 (open findings). |

View File

@@ -11,14 +11,14 @@ namespace StellaOps.EvidenceLocker.Storage;
/// </summary>
public sealed class PostgresEvidenceThreadRepository : IEvidenceThreadRepository
{
private readonly string _connectionString;
private readonly Func<CancellationToken, Task<NpgsqlConnection>> _openConnectionAsync;
private readonly ILogger<PostgresEvidenceThreadRepository> _logger;
public PostgresEvidenceThreadRepository(
string connectionString,
Func<CancellationToken, Task<NpgsqlConnection>> openConnectionAsync,
ILogger<PostgresEvidenceThreadRepository> logger)
{
_connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
_openConnectionAsync = openConnectionAsync ?? throw new ArgumentNullException(nameof(openConnectionAsync));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
@@ -45,8 +45,7 @@ public sealed class PostgresEvidenceThreadRepository : IEvidenceThreadRepository
try
{
await using var connection = new NpgsqlConnection(_connectionString);
await connection.OpenAsync(cancellationToken);
await using var connection = await _openConnectionAsync(cancellationToken).ConfigureAwait(false);
var record = await connection.QuerySingleOrDefaultAsync<ArtifactCanonicalRecord>(
new CommandDefinition(
@@ -88,8 +87,7 @@ public sealed class PostgresEvidenceThreadRepository : IEvidenceThreadRepository
try
{
await using var connection = new NpgsqlConnection(_connectionString);
await connection.OpenAsync(cancellationToken);
await using var connection = await _openConnectionAsync(cancellationToken).ConfigureAwait(false);
var results = await connection.QueryAsync<ArtifactCanonicalRecord>(
new CommandDefinition(

View File

@@ -9,16 +9,16 @@ namespace StellaOps.EvidenceLocker.Storage;
/// </summary>
public sealed class PostgresVerdictRepository : IVerdictRepository
{
private readonly string _connectionString;
private readonly Func<CancellationToken, Task<NpgsqlConnection>> _openConnectionAsync;
private readonly ILogger<PostgresVerdictRepository> _logger;
private readonly TimeProvider _timeProvider;
public PostgresVerdictRepository(
string connectionString,
Func<CancellationToken, Task<NpgsqlConnection>> openConnectionAsync,
ILogger<PostgresVerdictRepository> logger,
TimeProvider? timeProvider = null)
{
_connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
_openConnectionAsync = openConnectionAsync ?? throw new ArgumentNullException(nameof(openConnectionAsync));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_timeProvider = timeProvider ?? TimeProvider.System;
}
@@ -74,8 +74,7 @@ public sealed class PostgresVerdictRepository : IVerdictRepository
try
{
await using var connection = new NpgsqlConnection(_connectionString);
await connection.OpenAsync(cancellationToken);
await using var connection = await _openConnectionAsync(cancellationToken).ConfigureAwait(false);
var now = _timeProvider.GetUtcNow();
var verdictId = await connection.ExecuteScalarAsync<string>(
@@ -153,8 +152,7 @@ public sealed class PostgresVerdictRepository : IVerdictRepository
try
{
await using var connection = new NpgsqlConnection(_connectionString);
await connection.OpenAsync(cancellationToken);
await using var connection = await _openConnectionAsync(cancellationToken).ConfigureAwait(false);
var record = await connection.QuerySingleOrDefaultAsync<VerdictAttestationRecord>(
new CommandDefinition(
@@ -231,8 +229,7 @@ public sealed class PostgresVerdictRepository : IVerdictRepository
try
{
await using var connection = new NpgsqlConnection(_connectionString);
await connection.OpenAsync(cancellationToken);
await using var connection = await _openConnectionAsync(cancellationToken).ConfigureAwait(false);
var results = await connection.QueryAsync<VerdictAttestationSummary>(
new CommandDefinition(
@@ -309,8 +306,7 @@ public sealed class PostgresVerdictRepository : IVerdictRepository
try
{
await using var connection = new NpgsqlConnection(_connectionString);
await connection.OpenAsync(cancellationToken);
await using var connection = await _openConnectionAsync(cancellationToken).ConfigureAwait(false);
var results = await connection.QueryAsync<VerdictAttestationSummary>(
new CommandDefinition(
@@ -365,8 +361,7 @@ public sealed class PostgresVerdictRepository : IVerdictRepository
try
{
await using var connection = new NpgsqlConnection(_connectionString);
await connection.OpenAsync(cancellationToken);
await using var connection = await _openConnectionAsync(cancellationToken).ConfigureAwait(false);
var count = await connection.ExecuteScalarAsync<int>(
new CommandDefinition(