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:
@@ -12,8 +12,16 @@ namespace ZB.MOM.WW.ScadaBridge.DeploymentManager;
|
||||
|
||||
/// <summary>
|
||||
/// WP-7: System-wide artifact deployment.
|
||||
/// Broadcasts artifacts (shared scripts, external systems, notification lists, DB connections,
|
||||
/// data connections, and SMTP configurations) to all sites with per-site tracking.
|
||||
/// Broadcasts artifacts (shared scripts, external systems, DB connections, and
|
||||
/// data connections) to all sites with per-site tracking.
|
||||
///
|
||||
/// Notification lists and SMTP configuration are deliberately NOT shipped to
|
||||
/// sites: notification delivery is central-only (sites store-and-forward to
|
||||
/// central and never talk to SMTP), so no notification artifact or SMTP
|
||||
/// credential is ever distributed to a site. The
|
||||
/// <see cref="DeployArtifactsCommand"/> still carries the
|
||||
/// <c>NotificationLists</c>/<c>SmtpConfigurations</c> fields for additive
|
||||
/// message-contract compatibility, but central never populates them.
|
||||
///
|
||||
/// - Successful sites are NOT rolled back on other failures.
|
||||
/// - Failed sites are retryable individually.
|
||||
@@ -26,7 +34,6 @@ public class ArtifactDeploymentService
|
||||
private readonly IDeploymentManagerRepository _deploymentRepo;
|
||||
private readonly ITemplateEngineRepository _templateRepo;
|
||||
private readonly IExternalSystemRepository _externalSystemRepo;
|
||||
private readonly INotificationRepository _notificationRepo;
|
||||
private readonly CommunicationService _communicationService;
|
||||
private readonly IAuditService _auditService;
|
||||
private readonly DeploymentManagerOptions _options;
|
||||
@@ -39,7 +46,12 @@ public class ArtifactDeploymentService
|
||||
/// <param name="deploymentRepo">Repository for deployment records.</param>
|
||||
/// <param name="templateRepo">Repository for templates.</param>
|
||||
/// <param name="externalSystemRepo">Repository for external systems.</param>
|
||||
/// <param name="notificationRepo">Repository for notifications.</param>
|
||||
/// <param name="notificationRepo">
|
||||
/// DeploymentManager-025: retained on the signature for DI/source compatibility but
|
||||
/// intentionally NOT consumed. Notification lists and SMTP configuration are
|
||||
/// central-only and are never shipped to sites, so the artifact path must not read
|
||||
/// the notification repository at all.
|
||||
/// </param>
|
||||
/// <param name="communicationService">Service for communicating with sites.</param>
|
||||
/// <param name="auditService">Service for audit logging.</param>
|
||||
/// <param name="options">Deployment manager options.</param>
|
||||
@@ -59,7 +71,9 @@ public class ArtifactDeploymentService
|
||||
_deploymentRepo = deploymentRepo;
|
||||
_templateRepo = templateRepo;
|
||||
_externalSystemRepo = externalSystemRepo;
|
||||
_notificationRepo = notificationRepo;
|
||||
// DeploymentManager-025: notificationRepo is deliberately not stored — notification
|
||||
// lists and SMTP configs are central-only and are never fetched for shipping to sites.
|
||||
_ = notificationRepo;
|
||||
_communicationService = communicationService;
|
||||
_auditService = auditService;
|
||||
_options = options.Value;
|
||||
@@ -98,15 +112,19 @@ public class ArtifactDeploymentService
|
||||
/// <summary>
|
||||
/// Builds a per-site <see cref="DeployArtifactsCommand"/> using a previously-fetched
|
||||
/// snapshot of the global artifact sets (shared scripts, external systems + methods,
|
||||
/// DB connections, notification lists, SMTP configurations). Only the per-site
|
||||
/// data-connection query runs here.
|
||||
/// DB connections). Only the per-site data-connection query runs here.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// DeploymentManager-023: separating the global fetch from the per-site build lets
|
||||
/// <see cref="DeployToAllSitesAsync"/> issue the global queries exactly once across
|
||||
/// the whole multi-site sweep, eliminating the N+1 re-query of shared scripts,
|
||||
/// external systems, methods, DB connections, notification lists, and SMTP
|
||||
/// configurations.
|
||||
/// external systems, methods, and DB connections.
|
||||
///
|
||||
/// DeploymentManager-025: the command's <c>NotificationLists</c> and
|
||||
/// <c>SmtpConfigurations</c> fields are always sent <c>null</c> — notification
|
||||
/// delivery is central-only and no notification artifact or SMTP credential is
|
||||
/// ever distributed to a site. The fields remain on the contract only for
|
||||
/// additive compatibility.
|
||||
/// </remarks>
|
||||
private async Task<DeployArtifactsCommand> BuildDeployArtifactsCommandAsync(
|
||||
int siteId,
|
||||
@@ -125,30 +143,34 @@ public class ArtifactDeploymentService
|
||||
globals.SharedScripts,
|
||||
globals.ExternalSystems,
|
||||
globals.DatabaseConnections,
|
||||
globals.NotificationLists,
|
||||
// DeploymentManager-025: notification lists are central-only — never shipped to sites.
|
||||
NotificationLists: null,
|
||||
dataConnectionArtifacts,
|
||||
globals.SmtpConfigurations,
|
||||
// DeploymentManager-025: SMTP config (incl. credentials) is central-only — never shipped to sites.
|
||||
SmtpConfigurations: null,
|
||||
DateTimeOffset.UtcNow);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches the system-wide artifact sets that are identical across every site —
|
||||
/// shared scripts, external systems (with their methods serialized in), database
|
||||
/// connections, notification lists, and SMTP configurations. Used by
|
||||
/// <see cref="DeployToAllSitesAsync"/> to pre-load once before the per-site loop.
|
||||
/// shared scripts, external systems (with their methods serialized in), and
|
||||
/// database connections. Used by <see cref="DeployToAllSitesAsync"/> to pre-load
|
||||
/// once before the per-site loop.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// DeploymentManager-023: the per-site artifact build path previously re-issued
|
||||
/// every one of these queries per site (≈ 5·N + M·N round trips for N sites
|
||||
/// every one of these queries per site (≈ N + M·N round trips for N sites
|
||||
/// and M external systems). Hoisting them here drops that to a single fetch.
|
||||
///
|
||||
/// DeploymentManager-025: notification lists and SMTP configurations are NOT
|
||||
/// fetched here. Notification delivery is central-only, so they are never
|
||||
/// shipped to sites — the artifact path must not even read them.
|
||||
/// </remarks>
|
||||
private async Task<GlobalArtifactSnapshot> FetchGlobalArtifactsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var sharedScripts = await _templateRepo.GetAllSharedScriptsAsync(cancellationToken);
|
||||
var externalSystems = await _externalSystemRepo.GetAllExternalSystemsAsync(cancellationToken);
|
||||
var dbConnections = await _externalSystemRepo.GetAllDatabaseConnectionsAsync(cancellationToken);
|
||||
var notificationLists = await _notificationRepo.GetAllNotificationListsAsync(cancellationToken);
|
||||
var smtpConfigurations = await _notificationRepo.GetAllSmtpConfigurationsAsync(cancellationToken);
|
||||
|
||||
// Map shared scripts
|
||||
var scriptArtifacts = sharedScripts.Select(s =>
|
||||
@@ -177,35 +199,23 @@ public class ArtifactDeploymentService
|
||||
var dbConnectionArtifacts = dbConnections.Select(d =>
|
||||
new DatabaseConnectionArtifact(d.Name, d.ConnectionString, d.MaxRetries, d.RetryDelay)).ToList();
|
||||
|
||||
// Map notification lists
|
||||
var notificationListArtifacts = notificationLists.Select(nl =>
|
||||
new NotificationListArtifact(nl.Name, nl.Recipients.Where(r => r.EmailAddress is not null).Select(r => r.EmailAddress!).ToList())).ToList();
|
||||
|
||||
// Map SMTP configurations — use Host as the artifact name (matches SQLite PK on site)
|
||||
var smtpArtifacts = smtpConfigurations.Select(smtp =>
|
||||
new SmtpConfigurationArtifact(
|
||||
$"{smtp.Host}:{smtp.Port}", smtp.Host, smtp.Port, smtp.AuthType, smtp.FromAddress,
|
||||
smtp.Credentials, null, smtp.TlsMode)).ToList();
|
||||
|
||||
return new GlobalArtifactSnapshot(
|
||||
scriptArtifacts,
|
||||
externalSystemArtifacts,
|
||||
dbConnectionArtifacts,
|
||||
notificationListArtifacts,
|
||||
smtpArtifacts);
|
||||
dbConnectionArtifacts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bag of the global artifact sets that do not vary per site, captured once at
|
||||
/// the start of <see cref="DeployToAllSitesAsync"/> and reused for every per-site
|
||||
/// command build (DeploymentManager-023).
|
||||
/// command build (DeploymentManager-023). Notification lists and SMTP
|
||||
/// configurations are deliberately absent — they are central-only and never
|
||||
/// shipped to sites (DeploymentManager-025).
|
||||
/// </summary>
|
||||
private sealed record GlobalArtifactSnapshot(
|
||||
IReadOnlyList<SharedScriptArtifact> SharedScripts,
|
||||
IReadOnlyList<ExternalSystemArtifact> ExternalSystems,
|
||||
IReadOnlyList<DatabaseConnectionArtifact> DatabaseConnections,
|
||||
IReadOnlyList<NotificationListArtifact> NotificationLists,
|
||||
IReadOnlyList<SmtpConfigurationArtifact> SmtpConfigurations);
|
||||
IReadOnlyList<DatabaseConnectionArtifact> DatabaseConnections);
|
||||
|
||||
/// <summary>
|
||||
/// Deploys artifacts to all sites. Builds a per-site command with that site's data connections.
|
||||
@@ -226,9 +236,10 @@ public class ArtifactDeploymentService
|
||||
var perSiteResults = new Dictionary<string, SiteArtifactResult>();
|
||||
|
||||
// DeploymentManager-023: hoist the system-wide artifact queries (shared scripts,
|
||||
// external systems + methods, DB connections, notification lists, SMTP configs)
|
||||
// OUT of the per-site loop so they run ONCE instead of once per site. Only
|
||||
// data connections legitimately vary per site, so they stay inside the loop.
|
||||
// external systems + methods, DB connections) OUT of the per-site loop so they
|
||||
// run ONCE instead of once per site. Only data connections legitimately vary
|
||||
// per site, so they stay inside the loop. (Notification lists and SMTP config
|
||||
// are central-only and not fetched at all — DeploymentManager-025.)
|
||||
var globals = await FetchGlobalArtifactsAsync(cancellationToken);
|
||||
|
||||
// Build per-site commands sequentially (DbContext is not thread-safe).
|
||||
|
||||
Reference in New Issue
Block a user