fix(configuration): resolve Medium code-review findings (Configuration-002, -003, -006, -009)

Configuration-002: sp_PublishGeneration is transaction-nesting aware
(BEGIN TRANSACTION vs SAVE TRANSACTION on @@TRANCOUNT) so a caller's outer
transaction survives a publish failure; sp_ValidateDraft wrapped in TRY/CATCH.
Configuration-003: ValidatePathLength uses the cluster's actual Enterprise/Site
lengths when available, falling back to the conservative approximation.
Configuration-006: ResilientConfigReader treats a command-timeout
TaskCanceledException as a fault (not caller cancellation) and falls back.
Configuration-009: removed the checked-in plaintext sa connection string;
CreateDbContext now requires OTOPCUA_CONFIG_CONNECTION.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-05-22 08:13:27 -04:00
parent 7e54e1e4a0
commit c126fc7a7d
9 changed files with 274 additions and 31 deletions

View File

@@ -18,10 +18,16 @@ public sealed class DraftValidationService(OtOpcUaConfigDbContext db)
.FirstOrDefaultAsync(g => g.GenerationId == draftId, ct)
?? throw new InvalidOperationException($"Draft {draftId} not found");
// Load the cluster row so path-length validation uses actual Enterprise/Site lengths.
var cluster = await db.ServerClusters.AsNoTracking()
.FirstOrDefaultAsync(c => c.ClusterId == draft.ClusterId, ct);
var snapshot = new DraftSnapshot
{
GenerationId = draft.GenerationId,
ClusterId = draft.ClusterId,
Enterprise = cluster?.Enterprise,
Site = cluster?.Site,
Namespaces = await db.Namespaces.AsNoTracking().Where(n => n.GenerationId == draftId).ToListAsync(ct),
DriverInstances = await db.DriverInstances.AsNoTracking().Where(d => d.GenerationId == draftId).ToListAsync(ct),
Devices = await db.Devices.AsNoTracking().Where(d => d.GenerationId == draftId).ToListAsync(ct),