fix(review): full code-review remediation — 5 High + Medium/Low across 16 modules

Remediation from the full per-module code review at 4307c381 (findings recorded
separately in code-reviews/).

Highs fixed:
- DeploymentManager-025/SiteRuntime-031: stop broadcasting notification lists + SMTP
  configs (incl. credentials) to sites; site purges already-persisted rows on apply
  (enforces the central-only delivery design; clears plaintext SMTP creds at rest).
- DataConnectionLayer-023: guard the native-alarm subscribe path against the
  mid-flight-unsubscribe adapter-feed leak (mirrors the DCL-021 tag-path fix).
- SiteEventLogging-024: normalize From/To query bounds to UTC (the -016 fix the
  audit trail claimed but never committed).
- KpiHistory-001: add an in-flight guard to the recorder sample tick.
- ScriptAnalysis-001: harden the trust analyzer's TPA-absent fallback (resolve
  forbidden anchors in the minimal reference set; warn on degraded mode) — anchors
  added to validation references only, never the compile gate.
(InboundAPI-026 left to the feat/ipsen-movein effort per owner decision.)

Medium/Low: DM-026 deterministic deploy-status tiebreaker; SR-027/028/029/030
native-alarm leak/phantom-active/delete-during-redeploy fixes; AL-013/014/016;
TE-024 (folder-mutation audit rows now persisted)/025; SF-025 gauge-provider
clear-on-stop; ESG-025/026; SEC-023/024/025; SCA-007/008/009; plus doc/test
accuracy COM-023/024, HOST-025/026, HM-024/025, NS-027/028.

Full-solution build 0 warnings; ~3560 tests across 18 touched suites green.
This commit is contained in:
Joseph Doherty
2026-06-20 17:55:12 -04:00
parent 4307c38117
commit fd618cf1dc
52 changed files with 2239 additions and 313 deletions
@@ -370,7 +370,7 @@ public class DatabaseGatewayTests
[MemberData(nameof(TransientNonSqlOutages))]
public async Task CachedWrite_NonSqlOutage_ClassifiedTransient_BuffersNotCrash(Exception outage)
{
// [1] A live outage that is NOT a SqlException must be classified TRANSIENT
// [3] A live outage that is NOT a SqlException must be classified TRANSIENT
// (buffered for retry), NOT escape unclassified to crash the script actor,
// and NOT be returned as a permanent Failed result.
var conn = new DatabaseConnectionDefinition("testDb", "Server=localhost;Database=test")
@@ -401,7 +401,7 @@ public class DatabaseGatewayTests
[Fact]
public async Task CachedWrite_CancellationRequested_PropagatesOperationCanceled_NotReclassified()
{
// [2] OperationCanceledException raised while the caller's token is
// [1] OperationCanceledException raised while the caller's token is
// cancelled must propagate UNCHANGED — never reclassified as a transient
// DB error and never buffered. Mirrors the HTTP path's first catch:
// `catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) throw;`
@@ -449,7 +449,7 @@ public class DatabaseGatewayTests
[Fact]
public async Task DeliverBuffered_NonSqlOutage_RethrowsAsTransient_SoEngineRetries()
{
// [1] on the RETRY path: a non-SqlException outage during delivery must be
// [3] on the RETRY path: a non-SqlException outage during delivery must be
// classified transient and propagate (as TransientDatabaseException) so
// the S&F engine schedules another retry — it must NOT crash/park.
var conn = new DatabaseConnectionDefinition("testDb", "Server=localhost;Database=test") { Id = 1 };
@@ -473,6 +473,71 @@ public class DatabaseGatewayTests
() => gateway.DeliverBufferedAsync(message));
}
// ── ExternalSystemGateway-025: a caller-token cancel that surfaces from the SQL
// driver as a SqlException (mid-flight cancel) must propagate as
// OperationCanceledException — never reclassified as a permanent DB error.
// The fix re-checks the caller's token at the TOP of `catch (SqlException)`
// via cancellationToken.ThrowIfCancellationRequested(), so the cancel wins
// regardless of the driver's exception shape (version-independent). ──
[Fact]
public async Task CachedWrite_CancellationSurfacingAsSqlException_PropagatesCanceled_NotReclassifiedPermanent()
{
var conn = new DatabaseConnectionDefinition("testDb", "Server=localhost;Database=test") { Id = 1 };
StubConnection(conn);
var (sf, connStr, keepAlive) = NewStoreAndForward();
using var _ = keepAlive;
using var cts = new CancellationTokenSource();
cts.Cancel();
// The SQL driver raises a SqlException on a mid-flight cancel (error
// number 0, not in the transient set — pre-fix it was reclassified as a
// PERMANENT DB error). The raw seam throws it through the production
// ExecuteWriteAsync classification so the new ThrowIfCancellationRequested
// guard at the top of `catch (SqlException)` runs end-to-end.
var sqlException = FabricateSqlException("Operation cancelled by user.", number: 0);
var gateway = new RawExecuteStubGateway(_repository, sf, onRunSql: () => throw sqlException);
await Assert.ThrowsAsync<OperationCanceledException>(
() => gateway.CachedWriteAsync("testDb", "INSERT INTO t VALUES (1)", cancellationToken: cts.Token));
// The cancel won — it must NOT have been classified as transient (buffered)
// nor returned as a permanent Failed result.
Assert.Equal(0, ReadBufferDepth(connStr));
}
/// <summary>
/// Fabricates a <see cref="Microsoft.Data.SqlClient.SqlException"/> with a given
/// message and error number via the driver's internal <c>CreateException</c>
/// factory (the type has no public constructor). Used only to drive the
/// <c>catch (SqlException)</c> branch of <c>ExecuteWriteAsync</c> in tests.
/// </summary>
private static Microsoft.Data.SqlClient.SqlException FabricateSqlException(string message, int number)
{
var errorCtor = typeof(Microsoft.Data.SqlClient.SqlError).GetConstructors(
System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)
.First(c => c.GetParameters().Length == 8
&& c.GetParameters()[7].ParameterType == typeof(Exception));
var sqlError = (Microsoft.Data.SqlClient.SqlError)errorCtor.Invoke(
new object?[] { number, (byte)0, (byte)0, "server", message, "procedure", 0, null });
var collectionCtor = typeof(Microsoft.Data.SqlClient.SqlErrorCollection).GetConstructors(
System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).First();
var collection = (Microsoft.Data.SqlClient.SqlErrorCollection)collectionCtor.Invoke(Array.Empty<object?>());
typeof(Microsoft.Data.SqlClient.SqlErrorCollection)
.GetMethod("Add", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)!
.Invoke(collection, new object?[] { sqlError });
var createException = typeof(Microsoft.Data.SqlClient.SqlException).GetMethod(
"CreateException",
System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic,
new[] { typeof(Microsoft.Data.SqlClient.SqlErrorCollection), typeof(string) })!;
return (Microsoft.Data.SqlClient.SqlException)createException.Invoke(
null, new object?[] { collection, "6.0.0" })!;
}
/// <summary>
/// Reads the current buffered-message count off the S&amp;F SQLite DB by
/// counting <c>sf_messages</c> rows (the engine's persistence table).