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:
@@ -5,6 +5,7 @@ Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229
|
|||||||
|
|
||||||
| Task ID | Status | Notes |
|
| Task ID | Status | Notes |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
|
| SPRINT_20260405_011-XPORT | DONE | `docs/implplan/SPRINT_20260405_011___Libraries_transport_pooling_and_attribution_hardening.md`: named the Rekor checkpoint PostgreSQL store and aligned it with reusable runtime data-source conventions. |
|
||||||
| AUDIT-0049-M | DONE | Revalidated maintainability for StellaOps.Attestor.Core. |
|
| AUDIT-0049-M | DONE | Revalidated maintainability for StellaOps.Attestor.Core. |
|
||||||
| AUDIT-0049-T | DONE | Revalidated test coverage for StellaOps.Attestor.Core. |
|
| AUDIT-0049-T | DONE | Revalidated test coverage for StellaOps.Attestor.Core. |
|
||||||
| AUDIT-0049-A | TODO | Reopened on revalidation; address canonicalization, time/ID determinism, and Ed25519 gaps. |
|
| AUDIT-0049-A | TODO | Reopened on revalidation; address canonicalization, time/ID determinism, and Ed25519 gaps. |
|
||||||
|
|||||||
@@ -180,7 +180,9 @@ public static class ServiceCollectionExtensions
|
|||||||
throw new InvalidOperationException("Redis connection string is required when redis dedupe is enabled.");
|
throw new InvalidOperationException("Redis connection string is required when redis dedupe is enabled.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return ConnectionMultiplexer.Connect(options.Redis.Url);
|
var redisOptions = ConfigurationOptions.Parse(options.Redis.Url);
|
||||||
|
redisOptions.ClientName ??= "stellaops-attestor-dedupe";
|
||||||
|
return ConnectionMultiplexer.Connect(redisOptions);
|
||||||
});
|
});
|
||||||
|
|
||||||
services.AddSingleton<IAttestorArchiveStore>(sp =>
|
services.AddSingleton<IAttestorArchiveStore>(sp =>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229
|
|||||||
|
|
||||||
| Task ID | Status | Notes |
|
| Task ID | Status | Notes |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
|
| SPRINT_20260405_011-XPORT-VALKEY | DONE | `docs/implplan/SPRINT_20260405_011___Libraries_transport_pooling_and_attribution_hardening.md`: named the Attestor Redis dedupe client construction path. |
|
||||||
| AUDIT-0055-M | DONE | Revalidated 2026-01-06. |
|
| AUDIT-0055-M | DONE | Revalidated 2026-01-06. |
|
||||||
| AUDIT-0055-T | DONE | Revalidated 2026-01-06. |
|
| AUDIT-0055-T | DONE | Revalidated 2026-01-06. |
|
||||||
| AUDIT-0055-A | DONE | Applied determinism, backend resolver, and Rekor client fixes 2026-01-08. |
|
| AUDIT-0055-A | DONE | Applied determinism, backend resolver, and Rekor client fixes 2026-01-08. |
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ namespace StellaOps.Attestor.Core.Rekor;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// PostgreSQL implementation of the Rekor checkpoint store.
|
/// PostgreSQL implementation of the Rekor checkpoint store.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class PostgresRekorCheckpointStore : IRekorCheckpointStore
|
public sealed class PostgresRekorCheckpointStore : IRekorCheckpointStore, IAsyncDisposable
|
||||||
{
|
{
|
||||||
private readonly string _connectionString;
|
private readonly NpgsqlDataSource _dataSource;
|
||||||
private readonly PostgresCheckpointStoreOptions _options;
|
private readonly PostgresCheckpointStoreOptions _options;
|
||||||
private readonly ILogger<PostgresRekorCheckpointStore> _logger;
|
private readonly ILogger<PostgresRekorCheckpointStore> _logger;
|
||||||
|
|
||||||
@@ -30,8 +30,9 @@ public sealed class PostgresRekorCheckpointStore : IRekorCheckpointStore
|
|||||||
ILogger<PostgresRekorCheckpointStore> logger)
|
ILogger<PostgresRekorCheckpointStore> logger)
|
||||||
{
|
{
|
||||||
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
|
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
|
||||||
_connectionString = _options.ConnectionString
|
var connectionString = _options.ConnectionString
|
||||||
?? throw new InvalidOperationException("ConnectionString is required");
|
?? throw new InvalidOperationException("ConnectionString is required");
|
||||||
|
_dataSource = CreateDataSource(connectionString);
|
||||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,11 +286,23 @@ public sealed class PostgresRekorCheckpointStore : IRekorCheckpointStore
|
|||||||
|
|
||||||
private async Task<NpgsqlConnection> OpenConnectionAsync(CancellationToken cancellationToken)
|
private async Task<NpgsqlConnection> OpenConnectionAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var conn = new NpgsqlConnection(_connectionString);
|
return await _dataSource.OpenConnectionAsync(cancellationToken);
|
||||||
await conn.OpenAsync(cancellationToken);
|
|
||||||
return conn;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static NpgsqlDataSource CreateDataSource(string connectionString)
|
||||||
|
{
|
||||||
|
ArgumentException.ThrowIfNullOrWhiteSpace(connectionString);
|
||||||
|
|
||||||
|
var connectionStringBuilder = new NpgsqlConnectionStringBuilder(connectionString)
|
||||||
|
{
|
||||||
|
ApplicationName = "stellaops-attestor-rekor-checkpoint"
|
||||||
|
};
|
||||||
|
|
||||||
|
return new NpgsqlDataSourceBuilder(connectionStringBuilder.ConnectionString).Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueTask DisposeAsync() => _dataSource.DisposeAsync();
|
||||||
|
|
||||||
private static StoredCheckpoint MapCheckpoint(NpgsqlDataReader reader)
|
private static StoredCheckpoint MapCheckpoint(NpgsqlDataReader reader)
|
||||||
{
|
{
|
||||||
return new StoredCheckpoint
|
return new StoredCheckpoint
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ namespace StellaOps.Attestor.Persistence.Repositories;
|
|||||||
/// EF Core implementation of the predicate type registry repository.
|
/// EF Core implementation of the predicate type registry repository.
|
||||||
/// Preserves idempotent registration via ON CONFLICT DO NOTHING semantics.
|
/// Preserves idempotent registration via ON CONFLICT DO NOTHING semantics.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class PostgresPredicateTypeRegistryRepository : IPredicateTypeRegistryRepository
|
public sealed class PostgresPredicateTypeRegistryRepository : IPredicateTypeRegistryRepository, IAsyncDisposable
|
||||||
{
|
{
|
||||||
private readonly string _connectionString;
|
private readonly NpgsqlDataSource _dataSource;
|
||||||
private readonly string _schemaName;
|
private readonly string _schemaName;
|
||||||
private const int DefaultCommandTimeoutSeconds = 30;
|
private const int DefaultCommandTimeoutSeconds = 30;
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ public sealed class PostgresPredicateTypeRegistryRepository : IPredicateTypeRegi
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public PostgresPredicateTypeRegistryRepository(string connectionString, string? schemaName = null)
|
public PostgresPredicateTypeRegistryRepository(string connectionString, string? schemaName = null)
|
||||||
{
|
{
|
||||||
_connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
|
_dataSource = CreateDataSource(connectionString);
|
||||||
_schemaName = schemaName ?? AttestorDbContextFactory.DefaultSchemaName;
|
_schemaName = schemaName ?? AttestorDbContextFactory.DefaultSchemaName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,8 +38,7 @@ public sealed class PostgresPredicateTypeRegistryRepository : IPredicateTypeRegi
|
|||||||
int limit = 100,
|
int limit = 100,
|
||||||
CancellationToken ct = default)
|
CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await using var conn = new NpgsqlConnection(_connectionString);
|
await using var conn = await _dataSource.OpenConnectionAsync(ct);
|
||||||
await conn.OpenAsync(ct);
|
|
||||||
await using var dbContext = AttestorDbContextFactory.Create(conn, DefaultCommandTimeoutSeconds, _schemaName);
|
await using var dbContext = AttestorDbContextFactory.Create(conn, DefaultCommandTimeoutSeconds, _schemaName);
|
||||||
|
|
||||||
var query = dbContext.PredicateTypeRegistry.AsNoTracking().AsQueryable();
|
var query = dbContext.PredicateTypeRegistry.AsNoTracking().AsQueryable();
|
||||||
@@ -67,8 +66,7 @@ public sealed class PostgresPredicateTypeRegistryRepository : IPredicateTypeRegi
|
|||||||
string predicateTypeUri,
|
string predicateTypeUri,
|
||||||
CancellationToken ct = default)
|
CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await using var conn = new NpgsqlConnection(_connectionString);
|
await using var conn = await _dataSource.OpenConnectionAsync(ct);
|
||||||
await conn.OpenAsync(ct);
|
|
||||||
await using var dbContext = AttestorDbContextFactory.Create(conn, DefaultCommandTimeoutSeconds, _schemaName);
|
await using var dbContext = AttestorDbContextFactory.Create(conn, DefaultCommandTimeoutSeconds, _schemaName);
|
||||||
|
|
||||||
return await dbContext.PredicateTypeRegistry
|
return await dbContext.PredicateTypeRegistry
|
||||||
@@ -85,8 +83,7 @@ public sealed class PostgresPredicateTypeRegistryRepository : IPredicateTypeRegi
|
|||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(entry);
|
ArgumentNullException.ThrowIfNull(entry);
|
||||||
|
|
||||||
await using var conn = new NpgsqlConnection(_connectionString);
|
await using var conn = await _dataSource.OpenConnectionAsync(ct);
|
||||||
await conn.OpenAsync(ct);
|
|
||||||
await using var dbContext = AttestorDbContextFactory.Create(conn, DefaultCommandTimeoutSeconds, _schemaName);
|
await using var dbContext = AttestorDbContextFactory.Create(conn, DefaultCommandTimeoutSeconds, _schemaName);
|
||||||
|
|
||||||
dbContext.PredicateTypeRegistry.Add(entry);
|
dbContext.PredicateTypeRegistry.Add(entry);
|
||||||
@@ -115,4 +112,18 @@ public sealed class PostgresPredicateTypeRegistryRepository : IPredicateTypeRegi
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ValueTask DisposeAsync() => _dataSource.DisposeAsync();
|
||||||
|
|
||||||
|
private static NpgsqlDataSource CreateDataSource(string connectionString)
|
||||||
|
{
|
||||||
|
ArgumentException.ThrowIfNullOrWhiteSpace(connectionString);
|
||||||
|
|
||||||
|
var connectionStringBuilder = new NpgsqlConnectionStringBuilder(connectionString)
|
||||||
|
{
|
||||||
|
ApplicationName = "stellaops-attestor-predicate-registry"
|
||||||
|
};
|
||||||
|
|
||||||
|
return new NpgsqlDataSourceBuilder(connectionStringBuilder.ConnectionString).Build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ namespace StellaOps.Attestor.Persistence.Repositories;
|
|||||||
/// EF Core implementation of the verdict ledger repository.
|
/// EF Core implementation of the verdict ledger repository.
|
||||||
/// Enforces append-only semantics with hash chain validation.
|
/// Enforces append-only semantics with hash chain validation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class PostgresVerdictLedgerRepository : IVerdictLedgerRepository
|
public sealed class PostgresVerdictLedgerRepository : IVerdictLedgerRepository, IAsyncDisposable
|
||||||
{
|
{
|
||||||
private readonly string _connectionString;
|
private readonly NpgsqlDataSource _dataSource;
|
||||||
private readonly string _schemaName;
|
private readonly string _schemaName;
|
||||||
private const int DefaultCommandTimeoutSeconds = 30;
|
private const int DefaultCommandTimeoutSeconds = 30;
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ public sealed class PostgresVerdictLedgerRepository : IVerdictLedgerRepository
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public PostgresVerdictLedgerRepository(string connectionString, string? schemaName = null)
|
public PostgresVerdictLedgerRepository(string connectionString, string? schemaName = null)
|
||||||
{
|
{
|
||||||
_connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
|
_dataSource = CreateDataSource(connectionString);
|
||||||
_schemaName = schemaName ?? AttestorDbContextFactory.DefaultSchemaName;
|
_schemaName = schemaName ?? AttestorDbContextFactory.DefaultSchemaName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,8 +47,7 @@ public sealed class PostgresVerdictLedgerRepository : IVerdictLedgerRepository
|
|||||||
|
|
||||||
// Use raw SQL for the INSERT with RETURNING and enum cast, which is cleaner
|
// Use raw SQL for the INSERT with RETURNING and enum cast, which is cleaner
|
||||||
// for the verdict_decision custom enum type
|
// for the verdict_decision custom enum type
|
||||||
await using var conn = new NpgsqlConnection(_connectionString);
|
await using var conn = await _dataSource.OpenConnectionAsync(ct);
|
||||||
await conn.OpenAsync(ct);
|
|
||||||
await using var dbContext = AttestorDbContextFactory.Create(conn, DefaultCommandTimeoutSeconds, _schemaName);
|
await using var dbContext = AttestorDbContextFactory.Create(conn, DefaultCommandTimeoutSeconds, _schemaName);
|
||||||
|
|
||||||
dbContext.VerdictLedger.Add(entry);
|
dbContext.VerdictLedger.Add(entry);
|
||||||
@@ -74,8 +73,7 @@ public sealed class PostgresVerdictLedgerRepository : IVerdictLedgerRepository
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<VerdictLedgerEntry?> GetByHashAsync(string verdictHash, CancellationToken ct = default)
|
public async Task<VerdictLedgerEntry?> GetByHashAsync(string verdictHash, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await using var conn = new NpgsqlConnection(_connectionString);
|
await using var conn = await _dataSource.OpenConnectionAsync(ct);
|
||||||
await conn.OpenAsync(ct);
|
|
||||||
await using var dbContext = AttestorDbContextFactory.Create(conn, DefaultCommandTimeoutSeconds, _schemaName);
|
await using var dbContext = AttestorDbContextFactory.Create(conn, DefaultCommandTimeoutSeconds, _schemaName);
|
||||||
|
|
||||||
return await dbContext.VerdictLedger
|
return await dbContext.VerdictLedger
|
||||||
@@ -89,8 +87,7 @@ public sealed class PostgresVerdictLedgerRepository : IVerdictLedgerRepository
|
|||||||
Guid tenantId,
|
Guid tenantId,
|
||||||
CancellationToken ct = default)
|
CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await using var conn = new NpgsqlConnection(_connectionString);
|
await using var conn = await _dataSource.OpenConnectionAsync(ct);
|
||||||
await conn.OpenAsync(ct);
|
|
||||||
await using var dbContext = AttestorDbContextFactory.Create(conn, DefaultCommandTimeoutSeconds, _schemaName);
|
await using var dbContext = AttestorDbContextFactory.Create(conn, DefaultCommandTimeoutSeconds, _schemaName);
|
||||||
|
|
||||||
return await dbContext.VerdictLedger
|
return await dbContext.VerdictLedger
|
||||||
@@ -103,8 +100,7 @@ public sealed class PostgresVerdictLedgerRepository : IVerdictLedgerRepository
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<VerdictLedgerEntry?> GetLatestAsync(Guid tenantId, CancellationToken ct = default)
|
public async Task<VerdictLedgerEntry?> GetLatestAsync(Guid tenantId, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await using var conn = new NpgsqlConnection(_connectionString);
|
await using var conn = await _dataSource.OpenConnectionAsync(ct);
|
||||||
await conn.OpenAsync(ct);
|
|
||||||
await using var dbContext = AttestorDbContextFactory.Create(conn, DefaultCommandTimeoutSeconds, _schemaName);
|
await using var dbContext = AttestorDbContextFactory.Create(conn, DefaultCommandTimeoutSeconds, _schemaName);
|
||||||
|
|
||||||
return await dbContext.VerdictLedger
|
return await dbContext.VerdictLedger
|
||||||
@@ -151,8 +147,7 @@ public sealed class PostgresVerdictLedgerRepository : IVerdictLedgerRepository
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<long> CountAsync(Guid tenantId, CancellationToken ct = default)
|
public async Task<long> CountAsync(Guid tenantId, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await using var conn = new NpgsqlConnection(_connectionString);
|
await using var conn = await _dataSource.OpenConnectionAsync(ct);
|
||||||
await conn.OpenAsync(ct);
|
|
||||||
await using var dbContext = AttestorDbContextFactory.Create(conn, DefaultCommandTimeoutSeconds, _schemaName);
|
await using var dbContext = AttestorDbContextFactory.Create(conn, DefaultCommandTimeoutSeconds, _schemaName);
|
||||||
|
|
||||||
return await dbContext.VerdictLedger
|
return await dbContext.VerdictLedger
|
||||||
@@ -172,4 +167,18 @@ public sealed class PostgresVerdictLedgerRepository : IVerdictLedgerRepository
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ValueTask DisposeAsync() => _dataSource.DisposeAsync();
|
||||||
|
|
||||||
|
private static NpgsqlDataSource CreateDataSource(string connectionString)
|
||||||
|
{
|
||||||
|
ArgumentException.ThrowIfNullOrWhiteSpace(connectionString);
|
||||||
|
|
||||||
|
var connectionStringBuilder = new NpgsqlConnectionStringBuilder(connectionString)
|
||||||
|
{
|
||||||
|
ApplicationName = "stellaops-attestor-verdict-ledger"
|
||||||
|
};
|
||||||
|
|
||||||
|
return new NpgsqlDataSourceBuilder(connectionStringBuilder.ConnectionString).Build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Source of truth: `docs/implplan/SPRINT_20260222_092_Attestor_dal_to_efcore.md`.
|
|||||||
|
|
||||||
| Task ID | Status | Notes |
|
| Task ID | Status | Notes |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
|
| SPRINT_20260405_011-XPORT | DONE | `docs/implplan/SPRINT_20260405_011___Libraries_transport_pooling_and_attribution_hardening.md`: named reusable PostgreSQL data sources for verdict-ledger and predicate-registry runtime repositories. |
|
||||||
| AUDIT-0060-M | DONE | Revalidated 2026-01-06. |
|
| AUDIT-0060-M | DONE | Revalidated 2026-01-06. |
|
||||||
| AUDIT-0060-T | DONE | Revalidated 2026-01-06. |
|
| AUDIT-0060-T | DONE | Revalidated 2026-01-06. |
|
||||||
| AUDIT-0060-A | TODO | Reopened after revalidation 2026-01-06. |
|
| AUDIT-0060-A | TODO | Reopened after revalidation 2026-01-06. |
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ public sealed partial class PostgresAlertDedupRepository
|
|||||||
int dedupWindowMinutes,
|
int dedupWindowMinutes,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
await using var conn = new NpgsqlConnection(_connectionString);
|
await using var conn = await _dataSource.OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||||
await conn.OpenAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
var now = _timeProvider.GetUtcNow();
|
var now = _timeProvider.GetUtcNow();
|
||||||
var windowStart = now.AddMinutes(-dedupWindowMinutes);
|
var windowStart = now.AddMinutes(-dedupWindowMinutes);
|
||||||
|
|||||||
@@ -8,15 +8,15 @@ namespace StellaOps.Attestor.Watchlist.Storage;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// PostgreSQL implementation of the alert deduplication repository.
|
/// PostgreSQL implementation of the alert deduplication repository.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed partial class PostgresAlertDedupRepository : IAlertDedupRepository
|
public sealed partial class PostgresAlertDedupRepository : IAlertDedupRepository, IAsyncDisposable
|
||||||
{
|
{
|
||||||
private readonly string _connectionString;
|
private readonly NpgsqlDataSource _dataSource;
|
||||||
private readonly ILogger<PostgresAlertDedupRepository> _logger;
|
private readonly ILogger<PostgresAlertDedupRepository> _logger;
|
||||||
private readonly TimeProvider _timeProvider;
|
private readonly TimeProvider _timeProvider;
|
||||||
|
|
||||||
public PostgresAlertDedupRepository(string connectionString, ILogger<PostgresAlertDedupRepository> logger, TimeProvider? timeProvider = null)
|
public PostgresAlertDedupRepository(string connectionString, ILogger<PostgresAlertDedupRepository> logger, TimeProvider? timeProvider = null)
|
||||||
{
|
{
|
||||||
_connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
|
_dataSource = CreateDataSource(connectionString);
|
||||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||||
}
|
}
|
||||||
@@ -27,8 +27,7 @@ public sealed partial class PostgresAlertDedupRepository : IAlertDedupRepository
|
|||||||
string identityHash,
|
string identityHash,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
await using var conn = new NpgsqlConnection(_connectionString);
|
await using var conn = await _dataSource.OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||||
await conn.OpenAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
const string sql = @"
|
const string sql = @"
|
||||||
SELECT alert_count FROM attestor.identity_alert_dedup
|
SELECT alert_count FROM attestor.identity_alert_dedup
|
||||||
@@ -45,8 +44,7 @@ public sealed partial class PostgresAlertDedupRepository : IAlertDedupRepository
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<int> CleanupExpiredAsync(CancellationToken cancellationToken = default)
|
public async Task<int> CleanupExpiredAsync(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
await using var conn = new NpgsqlConnection(_connectionString);
|
await using var conn = await _dataSource.OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||||
await conn.OpenAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
var cutoff = _timeProvider.GetUtcNow().AddDays(-7);
|
var cutoff = _timeProvider.GetUtcNow().AddDays(-7);
|
||||||
const string sql = @"
|
const string sql = @"
|
||||||
@@ -57,4 +55,18 @@ public sealed partial class PostgresAlertDedupRepository : IAlertDedupRepository
|
|||||||
cmd.Parameters.AddWithValue("cutoff", cutoff);
|
cmd.Parameters.AddWithValue("cutoff", cutoff);
|
||||||
return await cmd.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
|
return await cmd.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ValueTask DisposeAsync() => _dataSource.DisposeAsync();
|
||||||
|
|
||||||
|
private static NpgsqlDataSource CreateDataSource(string connectionString)
|
||||||
|
{
|
||||||
|
ArgumentException.ThrowIfNullOrWhiteSpace(connectionString);
|
||||||
|
|
||||||
|
var connectionStringBuilder = new NpgsqlConnectionStringBuilder(connectionString)
|
||||||
|
{
|
||||||
|
ApplicationName = "stellaops-attestor-alert-dedup"
|
||||||
|
};
|
||||||
|
|
||||||
|
return new NpgsqlDataSourceBuilder(connectionStringBuilder.ConnectionString).Build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,7 @@ public sealed partial class PostgresWatchlistRepository
|
|||||||
bool includeGlobal = true,
|
bool includeGlobal = true,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
await using var conn = new NpgsqlConnection(_connectionString);
|
await using var conn = await _dataSource.OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||||
await conn.OpenAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
var sql = includeGlobal
|
var sql = includeGlobal
|
||||||
? SqlQueries.SelectByTenantIncludingGlobal
|
? SqlQueries.SelectByTenantIncludingGlobal
|
||||||
@@ -46,8 +45,7 @@ public sealed partial class PostgresWatchlistRepository
|
|||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
|
|
||||||
await using var conn = new NpgsqlConnection(_connectionString);
|
await using var conn = await _dataSource.OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||||
await conn.OpenAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
await using var cmd = new NpgsqlCommand(SqlQueries.SelectActiveByTenant, conn);
|
await using var cmd = new NpgsqlCommand(SqlQueries.SelectActiveByTenant, conn);
|
||||||
cmd.Parameters.AddWithValue("tenant_id", tenantId);
|
cmd.Parameters.AddWithValue("tenant_id", tenantId);
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ public sealed partial class PostgresWatchlistRepository
|
|||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(entry);
|
ArgumentNullException.ThrowIfNull(entry);
|
||||||
|
|
||||||
await using var conn = new NpgsqlConnection(_connectionString);
|
await using var conn = await _dataSource.OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||||
await conn.OpenAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
await using var cmd = new NpgsqlCommand(SqlQueries.Upsert, conn);
|
await using var cmd = new NpgsqlCommand(SqlQueries.Upsert, conn);
|
||||||
AddUpsertParameters(cmd, entry);
|
AddUpsertParameters(cmd, entry);
|
||||||
@@ -38,8 +37,7 @@ public sealed partial class PostgresWatchlistRepository
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<bool> DeleteAsync(Guid id, string tenantId, CancellationToken cancellationToken = default)
|
public async Task<bool> DeleteAsync(Guid id, string tenantId, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
await using var conn = new NpgsqlConnection(_connectionString);
|
await using var conn = await _dataSource.OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||||
await conn.OpenAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
await using var cmd = new NpgsqlCommand(SqlQueries.Delete, conn);
|
await using var cmd = new NpgsqlCommand(SqlQueries.Delete, conn);
|
||||||
cmd.Parameters.AddWithValue("id", id);
|
cmd.Parameters.AddWithValue("id", id);
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ namespace StellaOps.Attestor.Watchlist.Storage;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// PostgreSQL implementation of the watchlist repository with caching.
|
/// PostgreSQL implementation of the watchlist repository with caching.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed partial class PostgresWatchlistRepository : IWatchlistRepository
|
public sealed partial class PostgresWatchlistRepository : IWatchlistRepository, IAsyncDisposable
|
||||||
{
|
{
|
||||||
private readonly string _connectionString;
|
private readonly NpgsqlDataSource _dataSource;
|
||||||
private readonly IMemoryCache _cache;
|
private readonly IMemoryCache _cache;
|
||||||
private readonly ILogger<PostgresWatchlistRepository> _logger;
|
private readonly ILogger<PostgresWatchlistRepository> _logger;
|
||||||
private readonly TimeSpan _cacheExpiration = TimeSpan.FromSeconds(5);
|
private readonly TimeSpan _cacheExpiration = TimeSpan.FromSeconds(5);
|
||||||
@@ -28,7 +28,7 @@ public sealed partial class PostgresWatchlistRepository : IWatchlistRepository
|
|||||||
IMemoryCache cache,
|
IMemoryCache cache,
|
||||||
ILogger<PostgresWatchlistRepository> logger)
|
ILogger<PostgresWatchlistRepository> logger)
|
||||||
{
|
{
|
||||||
_connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
|
_dataSource = CreateDataSource(connectionString);
|
||||||
_cache = cache ?? throw new ArgumentNullException(nameof(cache));
|
_cache = cache ?? throw new ArgumentNullException(nameof(cache));
|
||||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
}
|
}
|
||||||
@@ -36,8 +36,7 @@ public sealed partial class PostgresWatchlistRepository : IWatchlistRepository
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<WatchedIdentity?> GetAsync(Guid id, CancellationToken cancellationToken = default)
|
public async Task<WatchedIdentity?> GetAsync(Guid id, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
await using var conn = new NpgsqlConnection(_connectionString);
|
await using var conn = await _dataSource.OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||||
await conn.OpenAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
await using var cmd = new NpgsqlCommand(SqlQueries.SelectById, conn);
|
await using var cmd = new NpgsqlCommand(SqlQueries.SelectById, conn);
|
||||||
cmd.Parameters.AddWithValue("id", id);
|
cmd.Parameters.AddWithValue("id", id);
|
||||||
@@ -54,8 +53,7 @@ public sealed partial class PostgresWatchlistRepository : IWatchlistRepository
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<int> GetCountAsync(string tenantId, CancellationToken cancellationToken = default)
|
public async Task<int> GetCountAsync(string tenantId, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
await using var conn = new NpgsqlConnection(_connectionString);
|
await using var conn = await _dataSource.OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||||
await conn.OpenAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
const string sql = "SELECT COUNT(*) FROM attestor.identity_watchlist WHERE tenant_id = @tenant_id";
|
const string sql = "SELECT COUNT(*) FROM attestor.identity_watchlist WHERE tenant_id = @tenant_id";
|
||||||
|
|
||||||
@@ -65,4 +63,18 @@ public sealed partial class PostgresWatchlistRepository : IWatchlistRepository
|
|||||||
var result = await cmd.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false);
|
var result = await cmd.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false);
|
||||||
return Convert.ToInt32(result);
|
return Convert.ToInt32(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ValueTask DisposeAsync() => _dataSource.DisposeAsync();
|
||||||
|
|
||||||
|
private static NpgsqlDataSource CreateDataSource(string connectionString)
|
||||||
|
{
|
||||||
|
ArgumentException.ThrowIfNullOrWhiteSpace(connectionString);
|
||||||
|
|
||||||
|
var connectionStringBuilder = new NpgsqlConnectionStringBuilder(connectionString)
|
||||||
|
{
|
||||||
|
ApplicationName = "stellaops-attestor-watchlist"
|
||||||
|
};
|
||||||
|
|
||||||
|
return new NpgsqlDataSourceBuilder(connectionStringBuilder.ConnectionString).Build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,5 +4,6 @@ Source of truth: `docs/implplan/SPRINT_20260130_002_Tools_csproj_remediation_sol
|
|||||||
|
|
||||||
| Task ID | Status | Notes |
|
| Task ID | Status | Notes |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
|
| SPRINT_20260405_011-XPORT | DONE | `docs/implplan/SPRINT_20260405_011___Libraries_transport_pooling_and_attribution_hardening.md`: replaced raw PostgreSQL connection construction with named reusable data sources for watchlist and alert-dedup runtime paths. |
|
||||||
| REMED-05 | TODO | Remediation checklist: docs/implplan/audits/csproj-standards/remediation/checklists/src/Attestor/__Libraries/StellaOps.Attestor.Watchlist/StellaOps.Attestor.Watchlist.md. |
|
| REMED-05 | TODO | Remediation checklist: docs/implplan/audits/csproj-standards/remediation/checklists/src/Attestor/__Libraries/StellaOps.Attestor.Watchlist/StellaOps.Attestor.Watchlist.md. |
|
||||||
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
|
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
|
||||||
|
|||||||
@@ -176,7 +176,11 @@ builder.Services.TryAddSingleton<IDpopProofValidator, DpopProofValidator>();
|
|||||||
if (string.Equals(senderConstraints.Dpop.Nonce.Store, "redis", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(senderConstraints.Dpop.Nonce.Store, "redis", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
builder.Services.TryAddSingleton<IConnectionMultiplexer>(_ =>
|
builder.Services.TryAddSingleton<IConnectionMultiplexer>(_ =>
|
||||||
ConnectionMultiplexer.Connect(senderConstraints.Dpop.Nonce.RedisConnectionString!));
|
{
|
||||||
|
var redisOptions = ConfigurationOptions.Parse(senderConstraints.Dpop.Nonce.RedisConnectionString!);
|
||||||
|
redisOptions.ClientName ??= "stellaops-authority-dpop-nonce";
|
||||||
|
return ConnectionMultiplexer.Connect(redisOptions);
|
||||||
|
});
|
||||||
|
|
||||||
builder.Services.TryAddSingleton<IDpopNonceStore>(provider =>
|
builder.Services.TryAddSingleton<IDpopNonceStore>(provider =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229
|
|||||||
|
|
||||||
| Task ID | Status | Notes |
|
| Task ID | Status | Notes |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
|
| SPRINT_20260405_011-XPORT-VALKEY | DONE | `docs/implplan/SPRINT_20260405_011___Libraries_transport_pooling_and_attribution_hardening.md`: named the Authority DPoP nonce-store Valkey client construction path. |
|
||||||
| AUDIT-0085-M | DONE | Revalidated 2026-01-06. |
|
| AUDIT-0085-M | DONE | Revalidated 2026-01-06. |
|
||||||
| AUDIT-0085-T | DONE | Revalidated 2026-01-06 (coverage reviewed). |
|
| AUDIT-0085-T | DONE | Revalidated 2026-01-06 (coverage reviewed). |
|
||||||
| AUDIT-0085-A | TODO | Reopened 2026-01-06: remove Guid.NewGuid/DateTimeOffset.UtcNow, fix branding error messages, and modularize Program.cs. |
|
| AUDIT-0085-A | TODO | Reopened 2026-01-06: remove Guid.NewGuid/DateTimeOffset.UtcNow, fix branding error messages, and modularize Program.cs. |
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ static IResolutionCacheService CreateResolutionCacheService(IServiceProvider ser
|
|||||||
redisOptions.ConnectTimeout = 500;
|
redisOptions.ConnectTimeout = 500;
|
||||||
redisOptions.SyncTimeout = 500;
|
redisOptions.SyncTimeout = 500;
|
||||||
redisOptions.AsyncTimeout = 500;
|
redisOptions.AsyncTimeout = 500;
|
||||||
|
redisOptions.ClientName ??= "stellaops-binaryindex-resolution-cache";
|
||||||
|
|
||||||
var multiplexer = ConnectionMultiplexer.Connect(redisOptions);
|
var multiplexer = ConnectionMultiplexer.Connect(redisOptions);
|
||||||
_ = multiplexer.GetDatabase().Ping();
|
_ = multiplexer.GetDatabase().Ping();
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229
|
|||||||
|
|
||||||
| Task ID | Status | Notes |
|
| Task ID | Status | Notes |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
|
| SPRINT_20260405_011-XPORT-VALKEY | DONE | `docs/implplan/SPRINT_20260405_011___Libraries_transport_pooling_and_attribution_hardening.md`: named the BinaryIndex resolution-cache Valkey client. |
|
||||||
| QA-BINARYINDEX-VERIFY-029 | DONE | SPRINT_20260211_033 run-001: `patch-coverage-tracking` passed Tier 0/1/2 with patch-coverage controller behavioral evidence (including post-create coverage updates) and was moved to `docs/features/checked/binaryindex/patch-coverage-tracking.md`. |
|
| QA-BINARYINDEX-VERIFY-029 | DONE | SPRINT_20260211_033 run-001: `patch-coverage-tracking` passed Tier 0/1/2 with patch-coverage controller behavioral evidence (including post-create coverage updates) and was moved to `docs/features/checked/binaryindex/patch-coverage-tracking.md`. |
|
||||||
| QA-BINARYINDEX-VERIFY-028 | BLOCKED | SPRINT_20260211_033: `ml-function-embedding-service` is actively owned by another lane (`run-001` in progress); this lane terminalized collision as `skipped` (`owned_by_other_agent`) per FLOW 0.1. |
|
| QA-BINARYINDEX-VERIFY-028 | BLOCKED | SPRINT_20260211_033: `ml-function-embedding-service` is actively owned by another lane (`run-001` in progress); this lane terminalized collision as `skipped` (`owned_by_other_agent`) per FLOW 0.1. |
|
||||||
| QA-BINARYINDEX-VERIFY-026 | DONE | SPRINT_20260211_033 run-002: `known-build-binary-catalog` passed Tier 0/1/2 with Build-ID/SHA256/assertion/cache/method-mapping behavioral evidence; dossier verified in `docs/features/checked/binaryindex/known-build-binary-catalog.md`. |
|
| QA-BINARYINDEX-VERIFY-026 | DONE | SPRINT_20260211_033 run-002: `known-build-binary-catalog` passed Tier 0/1/2 with Build-ID/SHA256/assertion/cache/method-mapping behavioral evidence; dossier verified in `docs/features/checked/binaryindex/known-build-binary-catalog.md`. |
|
||||||
|
|||||||
@@ -10,14 +10,14 @@ namespace StellaOps.BinaryIndex.Validation.Persistence;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// PostgreSQL repository for match results.
|
/// PostgreSQL repository for match results.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class MatchResultRepository : IMatchResultRepository
|
public sealed class MatchResultRepository : IMatchResultRepository, IAsyncDisposable
|
||||||
{
|
{
|
||||||
private readonly string _connectionString;
|
private readonly NpgsqlDataSource _dataSource;
|
||||||
private readonly JsonSerializerOptions _jsonOptions;
|
private readonly JsonSerializerOptions _jsonOptions;
|
||||||
|
|
||||||
public MatchResultRepository(string connectionString)
|
public MatchResultRepository(string connectionString)
|
||||||
{
|
{
|
||||||
_connectionString = connectionString;
|
_dataSource = CreateDataSource(connectionString);
|
||||||
_jsonOptions = new JsonSerializerOptions
|
_jsonOptions = new JsonSerializerOptions
|
||||||
{
|
{
|
||||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||||
@@ -30,8 +30,7 @@ public sealed class MatchResultRepository : IMatchResultRepository
|
|||||||
{
|
{
|
||||||
if (results.Count == 0) return;
|
if (results.Count == 0) return;
|
||||||
|
|
||||||
await using var conn = new NpgsqlConnection(_connectionString);
|
await using var conn = await _dataSource.OpenConnectionAsync(ct);
|
||||||
await conn.OpenAsync(ct);
|
|
||||||
|
|
||||||
await using var transaction = await conn.BeginTransactionAsync(ct);
|
await using var transaction = await conn.BeginTransactionAsync(ct);
|
||||||
|
|
||||||
@@ -105,8 +104,7 @@ public sealed class MatchResultRepository : IMatchResultRepository
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async Task<IReadOnlyList<MatchResult>> GetForRunAsync(Guid runId, CancellationToken ct = default)
|
public async Task<IReadOnlyList<MatchResult>> GetForRunAsync(Guid runId, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await using var conn = new NpgsqlConnection(_connectionString);
|
await using var conn = await _dataSource.OpenConnectionAsync(ct);
|
||||||
await conn.OpenAsync(ct);
|
|
||||||
|
|
||||||
const string query = """
|
const string query = """
|
||||||
SELECT
|
SELECT
|
||||||
@@ -215,4 +213,18 @@ public sealed class MatchResultRepository : IMatchResultRepository
|
|||||||
string ExpectedName, string? ExpectedDemangledName, long ExpectedAddress, long? ExpectedSize, string ExpectedBuildId, string ExpectedBinaryName,
|
string ExpectedName, string? ExpectedDemangledName, long ExpectedAddress, long? ExpectedSize, string ExpectedBuildId, string ExpectedBinaryName,
|
||||||
string? ActualName, string? ActualDemangledName, long? ActualAddress, long? ActualSize, string? ActualBuildId, string? ActualBinaryName,
|
string? ActualName, string? ActualDemangledName, long? ActualAddress, long? ActualSize, string? ActualBuildId, string? ActualBinaryName,
|
||||||
string Outcome, double? MatchScore, string? Confidence, string? InferredCause, string? MismatchDetail, double? MatchDurationMs);
|
string Outcome, double? MatchScore, string? Confidence, string? InferredCause, string? MismatchDetail, double? MatchDurationMs);
|
||||||
|
|
||||||
|
public ValueTask DisposeAsync() => _dataSource.DisposeAsync();
|
||||||
|
|
||||||
|
private static NpgsqlDataSource CreateDataSource(string connectionString)
|
||||||
|
{
|
||||||
|
ArgumentException.ThrowIfNullOrWhiteSpace(connectionString);
|
||||||
|
|
||||||
|
var connectionStringBuilder = new NpgsqlConnectionStringBuilder(connectionString)
|
||||||
|
{
|
||||||
|
ApplicationName = "stellaops-binaryindex-match-results"
|
||||||
|
};
|
||||||
|
|
||||||
|
return new NpgsqlDataSourceBuilder(connectionStringBuilder.ConnectionString).Build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,14 +10,14 @@ namespace StellaOps.BinaryIndex.Validation.Persistence;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// PostgreSQL repository for validation runs.
|
/// PostgreSQL repository for validation runs.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class ValidationRunRepository : IValidationRunRepository
|
public sealed class ValidationRunRepository : IValidationRunRepository, IAsyncDisposable
|
||||||
{
|
{
|
||||||
private readonly string _connectionString;
|
private readonly NpgsqlDataSource _dataSource;
|
||||||
private readonly JsonSerializerOptions _jsonOptions;
|
private readonly JsonSerializerOptions _jsonOptions;
|
||||||
|
|
||||||
public ValidationRunRepository(string connectionString)
|
public ValidationRunRepository(string connectionString)
|
||||||
{
|
{
|
||||||
_connectionString = connectionString;
|
_dataSource = CreateDataSource(connectionString);
|
||||||
_jsonOptions = new JsonSerializerOptions
|
_jsonOptions = new JsonSerializerOptions
|
||||||
{
|
{
|
||||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||||
@@ -28,8 +28,7 @@ public sealed class ValidationRunRepository : IValidationRunRepository
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async Task SaveAsync(ValidationRun run, CancellationToken ct = default)
|
public async Task SaveAsync(ValidationRun run, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await using var conn = new NpgsqlConnection(_connectionString);
|
await using var conn = await _dataSource.OpenConnectionAsync(ct);
|
||||||
await conn.OpenAsync(ct);
|
|
||||||
|
|
||||||
const string upsert = """
|
const string upsert = """
|
||||||
INSERT INTO groundtruth.validation_runs (
|
INSERT INTO groundtruth.validation_runs (
|
||||||
@@ -107,8 +106,7 @@ public sealed class ValidationRunRepository : IValidationRunRepository
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async Task<ValidationRun?> GetAsync(Guid runId, CancellationToken ct = default)
|
public async Task<ValidationRun?> GetAsync(Guid runId, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await using var conn = new NpgsqlConnection(_connectionString);
|
await using var conn = await _dataSource.OpenConnectionAsync(ct);
|
||||||
await conn.OpenAsync(ct);
|
|
||||||
|
|
||||||
const string query = """
|
const string query = """
|
||||||
SELECT
|
SELECT
|
||||||
@@ -132,8 +130,7 @@ public sealed class ValidationRunRepository : IValidationRunRepository
|
|||||||
ValidationRunFilter? filter,
|
ValidationRunFilter? filter,
|
||||||
CancellationToken ct = default)
|
CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await using var conn = new NpgsqlConnection(_connectionString);
|
await using var conn = await _dataSource.OpenConnectionAsync(ct);
|
||||||
await conn.OpenAsync(ct);
|
|
||||||
|
|
||||||
var sql = new System.Text.StringBuilder("""
|
var sql = new System.Text.StringBuilder("""
|
||||||
SELECT id, name, status, created_at, completed_at,
|
SELECT id, name, status, created_at, completed_at,
|
||||||
@@ -264,4 +261,18 @@ public sealed class ValidationRunRepository : IValidationRunRepository
|
|||||||
private sealed record ValidationRunSummaryRow(
|
private sealed record ValidationRunSummaryRow(
|
||||||
Guid Id, string Name, string Status, DateTimeOffset CreatedAt, DateTimeOffset? CompletedAt,
|
Guid Id, string Name, string Status, DateTimeOffset CreatedAt, DateTimeOffset? CompletedAt,
|
||||||
double? MatchRate, double? F1Score, int? PairCount, int? FunctionCount, string[]? Tags);
|
double? MatchRate, double? F1Score, int? PairCount, int? FunctionCount, string[]? Tags);
|
||||||
|
|
||||||
|
public ValueTask DisposeAsync() => _dataSource.DisposeAsync();
|
||||||
|
|
||||||
|
private static NpgsqlDataSource CreateDataSource(string connectionString)
|
||||||
|
{
|
||||||
|
ArgumentException.ThrowIfNullOrWhiteSpace(connectionString);
|
||||||
|
|
||||||
|
var connectionStringBuilder = new NpgsqlConnectionStringBuilder(connectionString)
|
||||||
|
{
|
||||||
|
ApplicationName = "stellaops-binaryindex-validation-runs"
|
||||||
|
};
|
||||||
|
|
||||||
|
return new NpgsqlDataSourceBuilder(connectionStringBuilder.ConnectionString).Build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ Source of truth: `docs/implplan/SPRINT_20260130_002_Tools_csproj_remediation_sol
|
|||||||
|
|
||||||
| Task ID | Status | Notes |
|
| Task ID | Status | Notes |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
|
| SPRINT_20260405_011-XPORT | DONE | `docs/implplan/SPRINT_20260405_011___Libraries_transport_pooling_and_attribution_hardening.md`: named reusable PostgreSQL data sources for validation-run and match-result persistence. |
|
||||||
| QA-BINARYINDEX-VERIFY-023 | BLOCKED | SPRINT_20260211_033 run-001: blocked because `AGENTS.md` is missing in this module and related validation modules/tests (repo AGENTS rule 5). |
|
| QA-BINARYINDEX-VERIFY-023 | BLOCKED | SPRINT_20260211_033 run-001: blocked because `AGENTS.md` is missing in this module and related validation modules/tests (repo AGENTS rule 5). |
|
||||||
| REMED-05 | TODO | Remediation checklist: docs/implplan/audits/csproj-standards/remediation/checklists/src/BinaryIndex/__Libraries/StellaOps.BinaryIndex.Validation/StellaOps.BinaryIndex.Validation.md. |
|
| REMED-05 | TODO | Remediation checklist: docs/implplan/audits/csproj-standards/remediation/checklists/src/BinaryIndex/__Libraries/StellaOps.BinaryIndex.Validation/StellaOps.BinaryIndex.Validation.md. |
|
||||||
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
|
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
|
||||||
|
|||||||
@@ -11,16 +11,16 @@ namespace StellaOps.Concelier.ProofService.Postgres;
|
|||||||
/// PostgreSQL implementation of distro advisory repository.
|
/// PostgreSQL implementation of distro advisory repository.
|
||||||
/// Queries the vuln.distro_advisories table for CVE + package evidence.
|
/// Queries the vuln.distro_advisories table for CVE + package evidence.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class PostgresDistroAdvisoryRepository : IDistroAdvisoryRepository
|
public sealed class PostgresDistroAdvisoryRepository : IDistroAdvisoryRepository, IAsyncDisposable
|
||||||
{
|
{
|
||||||
private readonly string _connectionString;
|
private readonly NpgsqlDataSource _dataSource;
|
||||||
private readonly ILogger<PostgresDistroAdvisoryRepository> _logger;
|
private readonly ILogger<PostgresDistroAdvisoryRepository> _logger;
|
||||||
|
|
||||||
public PostgresDistroAdvisoryRepository(
|
public PostgresDistroAdvisoryRepository(
|
||||||
string connectionString,
|
string connectionString,
|
||||||
ILogger<PostgresDistroAdvisoryRepository> logger)
|
ILogger<PostgresDistroAdvisoryRepository> logger)
|
||||||
{
|
{
|
||||||
_connectionString = connectionString;
|
_dataSource = CreateDataSource(connectionString);
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,8 +48,7 @@ public sealed class PostgresDistroAdvisoryRepository : IDistroAdvisoryRepository
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await using var connection = new NpgsqlConnection(_connectionString);
|
await using var connection = await _dataSource.OpenConnectionAsync(ct);
|
||||||
await connection.OpenAsync(ct);
|
|
||||||
|
|
||||||
var result = await connection.QuerySingleOrDefaultAsync<DistroAdvisoryDto>(
|
var result = await connection.QuerySingleOrDefaultAsync<DistroAdvisoryDto>(
|
||||||
new CommandDefinition(sql, new { CveId = cveId, PackagePurl = packagePurl }, cancellationToken: ct));
|
new CommandDefinition(sql, new { CveId = cveId, PackagePurl = packagePurl }, cancellationToken: ct));
|
||||||
@@ -71,4 +70,18 @@ public sealed class PostgresDistroAdvisoryRepository : IDistroAdvisoryRepository
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ValueTask DisposeAsync() => _dataSource.DisposeAsync();
|
||||||
|
|
||||||
|
private static NpgsqlDataSource CreateDataSource(string connectionString)
|
||||||
|
{
|
||||||
|
ArgumentException.ThrowIfNullOrWhiteSpace(connectionString);
|
||||||
|
|
||||||
|
var connectionStringBuilder = new NpgsqlConnectionStringBuilder(connectionString)
|
||||||
|
{
|
||||||
|
ApplicationName = "stellaops-concelier-proofservice-distro-advisories"
|
||||||
|
};
|
||||||
|
|
||||||
|
return new NpgsqlDataSourceBuilder(connectionStringBuilder.ConnectionString).Build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,16 +11,16 @@ namespace StellaOps.Concelier.ProofService.Postgres;
|
|||||||
/// PostgreSQL implementation of patch repository.
|
/// PostgreSQL implementation of patch repository.
|
||||||
/// Queries vuln.patch_evidence and feedser.binary_fingerprints tables.
|
/// Queries vuln.patch_evidence and feedser.binary_fingerprints tables.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class PostgresPatchRepository : IPatchRepository
|
public sealed class PostgresPatchRepository : IPatchRepository, IAsyncDisposable
|
||||||
{
|
{
|
||||||
private readonly string _connectionString;
|
private readonly NpgsqlDataSource _dataSource;
|
||||||
private readonly ILogger<PostgresPatchRepository> _logger;
|
private readonly ILogger<PostgresPatchRepository> _logger;
|
||||||
|
|
||||||
public PostgresPatchRepository(
|
public PostgresPatchRepository(
|
||||||
string connectionString,
|
string connectionString,
|
||||||
ILogger<PostgresPatchRepository> logger)
|
ILogger<PostgresPatchRepository> logger)
|
||||||
{
|
{
|
||||||
_connectionString = connectionString;
|
_dataSource = CreateDataSource(connectionString);
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,8 +45,7 @@ public sealed class PostgresPatchRepository : IPatchRepository
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await using var connection = new NpgsqlConnection(_connectionString);
|
await using var connection = await _dataSource.OpenConnectionAsync(ct);
|
||||||
await connection.OpenAsync(ct);
|
|
||||||
|
|
||||||
var results = await connection.QueryAsync<PatchHeaderDto>(
|
var results = await connection.QueryAsync<PatchHeaderDto>(
|
||||||
new CommandDefinition(sql, new { CveId = cveId }, cancellationToken: ct));
|
new CommandDefinition(sql, new { CveId = cveId }, cancellationToken: ct));
|
||||||
@@ -89,8 +88,7 @@ public sealed class PostgresPatchRepository : IPatchRepository
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await using var connection = new NpgsqlConnection(_connectionString);
|
await using var connection = await _dataSource.OpenConnectionAsync(ct);
|
||||||
await connection.OpenAsync(ct);
|
|
||||||
|
|
||||||
var results = await connection.QueryAsync<PatchSigDto>(
|
var results = await connection.QueryAsync<PatchSigDto>(
|
||||||
new CommandDefinition(sql, new { CveId = cveId }, cancellationToken: ct));
|
new CommandDefinition(sql, new { CveId = cveId }, cancellationToken: ct));
|
||||||
@@ -144,8 +142,7 @@ public sealed class PostgresPatchRepository : IPatchRepository
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await using var connection = new NpgsqlConnection(_connectionString);
|
await using var connection = await _dataSource.OpenConnectionAsync(ct);
|
||||||
await connection.OpenAsync(ct);
|
|
||||||
|
|
||||||
var results = await connection.QueryAsync<BinaryFingerprintRow>(
|
var results = await connection.QueryAsync<BinaryFingerprintRow>(
|
||||||
new CommandDefinition(sql, new { CveId = cveId }, cancellationToken: ct));
|
new CommandDefinition(sql, new { CveId = cveId }, cancellationToken: ct));
|
||||||
@@ -206,4 +203,18 @@ public sealed class PostgresPatchRepository : IPatchRepository
|
|||||||
public required DateTimeOffset ExtractedAt { get; init; }
|
public required DateTimeOffset ExtractedAt { get; init; }
|
||||||
public required string ExtractorVersion { get; init; }
|
public required string ExtractorVersion { get; init; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ValueTask DisposeAsync() => _dataSource.DisposeAsync();
|
||||||
|
|
||||||
|
private static NpgsqlDataSource CreateDataSource(string connectionString)
|
||||||
|
{
|
||||||
|
ArgumentException.ThrowIfNullOrWhiteSpace(connectionString);
|
||||||
|
|
||||||
|
var connectionStringBuilder = new NpgsqlConnectionStringBuilder(connectionString)
|
||||||
|
{
|
||||||
|
ApplicationName = "stellaops-concelier-proofservice-patches"
|
||||||
|
};
|
||||||
|
|
||||||
|
return new NpgsqlDataSourceBuilder(connectionStringBuilder.ConnectionString).Build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,16 +10,16 @@ namespace StellaOps.Concelier.ProofService.Postgres;
|
|||||||
/// PostgreSQL implementation of source artifact repository.
|
/// PostgreSQL implementation of source artifact repository.
|
||||||
/// Queries vuln.changelog_evidence for CVE mentions in changelogs.
|
/// Queries vuln.changelog_evidence for CVE mentions in changelogs.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class PostgresSourceArtifactRepository : ISourceArtifactRepository
|
public sealed class PostgresSourceArtifactRepository : ISourceArtifactRepository, IAsyncDisposable
|
||||||
{
|
{
|
||||||
private readonly string _connectionString;
|
private readonly NpgsqlDataSource _dataSource;
|
||||||
private readonly ILogger<PostgresSourceArtifactRepository> _logger;
|
private readonly ILogger<PostgresSourceArtifactRepository> _logger;
|
||||||
|
|
||||||
public PostgresSourceArtifactRepository(
|
public PostgresSourceArtifactRepository(
|
||||||
string connectionString,
|
string connectionString,
|
||||||
ILogger<PostgresSourceArtifactRepository> logger)
|
ILogger<PostgresSourceArtifactRepository> logger)
|
||||||
{
|
{
|
||||||
_connectionString = connectionString;
|
_dataSource = CreateDataSource(connectionString);
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,8 +46,7 @@ public sealed class PostgresSourceArtifactRepository : ISourceArtifactRepository
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await using var connection = new NpgsqlConnection(_connectionString);
|
await using var connection = await _dataSource.OpenConnectionAsync(ct);
|
||||||
await connection.OpenAsync(ct);
|
|
||||||
|
|
||||||
var results = await connection.QueryAsync<ChangelogDto>(
|
var results = await connection.QueryAsync<ChangelogDto>(
|
||||||
new CommandDefinition(sql, new { CveId = cveId, PackagePurl = packagePurl }, cancellationToken: ct));
|
new CommandDefinition(sql, new { CveId = cveId, PackagePurl = packagePurl }, cancellationToken: ct));
|
||||||
@@ -68,4 +67,18 @@ public sealed class PostgresSourceArtifactRepository : ISourceArtifactRepository
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ValueTask DisposeAsync() => _dataSource.DisposeAsync();
|
||||||
|
|
||||||
|
private static NpgsqlDataSource CreateDataSource(string connectionString)
|
||||||
|
{
|
||||||
|
ArgumentException.ThrowIfNullOrWhiteSpace(connectionString);
|
||||||
|
|
||||||
|
var connectionStringBuilder = new NpgsqlConnectionStringBuilder(connectionString)
|
||||||
|
{
|
||||||
|
ApplicationName = "stellaops-concelier-proofservice-source-artifacts"
|
||||||
|
};
|
||||||
|
|
||||||
|
return new NpgsqlDataSourceBuilder(connectionStringBuilder.ConnectionString).Build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229
|
|||||||
|
|
||||||
| Task ID | Status | Notes |
|
| Task ID | Status | Notes |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
|
| SPRINT_20260405_011-XPORT | DONE | `docs/implplan/SPRINT_20260405_011___Libraries_transport_pooling_and_attribution_hardening.md`: replaced raw PostgreSQL connection construction with named reusable proof-service data sources. |
|
||||||
| AUDIT-0233-M | DONE | Revalidated 2026-01-07. |
|
| AUDIT-0233-M | DONE | Revalidated 2026-01-07. |
|
||||||
| AUDIT-0233-T | DONE | Revalidated 2026-01-07. |
|
| AUDIT-0233-T | DONE | Revalidated 2026-01-07. |
|
||||||
| AUDIT-0233-A | TODO | Revalidated 2026-01-07 (open findings). |
|
| AUDIT-0233-A | TODO | Revalidated 2026-01-07 (open findings). |
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ namespace StellaOps.Doctor.WebService.Services;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class PostgresReportStorageService : IReportStorageService, IDisposable
|
public sealed class PostgresReportStorageService : IReportStorageService, IDisposable
|
||||||
{
|
{
|
||||||
private readonly string _connectionString;
|
private readonly NpgsqlDataSource _dataSource;
|
||||||
private readonly DoctorServiceOptions _options;
|
private readonly DoctorServiceOptions _options;
|
||||||
private readonly ILogger<PostgresReportStorageService> _logger;
|
private readonly ILogger<PostgresReportStorageService> _logger;
|
||||||
private readonly Timer? _cleanupTimer;
|
private readonly Timer? _cleanupTimer;
|
||||||
@@ -37,9 +37,10 @@ public sealed class PostgresReportStorageService : IReportStorageService, IDispo
|
|||||||
IOptions<DoctorServiceOptions> options,
|
IOptions<DoctorServiceOptions> options,
|
||||||
ILogger<PostgresReportStorageService> logger)
|
ILogger<PostgresReportStorageService> logger)
|
||||||
{
|
{
|
||||||
_connectionString = configuration.GetConnectionString("StellaOps")
|
var connectionString = configuration.GetConnectionString("StellaOps")
|
||||||
?? configuration["Database:ConnectionString"]
|
?? configuration["Database:ConnectionString"]
|
||||||
?? throw new InvalidOperationException("Database connection string not configured");
|
?? throw new InvalidOperationException("Database connection string not configured");
|
||||||
|
_dataSource = CreateDataSource(connectionString);
|
||||||
_options = options.Value;
|
_options = options.Value;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
|
||||||
@@ -60,8 +61,7 @@ public sealed class PostgresReportStorageService : IReportStorageService, IDispo
|
|||||||
var json = JsonSerializer.Serialize(report, JsonSerializerOptions.Default);
|
var json = JsonSerializer.Serialize(report, JsonSerializerOptions.Default);
|
||||||
var compressed = CompressJson(json);
|
var compressed = CompressJson(json);
|
||||||
|
|
||||||
await using var connection = new NpgsqlConnection(_connectionString);
|
await using var connection = await _dataSource.OpenConnectionAsync(ct);
|
||||||
await connection.OpenAsync(ct);
|
|
||||||
|
|
||||||
const string sql = """
|
const string sql = """
|
||||||
INSERT INTO doctor_reports (run_id, started_at, completed_at, overall_severity,
|
INSERT INTO doctor_reports (run_id, started_at, completed_at, overall_severity,
|
||||||
@@ -104,8 +104,7 @@ public sealed class PostgresReportStorageService : IReportStorageService, IDispo
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<DoctorReport?> GetReportAsync(string runId, CancellationToken ct)
|
public async Task<DoctorReport?> GetReportAsync(string runId, CancellationToken ct)
|
||||||
{
|
{
|
||||||
await using var connection = new NpgsqlConnection(_connectionString);
|
await using var connection = await _dataSource.OpenConnectionAsync(ct);
|
||||||
await connection.OpenAsync(ct);
|
|
||||||
|
|
||||||
const string sql = "SELECT report_json_compressed FROM doctor_reports WHERE run_id = @runId";
|
const string sql = "SELECT report_json_compressed FROM doctor_reports WHERE run_id = @runId";
|
||||||
|
|
||||||
@@ -126,8 +125,7 @@ public sealed class PostgresReportStorageService : IReportStorageService, IDispo
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<IReadOnlyList<ReportSummaryDto>> ListReportsAsync(int limit, int offset, CancellationToken ct)
|
public async Task<IReadOnlyList<ReportSummaryDto>> ListReportsAsync(int limit, int offset, CancellationToken ct)
|
||||||
{
|
{
|
||||||
await using var connection = new NpgsqlConnection(_connectionString);
|
await using var connection = await _dataSource.OpenConnectionAsync(ct);
|
||||||
await connection.OpenAsync(ct);
|
|
||||||
|
|
||||||
const string sql = """
|
const string sql = """
|
||||||
SELECT run_id, started_at, completed_at, overall_severity,
|
SELECT run_id, started_at, completed_at, overall_severity,
|
||||||
@@ -170,8 +168,7 @@ public sealed class PostgresReportStorageService : IReportStorageService, IDispo
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<bool> DeleteReportAsync(string runId, CancellationToken ct)
|
public async Task<bool> DeleteReportAsync(string runId, CancellationToken ct)
|
||||||
{
|
{
|
||||||
await using var connection = new NpgsqlConnection(_connectionString);
|
await using var connection = await _dataSource.OpenConnectionAsync(ct);
|
||||||
await connection.OpenAsync(ct);
|
|
||||||
|
|
||||||
const string sql = "DELETE FROM doctor_reports WHERE run_id = @runId";
|
const string sql = "DELETE FROM doctor_reports WHERE run_id = @runId";
|
||||||
|
|
||||||
@@ -185,8 +182,7 @@ public sealed class PostgresReportStorageService : IReportStorageService, IDispo
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<int> GetCountAsync(CancellationToken ct)
|
public async Task<int> GetCountAsync(CancellationToken ct)
|
||||||
{
|
{
|
||||||
await using var connection = new NpgsqlConnection(_connectionString);
|
await using var connection = await _dataSource.OpenConnectionAsync(ct);
|
||||||
await connection.OpenAsync(ct);
|
|
||||||
|
|
||||||
const string sql = "SELECT COUNT(*) FROM doctor_reports";
|
const string sql = "SELECT COUNT(*) FROM doctor_reports";
|
||||||
|
|
||||||
@@ -207,8 +203,7 @@ public sealed class PostgresReportStorageService : IReportStorageService, IDispo
|
|||||||
|
|
||||||
var cutoff = DateTimeOffset.UtcNow.AddDays(-_options.ReportRetentionDays);
|
var cutoff = DateTimeOffset.UtcNow.AddDays(-_options.ReportRetentionDays);
|
||||||
|
|
||||||
await using var connection = new NpgsqlConnection(_connectionString);
|
await using var connection = await _dataSource.OpenConnectionAsync(ct);
|
||||||
await connection.OpenAsync(ct);
|
|
||||||
|
|
||||||
const string sql = "DELETE FROM doctor_reports WHERE created_at < @cutoff";
|
const string sql = "DELETE FROM doctor_reports WHERE created_at < @cutoff";
|
||||||
|
|
||||||
@@ -261,7 +256,20 @@ public sealed class PostgresReportStorageService : IReportStorageService, IDispo
|
|||||||
if (!_disposed)
|
if (!_disposed)
|
||||||
{
|
{
|
||||||
_cleanupTimer?.Dispose();
|
_cleanupTimer?.Dispose();
|
||||||
|
_dataSource.Dispose();
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static NpgsqlDataSource CreateDataSource(string connectionString)
|
||||||
|
{
|
||||||
|
ArgumentException.ThrowIfNullOrWhiteSpace(connectionString);
|
||||||
|
|
||||||
|
var connectionStringBuilder = new NpgsqlConnectionStringBuilder(connectionString)
|
||||||
|
{
|
||||||
|
ApplicationName = "stellaops-doctor-report-storage"
|
||||||
|
};
|
||||||
|
|
||||||
|
return new NpgsqlDataSourceBuilder(connectionStringBuilder.ConnectionString).Build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
## Completed
|
## Completed
|
||||||
|
|
||||||
### 2026-01-12 - Sprint 001_007 Implementation
|
### 2026-01-12 - Sprint 001_007 Implementation
|
||||||
|
- [x] SPRINT_20260405_011-XPORT: named the PostgreSQL report-storage data source and aligned the runtime path with the shared transport attribution guardrail.
|
||||||
- [x] Created project structure following Platform WebService pattern
|
- [x] Created project structure following Platform WebService pattern
|
||||||
- [x] Implemented DoctorServiceOptions with authority configuration
|
- [x] Implemented DoctorServiceOptions with authority configuration
|
||||||
- [x] Defined DoctorPolicies and DoctorScopes for authorization
|
- [x] Defined DoctorPolicies and DoctorScopes for authorization
|
||||||
|
|||||||
@@ -38,7 +38,12 @@ public sealed class EvidenceLockerDataSource : IAsyncDisposable
|
|||||||
|
|
||||||
private static NpgsqlDataSource CreateDataSource(string connectionString)
|
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();
|
builder.EnableDynamicJson();
|
||||||
return builder.Build();
|
return builder.Build();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,10 +83,10 @@ public static class EvidenceLockerInfrastructureServiceCollectionExtensions
|
|||||||
// Verdict attestation repository
|
// Verdict attestation repository
|
||||||
services.AddScoped<StellaOps.EvidenceLocker.Storage.IVerdictRepository>(provider =>
|
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>>();
|
var logger = provider.GetRequiredService<ILogger<StellaOps.EvidenceLocker.Storage.PostgresVerdictRepository>>();
|
||||||
return new StellaOps.EvidenceLocker.Storage.PostgresVerdictRepository(
|
return new StellaOps.EvidenceLocker.Storage.PostgresVerdictRepository(
|
||||||
options.Database.ConnectionString,
|
cancellationToken => dataSource.OpenConnectionAsync(cancellationToken),
|
||||||
logger);
|
logger);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -94,10 +94,10 @@ public static class EvidenceLockerInfrastructureServiceCollectionExtensions
|
|||||||
// Sprint: SPRINT_20260219_009 (CID-04)
|
// Sprint: SPRINT_20260219_009 (CID-04)
|
||||||
services.AddScoped<StellaOps.EvidenceLocker.Storage.IEvidenceThreadRepository>(provider =>
|
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>>();
|
var logger = provider.GetRequiredService<ILogger<StellaOps.EvidenceLocker.Storage.PostgresEvidenceThreadRepository>>();
|
||||||
return new StellaOps.EvidenceLocker.Storage.PostgresEvidenceThreadRepository(
|
return new StellaOps.EvidenceLocker.Storage.PostgresEvidenceThreadRepository(
|
||||||
options.Database.ConnectionString,
|
cancellationToken => dataSource.OpenConnectionAsync(cancellationToken),
|
||||||
logger);
|
logger);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229
|
|||||||
|
|
||||||
| Task ID | Status | Notes |
|
| 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-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-T | DONE | Revalidated 2026-01-07; open findings tracked in audit report. |
|
||||||
| AUDIT-0289-A | TODO | Revalidated 2026-01-07 (open findings). |
|
| AUDIT-0289-A | TODO | Revalidated 2026-01-07 (open findings). |
|
||||||
|
|||||||
@@ -11,14 +11,14 @@ namespace StellaOps.EvidenceLocker.Storage;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class PostgresEvidenceThreadRepository : IEvidenceThreadRepository
|
public sealed class PostgresEvidenceThreadRepository : IEvidenceThreadRepository
|
||||||
{
|
{
|
||||||
private readonly string _connectionString;
|
private readonly Func<CancellationToken, Task<NpgsqlConnection>> _openConnectionAsync;
|
||||||
private readonly ILogger<PostgresEvidenceThreadRepository> _logger;
|
private readonly ILogger<PostgresEvidenceThreadRepository> _logger;
|
||||||
|
|
||||||
public PostgresEvidenceThreadRepository(
|
public PostgresEvidenceThreadRepository(
|
||||||
string connectionString,
|
Func<CancellationToken, Task<NpgsqlConnection>> openConnectionAsync,
|
||||||
ILogger<PostgresEvidenceThreadRepository> logger)
|
ILogger<PostgresEvidenceThreadRepository> logger)
|
||||||
{
|
{
|
||||||
_connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
|
_openConnectionAsync = openConnectionAsync ?? throw new ArgumentNullException(nameof(openConnectionAsync));
|
||||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,8 +45,7 @@ public sealed class PostgresEvidenceThreadRepository : IEvidenceThreadRepository
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await using var connection = new NpgsqlConnection(_connectionString);
|
await using var connection = await _openConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||||
await connection.OpenAsync(cancellationToken);
|
|
||||||
|
|
||||||
var record = await connection.QuerySingleOrDefaultAsync<ArtifactCanonicalRecord>(
|
var record = await connection.QuerySingleOrDefaultAsync<ArtifactCanonicalRecord>(
|
||||||
new CommandDefinition(
|
new CommandDefinition(
|
||||||
@@ -88,8 +87,7 @@ public sealed class PostgresEvidenceThreadRepository : IEvidenceThreadRepository
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await using var connection = new NpgsqlConnection(_connectionString);
|
await using var connection = await _openConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||||
await connection.OpenAsync(cancellationToken);
|
|
||||||
|
|
||||||
var results = await connection.QueryAsync<ArtifactCanonicalRecord>(
|
var results = await connection.QueryAsync<ArtifactCanonicalRecord>(
|
||||||
new CommandDefinition(
|
new CommandDefinition(
|
||||||
|
|||||||
@@ -9,16 +9,16 @@ namespace StellaOps.EvidenceLocker.Storage;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class PostgresVerdictRepository : IVerdictRepository
|
public sealed class PostgresVerdictRepository : IVerdictRepository
|
||||||
{
|
{
|
||||||
private readonly string _connectionString;
|
private readonly Func<CancellationToken, Task<NpgsqlConnection>> _openConnectionAsync;
|
||||||
private readonly ILogger<PostgresVerdictRepository> _logger;
|
private readonly ILogger<PostgresVerdictRepository> _logger;
|
||||||
private readonly TimeProvider _timeProvider;
|
private readonly TimeProvider _timeProvider;
|
||||||
|
|
||||||
public PostgresVerdictRepository(
|
public PostgresVerdictRepository(
|
||||||
string connectionString,
|
Func<CancellationToken, Task<NpgsqlConnection>> openConnectionAsync,
|
||||||
ILogger<PostgresVerdictRepository> logger,
|
ILogger<PostgresVerdictRepository> logger,
|
||||||
TimeProvider? timeProvider = null)
|
TimeProvider? timeProvider = null)
|
||||||
{
|
{
|
||||||
_connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
|
_openConnectionAsync = openConnectionAsync ?? throw new ArgumentNullException(nameof(openConnectionAsync));
|
||||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||||
}
|
}
|
||||||
@@ -74,8 +74,7 @@ public sealed class PostgresVerdictRepository : IVerdictRepository
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await using var connection = new NpgsqlConnection(_connectionString);
|
await using var connection = await _openConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||||
await connection.OpenAsync(cancellationToken);
|
|
||||||
|
|
||||||
var now = _timeProvider.GetUtcNow();
|
var now = _timeProvider.GetUtcNow();
|
||||||
var verdictId = await connection.ExecuteScalarAsync<string>(
|
var verdictId = await connection.ExecuteScalarAsync<string>(
|
||||||
@@ -153,8 +152,7 @@ public sealed class PostgresVerdictRepository : IVerdictRepository
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await using var connection = new NpgsqlConnection(_connectionString);
|
await using var connection = await _openConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||||
await connection.OpenAsync(cancellationToken);
|
|
||||||
|
|
||||||
var record = await connection.QuerySingleOrDefaultAsync<VerdictAttestationRecord>(
|
var record = await connection.QuerySingleOrDefaultAsync<VerdictAttestationRecord>(
|
||||||
new CommandDefinition(
|
new CommandDefinition(
|
||||||
@@ -231,8 +229,7 @@ public sealed class PostgresVerdictRepository : IVerdictRepository
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await using var connection = new NpgsqlConnection(_connectionString);
|
await using var connection = await _openConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||||
await connection.OpenAsync(cancellationToken);
|
|
||||||
|
|
||||||
var results = await connection.QueryAsync<VerdictAttestationSummary>(
|
var results = await connection.QueryAsync<VerdictAttestationSummary>(
|
||||||
new CommandDefinition(
|
new CommandDefinition(
|
||||||
@@ -309,8 +306,7 @@ public sealed class PostgresVerdictRepository : IVerdictRepository
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await using var connection = new NpgsqlConnection(_connectionString);
|
await using var connection = await _openConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||||
await connection.OpenAsync(cancellationToken);
|
|
||||||
|
|
||||||
var results = await connection.QueryAsync<VerdictAttestationSummary>(
|
var results = await connection.QueryAsync<VerdictAttestationSummary>(
|
||||||
new CommandDefinition(
|
new CommandDefinition(
|
||||||
@@ -365,8 +361,7 @@ public sealed class PostgresVerdictRepository : IVerdictRepository
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await using var connection = new NpgsqlConnection(_connectionString);
|
await using var connection = await _openConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||||
await connection.OpenAsync(cancellationToken);
|
|
||||||
|
|
||||||
var count = await connection.ExecuteScalarAsync<int>(
|
var count = await connection.ExecuteScalarAsync<int>(
|
||||||
new CommandDefinition(
|
new CommandDefinition(
|
||||||
|
|||||||
@@ -40,7 +40,12 @@ public sealed class ExportCenterDataSource : IAsyncDisposable
|
|||||||
|
|
||||||
private static NpgsqlDataSource CreateDataSource(string connectionString)
|
private static NpgsqlDataSource CreateDataSource(string connectionString)
|
||||||
{
|
{
|
||||||
var builder = new NpgsqlDataSourceBuilder(connectionString);
|
var connectionStringBuilder = new NpgsqlConnectionStringBuilder(connectionString)
|
||||||
|
{
|
||||||
|
ApplicationName = "stellaops-export-center",
|
||||||
|
};
|
||||||
|
|
||||||
|
var builder = new NpgsqlDataSourceBuilder(connectionStringBuilder.ConnectionString);
|
||||||
builder.EnableDynamicJson();
|
builder.EnableDynamicJson();
|
||||||
return builder.Build();
|
return builder.Build();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229
|
|||||||
|
|
||||||
| Task ID | Status | Notes |
|
| Task ID | Status | Notes |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
|
| SPRINT_20260405_011-XPORT | DONE | `docs/implplan/SPRINT_20260405_011___Libraries_transport_pooling_and_attribution_hardening.md`: named Export Center PostgreSQL datasource sessions for runtime attribution. |
|
||||||
| QA-EXPORTCENTER-VERIFY-001 | DONE | `cli-ui-surfacing-of-hidden-backend-capabilities` verified in run-002 (Tier 0/1/2 pass; Policy blocker remediated; client 62/62 and service 920/920). |
|
| QA-EXPORTCENTER-VERIFY-001 | DONE | `cli-ui-surfacing-of-hidden-backend-capabilities` verified in run-002 (Tier 0/1/2 pass; Policy blocker remediated; client 62/62 and service 920/920). |
|
||||||
| QA-EXPORTCENTER-VERIFY-002 | DONE | `export-center-risk-bundle-builder` verified in run-001 (Tier 0/1/2 pass; service suite 920/920). |
|
| QA-EXPORTCENTER-VERIFY-002 | DONE | `export-center-risk-bundle-builder` verified in run-001 (Tier 0/1/2 pass; service suite 920/920). |
|
||||||
| QA-EXPORTCENTER-VERIFY-003 | DONE | `export-telemetry-and-worker` verified in run-001 (Tier 0/1/2 pass; service suite 920/920). |
|
| QA-EXPORTCENTER-VERIFY-003 | DONE | `export-telemetry-and-worker` verified in run-001 (Tier 0/1/2 pass; service suite 920/920). |
|
||||||
|
|||||||
@@ -22,7 +22,12 @@ public sealed class LedgerDataSource : IAsyncDisposable
|
|||||||
_options = options.Value.Database;
|
_options = options.Value.Database;
|
||||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
|
|
||||||
var builder = new NpgsqlDataSourceBuilder(_options.ConnectionString);
|
var connectionStringBuilder = new NpgsqlConnectionStringBuilder(_options.ConnectionString)
|
||||||
|
{
|
||||||
|
ApplicationName = "stellaops-findings-ledger",
|
||||||
|
};
|
||||||
|
|
||||||
|
var builder = new NpgsqlDataSourceBuilder(connectionStringBuilder.ConnectionString);
|
||||||
_dataSource = builder.Build();
|
_dataSource = builder.Build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229
|
|||||||
|
|
||||||
| Task ID | Status | Notes |
|
| Task ID | Status | Notes |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
|
| SPRINT_20260405_011-XPORT | DONE | `docs/implplan/SPRINT_20260405_011___Libraries_transport_pooling_and_attribution_hardening.md`: named Findings Ledger PostgreSQL datasource sessions for runtime attribution. |
|
||||||
| AUDIT-0342-M | DONE | Revalidated 2026-01-07; maintainability audit for Findings Ledger. |
|
| AUDIT-0342-M | DONE | Revalidated 2026-01-07; maintainability audit for Findings Ledger. |
|
||||||
| AUDIT-0342-T | DONE | Revalidated 2026-01-07; test coverage audit for Findings Ledger. |
|
| AUDIT-0342-T | DONE | Revalidated 2026-01-07; test coverage audit for Findings Ledger. |
|
||||||
| AUDIT-0342-A | TODO | Pending approval (non-test project; revalidated 2026-01-07). |
|
| AUDIT-0342-A | TODO | Pending approval (non-test project; revalidated 2026-01-07). |
|
||||||
|
|||||||
@@ -20,7 +20,13 @@ public sealed class PostgresRiskScoreResultStore : IRiskScoreResultStore, IAsync
|
|||||||
public PostgresRiskScoreResultStore(string connectionString)
|
public PostgresRiskScoreResultStore(string connectionString)
|
||||||
{
|
{
|
||||||
ArgumentException.ThrowIfNullOrWhiteSpace(connectionString);
|
ArgumentException.ThrowIfNullOrWhiteSpace(connectionString);
|
||||||
_dataSource = NpgsqlDataSource.Create(connectionString);
|
|
||||||
|
var connectionStringBuilder = new NpgsqlConnectionStringBuilder(connectionString)
|
||||||
|
{
|
||||||
|
ApplicationName = "stellaops-riskengine",
|
||||||
|
};
|
||||||
|
|
||||||
|
_dataSource = new NpgsqlDataSourceBuilder(connectionStringBuilder.ConnectionString).Build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SaveAsync(RiskScoreResult result, CancellationToken cancellationToken)
|
public async Task SaveAsync(RiskScoreResult result, CancellationToken cancellationToken)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ Source of truth: `docs/implplan/SPRINT_20260130_002_Tools_csproj_remediation_sol
|
|||||||
|
|
||||||
| Task ID | Status | Notes |
|
| Task ID | Status | Notes |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
|
| SPRINT_20260405_011-XPORT | DONE | `docs/implplan/SPRINT_20260405_011___Libraries_transport_pooling_and_attribution_hardening.md`: named the risk-engine PostgreSQL result-store datasource. |
|
||||||
| REMED-05 | TODO | Remediation checklist: docs/implplan/audits/csproj-standards/remediation/checklists/src/RiskEngine/StellaOps.RiskEngine/StellaOps.RiskEngine.Infrastructure/StellaOps.RiskEngine.Infrastructure.md. |
|
| REMED-05 | TODO | Remediation checklist: docs/implplan/audits/csproj-standards/remediation/checklists/src/RiskEngine/StellaOps.RiskEngine/StellaOps.RiskEngine.Infrastructure/StellaOps.RiskEngine.Infrastructure.md. |
|
||||||
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
|
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
|
||||||
| SPRINT-312-005 | DONE | Added `PostgresRiskScoreResultStore` with schema/bootstrap and deterministic upsert/read behavior. |
|
| SPRINT-312-005 | DONE | Added `PostgresRiskScoreResultStore` with schema/bootstrap and deterministic upsert/read behavior. |
|
||||||
|
|||||||
@@ -32,10 +32,7 @@ public sealed class AnalyticsIngestionDataSource : IAsyncDisposable
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_dataSource ??= new NpgsqlDataSourceBuilder(_connectionString!)
|
_dataSource ??= CreateDataSource(_connectionString!);
|
||||||
{
|
|
||||||
Name = "StellaOps.Platform.Analytics.Ingestion"
|
|
||||||
}.Build();
|
|
||||||
|
|
||||||
var connection = await _dataSource.OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
|
var connection = await _dataSource.OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||||
await ConfigureSessionAsync(connection, cancellationToken).ConfigureAwait(false);
|
await ConfigureSessionAsync(connection, cancellationToken).ConfigureAwait(false);
|
||||||
@@ -62,4 +59,17 @@ public sealed class AnalyticsIngestionDataSource : IAsyncDisposable
|
|||||||
|
|
||||||
_logger.LogDebug("Configured analytics ingestion session for PostgreSQL connection.");
|
_logger.LogDebug("Configured analytics ingestion session for PostgreSQL connection.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static NpgsqlDataSource CreateDataSource(string connectionString)
|
||||||
|
{
|
||||||
|
var connectionStringBuilder = new NpgsqlConnectionStringBuilder(connectionString)
|
||||||
|
{
|
||||||
|
ApplicationName = "stellaops-platform-analytics-ingestion",
|
||||||
|
};
|
||||||
|
|
||||||
|
return new NpgsqlDataSourceBuilder(connectionStringBuilder.ConnectionString)
|
||||||
|
{
|
||||||
|
Name = "StellaOps.Platform.Analytics.Ingestion"
|
||||||
|
}.Build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ Source of truth: `docs/implplan/SPRINT_20260130_002_Tools_csproj_remediation_sol
|
|||||||
|
|
||||||
| Task ID | Status | Notes |
|
| Task ID | Status | Notes |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
|
| SPRINT_20260405_011-XPORT | DONE | `docs/implplan/SPRINT_20260405_011___Libraries_transport_pooling_and_attribution_hardening.md`: named analytics ingestion PostgreSQL datasource construction for runtime attribution. |
|
||||||
| QA-PLATFORM-VERIFY-001 | DONE | run-002 verification captured analytics rollup/materialized-view behavior evidence; feature terminalized as `not_implemented` due missing advisory lock/LISTEN-NOTIFY parity. |
|
| QA-PLATFORM-VERIFY-001 | DONE | run-002 verification captured analytics rollup/materialized-view behavior evidence; feature terminalized as `not_implemented` due missing advisory lock/LISTEN-NOTIFY parity. |
|
||||||
| QA-PLATFORM-VERIFY-002 | DONE | run-001 verification passed with maintenance, endpoint paths, analytics service behavior, and Docker schema integration (`38/38` scoped tests). |
|
| QA-PLATFORM-VERIFY-002 | DONE | run-001 verification passed with maintenance, endpoint paths, analytics service behavior, and Docker schema integration (`38/38` scoped tests). |
|
||||||
| QA-PLATFORM-VERIFY-003 | DONE | `platform-service-aggregation-layer` verified with run-001 Tier 0/1/2 endpoint evidence and moved to `docs/features/checked/platform/`. |
|
| QA-PLATFORM-VERIFY-003 | DONE | `platform-service-aggregation-layer` verified with run-001 Tier 0/1/2 endpoint evidence and moved to `docs/features/checked/platform/`. |
|
||||||
|
|||||||
@@ -32,10 +32,7 @@ public sealed class PlatformAnalyticsDataSource : IAsyncDisposable
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_dataSource ??= new NpgsqlDataSourceBuilder(_connectionString!)
|
_dataSource ??= CreateDataSource(_connectionString!);
|
||||||
{
|
|
||||||
Name = "StellaOps.Platform.Analytics"
|
|
||||||
}.Build();
|
|
||||||
|
|
||||||
var connection = await _dataSource.OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
|
var connection = await _dataSource.OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||||
await ConfigureSessionAsync(connection, cancellationToken).ConfigureAwait(false);
|
await ConfigureSessionAsync(connection, cancellationToken).ConfigureAwait(false);
|
||||||
@@ -64,4 +61,17 @@ public sealed class PlatformAnalyticsDataSource : IAsyncDisposable
|
|||||||
|
|
||||||
_logger.LogDebug("Configured analytics session for PostgreSQL connection.");
|
_logger.LogDebug("Configured analytics session for PostgreSQL connection.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static NpgsqlDataSource CreateDataSource(string connectionString)
|
||||||
|
{
|
||||||
|
var connectionStringBuilder = new NpgsqlConnectionStringBuilder(connectionString)
|
||||||
|
{
|
||||||
|
ApplicationName = "stellaops-platform-analytics",
|
||||||
|
};
|
||||||
|
|
||||||
|
return new NpgsqlDataSourceBuilder(connectionStringBuilder.ConnectionString)
|
||||||
|
{
|
||||||
|
Name = "StellaOps.Platform.Analytics"
|
||||||
|
}.Build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,12 @@ public static class ServiceCollectionExtensions
|
|||||||
services.AddSingleton<NpgsqlDataSource>(sp =>
|
services.AddSingleton<NpgsqlDataSource>(sp =>
|
||||||
{
|
{
|
||||||
var options = sp.GetRequiredService<IOptions<PluginRegistryOptions>>().Value;
|
var options = sp.GetRequiredService<IOptions<PluginRegistryOptions>>().Value;
|
||||||
var dataSourceBuilder = new NpgsqlDataSourceBuilder(options.ConnectionString);
|
var connectionStringBuilder = new NpgsqlConnectionStringBuilder(options.ConnectionString)
|
||||||
|
{
|
||||||
|
ApplicationName = "stellaops-plugin-registry",
|
||||||
|
};
|
||||||
|
|
||||||
|
var dataSourceBuilder = new NpgsqlDataSourceBuilder(connectionStringBuilder.ConnectionString);
|
||||||
return dataSourceBuilder.Build();
|
return dataSourceBuilder.Build();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -62,7 +67,12 @@ public static class ServiceCollectionExtensions
|
|||||||
services.AddSingleton<NpgsqlDataSource>(sp =>
|
services.AddSingleton<NpgsqlDataSource>(sp =>
|
||||||
{
|
{
|
||||||
var options = sp.GetRequiredService<IOptions<PluginRegistryOptions>>().Value;
|
var options = sp.GetRequiredService<IOptions<PluginRegistryOptions>>().Value;
|
||||||
var dataSourceBuilder = new NpgsqlDataSourceBuilder(options.ConnectionString);
|
var connectionStringBuilder = new NpgsqlConnectionStringBuilder(options.ConnectionString)
|
||||||
|
{
|
||||||
|
ApplicationName = "stellaops-plugin-registry",
|
||||||
|
};
|
||||||
|
|
||||||
|
var dataSourceBuilder = new NpgsqlDataSourceBuilder(connectionStringBuilder.ConnectionString);
|
||||||
return dataSourceBuilder.Build();
|
return dataSourceBuilder.Build();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -4,5 +4,6 @@ Source of truth: `docs/implplan/SPRINT_20260130_002_Tools_csproj_remediation_sol
|
|||||||
|
|
||||||
| Task ID | Status | Notes |
|
| Task ID | Status | Notes |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
|
| SPRINT_20260405_011-XPORT | DONE | `docs/implplan/SPRINT_20260405_011___Libraries_transport_pooling_and_attribution_hardening.md`: named plugin registry PostgreSQL datasource construction. |
|
||||||
| REMED-05 | TODO | Remediation checklist: docs/implplan/audits/csproj-standards/remediation/checklists/src/Plugin/StellaOps.Plugin.Registry/StellaOps.Plugin.Registry.md. |
|
| REMED-05 | TODO | Remediation checklist: docs/implplan/audits/csproj-standards/remediation/checklists/src/Plugin/StellaOps.Plugin.Registry/StellaOps.Plugin.Registry.md. |
|
||||||
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
|
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
|
||||||
|
|||||||
@@ -272,7 +272,11 @@ public static class PolicyEngineServiceCollectionExtensions
|
|||||||
string connectionString)
|
string connectionString)
|
||||||
{
|
{
|
||||||
services.TryAddSingleton<IConnectionMultiplexer>(sp =>
|
services.TryAddSingleton<IConnectionMultiplexer>(sp =>
|
||||||
ConnectionMultiplexer.Connect(connectionString));
|
{
|
||||||
|
var redisOptions = ConfigurationOptions.Parse(connectionString);
|
||||||
|
redisOptions.ClientName ??= "stellaops-policy-engine";
|
||||||
|
return ConnectionMultiplexer.Connect(redisOptions);
|
||||||
|
});
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Source of truth: `docs/implplan/SPRINT_20260119_021_Policy_license_compliance.md
|
|||||||
|
|
||||||
| Task ID | Status | Notes |
|
| Task ID | Status | Notes |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
|
| SPRINT_20260405_011-XPORT-VALKEY | DONE | `docs/implplan/SPRINT_20260405_011___Libraries_transport_pooling_and_attribution_hardening.md`: named Policy Engine Valkey client construction. |
|
||||||
| AUDIT-0440-M | DONE | Revalidated 2026-01-07; maintainability audit for StellaOps.Policy.Engine. |
|
| AUDIT-0440-M | DONE | Revalidated 2026-01-07; maintainability audit for StellaOps.Policy.Engine. |
|
||||||
| AUDIT-0440-T | DONE | Revalidated 2026-01-07; test coverage audit for StellaOps.Policy.Engine. |
|
| AUDIT-0440-T | DONE | Revalidated 2026-01-07; test coverage audit for StellaOps.Policy.Engine. |
|
||||||
| AUDIT-0440-A | DOING | Revalidated 2026-01-07 (open findings). |
|
| AUDIT-0440-A | DOING | Revalidated 2026-01-07 (open findings). |
|
||||||
|
|||||||
@@ -35,7 +35,13 @@ builder.Services.AddSingleton<NpgsqlDataSource>(sp =>
|
|||||||
var config = sp.GetRequiredService<IConfiguration>();
|
var config = sp.GetRequiredService<IConfiguration>();
|
||||||
var connStr = config.GetConnectionString("PostgreSQL")
|
var connStr = config.GetConnectionString("PostgreSQL")
|
||||||
?? throw new InvalidOperationException("PostgreSQL connection string not configured");
|
?? throw new InvalidOperationException("PostgreSQL connection string not configured");
|
||||||
return new NpgsqlDataSourceBuilder(connStr).Build();
|
|
||||||
|
var connectionStringBuilder = new NpgsqlConnectionStringBuilder(connStr)
|
||||||
|
{
|
||||||
|
ApplicationName = "stellaops-reachgraph",
|
||||||
|
};
|
||||||
|
|
||||||
|
return new NpgsqlDataSourceBuilder(connectionStringBuilder.ConnectionString).Build();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Redis/Valkey (lazy so integration tests can replace before first resolve)
|
// Redis/Valkey (lazy so integration tests can replace before first resolve)
|
||||||
@@ -43,7 +49,9 @@ builder.Services.AddSingleton<IConnectionMultiplexer>(sp =>
|
|||||||
{
|
{
|
||||||
var config = sp.GetRequiredService<IConfiguration>();
|
var config = sp.GetRequiredService<IConfiguration>();
|
||||||
var redisConnStr = config.GetConnectionString("Redis") ?? "localhost:6379";
|
var redisConnStr = config.GetConnectionString("Redis") ?? "localhost:6379";
|
||||||
return ConnectionMultiplexer.Connect(redisConnStr);
|
var redisOptions = ConfigurationOptions.Parse(redisConnStr);
|
||||||
|
redisOptions.ClientName ??= "stellaops-reachgraph";
|
||||||
|
return ConnectionMultiplexer.Connect(redisOptions);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Core services
|
// Core services
|
||||||
|
|||||||
@@ -4,5 +4,7 @@ Source of truth: `docs/implplan/SPRINT_20260130_002_Tools_csproj_remediation_sol
|
|||||||
|
|
||||||
| Task ID | Status | Notes |
|
| Task ID | Status | Notes |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
|
| SPRINT_20260405_011-XPORT | DONE | `docs/implplan/SPRINT_20260405_011___Libraries_transport_pooling_and_attribution_hardening.md`: named the ReachGraph PostgreSQL datasource for runtime attribution. |
|
||||||
|
| SPRINT_20260405_011-XPORT-VALKEY | DONE | `docs/implplan/SPRINT_20260405_011___Libraries_transport_pooling_and_attribution_hardening.md`: named the ReachGraph Valkey client construction path. |
|
||||||
| REMED-05 | TODO | Remediation checklist: docs/implplan/audits/csproj-standards/remediation/checklists/src/ReachGraph/StellaOps.ReachGraph.WebService/StellaOps.ReachGraph.WebService.md. |
|
| REMED-05 | TODO | Remediation checklist: docs/implplan/audits/csproj-standards/remediation/checklists/src/ReachGraph/StellaOps.ReachGraph.WebService/StellaOps.ReachGraph.WebService.md. |
|
||||||
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
|
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
|
||||||
|
|||||||
@@ -29,7 +29,11 @@ public static class ServiceCollectionExtensions
|
|||||||
ArgumentException.ThrowIfNullOrWhiteSpace(connectionString);
|
ArgumentException.ThrowIfNullOrWhiteSpace(connectionString);
|
||||||
|
|
||||||
// Create NpgsqlDataSource from connection string
|
// Create NpgsqlDataSource from connection string
|
||||||
var dataSourceBuilder = new NpgsqlDataSourceBuilder(connectionString);
|
var connectionStringBuilder = new NpgsqlConnectionStringBuilder(connectionString)
|
||||||
|
{
|
||||||
|
ApplicationName = "stellaops-release-orchestrator-policygate",
|
||||||
|
};
|
||||||
|
var dataSourceBuilder = new NpgsqlDataSourceBuilder(connectionStringBuilder.ConnectionString);
|
||||||
var dataSource = dataSourceBuilder.Build();
|
var dataSource = dataSourceBuilder.Build();
|
||||||
|
|
||||||
// Register stores
|
// Register stores
|
||||||
|
|||||||
@@ -4,5 +4,6 @@ Source of truth: `docs/implplan/SPRINT_20260130_002_Tools_csproj_remediation_sol
|
|||||||
|
|
||||||
| Task ID | Status | Notes |
|
| Task ID | Status | Notes |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
|
| SPRINT_20260405_011-XPORT | DONE | `docs/implplan/SPRINT_20260405_011___Libraries_transport_pooling_and_attribution_hardening.md`: named Policy Gate PostgreSQL datasource construction. |
|
||||||
| REMED-05 | TODO | Remediation checklist: docs/implplan/audits/csproj-standards/remediation/checklists/src/ReleaseOrchestrator/__Libraries/StellaOps.ReleaseOrchestrator.PolicyGate/StellaOps.ReleaseOrchestrator.PolicyGate.md. |
|
| REMED-05 | TODO | Remediation checklist: docs/implplan/audits/csproj-standards/remediation/checklists/src/ReleaseOrchestrator/__Libraries/StellaOps.ReleaseOrchestrator.PolicyGate/StellaOps.ReleaseOrchestrator.PolicyGate.md. |
|
||||||
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
|
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
|
||||||
|
|||||||
@@ -14,7 +14,13 @@ public sealed class PostgresFeedSnapshotIndexStore : IFeedSnapshotIndexStore, IA
|
|||||||
public PostgresFeedSnapshotIndexStore(string connectionString)
|
public PostgresFeedSnapshotIndexStore(string connectionString)
|
||||||
{
|
{
|
||||||
ArgumentException.ThrowIfNullOrWhiteSpace(connectionString);
|
ArgumentException.ThrowIfNullOrWhiteSpace(connectionString);
|
||||||
_dataSource = NpgsqlDataSource.Create(connectionString);
|
|
||||||
|
var connectionStringBuilder = new NpgsqlConnectionStringBuilder(connectionString)
|
||||||
|
{
|
||||||
|
ApplicationName = "stellaops-replay",
|
||||||
|
};
|
||||||
|
|
||||||
|
_dataSource = new NpgsqlDataSourceBuilder(connectionStringBuilder.ConnectionString).Build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task IndexSnapshotAsync(FeedSnapshotIndexEntry entry, CancellationToken ct = default)
|
public async Task IndexSnapshotAsync(FeedSnapshotIndexEntry entry, CancellationToken ct = default)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ Source of truth: `docs/implplan/SPRINT_20260130_002_Tools_csproj_remediation_sol
|
|||||||
|
|
||||||
| Task ID | Status | Notes |
|
| Task ID | Status | Notes |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
|
| SPRINT_20260405_011-XPORT | DONE | `docs/implplan/SPRINT_20260405_011___Libraries_transport_pooling_and_attribution_hardening.md`: named Replay PostgreSQL snapshot-index datasource construction. |
|
||||||
| REMED-05 | TODO | Remediation checklist: docs/implplan/audits/csproj-standards/remediation/checklists/src/Replay/StellaOps.Replay.WebService/StellaOps.Replay.WebService.md. |
|
| REMED-05 | TODO | Remediation checklist: docs/implplan/audits/csproj-standards/remediation/checklists/src/Replay/StellaOps.Replay.WebService/StellaOps.Replay.WebService.md. |
|
||||||
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
|
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
|
||||||
| SPRINT-312-006 | DONE | Added Postgres snapshot index + seed-fs snapshot blob stores and wired storage-driver registration in webservice startup. |
|
| SPRINT-312-006 | DONE | Added Postgres snapshot index + seed-fs snapshot blob stores and wired storage-driver registration in webservice startup. |
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ namespace StellaOps.Scanner.Reachability.Cache;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class PostgresReachabilityCache : IReachabilityCache
|
public sealed class PostgresReachabilityCache : IReachabilityCache
|
||||||
{
|
{
|
||||||
private readonly string _connectionString;
|
private readonly Services.PostgresReachabilityDataSourceProvider _dataSourceProvider;
|
||||||
private readonly ILogger<PostgresReachabilityCache> _logger;
|
private readonly ILogger<PostgresReachabilityCache> _logger;
|
||||||
private readonly TimeProvider _timeProvider;
|
private readonly TimeProvider _timeProvider;
|
||||||
|
|
||||||
@@ -28,8 +28,16 @@ public sealed class PostgresReachabilityCache : IReachabilityCache
|
|||||||
string connectionString,
|
string connectionString,
|
||||||
ILogger<PostgresReachabilityCache> logger,
|
ILogger<PostgresReachabilityCache> logger,
|
||||||
TimeProvider? timeProvider = null)
|
TimeProvider? timeProvider = null)
|
||||||
|
: this(new Services.PostgresReachabilityDataSourceProvider(connectionString), logger, timeProvider)
|
||||||
{
|
{
|
||||||
_connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
|
}
|
||||||
|
|
||||||
|
internal PostgresReachabilityCache(
|
||||||
|
Services.PostgresReachabilityDataSourceProvider dataSourceProvider,
|
||||||
|
ILogger<PostgresReachabilityCache> logger,
|
||||||
|
TimeProvider? timeProvider = null)
|
||||||
|
{
|
||||||
|
_dataSourceProvider = dataSourceProvider ?? throw new ArgumentNullException(nameof(dataSourceProvider));
|
||||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||||
}
|
}
|
||||||
@@ -40,8 +48,7 @@ public sealed class PostgresReachabilityCache : IReachabilityCache
|
|||||||
string graphHash,
|
string graphHash,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
await using var conn = new NpgsqlConnection(_connectionString);
|
await using var conn = await OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||||
await conn.OpenAsync(cancellationToken);
|
|
||||||
|
|
||||||
// Get cache entry
|
// Get cache entry
|
||||||
const string entrySql = """
|
const string entrySql = """
|
||||||
@@ -120,8 +127,7 @@ public sealed class PostgresReachabilityCache : IReachabilityCache
|
|||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(entry);
|
ArgumentNullException.ThrowIfNull(entry);
|
||||||
|
|
||||||
await using var conn = new NpgsqlConnection(_connectionString);
|
await using var conn = await OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||||
await conn.OpenAsync(cancellationToken);
|
|
||||||
await using var tx = await conn.BeginTransactionAsync(cancellationToken);
|
await using var tx = await conn.BeginTransactionAsync(cancellationToken);
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -202,8 +208,7 @@ public sealed class PostgresReachabilityCache : IReachabilityCache
|
|||||||
string sinkMethodKey,
|
string sinkMethodKey,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
await using var conn = new NpgsqlConnection(_connectionString);
|
await using var conn = await OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||||
await conn.OpenAsync(cancellationToken);
|
|
||||||
|
|
||||||
const string sql = """
|
const string sql = """
|
||||||
SELECT p.is_reachable, p.path_length, p.confidence, p.computed_at
|
SELECT p.is_reachable, p.path_length, p.confidence, p.computed_at
|
||||||
@@ -246,8 +251,7 @@ public sealed class PostgresReachabilityCache : IReachabilityCache
|
|||||||
IEnumerable<string> affectedMethodKeys,
|
IEnumerable<string> affectedMethodKeys,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
await using var conn = new NpgsqlConnection(_connectionString);
|
await using var conn = await OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||||
await conn.OpenAsync(cancellationToken);
|
|
||||||
|
|
||||||
// For now, invalidate entire cache for service
|
// For now, invalidate entire cache for service
|
||||||
// More granular invalidation would require additional indices
|
// More granular invalidation would require additional indices
|
||||||
@@ -283,8 +287,7 @@ public sealed class PostgresReachabilityCache : IReachabilityCache
|
|||||||
string serviceId,
|
string serviceId,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
await using var conn = new NpgsqlConnection(_connectionString);
|
await using var conn = await OpenConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||||
await conn.OpenAsync(cancellationToken);
|
|
||||||
|
|
||||||
const string sql = """
|
const string sql = """
|
||||||
SELECT total_hits, total_misses, full_recomputes, incremental_computes,
|
SELECT total_hits, total_misses, full_recomputes, incremental_computes,
|
||||||
@@ -392,4 +395,7 @@ public sealed class PostgresReachabilityCache : IReachabilityCache
|
|||||||
|
|
||||||
await cmd.ExecuteNonQueryAsync(cancellationToken);
|
await cmd.ExecuteNonQueryAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ValueTask<NpgsqlConnection> OpenConnectionAsync(CancellationToken cancellationToken)
|
||||||
|
=> _dataSourceProvider.OpenConnectionAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,10 +34,12 @@ public static class ServiceCollectionExtensions
|
|||||||
ArgumentNullException.ThrowIfNull(services);
|
ArgumentNullException.ThrowIfNull(services);
|
||||||
ArgumentException.ThrowIfNullOrWhiteSpace(connectionString);
|
ArgumentException.ThrowIfNullOrWhiteSpace(connectionString);
|
||||||
|
|
||||||
|
services.TryAddSingleton(_ => new PostgresReachabilityDataSourceProvider(connectionString));
|
||||||
|
|
||||||
// CVE-Symbol Mapping Service
|
// CVE-Symbol Mapping Service
|
||||||
services.TryAddSingleton<ICveSymbolMappingService>(sp =>
|
services.TryAddSingleton<ICveSymbolMappingService>(sp =>
|
||||||
new PostgresCveSymbolMappingRepository(
|
new PostgresCveSymbolMappingRepository(
|
||||||
connectionString,
|
sp.GetRequiredService<PostgresReachabilityDataSourceProvider>(),
|
||||||
sp.GetRequiredService<Microsoft.Extensions.Logging.ILogger<PostgresCveSymbolMappingRepository>>()));
|
sp.GetRequiredService<Microsoft.Extensions.Logging.ILogger<PostgresCveSymbolMappingRepository>>()));
|
||||||
|
|
||||||
// Stack Evaluator (already exists, ensure registered)
|
// Stack Evaluator (already exists, ensure registered)
|
||||||
|
|||||||
@@ -15,14 +15,21 @@ namespace StellaOps.Scanner.Reachability.Services;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class PostgresCveSymbolMappingRepository : ICveSymbolMappingService
|
public sealed class PostgresCveSymbolMappingRepository : ICveSymbolMappingService
|
||||||
{
|
{
|
||||||
private readonly string _connectionString;
|
private readonly PostgresReachabilityDataSourceProvider _dataSourceProvider;
|
||||||
private readonly ILogger<PostgresCveSymbolMappingRepository> _logger;
|
private readonly ILogger<PostgresCveSymbolMappingRepository> _logger;
|
||||||
|
|
||||||
public PostgresCveSymbolMappingRepository(
|
public PostgresCveSymbolMappingRepository(
|
||||||
string connectionString,
|
string connectionString,
|
||||||
ILogger<PostgresCveSymbolMappingRepository> logger)
|
ILogger<PostgresCveSymbolMappingRepository> logger)
|
||||||
|
: this(new PostgresReachabilityDataSourceProvider(connectionString), logger)
|
||||||
{
|
{
|
||||||
_connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
|
}
|
||||||
|
|
||||||
|
internal PostgresCveSymbolMappingRepository(
|
||||||
|
PostgresReachabilityDataSourceProvider dataSourceProvider,
|
||||||
|
ILogger<PostgresCveSymbolMappingRepository> logger)
|
||||||
|
{
|
||||||
|
_dataSourceProvider = dataSourceProvider ?? throw new ArgumentNullException(nameof(dataSourceProvider));
|
||||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,9 +213,7 @@ public sealed class PostgresCveSymbolMappingRepository : ICveSymbolMappingServic
|
|||||||
|
|
||||||
private async Task<NpgsqlConnection> OpenConnectionAsync(CancellationToken ct)
|
private async Task<NpgsqlConnection> OpenConnectionAsync(CancellationToken ct)
|
||||||
{
|
{
|
||||||
var conn = new NpgsqlConnection(_connectionString);
|
return await _dataSourceProvider.OpenConnectionAsync(ct).ConfigureAwait(false);
|
||||||
await conn.OpenAsync(ct);
|
|
||||||
return conn;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CveSinkMapping MapFromReader(NpgsqlDataReader reader)
|
private static CveSinkMapping MapFromReader(NpgsqlDataReader reader)
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
using Npgsql;
|
||||||
|
|
||||||
|
namespace StellaOps.Scanner.Reachability.Services;
|
||||||
|
|
||||||
|
internal sealed class PostgresReachabilityDataSourceProvider : IAsyncDisposable
|
||||||
|
{
|
||||||
|
private readonly Lazy<NpgsqlDataSource> _dataSource;
|
||||||
|
|
||||||
|
public PostgresReachabilityDataSourceProvider(string connectionString)
|
||||||
|
{
|
||||||
|
ArgumentException.ThrowIfNullOrWhiteSpace(connectionString);
|
||||||
|
_dataSource = new Lazy<NpgsqlDataSource>(
|
||||||
|
() => CreateDataSource(connectionString),
|
||||||
|
isThreadSafe: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueTask<NpgsqlConnection> OpenConnectionAsync(CancellationToken cancellationToken)
|
||||||
|
=> _dataSource.Value.OpenConnectionAsync(cancellationToken);
|
||||||
|
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
if (_dataSource.IsValueCreated)
|
||||||
|
{
|
||||||
|
await _dataSource.Value.DisposeAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static NpgsqlDataSource CreateDataSource(string connectionString)
|
||||||
|
{
|
||||||
|
var connectionStringBuilder = new NpgsqlConnectionStringBuilder(connectionString)
|
||||||
|
{
|
||||||
|
ApplicationName = "stellaops-scanner-reachability",
|
||||||
|
};
|
||||||
|
|
||||||
|
return new NpgsqlDataSourceBuilder(connectionStringBuilder.ConnectionString).Build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ Source of truth: `docs/implplan/SPRINT_20260130_002_Tools_csproj_remediation_sol
|
|||||||
|
|
||||||
| Task ID | Status | Notes |
|
| Task ID | Status | Notes |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
|
| SPRINT_20260405_011-XPORT | DONE | `docs/implplan/SPRINT_20260405_011___Libraries_transport_pooling_and_attribution_hardening.md`: added a named reachability datasource provider and routed PostgreSQL-backed reachability services through it. |
|
||||||
| QA-SCANNER-VERIFY-005 | DONE | SPRINT_20260212_002 run-001: `api-gateway-boundary-extractor` passed Tier 0/1/2 and moved to `docs/features/checked/scanner/`. |
|
| QA-SCANNER-VERIFY-005 | DONE | SPRINT_20260212_002 run-001: `api-gateway-boundary-extractor` passed Tier 0/1/2 and moved to `docs/features/checked/scanner/`. |
|
||||||
| REMED-05 | TODO | Remediation checklist: docs/implplan/audits/csproj-standards/remediation/checklists/src/Scanner/__Libraries/StellaOps.Scanner.Reachability/StellaOps.Scanner.Reachability.md. |
|
| REMED-05 | TODO | Remediation checklist: docs/implplan/audits/csproj-standards/remediation/checklists/src/Scanner/__Libraries/StellaOps.Scanner.Reachability/StellaOps.Scanner.Reachability.md. |
|
||||||
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
|
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
|
||||||
|
|||||||
@@ -236,6 +236,7 @@ public static class VexLensServiceCollectionExtensions
|
|||||||
{
|
{
|
||||||
var connStringBuilder = new NpgsqlConnectionStringBuilder(options.ConnectionString)
|
var connStringBuilder = new NpgsqlConnectionStringBuilder(options.ConnectionString)
|
||||||
{
|
{
|
||||||
|
ApplicationName = "stellaops-vexlens",
|
||||||
CommandTimeout = options.CommandTimeoutSeconds
|
CommandTimeout = options.CommandTimeoutSeconds
|
||||||
};
|
};
|
||||||
var builder = new NpgsqlDataSourceBuilder(connStringBuilder.ConnectionString);
|
var builder = new NpgsqlDataSourceBuilder(connStringBuilder.ConnectionString);
|
||||||
@@ -274,6 +275,7 @@ public static class VexLensServiceCollectionExtensions
|
|||||||
{
|
{
|
||||||
var connStringBuilder = new NpgsqlConnectionStringBuilder(options.ConnectionString)
|
var connStringBuilder = new NpgsqlConnectionStringBuilder(options.ConnectionString)
|
||||||
{
|
{
|
||||||
|
ApplicationName = "stellaops-vexlens",
|
||||||
CommandTimeout = options.CommandTimeoutSeconds
|
CommandTimeout = options.CommandTimeoutSeconds
|
||||||
};
|
};
|
||||||
var builder = new NpgsqlDataSourceBuilder(connStringBuilder.ConnectionString);
|
var builder = new NpgsqlDataSourceBuilder(connStringBuilder.ConnectionString);
|
||||||
|
|||||||
@@ -4,5 +4,6 @@ Source of truth: `docs/implplan/SPRINT_20260130_002_Tools_csproj_remediation_sol
|
|||||||
|
|
||||||
| Task ID | Status | Notes |
|
| Task ID | Status | Notes |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
|
| SPRINT_20260405_011-XPORT | DONE | `docs/implplan/SPRINT_20260405_011___Libraries_transport_pooling_and_attribution_hardening.md`: named VexLens PostgreSQL consensus-store data sources. |
|
||||||
| REMED-05 | TODO | Remediation checklist: docs/implplan/audits/csproj-standards/remediation/checklists/src/VexLens/StellaOps.VexLens/StellaOps.VexLens.md. |
|
| REMED-05 | TODO | Remediation checklist: docs/implplan/audits/csproj-standards/remediation/checklists/src/VexLens/StellaOps.VexLens/StellaOps.VexLens.md. |
|
||||||
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
|
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ public static class ServiceCollectionExtensions
|
|||||||
"Eventing:ConnectionString must be configured when Eventing:UseInMemoryStore is false.");
|
"Eventing:ConnectionString must be configured when Eventing:UseInMemoryStore is false.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return NpgsqlDataSource.Create(configuredOptions.ConnectionString);
|
return CreateNamedDataSource(configuredOptions.ConnectionString, configuredOptions.ServiceName);
|
||||||
});
|
});
|
||||||
|
|
||||||
services.TryAddSingleton<ITimelineEventStore, PostgresTimelineEventStore>();
|
services.TryAddSingleton<ITimelineEventStore, PostgresTimelineEventStore>();
|
||||||
@@ -104,7 +104,7 @@ public static class ServiceCollectionExtensions
|
|||||||
TryAddHybridLogicalClock(services, serviceName);
|
TryAddHybridLogicalClock(services, serviceName);
|
||||||
|
|
||||||
// Register NpgsqlDataSource
|
// Register NpgsqlDataSource
|
||||||
services.TryAddSingleton(_ => NpgsqlDataSource.Create(connectionString));
|
services.TryAddSingleton(_ => CreateNamedDataSource(connectionString, serviceName));
|
||||||
|
|
||||||
services.TryAddSingleton<ITimelineEventStore, PostgresTimelineEventStore>();
|
services.TryAddSingleton<ITimelineEventStore, PostgresTimelineEventStore>();
|
||||||
services.TryAddSingleton<ITimelineEventEmitter, TimelineEventEmitter>();
|
services.TryAddSingleton<ITimelineEventEmitter, TimelineEventEmitter>();
|
||||||
@@ -186,4 +186,15 @@ public static class ServiceCollectionExtensions
|
|||||||
|
|
||||||
return nodeId.Trim().Replace(' ', '-').ToLowerInvariant();
|
return nodeId.Trim().Replace(' ', '-').ToLowerInvariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static NpgsqlDataSource CreateNamedDataSource(string connectionString, string? serviceName)
|
||||||
|
{
|
||||||
|
var normalizedServiceName = ResolveNodeId(serviceName);
|
||||||
|
var connectionStringBuilder = new NpgsqlConnectionStringBuilder(connectionString)
|
||||||
|
{
|
||||||
|
ApplicationName = $"stellaops-eventing-{normalizedServiceName}",
|
||||||
|
};
|
||||||
|
|
||||||
|
return new NpgsqlDataSourceBuilder(connectionStringBuilder.ConnectionString).Build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
## Determinism & Guardrails
|
## Determinism & Guardrails
|
||||||
- Target runtime: .NET 10, Npgsql 9.x; keep options defaults deterministic (UTC timezone, statement timeout, stable pagination ordering).
|
- Target runtime: .NET 10, Npgsql 9.x; keep options defaults deterministic (UTC timezone, statement timeout, stable pagination ordering).
|
||||||
- Tenant context must be set via `set_config('app.current_tenant', ...)` on every connection before use; never bypass DataSourceBase.
|
- Tenant context must be set via `set_config('app.current_tenant', ...)` on every connection before use; never bypass DataSourceBase.
|
||||||
|
- Runtime `NpgsqlDataSource` creation must carry a stable `application_name`; prefer `PostgresConnectionStringPolicy` or an explicit `NpgsqlConnectionStringBuilder.ApplicationName`.
|
||||||
|
- Anonymous `NpgsqlDataSource.Create(...)` is forbidden in steady-state runtime code. Allow exceptions only for tests, migrations, CLI/setup paths, or sprint-documented one-shot diagnostics.
|
||||||
- Migrations ship as embedded resources; MigrationRunner uses SHA256 checksums and `RunFromAssemblyAsync`???do not execute ad-hoc SQL outside tracked migrations.
|
- Migrations ship as embedded resources; MigrationRunner uses SHA256 checksums and `RunFromAssemblyAsync`???do not execute ad-hoc SQL outside tracked migrations.
|
||||||
- Respect air-gap posture: no external downloads at runtime; pin Postgres/Testcontainers images (`postgres:16-alpine` or later) in tests.
|
- Respect air-gap posture: no external downloads at runtime; pin Postgres/Testcontainers images (`postgres:16-alpine` or later) in tests.
|
||||||
|
|
||||||
@@ -30,4 +32,3 @@
|
|||||||
## Handoff Notes
|
## Handoff Notes
|
||||||
- Align configuration defaults with the provisioning values under `devops/database/postgres` (ports, pool sizes, SSL/TLS).
|
- Align configuration defaults with the provisioning values under `devops/database/postgres` (ports, pool sizes, SSL/TLS).
|
||||||
- Update this AGENTS file whenever connection/session rules or provisioning defaults change; record updates in the sprint Execution Log.
|
- Update this AGENTS file whenever connection/session rules or provisioning defaults change; record updates in the sprint Execution Log.
|
||||||
|
|
||||||
|
|||||||
@@ -241,15 +241,8 @@ public abstract class DataSourceBase : IAsyncDisposable
|
|||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string BuildConnectionString(PostgresOptions options)
|
private string BuildConnectionString(PostgresOptions options)
|
||||||
{
|
=> PostgresConnectionStringPolicy.Build(
|
||||||
var builder = new NpgsqlConnectionStringBuilder(options.ConnectionString)
|
options,
|
||||||
{
|
PostgresConnectionStringPolicy.BuildDefaultApplicationName(ModuleName));
|
||||||
Pooling = options.Pooling,
|
|
||||||
MaxPoolSize = options.MaxPoolSize,
|
|
||||||
MinPoolSize = options.MinPoolSize
|
|
||||||
};
|
|
||||||
|
|
||||||
return builder.ToString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
using Npgsql;
|
||||||
|
using StellaOps.Infrastructure.Postgres.Options;
|
||||||
|
|
||||||
|
namespace StellaOps.Infrastructure.Postgres.Connections;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies StellaOps runtime defaults to PostgreSQL connection strings.
|
||||||
|
/// </summary>
|
||||||
|
public static class PostgresConnectionStringPolicy
|
||||||
|
{
|
||||||
|
public static string Build(PostgresOptions options, string defaultApplicationName)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(options);
|
||||||
|
ArgumentException.ThrowIfNullOrWhiteSpace(defaultApplicationName);
|
||||||
|
|
||||||
|
return Build(
|
||||||
|
options.ConnectionString,
|
||||||
|
string.IsNullOrWhiteSpace(options.ApplicationName)
|
||||||
|
? defaultApplicationName.Trim()
|
||||||
|
: options.ApplicationName.Trim(),
|
||||||
|
options.Pooling,
|
||||||
|
options.MinPoolSize,
|
||||||
|
options.MaxPoolSize,
|
||||||
|
options.ConnectionIdleLifetimeSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Build(
|
||||||
|
string connectionString,
|
||||||
|
string applicationName,
|
||||||
|
bool pooling = true,
|
||||||
|
int minPoolSize = 1,
|
||||||
|
int maxPoolSize = 100,
|
||||||
|
int? connectionIdleLifetimeSeconds = null)
|
||||||
|
{
|
||||||
|
ArgumentException.ThrowIfNullOrWhiteSpace(connectionString);
|
||||||
|
ArgumentException.ThrowIfNullOrWhiteSpace(applicationName);
|
||||||
|
|
||||||
|
var normalizedMinPoolSize = Math.Max(minPoolSize, 0);
|
||||||
|
var normalizedMaxPoolSize = Math.Max(maxPoolSize, normalizedMinPoolSize);
|
||||||
|
|
||||||
|
var builder = new NpgsqlConnectionStringBuilder(connectionString)
|
||||||
|
{
|
||||||
|
ApplicationName = applicationName.Trim(),
|
||||||
|
Pooling = pooling,
|
||||||
|
MinPoolSize = normalizedMinPoolSize,
|
||||||
|
MaxPoolSize = normalizedMaxPoolSize,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (connectionIdleLifetimeSeconds.HasValue && connectionIdleLifetimeSeconds.Value > 0)
|
||||||
|
{
|
||||||
|
builder.ConnectionIdleLifetime = connectionIdleLifetimeSeconds.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string BuildDefaultApplicationName(string moduleName)
|
||||||
|
{
|
||||||
|
ArgumentException.ThrowIfNullOrWhiteSpace(moduleName);
|
||||||
|
|
||||||
|
return $"stellaops-{ToKebabCase(moduleName.Trim())}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ToKebabCase(string value)
|
||||||
|
{
|
||||||
|
var chars = new List<char>(value.Length + 8);
|
||||||
|
for (var i = 0; i < value.Length; i++)
|
||||||
|
{
|
||||||
|
var current = value[i];
|
||||||
|
if (char.IsWhiteSpace(current) || current is '_' or '-')
|
||||||
|
{
|
||||||
|
if (chars.Count > 0 && chars[^1] != '-')
|
||||||
|
{
|
||||||
|
chars.Add('-');
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (char.IsUpper(current))
|
||||||
|
{
|
||||||
|
var hasPrevious = chars.Count > 0;
|
||||||
|
var previous = hasPrevious ? value[i - 1] : '\0';
|
||||||
|
var hasNext = i + 1 < value.Length;
|
||||||
|
var next = hasNext ? value[i + 1] : '\0';
|
||||||
|
|
||||||
|
if (hasPrevious
|
||||||
|
&& chars[^1] != '-'
|
||||||
|
&& (!char.IsUpper(previous) || (hasNext && char.IsLower(next))))
|
||||||
|
{
|
||||||
|
chars.Add('-');
|
||||||
|
}
|
||||||
|
|
||||||
|
chars.Add(char.ToLowerInvariant(current));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
chars.Add(char.ToLowerInvariant(current));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new string(chars.ToArray()).Trim('-');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,11 @@ public sealed class PostgresOptions
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int MaxPoolSize { get; set; } = 100;
|
public int MaxPoolSize { get; set; } = 100;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stable PostgreSQL application name used for runtime attribution.
|
||||||
|
/// </summary>
|
||||||
|
public string? ApplicationName { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Minimum number of connections in the pool. Default is 1.
|
/// Minimum number of connections in the pool. Default is 1.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229
|
|||||||
|
|
||||||
| Task ID | Status | Notes |
|
| Task ID | Status | Notes |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
|
| SPRINT_20260405_011-XPORT-STD | DONE | `docs/implplan/SPRINT_20260405_011___Libraries_transport_pooling_and_attribution_hardening.md`: shared transport hardening, PostgreSQL application-name policy, runtime caller remediation, and static guardrails. |
|
||||||
| AUDIT-0089-M | DONE | Revalidated 2026-01-08; maintainability audit for Infrastructure.Postgres. |
|
| AUDIT-0089-M | DONE | Revalidated 2026-01-08; maintainability audit for Infrastructure.Postgres. |
|
||||||
| AUDIT-0089-T | DONE | Revalidated 2026-01-08; test coverage audit for Infrastructure.Postgres. |
|
| AUDIT-0089-T | DONE | Revalidated 2026-01-08; test coverage audit for Infrastructure.Postgres. |
|
||||||
| AUDIT-0089-A | TODO | Pending approval (non-test project; revalidated 2026-01-08). |
|
| AUDIT-0089-A | TODO | Pending approval (non-test project; revalidated 2026-01-08). |
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
using FluentAssertions;
|
||||||
|
using Npgsql;
|
||||||
|
using StellaOps.Infrastructure.Postgres.Connections;
|
||||||
|
using StellaOps.Infrastructure.Postgres.Options;
|
||||||
|
using StellaOps.TestKit;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace StellaOps.Infrastructure.Postgres.Tests;
|
||||||
|
|
||||||
|
[Trait("Category", TestCategories.Unit)]
|
||||||
|
public sealed class PostgresConnectionStringPolicyTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Build_applies_runtime_attribution_and_pooling_policy()
|
||||||
|
{
|
||||||
|
var options = new PostgresOptions
|
||||||
|
{
|
||||||
|
ConnectionString = "Host=localhost;Database=stellaops;Username=stellaops;Password=stellaops",
|
||||||
|
ApplicationName = "custom-ledger-app",
|
||||||
|
Pooling = false,
|
||||||
|
MinPoolSize = 3,
|
||||||
|
MaxPoolSize = 12,
|
||||||
|
ConnectionIdleLifetimeSeconds = 900,
|
||||||
|
};
|
||||||
|
|
||||||
|
var connectionString = PostgresConnectionStringPolicy.Build(options, "stellaops-findings-ledger");
|
||||||
|
var builder = new NpgsqlConnectionStringBuilder(connectionString);
|
||||||
|
|
||||||
|
builder.ApplicationName.Should().Be("custom-ledger-app");
|
||||||
|
builder.Pooling.Should().BeFalse();
|
||||||
|
builder.MinPoolSize.Should().Be(3);
|
||||||
|
builder.MaxPoolSize.Should().Be(12);
|
||||||
|
builder.ConnectionIdleLifetime.Should().Be(900);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Build_uses_default_application_name_when_none_is_configured()
|
||||||
|
{
|
||||||
|
var options = new PostgresOptions
|
||||||
|
{
|
||||||
|
ConnectionString = "Host=localhost;Database=stellaops;Username=stellaops;Password=stellaops",
|
||||||
|
MinPoolSize = 0,
|
||||||
|
MaxPoolSize = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
var connectionString = PostgresConnectionStringPolicy.Build(options, "stellaops-policy");
|
||||||
|
var builder = new NpgsqlConnectionStringBuilder(connectionString);
|
||||||
|
|
||||||
|
builder.ApplicationName.Should().Be("stellaops-policy");
|
||||||
|
builder.MinPoolSize.Should().Be(0);
|
||||||
|
builder.MaxPoolSize.Should().Be(1);
|
||||||
|
builder.ConnectionIdleLifetime.Should().Be(300);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("Policy", "stellaops-policy")]
|
||||||
|
[InlineData("PacksRegistry", "stellaops-packs-registry")]
|
||||||
|
[InlineData("IssuerDirectory", "stellaops-issuer-directory")]
|
||||||
|
[InlineData("ReachGraph", "stellaops-reach-graph")]
|
||||||
|
public void BuildDefaultApplicationName_normalizes_module_names(string moduleName, string expected)
|
||||||
|
{
|
||||||
|
var result = PostgresConnectionStringPolicy.BuildDefaultApplicationName(moduleName);
|
||||||
|
|
||||||
|
result.Should().Be(expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user