docs(comments): strip internal task/milestone/bundle bookkeeping from code comments
Remove project bookkeeping citations from shipped code comments across the solution: hyphenated task IDs (WP-14, StoreAndForward-025), milestone/task/ issue refs (M3, Task 4, Audit Log #23, #21), Bundle X task-bundle labels, and C/D/K/S/T phase labels. Comment text only — no code logic, string/log literals, or XML-doc structure changed. Genuine descriptions are preserved (only the citation is stripped), and technical lookalikes are retained (UTF-8, SHA-256, T00:00:00, M365, UTC-5, pre-C4/pre-C5 schema versions). Flagged by the new CommentChecker TaskReferenceInComment / TrackingReferenceInComment checks plus targeted grep passes; full solution builds clean, append-only guard tests pass.
This commit is contained in:
@@ -11,7 +11,7 @@ using ZB.MOM.WW.ScadaBridge.Communication;
|
||||
namespace ZB.MOM.WW.ScadaBridge.DeploymentManager;
|
||||
|
||||
/// <summary>
|
||||
/// WP-7: System-wide artifact deployment.
|
||||
/// System-wide artifact deployment.
|
||||
/// Broadcasts artifacts (shared scripts, external systems, DB connections, and
|
||||
/// data connections) to all sites with per-site tracking.
|
||||
///
|
||||
@@ -47,7 +47,7 @@ public class ArtifactDeploymentService
|
||||
/// <param name="templateRepo">Repository for templates.</param>
|
||||
/// <param name="externalSystemRepo">Repository for external systems.</param>
|
||||
/// <param name="notificationRepo">
|
||||
/// DeploymentManager-025: retained on the signature for DI/source compatibility but
|
||||
/// 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.
|
||||
@@ -71,7 +71,7 @@ public class ArtifactDeploymentService
|
||||
_deploymentRepo = deploymentRepo;
|
||||
_templateRepo = templateRepo;
|
||||
_externalSystemRepo = externalSystemRepo;
|
||||
// DeploymentManager-025: notificationRepo is deliberately not stored — notification
|
||||
// notificationRepo is deliberately not stored — notification
|
||||
// lists and SMTP configs are central-only and are never fetched for shipping to sites.
|
||||
_ = notificationRepo;
|
||||
_communicationService = communicationService;
|
||||
@@ -87,14 +87,14 @@ public class ArtifactDeploymentService
|
||||
/// <param name="siteId">The DB id of the site whose data connections are collected.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <param name="deploymentId">
|
||||
/// DeploymentManager-010: the logical deployment id for this artifact deployment. All per-site
|
||||
/// The logical deployment id for this artifact deployment. All per-site
|
||||
/// commands of one <see cref="DeployToAllSitesAsync"/> call share this id so the audit log,
|
||||
/// UI summary, and persisted record correlate. When <c>null</c> a fresh id is minted (used by
|
||||
/// single-site retries).
|
||||
/// </param>
|
||||
/// <returns>A deployment artifacts command for the site.</returns>
|
||||
/// <remarks>
|
||||
/// DeploymentManager-023: this convenience overload runs the global artifact queries
|
||||
/// This convenience overload runs the global artifact queries
|
||||
/// for a single site (used by <see cref="RetryForSiteAsync"/>). The multi-site
|
||||
/// <see cref="DeployToAllSitesAsync"/> path hoists the global queries OUT of the
|
||||
/// per-site loop and calls the prefetched-globals overload to avoid the N+1
|
||||
@@ -115,12 +115,12 @@ public class ArtifactDeploymentService
|
||||
/// 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
|
||||
/// 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, and DB connections.
|
||||
///
|
||||
/// DeploymentManager-025: the command's <c>NotificationLists</c> and
|
||||
/// 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
|
||||
@@ -143,10 +143,10 @@ public class ArtifactDeploymentService
|
||||
globals.SharedScripts,
|
||||
globals.ExternalSystems,
|
||||
globals.DatabaseConnections,
|
||||
// DeploymentManager-025: notification lists are central-only — never shipped to sites.
|
||||
// Notification lists are central-only — never shipped to sites.
|
||||
NotificationLists: null,
|
||||
dataConnectionArtifacts,
|
||||
// DeploymentManager-025: SMTP config (incl. credentials) is central-only — never shipped to sites.
|
||||
// SMTP config (incl. credentials) is central-only — never shipped to sites.
|
||||
SmtpConfigurations: null,
|
||||
DateTimeOffset.UtcNow);
|
||||
}
|
||||
@@ -158,11 +158,11 @@ public class ArtifactDeploymentService
|
||||
/// once before the per-site loop.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// DeploymentManager-023: the per-site artifact build path previously re-issued
|
||||
/// The per-site artifact build path previously re-issued
|
||||
/// 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
|
||||
/// 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>
|
||||
@@ -208,9 +208,9 @@ public class ArtifactDeploymentService
|
||||
/// <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). Notification lists and SMTP
|
||||
/// command build. Notification lists and SMTP
|
||||
/// configurations are deliberately absent — they are central-only and never
|
||||
/// shipped to sites (DeploymentManager-025).
|
||||
/// shipped to sites.
|
||||
/// </summary>
|
||||
private sealed record GlobalArtifactSnapshot(
|
||||
IReadOnlyList<SharedScriptArtifact> SharedScripts,
|
||||
@@ -235,15 +235,15 @@ public class ArtifactDeploymentService
|
||||
var deploymentId = Guid.NewGuid().ToString("N");
|
||||
var perSiteResults = new Dictionary<string, SiteArtifactResult>();
|
||||
|
||||
// DeploymentManager-023: hoist the system-wide artifact queries (shared scripts,
|
||||
// Hoist the system-wide artifact queries (shared scripts,
|
||||
// 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.)
|
||||
// are central-only and not fetched at all.)
|
||||
var globals = await FetchGlobalArtifactsAsync(cancellationToken);
|
||||
|
||||
// Build per-site commands sequentially (DbContext is not thread-safe).
|
||||
// DeploymentManager-010: every per-site command carries the SAME logical
|
||||
// Every per-site command carries the SAME logical
|
||||
// deploymentId, so the per-site commands, audit log, persisted record,
|
||||
// and UI summary all reference one id instead of N+1 unrelated GUIDs.
|
||||
var siteCommands = new Dictionary<int, DeployArtifactsCommand>();
|
||||
@@ -301,7 +301,7 @@ public class ArtifactDeploymentService
|
||||
}
|
||||
|
||||
// Persist the system artifact deployment record.
|
||||
// DeploymentManager-010: SystemArtifactDeploymentRecord has no dedicated
|
||||
// SystemArtifactDeploymentRecord has no dedicated
|
||||
// DeploymentId column (adding one is a Commons/ConfigurationDatabase
|
||||
// schema change outside this module). The logical deploymentId is
|
||||
// embedded in the PerSiteStatus payload so the persisted record can be
|
||||
@@ -333,7 +333,7 @@ public class ArtifactDeploymentService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WP-7: Retry artifact deployment to a specific site that previously failed.
|
||||
/// Retry artifact deployment to a specific site that previously failed.
|
||||
/// </summary>
|
||||
/// <param name="siteDbId">The database identifier of the site.</param>
|
||||
/// <param name="siteIdentifier">The site identifier string.</param>
|
||||
@@ -371,7 +371,7 @@ public class ArtifactDeploymentService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WP-7: Per-site result for artifact deployment.
|
||||
/// Per-site result for artifact deployment.
|
||||
/// </summary>
|
||||
public record SiteArtifactResult(
|
||||
string SiteId,
|
||||
@@ -380,7 +380,7 @@ public record SiteArtifactResult(
|
||||
string? ErrorMessage);
|
||||
|
||||
/// <summary>
|
||||
/// WP-7: Summary of system-wide artifact deployment with per-site results.
|
||||
/// Summary of system-wide artifact deployment with per-site results.
|
||||
/// </summary>
|
||||
public record ArtifactDeploymentSummary(
|
||||
string DeploymentId,
|
||||
|
||||
@@ -6,15 +6,15 @@ namespace ZB.MOM.WW.ScadaBridge.DeploymentManager;
|
||||
public class DeploymentManagerOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// WP-6: Timeout for a lifecycle command round-trip (disable, enable, delete).
|
||||
/// Timeout for a lifecycle command round-trip (disable, enable, delete).
|
||||
/// Applied as a linked-CTS deadline in <c>DeploymentService</c> so a hung or
|
||||
/// unreachable site does not hold the per-instance operation lock indefinitely.
|
||||
/// </summary>
|
||||
public TimeSpan LifecycleCommandTimeout { get; set; } = TimeSpan.FromSeconds(30);
|
||||
|
||||
/// <summary>WP-7: Timeout per site for system-wide artifact deployment.</summary>
|
||||
/// <summary>Timeout per site for system-wide artifact deployment.</summary>
|
||||
public TimeSpan ArtifactDeploymentTimeoutPerSite { get; set; } = TimeSpan.FromSeconds(120);
|
||||
|
||||
/// <summary>WP-3: Timeout for acquiring an operation lock on an instance.</summary>
|
||||
/// <summary>Timeout for acquiring an operation lock on an instance.</summary>
|
||||
public TimeSpan OperationLockTimeout { get; set; } = TimeSpan.FromSeconds(5);
|
||||
}
|
||||
|
||||
@@ -19,20 +19,20 @@ using ZB.MOM.WW.ScadaBridge.TemplateEngine.Validation;
|
||||
namespace ZB.MOM.WW.ScadaBridge.DeploymentManager;
|
||||
|
||||
/// <summary>
|
||||
/// WP-1: Central-side deployment orchestration service.
|
||||
/// Central-side deployment orchestration service.
|
||||
/// Coordinates the full deployment pipeline:
|
||||
/// 1. Validate instance state transition (WP-4)
|
||||
/// 2. Acquire per-instance operation lock (WP-3)
|
||||
/// 3. Flatten configuration via TemplateEngine (captures template state at time of flatten -- WP-16)
|
||||
/// 1. Validate instance state transition
|
||||
/// 2. Acquire per-instance operation lock
|
||||
/// 3. Flatten configuration via TemplateEngine (captures template state at time of flatten)
|
||||
/// 4. Validate flattened configuration
|
||||
/// 5. Compute revision hash and diff
|
||||
/// 6. Stage a PendingDeployment row + send RefreshDeploymentCommand (notify-and-fetch; site fetches the config over HTTP)
|
||||
/// 7. Track deployment status with optimistic concurrency (WP-4)
|
||||
/// 8. Store deployed config snapshot (WP-8)
|
||||
/// 7. Track deployment status with optimistic concurrency
|
||||
/// 8. Store deployed config snapshot
|
||||
/// 9. Audit log all actions
|
||||
///
|
||||
/// WP-2: Each deployment has a unique deployment ID (GUID) + revision hash.
|
||||
/// WP-16: Template state captured at flatten time -- last-write-wins on templates is safe.
|
||||
/// Each deployment has a unique deployment ID (GUID) + revision hash.
|
||||
/// Template state captured at flatten time -- last-write-wins on templates is safe.
|
||||
/// </summary>
|
||||
public class DeploymentService
|
||||
{
|
||||
@@ -52,7 +52,7 @@ public class DeploymentService
|
||||
/// <summary>
|
||||
/// Prefix written to <see cref="DeploymentRecord.ErrorMessage"/> when a
|
||||
/// deployment fails because the site command timed out or was cancelled.
|
||||
/// Used by the query-before-redeploy trigger (DeploymentManager-006) to tell
|
||||
/// Used by the query-before-redeploy trigger to tell
|
||||
/// a timeout-induced failure apart from other deployment errors.
|
||||
/// </summary>
|
||||
private const string TimeoutFailurePrefix = "Communication failure:";
|
||||
@@ -111,7 +111,7 @@ public class DeploymentService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CentralUI-006: raises a push notification that a deployment record's
|
||||
/// Raises a push notification that a deployment record's
|
||||
/// status was just persisted, so the Central UI deployment-status page can
|
||||
/// re-render over its SignalR circuit instead of polling. Called at every
|
||||
/// point a <see cref="DeploymentRecord"/> status is written.
|
||||
@@ -124,7 +124,7 @@ public class DeploymentService
|
||||
/// Resolves the site's string identifier from the numeric DB ID.
|
||||
/// The communication layer routes by string identifier (e.g. "site-a"), not DB ID.
|
||||
///
|
||||
/// DeploymentManager-021: when the <see cref="Site"/> row is missing (FK was
|
||||
/// When the <see cref="Site"/> row is missing (FK was
|
||||
/// deleted, race with admin delete, DB inconsistency) the previous behaviour
|
||||
/// silently substituted the numeric id rendered as a string — every
|
||||
/// downstream `CommunicationService` call then failed with a confusing
|
||||
@@ -146,12 +146,12 @@ public class DeploymentService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WP-1: Deploy an instance to its site.
|
||||
/// WP-2: Generates unique deployment ID, computes revision hash.
|
||||
/// WP-4: Validates state transitions, uses optimistic concurrency.
|
||||
/// WP-5: Site-side apply is all-or-nothing (handled by DeploymentManagerActor).
|
||||
/// WP-8: Stores deployed config snapshot on success.
|
||||
/// WP-16: Captures template state at time of flatten.
|
||||
/// Deploy an instance to its site.
|
||||
/// Generates unique deployment ID, computes revision hash.
|
||||
/// Validates state transitions, uses optimistic concurrency.
|
||||
/// Site-side apply is all-or-nothing (handled by DeploymentManagerActor).
|
||||
/// Stores deployed config snapshot on success.
|
||||
/// Captures template state at time of flatten.
|
||||
/// </summary>
|
||||
/// <param name="instanceId">The database ID of the instance to deploy.</param>
|
||||
/// <param name="user">The username initiating the deployment, recorded in the audit log.</param>
|
||||
@@ -167,19 +167,19 @@ public class DeploymentService
|
||||
if (instance == null)
|
||||
return Result<DeploymentRecord>.Failure($"Instance with ID {instanceId} not found.");
|
||||
|
||||
// WP-4: Validate state transition
|
||||
// Validate state transition
|
||||
var transitionError = StateTransitionValidator.ValidateTransition(instance.State, "deploy");
|
||||
if (transitionError != null)
|
||||
return Result<DeploymentRecord>.Failure(transitionError);
|
||||
|
||||
// WP-3: Acquire per-instance operation lock
|
||||
// Acquire per-instance operation lock
|
||||
using var lockHandle = await _lockManager.AcquireAsync(
|
||||
instance.UniqueName, _options.OperationLockTimeout, cancellationToken);
|
||||
|
||||
// WP-2: Generate unique deployment ID
|
||||
// Generate unique deployment ID
|
||||
var deploymentId = Guid.NewGuid().ToString("N");
|
||||
|
||||
// WP-1/16: Flatten configuration (captures template state at this point in time)
|
||||
// Flatten configuration (captures template state at this point in time)
|
||||
var flattenResult = await _flatteningPipeline.FlattenAndValidateAsync(instanceId, cancellationToken);
|
||||
if (flattenResult.IsFailure)
|
||||
return Result<DeploymentRecord>.Failure($"Validation failed: {flattenResult.Error}");
|
||||
@@ -190,7 +190,7 @@ public class DeploymentService
|
||||
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
// Followup #8: return a grouped/summarized error (leading count + per-module
|
||||
// Return a grouped/summarized error (leading count + per-module
|
||||
// rollup, capped) instead of a flat semicolon-joined dump that becomes a wall
|
||||
// of text for instances with dozens of unbound attributes. The full per-entry
|
||||
// list still goes to the deploy log for operators who need every clause.
|
||||
@@ -208,7 +208,7 @@ public class DeploymentService
|
||||
// snapshot on success / reconciliation).
|
||||
var configJson = JsonSerializer.Serialize(flattenedConfig);
|
||||
|
||||
// DeploymentManager-006: query-the-site-before-redeploy idempotency.
|
||||
// Query-the-site-before-redeploy idempotency.
|
||||
// If a prior deployment for this instance is stuck InProgress or Failed
|
||||
// due to a timeout, the site may have actually applied the config. Query
|
||||
// the site for its currently-applied revision before re-sending so a
|
||||
@@ -230,13 +230,13 @@ public class DeploymentService
|
||||
return Result<DeploymentRecord>.Failure(
|
||||
"CentralFetchBaseUrl is not configured — required for deployment (notify-and-fetch).");
|
||||
|
||||
// WP-4: Create the deployment record directly in InProgress.
|
||||
// Create the deployment record directly in InProgress.
|
||||
//
|
||||
// DeploymentManager-022: the previous code wrote the record as Pending,
|
||||
// The previous code wrote the record as Pending,
|
||||
// then immediately updated it to InProgress with no work in between
|
||||
// (flattening, validation, and reconciliation all completed above). The
|
||||
// back-to-back write cost an extra SaveChangesAsync round-trip, an
|
||||
// extra IDeploymentStatusNotifier push (CentralUI-006 rendered a
|
||||
// extra IDeploymentStatusNotifier push (which rendered a
|
||||
// Pending→InProgress flicker for ~ms), and an extra row-version bump
|
||||
// for nothing. The transient Pending slot carried no operational
|
||||
// meaning — it was set and immediately overwritten — so dropping it
|
||||
@@ -290,12 +290,12 @@ public class DeploymentService
|
||||
// CommunicationOptions.PendingDeploymentPurgeInterval cadence.
|
||||
var response = await _communicationService.RefreshDeploymentAsync(siteId, command, cancellationToken);
|
||||
|
||||
// WP-1: Update status based on site response.
|
||||
// Update status based on site response.
|
||||
record.Status = response.Status;
|
||||
record.ErrorMessage = response.ErrorMessage;
|
||||
record.CompletedAt = DateTimeOffset.UtcNow;
|
||||
|
||||
// DeploymentManager-003: once the site has confirmed the apply,
|
||||
// Once the site has confirmed the apply,
|
||||
// commit the deployment record's terminal status BEFORE touching
|
||||
// instance state and the deployed-config snapshot. If a later write
|
||||
// (instance update / snapshot store) fails, the recorded fact that
|
||||
@@ -342,12 +342,12 @@ public class DeploymentService
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// DeploymentManager-001: any exception out of the try (timeout,
|
||||
// Any exception out of the try (timeout,
|
||||
// cancellation, transport, serialization, DB) must leave the
|
||||
// deployment record as Failed -- the design requires an interrupted
|
||||
// deployment to be treated as failed, never stuck in InProgress.
|
||||
//
|
||||
// DeploymentManager-002: the failure-status write must NOT use the
|
||||
// The failure-status write must NOT use the
|
||||
// operation's cancellation token. If the operation was cancelled or
|
||||
// timed out, that token is already cancelled and the cleanup writes
|
||||
// would themselves throw before the Failed status is persisted.
|
||||
@@ -398,7 +398,7 @@ public class DeploymentService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WP-6: Disable an instance. Stops Instance Actor, retains config, S&F drains.
|
||||
/// Disable an instance. Stops Instance Actor, retains config, S&F drains.
|
||||
/// </summary>
|
||||
/// <param name="instanceId">The database ID of the instance to disable.</param>
|
||||
/// <param name="user">The username initiating the operation, recorded in the audit log.</param>
|
||||
@@ -424,7 +424,7 @@ public class DeploymentService
|
||||
var siteId = await ResolveSiteIdentifierAsync(instance.SiteId, cancellationToken);
|
||||
var command = new DisableInstanceCommand(commandId, instance.UniqueName, DateTimeOffset.UtcNow);
|
||||
|
||||
// WP-6: bound the round-trip with the configured lifecycle timeout so a
|
||||
// Bound the round-trip with the configured lifecycle timeout so a
|
||||
// hung/unreachable site does not block the operation lock indefinitely.
|
||||
InstanceLifecycleResponse response;
|
||||
try
|
||||
@@ -435,7 +435,7 @@ public class DeploymentService
|
||||
}
|
||||
catch (Exception ex) when (ex is TimeoutException or OperationCanceledException)
|
||||
{
|
||||
// DeploymentManager-019: a lifecycle command timeout produced no
|
||||
// A lifecycle command timeout produced no
|
||||
// audit row pre-fix — the operator saw a timeout in the UI but
|
||||
// the audit trail showed nothing happened, contrary to the
|
||||
// design's "audit logging for all instance lifecycle changes"
|
||||
@@ -468,7 +468,7 @@ public class DeploymentService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WP-6: Enable an instance. Re-creates Instance Actor from stored config.
|
||||
/// Enable an instance. Re-creates Instance Actor from stored config.
|
||||
/// </summary>
|
||||
/// <param name="instanceId">The database ID of the instance to enable.</param>
|
||||
/// <param name="user">The username initiating the operation, recorded in the audit log.</param>
|
||||
@@ -494,7 +494,7 @@ public class DeploymentService
|
||||
var siteId = await ResolveSiteIdentifierAsync(instance.SiteId, cancellationToken);
|
||||
var command = new EnableInstanceCommand(commandId, instance.UniqueName, DateTimeOffset.UtcNow);
|
||||
|
||||
// WP-6: bound the round-trip with the configured lifecycle timeout.
|
||||
// Bound the round-trip with the configured lifecycle timeout.
|
||||
InstanceLifecycleResponse response;
|
||||
try
|
||||
{
|
||||
@@ -504,7 +504,7 @@ public class DeploymentService
|
||||
}
|
||||
catch (Exception ex) when (ex is TimeoutException or OperationCanceledException)
|
||||
{
|
||||
// DeploymentManager-019: emit an audit entry on lifecycle timeout
|
||||
// Emit an audit entry on lifecycle timeout
|
||||
// so the operator's attempted Enable is recorded; see the matching
|
||||
// comment in DisableInstanceAsync for the full rationale.
|
||||
await TryLogLifecycleTimeoutAsync(
|
||||
@@ -532,7 +532,7 @@ public class DeploymentService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WP-6: Delete an instance. Stops the site actor, removes site config, and
|
||||
/// Delete an instance. Stops the site actor, removes site config, and
|
||||
/// removes the central instance record (deployment history, snapshot,
|
||||
/// overrides, and connection bindings go with it). S&F NOT cleared.
|
||||
/// Delete fails if the site is unreachable within
|
||||
@@ -563,7 +563,7 @@ public class DeploymentService
|
||||
var siteId = await ResolveSiteIdentifierAsync(instance.SiteId, cancellationToken);
|
||||
var command = new DeleteInstanceCommand(commandId, instance.UniqueName, DateTimeOffset.UtcNow);
|
||||
|
||||
// WP-6: bound the round-trip with the configured lifecycle timeout.
|
||||
// Bound the round-trip with the configured lifecycle timeout.
|
||||
InstanceLifecycleResponse response;
|
||||
try
|
||||
{
|
||||
@@ -573,7 +573,7 @@ public class DeploymentService
|
||||
}
|
||||
catch (Exception ex) when (ex is TimeoutException or OperationCanceledException)
|
||||
{
|
||||
// DeploymentManager-019: emit an audit entry on lifecycle timeout
|
||||
// Emit an audit entry on lifecycle timeout
|
||||
// so the operator's attempted Delete is recorded; see the matching
|
||||
// comment in DisableInstanceAsync for the full rationale.
|
||||
await TryLogLifecycleTimeoutAsync(
|
||||
@@ -590,7 +590,7 @@ public class DeploymentService
|
||||
// Deployment records, snapshot, overrides, and connection bindings
|
||||
// are removed with it (see repository implementation).
|
||||
//
|
||||
// DeploymentManager-004: the site has already destroyed the Instance
|
||||
// The site has already destroyed the Instance
|
||||
// Actor and removed its config. If the central record removal now
|
||||
// fails (DB error / concurrency), the exception must NOT escape
|
||||
// uncaught -- that would leave the central record orphaned and
|
||||
@@ -630,7 +630,7 @@ public class DeploymentService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WP-8: Get the deployed config snapshot and compare with current
|
||||
/// Get the deployed config snapshot and compare with current
|
||||
/// template-derived state. Produces both a staleness flag and — per the
|
||||
/// design's "Diff View" — a structured <see cref="ConfigurationDiff"/> of
|
||||
/// added/removed/changed attributes, alarms, and scripts (including data
|
||||
@@ -658,7 +658,7 @@ public class DeploymentService
|
||||
|
||||
// I-1 (latent): the snapshot's ConfigurationJson + RevisionHash froze the
|
||||
// FLATTENED config at deploy time. The current config is a FRESH flatten,
|
||||
// now always in native List form (#93 consolidated element-type/coercion
|
||||
// now always in native List form (consolidated element-type/coercion
|
||||
// into AttributeValueCodec, which emits native-form JSON arrays). A List
|
||||
// attribute deployed in the OLD quoted form (e.g. ["10","20"]) therefore
|
||||
// both (a) hashes differently from the native re-flatten — a spurious
|
||||
@@ -669,7 +669,7 @@ public class DeploymentService
|
||||
// equal to the native re-flatten, then drive BOTH the staleness hash and
|
||||
// the diff off that normalized snapshot. Scalars are left untouched.
|
||||
//
|
||||
// DeploymentManager-007: a snapshot that cannot be deserialized (corrupt /
|
||||
// A snapshot that cannot be deserialized (corrupt /
|
||||
// older schema) still yields the frozen-hash staleness result, with a
|
||||
// null diff.
|
||||
var deployedRevisionHash = snapshot.RevisionHash;
|
||||
@@ -781,7 +781,7 @@ public class DeploymentService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WP-2: Returns the current persisted <see cref="DeploymentRecord"/> for
|
||||
/// Returns the current persisted <see cref="DeploymentRecord"/> for
|
||||
/// the given deployment ID from the configuration database. This is a pure
|
||||
/// local DB read — it does not contact the site. The query-the-site-before-
|
||||
/// redeploy reconciliation (design: "Deployment Identity & Idempotency")
|
||||
@@ -799,7 +799,7 @@ public class DeploymentService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DeploymentManager-006: query-the-site-before-redeploy reconciliation.
|
||||
/// Query-the-site-before-redeploy reconciliation.
|
||||
///
|
||||
/// The site query is issued ONLY when a prior <see cref="DeploymentRecord"/>
|
||||
/// for this instance is stuck <see cref="DeploymentStatus.InProgress"/>, or
|
||||
@@ -810,11 +810,11 @@ public class DeploymentService
|
||||
///
|
||||
/// Reconciliation: if the site already has the TARGET revision hash, the
|
||||
/// prior record is marked <see cref="DeploymentStatus.Success"/> (with its
|
||||
/// <see cref="DeploymentRecord.RevisionHash"/> corrected to the target —
|
||||
/// DeploymentManager-016) and returned (the caller must NOT re-send the
|
||||
/// <see cref="DeploymentRecord.RevisionHash"/> corrected to the target)
|
||||
/// and returned (the caller must NOT re-send the
|
||||
/// deploy). The same post-success side effects as the normal deploy path
|
||||
/// are applied — instance <see cref="InstanceState.Enabled"/> and a stored
|
||||
/// <see cref="DeployedConfigSnapshot"/> (DeploymentManager-015) — so central
|
||||
/// <see cref="DeployedConfigSnapshot"/> — so central
|
||||
/// and site state do not diverge. Otherwise <c>null</c> is returned and the
|
||||
/// normal deploy proceeds.
|
||||
///
|
||||
@@ -875,7 +875,7 @@ public class DeploymentService
|
||||
prior.Status = DeploymentStatus.Success;
|
||||
prior.ErrorMessage = null;
|
||||
prior.CompletedAt = DateTimeOffset.UtcNow;
|
||||
// DeploymentManager-016: the prior record can legitimately carry a
|
||||
// The prior record can legitimately carry a
|
||||
// different (stale) revision hash than the current target. The site
|
||||
// confirmed it is running the target revision, so the persisted
|
||||
// record, the audit entry below, and the site must all agree.
|
||||
@@ -884,13 +884,13 @@ public class DeploymentService
|
||||
await _repository.SaveChangesAsync(cancellationToken);
|
||||
NotifyStatusChange(prior);
|
||||
|
||||
// DeploymentManager-015: a reconciled deployment must perform the
|
||||
// A reconciled deployment must perform the
|
||||
// SAME post-success side effects as the normal deploy path — set
|
||||
// the instance State to Enabled and store/refresh the deployed
|
||||
// config snapshot — otherwise the central state machine and the
|
||||
// deployed-snapshot invariant diverge from what the site is running.
|
||||
//
|
||||
// DeploymentManager-018: the reconciliation path runs only when the
|
||||
// The reconciliation path runs only when the
|
||||
// prior record is InProgress or timeout-Failed — exactly the cases
|
||||
// that survive a central failover. The in-memory operation lock is
|
||||
// lost on failover, so an operator may have legitimately invoked
|
||||
@@ -906,7 +906,7 @@ public class DeploymentService
|
||||
instance, prior.DeploymentId, targetRevisionHash, configJson,
|
||||
forceEnabledState: false, cancellationToken);
|
||||
|
||||
// DeploymentManager-020: attribute the audit row to the user driving
|
||||
// Attribute the audit row to the user driving
|
||||
// THIS redeploy (the caller of DeployInstanceAsync), not the user
|
||||
// who issued the original timed-out / stuck deployment. The original
|
||||
// deployer is preserved in the detail object so forensics can still
|
||||
@@ -930,7 +930,7 @@ public class DeploymentService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DeploymentManager-006: the site is queried before a re-deploy only when a
|
||||
/// The site is queried before a re-deploy only when a
|
||||
/// prior record is stuck <see cref="DeploymentStatus.InProgress"/>, or is
|
||||
/// <see cref="DeploymentStatus.Failed"/> because the site command timed out
|
||||
/// (detected via the <see cref="TimeoutFailurePrefix"/> error-message
|
||||
@@ -944,12 +944,12 @@ public class DeploymentService
|
||||
|
||||
/// <summary>
|
||||
/// Post-success side effects shared by the normal deploy path and the
|
||||
/// DeploymentManager-006 reconciliation path: set the instance
|
||||
/// <see cref="InstanceState.Enabled"/> (WP-4) and store/refresh the
|
||||
/// deployed config snapshot (WP-8). Factored into one helper so the two
|
||||
/// paths cannot drift (DeploymentManager-015).
|
||||
/// reconciliation path: set the instance
|
||||
/// <see cref="InstanceState.Enabled"/> and store/refresh the
|
||||
/// deployed config snapshot. Factored into one helper so the two
|
||||
/// paths cannot drift.
|
||||
///
|
||||
/// DeploymentManager-018: <paramref name="forceEnabledState"/> distinguishes
|
||||
/// <paramref name="forceEnabledState"/> distinguishes
|
||||
/// the two callers. The normal deploy path passes <c>true</c> — a fresh
|
||||
/// successful apply legitimately puts the instance into <see cref="InstanceState.Enabled"/>
|
||||
/// (the documented "Deploy on a Disabled instance also enables it" semantics
|
||||
@@ -977,8 +977,8 @@ public class DeploymentService
|
||||
{
|
||||
try
|
||||
{
|
||||
// WP-4: Update instance state to Enabled on successful deployment.
|
||||
// DeploymentManager-018: on the reconciliation path
|
||||
// Update instance state to Enabled on successful deployment.
|
||||
// On the reconciliation path
|
||||
// (forceEnabledState=false) only promote NotDeployed → Enabled,
|
||||
// preserving an intentional Disabled state set between the original
|
||||
// timed-out deploy and the redeploy.
|
||||
@@ -988,7 +988,7 @@ public class DeploymentService
|
||||
}
|
||||
await _repository.UpdateInstanceAsync(instance, cancellationToken);
|
||||
|
||||
// WP-8: Store deployed config snapshot
|
||||
// Store deployed config snapshot
|
||||
await StoreDeployedSnapshotAsync(
|
||||
instance.Id, deploymentId, revisionHash, configJson, cancellationToken);
|
||||
|
||||
@@ -1005,7 +1005,7 @@ public class DeploymentService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DeploymentManager-019: write a "<Action>TimedOut" audit entry on
|
||||
/// Write a "<Action>TimedOut" audit entry on
|
||||
/// behalf of a lifecycle command (Disable / Enable / Delete) whose site
|
||||
/// round-trip exceeded <see cref="DeploymentManagerOptions.LifecycleCommandTimeout"/>.
|
||||
///
|
||||
@@ -1094,10 +1094,10 @@ public class DeploymentService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WP-8: Result of comparing deployed vs template-derived configuration.
|
||||
/// Result of comparing deployed vs template-derived configuration.
|
||||
/// </summary>
|
||||
/// <param name="Diff">
|
||||
/// DeploymentManager-007: structured added/removed/changed detail for
|
||||
/// Structured added/removed/changed detail for
|
||||
/// attributes, alarms, and scripts. Null only when the deployed snapshot could
|
||||
/// not be deserialized (corrupt / older schema), in which case
|
||||
/// <see cref="IsStale"/> still reflects the hash comparison.
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace ZB.MOM.WW.ScadaBridge.DeploymentManager;
|
||||
/// Default <see cref="IDeploymentStatusNotifier"/> implementation. A simple
|
||||
/// in-process event broadcaster: registered as a DI singleton so it is shared
|
||||
/// between the central-process <see cref="DeploymentService"/> and the Central
|
||||
/// UI's Blazor circuits (CentralUI-006).
|
||||
/// UI's Blazor circuits.
|
||||
///
|
||||
/// A throwing subscriber must never break the deployment pipeline, so each
|
||||
/// handler is invoked individually and its exceptions are caught and logged.
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace ZB.MOM.WW.ScadaBridge.DeploymentManager;
|
||||
/// Orchestrates the TemplateEngine services (FlatteningService, ValidationService, RevisionHashService)
|
||||
/// into a single pipeline for deployment use.
|
||||
///
|
||||
/// WP-16: This captures template state at the time of flatten, ensuring that concurrent template edits
|
||||
/// This captures template state at the time of flatten, ensuring that concurrent template edits
|
||||
/// (last-write-wins) do not conflict with in-progress deployments.
|
||||
/// </summary>
|
||||
public class FlatteningPipeline : IFlatteningPipeline
|
||||
@@ -31,7 +31,7 @@ public class FlatteningPipeline : IFlatteningPipeline
|
||||
/// <param name="validationService">Service that performs semantic validation on the flattened config.</param>
|
||||
/// <param name="revisionHashService">Service that computes the revision hash for staleness detection.</param>
|
||||
/// <param name="sharedSchemaRepo">
|
||||
/// M9-T32b: repository backing the JSON-Schema <c>$ref</c> resolution seam. Used to
|
||||
/// Repository backing the JSON-Schema <c>$ref</c> resolution seam. Used to
|
||||
/// look up <c>lib:Name</c> library references so a dangling reference in any validated
|
||||
/// script schema becomes a deploy-blocking error.
|
||||
/// </param>
|
||||
@@ -135,7 +135,7 @@ public class FlatteningPipeline : IFlatteningPipeline
|
||||
.Select(c => c.Name)
|
||||
.ToHashSet(StringComparer.Ordinal);
|
||||
|
||||
// M2.8 (#23): the set of data-connection names that actually exist on the
|
||||
// The set of data-connection names that actually exist on the
|
||||
// target site, used to verify each bound connection resolves to a real site
|
||||
// connection. Same StringComparer.Ordinal as the rest of the binding-resolution
|
||||
// path (connection names are matched as-authored throughout the pipeline).
|
||||
@@ -143,7 +143,7 @@ public class FlatteningPipeline : IFlatteningPipeline
|
||||
.Select(c => c.Name)
|
||||
.ToHashSet(StringComparer.Ordinal);
|
||||
|
||||
// M9-T32b: build the JSON-Schema $ref resolution seam from the shared-schema
|
||||
// Build the JSON-Schema $ref resolution seam from the shared-schema
|
||||
// library. The seam ValidationService consumes is synchronous, so the library is
|
||||
// pre-loaded once into a name→JSON map here (avoiding sync-over-async) and the
|
||||
// seam is a pure in-memory lookup. An unresolved {"$ref":"lib:Name"} in any
|
||||
|
||||
@@ -16,7 +16,7 @@ public readonly record struct DeploymentStatusChange(
|
||||
DeploymentStatus Status);
|
||||
|
||||
/// <summary>
|
||||
/// CentralUI-006: push-based deployment-status change notification.
|
||||
/// Push-based deployment-status change notification.
|
||||
///
|
||||
/// The design (Component-CentralUI "Real-Time Updates") requires deployment
|
||||
/// status transitions to push to the UI immediately via SignalR, with no
|
||||
|
||||
@@ -3,14 +3,14 @@ using System.Collections.Concurrent;
|
||||
namespace ZB.MOM.WW.ScadaBridge.DeploymentManager;
|
||||
|
||||
/// <summary>
|
||||
/// WP-3: Per-instance operation lock. Only one mutating operation (deploy, disable, enable, delete)
|
||||
/// Per-instance operation lock. Only one mutating operation (deploy, disable, enable, delete)
|
||||
/// may be in progress per instance at a time. Different instances can proceed in parallel.
|
||||
///
|
||||
/// Implementation: ConcurrentDictionary of ref-counted SemaphoreSlim(1,1) keyed by instance
|
||||
/// unique name. The lock is released on completion, timeout, or failure.
|
||||
/// Lost on central failover (acceptable per design -- in-progress treated as failed).
|
||||
///
|
||||
/// DeploymentManager-005: each entry is ref-counted. The semaphore is created on the
|
||||
/// Each entry is ref-counted. The semaphore is created on the
|
||||
/// first acquire/wait, shared while there are waiters or a holder, and removed +
|
||||
/// <see cref="IDisposable.Dispose"/>d when the last reference is released — so the dictionary
|
||||
/// does not accumulate one kernel wait handle per distinct instance name forever.
|
||||
@@ -22,7 +22,7 @@ public class OperationLockManager
|
||||
|
||||
/// <summary>
|
||||
/// Number of lock entries currently tracked. Used for diagnostics and to
|
||||
/// verify that semaphores are reclaimed (DeploymentManager-005).
|
||||
/// verify that semaphores are reclaimed.
|
||||
/// </summary>
|
||||
public int TrackedLockCount
|
||||
{
|
||||
|
||||
@@ -25,13 +25,13 @@ public static class ServiceCollectionExtensions
|
||||
/// <returns>The <paramref name="services"/> instance for chaining.</returns>
|
||||
public static IServiceCollection AddDeploymentManager(this IServiceCollection services)
|
||||
{
|
||||
// DeploymentManager-008: ensure the options class is always resolvable.
|
||||
// Ensure the options class is always resolvable.
|
||||
// The Host binds OptionsSection to appsettings.json; absent that binding
|
||||
// the declared option-class defaults apply.
|
||||
services.AddOptions<DeploymentManagerOptions>();
|
||||
services.AddSingleton<OperationLockManager>();
|
||||
|
||||
// CentralUI-006: push-based deployment-status notification. Registered
|
||||
// Push-based deployment-status notification. Registered
|
||||
// as a singleton so the scoped DeploymentService and the Central UI's
|
||||
// scoped Blazor page component share one instance — both run in the
|
||||
// same central Host process. The deployment-status page subscribes to
|
||||
@@ -42,7 +42,7 @@ public static class ServiceCollectionExtensions
|
||||
services.AddScoped<DeploymentService>();
|
||||
services.AddScoped<ArtifactDeploymentService>();
|
||||
|
||||
// #16 (M8 D2): expose the flattening pipeline's revision-hash computation
|
||||
// Expose the flattening pipeline's revision-hash computation
|
||||
// to the Transport bundle importer through the Commons IStaleInstanceProbe
|
||||
// seam, so a template overwrite can enumerate the deployed instances whose
|
||||
// flattened config has drifted from their deployed snapshot. Scoped so it
|
||||
|
||||
@@ -3,7 +3,7 @@ using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
|
||||
namespace ZB.MOM.WW.ScadaBridge.DeploymentManager;
|
||||
|
||||
/// <summary>
|
||||
/// WP-4: State transition matrix for instance lifecycle.
|
||||
/// State transition matrix for instance lifecycle.
|
||||
///
|
||||
/// State | Deploy | Disable | Enable | Delete
|
||||
/// ----------|--------|---------|--------|-------
|
||||
@@ -42,8 +42,8 @@ public static class StateTransitionValidator
|
||||
/// undeployed instance would otherwise linger as an unremovable orphan record.
|
||||
/// Delete from <c>NotDeployed</c> is a central-side record cleanup (no live site
|
||||
/// config to tear down). This matches the state-transition matrix in
|
||||
/// Component-DeploymentManager.md ("Delete from Not deployed = Yes") — reconciled
|
||||
/// in M2.17 (#31); the deliberate behaviour was introduced in commit 1d5465f3.
|
||||
/// Component-DeploymentManager.md ("Delete from Not deployed = Yes") — reconciled;
|
||||
/// the deliberate behaviour was introduced in commit 1d5465f3.
|
||||
/// </remarks>
|
||||
public static bool CanDelete(InstanceState currentState) =>
|
||||
currentState is InstanceState.NotDeployed or InstanceState.Enabled or InstanceState.Disabled;
|
||||
|
||||
Reference in New Issue
Block a user