diff --git a/src/ZB.MOM.WW.ScadaBridge.AuditLog/Central/AuditLogIngestActor.cs b/src/ZB.MOM.WW.ScadaBridge.AuditLog/Central/AuditLogIngestActor.cs
index f4460a37..7be9bb4a 100644
--- a/src/ZB.MOM.WW.ScadaBridge.AuditLog/Central/AuditLogIngestActor.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.AuditLog/Central/AuditLogIngestActor.cs
@@ -227,7 +227,7 @@ public class AuditLogIngestActor : ReceiveActor
projected.Add(AuditRowProjection.WithIngestedAtUtc(filtered, nowUtc));
}
- // ONE set-based statement for the whole packet (WP2.2) instead of one
+ // ONE set-based statement for the whole packet instead of one
// IF NOT EXISTS … INSERT round trip per event. A telemetry packet of 200
// rows used to cost 200 sequential round trips on the central singleton's
// dispatcher; it now costs two. Idempotency is unchanged — duplicates
@@ -356,7 +356,7 @@ public class AuditLogIngestActor : ReceiveActor
var strategy = dbContext.Database.CreateExecutionStrategy();
- // Fast path (WP2.2): the WHOLE packet in ONE transaction — one
+ // Fast path: the WHOLE packet in ONE transaction — one
// set-based audit insert plus one single-statement upsert per entry.
// The predecessor opened a transaction PER entry and issued three
// statements inside it (IF NOT EXISTS insert, then the two-statement
diff --git a/src/ZB.MOM.WW.ScadaBridge.AuditLog/Central/AuditLogPurgeActor.cs b/src/ZB.MOM.WW.ScadaBridge.AuditLog/Central/AuditLogPurgeActor.cs
index bb18a2c9..8ef23a1d 100644
--- a/src/ZB.MOM.WW.ScadaBridge.AuditLog/Central/AuditLogPurgeActor.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.AuditLog/Central/AuditLogPurgeActor.cs
@@ -18,8 +18,8 @@ namespace ZB.MOM.WW.ScadaBridge.AuditLog.Central;
/// than DateTime.UtcNow - RetentionDays.
/// For each eligible boundary, calls
/// , a
-/// metadata-only staging-table switch (WP2.2 removed the index
-/// drop/rebuild that used to bracket it).
+/// metadata-only staging-table switch (no index
+/// drop/rebuild brackets it).
/// Publishes on the actor-system
/// EventStream so the central health collector + ops surfaces
/// can subscribe without coupling to this actor.
diff --git a/src/ZB.MOM.WW.ScadaBridge.AuditLog/Central/AuditLogPurgeOptions.cs b/src/ZB.MOM.WW.ScadaBridge.AuditLog/Central/AuditLogPurgeOptions.cs
index c2f4d3f6..8601bb44 100644
--- a/src/ZB.MOM.WW.ScadaBridge.AuditLog/Central/AuditLogPurgeOptions.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.AuditLog/Central/AuditLogPurgeOptions.cs
@@ -11,8 +11,8 @@ namespace ZB.MOM.WW.ScadaBridge.AuditLog.Central;
///
///
/// The purge actor is a daily-cadence singleton, not a hot-loop. The
-/// partition switch itself is metadata-only (since WP2.2's
-/// AlignAuditLogEventIdUniqueness it no longer drops and rebuilds a
+/// partition switch itself is metadata-only (since the
+/// AlignAuditLogEventIdUniqueness migration it no longer drops and rebuilds a
/// non-aligned unique index around it), but it still takes schema-modification
/// locks and competes with ingest for the same table. Lower this only when an
/// operator can prove they need sub-daily purge granularity.
diff --git a/src/ZB.MOM.WW.ScadaBridge.AuditLog/Central/GrpcPullTraceEventsClient.cs b/src/ZB.MOM.WW.ScadaBridge.AuditLog/Central/GrpcPullTraceEventsClient.cs
index dcc4b5e6..6ca51d4f 100644
--- a/src/ZB.MOM.WW.ScadaBridge.AuditLog/Central/GrpcPullTraceEventsClient.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.AuditLog/Central/GrpcPullTraceEventsClient.cs
@@ -28,6 +28,9 @@ public sealed class GrpcPullTraceEventsClient : IPullTraceEventsClient
private readonly ILogger _logger;
/// Creates the client over the given site enumerator and unary-call invoker.
+ /// Enumerates the sites and their gRPC endpoints.
+ /// Issues the unary PullTraceEvents call.
+ /// Logger for pull diagnostics.
public GrpcPullTraceEventsClient(
ISiteEnumerator sites,
IPullTraceEventsInvoker invoker,
@@ -152,6 +155,11 @@ public sealed class GrpcPullTraceEventsClient : IPullTraceEventsClient
public interface IPullTraceEventsInvoker
{
/// Issues the RPC against with the site's preshared key.
+ /// The site the call targets.
+ /// The site node gRPC endpoint to call.
+ /// The pull request.
+ /// Token to cancel the call.
+ /// The site's trace-event response.
Task InvokeAsync(
string siteId, string endpoint, PullTraceEventsRequest request, CancellationToken ct);
}
@@ -169,6 +177,8 @@ public sealed class GrpcPullTraceEventsInvoker : GrpcPullTraceEventsClient.IPull
private readonly ISitePskProvider? _pskProvider;
/// Creates the invoker with the given options and optional per-site PSK provider.
+ /// Communication options supplying keepalive settings.
+ /// Optional provider of per-site preshared keys.
public GrpcPullTraceEventsInvoker(CommunicationOptions options, ISitePskProvider? pskProvider)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
@@ -212,7 +222,7 @@ public sealed class GrpcPullTraceEventsInvoker : GrpcPullTraceEventsClient.IPull
},
}.WithMessageLimits().WithSiteCredentials(_pskProvider, siteId));
- ///
+ /// Disposes every cached gRPC channel.
public void Dispose()
{
foreach (var channel in _channels.Values)
diff --git a/src/ZB.MOM.WW.ScadaBridge.AuditLog/Central/SiteAuditReconciliationActor.cs b/src/ZB.MOM.WW.ScadaBridge.AuditLog/Central/SiteAuditReconciliationActor.cs
index fd83b09f..a450be32 100644
--- a/src/ZB.MOM.WW.ScadaBridge.AuditLog/Central/SiteAuditReconciliationActor.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.AuditLog/Central/SiteAuditReconciliationActor.cs
@@ -521,6 +521,8 @@ public class SiteAuditReconciliationActor : ReceiveActor
/// lost rows get distinct synthetic ids.
///
///
+ /// The event id of the lost row being abandoned.
+ /// A deterministic synthetic GUID for the abandonment row.
internal static Guid DeriveAbandonmentEventId(Guid lostEventId)
{
Span input = stackalloc byte[32];
diff --git a/src/ZB.MOM.WW.ScadaBridge.AuditLog/Redaction/ScadaBridgeAuditRedactor.cs b/src/ZB.MOM.WW.ScadaBridge.AuditLog/Redaction/ScadaBridgeAuditRedactor.cs
index 627922ee..3dce3036 100644
--- a/src/ZB.MOM.WW.ScadaBridge.AuditLog/Redaction/ScadaBridgeAuditRedactor.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.AuditLog/Redaction/ScadaBridgeAuditRedactor.cs
@@ -150,7 +150,7 @@ public sealed class ScadaBridgeAuditRedactor : IAuditRedactor, IDisposable
}
}
- ///
+ /// Disposes the options-change registration, if any.
public void Dispose() => _optionsReload?.Dispose();
///
diff --git a/src/ZB.MOM.WW.ScadaBridge.AuditLog/Site/SqliteAuditWriter.cs b/src/ZB.MOM.WW.ScadaBridge.AuditLog/Site/SqliteAuditWriter.cs
index f08fac67..36b87431 100644
--- a/src/ZB.MOM.WW.ScadaBridge.AuditLog/Site/SqliteAuditWriter.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.AuditLog/Site/SqliteAuditWriter.cs
@@ -174,7 +174,7 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable
pragmaCmd.ExecuteNonQuery();
}
- // synchronous=NORMAL (arch-review remediation WP1.2): SQLite's FULL default
+ // synchronous=NORMAL: SQLite's FULL default
// fsyncs on every transaction commit, which is the dominant cost of the
// per-event write path this same change coalesces via FlushIntervalMs — FULL
// would still fsync once per FLUSH even after that fix. NORMAL fsyncs at
@@ -346,8 +346,8 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable
// burst of concurrent enqueues. Once the immediately-available
// burst is drained, keep the transaction open for up to
// FlushIntervalMs waiting for MORE events to trickle in — this is
- // the soft-flush coalescing arch-review remediation WP1.2 adds:
- // without it, a low-rate trickle of writes (one script call every
+ // the soft-flush coalescing
+ // this loop provides: without it, a low-rate trickle of writes (one script call every
// few ms, well under BatchSize) fsyncs once PER EVENT, because the
// writer loop reaches an empty channel and flushes immediately.
// FlushIntervalMs bounds the worst-case added latency any single
@@ -392,8 +392,7 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable
///
/// Total number of transactions committed since
- /// construction. Test-only observability seam (arch-review remediation
- /// WP1.2) for asserting that FlushIntervalMs coalesces a trickle of
+ /// construction. Test-only observability seam for asserting that FlushIntervalMs coalesces a trickle of
/// near-simultaneous writes into a single transaction rather than one
/// commit per event; production code never reads this.
///
@@ -499,8 +498,7 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable
{
eventCmd.ExecuteNonQuery();
fwdCmd.ExecuteNonQuery();
- // Do NOT complete pending.Completion here (arch-review 2026-08-19
- // P4-2). A row's INSERTs succeeding is not durable until the
+ // Do NOT complete pending.Completion here. A row's INSERTs succeeding is not durable until the
// transaction actually commits below — completing the caller's Task
// this early means a later commit failure (SQLITE_FULL, IOERR, or a
// later row in this same batch throwing) reports false success: the
@@ -866,7 +864,15 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable
/// Flow Debugger PullTraceEvents: hard cap on ids per read (design §5.4).
public const int MaxExecutionIdsPerRead = 64;
- ///
+ ///
+ /// Reads audit events belonging to any of the given execution ids, newer than , in keyset-paged order.
+ ///
+ /// Execution ids to match (capped at ).
+ /// Lower bound on the event's occurrence time.
+ /// Maximum number of events to return; must be positive.
+ /// Cancellation token.
+ /// Optional keyset cursor; only events after this id are returned.
+ /// The matching audit events.
public Task> ReadByExecutionIdsAsync(
IReadOnlyCollection executionIds, DateTime sinceUtc, int limit, CancellationToken ct = default, string? afterEventId = null)
{
@@ -925,6 +931,8 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable
/// UNION deduplicates rows matching both IDs. Keeping payloads outside the union bounds sorting
/// to IDs/timestamps and avoids the optimizer preferring a whole-store timestamp scan.
///
+ /// The pre-built SQL parameter list for the IN (...) clauses.
+ /// The trace-read SQL text.
internal static string BuildTraceReadSql(string inList) => $"""
SELECT e.EventId, e.OccurredAtUtc, e.Actor, e.Action, e.Outcome,
e.Category, e.Target, e.SourceNode, e.CorrelationId, e.DetailsJson
diff --git a/src/ZB.MOM.WW.ScadaBridge.AuditLog/Site/SqliteAuditWriterOptionsValidator.cs b/src/ZB.MOM.WW.ScadaBridge.AuditLog/Site/SqliteAuditWriterOptionsValidator.cs
index cc79e76b..0f798e64 100644
--- a/src/ZB.MOM.WW.ScadaBridge.AuditLog/Site/SqliteAuditWriterOptionsValidator.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.AuditLog/Site/SqliteAuditWriterOptionsValidator.cs
@@ -12,7 +12,7 @@ namespace ZB.MOM.WW.ScadaBridge.AuditLog.Site;
///
///
/// is deliberately NOT
-/// validated here (arch-review remediation WP1.2). AddAuditLog runs on
+/// validated here. AddAuditLog runs on
/// BOTH Central and Site composition roots and binds this options type with
/// ValidateOnStart either way, but only Site nodes ever resolve
/// — requiring a non-empty path in this
diff --git a/src/ZB.MOM.WW.ScadaBridge.AuditLog/Site/Telemetry/SqliteErrorCodes.cs b/src/ZB.MOM.WW.ScadaBridge.AuditLog/Site/Telemetry/SqliteErrorCodes.cs
index 83bc351a..165a5821 100644
--- a/src/ZB.MOM.WW.ScadaBridge.AuditLog/Site/Telemetry/SqliteErrorCodes.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.AuditLog/Site/Telemetry/SqliteErrorCodes.cs
@@ -17,6 +17,8 @@ internal static class SqliteErrorCodes
/// "primary/extended" (e.g. "10/522") for a
/// anywhere in the exception chain; "n/a" for non-SQLite failures.
///
+ /// The exception to inspect, including its inner exceptions.
+ /// The "primary/extended" SQLite error code string, or "n/a" for non-SQLite failures.
public static string Describe(Exception ex)
{
for (Exception? e = ex; e is not null; e = e.InnerException)
diff --git a/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/AuditCommands.cs b/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/AuditCommands.cs
index 112550ea..561f3527 100644
--- a/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/AuditCommands.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/AuditCommands.cs
@@ -220,8 +220,8 @@ public static class AuditCommands
// Ctrl-C is the one way to abort an in-flight export other than the
// headers/inactivity deadlines below — mirrors the CancellationTokenSource
- // owned by DebugCommands' streaming debug-view command (arch-review
- // 2026-08-19 P6-8: this was the one CLI HTTP path with NO deadline at all).
+ // owned by DebugCommands' streaming debug-view command (this was the
+ // one CLI HTTP path with NO deadline at all).
using var cts = new CancellationTokenSource();
ConsoleCancelEventHandler onCancel = (_, e) =>
{
diff --git a/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/AuditExportHelpers.cs b/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/AuditExportHelpers.cs
index 08dbf343..b57a82c7 100644
--- a/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/AuditExportHelpers.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/AuditExportHelpers.cs
@@ -115,7 +115,7 @@ public static class AuditExportHelpers
/// and bounds only the TCP/TLS
/// connect phase, not a connected-but-silent server. Reuses
/// as the standard CLI
- /// per-call default (arch-review 2026-08-19 P6-8).
+ /// per-call default.
///
public static readonly TimeSpan DefaultHeadersTimeout = ManagementHttpClient.DefaultConnectTimeout;
@@ -126,7 +126,7 @@ public static class AuditExportHelpers
/// hung/crashed server holding the connection open) is bounded rather than
/// hanging the CLI forever — the residual gap the timeout-honesty fix
/// (ManagementHttpClient.DefaultConnectTimeout doc comment) left in this
- /// one path (arch-review 2026-08-19 P6-8).
+ /// one path.
///
public static readonly TimeSpan DefaultInactivityTimeout = ManagementHttpClient.DefaultConnectTimeout;
@@ -204,7 +204,7 @@ public static class AuditExportHelpers
// Honour the documented "authorization failure → exit 2"
// contract on the REST audit surface as well. HTTP 403 is the
// primary signal; the server may also surface UNAUTHORIZED /
- // FORBIDDEN via the JSON error envelope on a non-403 status.
+ // FORBIDDEN via the JSON error envelope on a status other than 403.
var errorCode = TryExtractErrorCode(message);
var isAuthFailure = (int)response.StatusCode == 403
|| string.Equals(errorCode, "FORBIDDEN", StringComparison.OrdinalIgnoreCase)
diff --git a/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/BundleCommands.cs b/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/BundleCommands.cs
index 549522d5..1a7de401 100644
--- a/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/BundleCommands.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/BundleCommands.cs
@@ -404,7 +404,7 @@ public static class BundleCommands
// RunBundleCommandAsync that re-implemented URL/credential resolution and
// skipped the IsAuthorizationFailure(...) check that ExecuteCommandAsync
// enforces — a server that signalled FORBIDDEN/UNAUTHORIZED via the error
- // code on a non-403 status would exit 1 instead of the documented exit 2.
+ // code on a status other than 403 would exit 1 instead of the documented exit 2.
// The bundle path now delegates to CommandHelpers.ExecuteCommandAsync with
// the longer BundleCommandTimeout and a per-command success handler, so the
// exit-code contract is unified across every command group.
diff --git a/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/CommandHelpers.cs b/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/CommandHelpers.cs
index a104b2fe..0341ef5d 100644
--- a/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/CommandHelpers.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/CommandHelpers.cs
@@ -262,7 +262,7 @@ internal static class CommandHelpers
var objectItems = items.Where(i => i.ValueKind == JsonValueKind.Object).ToList();
// A "note" property that appears ONLY on single-property rows (a trailing
- // run-level note, or — Task 14 review, 2026-09-09 — a step's folded log/exception
+ // run-level note, or a step's folded log/exception
// line) is not a real column: every other row's cell there would render blank,
// and column padding would swallow the raw line's own leading indentation. Such
// rows print as a raw unpadded line instead, at their position in the sequence,
diff --git a/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/DeployCommands.cs b/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/DeployCommands.cs
index 39491a2c..7c4ec842 100644
--- a/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/DeployCommands.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/DeployCommands.cs
@@ -96,6 +96,10 @@ public static class DeployCommands
/// whose summaries all carry failureCount — a per-site failure is a successful HTTP
/// call, so without this the process exited 0 and scripts could not detect it.
///
+ /// The JSON response body from the management API.
+ /// The output format (e.g. json or table).
+ /// Display name for the summary, used in the invalid-response error.
+ /// 0 when nothing failed; 1 when any failure was reported or the summary was invalid.
internal static int HandleSiteDeploymentResponse(string json, string format, string summaryName = "site deployment")
{
CommandHelpers.HandleResponse(new ManagementResponse(200, json, null, null), format);
diff --git a/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/FlowDebugCommands.cs b/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/FlowDebugCommands.cs
index 62316187..8c85cf56 100644
--- a/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/FlowDebugCommands.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/FlowDebugCommands.cs
@@ -215,6 +215,8 @@ public static class FlowDebugCommands
/// means "no parameters". Throws on malformed input or a
/// non-object root (the inbound API body is always a flat JSON object).
///
+ /// The raw --params JSON text, or null/blank for none.
+ /// The parsed root object, or null when no parameters were supplied.
internal static JsonElement? ParseParameters(string? json)
{
if (string.IsNullOrWhiteSpace(json))
diff --git a/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/FlowDebugTableProjection.cs b/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/FlowDebugTableProjection.cs
index 00ce98fa..f278a1dd 100644
--- a/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/FlowDebugTableProjection.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/FlowDebugTableProjection.cs
@@ -45,7 +45,7 @@ internal static class FlowDebugTableProjection
///
/// When true, prints each step's ScriptLog lines and (on a failed step) the first
/// three lines of its exception detail as additional indented note rows immediately
- /// following that step's row (step-logs design 2026-09-09, CLI Task 14). The logs
+ /// following that step's row. The logs
/// column (a count of attached log lines) is always present, independent of this flag.
///
/// Compact JSON suitable for table rendering.
@@ -103,7 +103,7 @@ internal static class FlowDebugTableProjection
if (truncated)
notes.Add("event list truncated — the audit rows remain complete (audit tree --execution-id )");
- // Final review M8: the page shows this; the CLI silently dropped it.
+ // The page shows this; the CLI silently dropped it.
if (logsTruncated)
notes.Add("inline log lines capped — replay pulls the rest");
diff --git a/src/ZB.MOM.WW.ScadaBridge.CLI/ManagementHttpClient.cs b/src/ZB.MOM.WW.ScadaBridge.CLI/ManagementHttpClient.cs
index a65f5f09..24cc3b03 100644
--- a/src/ZB.MOM.WW.ScadaBridge.CLI/ManagementHttpClient.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.CLI/ManagementHttpClient.cs
@@ -97,7 +97,7 @@ public class ManagementHttpClient : IDisposable
internal ManagementHttpClient(HttpClient httpClient, string baseUrl, string username, string password)
{
_httpClient = httpClient;
- // Test seam (WP2.6e): exposes the constructed HttpClient's effective Timeout
+ // Test seam: exposes the constructed HttpClient's effective Timeout
// without requiring reflection.
EffectiveTimeout = httpClient.Timeout;
_httpClient.BaseAddress = new Uri(baseUrl.TrimEnd('/') + "/");
diff --git a/src/ZB.MOM.WW.ScadaBridge.CLI/OutputFormatter.cs b/src/ZB.MOM.WW.ScadaBridge.CLI/OutputFormatter.cs
index 38a94378..9dd3e4cd 100644
--- a/src/ZB.MOM.WW.ScadaBridge.CLI/OutputFormatter.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.CLI/OutputFormatter.cs
@@ -36,8 +36,7 @@ public static class OutputFormatter
///
/// One row for the overload of WriteTable: either padded
/// column cells like the plain overload, or an already-formatted
- /// line printed verbatim with no column padding/alignment at all (Task 14 review,
- /// 2026-09-09) — used for a note/log line folded onto a step, which must render as a raw
+ /// line printed verbatim with no column padding/alignment at all — used for a note/log line folded onto a step, which must render as a raw
/// indented line under that step's row rather than as a cell in a table column (every
/// other row's cell there would otherwise render blank, and the line's own leading
/// whitespace would be swallowed by column padding).
@@ -50,14 +49,20 @@ public static class OutputFormatter
RawLine = rawLine;
}
+ /// The column cells of a normal row, or null for a raw line.
internal string[]? Cells { get; }
+ /// The verbatim line of a raw row, or null for a column row.
internal string? RawLine { get; }
/// A normal row, rendered as padded columns aligned with the header set.
+ /// The column cell values.
+ /// A column row.
internal static TableRow Columns(string[] cells) => new(cells, null);
/// An already-formatted line, printed exactly as given with no column alignment.
+ /// The preformatted line.
+ /// A raw row.
internal static TableRow Raw(string line) => new(null, line);
}
diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI.SandboxWorker/Program.cs b/src/ZB.MOM.WW.ScadaBridge.CentralUI.SandboxWorker/Program.cs
index a37a77a1..e5b667d2 100644
--- a/src/ZB.MOM.WW.ScadaBridge.CentralUI.SandboxWorker/Program.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI.SandboxWorker/Program.cs
@@ -2,5 +2,7 @@ namespace ZB.MOM.WW.ScadaBridge.CentralUI.SandboxWorker;
internal static class WorkerProgram
{
+ /// Runs the sandbox worker protocol loop until the host closes it.
+ /// A task that represents the asynchronous operation.
public static Task Main() => ScriptAnalysis.SandboxWorkerProtocol.RunWorkerAsync();
}
diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Auth/AuthEndpoints.cs b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Auth/AuthEndpoints.cs
index 34f0edd3..a32f318e 100644
--- a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Auth/AuthEndpoints.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Auth/AuthEndpoints.cs
@@ -228,6 +228,11 @@ public static class AuthEndpoints
}
/// Records real browser input only after authenticating and validating antiforgery.
+ /// The current HTTP context.
+ /// Antiforgery service used to validate the request.
+ /// Tracker that records the session activity.
+ /// Time source for the activity timestamp.
+ /// An HTTP result indicating whether the activity was recorded.
public static async Task HandleActivity(HttpContext context, IAntiforgery antiforgery,
SessionActivityTracker tracker, TimeProvider clock)
{
diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Auth/ClaimsPrincipalExtensions.cs b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Auth/ClaimsPrincipalExtensions.cs
index 05bfc7a9..36d15881 100644
--- a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Auth/ClaimsPrincipalExtensions.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Auth/ClaimsPrincipalExtensions.cs
@@ -13,11 +13,16 @@ namespace ZB.MOM.WW.ScadaBridge.CentralUI.Auth;
public static class ClaimsPrincipalExtensions
{
/// Gets fresh mutation authority, failing closed on a mapping error.
+ /// The authentication state provider.
+ /// The authoritative authentication state.
public static Task GetAuthoritativeStateAsync(this AuthenticationStateProvider provider)
=> provider is CookieAuthenticationStateProvider cookie
? cookie.GetAuthoritativeStateAsync() : provider.GetAuthenticationStateAsync();
/// Checks the current grant before invoking a directly audited application service.
+ /// The authentication state provider.
+ /// The role the current session must hold.
+ /// The username of the authorized principal.
public static async Task RequireRoleAsync(this AuthenticationStateProvider provider, string role)
{
var principal = (await provider.GetAuthoritativeStateAsync()).User;
diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Auth/CookieAuthenticationStateProvider.cs b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Auth/CookieAuthenticationStateProvider.cs
index 19c8c493..bd4997bd 100644
--- a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Auth/CookieAuthenticationStateProvider.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Auth/CookieAuthenticationStateProvider.cs
@@ -23,6 +23,9 @@ public class CookieAuthenticationStateProvider : ServerAuthenticationStateProvid
private readonly SemaphoreSlim _gate = new(1, 1);
/// Captures identity and the factory used for subsequent server-side revalidation.
+ /// Supplies the user captured at construction.
+ /// Factory for fresh scopes used during revalidation.
+ /// Options indicating whether login is disabled (auto-login).
public CookieAuthenticationStateProvider(IHttpContextAccessor httpContextAccessor, IServiceScopeFactory? scopes = null, IOptions? autoLogin = null)
{
_state = new AuthenticationState(httpContextAccessor.HttpContext?.User ?? new ClaimsPrincipal(new ClaimsIdentity()));
@@ -36,6 +39,7 @@ public class CookieAuthenticationStateProvider : ServerAuthenticationStateProvid
public override Task GetAuthenticationStateAsync() => RefreshAsync(failClosed: false);
/// Requires a current mapping before a mutation; mapping faults deny rather than reuse old grants.
+ /// The authoritative authentication state.
public Task GetAuthoritativeStateAsync() => RefreshAsync(failClosed: true);
private async Task RefreshAsync(bool failClosed)
diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/BrowserTime.cs b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/BrowserTime.cs
index 45f0c492..f2eefb92 100644
--- a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/BrowserTime.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/BrowserTime.cs
@@ -23,7 +23,7 @@ public static class BrowserTime
///
/// The value of JavaScript new Date().getTimezoneOffset(): the number
/// of minutes that, added to local time, yields UTC. It is positive
- /// for time zones behind UTC (e.g. +300 for UTC-5) and negative for zones
+ /// for time zones behind UTC (e.g. +300 for five hours behind UTC) and negative for zones
/// ahead (e.g. -120 for UTC+2).
///
/// The equivalent instant in UTC, or null when the input is null.
diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/FlowDebugger.razor.cs b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/FlowDebugger.razor.cs
index 3de0a4f0..14f33503 100644
--- a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/FlowDebugger.razor.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/FlowDebugger.razor.cs
@@ -79,7 +79,7 @@ public partial class FlowDebugger : ComponentBase, IDisposable
[Inject] private ISiteRepository SiteRepository { get; set; } = default!;
///
/// Resolves {"$ref":"lib:Name"} nodes in a method's parameter schema so the raw-JSON
- /// escape hatch validates against the same resolved shape the form renders (#37).
+ /// escape hatch validates against the same resolved shape the form renders.
///
[Inject] private ISchemaLibraryQueryService SchemaLibrary { get; set; } = default!;
[Inject] private SiteScopeService SiteScope { get; set; } = default!;
@@ -154,7 +154,7 @@ public partial class FlowDebugger : ComponentBase, IDisposable
private bool _scriptParamsValid;
///
- /// Method entry (#37): the chosen method's resolved root schema (null when it declares no
+ /// Method entry: the chosen method's resolved root schema (null when it declares no
/// parameters or the definitions do not parse) and its top-level fields — the card renders
/// only when there is at least one. The values live in , the same
/// coerced value tree ParameterValueForm emits for the script pane, so the request
@@ -166,7 +166,7 @@ public partial class FlowDebugger : ComponentBase, IDisposable
private bool _methodParamsValid;
///
- /// Raw-JSON escape hatch for the whole method payload (#37): paste the exact body an
+ /// Raw-JSON escape hatch for the whole method payload: paste the exact body an
/// external caller would send. The text is parsed and schema-validated on every edit;
/// only a valid document reaches , which stays the single source
/// of truth in both modes.
@@ -214,6 +214,8 @@ public partial class FlowDebugger : ComponentBase, IDisposable
/// on a step are NOT here — they are reported separately as
/// .
///
+ /// The flow graph to count log lines in, or null.
+ /// The error, warning, info and total log-line counts.
internal static (int Errors, int Warnings, int Info, int Total) CountLogs(FlowGraph? graph)
{
if (graph is null)
@@ -706,7 +708,7 @@ public partial class FlowDebugger : ComponentBase, IDisposable
// row the run did not already have, the page is no longer short of lines and the flag
// clears. Until then it stays: the site's rows reach central's audit store by telemetry
// 5-30 s later, so a refresh that adds nothing proves nothing.
- // Final review M7: read the OUTCOME's own flag — FlowDebugService copies the raw result's
+ // Read the OUTCOME's own flag — FlowDebugService copies the raw result's
// onto it precisely so a caller need not unwrap Raw, and reading Raw here left the record
// member with no consumer.
var logsTruncated = (_raw.LogsTruncated || outcome.LogsTruncated)
@@ -721,6 +723,9 @@ public partial class FlowDebugger : ComponentBase, IDisposable
=> events?.Count(FlowStepProjector.IsLogLine) ?? 0;
/// Preserves fresh Replay order, then appends live-only rows; duplicate event IDs use the fresh copy.
+ /// Events already held by the page, or null.
+ /// Newly read Replay events, or null.
+ /// The merged event list.
internal static IReadOnlyList MergeEvents(IReadOnlyList? known, IReadOnlyList? fresh)
{
var byId = new Dictionary();
@@ -852,7 +857,7 @@ public partial class FlowDebugger : ComponentBase, IDisposable
return (await SiteScope.PermittedSiteIdsAsync()).Contains(site.Id);
}
- ///
+ /// Cancels any pending outcome refresh when the component is disposed.
public void Dispose() => CancelPendingRefresh();
private async Task LoadReplayAsync()
@@ -972,7 +977,7 @@ public partial class FlowDebugger : ComponentBase, IDisposable
}
///
- /// Form ⇄ Raw JSON (#37). Entering raw mode seeds the editor from the current value tree so
+ /// Form ⇄ Raw JSON. Entering raw mode seeds the editor from the current value tree so
/// the operator edits what the form would have sent; leaving it re-mounts the form over the
/// last VALID tree (the form is keyed on the method, and it is not rendered while raw mode is
/// on, so it mounts fresh and re-reports validity from the tree).
@@ -1054,6 +1059,7 @@ public partial class FlowDebugger : ComponentBase, IDisposable
/// ParameterValueForm emits and what the raw editor parsed), or null when the method
/// declares no parameters. Run is gated on validity, so nothing here re-checks the tree.
///
+ /// The serialized parameters, or null when the method declares none.
internal JsonElement? BuildParameters()
=> _inputFields.Count == 0 ? null : JsonSerializer.SerializeToElement(_methodParams);
@@ -1062,6 +1068,7 @@ public partial class FlowDebugger : ComponentBase, IDisposable
/// nothing. ParameterValueForm already emits the coerced value tree (scalars as
/// bool/long/double/string, objects as dictionaries, lists as lists), so it serializes as-is.
///
+ /// The serialized parameters, or null when the form produced nothing.
internal JsonElement? BuildScriptParameters()
=> _scriptParams.Count == 0 ? null : JsonSerializer.SerializeToElement(_scriptParams);
@@ -1092,7 +1099,7 @@ public partial class FlowDebugger : ComponentBase, IDisposable
///
/// Restores a persisted value tree only when it still validates against the method's current
- /// schema — a stale tree (the schema changed, or the pre-#37 flat string-per-field entry) is
+ /// schema — a stale tree (the schema changed, or the older flat string-per-field entry) is
/// dropped rather than painted into the form as errors.
///
private async Task RestoreInputsAsync(int methodId)
diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/InstanceConfigure.razor.cs b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/InstanceConfigure.razor.cs
index d3177571..187bd507 100644
--- a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/InstanceConfigure.razor.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/InstanceConfigure.razor.cs
@@ -123,6 +123,10 @@ public partial class InstanceConfigure
/// counts: it pins a reportable template alarm off for this instance, which is a
/// real override and not the same as inheriting.
///
+ /// The override JSON entered in the modal, if any.
+ /// The priority override, if one was chosen.
+ /// The MES-reportable override, if one was chosen.
+ /// true when the modal carries any override worth persisting; otherwise false.
internal static bool HasOverrideDelta(string? overrideJson, int? priority, bool? mesReportable)
=> !string.IsNullOrWhiteSpace(overrideJson) || priority.HasValue || mesReportable.HasValue;
diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/TransportImport.razor.cs b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/TransportImport.razor.cs
index ff0f2059..3789134f 100644
--- a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/TransportImport.razor.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/TransportImport.razor.cs
@@ -112,7 +112,7 @@ public partial class TransportImport : ComponentBase, IDisposable
///
/// Guards together with (register row
- /// 103, the P6-6 lesser instance — mirrors DebugView's _sessionLock).
+ /// 103 — mirrors DebugView's _sessionLock).
/// awaits , and the
/// circuit can be torn down inside that window; publishing the freshly-opened session
/// and observing disposal must be one atomic step so exactly one side (this method or
@@ -258,7 +258,7 @@ public partial class TransportImport : ComponentBase, IDisposable
var loaded = await BundleImporter.LoadAsync(
stream, passphrase, CancellationToken.None, ClientAddress.ClientAddress);
- // Register row 103 (P6-6 lesser instance): the component may have been
+ // Register row 103: the component may have been
// disposed while LoadAsync was in flight (navigation away mid-upload).
// Publish and re-check disposal atomically under _sessionLock so exactly
// one side tears the freshly-opened session down.
@@ -278,9 +278,9 @@ public partial class TransportImport : ComponentBase, IDisposable
}
catch (ArgumentException)
{
- // Encrypted bundle, no passphrase supplied — caller advances to Step 2.
+ // Encrypted bundle, no passphrase supplied — caller advances to the passphrase step.
// We deliberately do NOT set _errorMessage here; the page surfaces
- // an empty Step-2 prompt instead.
+ // an empty passphrase prompt instead.
_session = null;
throw;
}
@@ -977,7 +977,7 @@ public partial class TransportImport : ComponentBase, IDisposable
/// Register row 103: the flag is set and the session taken under the same lock
/// publishes through, so a still
/// awaiting either hands the session over here
- /// or abandons it itself — never neither (mirrors DebugView's P6-6 fix).
+ /// or abandons it itself — never neither (mirrors DebugView's fix).
///
///
public void Dispose()
diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/FlowGraphView.razor.cs b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/FlowGraphView.razor.cs
index 556f8e7e..bc9befc5 100644
--- a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/FlowGraphView.razor.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/FlowGraphView.razor.cs
@@ -138,6 +138,8 @@ public partial class FlowGraphView : ComponentBase
}
/// Formats a coordinate culture-invariantly.
+ /// The value to format.
+ /// The value formatted with up to two decimals using the invariant culture.
public static string F(double v) => v.ToString("0.##", CultureInfo.InvariantCulture);
private string RenderedWidth => F(_layout.Width * Math.Max(0.1, Zoom));
@@ -199,6 +201,8 @@ public partial class FlowGraphView : ComponentBase
=> n.Step != null && n.Kind is FlowStepKind.ApiCall or FlowStepKind.DbCall or FlowStepKind.Notify;
/// Pill wording for an outbound step's folded outcome (plan D3: the latest lifecycle row decides).
+ /// The folded outcome of the outbound step.
+ /// The short pill label for the outcome.
public static string PillText(FlowStepOutcome outcome) => outcome switch
{
FlowStepOutcome.Ok => "Ok",
@@ -224,6 +228,9 @@ public partial class FlowGraphView : ComponentBase
/// sync database call's operation detail (write · 3 rows) kept in front of it. The projector's
/// subtitle already leads with the audit status, which the pill now carries, so that head is dropped.
///
+ /// The kind of the outbound step.
+ /// The step's projected subtitle, which may lead with the audit status.
+ /// The mode text shown after the status pill.
public static string OutboundMode(FlowStepKind kind, string? subtitle)
{
if (kind == FlowStepKind.Notify) return "notify";
@@ -243,6 +250,8 @@ public partial class FlowGraphView : ComponentBase
/// everything else — info, debug, an unknown level — counts as info, so the badge
/// never silently drops a line.
///
+ /// The step whose log lines are counted; may be null.
+ /// The error, warning and info line counts.
internal static (int Errors, int Warnings, int Info) LogCounts(FlowStep? s)
{
if (s?.Logs is not { Count: > 0 } lines) return (0, 0, 0);
@@ -260,6 +269,8 @@ public partial class FlowGraphView : ComponentBase
}
/// Hover text for the log badge — the non-zero levels only ("1 error, 2 warnings, 4 info").
+ /// The per-severity log line counts.
+ /// The hover text listing the non-zero levels.
internal static string LogTitle((int Errors, int Warnings, int Info) counts)
{
var (errors, warnings, info) = counts;
@@ -273,12 +284,14 @@ public partial class FlowGraphView : ComponentBase
///
/// The subtitle a failed or timed-out step shows instead of its own: {Type}: {message}, where
/// the type is the exception's name without its namespace. Parsing is
- /// (final review M1 — the same parser the step detail panel
+ /// (the same parser the step detail panel
/// uses), which takes the first line of the captured and follows
/// its " ---> " chain to the innermost exception. Falls back to the line itself when it
/// carries no ": ", and to when there is no usable detail.
/// Null for every non-failed step.
///
+ /// The step to build the subtitle for.
+ /// The exception subtitle, or null for a step that did not fail.
internal static string? ExceptionSubtitle(FlowStep s)
{
if (s.Outcome is not (FlowStepOutcome.Failed or FlowStepOutcome.TimedOut)) return null;
@@ -305,6 +318,8 @@ public partial class FlowGraphView : ComponentBase
/// than one row — the submit→terminal wall time with a leading + (+4.1 s), since that
/// span is elapsed time downstream of the script, not time the script spent (node anatomy).
///
+ /// The layout node to describe.
+ /// The formatted duration text for the node's head row.
public static string Duration(FlowLayoutNode n)
{
if (n.Step?.DurationMs is not int ms)
@@ -317,6 +332,9 @@ public partial class FlowGraphView : ComponentBase
}
/// Clips a gutter label to what fits, keeping the full text in a title.
+ /// The label text; may be null.
+ /// The maximum number of characters to keep.
+ /// The text, clipped to fit when it is longer than the maximum.
public static string Clip(string? text, int max)
{
if (string.IsNullOrEmpty(text)) return string.Empty;
@@ -329,6 +347,8 @@ public partial class FlowGraphView : ComponentBase
/// titles on the Tags lane are attribute paths that mostly differ in their last segment, so the
/// shared head is shown once in the lane gutter and each node shows the tail that tells it apart.
///
+ /// The node titles on the lane.
+ /// The shared dotted prefix, or null when there is none worth showing.
public static string? CommonDottedPrefix(IReadOnlyList titles)
{
if (titles.Count < 2) return null;
@@ -352,6 +372,8 @@ public partial class FlowGraphView : ComponentBase
/// camel-case boundaries so a wrapped title breaks at DelmiaRecipe|Download rather than
/// mid-word; the browser still falls back to breaking anywhere for a single overlong segment.
///
+ /// The title text; may be null.
+ /// The text with zero-width spaces inserted at preferred break points.
public static string WrapHints(string? text)
{
if (string.IsNullOrEmpty(text)) return string.Empty;
@@ -373,6 +395,9 @@ public partial class FlowGraphView : ComponentBase
/// Removes where it starts or a comma-separated
/// item within it (the write subtitle is a.X = 1, a.Y = 2); null/empty text passes through.
///
+ /// The text to strip; may be null.
+ /// The prefix to remove; may be null.
+ /// The text with the prefix removed.
public static string? StripPrefix(string? text, string? prefix)
{
if (string.IsNullOrEmpty(text) || string.IsNullOrEmpty(prefix)) return text;
@@ -385,6 +410,14 @@ public partial class FlowGraphView : ComponentBase
/// <text> tag for its own markup-mode switch and refuses attributes on it,
/// so the SVG element of the same name cannot be written literally inside a code block.
///
+ /// The x coordinate.
+ /// The y coordinate.
+ /// The CSS class for the element.
+ /// The text content, encoded by this method.
+ /// The optional text-anchor value.
+ /// The optional tooltip title.
+ /// The optional data-test attribute value.
+ /// The encoded SVG text element markup.
public static MarkupString SvgText(string x, string y, string cssClass, string content, string? anchor = null, string? title = null, string? dataTest = null)
{
var sb = new StringBuilder(" to (the middle of a same-lane straight line).
/// Kept in step with those two path builders.
///
+ /// The source node.
+ /// The target node.
+ /// True when the connector is an async branch.
+ /// The midpoint of the connector's vertical run.
public static (double X, double Y) SpawnBadgePoint(FlowLayoutNode a, FlowLayoutNode b, bool isAsync)
{
var x1 = a.X + NodeWidth;
@@ -460,6 +497,7 @@ public partial class FlowGraphView : ComponentBase
///
/// The real graph, or null.
/// The prediction, or null.
+ /// The computed layout.
public static FlowLayout ComputeLayout(FlowGraph? graph, PredictedFlow? predicted)
{
var hasReal = graph is { Steps.Count: > 0 };
@@ -652,6 +690,8 @@ public partial class FlowGraphView : ComponentBase
/// up to named, the rest folded into +n
/// (Delmia.AckRecipe · MesDb · Planners +1). Empty when the lane is unused.
///
+ /// The distinct external targets in first-seen order.
+ /// The gutter sublabel text.
public static string ExternalSublabel(IReadOnlyList distinctTargets)
{
if (distinctTargets.Count == 0) return string.Empty;
@@ -666,6 +706,9 @@ public partial class FlowGraphView : ComponentBase
/// crosses a node sitting between the two lanes. Falls back to when
/// the target is not below the source.
///
+ /// The source node.
+ /// The target node.
+ /// The SVG path data for the connector.
public static string AsyncPath(FlowLayoutNode a, FlowLayoutNode b)
{
if (b.Y <= a.Y)
@@ -682,6 +725,9 @@ public partial class FlowGraphView : ComponentBase
/// of : straight when in the same lane, otherwise right-down/up-right
/// through the inter-column gap. A backwards edge (target left of source) loops under.
///
+ /// The source node.
+ /// The target node.
+ /// The SVG path data for the connector.
public static string OrthogonalPath(FlowLayoutNode a, FlowLayoutNode b)
{
var x1 = a.X + NodeWidth;
diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/IDialogService.cs b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/IDialogService.cs
index 6a7fd9de..8df2331c 100644
--- a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/IDialogService.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/IDialogService.cs
@@ -58,8 +58,8 @@ public interface IDialogService
/// button's styling.
/// Verb rendered on the confirm button. Only
/// falls back to "Delete" when this is
- /// blank; the other severities fall back to "Confirm". The row-28 rule still
- /// applies — name the action.
+ /// blank; the other severities fall back to "Confirm". The rule to name the
+ /// action still applies.
/// A task that resolves to true when the user confirms, or false when cancelled.
Task ConfirmAsync(string title, string message, DialogSeverity severity, string? confirmLabel = null);
diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/ScriptAnalysis/BoundedSandboxWriter.cs b/src/ZB.MOM.WW.ScadaBridge.CentralUI/ScriptAnalysis/BoundedSandboxWriter.cs
index 95d3814e..711c2ea6 100644
--- a/src/ZB.MOM.WW.ScadaBridge.CentralUI/ScriptAnalysis/BoundedSandboxWriter.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/ScriptAnalysis/BoundedSandboxWriter.cs
@@ -8,11 +8,17 @@ public sealed class BoundedSandboxWriter(int capacity) : StringWriter
private readonly StringBuilder _retained = new(Math.Max(0, capacity));
private readonly object _gate = new();
private long _dropped;
+ /// Number of characters currently retained.
public int RetainedCharacters { get { lock (_gate) return _retained.Length; } }
+ /// Number of characters discarded because the budget was reached.
public long DroppedCharacters { get { lock (_gate) return _dropped; } }
+ ///
public override void Write(char value) { Span one = stackalloc char[1]; one[0] = value; Write(one); }
+ ///
public override void Write(string? value) { if (value is not null) Write(value.AsSpan()); }
+ ///
public override void Write(char[] buffer, int index, int count) => Write(buffer.AsSpan(index, count));
+ ///
public override void Write(ReadOnlySpan buffer)
{
lock (_gate)
@@ -22,24 +28,32 @@ public sealed class BoundedSandboxWriter(int capacity) : StringWriter
_dropped = Math.Min(long.MaxValue - (buffer.Length - take), _dropped) + buffer.Length - take;
}
}
+ ///
public override void WriteLine(string? value) { lock (_gate) { Write(value); Write(NewLine); } }
+ ///
public override void WriteLine() => Write(NewLine);
+ ///
public override string ToString() { lock (_gate) return _retained.ToString() + (_dropped > 0 ? "\n… (truncated)" : ""); }
}
/// Rejects serialized data once its byte budget is reached, without retaining the excess.
public sealed class BoundedSandboxStream(int capacity) : MemoryStream
{
+ ///
public override void Write(byte[] buffer, int offset, int count) => Write(buffer.AsSpan(offset, count));
+ ///
public override void Write(ReadOnlySpan buffer)
{
if (Length + buffer.Length > capacity) throw new InvalidDataException("Sandbox output exceeds its byte budget.");
var bounded = buffer.ToArray();
base.Write(bounded, 0, bounded.Length);
}
+ ///
public override void WriteByte(byte value) { Span one = stackalloc byte[1]; one[0] = value; Write(one); }
+ ///
public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default)
{ cancellationToken.ThrowIfCancellationRequested(); Write(buffer.Span); return ValueTask.CompletedTask; }
+ ///
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{ cancellationToken.ThrowIfCancellationRequested(); Write(buffer, offset, count); return Task.CompletedTask; }
}
diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/ScriptAnalysis/SandboxScriptHost.cs b/src/ZB.MOM.WW.ScadaBridge.CentralUI/ScriptAnalysis/SandboxScriptHost.cs
index 56be1e37..6e6f957a 100644
--- a/src/ZB.MOM.WW.ScadaBridge.CentralUI/ScriptAnalysis/SandboxScriptHost.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/ScriptAnalysis/SandboxScriptHost.cs
@@ -521,7 +521,7 @@ public class SandboxAttributeAccessor
///
/// Sandbox stand-in for the reset-aware AttributeAccessor.WriteBatchAndWaitAsync
- /// overload (Gitea #36): present for editor/compile parity, throws
+ /// overload: present for editor/compile parity, throws
/// when run in Test Run (no device batch-write transport).
///
/// The attribute values to write as a batch.
diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/ScriptAnalysis/SandboxWorkerProtocol.cs b/src/ZB.MOM.WW.ScadaBridge.CentralUI/ScriptAnalysis/SandboxWorkerProtocol.cs
index 9e80b5f1..04f048ab 100644
--- a/src/ZB.MOM.WW.ScadaBridge.CentralUI/ScriptAnalysis/SandboxWorkerProtocol.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/ScriptAnalysis/SandboxWorkerProtocol.cs
@@ -21,10 +21,22 @@ public static class SandboxWorkerProtocol
private sealed record ConnectionInfo(string Provider, string ConnectionString, bool Open);
/// Runs one child under a deadline; a slot remains charged until confirmed process exit.
+ /// The sandbox run request to execute in the child process.
+ /// The service provider supplying host services for the run.
+ /// The shared-script catalog the child resolves shared scripts through.
+ /// Cancellation token from the caller.
+ /// The sandbox run result.
public static async Task RunAsync(SandboxRunRequest request, IServiceProvider services,
ISharedScriptCatalog catalog, CancellationToken caller)
=> await RunCoreAsync(request, services, catalog, caller);
+ /// Core run implementation; exposes a callback that reports the child process id once started.
+ /// The sandbox run request to execute in the child process.
+ /// The service provider supplying host services for the run.
+ /// The shared-script catalog the child resolves shared scripts through.
+ /// Cancellation token from the caller.
+ /// Optional callback invoked with the child process id after launch.
+ /// The sandbox run result.
internal static async Task RunCoreAsync(SandboxRunRequest request, IServiceProvider services,
ISharedScriptCatalog catalog, CancellationToken caller, Action? started = null)
{
@@ -163,6 +175,7 @@ public static class SandboxWorkerProtocol
}
/// Worker executable entry. Protocol streams are captured before script console routing.
+ /// A task that represents the asynchronous operation.
public static async Task RunWorkerAsync()
{
var input = Console.OpenStandardInput(); var output = Console.OpenStandardOutput();
@@ -195,15 +208,21 @@ public static class SandboxWorkerProtocol
}
finally { _gate.Release(); }
}
+ ///
public Task> GetShapesAsync() => Task.FromResult>([]);
+ ///
public async Task GetByNameAsync(string name, CancellationToken cancellationToken = default)
=> (await Call("shared", [name], cancellationToken)).Deserialize(Json);
+ ///
public async Task CallAsync(string systemName, string methodName, IReadOnlyDictionary? parameters = null, CancellationToken cancellationToken = default)
=> (await Call("external", [systemName, methodName, parameters], cancellationToken)).Deserialize(Json)!;
+ ///
public async Task CachedCallAsync(string systemName, string methodName, IReadOnlyDictionary? parameters = null, string? originInstanceName = null, CancellationToken cancellationToken = default, TrackedOperationId? trackedOperationId = null, Guid? executionId = null, string? sourceScript = null, Guid? parentExecutionId = null)
=> (await Call("cachedExternal", [systemName, methodName, parameters, originInstanceName], cancellationToken)).Deserialize(Json)!;
+ ///
public async Task CachedWriteAsync(string connectionName, string sql, IReadOnlyDictionary? parameters = null, string? originInstanceName = null, CancellationToken cancellationToken = default, TrackedOperationId? trackedOperationId = null, Guid? executionId = null, string? sourceScript = null, Guid? parentExecutionId = null)
=> (await Call("cachedDb", [connectionName, sql, parameters, originInstanceName], cancellationToken)).Deserialize(Json)!;
+ ///
public async Task GetConnectionAsync(string connectionName, CancellationToken cancellationToken = default)
{
var info = (await Call("connection", [connectionName], cancellationToken)).Deserialize(Json)!;
@@ -213,11 +232,15 @@ public static class SandboxWorkerProtocol
if (info.Open) await connection.OpenAsync(cancellationToken);
return connection;
}
+ ///
public async Task
public sealed class ConnectionHealthQueryService : IConnectionHealthQueryService
{
diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Services/FlowDebugService.cs b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Services/FlowDebugService.cs
index 575a927e..4c7c106c 100644
--- a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Services/FlowDebugService.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Services/FlowDebugService.cs
@@ -59,6 +59,8 @@ public sealed class FlowDebugService : IFlowDebugService
/// the run itself at timeoutSeconds + RunGrace (see FlowDebugRunner.RunMethodAsync),
/// so the client must wait at least that long or it cuts the run off before the server does.
///
+ /// The method's own timeout in seconds.
+ /// The Ask timeout to use for the run.
public static TimeSpan RunAskTimeoutFor(int timeoutSeconds)
{
var t = TimeSpan.FromSeconds(Math.Max(1, timeoutSeconds)) + FlowDebugRunner.RunGrace + ClientSlack;
@@ -69,6 +71,8 @@ public sealed class FlowDebugService : IFlowDebugService
/// Client-side Ask wait for a WRITE-kind script run (): the
/// trigger-wait window plus .
///
+ /// The trigger-wait window in seconds.
+ /// The Ask timeout to use for the run.
public static TimeSpan ValueChangeAskTimeoutFor(int waitSeconds)
=> TimeSpan.FromSeconds(Math.Max(1, waitSeconds)) + ClientSlack;
@@ -110,6 +114,14 @@ public sealed class FlowDebugService : IFlowDebugService
IReadOnlyList Options);
/// Initializes the service.
+ /// Holder for the management actor reference.
+ /// Provides the caller's authentication state.
+ /// Repository for inbound API methods.
+ /// Repository for templates and instances.
+ /// Flattening pipeline used to resolve instance scripts.
+ /// Communication service for central-to-site requests.
+ /// Logger for diagnostics.
+ /// Repository for sites.
public FlowDebugService(
ManagementActorHolder holder,
AuthenticationStateProvider auth,
diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Services/IAlarmSummaryService.cs b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Services/IAlarmSummaryService.cs
index 9ffe92ea..1735fb18 100644
--- a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Services/IAlarmSummaryService.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Services/IAlarmSummaryService.cs
@@ -44,7 +44,7 @@ public interface IAlarmSummaryService
///
/// Builds an directly from an in-memory live
- /// snapshot (plan #10, Task 5) — the current alarms served by
+ /// snapshot — the current alarms served by
///
/// — instead of fanning out per-instance snapshot Asks. Each
/// is flattened to one
diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Services/IFlowDebugService.cs b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Services/IFlowDebugService.cs
index eb3d33b3..2fbb0df5 100644
--- a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Services/IFlowDebugService.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Services/IFlowDebugService.cs
@@ -28,6 +28,9 @@ public sealed record FlowDebugOutcome(
public bool Success => Graph != null && Error == null;
/// Creates a failed outcome.
+ /// The error message.
+ /// The machine-readable error code.
+ /// A failed outcome carrying the error.
public static FlowDebugOutcome Fail(string error, string errorCode) => new(null, null, error, errorCode);
}
@@ -83,18 +86,21 @@ public interface IFlowDebugService
/// The instance's scripts from its flattened configuration, ordered by canonical name; empty when the instance does not exist.
/// The instance id.
/// Cancellation token.
+ /// The instance's scripts ordered by canonical name.
Task> GetInstanceScriptsAsync(int instanceId, CancellationToken cancellationToken = default);
/// Predicts the flow of running : the write + spawned script for a write kind, a RouteCall root + the script for a call kind.
/// The instance id.
/// Canonical script name (case-insensitive).
/// Cancellation token.
+ /// The predicted flow for the script.
Task PredictScriptAsync(int instanceId, string scriptName, CancellationToken cancellationToken = default);
/// Runs an inbound API method with tracing on the active central node and returns the flow.
/// The method id.
/// Flat JSON object of parameters, or null.
/// Cancellation token.
+ /// The outcome of the run, including the built flow graph.
Task RunMethodAsync(int apiMethodId, JsonElement? parameters, CancellationToken cancellationToken = default);
/// Runs a script by name (write kinds take , call kinds ) and returns the flow.
@@ -103,11 +109,13 @@ public interface IFlowDebugService
/// Call kinds: flat JSON object of parameters, or null.
/// Write kinds: the new value for the trigger's attribute, in canonical string form.
/// Cancellation token.
+ /// The outcome of the run, including the built flow graph.
Task RunScriptAsync(int instanceId, string scriptName, JsonElement? parameters, string? value, CancellationToken cancellationToken = default);
/// Rebuilds a past flow from the central audit store.
/// Any execution id in the chain.
/// Cancellation token.
+ /// The outcome of the replay, including the rebuilt flow graph.
Task ReplayAsync(Guid executionId, CancellationToken cancellationToken = default);
///
@@ -120,6 +128,7 @@ public interface IFlowDebugService
///
/// The outbound step to retry.
/// Cancellation token.
+ /// The result of the retry request.
Task RetryOutboundAsync(FlowStep step, CancellationToken cancellationToken = default);
///
@@ -128,5 +137,6 @@ public interface IFlowDebugService
///
/// The outbound step to discard.
/// Cancellation token.
+ /// The result of the discard request.
Task DiscardOutboundAsync(FlowStep step, CancellationToken cancellationToken = default);
}
diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Services/IKpiSnapshotCache.cs b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Services/IKpiSnapshotCache.cs
index 4785c4a1..e99dcad2 100644
--- a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Services/IKpiSnapshotCache.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Services/IKpiSnapshotCache.cs
@@ -6,7 +6,7 @@ namespace ZB.MOM.WW.ScadaBridge.CentralUI.Services;
///
/// Process-level memoized point-in-time KPI snapshots shared by every Blazor circuit on
-/// this node (arch-review WP2.4, finding #8 — the per-circuit polling half).
+/// this node (the per-circuit polling half).
///
/// Every KPI here is a global, point-in-time aggregate: it does not vary by user,
/// site scope, or role, so N circuits asking for it inside one refresh window are asking
diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Services/PollGate.cs b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Services/PollGate.cs
index 065bbffa..c873ebba 100644
--- a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Services/PollGate.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Services/PollGate.cs
@@ -13,6 +13,9 @@ public sealed class PollGate
public bool TryEnter() => Interlocked.CompareExchange(ref _inFlight, 1, 0) == 0;
/// Runs and observes a posted refresh, releasing the gate even if dispatch is rejected.
+ /// Delegate that posts the refresh.
+ /// Callback invoked with any exception thrown by .
+ /// A task that represents the asynchronous operation.
public async Task RunAsync(Func dispatch, Action onError)
{
if (!TryEnter()) return;
diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Services/SharedAlarmSummaryService.cs b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Services/SharedAlarmSummaryService.cs
index 3457ded2..3156ef6a 100644
--- a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Services/SharedAlarmSummaryService.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Services/SharedAlarmSummaryService.cs
@@ -6,7 +6,7 @@ using ZB.MOM.WW.ScadaBridge.Communication;
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Services;
///
-/// Process-level memoizing façade over (arch-review WP2.4).
+/// Process-level memoizing façade over .
///
/// The Alarm Summary page polls every 15s per circuit, and each poll fans one debug
/// snapshot Ask out to every Enabled instance on the site. Ten operators watching one site
diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Services/TemplatePathFormatter.cs b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Services/TemplatePathFormatter.cs
index fb5fdfdf..c7f3f880 100644
--- a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Services/TemplatePathFormatter.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Services/TemplatePathFormatter.cs
@@ -17,6 +17,9 @@ public static class TemplatePathFormatter
/// Builds the display path for by walking its
/// folder chain upward through .
///
+ /// The template whose path is built.
+ /// Folders keyed by id, used to walk the parent chain.
+ /// The folder-qualified display path.
public static string Format(Template template, IReadOnlyDictionary foldersById)
{
var parts = new List { template.Name };
diff --git a/src/ZB.MOM.WW.ScadaBridge.ClusterInfrastructure/ClusterBootstrapGuard.cs b/src/ZB.MOM.WW.ScadaBridge.ClusterInfrastructure/ClusterBootstrapGuard.cs
index cd0fb8e1..7c6eb211 100644
--- a/src/ZB.MOM.WW.ScadaBridge.ClusterInfrastructure/ClusterBootstrapGuard.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.ClusterInfrastructure/ClusterBootstrapGuard.cs
@@ -3,7 +3,7 @@ using System.Text.RegularExpressions;
namespace ZB.MOM.WW.ScadaBridge.ClusterInfrastructure;
///
-/// Pure decision logic for the simultaneous-cold-start split-brain guard (Gitea #33).
+/// Pure decision logic for the simultaneous-cold-start split-brain guard.
///
///
///
diff --git a/src/ZB.MOM.WW.ScadaBridge.ClusterInfrastructure/ClusterOptions.cs b/src/ZB.MOM.WW.ScadaBridge.ClusterInfrastructure/ClusterOptions.cs
index 87b5952e..8c47fcee 100644
--- a/src/ZB.MOM.WW.ScadaBridge.ClusterInfrastructure/ClusterOptions.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.ClusterInfrastructure/ClusterOptions.cs
@@ -119,7 +119,7 @@ public class ClusterOptions
/// The simultaneous-cold-start split-brain guard (ScadaBridge:Cluster:BootstrapGuard).
/// Default OFF — a dark switch, so existing deployments and tests keep Akka's config-driven
/// self-first auto-join byte-identical. See for the
- /// decision logic and ClusterBootstrapCoordinator for the runtime (Gitea #33).
+ /// decision logic and ClusterBootstrapCoordinator for the runtime.
///
public ClusterBootstrapGuardOptions BootstrapGuard { get; set; } = new();
}
diff --git a/src/ZB.MOM.WW.ScadaBridge.ClusterInfrastructure/ClusterOptionsValidator.cs b/src/ZB.MOM.WW.ScadaBridge.ClusterInfrastructure/ClusterOptionsValidator.cs
index 26f26e05..acca52d3 100644
--- a/src/ZB.MOM.WW.ScadaBridge.ClusterInfrastructure/ClusterOptionsValidator.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.ClusterInfrastructure/ClusterOptionsValidator.cs
@@ -82,7 +82,7 @@ public sealed class ClusterOptionsValidator : OptionsValidatorBase
- /// When the bootstrap guard is enabled (Gitea #33), its timing knobs must be positive. A
+ /// When the bootstrap guard is enabled, its timing knobs must be positive. A
/// zero/negative in particular
/// silently degrades the guard to "never wait, always conclude the partner is dead" — the
/// higher node would form alone immediately and re-open the very split the guard exists to
diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Repositories/IAuditLogRepository.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Repositories/IAuditLogRepository.cs
index 12d4eab3..e75ad3d8 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Repositories/IAuditLogRepository.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Repositories/IAuditLogRepository.cs
@@ -292,6 +292,7 @@ public interface IAuditLogRepository
///
/// Any execution id in the chain; the implementation walks to the root and back down.
/// Cancellation token.
+ /// Optional site scope restricting which sites' rows are visible; null means unrestricted.
/// A task that resolves to the full execution tree rooted at the topmost ancestor, one node per distinct execution.
Task> GetExecutionTreeAsync(
Guid executionId,
@@ -307,6 +308,7 @@ public interface IAuditLogRepository
///
/// Any execution id in the chain.
/// Cancellation token.
+ /// Optional site scope restricting which sites' rows are visible; null means unrestricted.
/// The canonical audit events that make up the flow, oldest first.
Task> GetTraceEventsAsync(
Guid executionId,
diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Repositories/INotificationOutboxRepository.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Repositories/INotificationOutboxRepository.cs
index 4b16636e..777b410c 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Repositories/INotificationOutboxRepository.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Repositories/INotificationOutboxRepository.cs
@@ -93,7 +93,7 @@ public interface INotificationOutboxRepository
/// is its own transaction.
///
///
- /// Why this exists (arch-review 2026-08-19 P5-4). The operator retry/
+ /// Why this exists. The operator retry/
/// discard handlers are read-check-write across two round trips:
/// reads the row, the handler checks its
/// in-memory Status, then a write persists the transition. A detached
diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Repositories/ISecuredWriteRepository.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Repositories/ISecuredWriteRepository.cs
index 4e6aae96..dbc51ff7 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Repositories/ISecuredWriteRepository.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Repositories/ISecuredWriteRepository.cs
@@ -93,7 +93,7 @@ public interface ISecuredWriteRepository
/// the compare-and-swap guard for the approve-vs-reject race: a stale-read reject
/// used to land a blind full-row update while a concurrent approve's multi-second
/// site relay was still in flight, marking the row Rejected while the plant
- /// write physically executed (arch-review 2026-08-19 P6-1). The conditional
+ /// write physically executed. The conditional
/// WHERE Status='Pending' makes exactly one of approve/reject/expiry win.
/// The loser observes false and must record nothing — no status change and no
/// reject audit row.
diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Repositories/ITemplateEngineRepository.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Repositories/ITemplateEngineRepository.cs
index 49f7b277..08f3106b 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Repositories/ITemplateEngineRepository.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Repositories/ITemplateEngineRepository.cs
@@ -252,6 +252,12 @@ public interface ITemplateEngineRepository
Task> GetInstancesBySiteIdAsync(int siteId, CancellationToken cancellationToken = default);
/// Reads one bounded, untracked page of topology instance rows for a site/area.
+ /// The site ID.
+ /// The area ID to filter by, or null for every area.
+ /// The number of rows to skip.
+ /// The maximum number of rows to return.
+ /// Cancellation token.
+ /// A task that resolves to a read-only page of instances.
Task> GetTopologyInstancesAsync(int siteId, int? areaId, int skip, int take, CancellationToken cancellationToken = default);
/// Retrieves an instance by unique name.
diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Services/IConfigurationCommandTransaction.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Services/IConfigurationCommandTransaction.cs
index 5742f5b8..346f12d0 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Services/IConfigurationCommandTransaction.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Services/IConfigurationCommandTransaction.cs
@@ -4,6 +4,8 @@ namespace ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
public interface IConfigurationCommandTransaction
{
/// Each database retry supplies a fresh scope; the operation must not perform external side effects.
+ /// The database-only operation to run within the transaction.
+ /// The operation's result, if any.
Task ExecuteAsync(Func> operation);
}
@@ -11,6 +13,7 @@ public interface IConfigurationCommandTransaction
public interface IConfigurationCommitActions
{
/// Queues an idempotent local maintenance action.
+ /// The action to run after a successful commit.
void Add(Func action);
}
diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Services/ISandboxDatabaseConnectionSource.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Services/ISandboxDatabaseConnectionSource.cs
index 5c94cf26..af3960e0 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Services/ISandboxDatabaseConnectionSource.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Services/ISandboxDatabaseConnectionSource.cs
@@ -2,5 +2,9 @@ namespace ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
/// Private worker handoff for an existing configured SQL connection. Never log or expose the returned secret.
public interface ISandboxDatabaseConnectionSource
{
+ /// Resolves the connection string for a configured connection.
+ /// The configured database connection name.
+ /// Cancellation token.
+ /// The connection string; a secret that must never be logged or exposed.
Task GetSandboxConnectionStringAsync(string connectionName, CancellationToken cancellationToken);
}
diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Services/ISiteAuditQueue.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Services/ISiteAuditQueue.cs
index 37abfca5..532a96fb 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Services/ISiteAuditQueue.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Services/ISiteAuditQueue.cs
@@ -110,7 +110,7 @@ public interface ISiteAuditQueue
/// Lower bound timestamp (UTC).
/// Maximum number of rows to return.
///
- /// Composite-keyset tiebreak cursor (WP2.3), mirroring
+ /// Composite-keyset tiebreak cursor, mirroring
/// : when non-null it is the
/// ("D" GUID form) of the last row already consumed at
/// , and only rows strictly after the composite
@@ -125,7 +125,7 @@ public interface ISiteAuditQueue
DateTime sinceUtc, int batchSize, string? afterId = null, CancellationToken ct = default);
///
- /// Reconciliation-pull commit surface, cursor form (WP2.3): flips every row at or before
+ /// Reconciliation-pull commit surface, cursor form: flips every row at or before
/// the composite cursor (, )
/// from /
/// to
@@ -231,6 +231,12 @@ public interface ISiteAuditQueue
/// (OccurredAtUtc, EventId), at most rows. Independent of
/// forwarding state — moves no cursor and flips nothing. At most 64 ids per call.
///
+ /// Execution ids to match against ExecutionId or ParentExecutionId.
+ /// Lower bound on OccurredAtUtc (UTC).
+ /// Maximum number of rows to return.
+ /// Cancellation token.
+ /// Optional EventId keyset cursor for paging past rows already consumed.
+ /// A task that resolves to the matching audit events ordered by (OccurredAtUtc, EventId).
Task> ReadByExecutionIdsAsync(
IReadOnlyCollection executionIds, DateTime sinceUtc, int limit, CancellationToken ct = default, string? afterEventId = null);
}
diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/DebugView/DebugSnapshotRequest.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/DebugView/DebugSnapshotRequest.cs
index 306eb9fb..ce2bcb6b 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/DebugView/DebugSnapshotRequest.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/DebugView/DebugSnapshotRequest.cs
@@ -7,7 +7,7 @@ namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.DebugView;
/// Correlation id echoed on the reply.
///
/// When true the site builds ONLY the alarm half of the snapshot and returns an
-/// empty list (wire efficiency, WP2.3).
+/// empty list (wire efficiency).
/// Set by the central per-site live alarm cache, whose seed/reconcile fan-out discards
/// every attribute row anyway. Defaults to false so the Debug View and every other
/// caller keep the full snapshot; additive on the wire
diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Health/SecretMirrorNodeStatus.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Health/SecretMirrorNodeStatus.cs
index 21acdffd..b500924f 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Health/SecretMirrorNodeStatus.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Health/SecretMirrorNodeStatus.cs
@@ -2,7 +2,7 @@ namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Health;
///
/// One site node's latest deployed-secrets mirror outcome, as carried on
-/// (review finding M1).
+/// .
///
///
/// Every site node runs its own DeployedSecretsMirror and records its current
diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Health/SiteHealthReport.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Health/SiteHealthReport.cs
index 7525ebd0..4833baa1 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Health/SiteHealthReport.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Health/SiteHealthReport.cs
@@ -75,7 +75,7 @@ public record SiteHealthReport(
public double? ScriptOldestBusyAgeSeconds { get; init; }
///
- /// WP3.1: script-execution worker threads the stuck-script watchdog has DETACHED and
+ /// Script-execution worker threads the stuck-script watchdog has DETACHED and
/// replaced — a script wedged past its deadline plus grace in uninterruptible blocking
/// I/O, so the pool started a fresh thread and the wedged one will exit only when its
/// body finally returns. Point-in-time, refreshed by ScriptSchedulerStatsReporter.
@@ -85,7 +85,7 @@ public record SiteHealthReport(
public int DetachedScriptThreads { get; init; }
///
- /// WP3.1: per-interval count of script and alarm on-trigger runs SHED because
+ /// Per-interval count of script and alarm on-trigger runs SHED because
/// MaxConcurrentRunsPerScript runs were already in flight for that script. Raw
/// per-interval count (drained on collect) like . A
/// sustained non-zero value means a trigger is firing faster than its script completes;
@@ -136,8 +136,7 @@ public record SiteHealthReport(
public long? LocalDbOplogBacklog { get; init; }
///
- /// WP2.6d (arch-review misc — site stream alarm-vs-attribute shared buffer):
- /// cumulative count of alarm state changes dropped from
+ /// Cumulative count of alarm state changes dropped from
/// SiteStreamManager's dedicated alarm publish queue because it was at
/// capacity — i.e. the site-wide alarm hand-off itself fell behind, not a
/// per-subscriber buffer. Point-in-time (not reset on collect), refreshed by
@@ -154,9 +153,9 @@ public record SiteHealthReport(
///
///
///
- /// Tri-state, and the distinction is the whole point (deferred-work register row 73).
+ /// Tri-state, and the distinction is the whole point.
/// Null = not reported: a central self-report, where the notion does not apply,
- /// or a site node running a pre-row-73 build. = the
+ /// or a site node running an older build. = the
/// reconciler is running. = the node booted WITHOUT it,
/// because no IDeploymentConfigFetcher was resolvable.
///
@@ -175,12 +174,12 @@ public record SiteHealthReport(
public bool? StartupReconcilerActive { get; init; }
///
- /// Every site node's latest deployed-secrets mirror outcome (review finding M1) — one
+ /// Every site node's latest deployed-secrets mirror outcome — one
/// entry per node that has recorded a row in the replicated secret_mirror_status
/// table, including the STANDBY node, whose own health report is never sent.
///
///
- /// Null = no data: a central self-report, a pre-M1 site build, a site whose
+ /// Null = no data: a central self-report, an older site build, a site whose
/// reporter has not run yet, or a failed read. An EMPTY list means the feature is wired
/// but no node has recorded a status yet. A node entry with an empty
/// list is a healthy mirror. Names and
diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Instance/SetStaticAttributeCommand.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Instance/SetStaticAttributeCommand.cs
index 35bccb69..c464f3be 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Instance/SetStaticAttributeCommand.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Instance/SetStaticAttributeCommand.cs
@@ -10,7 +10,7 @@ namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Instance;
/// Canonical string form of the new value.
/// UTC timestamp of the command.
///
-/// Audit Log #23 (ParentExecutionId tag-cascade): the ExecutionId of the
+/// ParentExecutionId tag-cascade: the ExecutionId of the
/// execution issuing this write — a site script run
/// (ScriptRuntimeContext.SetAttribute) or the inbound API request behind a
/// Route.To(...).SetAttributes(...). Additive and nullable; null
diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Observability/ScadaBridgeTelemetry.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Observability/ScadaBridgeTelemetry.cs
index 12d808e3..ce653c63 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Commons/Observability/ScadaBridgeTelemetry.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Observability/ScadaBridgeTelemetry.cs
@@ -59,7 +59,7 @@ public static class ScadaBridgeTelemetry
///
/// Incremented each time a per-site live-alarm aggregator re-establishes its site-wide
/// gRPC stream — a NodeA↔NodeB failover flip or a reconcile-driven reopen after the
- /// stream was given up (plan #10, Task 6). A sustained climb signals a flapping site link.
+ /// stream was given up. A sustained climb signals a flapping site link.
///
private static readonly Counter _liveAlarmStreamReconnects =
Meter.CreateCounter("scadabridge.site.alarm_cache.reconnects", unit: "1",
@@ -67,7 +67,7 @@ public static class ScadaBridgeTelemetry
///
/// Incremented for each live delta evicted from a per-site live-alarm aggregator's
- /// bounded pre-seed buffer (drop-oldest, WP2.3). Non-zero means a seed/reconcile
+ /// bounded pre-seed buffer (drop-oldest). Non-zero means a seed/reconcile
/// fan-out ran long enough for the delta storm behind it to exceed the cap — the
/// dropped transitions are recovered by the fan-out's authoritative snapshot, but a
/// sustained climb points at a slow site.
@@ -78,7 +78,7 @@ public static class ScadaBridgeTelemetry
///
/// Incremented for each debug event evicted from a central debug session's bounded
- /// pre-snapshot buffer (drop-oldest, WP2.3). The Debug View is lossy-under-backpressure
+ /// pre-snapshot buffer (drop-oldest). The Debug View is lossy-under-backpressure
/// by design; this makes the loss measurable instead of unbounded memory growth.
///
private static readonly Counter _debugPreSnapshotDrops =
diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Audit/AuditQueryParamParsers.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Audit/AuditQueryParamParsers.cs
index c4907680..f6f9a95e 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Audit/AuditQueryParamParsers.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Audit/AuditQueryParamParsers.cs
@@ -79,7 +79,7 @@ public static class AuditQueryParamParsers
/// the filter dimension stays unconstrained.
///
///
- /// Register row 99 (closure defect fixed by arch-review 2026-08-19 P6-3). Three
+ /// Register row 99. Three
/// traps this closes that a bare
/// does not:
///
diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Audit/TraceEventBatch.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Audit/TraceEventBatch.cs
index bd4f3acf..c0201b3e 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Audit/TraceEventBatch.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Audit/TraceEventBatch.cs
@@ -4,8 +4,15 @@ namespace ZB.MOM.WW.ScadaBridge.Commons.Types.Audit;
/// Bounded replay data with independently retained chain-root and truncation metadata.
public sealed record TraceEventBatch(Guid RootExecutionId, IReadOnlyList Events, bool Truncated) : IReadOnlyList
{
+ /// Gets the trace event at the given position.
+ /// Zero-based position in .
+ /// The event at .
public AuditEvent this[int index] => Events[index];
+ /// Gets the number of events in the batch.
public int Count => Events.Count;
+ /// Returns an enumerator over the batch's events.
+ /// An enumerator over .
public IEnumerator GetEnumerator() => Events.GetEnumerator();
+ ///
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/DataConnections/OpcUaReferenceForm.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/DataConnections/OpcUaReferenceForm.cs
index b4c963d5..30218c1e 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/DataConnections/OpcUaReferenceForm.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/DataConnections/OpcUaReferenceForm.cs
@@ -25,6 +25,8 @@ public static class OpcUaReferenceForm
/// prefix, or empty/whitespace (nothing to warn about). False only for a bare
/// server-namespace index (ns=<n>;… with n ≥ 1).
///
+ /// The OPC UA node reference string to classify.
+ /// when the reference is index-durable; otherwise .
public static bool IsDurable(string? reference)
{
if (string.IsNullOrWhiteSpace(reference))
diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Debugging/ExceptionHeader.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Debugging/ExceptionHeader.cs
index 4f3352b3..07ab68d0 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Debugging/ExceptionHeader.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Debugging/ExceptionHeader.cs
@@ -1,7 +1,7 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Types.Debugging;
///
-/// Flow Debugger (final review M1): the ONE parser for the header line of a captured
+/// Flow Debugger: the ONE parser for the header line of a captured
/// exception detail. A .NET Exception.ToString() opens with
/// Namespace.Type: message, and a wrapper (an AggregateException, a rethrow)
/// chains its inner exceptions on that same first line with " ---> ".
diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Debugging/FlowGraphBuilder.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Debugging/FlowGraphBuilder.cs
index 7801beb5..a839bacb 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Debugging/FlowGraphBuilder.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Debugging/FlowGraphBuilder.cs
@@ -59,6 +59,10 @@ public static class FlowGraphBuilder
/// Builds the graph. and are passed
/// through to / .
///
+ /// The audit events to project into the graph.
+ /// Whether the event set was truncated.
+ /// Whether the log rows were truncated.
+ /// The built flow graph.
public static FlowGraph Build(IReadOnlyList events, bool truncated = false, bool logsTruncated = false)
{
ArgumentNullException.ThrowIfNull(events);
@@ -338,8 +342,10 @@ public static class FlowGraphBuilder
/// A step the script did not wait for: a notification, or a cached operation — any API / DB step whose
/// first lifecycle row is not the sync one-shot kind (ApiCall / DbWrite), the same rule the
/// projector uses for the sync / cached subtitle. Such steps hang off the chain on an
- /// edge (D4).
+ /// edge.
///
+ /// The step to classify.
+ /// True when the step is not awaited by the script.
public static bool IsAsync(FlowStep step)
{
if (step.Kind == FlowStepKind.Notify) return true;
@@ -352,9 +358,12 @@ public static class FlowGraphBuilder
///
/// Orders the rows of one outbound operation when timestamps tie: submit before attempts before the
- /// resolve, then non-terminal before terminal statuses — so "first" and "latest" (D3) never fall back
+ /// resolve, then non-terminal before terminal statuses — so "first" and "latest" never fall back
/// to GUID order for rows written in the same tick.
///
+ /// The audit row kind.
+ /// The audit row status.
+ /// The rank used to order the rows.
public static int LifecycleRank(string? kind, string? status)
{
var k = kind switch
@@ -444,6 +453,8 @@ public static class FlowGraphBuilder
}
/// Display rank used to break timestamp ties: spawners before what they spawn.
+ /// The step kind.
+ /// The display rank for the kind.
public static int KindRank(FlowStepKind kind) => kind switch
{
FlowStepKind.InboundRequest => 0,
diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Debugging/FlowStepProjector.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Debugging/FlowStepProjector.cs
index 46a10949..cfb44e7f 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Debugging/FlowStepProjector.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Debugging/FlowStepProjector.cs
@@ -14,12 +14,18 @@ namespace ZB.MOM.WW.ScadaBridge.Commons.Types.Debugging;
public static class FlowStepProjector
{
/// Projects an audit event onto a flow step.
+ /// The audit event.
+ /// The projected flow step.
public static FlowStep Project(AuditEvent evt)
{
ArgumentNullException.ThrowIfNull(evt);
return Project(evt, AuditDetailsCodec.Deserialize(evt.DetailsJson));
}
+ /// Projects an audit event and its already-deserialized details onto a flow step.
+ /// The audit event.
+ /// The deserialized audit details of .
+ /// The projected flow step.
internal static FlowStep Project(AuditEvent evt, AuditDetails d)
{
var extra = ScriptTraceExtra.TryParse(d.Extra);
@@ -52,6 +58,8 @@ public static class FlowStepProjector
}
/// True for a ScriptTrace.ScriptLog row — a log line, never a step.
+ /// The audit event.
+ /// when the event is a script log line.
public static bool IsLogLine(AuditEvent evt)
{
ArgumentNullException.ThrowIfNull(evt);
@@ -59,6 +67,9 @@ public static class FlowStepProjector
return IsLogLine(d);
}
+ /// True when the details describe a ScriptTrace.ScriptLog row.
+ /// The deserialized audit details of the event.
+ /// when the details describe a script log line.
internal static bool IsLogLine(AuditDetails d)
{
return string.Equals(d.Channel, nameof(AuditChannel.ScriptTrace), StringComparison.Ordinal)
@@ -66,12 +77,18 @@ public static class FlowStepProjector
}
/// Projects a ScriptLog row onto its line. Level/source default to info/script when the extra is missing.
+ /// The audit event.
+ /// The projected log line.
public static FlowLogLine ToLogLine(AuditEvent evt)
{
ArgumentNullException.ThrowIfNull(evt);
return ToLogLine(evt, AuditDetailsCodec.Deserialize(evt.DetailsJson));
}
+ /// Projects a ScriptLog row and its already-deserialized details onto its line.
+ /// The audit event.
+ /// The deserialized audit details of .
+ /// The projected log line.
internal static FlowLogLine ToLogLine(AuditEvent evt, AuditDetails d)
{
var x = ScriptTraceExtra.TryParse(d.Extra);
@@ -89,12 +106,18 @@ public static class FlowStepProjector
}
/// Projects one outbound audit row onto its lifecycle entry ().
+ /// The audit event.
+ /// The projected lifecycle row.
public static FlowLifecycleRow ToLifecycleRow(AuditEvent evt)
{
ArgumentNullException.ThrowIfNull(evt);
return ToLifecycleRow(evt, AuditDetailsCodec.Deserialize(evt.DetailsJson));
}
+ /// Projects one outbound audit row and its already-deserialized details onto its lifecycle entry.
+ /// The audit event.
+ /// The deserialized audit details of .
+ /// The projected lifecycle row.
internal static FlowLifecycleRow ToLifecycleRow(AuditEvent evt, AuditDetails d)
{
return new FlowLifecycleRow(
@@ -109,6 +132,9 @@ public static class FlowStepProjector
}
/// Maps a channel/kind string pair onto a step kind.
+ /// The audit channel name.
+ /// The audit kind name.
+ /// The matching step kind, or .
public static FlowStepKind ResolveKind(string? channel, string? kind)
{
if (string.Equals(channel, nameof(AuditChannel.ScriptTrace), StringComparison.Ordinal))
@@ -138,6 +164,9 @@ public static class FlowStepProjector
}
/// Lane rule: route call / inbound / response → Central; script run / wait → Site; write → Tags; outbound API / DB / notify → External.
+ /// The step kind.
+ /// The originating site id, used to place unknown kinds.
+ /// The lane the step is drawn on.
public static FlowLane ResolveLane(FlowStepKind kind, string? sourceSiteId) => kind switch
{
FlowStepKind.InboundRequest or FlowStepKind.RouteCall or FlowStepKind.Response => FlowLane.Central,
@@ -148,6 +177,8 @@ public static class FlowStepProjector
};
/// True for the three outbound kinds that live on the External lane.
+ /// The step kind.
+ /// for an API call, DB call or notification.
public static bool IsOutbound(FlowStepKind kind)
=> kind is FlowStepKind.ApiCall or FlowStepKind.DbCall or FlowStepKind.Notify;
diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Debugging/FlowTraceCollector.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Debugging/FlowTraceCollector.cs
index 10074ae4..fa1bf715 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Debugging/FlowTraceCollector.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Debugging/FlowTraceCollector.cs
@@ -92,6 +92,9 @@ public sealed class FlowTraceCollector
/// the inline copy is filtered through. A null — a host that
/// never called AddAuditLog, or a test — collects raw but is still capped.
///
+ /// The maximum number of events kept inline.
+ /// The maximum summed size of the events' details, in bytes.
+ /// The redactor applied to the inline copy; null collects raw.
public FlowTraceCollector(
int maxEvents = DefaultMaxEvents,
int maxBytes = DefaultMaxBytes,
@@ -142,6 +145,7 @@ public sealed class FlowTraceCollector
/// where it builds ScriptCallResult.TraceEvents. Idempotent: a second call returns
/// the same contents.
///
+ /// The collected events in emission order.
public IReadOnlyList Complete()
{
lock (_gate)
@@ -159,6 +163,7 @@ public sealed class FlowTraceCollector
/// test-only peek. A reply path must use , otherwise an un-awaited call
/// finishing after the reply could mutate the list the reply was built from.
///
+ /// A copy of the events collected so far.
public IReadOnlyList Snapshot()
{
lock (_gate) return _events.ToArray();
diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Debugging/ScriptTraceExtra.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Debugging/ScriptTraceExtra.cs
index c5a5ddda..241a168d 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Debugging/ScriptTraceExtra.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Debugging/ScriptTraceExtra.cs
@@ -68,9 +68,12 @@ public sealed record ScriptTraceExtra
};
/// Serializes to the compact camelCase JSON stored in AuditDetails.Extra.
+ /// The serialized JSON string.
public string ToJson() => JsonSerializer.Serialize(this, JsonOptions);
/// Parses AuditDetails.Extra; returns null for null/blank/invalid JSON — never throws.
+ /// The raw AuditDetails.Extra JSON text.
+ /// The parsed value, or null when the input is null, blank or invalid.
public static ScriptTraceExtra? TryParse(string? json)
{
if (string.IsNullOrWhiteSpace(json)) return null;
diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/HandshakeResult.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/HandshakeResult.cs
index 1698b389..b82c113c 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/HandshakeResult.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/HandshakeResult.cs
@@ -2,7 +2,7 @@ namespace ZB.MOM.WW.ScadaBridge.Commons.Types;
///
/// Which leg of a reset-aware Attributes.WriteBatchAndWaitAsync handshake a
-/// describes (Gitea #36, design 2026-09-10 §4).
+/// describes.
///
public enum HandshakePhase
{
@@ -14,7 +14,7 @@ public enum HandshakePhase
}
///
-/// Result of the reset-aware Attributes.WriteBatchAndWaitAsync overload (Gitea #36):
+/// Result of the reset-aware Attributes.WriteBatchAndWaitAsync overload:
/// like , plus the the outcome belongs to so a
/// script can report "timed out waiting for the reset echo" separately from "timed out
/// waiting for the device to complete". A reset that is never echoed means the batch and
diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Kpi/KpiMetricAggregation.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Kpi/KpiMetricAggregation.cs
index a463a875..2b528910 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Kpi/KpiMetricAggregation.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Kpi/KpiMetricAggregation.cs
@@ -4,7 +4,7 @@ namespace ZB.MOM.WW.ScadaBridge.Commons.Types.Kpi;
/// How a KPI metric must be folded when an hour of one-minute
/// rows is
/// aggregated into a single KpiRollupHourly row ("KPI History & Trends"
-/// hourly rollups, deferred #22). The correct fold differs by metric semantics, and
+/// hourly rollups). The correct fold differs by metric semantics, and
/// choosing the wrong one silently corrupts long-range trend charts.
///
public enum KpiRollupAggregation
diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Scripts/ScriptTriggerAttribute.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Scripts/ScriptTriggerAttribute.cs
index c9f371b6..55980c32 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Scripts/ScriptTriggerAttribute.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Scripts/ScriptTriggerAttribute.cs
@@ -11,6 +11,8 @@ namespace ZB.MOM.WW.ScadaBridge.Commons.Types.Scripts;
public static class ScriptTriggerAttribute
{
/// True for the trigger kinds the Flow Debugger fires by WRITING the monitored attribute.
+ /// The trigger type name.
+ /// True when the trigger type is a write kind.
public static bool IsWriteKind(string? triggerType) =>
string.Equals(triggerType, "ValueChange", StringComparison.OrdinalIgnoreCase)
|| string.Equals(triggerType, "Conditional", StringComparison.OrdinalIgnoreCase);
@@ -20,6 +22,10 @@ public static class ScriptTriggerAttribute
/// false (and null) for every other kind, and for a write kind whose config is missing,
/// malformed, not an object, or names no attribute.
///
+ /// The trigger type name.
+ /// The trigger configuration JSON.
+ /// Receives the monitored attribute name, or null.
+ /// True when a monitored attribute was read.
public static bool TryRead(string? triggerType, string? triggerConfigurationJson, out string? attribute)
{
attribute = null;
diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Secrets/SecretReferenceScanner.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Secrets/SecretReferenceScanner.cs
index 3cfe4d22..e5ee5459 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Secrets/SecretReferenceScanner.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Secrets/SecretReferenceScanner.cs
@@ -20,7 +20,7 @@ public static class SecretReferenceScanner
///
/// The single parser for a secret:<name> reference value, shared by this scanner
/// (what central ships) and every adapter that resolves the reference at connect time (what
- /// the site looks up), so both sides always agree on the name (final review M3). The value is
+ /// the site looks up), so both sides always agree on the name. The value is
/// trimmed, the secret: prefix matches ordinal-ignore-case, and the remainder is
/// trimmed; an empty remainder is not a reference. The config-expander form
/// ${secret:…} is a different syntax and never matches.
@@ -45,6 +45,8 @@ public static class SecretReferenceScanner
/// string values and returns the distinct names found, ordinal-sorted, each with the ordinal-sorted,
/// de-duplicated list of connection names that referenced it.
///
+ /// The data connection artifacts to scan.
+ /// The distinct referenced secrets, each with the connections that reference it.
public static IReadOnlyList Scan(IEnumerable connections)
{
var found = new SortedDictionary>(StringComparer.Ordinal);
diff --git a/src/ZB.MOM.WW.ScadaBridge.Communication/Actors/DebugStreamBridgeActor.cs b/src/ZB.MOM.WW.ScadaBridge.Communication/Actors/DebugStreamBridgeActor.cs
index 8d105103..908156c2 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Communication/Actors/DebugStreamBridgeActor.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Communication/Actors/DebugStreamBridgeActor.cs
@@ -52,7 +52,7 @@ public class DebugStreamBridgeActor : ReceiveActor, IWithTimers
internal static TimeSpan ReconnectDelay { get; set; } = TimeSpan.FromSeconds(5);
///
- /// Hard deadline on the initial (WP2.3). The site builds
+ /// Hard deadline on the initial The site builds
/// it in milliseconds; if none arrives inside this window the site never answered (the
/// Ask was lost, the singleton moved mid-request, the instance actor is wedged) and the
/// session must FAIL rather than sit in the buffering phase accumulating live events
@@ -125,7 +125,7 @@ public class DebugStreamBridgeActor : ReceiveActor, IWithTimers
/// ) that arrived before the snapshot was delivered.
/// Flushed (with per-entity dedup against the snapshot) when the snapshot arrives,
/// then never used again. Bounded by with drop-oldest
- /// eviction (WP2.3): a snapshot that never arrives used to buffer without limit on the
+ /// eviction: a snapshot that never arrives used to buffer without limit on the
/// central node. Mutated only on the actor thread.
///
private readonly Queue _preSnapshotBuffer = new();
@@ -243,7 +243,7 @@ public class DebugStreamBridgeActor : ReceiveActor, IWithTimers
_snapshotDelivered = true;
});
- // Hard snapshot deadline (WP2.3). Nothing else ends a session stuck in the
+ // Hard snapshot deadline. Nothing else ends a session stuck in the
// buffering phase: the site's reply was lost, so no gRPC error fires, the stream
// keeps delivering events, and stream traffic does not renew the orphan net (which
// measures the consumer). Fail the session so the consumer is told and can reopen.
@@ -351,7 +351,7 @@ public class DebugStreamBridgeActor : ReceiveActor, IWithTimers
_consumerLastSeenUtc = DateTime.UtcNow;
});
- // Orphan safety net, CONSUMER-measured (WP2.3 follow-up). A periodic self-tick
+ // Orphan safety net, CONSUMER-measured. A periodic self-tick
// compares the consumer-last-seen stamp against ConsumerIdleTimeout; the previous
// SetReceiveTimeout(5 min) measured the mailbox instead, and once stream events were
// (correctly) marked INotInfluenceReceiveTimeout nothing recurring reset it — the
diff --git a/src/ZB.MOM.WW.ScadaBridge.Communication/Actors/SiteAlarmAggregatorActor.cs b/src/ZB.MOM.WW.ScadaBridge.Communication/Actors/SiteAlarmAggregatorActor.cs
index ea2e201c..8eee78f6 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Communication/Actors/SiteAlarmAggregatorActor.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Communication/Actors/SiteAlarmAggregatorActor.cs
@@ -8,7 +8,7 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Actors;
///
/// One-per-site aggregator on the active central node backing the operator Alarm
-/// Summary live cache (plan #10, Task 4). Holds a transient in-memory
+/// Summary live cache. Holds a transient in-memory
/// Dictionary<AlarmKey, AlarmStateChanged> of the whole site's current
/// alarm state — NO persistence (locked [PERM]: no central alarm store). Created
/// and torn down by under viewer reference-count.
@@ -33,7 +33,7 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Actors;
/// Failover + drift: a gRPC error flips NodeA↔NodeB with the same retry budget +
/// stability window as . A re-seed runs once per
/// successful (re)connect — driven by the stream's connected callback, not by each
-/// reconnect ATTEMPT (WP2.3: a site outage used to fan a full snapshot out per retry, all
+/// reconnect ATTEMPT (a site outage used to fan a full snapshot out per retry, all
/// of them against the very site that is unreachable). A periodic reconcile snapshot
/// (, default 60s, jittered per site so N aggregators do
/// not fan out in lockstep) remains the drift/backstop: it corrects instance-set drift and
@@ -68,7 +68,7 @@ public sealed class SiteAlarmAggregatorActor : ReceiveActor, IWithTimers
private const string OpenDeadlineTimerKey = "alarm-grpc-open-deadline";
///
- /// Production default for the stream open-attempt deadline (arch-review 2026-08-19 P3-4).
+ /// Production default for the stream open-attempt deadline.
/// Comfortably above SiteStreamGrpcClient.ConnectedHeaderTimeout (10 s) so a
/// normally-slow connect is never pre-empted, and comfortably below the 60 s reconcile
/// interval so the tick that follows always finds the flag released.
@@ -125,7 +125,7 @@ public sealed class SiteAlarmAggregatorActor : ReceiveActor, IWithTimers
///
/// Ordered buffer of live deltas that arrived while a fan-out was in flight, capped at
- /// with drop-oldest eviction (WP2.3 — a stuck fan-out
+ /// with drop-oldest eviction (a stuck fan-out
/// used to buffer without bound). Dropping the OLDEST is the right eviction here: the
/// fan-out that follows rebuilds the cache authoritatively, so an evicted delta is
/// superseded rather than lost, while the newest transitions — the ones the snapshot may
@@ -195,7 +195,7 @@ public sealed class SiteAlarmAggregatorActor : ReceiveActor, IWithTimers
///
/// Deadline on ONE stream open attempt, after which is
- /// released so can reopen (arch-review 2026-08-19 P3-4).
+ /// released so can reopen.
///
/// The three outcomes that clear the flag — connected, error, graceful completion — are
/// all events the peer has to produce. A stream that produces NONE of them pins the flag:
@@ -244,7 +244,7 @@ public sealed class SiteAlarmAggregatorActor : ReceiveActor, IWithTimers
private const int BufferWarnThreshold = 10_000;
private bool _bufferWarned;
- ///
+ /// Akka timer scheduler injected by the framework via .
public ITimerScheduler Timers { get; set; } = null!;
///
@@ -263,7 +263,7 @@ public sealed class SiteAlarmAggregatorActor : ReceiveActor, IWithTimers
/// The second argument is the live-stream liveness flag: false means the site-wide
/// gRPC stream is currently down (fault-exhausted or gracefully completed), so the cache
/// is only as fresh as the last reconcile and the page must fall back to polling
- /// (WP2.3 carried residual — IsLive used to stay true across a dead stream).
+ /// (IsLive used to stay true across a dead stream).
///
/// Factory caching one gRPC client per (site, endpoint).
/// gRPC address of the site's node A.
@@ -284,7 +284,7 @@ public sealed class SiteAlarmAggregatorActor : ReceiveActor, IWithTimers
/// up to +20%). Zero disables jitter, which is what tests want for determinism.
///
///
- /// Deadline on one gRPC stream open attempt (P3-4); null takes
+ /// Deadline on one gRPC stream open attempt; null takes
/// (30 s) and or less
/// disables the deadline. See .
///
@@ -350,7 +350,7 @@ public sealed class SiteAlarmAggregatorActor : ReceiveActor, IWithTimers
OnStreamConnected();
});
- // The open attempt produced none of its three outcomes inside the deadline (P3-4):
+ // The open attempt produced none of its three outcomes inside the deadline:
// release the in-flight flag so the reconcile tick's reopen guard can run again.
// Generation-fenced like every other stream callback, and a no-op once the attempt
// has already resolved (a late timer racing its own stream's connect).
@@ -486,7 +486,7 @@ public sealed class SiteAlarmAggregatorActor : ReceiveActor, IWithTimers
/// Periodic reconcile backstop: re-run the snapshot fan-out (corrects instance-set drift
/// + any missed delta) UNLESS a CONNECT- OR FAILOVER-DRIVEN seed already completed inside
/// this window — that seed makes the tick redundant, and running both was the "full
- /// unconditional snapshot every 60s" waste (WP2.3). A tick's OWN fan-out never arms that
+ /// unconditional snapshot every 60s" waste. A tick's OWN fan-out never arms that
/// skip: it used to, which made steady state alternate fan-out/skip and halved the
/// effective reconcile rate. If the live stream was previously given up, self-heal it by
/// resetting the retry budget and reopening, so a sustained outage never permanently kills
@@ -700,7 +700,7 @@ public sealed class SiteAlarmAggregatorActor : ReceiveActor, IWithTimers
return;
}
- // Backoff retry (WP2.3): the seed leg used to have none — a site returning errors
+ // Backoff retry: the seed leg used to have none — a site returning errors
// was re-fanned at full reconcile cadence forever. Retry at reconnectDelay doubling
// up to MaxSeedRetryBackoffMultiplier × the reconcile interval; the periodic
// reconcile remains the floor once the backoff saturates.
@@ -848,7 +848,7 @@ public sealed class SiteAlarmAggregatorActor : ReceiveActor, IWithTimers
if (_stopped) return;
// The stream is not live again until the server actually accepts it — liveness flips
- // on GrpcAlarmStreamConnected, not on the attempt (WP2.3 carried residual).
+ // on GrpcAlarmStreamConnected, not on the attempt.
var endpoint = _useNodeA ? _grpcNodeAAddress : _grpcNodeBAddress;
_log.Info("Opening site-alarm gRPC stream for {0} to {1}", _siteIdentifier, endpoint);
@@ -861,7 +861,7 @@ public sealed class SiteAlarmAggregatorActor : ReceiveActor, IWithTimers
_openInFlight = true;
var generation = ++_streamGeneration;
- // Bound this attempt (P3-4). Single-shot on a shared key, so a re-open replaces the
+ // Bound this attempt. Single-shot on a shared key, so a re-open replaces the
// previous attempt's deadline rather than stacking one.
if (_openAttemptTimeout > TimeSpan.Zero)
{
@@ -1005,7 +1005,7 @@ public sealed class SiteAlarmAggregatorActor : ReceiveActor, IWithTimers
CleanupGrpc();
// A completed stream is NOT live: without this the cache kept reporting live for up
- // to a full reconcile interval after the site closed the feed (WP2.3 residual).
+ // to a full reconcile interval after the site closed the feed.
if (wasLive && _seeded)
Publish();
}
@@ -1080,6 +1080,6 @@ internal sealed record ReconnectAlarmStream;
internal sealed record GrpcAlarmStreamStable;
/// Internal: a stream OPEN attempt produced none of connected/error/completed inside its
-/// deadline (P3-4), stamped with the generation it was armed for so a late timer from a superseded
+/// deadline, stamped with the generation it was armed for so a late timer from a superseded
/// attempt is ignored. Releases the in-flight guard only — it is not a fault.
internal sealed record GrpcAlarmStreamOpenTimedOut(int Generation);
diff --git a/src/ZB.MOM.WW.ScadaBridge.Communication/Actors/SiteCommunicationActor.cs b/src/ZB.MOM.WW.ScadaBridge.Communication/Actors/SiteCommunicationActor.cs
index b70f3d83..f022102d 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Communication/Actors/SiteCommunicationActor.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Communication/Actors/SiteCommunicationActor.cs
@@ -76,6 +76,10 @@ public class SiteCommunicationActor : ReceiveActor, IWithTimers
/// pass a stub so they do not need to load Akka.Cluster into the TestKit
/// ActorSystem.
///
+ ///
+ /// Optional delegate mapping a site identifier to the cluster role used for failover/singleton
+ /// lookups; null uses the default role resolution.
+ ///
///
/// The site→central transport. Production always passes the Host-built gRPC
/// . null (used by TestKit suites that only
diff --git a/src/ZB.MOM.WW.ScadaBridge.Communication/CommunicationOptions.cs b/src/ZB.MOM.WW.ScadaBridge.Communication/CommunicationOptions.cs
index 737e9ab6..76f863f3 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Communication/CommunicationOptions.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Communication/CommunicationOptions.cs
@@ -149,7 +149,7 @@ public class CommunicationOptions
///
/// Send-channel capacity for the site-wide alarm stream (SubscribeSite). Larger
- /// than the Debug View's (WP2.3): that feed backs the operator Alarm Summary, where a
+ /// than the Debug View's: that feed backs the operator Alarm Summary, where a
/// silently dropped transition is a missed alarm rather than a missed diagnostic frame,
/// and an alarm burst arriving during a WAN stall must survive the stall.
///
@@ -211,9 +211,9 @@ public class CommunicationOptions
///
public TimeSpan PendingDeploymentPurgeInterval { get; set; } = TimeSpan.FromHours(1);
- // ── Aggregated live alarm cache (plan #10, Task 4) ───────────────────────────
- // Introduced by Task 4 with sane defaults; Task 6 formalizes eager validation
- // (positive interval/linger, positive concurrency/subscriber cap) and telemetry.
+ // ── Aggregated live alarm cache ───────────────────────────────────────────────
+ // Sane defaults, with eager validation (positive interval/linger, positive
+ // concurrency/subscriber cap) and telemetry.
///
/// How long the shared per-site live alarm aggregator keeps its gRPC stream +
@@ -256,7 +256,7 @@ public class CommunicationOptions
///
/// Random jitter added to each per-site reconcile tick, as a fraction of
- /// (WP2.3). Without it every aggregator
+ /// . Without it every aggregator
/// started by one central failover fans its whole-site snapshot out on the same 60s
/// boundary forever. Zero disables jitter.
///
diff --git a/src/ZB.MOM.WW.ScadaBridge.Communication/CommunicationOptionsValidator.cs b/src/ZB.MOM.WW.ScadaBridge.Communication/CommunicationOptionsValidator.cs
index 8d45bfce..8b88ea9c 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Communication/CommunicationOptionsValidator.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Communication/CommunicationOptionsValidator.cs
@@ -113,7 +113,7 @@ public sealed class CommunicationOptionsValidator : OptionsValidatorBase= TimeSpan.Zero,
diff --git a/src/ZB.MOM.WW.ScadaBridge.Communication/CommunicationService.cs b/src/ZB.MOM.WW.ScadaBridge.Communication/CommunicationService.cs
index c2465d52..8445e16e 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Communication/CommunicationService.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Communication/CommunicationService.cs
@@ -230,7 +230,7 @@ public class CommunicationService
// RouteToSetAttributes / RouteToWaitForAttribute), driven from
// InboundAPI/CommunicationServiceInstanceRouter and sharing IntegrationTimeout.
// The earlier generic IntegrationCallRequest primitive was never wired to a
- // producer or a site handler and was removed as dead code (Gitea #32).
+ // producer or a site handler and was removed as dead code.
// ── Pattern 5: Debug View ──
@@ -803,7 +803,7 @@ public class CommunicationService
}
///
- /// Asks a site to gracefully fail over its own two-node pair (Task 10). Central and each
+ /// Asks a site to gracefully fail over its own two-node pair. Central and each
/// site are SEPARATE Akka clusters, so central cannot act on site membership directly —
/// the site's own SiteCommunicationActor performs the Cluster.Leave against
/// its site-specific role and acks the outcome.
diff --git a/src/ZB.MOM.WW.ScadaBridge.Communication/DebugStreamService.cs b/src/ZB.MOM.WW.ScadaBridge.Communication/DebugStreamService.cs
index 48602795..66893f2c 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Communication/DebugStreamService.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Communication/DebugStreamService.cs
@@ -82,7 +82,7 @@ public class DebugStreamService : IDisposable
/// A cancellation token that can be used to cancel the operation.
/// A debug stream session with the initial snapshot.
///
- /// Virtual purely as a test seam: the consumers' dispose-vs-start race (P6-6) can only
+ /// Virtual purely as a test seam: the consumers' dispose-vs-start race can only
/// be driven deterministically by a stub that holds the start open. Behaviour and the
/// keepalive/orphan contract are unchanged.
///
@@ -114,7 +114,7 @@ public class DebugStreamService : IDisposable
instanceUniqueName = instance.UniqueName;
siteIdentifier = site.SiteIdentifier;
- // Single-endpoint site (arch-review 2026-08-19 P3-3, applying the same N9
+ // Single-endpoint site (applying the same
// degeneration SiteAlarmLiveCacheService.ResolveSiteAsync already uses): reuse the
// sole configured endpoint for both slots, so the bridge actor's NodeA↔NodeB
// failover flip degenerates to a reconnect against the same node. Requiring BOTH
diff --git a/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/AuditWireBudget.cs b/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/AuditWireBudget.cs
index 4b63fd67..79fe40ad 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/AuditWireBudget.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/AuditWireBudget.cs
@@ -11,10 +11,16 @@ public static class AuditWireBudget
public const int InitialBytes = 7;
///
- /// Appends one row to a field-1 repeated-message collection if it fits. A row larger than the
+ /// Appends one row to a repeated-message collection if it fits. A row larger than the
/// normal budget travels alone, up to the hard channel ceiling. Oversize rows are retained
/// locally and fail explicitly with their identity; they are never acknowledged or truncated.
///
+ /// The protobuf message type of the rows.
+ /// The repeated field to append to.
+ /// The row to append.
+ /// The running wire size of the batch; increased by the row's size when appended.
+ /// Identity of the row, used in the error raised when it exceeds the hard ceiling.
+ /// true when the row was appended; false when it does not fit in the current batch.
public static bool TryAppend(RepeatedField rows, T row, ref int wireBytes, string rowIdentity)
where T : IMessage
{
diff --git a/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/CentralChannelProvider.cs b/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/CentralChannelProvider.cs
index 6c2b36e2..26409601 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/CentralChannelProvider.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/CentralChannelProvider.cs
@@ -268,7 +268,7 @@ public sealed class CentralChannelProvider : IDisposable
return TimeSpan.FromTicks(scaled >= cap || scaled < 0 ? cap : scaled);
}
- ///
+ /// Stops the failback timer and disposes all cached gRPC channels.
public void Dispose()
{
lock (_gate)
diff --git a/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/CentralControlDtoMapper.cs b/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/CentralControlDtoMapper.cs
index cf977609..a981aab3 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/CentralControlDtoMapper.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/CentralControlDtoMapper.cs
@@ -74,7 +74,7 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc;
public static class CentralControlDtoMapper
{
// -----------------------------------------------------------------------
- // Notification Outbox (#21)
+ // Notification Outbox
// -----------------------------------------------------------------------
/// Projects a onto its wire DTO.
@@ -216,7 +216,7 @@ public static class CentralControlDtoMapper
}
// -----------------------------------------------------------------------
- // Audit Log (#23) ingest — envelopes only; rows go through the existing mappers
+ // Audit Log ingest — envelopes only; rows go through the existing mappers
// -----------------------------------------------------------------------
///
@@ -437,7 +437,7 @@ public static class CentralControlDtoMapper
}
// -----------------------------------------------------------------------
- // Health Monitoring (#11)
+ // Health Monitoring
// -----------------------------------------------------------------------
/// Projects a onto its wire DTO.
@@ -559,7 +559,7 @@ public static class CentralControlDtoMapper
dto.SiteAuditBacklog = snapshot;
}
- // Review finding M1: wrapper presence keeps null ("no data") distinct from empty.
+ // Wrapper presence keeps null ("no data") distinct from empty.
if (msg.SecretMirrorStatuses is { } mirrorStatuses)
{
var wrapper = new SecretMirrorStatusListDto();
diff --git a/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/CentralControlGrpcService.cs b/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/CentralControlGrpcService.cs
index 942d75ec..2ab14894 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/CentralControlGrpcService.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/CentralControlGrpcService.cs
@@ -277,21 +277,15 @@ public sealed class CentralControlGrpcService : CentralControlService.CentralCon
return CentralControlDtoMapper.ToDto(ack);
}
- ///
- /// Application heartbeat — always answers OK, even when this node is not ready.
- ///
- ///
- /// The heartbeat is fire-and-forget on both sides: nothing on the site consumes the reply,
- /// and the site's heartbeat timer must never take a fault (a failing heartbeat that raised
- /// an error would be a self-inflicted outage on a purely informational signal). So this is
- /// the one RPC that does not go through : a heartbeat arriving
- /// before the actor exists is logged and dropped, exactly as the actor itself drops one
- /// that arrives before ICentralHealthAggregator is resolvable. Liveness is still
- /// detected — the aggregator's offline timeout fires when the heartbeats stop landing.
- ///
- /// The heartbeat.
- /// The gRPC call context.
- /// An empty reply, always.
+ // Application heartbeat — always answers OK, even when this node is not ready.
+ // The heartbeat is fire-and-forget on both sides: nothing on the site consumes the reply,
+ // and the site's heartbeat timer must never take a fault (a failing heartbeat that raised
+ // an error would be a self-inflicted outage on a purely informational signal). So this is
+ // the one RPC that does not go through RequireReady: a heartbeat arriving before the actor
+ // exists is logged and dropped, exactly as the actor itself drops one that arrives before
+ // ICentralHealthAggregator is resolvable. Liveness is still detected — the aggregator's
+ // offline timeout fires when the heartbeats stop landing.
+ ///
public override Task Heartbeat(HeartbeatDto request, ServerCallContext context)
{
var central = _central;
diff --git a/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/CentralControlSiteIdentity.cs b/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/CentralControlSiteIdentity.cs
index b4f71de7..c21117d9 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/CentralControlSiteIdentity.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/CentralControlSiteIdentity.cs
@@ -8,14 +8,20 @@ public static class CentralControlSiteIdentity
private static readonly object IdentityKey = new();
/// Stores an identity only after the site's credential has been verified.
+ /// The call context to record the identity on.
+ /// The verified site identifier.
public static void SetVerified(ServerCallContext context, string siteId) => context.UserState[IdentityKey] = siteId;
/// Requires a credential-verified identity on the current call.
+ /// The call context carrying the verified identity.
+ /// The verified site identifier.
public static string RequireVerified(ServerCallContext context) =>
context.UserState.TryGetValue(IdentityKey, out var value) && value is string site && !string.IsNullOrWhiteSpace(site)
? site : throw Denied();
/// Checks every ownership field before dispatching any part of a central control request.
+ /// The inbound request whose ownership fields are checked.
+ /// The call context carrying the verified site identity.
public static void ValidateRequest(object request, ServerCallContext context)
{
var site = RequireVerified(context);
diff --git a/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/GrpcRetrySafety.cs b/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/GrpcRetrySafety.cs
index a8786d50..5c58d716 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/GrpcRetrySafety.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/GrpcRetrySafety.cs
@@ -9,14 +9,20 @@ public static class GrpcRetrySafety
public const string DispatchTrailer = "scadabridge-dispatch";
/// Creates a readiness rejection that proves no work was dispatched.
+ /// Human-readable rejection detail placed on the status.
+ /// An with Unavailable status and the not-dispatched trailer.
public static RpcException NotDispatched(string detail) => new(
new Status(StatusCode.Unavailable, detail), new Metadata { { DispatchTrailer, "not-started" } });
/// True only for an explicit rejection from the server before it dispatched work.
+ /// The RPC failure to inspect.
+ /// true when the trailer proves the server never dispatched the command.
public static bool WasNotDispatched(RpcException error) => error.StatusCode == StatusCode.Unavailable
&& error.Trailers.Any(e => e.Key == DispatchTrailer && e.Value == "not-started");
/// Reports an ambiguous mutation without inviting a blind replay.
+ /// The original RPC failure whose trailers are preserved.
+ /// An with Aborted status marking the outcome as unknown.
public static RpcException OutcomeUnknown(RpcException error) => new(
new Status(StatusCode.Aborted, "OUTCOME_UNKNOWN: connection failed after the command may have been applied. Verify its state before retrying."),
error.Trailers);
diff --git a/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/GrpcSiteTransport.cs b/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/GrpcSiteTransport.cs
index 636d869a..44e6a85f 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/GrpcSiteTransport.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/GrpcSiteTransport.cs
@@ -23,7 +23,7 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc;
///
/// Per-call deadlines match today's Ask timeouts exactly — see .
/// An unknown/unconfigured site now fails fast: it surfaces to the caller as a
-/// immediately (WP1.6) instead of being warned-and-dropped to let the
+/// immediately instead of being warned-and-dropped to let the
/// caller's Ask burn its full timeout. A transport fault takes the same
/// path, which the S&F/audit layers already treat as transient.
///
@@ -83,7 +83,7 @@ public sealed class GrpcSiteTransport : ISiteCommandTransport
}
catch (SiteChannelUnavailableException ex)
{
- // WP1.6: a known-dead send (no configured channel for this site) used to warn-and-drop,
+ // A known-dead send (no configured channel for this site) used to warn-and-drop,
// leaving the caller's Ask to burn its full timeout for a failure we already know about.
// Fail fast instead — same Status.Failure completion path a transport fault takes below —
// so S&F/audit treat it as transient immediately rather than tens of seconds later.
@@ -130,6 +130,8 @@ public sealed class GrpcSiteTransport : ISiteCommandTransport
}
/// Only read-only commands can replay after a response may have been lost.
+ /// The command message about to be sent.
+ /// true when the message is a read-only command safe to replay; otherwise false.
internal static bool IsRetrySafe(object message) => message is
DeploymentStateQueryRequest or BrowseNodeCommand or SearchAddressSpaceCommand or ReadTagValuesCommand
or ListServerCertsCommand or EventLogQueryRequest or DebugSnapshotRequest or ParkedMessageQueryRequest
diff --git a/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/SiteCommandDtoMapper.cs b/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/SiteCommandDtoMapper.cs
index 213bf87a..f498fa2d 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/SiteCommandDtoMapper.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/SiteCommandDtoMapper.cs
@@ -56,7 +56,7 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc;
/// Nullable collections are wrapped in a per-collection message, so
/// the null/empty distinction becomes a message-presence question. proto3
/// repeated/map alone collapses the two — precisely the silent
-/// data-loss class the transport round-trip guard caught in PLAN-05 T8.
+/// data-loss class the transport round-trip guard caught in the transport round-trip tests.
///
///
/// Guid? members ride as a string holding the "D" form, with
diff --git a/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/SitePairChannelProvider.cs b/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/SitePairChannelProvider.cs
index 7bad95e6..9939a673 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/SitePairChannelProvider.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/SitePairChannelProvider.cs
@@ -10,7 +10,7 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc;
/// Raised when a site has no usable gRPC channel — an unknown site, or a site with neither
/// GrpcNodeAAddress nor GrpcNodeBAddress configured.
/// treats this as a known-dead send: it fails the caller's Ask fast with an
-/// Akka.Actor.Status.Failure wrapping this exception (WP1.6) instead of warning and dropping
+/// Akka.Actor.Status.Failure wrapping this exception instead of warning and dropping
/// the message, so the caller does not burn its full Ask timeout on a failure already known at send
/// time (central still never buffers).
///
@@ -51,8 +51,7 @@ public sealed class SitePairChannelProvider : IDisposable
private static readonly TimeSpan FailbackSweepInterval = TimeSpan.FromSeconds(1);
///
- /// How long a replaced/removed channel is PARKED before it is disposed (arch-review
- /// 2026-08-19 P3-2). Active calls retain the channel beyond this minimum until completion.
+ /// How long a replaced/removed channel is PARKED before it is disposed. Active calls retain the channel beyond this minimum until completion.
///
internal static readonly TimeSpan ChannelDisposalGrace = TimeSpan.FromMinutes(5);
@@ -366,7 +365,7 @@ public sealed class SitePairChannelProvider : IDisposable
///
/// Parks a channel that is no longer reachable through instead of
- /// disposing it inline (arch-review 2026-08-19 P3-2).
+ /// disposing it inline.
///
/// Why deferred. reads the channel under
/// pair.Gate but invokes the RPC OUTSIDE it, so an inline
@@ -472,7 +471,7 @@ public sealed class SitePairChannelProvider : IDisposable
}
}
- ///
+ /// Disposes every pooled channel and stops the failback sweep and parked-channel disposal.
public void Dispose()
{
lock (_lifecycleGate)
@@ -500,6 +499,7 @@ public sealed class SitePairChannelProvider : IDisposable
private sealed class SitePair(string siteId)
{
+ /// The site identifier this pair of node channels belongs to.
public string SiteId { get; } = siteId;
public readonly object Gate = new();
public string? NodeAEndpoint;
@@ -515,12 +515,15 @@ public sealed class SitePairChannelProvider : IDisposable
private TimeSpan _backoff = InitialBackoff;
+ /// Sets the failback backoff to and schedules the next probe that far ahead.
+ /// The delay before the next failback probe.
public void ScheduleFailback(TimeSpan delay)
{
_backoff = delay;
NextProbeUtc = DateTime.UtcNow + _backoff;
}
+ /// Doubles the failback backoff (capped at the maximum) and schedules the next probe.
public void BumpFailbackBackoff()
{
var next = TimeSpan.FromTicks(Math.Min(_backoff.Ticks * 2, MaxBackoff.Ticks));
@@ -528,6 +531,7 @@ public sealed class SitePairChannelProvider : IDisposable
NextProbeUtc = DateTime.UtcNow + _backoff;
}
+ /// Resets the backoff to its initial value and clears the scheduled failback probe.
public void ResetFailback()
{
_backoff = InitialBackoff;
diff --git a/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/SiteStreamGrpcClient.cs b/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/SiteStreamGrpcClient.cs
index 9d824348..9a3baf8c 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/SiteStreamGrpcClient.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/SiteStreamGrpcClient.cs
@@ -122,6 +122,7 @@ public class SiteStreamGrpcClient : IAsyncDisposable, IDisposable
/// Creates a test-only instance that has no gRPC channel. Used to test
/// Unsubscribe and Dispose behavior without needing a real endpoint.
///
+ /// Optional communication options; defaults are used when null.
/// A with no channel or client, for testing only.
internal static SiteStreamGrpcClient CreateForTesting(CommunicationOptions? options = null) => new(options ?? new CommunicationOptions());
@@ -146,8 +147,8 @@ public class SiteStreamGrpcClient : IAsyncDisposable, IDisposable
/// calls briefly share an ID), the prior CTS is
/// cancelled only — never disposed from here. Internal for testability.
///
- /// Cancel-only is the contract (arch-review 2026-08-19 P3-1, the client half of the
- /// row-47 class): the prior handler's is still running and
+ /// Cancel-only is the contract (the client half of the
+ /// shared-token-source disposal race): the prior handler's is still running and
/// still reads cts.Token (opening the call, ReadAllAsync, the
/// IsCancellationRequested checks), so disposing it from the REPLACING path throws
/// inside the prior stream — and, worse, out of the
@@ -172,7 +173,7 @@ public class SiteStreamGrpcClient : IAsyncDisposable, IDisposable
/// Cancels a subscription token source, swallowing .
/// A CTS whose owning stream loop has already unwound and disposed it is simply a
/// subscription that no longer needs cancelling; the caller must never see that race as a
- /// failure (arch-review 2026-08-19 P3-1).
+ /// failure.
///
/// The token source to cancel.
private static void CancelQuietly(CancellationTokenSource cts)
@@ -249,7 +250,7 @@ public class SiteStreamGrpcClient : IAsyncDisposable, IDisposable
}
// This call OWNS `cts` — it created it, so it disposes it once the stream loop has
- // fully unwound (P3-1). No other path may dispose it while this one is still reading
+ // fully unwound. No other path may dispose it while this one is still reading
// cts.Token; a replacing subscription cancels only.
try
{
@@ -270,15 +271,15 @@ public class SiteStreamGrpcClient : IAsyncDisposable, IDisposable
///
/// Opens the site-wide, alarm-only server-streaming subscription
/// (SubscribeSite) for a whole site rather than a single instance. This is
- /// the central per-site feed that backs the aggregated Alarm Summary live cache
- /// (plan #10): the site runtime's SubscribeSiteAlarms hub drops the
+ /// the central per-site feed that backs the aggregated Alarm Summary live cache:
+ /// the site runtime's SubscribeSiteAlarms hub drops the
/// per-instance filter and carries only events for
/// all instances on the site (attributes are deliberately excluded — the
/// summary never shows them and they are far higher-volume).
///
/// The callback is deliberately typed as
/// rather than the generic Action<object> used by
- /// : this stream is alarm-only by contract, so Task 4's
+ /// : this stream is alarm-only by contract, so the
/// per-site cache consumes an alarm delta directly with no downstream type test.
/// The mapping is still shared via ; a non-alarm
/// event (which should never appear on this stream) is defensively ignored rather
@@ -504,7 +505,7 @@ public class SiteStreamGrpcClient : IAsyncDisposable, IDisposable
///
/// Cancels an active subscription by correlation ID. Cancel-only: the subscription's own
/// / call disposes the token
- /// source once its stream loop unwinds (P3-1). Cancelling an entry whose owner already
+ /// source once its stream loop unwinds. Cancelling an entry whose owner already
/// disposed it — or unsubscribing after the client was disposed — is a silent no-op.
///
/// Unique identifier of the subscription to cancel.
@@ -664,7 +665,7 @@ public class SiteStreamGrpcClient : IAsyncDisposable, IDisposable
/// here is synchronous (CTS cancellation and ), so a
/// synchronous can release everything without sync-over-async blocking.
///
- /// Cancel-only, per-entry guarded (P3-1). The token sources are owned by their
+ /// Cancel-only, per-entry guarded. The token sources are owned by their
/// / calls, which dispose them
/// as their loops unwind in response to this cancellation; disposing them here raced those
/// loops, and one aborted the whole foreach — which in
diff --git a/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/SiteStreamGrpcClientFactory.cs b/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/SiteStreamGrpcClientFactory.cs
index 712cf457..11e6b9bd 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/SiteStreamGrpcClientFactory.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/SiteStreamGrpcClientFactory.cs
@@ -131,7 +131,7 @@ public class SiteStreamGrpcClientFactory : IAsyncDisposable, IDisposable
///
/// Asynchronously disposes all cached clients and clears the cache. Each client's disposal
/// is fault-isolated: one throwing client must not abort the loop and strand every later
- /// client's channel undisposed (arch-review 2026-08-19 P3-1).
+ /// client's channel undisposed.
///
/// A value task that completes when all clients have been disposed.
public async ValueTask DisposeAsync()
diff --git a/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/SiteStreamGrpcServer.cs b/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/SiteStreamGrpcServer.cs
index 01831ffd..0130f00d 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/SiteStreamGrpcServer.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/SiteStreamGrpcServer.cs
@@ -39,7 +39,7 @@ public class SiteStreamGrpcServer : SiteStreamService.SiteStreamServiceBase
private volatile bool _shuttingDown;
private long _actorCounter;
// Ask timeout is 30 s. The ingest actor's repo
- // calls are sub-100 ms in steady state; a generous timeout absorbs a slow
+ // calls take under 100 ms in steady state; a generous timeout absorbs a slow
// MSSQL connection without surfacing as a gRPC failure on a healthy site.
// Shared with CentralCommunicationActor (same assembly) so the two audit-ingest
// transports use one source of truth for the timeout.
@@ -275,7 +275,7 @@ public class SiteStreamGrpcServer : SiteStreamService.SiteStreamServiceBase
// enriched AlarmStateUpdate, so it is reused unchanged.
_streamSubscriber.SubscribeSiteAlarms,
"site-wide alarms",
- // Its OWN, much larger channel (WP2.3): sharing the Debug View's 1000-slot
+ // Its OWN, much larger channel: sharing the Debug View's 1000-slot
// DropOldest meant an alarm burst during a WAN stall silently evicted operator-
// visible transitions to make room for diagnostics traffic.
_siteAlarmChannelCapacity,
@@ -338,7 +338,7 @@ public class SiteStreamGrpcServer : SiteStreamService.SiteStreamServiceBase
//
// The removal is claimed FIRST, but nothing is cancelled until the concurrency cap
// has had its say, and a REPLACEMENT never consults the cap at all (arch-review
- // 2026-08-19 P3-5). Both halves matter:
+ // 2026-08-19). Both halves matter:
//
// * A replacement is net-neutral on the stream count (one entry out, one back in),
// so refusing it can only ever destroy capacity, never protect it. The old code
@@ -402,7 +402,7 @@ public class SiteStreamGrpcServer : SiteStreamService.SiteStreamServiceBase
{
// Lossy-under-backpressure is the debug-view spec; make the real
// loss visible: first eviction + every 500th thereafter, plus a
- // per-kind counter on the node (WP2.3).
+ // per-kind counter on the node.
var n = Interlocked.Increment(ref dropped);
Interlocked.Increment(ref _droppedStreamEvents);
ScadaBridgeTelemetry.RecordSiteStreamEventDropped(streamKind);
@@ -447,8 +447,8 @@ public class SiteStreamGrpcServer : SiteStreamService.SiteStreamServiceBase
"Stream {CorrelationId} started for {Description} (subscription {SubscriptionId})",
correlationId, description, subscriptionId);
- // Flush response headers NOW, while the relay is attached and before the first event
- // (WP2.3). This is the client's "the site accepted my subscription" signal — the
+ // Flush response headers NOW, while the relay is attached and before the first event.
+ // This is the client's "the site accepted my subscription" signal — the
// per-site aggregator hangs its once-per-successful-reconnect re-seed off it. Without
// an explicit flush, ASP.NET Core defers headers to the first written message, so a
// quiet site would never report connected. Best-effort: a client that vanished between
@@ -516,12 +516,12 @@ public class SiteStreamGrpcServer : SiteStreamService.SiteStreamServiceBase
}
}
- /// Retained wire method; ingest is served only by CentralControlService.
+ ///
public override Task IngestAuditEvents(AuditEventBatch request, ServerCallContext context)
=> Task.FromException(new RpcException(new GrpcStatus(
StatusCode.Unimplemented, "Use CentralControlService.IngestAuditEvents on central.")));
- /// Retained wire method; combined ingest is served only by CentralControlService.
+ ///
public override Task IngestCachedTelemetry(CachedTelemetryBatch request, ServerCallContext context)
=> Task.FromException(new RpcException(new GrpcStatus(
StatusCode.Unimplemented, "Use CentralControlService.IngestCachedTelemetry on central.")));
@@ -535,12 +535,11 @@ public class SiteStreamGrpcServer : SiteStreamService.SiteStreamServiceBase
/// Flow Debugger PullTraceEvents: server-side id cap, matching the site queue's.
public const int MaxTraceExecutionIdsPerPull = 64;
- ///
- /// Flow Debugger (design 2026-09-05 §5.4): point read of this site's audit rows whose
- /// ExecutionId or ParentExecutionId is one of the requested ids. NOT a
- /// reconciliation path — moves no cursor, flips no ForwardState. Gated by the same
- /// ControlPlaneAuthInterceptor prefix as .
- ///
+ // Flow Debugger (design 2026-09-05 §5.4): point read of this site's audit rows whose
+ // ExecutionId or ParentExecutionId is one of the requested ids. NOT a reconciliation
+ // path — moves no cursor, flips no ForwardState. Gated by the same
+ // ControlPlaneAuthInterceptor prefix as PullAuditEvents.
+ ///
public override async Task PullTraceEvents(
PullTraceEventsRequest request,
ServerCallContext context)
@@ -630,12 +629,12 @@ public class SiteStreamGrpcServer : SiteStreamService.SiteStreamServiceBase
? DateTime.SpecifyKind(request.SinceUtc.ToDateTime(), DateTimeKind.Utc)
: DateTime.MinValue;
- // Composite-keyset cursor (WP2.3), mirroring PullSiteCalls: proto3
+ // Composite-keyset cursor, mirroring PullSiteCalls: proto3
// defaults an unset string to "", which the queue treats as "no cursor"
// (legacy inclusive >= behaviour), so an older central is unaffected.
var afterId = string.IsNullOrEmpty(request.AfterId) ? null : request.AfterId;
- // AT-LEAST-ONCE (WP2.3). The incoming cursor is central's receipt for
+ // AT-LEAST-ONCE. The incoming cursor is central's receipt for
// everything at or before it — that is the ONLY proof the site accepts.
// Rows are flipped to Reconciled here, at the START of the NEXT pull,
// instead of right after the previous response was projected: a fault
diff --git a/src/ZB.MOM.WW.ScadaBridge.Communication/ISiteAlarmLiveCache.cs b/src/ZB.MOM.WW.ScadaBridge.Communication/ISiteAlarmLiveCache.cs
index 09042357..3a429918 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Communication/ISiteAlarmLiveCache.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Communication/ISiteAlarmLiveCache.cs
@@ -3,7 +3,7 @@ using ZB.MOM.WW.ScadaBridge.Commons.Messages.Streaming;
namespace ZB.MOM.WW.ScadaBridge.Communication;
///
-/// Central, transient, in-memory per-site live alarm cache (plan #10, Task 4).
+/// Central, transient, in-memory per-site live alarm cache.
/// A DI singleton shared by every Alarm Summary Blazor circuit on the active central
/// node. For each site with at least one active viewer it runs ONE shared per-site
/// aggregator that seeds from the existing debug-snapshot fan-out and stays warm on a
@@ -55,8 +55,8 @@ public interface ISiteAlarmLiveCache
///
/// Whether the site's aggregator has completed its initial seed and is serving live
- /// state. The page can use this to decide whether to keep its 15s poll fallback
- /// (Task 5): false means "not live yet — keep polling"; true means the
+ /// state. The page can use this to decide whether to keep its 15s poll fallback:
+ /// false means "not live yet — keep polling"; true means the
/// cache is authoritative.
///
/// The numeric site id.
diff --git a/src/ZB.MOM.WW.ScadaBridge.Communication/ServiceCollectionExtensions.cs b/src/ZB.MOM.WW.ScadaBridge.Communication/ServiceCollectionExtensions.cs
index d3297ba2..ade526c0 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Communication/ServiceCollectionExtensions.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Communication/ServiceCollectionExtensions.cs
@@ -35,7 +35,7 @@ public static class ServiceCollectionExtensions
sp.GetService()));
services.AddSingleton();
- // Aggregated live alarm cache (plan #10, Task 4): transient, in-memory, shared
+ // Aggregated live alarm cache transient, in-memory, shared
// per-site aggregator backing the operator Alarm Summary page. Singleton so every
// Blazor circuit shares one aggregator per site; started/stopped by viewer
// reference-count. No persistence (locked [PERM]: no central alarm store).
diff --git a/src/ZB.MOM.WW.ScadaBridge.Communication/SiteAlarmLiveCacheService.cs b/src/ZB.MOM.WW.ScadaBridge.Communication/SiteAlarmLiveCacheService.cs
index f920d558..9c48a587 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Communication/SiteAlarmLiveCacheService.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Communication/SiteAlarmLiveCacheService.cs
@@ -14,7 +14,7 @@ namespace ZB.MOM.WW.ScadaBridge.Communication;
///
/// Default — the DI singleton that owns the shared,
-/// reference-counted, transient per-site live alarm cache (plan #10, Task 4). For each
+/// reference-counted, transient per-site live alarm cache. For each
/// site with active Alarm Summary viewers it runs ONE
/// , seeded by the existing debug-snapshot fan-out
/// () and kept warm on a
@@ -246,7 +246,7 @@ public sealed class SiteAlarmLiveCacheService : ISiteAlarmLiveCache
TimeSpan.FromSeconds(5), // reconnect delay — former ReconnectDelay static default
TimeSpan.FromSeconds(60), // stability window — former StabilityWindow static default
_options.LiveAlarmCacheReconcileJitterFraction,
- // Open-attempt deadline (P3-4). Passed explicitly rather than defaulted:
+ // Open-attempt deadline. Passed explicitly rather than defaulted:
// Props.Create takes an expression tree, which may not omit an optional
// argument (CS0854).
SiteAlarmAggregatorActor.DefaultOpenAttemptTimeout));
@@ -426,9 +426,9 @@ public sealed class SiteAlarmLiveCacheService : ISiteAlarmLiveCache
await gate.WaitAsync(ct);
try
{
- // Alarms-only (WP2.3): this fan-out discards snapshot.AttributeValues, so
+ // Alarms-only: this fan-out discards snapshot.AttributeValues, so
// asking the site to build and ship them is pure wire waste. The flag is
- // additive on the wire; a pre-WP2.3 site ignores it and returns the full
+ // additive on the wire; an older site ignores it and returns the full
// snapshot, which this loop reads exactly as before.
var request = new DebugSnapshotRequest(
instanceUniqueName, Guid.NewGuid().ToString("N"), AlarmsOnly: true);
@@ -470,7 +470,7 @@ public sealed class SiteAlarmLiveCacheService : ISiteAlarmLiveCache
entry.HasPublished = true;
// Liveness is the STREAM's, not the cache's: a completed or given-up stream must
// stop reporting live between reopen ticks, or the page grafts a freezing snapshot
- // over fresh poll data (WP2.3 carried residual).
+ // over fresh poll data.
entry.StreamLive = streamLive;
subscribers = entry.Subscribers.ToArray();
}
@@ -496,6 +496,9 @@ public sealed class SiteAlarmLiveCacheService : ISiteAlarmLiveCache
/// Watches one aggregator and fires a callback exactly once on Terminated.
private sealed class AggregatorTerminationWatcher : ReceiveActor
{
+ /// Creates the watcher and begins watching the aggregator.
+ /// The aggregator actor to watch.
+ /// Callback invoked once when the watched actor terminates.
public AggregatorTerminationWatcher(IActorRef watched, Action onTerminated)
{
Context.Watch(watched);
@@ -551,11 +554,20 @@ public sealed class SiteAlarmLiveCacheService : ISiteAlarmLiveCache
/// Per-site bookkeeping. All mutable fields are guarded by the service's _lock.
private sealed class SiteEntry
{
+ /// Creates the bookkeeping entry for a site.
+ /// The numeric site id.
public SiteEntry(int siteId) => SiteId = siteId;
+ /// The numeric site id this entry tracks.
public int SiteId { get; }
+
+ /// Active viewer subscriptions for the site.
public List Subscribers { get; } = new();
+
+ /// The site's shared aggregator actor, or null when none is running.
public IActorRef? Actor { get; set; }
+
+ /// True while an aggregator start attempt is in progress.
public bool Starting { get; set; }
/// Current published snapshot the page renders. Swapped as a whole immutable list.
@@ -578,7 +590,10 @@ public sealed class SiteAlarmLiveCacheService : ISiteAlarmLiveCache
///
public bool StreamLive { get; set; }
+ /// Timer that stops the aggregator after the last viewer leaves.
public Timer? LingerTimer { get; set; }
+
+ /// Version counter that invalidates a stale linger-timer callback.
public int LingerVersion { get; set; }
/// Bounded retry timer armed when a start attempt failed to create the actor.
@@ -611,6 +626,7 @@ public sealed class SiteAlarmLiveCacheService : ISiteAlarmLiveCache
{
public static readonly NoOpSubscription Instance = new();
private NoOpSubscription() { }
+ /// Does nothing; the rejected subscription was never registered.
public void Dispose() { }
}
@@ -620,6 +636,10 @@ public sealed class SiteAlarmLiveCacheService : ISiteAlarmLiveCache
private readonly SiteAlarmLiveCacheService _owner;
private int _disposed;
+ /// Creates a subscription handle for a viewer.
+ /// The owning cache service.
+ /// The numeric site id subscribed to.
+ /// Callback invoked when the site's cache changes.
public Subscription(SiteAlarmLiveCacheService owner, int siteId, Action onChanged)
{
_owner = owner;
@@ -627,9 +647,13 @@ public sealed class SiteAlarmLiveCacheService : ISiteAlarmLiveCache
OnChanged = onChanged;
}
+ /// The numeric site id subscribed to.
public int SiteId { get; }
+
+ /// Callback invoked when the site's cache changes.
public Action OnChanged { get; }
+ /// Unregisters this viewer from the owning cache; safe to call more than once.
public void Dispose()
{
if (Interlocked.Exchange(ref _disposed, 1) == 0)
diff --git a/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Configurations/AuditLogEntityTypeConfiguration.cs b/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Configurations/AuditLogEntityTypeConfiguration.cs
index ee94b2a7..b2f96c3d 100644
--- a/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Configurations/AuditLogEntityTypeConfiguration.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Configurations/AuditLogEntityTypeConfiguration.cs
@@ -191,7 +191,7 @@ public class AuditLogEntityTypeConfiguration : IEntityTypeConfiguration e.OccurredAtUtc)
.IsDescending(true)
.HasDatabaseName("IX_AuditLog_OccurredAtUtc")
diff --git a/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Configurations/NotificationOutboxConfiguration.cs b/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Configurations/NotificationOutboxConfiguration.cs
index 09bb4bea..9dca46b3 100644
--- a/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Configurations/NotificationOutboxConfiguration.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Configurations/NotificationOutboxConfiguration.cs
@@ -74,7 +74,7 @@ public class NotificationOutboxConfiguration : IEntityTypeConfiguration= deliveredSince): filtered to just the
// Delivered rows so the index stays small relative to the full table and the KPI
- // query can seek it directly instead of scanning every status (arch-review WP1.4).
+ // query can seek it directly instead of scanning every status.
// Status is stored as its enum name (HasConversion() above), so the filter
// predicate matches on the literal 'Delivered', not the underlying int value.
// No N'' prefix: the value is ASCII-only (fine on SQL Server's nvarchar column)
@@ -93,11 +93,10 @@ public class NotificationOutboxConfiguration : IEntityTypeConfiguration new { n.CreatedAt, n.NotificationId })
.IsDescending(true, true)
.HasDatabaseName("IX_Notifications_Created_NotificationId");
diff --git a/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Configurations/SiteCallEntityTypeConfiguration.cs b/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Configurations/SiteCallEntityTypeConfiguration.cs
index 88f1a6cc..93e91c91 100644
--- a/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Configurations/SiteCallEntityTypeConfiguration.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Configurations/SiteCallEntityTypeConfiguration.cs
@@ -99,7 +99,7 @@ public class SiteCallEntityTypeConfiguration : IEntityTypeConfiguration new { s.CreatedAtUtc, s.TrackedOperationId })
.IsDescending(true, true)
.HasDatabaseName("IX_SiteCalls_Created_TrackedOperationId");
diff --git a/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/DataConnectionSecretConverter.cs b/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/DataConnectionSecretConverter.cs
index 91f03dd1..c9777473 100644
--- a/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/DataConnectionSecretConverter.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/DataConnectionSecretConverter.cs
@@ -10,7 +10,15 @@ public sealed class DataConnectionSecretConverter(IDataProtector protector) : Va
{
internal const string Prefix = "dp:protocol:v1:";
internal const string Purpose = EncryptedStringConverter.ProtectorPurpose + ".DataConnection";
+ /// Encrypts plaintext protocol JSON and tags it with the versioned prefix.
+ /// The data protector to encrypt with.
+ /// The plaintext to protect.
+ /// The prefixed protected string.
internal static string Protect(IDataProtector protector, string plaintext) => Prefix + protector.Protect(plaintext);
+ /// Decrypts a stored, versioned protected value.
+ /// The data protector to decrypt with.
+ /// The stored value, which must carry the versioned prefix.
+ /// The decrypted plaintext.
internal static string Unprotect(IDataProtector protector, string stored)
=> stored.StartsWith(Prefix, StringComparison.Ordinal)
? protector.Unprotect(stored[Prefix.Length..])
diff --git a/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/DataConnectionSecretUpgrade.cs b/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/DataConnectionSecretUpgrade.cs
index b10b576e..d036e9ca 100644
--- a/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/DataConnectionSecretUpgrade.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/DataConnectionSecretUpgrade.cs
@@ -7,6 +7,9 @@ namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase;
public static class DataConnectionSecretUpgrade
{
/// Protects both configuration columns before repositories may materialize them.
+ /// The configuration database context.
+ /// Token to cancel the upgrade.
+ /// A task that represents the asynchronous operation.
public static async Task UpgradeAsync(ScadaBridgeDbContext db, CancellationToken cancellationToken = default)
{
var connection = db.Database.GetDbConnection();
diff --git a/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/AuditLogRepository.cs b/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/AuditLogRepository.cs
index 33347bcf..f0a0cb43 100644
--- a/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/AuditLogRepository.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/AuditLogRepository.cs
@@ -470,7 +470,7 @@ VALUES
// a transient fault. That replay is safe: every step is IF-EXISTS / IF-NOT-EXISTS
// guarded and the staging table is GUID-suffixed, so a re-run is idempotent.
//
- // ALIGNED UNIQUENESS (WP2.2): there is no longer an index drop/rebuild around
+ // ALIGNED UNIQUENESS: there is no longer an index drop/rebuild around
// the SWITCH. EventId uniqueness is now enforced solely by the clustered
// PK_AuditLog (EventId, OccurredAtUtc), which is partition-aligned on
// ps_AuditLog_Month(OccurredAtUtc) — so ALTER TABLE … SWITCH PARTITION has no
diff --git a/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/DeploymentManagerRepository.cs b/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/DeploymentManagerRepository.cs
index 92c65e48..1877510d 100644
--- a/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/DeploymentManagerRepository.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/DeploymentManagerRepository.cs
@@ -590,16 +590,27 @@ public class DeploymentManagerRepository : IDeploymentManagerRepository
///
private sealed class DeploymentInstanceJoin
{
+ /// Deployment record primary key.
public int Id { get; init; }
+ /// Unique deployment identifier.
public string DeploymentId { get; init; } = string.Empty;
+ /// Primary key of the target instance.
public int InstanceId { get; init; }
+ /// Unique name of the target instance.
public string InstanceUniqueName { get; init; } = string.Empty;
+ /// Primary key of the instance's site.
public int SiteId { get; init; }
+ /// Current deployment status.
public DeploymentStatus Status { get; init; }
+ /// Revision hash of the deployed configuration, if any.
public string? RevisionHash { get; init; }
+ /// User who initiated the deployment.
public string DeployedBy { get; init; } = string.Empty;
+ /// When the deployment was initiated.
public DateTimeOffset DeployedAt { get; init; }
+ /// When the deployment completed, if it has.
public DateTimeOffset? CompletedAt { get; init; }
+ /// Failure message, if the deployment failed.
public string? ErrorMessage { get; init; }
}
}
diff --git a/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/KpiHistoryRepository.cs b/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/KpiHistoryRepository.cs
index a357b830..7fb4703f 100644
--- a/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/KpiHistoryRepository.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/KpiHistoryRepository.cs
@@ -129,7 +129,7 @@ public sealed class KpiHistoryRepository : IKpiHistoryRepository
s.Source, s.Metric, s.Scope, s.ScopeKey, TruncateToHour(s.CapturedAtUtc)));
// Preload every rollup row already covering this window in ONE query and
- // index it by series+hour (WP2.2). The predecessor issued a
+ // index it by series+hour. The predecessor issued a
// FirstOrDefaultAsync existence probe PER (series, hour) group — an N+1
// that scaled with the metric catalogue times the lookback: a 3 h re-fold
// over ~40 series cost ~120 sequential round trips before a single row was
@@ -172,8 +172,8 @@ public sealed class KpiHistoryRepository : IKpiHistoryRepository
// That invariant used to be an unenforced assumption across three
// independently-owned config keys (KpiHistoryOptions.SampleInterval,
// NotificationOutboxOptions.DeliveredKpiWindow, SiteCallAuditOptions.KpiInterval)
- // — widening one silently over/under-counted the rollup (arch-review P5-5, Low).
- // As of P5-5, NotificationOutboxKpiSampleSource / SiteCallAuditKpiSampleSource
+ // — widening one silently over/under-counted the rollup.
+ // NotificationOutboxKpiSampleSource / SiteCallAuditKpiSampleSource
// rescale their Rate-classified samples onto SampleInterval before recording, so
// this Sum is correct here regardless of how the other two keys are tuned; a
// boot-time cross-validator (Host's KpiRateFoldIntervalCrossValidator) still
diff --git a/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/NotificationOutboxRepository.cs b/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/NotificationOutboxRepository.cs
index b3f5af4a..5da38635 100644
--- a/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/NotificationOutboxRepository.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/NotificationOutboxRepository.cs
@@ -140,7 +140,7 @@ VALUES
public async Task> GetDueAsync(
DateTimeOffset now, int batchSize, CancellationToken cancellationToken = default)
{
- // AsNoTracking (WP2.2): the dispatcher mutates each row's delivery state in
+ // AsNoTracking: the dispatcher mutates each row's delivery state in
// memory and persists it through UpdateAsync, which is now a targeted
// server-side ExecuteUpdate and needs no change tracker. Tracking a batch
// of notifications — each carrying an nvarchar(max) Body and TypeData —
@@ -162,8 +162,8 @@ VALUES
{
ArgumentNullException.ThrowIfNull(n);
- // Targeted server-side UPDATE of the seven mutable delivery-state columns
- // (WP2.2). The predecessor called DbSet.Update(n) + SaveChanges, which
+ // Targeted server-side UPDATE of the seven mutable delivery-state columns.
+ // The predecessor called DbSet.Update(n) + SaveChanges, which
// marks EVERY property modified and rewrites all 21 columns — including
// the immutable nvarchar(max) Body/TypeData payloads — on every single
// delivery attempt. ExecuteUpdate also bypasses the change tracker
@@ -212,7 +212,7 @@ VALUES
// Same seven mutable delivery-state columns as UpdateAsync, but the WHERE
// clause below ALSO requires the row's current Status to still be Parked
- // -- a single-statement compare-and-swap (arch-review 2026-08-19 P5-4).
+ // -- a single-statement compare-and-swap.
// Two round trips (GetByIdAsync then this write) with a status check in
// between is not a correctness guard by itself: a detached PipeTo task
// can race the dispatcher's own write (or a second concurrent operator
@@ -265,7 +265,7 @@ VALUES
public async Task<(IReadOnlyList Rows, int TotalCount)> QueryAsync(
NotificationOutboxFilter filter, int pageNumber, int pageSize, CancellationToken cancellationToken = default)
{
- // AsNoTracking (WP2.2): this is the Central UI's read-only list page. The
+ // AsNoTracking: this is the Central UI's read-only list page. The
// rows are projected to the wire and never saved, so tracking them cost a
// snapshot copy of every nvarchar(max) Body in the page for nothing.
var query = _context.Notifications.AsNoTracking().AsQueryable();
@@ -347,7 +347,7 @@ VALUES
{
// Time-sliced batches: each DELETE covers at most one hour of terminal rows,
// capping the lock/log footprint per statement — mirrors
- // KpiHistoryRepository.PurgeOlderThanAsync (arch-review WP1.4; an unbounded
+ // KpiHistoryRepository.PurgeOlderThanAsync (an unbounded
// single-statement ExecuteDeleteAsync would otherwise turn a long-idle catch-up
// of the default 365-day window into one giant transaction against
// dbo.Notifications). Pure EF LINQ (no raw SQL) so this stays portable across
@@ -381,14 +381,14 @@ VALUES
// each metric is a COUNT(CASE WHEN THEN 1 END) over the same scan
// (arch-review 04). GroupBy(_ => 1) yields a single group (no rows → no group).
//
- // WP2.2 — the aggregation is now PREDICATE-RESTRICTED instead of scanning the
+ // The aggregation is PREDICATE-RESTRICTED instead of scanning the
// whole table. Every KPI here is about the live queue (Pending/Retrying), the
// parked backlog, or the last delivery interval; the overwhelming bulk of the
// Notifications table is historical Delivered and Discarded rows that
// contribute to NONE of them. The pre-filter below is the union of the
// metric-contributing predicates, which lets the optimizer seek the status
// index for the live/parked legs and the filtered
- // (DeliveredAt) WHERE Status='Delivered' index (WP1.4) for the interval leg,
+ // (DeliveredAt) WHERE Status='Delivered' index for the interval leg,
// instead of paying a full scan whose cost grows with retained history.
// Mirrors the pre-filter ComputePerSiteKpisAsync/ComputePerNodeKpisAsync
// already use — the global snapshot was the odd one out.
diff --git a/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/SecuredWriteRepository.cs b/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/SecuredWriteRepository.cs
index f1fb9c3f..7897a2f1 100644
--- a/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/SecuredWriteRepository.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/SecuredWriteRepository.cs
@@ -125,8 +125,7 @@ WHERE Id = {id}
// A blind full-row UpdateAsync here used to be able to land AFTER a concurrent
// approve had won its own CAS and while its multi-second WriteTagAsync relay was
// still in flight — stamping the row Rejected even though the plant write was
- // physically executing, i.e. falsifying the two-person-control record
- // (arch-review 2026-08-19 P6-1). The conditional WHERE Status='Pending' makes
+ // physically executing, i.e. falsifying the two-person-control record. The conditional WHERE Status='Pending' makes
// reject win only from Pending, so approve and reject can never both transition
// one row. Only the columns the reject transition owns are written.
var rowsAffected = await _context.Database.ExecuteSqlInterpolatedAsync(
diff --git a/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/SiteCallAuditRepository.cs b/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/SiteCallAuditRepository.cs
index f78b99c8..8ddf2292 100644
--- a/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/SiteCallAuditRepository.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/SiteCallAuditRepository.cs
@@ -74,7 +74,7 @@ public class SiteCallAuditRepository : ISiteCallAuditRepository
var incomingRank = GetRankOrThrow(siteCall.Status);
// ONE round trip, INSERT-first. Both statements ship in a single command
- // text, so the round-trip saving of the WP2.2 rewrite is preserved — but
+ // text, so the round-trip saving of the single-command rewrite is preserved — but
// the ORDER is back to insert-then-update, because UPDATE-first LOSES
// DATA under a concurrent first-write.
//
@@ -283,8 +283,7 @@ WHERE TrackedOperationId = @Id
throw new ArgumentNullException(nameof(paging));
}
- // Raw SQL with NAMED parameters (not FromSqlInterpolated) — arch-review
- // 2026-08-19 P5-2. FromSqlInterpolated binds every interpolated string
+ // Raw SQL with NAMED parameters (not FromSqlInterpolated). FromSqlInterpolated binds every interpolated string
// value as an nvarchar(4000) parameter by default; every SiteCalls string
// column is IsUnicode(false) (varchar), so those parameters forced an
// implicit CONVERT on the column side of each comparison and blocked
diff --git a/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/SiteRepository.cs b/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/SiteRepository.cs
index 10de8662..6941dd15 100644
--- a/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/SiteRepository.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/SiteRepository.cs
@@ -175,32 +175,27 @@ public class SiteRepository : ISiteRepository
.ToListAsync(cancellationToken);
}
+ // Rationale for the watermark bump:
+ // Bumps the ITemplateGraphWatermark after a successful commit
+ // that touched a DataConnection.
+ //
+ // A data connection's Protocol, primary/backup configuration and
+ // failover retry count are flattening inputs — FlatteningService
+ // packages them into the flattened config's Connections map and
+ // RevisionHashService folds them into the revision hash. Without a bump,
+ // the process-wide StaleInstanceProbe memo and the flatten-session
+ // caches keep reporting the pre-edit hash for every instance bound to the
+ // connection, so an instance whose deployed config genuinely drifted reads as
+ // up to date.
+ //
+ // The bump is ITemplateGraphWatermark.BumpAll rather than a
+ // per-template one because a connection has no owning template: the affected
+ // set is "every instance at the site bound to it", which is exactly what the
+ // unattributed fallback is for. Applied only AFTER a successful commit, so a
+ // failed save does not invalidate caches for a change that never landed.
+ // Sites, areas and instances saved here are left alone — only the connection
+ // rows feed the flattener.
///
- ///
- /// Bumps the after a successful commit
- /// that touched a .
- ///
- ///
- /// A data connection's Protocol, primary/backup configuration and
- /// failover retry count are flattening inputs — FlatteningService
- /// packages them into the flattened config's Connections map and
- /// RevisionHashService folds them into the revision hash. Without a bump,
- /// the process-wide StaleInstanceProbe memo and the flatten-session
- /// caches keep reporting the pre-edit hash for every instance bound to the
- /// connection, so an instance whose deployed config genuinely drifted reads as
- /// up to date.
- ///
- ///
- ///
- /// The bump is rather than a
- /// per-template one because a connection has no owning template: the affected
- /// set is "every instance at the site bound to it", which is exactly what the
- /// unattributed fallback is for. Applied only AFTER a successful commit, so a
- /// failed save does not invalidate caches for a change that never landed.
- /// Sites, areas and instances saved here are left alone — only the connection
- /// rows feed the flattener.
- ///
- ///
public async Task SaveChangesAsync(CancellationToken cancellationToken = default)
{
// Inspect BEFORE committing: afterwards every entry is Unchanged and the
diff --git a/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/TemplateEngineRepository.cs b/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/TemplateEngineRepository.cs
index b079a9db..573d139f 100644
--- a/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/TemplateEngineRepository.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/TemplateEngineRepository.cs
@@ -91,27 +91,25 @@ public class TemplateEngineRepository : ITemplateEngineRepository
}
///
- ///
- /// Two deliberate departures from :
- ///
- ///
- /// AsNoTracking — the analysis walks (cycle detection, collision
- /// detection, canonical-name resolution) never write through the loaded
- /// graph, so paying for a change-tracker snapshot of every attribute,
- /// alarm, script and composition in the database is pure waste.
- ///
- ///
- /// TemplateScript.Code is projected away (left empty). Script
- /// bodies are by far the largest column in the graph and NONE of the
- /// analysis consumers read them — CycleDetector reads only ids and
- /// edges, CollisionDetector and TemplateResolver read only
- /// member names and lock flags.
- ///
- ///
- /// Callers that DO need bodies or tracked entities — the inheritance
- /// reconciler and the flattener — must keep using
- /// / .
- ///
+ // Two deliberate departures from :
+ //
+ //
+ // AsNoTracking — the analysis walks (cycle detection, collision
+ // detection, canonical-name resolution) never write through the loaded
+ // graph, so paying for a change-tracker snapshot of every attribute,
+ // alarm, script and composition in the database is pure waste.
+ //
+ //
+ // TemplateScript.Code is projected away (left empty). Script
+ // bodies are by far the largest column in the graph and NONE of the
+ // analysis consumers read them — CycleDetector reads only ids and
+ // edges, CollisionDetector and TemplateResolver read only
+ // member names and lock flags.
+ //
+ //
+ // Callers that DO need bodies or tracked entities — the inheritance
+ // reconciler and the flattener — must keep using
+ // / .
public async Task> GetAllTemplatesForAnalysisAsync(CancellationToken cancellationToken = default)
{
return await _context.Templates
@@ -772,13 +770,11 @@ public class TemplateEngineRepository : ITemplateEngineRepository
}
///
- ///
- /// Inspects the change tracker BEFORE committing (afterwards every entry is
- /// and the attribution is lost) and bumps
- /// the for each affected template /
- /// instance. The bump is applied only AFTER a successful commit, so a failed
- /// save does not invalidate caches for a change that never landed.
- ///
+ // Inspects the change tracker BEFORE committing (afterwards every entry is
+ // and the attribution is lost) and bumps
+ // the for each affected template /
+ // instance. The bump is applied only AFTER a successful commit, so a failed
+ // save does not invalidate caches for a change that never landed.
public async Task SaveChangesAsync(CancellationToken cancellationToken = default)
{
var pending = CollectWatermarkBumps();
diff --git a/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/ScadaBridgeDbContext.cs b/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/ScadaBridgeDbContext.cs
index 3a7b7b65..8fb4a859 100644
--- a/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/ScadaBridgeDbContext.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/ScadaBridgeDbContext.cs
@@ -251,6 +251,7 @@ public class ScadaBridgeDbContext : DbContext, IDataProtectionKeyContext
: error.InnerException?.GetType().Name == "SqliteException"
&& error.InnerException.Message.Contains("UNIQUE constraint failed: Instances.UniqueName", StringComparison.Ordinal);
+ /// Data Protection protector for data-connection secrets; throws when no provider was supplied.
internal IDataProtector DataConnectionProtector => (_dataProtectionProvider
?? throw new InvalidOperationException("Data Protection is required to migrate protocol credentials."))
.CreateProtector(DataConnectionSecretConverter.Purpose);
diff --git a/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/ServiceCollectionExtensions.cs b/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/ServiceCollectionExtensions.cs
index 325d5388..c2f3675a 100644
--- a/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/ServiceCollectionExtensions.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/ServiceCollectionExtensions.cs
@@ -29,7 +29,7 @@ public static class ServiceCollectionExtensions
// because key-ring loading is lazy (first Protect/Unprotect), not triggered by
// CreateProtector during model building.
//
- // POOLING IS DELIBERATELY NOT USED (WP2.2 — verified, not overlooked).
+ // POOLING IS DELIBERATELY NOT USED (verified, not overlooked).
// AddDbContextPool requires a context with a SINGLE public constructor taking
// only DbContextOptions; EF Core constructs pooled instances through
// its own activator and cannot supply anything else. ScadaBridgeDbContext has
diff --git a/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Services/ConfigurationCommandTransaction.cs b/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Services/ConfigurationCommandTransaction.cs
index 9a8c3839..4a3ca542 100644
--- a/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Services/ConfigurationCommandTransaction.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Services/ConfigurationCommandTransaction.cs
@@ -38,6 +38,8 @@ public sealed class ConfigurationCommitActions(ILogger> _actions = [];
///
public void Add(Func action) => _actions.Add(action);
+ /// Runs every registered post-commit action, in registration order.
+ /// A task that represents the asynchronous operation.
internal async Task RunAsync()
{
foreach (var action in _actions)
diff --git a/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/Actors/DataConnectionActor.cs b/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/Actors/DataConnectionActor.cs
index 2ddc250c..3683a27e 100644
--- a/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/Actors/DataConnectionActor.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/Actors/DataConnectionActor.cs
@@ -242,7 +242,7 @@ public class DataConnectionActor : UntypedActor, IWithTimers
private Action? _disconnectedHandler;
///
- /// Arch-review 2026-08-19 P2-6. Set when an for the
+ /// Set when an for the
/// CURRENT adapter generation is received while the actor is still Connecting/Reconnecting —
/// i.e. the adapter connected and dropped again before the piped
/// for that attempt reached the mailbox.
@@ -404,7 +404,7 @@ public class DataConnectionActor : UntypedActor, IWithTimers
HandleConnectResult(result);
break;
case AdapterDisconnected disconnected:
- // P2-6 — the adapter connected and dropped again before this attempt's
+ // The adapter connected and dropped again before this attempt's
// ConnectResult was applied. Remember it (see _disconnectDuringConnectAttempt)
// rather than acting now: acting would put the actor into Reconnecting and the
// in-flight ConnectResult(true) would then be applied THERE, re-subscribing and
@@ -557,7 +557,7 @@ public class DataConnectionActor : UntypedActor, IWithTimers
FlushQualityCountersIfDirty();
break;
case AdapterDisconnected disconnected:
- // P2-6 — fence the disconnect on its adapter generation. The outgoing adapter's
+ // Fence the disconnect on its adapter generation. The outgoing adapter's
// event can fire concurrently with SwapAdapter's unhook, so a Disconnected from
// the endpoint we have just failed over (or reconfigured) AWAY from could arrive
// here and knock the healthy replacement back into a reconnect cycle.
@@ -803,7 +803,7 @@ public class DataConnectionActor : UntypedActor, IWithTimers
HandleReconnectResult(result);
break;
case AdapterDisconnected disconnected:
- // P2-6 — same gap as Connecting: a reconnect that succeeds and drops again before
+ // Same gap as Connecting: a reconnect that succeeds and drops again before
// its ConnectResult is applied would otherwise be declared Connected on a dead
// adapter that will never raise Disconnected a second time.
NotePendingDisconnect(disconnected);
@@ -892,7 +892,7 @@ public class DataConnectionActor : UntypedActor, IWithTimers
private void HandleAttemptConnect()
{
_log.Debug("[{0}] Attempting connection...", _connectionName);
- // P2-6 — scope the pending-disconnect flag to THIS attempt. Anything noted before now
+ // Scope the pending-disconnect flag to THIS attempt. Anything noted before now
// belongs to a session that is already over and must not be replayed against the one
// about to start.
_disconnectDuringConnectAttempt = false;
@@ -912,7 +912,7 @@ public class DataConnectionActor : UntypedActor, IWithTimers
///
/// True when this connect result was produced by an adapter that has since been swapped
- /// out, in which case it is dropped WHOLE (arch-review 2026-08-19 P2-3).
+ /// out, in which case it is dropped WHOLE (arch-review 2026-08-19).
///
///
/// A success from the dead adapter used to drive and
@@ -949,7 +949,7 @@ public class DataConnectionActor : UntypedActor, IWithTimers
///
/// True when this was raised by an adapter that has since
/// been swapped out (failover or in-place reconfigure), in which case it is dropped — the
- /// disconnect twin of (arch-review 2026-08-19 P2-6).
+ /// disconnect twin of (arch-review 2026-08-19).
/// unhooks the outgoing adapter, but the event can already be
/// executing on a background thread when it does, so the message can still be delivered
/// after the swap.
@@ -1022,7 +1022,7 @@ public class DataConnectionActor : UntypedActor, IWithTimers
if (result.Success)
{
- // P2-6 — a disconnect that raced ahead of this result means the session this
+ // A disconnect that raced ahead of this result means the session this
// "success" describes is already gone; going Connected here would strand the
// connection on a dead adapter that never raises Disconnected again. Stay in
// Connecting and retry, exactly as a failed connect would.
@@ -1056,7 +1056,7 @@ public class DataConnectionActor : UntypedActor, IWithTimers
if (result.Success)
{
- // P2-6 — same demotion as the initial connect: the reconnect landed but the adapter
+ // Same demotion as the initial connect: the reconnect landed but the adapter
// has already reported the new session lost, so re-subscribing and going Connected
// would wedge on a dead adapter. Stay in Reconnecting and retry.
if (ConsumeDisconnectDuringConnectAttempt())
@@ -1709,8 +1709,8 @@ public class DataConnectionActor : UntypedActor, IWithTimers
// fresh-subscribe vs. unresolved→resolved-promotion split existed only to
// decide which of the two scalar counters to bump (a promotion had to move
// _resolvedTags WITHOUT re-bumping _totalSubscribed, or a tag that a second
- // instance resolved after a first instance failed it counted twice —
- // DataConnectionLayer-020). Derived counts get both cases right for free;
+ // instance resolved after a first instance failed it counted twice.
+ // Derived counts get both cases right for free;
// all that is left of the promotion is dropping the retry bookkeeping.
_subscriptionIds[result.TagPath] = result.SubscriptionId!;
if (_unresolvedTags.Remove(result.TagPath))
@@ -1731,7 +1731,7 @@ public class DataConnectionActor : UntypedActor, IWithTimers
// (an instance subscribes to it, so it is in the per-tag counted set); it
// simply does not count as resolved. Two instances failing the SAME tag is
// still one logical tag in both counts, because both counts are set sizes
- // rather than accumulated increments (DataConnectionLayer-020).
+ // rather than accumulated increments.
_unresolvedTags.Add(result.TagPath);
_log.Debug("[{0}] Tag resolution failed for {1}: {2}",
_connectionName, result.TagPath, result.Error);
@@ -1821,7 +1821,7 @@ public class DataConnectionActor : UntypedActor, IWithTimers
///
/// OPC UA status codes that mean the SESSION or the SECURE CHANNEL is gone, not that one
- /// node is unusable (arch-review 2026-08-19 P2-4). The SDK reports a dead session by
+ /// node is unusable (arch-review 2026-08-19). The SDK reports a dead session by
/// throwing out of the batch subscribe, which
/// classified as a per-tag fault: every tag in the chunk was marked unresolved and each
/// backoff probe re-issued the whole batch against the same dead session, growing the
@@ -2383,7 +2383,7 @@ public class DataConnectionActor : UntypedActor, IWithTimers
/// through the normal generation-guarded path — the same seed
/// the initial subscribe () and the reconnect re-subscribe
/// () already perform, which the resolution probe was missing
- /// (arch-review 2026-08-19 P2-5).
+ /// (arch-review 2026-08-19).
///
///
/// A newly advised subscription only starts producing values on the source's next CHANGE.
@@ -2447,8 +2447,8 @@ public class DataConnectionActor : UntypedActor, IWithTimers
/// Arms the single-shot tag-resolution retry timer at the current backoff interval
/// when unresolved tags remain, and cancels it when none do. Single-shot (rescheduled
/// on probe completion) rather than periodic — a periodic timer cannot back off, and
- /// re-arming a periodic timer on every failure is what starved the retry before
- /// (DataConnectionLayer-022). Gating on IsTimerActive keeps a burst of failed
+ /// re-arming a periodic timer on every failure is what starved the retry before.
+ /// Gating on IsTimerActive keeps a burst of failed
/// subscribes from pushing the already-running deadline out.
///
private void ScheduleTagResolutionRetry()
@@ -2901,11 +2901,22 @@ public class DataConnectionActor : UntypedActor, IWithTimers
private sealed class AlarmReplayState
{
+ /// The current alarm transitions keyed by alarm, as last published.
public Dictionary Current { get; set; } = new();
+
+ /// Transitions accumulated while a replay snapshot is being received.
public Dictionary Snapshot { get; } = new();
+
+ /// Live transitions that arrived while the snapshot was still in progress.
public Dictionary LiveDuringSnapshot { get; } = new();
+
+ /// True while a replay snapshot is being received.
public bool InSnapshot { get; set; }
+
+ /// True once the initial snapshot has completed and live updates may be published.
public bool Ready { get; set; }
+
+ /// The replay epoch the current state belongs to, if known.
public Guid? Epoch { get; set; }
}
private readonly Dictionary _alarmReplay = new();
diff --git a/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/Adapters/MxGatewayDataConnection.cs b/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/Adapters/MxGatewayDataConnection.cs
index f54e03f5..0911e32e 100644
--- a/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/Adapters/MxGatewayDataConnection.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/Adapters/MxGatewayDataConnection.cs
@@ -101,7 +101,7 @@ public class MxGatewayDataConnection
// (no `secret:` prefix) passes through unchanged for back-compat.
// The reference is parsed by the SAME parser central's SecretReferenceScanner uses to
// decide which secret to ship (trimmed, case-insensitive prefix), so a padded or
- // case-variant reference resolves exactly the name that was deployed (final review M3).
+ // case-variant reference resolves exactly the name that was deployed.
var apiKey = cfg.ApiKey;
if (SecretReferenceScanner.TryParseSecretReference(apiKey, out var secretName))
{
diff --git a/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/Adapters/OpcUaAlarmMapper.cs b/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/Adapters/OpcUaAlarmMapper.cs
index 0c874144..d10f9e8f 100644
--- a/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/Adapters/OpcUaAlarmMapper.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/Adapters/OpcUaAlarmMapper.cs
@@ -19,7 +19,7 @@ public static class OpcUaAlarmMapper
///
///
///
- /// Why the routing identity is the binding string, not the event's name (Gitea #17).
+ /// Why the routing identity is the binding string, not the event's name.
/// Routing does transition.SourceObjectReference.StartsWith(bindingRef), where the
/// binding is a NodeId reference (the picker, CSV and config all store NodeIds). Deriving
/// the routing identity from the event's SourceName — a plain name like the RawPath
@@ -39,7 +39,7 @@ public static class OpcUaAlarmMapper
///
///
/// The binding string this alarm feed was subscribed under (empty for the Server-object aggregate feed).
- /// The event's SourceName field (the RawPath for OtOpcUa post-#473), or null/empty when unset.
+ /// The event's SourceName field (the RawPath for OtOpcUa), or null/empty when unset.
/// The event's ConditionName field, or null/empty when unset.
/// The per-condition SourceReference key and the SourceObjectReference routing identity.
public static (string SourceReference, string SourceObjectReference) BuildIdentity(
diff --git a/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/Adapters/RealOpcUaClient.cs b/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/Adapters/RealOpcUaClient.cs
index e17b2caf..6a7b4dd4 100644
--- a/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/Adapters/RealOpcUaClient.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/Adapters/RealOpcUaClient.cs
@@ -29,7 +29,10 @@ public class RealOpcUaClient : IOpcUaClient
///
private sealed class DataShard(Subscription subscription)
{
+ /// The OPC UA subscription backing this shard.
public Subscription Subscription { get; } = subscription;
+
+ /// The number of monitored items currently placed on this shard.
public int ItemCount { get; set; }
}
@@ -97,6 +100,10 @@ public class RealOpcUaClient : IOpcUaClient
public event Action? ConnectionLost;
/// Selects exactly the requested protection mode; protected discovery failures fail closed.
+ /// The discovery URL to query.
+ /// The security mode that must be matched exactly.
+ /// Delegate that returns the server's advertised endpoints.
+ /// The endpoint matching the requested security mode.
internal static EndpointDescription DiscoverEndpoint(string url, MessageSecurityMode requested,
Func discover)
{
@@ -266,6 +273,8 @@ public class RealOpcUaClient : IOpcUaClient
/// A no-op when the advertised host/port is already reachable (so an opc-plc server that
/// advertises its real container name is left untouched).
///
+ /// The discovered endpoint to rewrite in place; may be null.
+ /// The reachable URL the operator configured.
internal static void RewriteEndpointHostForReachability(EndpointDescription? endpoint, string discoveryUrl)
{
if (endpoint is null || string.IsNullOrWhiteSpace(endpoint.EndpointUrl))
@@ -633,8 +642,7 @@ public class RealOpcUaClient : IOpcUaClient
// one) leaves every item created so far registered in _callbacks / _itemShard and
// counted against its shard's budget, with nothing to ever release them: the
// actor sees the throw as a whole-chunk fault and re-issues the SAME batch on its
- // backoff timer, so each probe grew the bookkeeping again (arch-review 2026-08-19
- // P2-4). Unwind before propagating.
+ // backoff timer, so each probe grew the bookkeeping again. Unwind before propagating.
UnwindCreatedItems(created);
throw;
}
@@ -889,7 +897,7 @@ public class RealOpcUaClient : IOpcUaClient
{
if (e.NotificationValue is EventFieldList efl)
// sourceNodeId is the binding this feed was subscribed under; every
- // transition on the feed is routed under it verbatim (see #17).
+ // transition on the feed is routed under it verbatim.
HandleAlarmEvent(handle, sourceNodeId, efl, onTransition);
};
@@ -1171,8 +1179,8 @@ public class RealOpcUaClient : IOpcUaClient
// Field layout (AlarmStateFields): [1]=SourceNode (NodeId), [2]=SourceName (string).
// The routing identity is the binding this feed was subscribed under (not the
- // event's name), so DataConnectionActor's NodeId-keyed routing matches — see #17
- // and OpcUaAlarmMapper.BuildIdentity. SourceName seeds the readable per-condition
+ // event's name), so DataConnectionActor's NodeId-keyed routing matches — see
+ // OpcUaAlarmMapper.BuildIdentity. SourceName seeds the readable per-condition
// key; fall back to the SourceNode NodeId string only when it is absent.
var sourceName = fields[2].Value as string;
if (string.IsNullOrEmpty(sourceName))
diff --git a/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/DataConnectionFactory.cs b/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/DataConnectionFactory.cs
index 759233d2..44e033bc 100644
--- a/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/DataConnectionFactory.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/DataConnectionFactory.cs
@@ -34,6 +34,7 @@ public class DataConnectionFactory : IDataConnectionFactory
/// the Site container (where is registered); direct
/// construction without it keeps literal-key behaviour.
///
+ /// Optional data connection options, such as the MxGateway supervisory-advise parallelism; defaults apply when null.
public DataConnectionFactory(
ILoggerFactory loggerFactory,
IOptions opcUaGlobalOptions,
diff --git a/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/MxGatewayGlobalOptionsValidator.cs b/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/MxGatewayGlobalOptionsValidator.cs
index aa05702a..df632d5c 100644
--- a/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/MxGatewayGlobalOptionsValidator.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/MxGatewayGlobalOptionsValidator.cs
@@ -5,7 +5,10 @@ namespace ZB.MOM.WW.ScadaBridge.DataConnectionLayer;
/// Validates the MxGateway client identity before connections start.
public sealed class MxGatewayGlobalOptionsValidator : IValidateOptions
{
- ///
+ /// Validates the MxGateway global options.
+ /// The named-options instance being validated, or .
+ /// The options to validate.
+ /// A success result, or a failure naming the first invalid setting.
public ValidateOptionsResult Validate(string? name, MxGatewayGlobalOptions options)
{
if (string.IsNullOrWhiteSpace(options.ClientNamePrefix))
diff --git a/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/OpcUaGlobalOptionsValidator.cs b/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/OpcUaGlobalOptionsValidator.cs
index 84c5629d..772aea7d 100644
--- a/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/OpcUaGlobalOptionsValidator.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/OpcUaGlobalOptionsValidator.cs
@@ -5,7 +5,10 @@ namespace ZB.MOM.WW.ScadaBridge.DataConnectionLayer;
/// Validates the OPC UA application identity before connections start.
public sealed class OpcUaGlobalOptionsValidator : IValidateOptions
{
- ///
+ /// Fails validation when the OPC UA application name is blank.
+ /// The named options instance being validated.
+ /// The options to validate.
+ /// The validation result.
public ValidateOptionsResult Validate(string? name, OpcUaGlobalOptions options)
{
if (string.IsNullOrWhiteSpace(options.ApplicationName))
diff --git a/src/ZB.MOM.WW.ScadaBridge.DelmiaNotifier/Program.cs b/src/ZB.MOM.WW.ScadaBridge.DelmiaNotifier/Program.cs
index 9ed47b96..6db080d5 100644
--- a/src/ZB.MOM.WW.ScadaBridge.DelmiaNotifier/Program.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.DelmiaNotifier/Program.cs
@@ -2,12 +2,12 @@ namespace ZB.MOM.WW.ScadaBridge.DelmiaNotifier;
internal static class Program
{
- /// Entry point: loads config, sends the recipe-download notification with failover across the configured base URLs, and reports the legacy YES/NO stdout contract.
- /// Command-line arguments parsed into the recipe download payload.
- /// A task that resolves to the process exit code.
/// Stdout reason + log line when neither key source is set; one literal so the two cannot drift.
private const string ApiKeyNotConfigured = "API key not configured (SCADABRIDGE_API_KEY or ScadaBridge:ApiKey)";
+ /// Entry point: loads config, sends the recipe-download notification with failover across the configured base URLs, and reports the legacy YES/NO stdout contract.
+ /// Command-line arguments parsed into the recipe download payload.
+ /// A task that resolves to the process exit code.
public static async Task Main(string[] args)
{
var stdout = Console.Out;
diff --git a/src/ZB.MOM.WW.ScadaBridge.DeploymentManager/ArtifactDeploymentService.cs b/src/ZB.MOM.WW.ScadaBridge.DeploymentManager/ArtifactDeploymentService.cs
index 7c975728..16e0601f 100644
--- a/src/ZB.MOM.WW.ScadaBridge.DeploymentManager/ArtifactDeploymentService.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.DeploymentManager/ArtifactDeploymentService.cs
@@ -188,7 +188,7 @@ public class ArtifactDeploymentService
// marked removed and the mirror deletes it), and a null Secrets means "central did
// not ship secrets at all" (the pre-feature/old-central contract), which touches
// nothing. Shipping null here when the LAST reference is dropped therefore left the
- // secret live on both site nodes forever (Task 13 live gate, check 5). An empty
+ // secret live on both site nodes forever (observed in the live gate). An empty
// list costs an old site nothing (it ignores proto field 9), and the hash is
// unaffected: the hash service folds an empty and a null list identically.
Secrets: collected.Secrets,
@@ -198,7 +198,7 @@ public class ArtifactDeploymentService
}
///
- /// Task 7 (design §3.4): reconciles a site's raw
+ /// Reconciles a site's raw
/// against what the deployed actually shipped, so
/// central never stamps a site *Current* on the strength of a reply that only proves the
/// artifact set was RECEIVED, not that every shipped secret was APPLIED.
@@ -267,8 +267,8 @@ public class ArtifactDeploymentService
{
// A removal-only deploy (the site's last secret reference was dropped, so nothing
// ships but the site retires previously-deployed rows) and a secret-store outage both
- // count as "secrets involved" — the former used to write no row at all (final review
- // M6 follow-up), leaving the removal visible only in the site's own logs.
+ // count as "secrets involved" — the former used to write no row at all,
+ // leaving the removal visible only in the site's own logs.
var hasSecretRefs = (command.Secrets?.Count ?? 0) > 0
|| missing.Count > 0
|| secretStoreError is not null
@@ -475,7 +475,7 @@ public class ArtifactDeploymentService
var (command, missing, secretStoreError) = siteCommands[site.Id];
- // A central secret-store read failure (final review M5) fails only the sites
+ // A central secret-store read failure fails only the sites
// that reference a secret — resolution is incomplete, so shipping would either
// drop a secret the site still needs or retire one it still uses. Sites with no
// secret references never touch the store and deploy normally.
@@ -515,7 +515,7 @@ public class ArtifactDeploymentService
var response = await _communicationService.DeployArtifactsAsync(
site.SiteIdentifier, command, cts.Token);
- // Task 7 (design §3.4): a reply that only proves receipt is not enough to
+ // A reply that only proves receipt is not enough to
// stamp *Current* — reconcile it against what was actually shipped before
// this result feeds the stamp decision below.
return ReconcileSecretsOutcome(command, response, site.SiteIdentifier, site.Name);
@@ -547,7 +547,7 @@ public class ArtifactDeploymentService
perSiteResults[result.SiteId] = result;
}
- // Task 7 (design §3.4): one best-effort DeploySiteSecrets audit row per site whose
+ // One best-effort DeploySiteSecrets audit row per site whose
// command carried or would have carried secrets — covers the reconciled
// success/failure path AND the fail-closed (missing secret, nothing sent) path, since
// both already flow into perSiteResults above by the time this loop runs. Driven off
@@ -641,7 +641,7 @@ public class ArtifactDeploymentService
var (command, missing, secretStoreError) = await BuildDeployArtifactsCommandAsync(siteDbId, cts.Token);
- // Same store-outage rule as the sweep path (final review M5).
+ // Same store-outage rule as the sweep path.
if (secretStoreError is not null)
{
_logger.LogError(
@@ -659,7 +659,7 @@ public class ArtifactDeploymentService
var missingMessage = BuildMissingSecretsMessage(missing);
_logger.LogError(
"Cannot deploy artifacts to site {SiteId}: {Error}", siteIdentifier, missingMessage);
- // Task 7: the fail-closed case still gets its DeploySiteSecrets row — nothing
+ // The fail-closed case still gets its DeploySiteSecrets row — nothing
// was sent, but the command carried (or would have carried) secrets.
await LogSecretsAuditAsync(
user, command, missing, null, null, siteIdentifier, siteIdentifier, false, missingMessage,
@@ -677,7 +677,7 @@ public class ArtifactDeploymentService
var response = await _communicationService.DeployArtifactsAsync(siteIdentifier, command, cts.Token);
- // Task 7 (design §3.4): reconcile the raw wire response against what was
+ // Reconcile the raw wire response against what was
// actually shipped before it feeds the audit row, the stamp decision, or the
// returned Result — an old site's/partial site's reply must not read as success.
var result = ReconcileSecretsOutcome(command, response, siteIdentifier, siteIdentifier);
@@ -739,7 +739,7 @@ public class ArtifactDeploymentService
/// faulted flight and the UI shows the aggregate as unavailable instead.
///
///
- /// A central secret-store read failure is the one per-site exception (final review M5): the
+ /// A central secret-store read failure is the one per-site exception: the
/// affected site is left OUT of the map, which the evaluator reads as Out of date (a
/// missing entry never matches a stamp), so a store outage degrades only the sites that
/// reference secrets instead of failing the fleet-wide check.
@@ -765,7 +765,7 @@ public class ArtifactDeploymentService
var (command, _, secretStoreError) = await BuildDeployArtifactsCommandAsync(
site.Id, globals, cancellationToken, deploymentId: "current");
- // A central secret-store outage (final review M5) leaves this site's current hash
+ // A central secret-store outage leaves this site's current hash
// unknowable. Omitting it from the map makes the site read *Out of date* — the
// conservative answer, never a false *Current* — while every other site, including
// every site with no secret references, still evaluates normally instead of the
diff --git a/src/ZB.MOM.WW.ScadaBridge.DeploymentManager/DeploymentService.cs b/src/ZB.MOM.WW.ScadaBridge.DeploymentManager/DeploymentService.cs
index 76332b92..01908aab 100644
--- a/src/ZB.MOM.WW.ScadaBridge.DeploymentManager/DeploymentService.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.DeploymentManager/DeploymentService.cs
@@ -211,7 +211,7 @@ public class DeploymentService
// idempotency, lock coverage and optimistic concurrency from drifting apart
// between them.
// reconcileGate is null on this path on purpose: the batch-wide reconcile
- // budget exists to stop a serial phase-1 loop multiplying one unreachable
+ // budget exists to stop a serial prepare-phase loop multiplying one unreachable
// site's query timeout by the instance count. A single instance pays it
// once, and this is the explicit operator path — it keeps the full
// communication-layer QueryTimeout.
@@ -248,7 +248,7 @@ public class DeploymentService
///
/// Everything here touches the scoped, non-thread-safe DbContext, so it
/// must run serially — a bulk site deploy loops this phase before fanning out
- /// the phase-2 network waits. On success the returned
+ /// the send-phase network waits. On success the returned
/// owns the operation lock; the CALLER is
/// responsible for disposing it once phase 3 has completed.
///
@@ -470,7 +470,7 @@ public class DeploymentService
///
///
///
- /// A staging failure is returned as the phase-2 fault rather than thrown, so
+ /// A staging failure is returned as the send-phase fault rather than thrown, so
/// phase 3 marks the record Failed on exactly the same path as a send fault —
/// the record must never be left InProgress.
///
@@ -702,7 +702,7 @@ public class DeploymentService
///
///
///
- /// The cleanup save is un-poisoned before it is given up on (P7-5). A
+ /// The cleanup save is un-poisoned before it is given up on. A
/// failed SaveChanges does not untrack the entity that caused it: EF
/// keeps it in the change set and re-attempts it on the NEXT save. Every phase
/// of a bulk site deploy shares ONE scoped DbContext across every
@@ -821,7 +821,7 @@ public class DeploymentService
IDisposable? LockHandle,
Result? EarlyResult)
{
- /// Builds a phase-1-resolved deployment carrying the given result and holding no lock.
+ /// Builds a prepare-phase-resolved deployment carrying the given result and holding no lock.
/// The result to hand back to the caller.
/// A prepared deployment whose is set.
public static PreparedDeployment Resolved(Result result) =>
@@ -930,7 +930,7 @@ public class DeploymentService
/// The username initiating the deployment, recorded on every record and audit row.
///
/// Deploy every deployable instance even when its config is unchanged — the
- /// pre-2026-08 behaviour. The operator escape hatch for the case central's
+ /// previous behaviour. The operator escape hatch for the case central's
/// recorded state cannot see: a site that lost its config (restored volume,
/// rebuilt node) looks up-to-date from here and needs the push anyway. Has no
/// effect on Disabled instances.
diff --git a/src/ZB.MOM.WW.ScadaBridge.DeploymentManager/SiteSecretsCollector.cs b/src/ZB.MOM.WW.ScadaBridge.DeploymentManager/SiteSecretsCollector.cs
index 8afffefa..568ebe34 100644
--- a/src/ZB.MOM.WW.ScadaBridge.DeploymentManager/SiteSecretsCollector.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.DeploymentManager/SiteSecretsCollector.cs
@@ -14,7 +14,7 @@ namespace ZB.MOM.WW.ScadaBridge.DeploymentManager;
/// References that are absent or tombstoned in the store.
///
/// when every reference was looked up. Otherwise the store read failed
-/// (final review M5: e.g. the shared SQL Server secret store is unreachable), resolution stopped
+/// (e.g. the shared SQL Server secret store is unreachable), resolution stopped
/// at the first failure, and / are INCOMPLETE — the
/// caller must treat this site as undeployable and its current hash as unknown. The message is
/// the exception type and message only; it never contains secret material.
@@ -43,7 +43,7 @@ public sealed class SiteSecretsCollector(ISecretStore store)
/// The resolved rows (mapped field-for-field from ) and the
/// references that were absent or tombstoned in the store. A store read failure is
/// returned as rather than thrown, so one
- /// site's lookup failing never aborts a whole multi-site sweep (final review M5); only
+ /// site's lookup failing never aborts a whole multi-site sweep; only
/// cancellation of propagates. A site whose connections reference no
/// secret never touches the store at all.
///
diff --git a/src/ZB.MOM.WW.ScadaBridge.DeploymentManager/StaleInstanceProbe.cs b/src/ZB.MOM.WW.ScadaBridge.DeploymentManager/StaleInstanceProbe.cs
index d3ee5210..799e113a 100644
--- a/src/ZB.MOM.WW.ScadaBridge.DeploymentManager/StaleInstanceProbe.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.DeploymentManager/StaleInstanceProbe.cs
@@ -10,6 +10,8 @@ public sealed class StaleInstanceProbe : IStaleInstanceProbe
private readonly IFlatteningPipeline _flatteningPipeline;
/// Initializes the authoritative probe. Watermark remains a constructor compatibility dependency.
+ /// The pipeline used to flatten instances for staleness comparison.
+ /// The template graph watermark; retained only for constructor compatibility.
public StaleInstanceProbe(IFlatteningPipeline flatteningPipeline, ITemplateGraphWatermark watermark)
{
_flatteningPipeline = flatteningPipeline ?? throw new ArgumentNullException(nameof(flatteningPipeline));
diff --git a/src/ZB.MOM.WW.ScadaBridge.ExternalSystemGateway/ExternalSystemClient.cs b/src/ZB.MOM.WW.ScadaBridge.ExternalSystemGateway/ExternalSystemClient.cs
index ac91678f..f1821522 100644
--- a/src/ZB.MOM.WW.ScadaBridge.ExternalSystemGateway/ExternalSystemClient.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.ExternalSystemGateway/ExternalSystemClient.cs
@@ -29,7 +29,7 @@ public class ExternalSystemClient : IExternalSystemClient
///
/// Shared options for the per-call AuthConfiguration JSON parse in
- /// (perf remediation, arch-review WP1.5) — avoids
+ /// — avoids
/// allocating a new (and its internal cached
/// metadata) on every outbound call. Settings preserved exactly.
///
diff --git a/src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/ISiteHealthCollector.cs b/src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/ISiteHealthCollector.cs
index a1bdac40..ed6d5d0a 100644
--- a/src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/ISiteHealthCollector.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/ISiteHealthCollector.cs
@@ -26,7 +26,7 @@ public interface ISiteHealthCollector
void IncrementDeadLetter();
///
- /// WP3.1: increments the per-interval count of script/alarm runs SHED because
+ /// Increments the per-interval count of script/alarm runs SHED because
/// MaxConcurrentRunsPerScript runs were already in flight for that script. A raw
/// per-interval count like the script/alarm error counters — a sustained non-zero value
/// means a trigger is firing faster than its script can complete. Every shed is counted
@@ -199,7 +199,7 @@ public interface ISiteHealthCollector
}
///
- /// Replace the latest per-node deployed-secrets mirror statuses (review finding M1) used
+ /// Replace the latest per-node deployed-secrets mirror statuses used
/// by the SecretMirrorStatuses field of the next call.
/// Refreshed periodically by the site-side SecretMirrorStatusReporter from the
/// LocalDb-replicated secret_mirror_status table, so the ACTIVE node's report
@@ -225,7 +225,7 @@ public interface ISiteHealthCollector
/// Worker threads currently executing a script.
/// Age (seconds) of the oldest in-flight script, or null when idle.
///
- /// WP3.1: worker threads the stuck-script watchdog has detached and replaced because
+ /// Worker threads the stuck-script watchdog has detached and replaced because
/// their script wedged past its deadline plus grace, and which have not yet returned.
/// A non-zero, non-draining value means script bodies are permanently blocking threads.
///
@@ -236,8 +236,7 @@ public interface ISiteHealthCollector
}
///
- /// WP2.6d (arch-review misc — site stream alarm-vs-attribute shared buffer):
- /// replace the latest cumulative count of alarm state changes dropped from
+ /// Replaces the latest cumulative count of alarm state changes dropped from
/// SiteStreamManager's dedicated alarm publish queue, used by the next
/// call. Refreshed periodically by the site-side
/// SiteStreamAlarmDropReporter hosted service. Point-in-time: NOT reset on
@@ -300,8 +299,7 @@ public interface ISiteHealthCollector
/// Site audit write failure count to add back.
/// Audit redaction failure count to add back.
///
- /// Interval-drained script/alarm run-shed count to add back (arch-review 2026-08-19
- /// P8-1). Additive trailing parameter defaulting to 0 so existing callers keep
+ /// Interval-drained script/alarm run-shed count to add back. Additive trailing parameter defaulting to 0 so existing callers keep
/// compiling; DetachedScriptThreads/SiteStreamAlarmDropCount are
/// point-in-time gauges, not drained on collect, so they need no restore path.
///
diff --git a/src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/LocalDbReplicationStatusReporter.cs b/src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/LocalDbReplicationStatusReporter.cs
index d36cfa3d..3a86c86a 100644
--- a/src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/LocalDbReplicationStatusReporter.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/LocalDbReplicationStatusReporter.cs
@@ -159,6 +159,6 @@ public sealed class LocalDbReplicationStatusReporter : IHostedService, IDisposab
}
}
- ///
+ /// Releases the cancellation source used by the polling loop.
public void Dispose() => _cts?.Dispose();
}
diff --git a/src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/ServiceCollectionExtensions.cs b/src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/ServiceCollectionExtensions.cs
index 7c548269..b84b0a2f 100644
--- a/src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/ServiceCollectionExtensions.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/ServiceCollectionExtensions.cs
@@ -171,7 +171,7 @@ public static class ServiceCollectionExtensions
/// . Pass the backlog through as
/// nullable — coercing null to 0 would report an unreadable oplog as a healthy one.
///
- /// is the register-row-80 gate: the
+ /// is the replication-configured gate: the
/// replication engine is registered unconditionally and answers false/0
/// while inert, so without it a deliberately-unreplicated site publishes
/// "disconnected, empty backlog" instead of the documented "no reading" null. The
diff --git a/src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/SiteHealthCollector.cs b/src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/SiteHealthCollector.cs
index da08d5ac..c7c2e4ec 100644
--- a/src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/SiteHealthCollector.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/SiteHealthCollector.cs
@@ -32,7 +32,7 @@ public class SiteHealthCollector : ISiteHealthCollector
// -1 = reconciler SKIPPED (no IDeploymentConfigFetcher)
// Null must stay distinct from -1: "we don't know" is not "self-heal is missing".
private volatile int _startupReconcilerActive;
- // Review finding M1: every site node's deployed-secrets mirror status, refreshed by the
+ // Every site node's deployed-secrets mirror status, refreshed by the
// SiteRuntime SecretMirrorStatusReporter. Null = the reporter has not run (or its read
// failed before it ever succeeded) — "no data", distinct from an empty list.
private volatile IReadOnlyList? _secretMirrorStatuses;
@@ -53,13 +53,13 @@ public class SiteHealthCollector : ISiteHealthCollector
private int _scriptQueueDepth;
private int _scriptBusyThreads;
private long _scriptOldestBusyAgeBits = BitConverter.DoubleToInt64Bits(double.NaN);
- // WP3.1: workers detached and replaced by the stuck-script watchdog and not yet exited
+ // Workers detached and replaced by the stuck-script watchdog and not yet exited
// (point-in-time, from the same reporter tick as the gauges above), and the per-interval
// count of runs shed by the per-script in-flight cap (reset on collect like the error
// counters, and restored by AddIntervalCounters when a report fails to send).
private int _scriptDetachedThreads;
private int _scriptRunShedCount;
- // WP2.6d: cumulative alarm-publish-queue drop count, refreshed by
+ // Cumulative alarm-publish-queue drop count, refreshed by
// SiteStreamAlarmDropReporter. Point-in-time (not reset on collect), like the
// scheduler gauges above.
private long _siteStreamAlarmDropCount;
@@ -263,8 +263,7 @@ public class SiteHealthCollector : ISiteHealthCollector
// (and therefore accumulated against the zero left by CollectReport's
// Exchange) is correctly summed with the values being put back. No
// ordering between the six Adds is required — they target independent
- // fields. scriptRunShedCount joins this restore path (arch-review
- // 2026-08-19 P8-1) because it is drained by CollectReport the same way
+ // fields. scriptRunShedCount joins this restore path because it is drained by CollectReport the same way
// as the other five; DetachedScriptThreads and SiteStreamAlarmDropCount
// are point-in-time gauges (CompareExchange/Read, never zeroed on
// collect) so a failed Send loses nothing for them to restore.
@@ -330,10 +329,10 @@ public class SiteHealthCollector : ISiteHealthCollector
ScriptQueueDepth = Interlocked.CompareExchange(ref _scriptQueueDepth, 0, 0),
ScriptBusyThreads = Interlocked.CompareExchange(ref _scriptBusyThreads, 0, 0),
ScriptOldestBusyAgeSeconds = ReadScriptOldestBusyAgeSeconds(),
- // WP3.1: point-in-time (like the three gauges above), so a CompareExchange read
+ // Point-in-time (like the three gauges above), so a CompareExchange read
// rather than an Exchange reset.
DetachedScriptThreads = Interlocked.CompareExchange(ref _scriptDetachedThreads, 0, 0),
- // WP3.1: per-interval, so drained on collect like the error counters.
+ // Per-interval, so drained on collect like the error counters.
ScriptRunShedCount = Interlocked.Exchange(ref _scriptRunShedCount, 0),
// Both fields come from the ONE snapshot read above. Null (the reporter has
// not run) leaves both report fields null — "no data", not "disconnected
@@ -344,7 +343,7 @@ public class SiteHealthCollector : ISiteHealthCollector
// Tri-state: 0 stays null ("not reported"), which must never collapse into
// false ("self-heal is missing"). Register row 73.
StartupReconcilerActive = startupReconciler == 0 ? null : startupReconciler > 0,
- // M1: point-in-time, not drained; null stays null ("no data").
+ // Point-in-time, not drained; null stays null ("no data").
SecretMirrorStatuses = _secretMirrorStatuses
};
}
diff --git a/src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/SiteHealthState.cs b/src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/SiteHealthState.cs
index 8c02ee6a..56572e57 100644
--- a/src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/SiteHealthState.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.HealthMonitoring/SiteHealthState.cs
@@ -44,7 +44,7 @@ public sealed record SiteHealthState
/// Gets the instant this site was first observed by the aggregator (via a
/// report or a heartbeat); null only for states not built by the
/// aggregator (hand-constructed fixtures). Anchors metrics-staleness for a
- /// site that has NEVER delivered a report (review 01 round-2 N3): with
+ /// site that has NEVER delivered a report: with
/// null the staleness check previously
/// skipped the site forever. cannot anchor
/// this — every heartbeat advances it.
diff --git a/src/ZB.MOM.WW.ScadaBridge.Host/Actors/AkkaHostedService.cs b/src/ZB.MOM.WW.ScadaBridge.Host/Actors/AkkaHostedService.cs
index ec7b7ab7..7f572f6a 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Host/Actors/AkkaHostedService.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Host/Actors/AkkaHostedService.cs
@@ -261,7 +261,7 @@ public class AkkaHostedService : IHostedService
TimeSpan transportHeartbeat,
TimeSpan transportFailure)
{
- // Simultaneous-cold-start split-brain guard (Gitea #33): when enabled the node must NOT
+ // Simultaneous-cold-start split-brain guard: when enabled the node must NOT
// auto-join from its config seeds — Akka's FirstSeedNodeProcess is exactly what races the
// peer and forms two 1-node clusters. Emit an EMPTY seed list so the ActorSystem comes up
// unjoined, and ClusterBootstrapCoordinator issues the single reachability-gated
@@ -471,7 +471,7 @@ akka {{
debugStreamService?.SetActorSystem(_actorSystem!);
// Wire up the aggregated live alarm cache with the ActorSystem so it can host
- // per-site aggregator actors on the active central node (plan #10, Task 4).
+ // per-site aggregator actors on the active central node.
var siteAlarmLiveCache = _serviceProvider.GetService();
siteAlarmLiveCache?.SetActorSystem(_actorSystem!);
@@ -511,10 +511,10 @@ akka {{
// All central singletons below are created through SingletonRegistrar.Start,
// which pins the actor to the active central node (ClusterSingletonManager), exposes a
// stable address (ClusterSingletonProxy), and — crucially — registers a PhaseClusterLeave
- // GracefulStop drain task so an in-flight EF write drains before handover (review 01
- // [Medium]: notification-outbox and audit-log-ingest previously had NO drain task). Names
+ // GracefulStop drain task so an in-flight EF write drains before handover (notification-outbox and
+ // audit-log-ingest previously had NO drain task). Names
// are unchanged ({name}-singleton / {name}-proxy). Site singletons use the same
- // registrar with `role: siteRole` (Task 11 / round-2 N5).
+ // registrar with `role: siteRole`.
var outbox = SingletonRegistrar.Start(
_actorSystem!, "notification-outbox",
Props.Create(() => new ZB.MOM.WW.ScadaBridge.NotificationOutbox.NotificationOutboxActor(
@@ -825,18 +825,18 @@ akka {{
? () => clusterNodeProvider.SelfIsPrimary
: null;
- // Deployment Manager — role-scoped singleton via SingletonRegistrar
- // (review 01 round-2 N5): previously hand-rolled with bare PoisonPill
+ // Deployment Manager — role-scoped singleton via SingletonRegistrar:
+ // previously hand-rolled with bare PoisonPill
// termination and NO PhaseClusterLeave drain, so in-flight SQLite
// writes (static overrides, native_alarm_state) could be cut off on
// graceful failover. Names unchanged: deployment-manager-singleton /
// deployment-manager-proxy.
- // WP2.6a: the shared cache backing SiteExternalSystemRepository reads
+ // The shared cache backing SiteExternalSystemRepository reads
// (registered singleton by AddSiteRuntime); DeploymentManagerActor invalidates
// it after an artifact deploy applies external-system changes.
var externalSystemCache = _serviceProvider
.GetService();
- // Task 10 (site-secrets artifact deploy): the same per-node singleton AddSiteRuntime
+ // Site-secrets artifact deploy: the same per-node singleton AddSiteRuntime
// registers (and AddHostedService runs periodically) is reconciled SYNCHRONOUSLY by
// HandleDeployArtifacts right after it writes a shipped secret set, so the deploy
// reply's SecretsApplied reflects this node's own apply/verify outcome immediately
@@ -909,7 +909,7 @@ akka {{
// singleton is what guarantees queries always read the actively-written copy;
// replication is what gives the singleton HISTORY to read after it moves —
// without it, a failover would restart the event log empty at exactly the moment
- // an operator is investigating an incident (WP3.2 design memo, Decision (b)).
+ // an operator is investigating an incident (design decision).
var eventLogQueryService = _serviceProvider.GetService();
if (eventLogQueryService != null)
{
@@ -1064,7 +1064,7 @@ akka {{
// Raised from Warning to Error (register row 73). This is a misconfiguration,
// not a degraded-but-expected state: no configuration makes the fetcher
// legitimately absent on a site node, and the consequence is that neither the
- // file heal nor the row-67 runtime heal will ever run on this node.
+ // file heal nor the runtime heal will ever run on this node.
_logger.LogError(
"No IDeploymentConfigFetcher available; SiteReconciliationActor not created for "
+ "site {SiteId} node {Node} — startup self-heal is DISABLED (replication "
diff --git a/src/ZB.MOM.WW.ScadaBridge.Host/Actors/ClusterBootstrapCoordinator.cs b/src/ZB.MOM.WW.ScadaBridge.Host/Actors/ClusterBootstrapCoordinator.cs
index 2990a3b4..b469701a 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Host/Actors/ClusterBootstrapCoordinator.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Host/Actors/ClusterBootstrapCoordinator.cs
@@ -8,7 +8,7 @@ using ZB.MOM.WW.ScadaBridge.ClusterInfrastructure;
namespace ZB.MOM.WW.ScadaBridge.Host.Actors;
///
-/// Runs the simultaneous-cold-start split-brain guard (Gitea #33): when
+/// Runs the simultaneous-cold-start split-brain guard: when
/// ScadaBridge:Cluster:BootstrapGuard:Enabled, the node starts with NO config seed nodes
/// (AkkaHostedService.BuildHocon emits an empty seed list), and this coordinator picks the join
/// order and issues the single once the ActorSystem is
@@ -56,7 +56,9 @@ public sealed class ClusterBootstrapCoordinator : IHostedService
_log = log ?? throw new ArgumentNullException(nameof(log));
}
- ///
+ /// When the bootstrap guard is enabled, decides the join order and issues the single seed-node join once the ActorSystem is up; otherwise does nothing.
+ /// Token that signals startup should be abandoned.
+ /// A task that represents the asynchronous operation.
public Task StartAsync(CancellationToken cancellationToken)
{
if (!_clusterOptions.BootstrapGuard.Enabled)
@@ -69,7 +71,9 @@ public sealed class ClusterBootstrapCoordinator : IHostedService
return Task.CompletedTask;
}
- ///
+ /// Cancels any in-flight join attempt and waits for it to finish.
+ /// Token that signals shutdown should no longer wait.
+ /// A task that represents the asynchronous operation.
public async Task StopAsync(CancellationToken cancellationToken)
{
await _cts.CancelAsync().ConfigureAwait(false);
diff --git a/src/ZB.MOM.WW.ScadaBridge.Host/Actors/SingletonRegistrar.cs b/src/ZB.MOM.WW.ScadaBridge.Host/Actors/SingletonRegistrar.cs
index e793938f..30b57dc3 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Host/Actors/SingletonRegistrar.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Host/Actors/SingletonRegistrar.cs
@@ -13,8 +13,7 @@ namespace ZB.MOM.WW.ScadaBridge.Host.Actors;
/// left the two busiest singletons (notification-outbox, audit-log-ingest)
/// without drain tasks (review 01 [Medium]). The optional role maps to
/// /
-/// .WithRole, so role-scoped site singletons get the same drain
-/// (review 01 round-2 N5).
+/// .WithRole, so role-scoped site singletons get the same drain.
///
internal static class SingletonRegistrar
{
diff --git a/src/ZB.MOM.WW.ScadaBridge.Host/CentralListenerIsolation.cs b/src/ZB.MOM.WW.ScadaBridge.Host/CentralListenerIsolation.cs
index 8ed31395..aad61199 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Host/CentralListenerIsolation.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Host/CentralListenerIsolation.cs
@@ -6,6 +6,11 @@ namespace ZB.MOM.WW.ScadaBridge.Host;
public static class CentralListenerIsolation
{
/// Runs after routing and before authentication or endpoint dispatch.
+ /// The application builder to add the middleware to.
+ /// The local port reserved for gRPC endpoints.
+ /// The dedicated metrics port, or null when metrics share the HTTP ports.
+ /// The local ports serving ordinary HTTP traffic.
+ /// The same application builder, for chaining.
public static IApplicationBuilder UseCentralListenerIsolation(this IApplicationBuilder app,
int grpcPort, int? metricsPort, IReadOnlyCollection httpPorts)
=> app.Use(async (context, next) =>
diff --git a/src/ZB.MOM.WW.ScadaBridge.Host/InboundApiKeyStoreImportService.cs b/src/ZB.MOM.WW.ScadaBridge.Host/InboundApiKeyStoreImportService.cs
index 2deb0cbd..ffadb001 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Host/InboundApiKeyStoreImportService.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Host/InboundApiKeyStoreImportService.cs
@@ -84,7 +84,9 @@ internal sealed class InboundApiKeyStoreImportService(
/// SQL Server "Violation of PRIMARY KEY constraint" — the only error worth one retry.
private const int PrimaryKeyViolation = 2627;
- ///
+ /// Runs the one-off SQLite-to-store key import at host startup; failures are logged, never thrown.
+ /// Cancellation token for the startup import.
+ /// A task that represents the asynchronous operation.
public async Task StartAsync(CancellationToken cancellationToken)
{
var configured = storeOptions.Value.ImportFromSqlitePath;
@@ -124,7 +126,9 @@ internal sealed class InboundApiKeyStoreImportService(
}
}
- ///
+ /// No shutdown work is required.
+ /// Cancellation token (unused).
+ /// A completed task.
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
private async Task ImportWithOneRetryAsync(string path, CancellationToken ct)
diff --git a/src/ZB.MOM.WW.ScadaBridge.Host/InboundApiKeyStoreOptions.cs b/src/ZB.MOM.WW.ScadaBridge.Host/InboundApiKeyStoreOptions.cs
index 69798ed7..c35285c9 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Host/InboundApiKeyStoreOptions.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Host/InboundApiKeyStoreOptions.cs
@@ -10,12 +10,15 @@ public sealed class InboundApiKeyStoreOptions
{
public const string SectionPath = "ScadaBridge:InboundApi:ApiKeyStore";
+ /// The selected inbound API key store backend; defaults to .
public InboundApiKeyStoreProvider Provider { get; set; } = InboundApiKeyStoreProvider.Sqlite;
/// SQLite file to import once when is SqlServer; defaults to the library's SqlitePath. Renamed *.imported afterwards.
public string? ImportFromSqlitePath { get; set; }
/// Reads Provider from configuration (case-insensitive). Throws naming the legal values on an unknown one, so a typo fails the boot with a readable message instead of silently falling back to SQLite.
+ /// The configuration to read the provider setting from.
+ /// The configured provider, or when unset.
public static InboundApiKeyStoreProvider ReadProvider(IConfiguration configuration)
{
var value = configuration[$"{SectionPath}:Provider"];
diff --git a/src/ZB.MOM.WW.ScadaBridge.Host/KpiRateFoldIntervalCrossValidator.cs b/src/ZB.MOM.WW.ScadaBridge.Host/KpiRateFoldIntervalCrossValidator.cs
index 2eee28e7..ad8dcdfe 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Host/KpiRateFoldIntervalCrossValidator.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Host/KpiRateFoldIntervalCrossValidator.cs
@@ -7,8 +7,8 @@ using ZB.MOM.WW.ScadaBridge.SiteCallAudit;
namespace ZB.MOM.WW.ScadaBridge.Host;
///
-/// Boot-time cross-validation of the "rate-fold interval triple" (arch-review P5-5,
-/// Low). ,
+/// Boot-time cross-validation of the "rate-fold interval triple":
+/// ,
/// , and
/// are three independently-owned config
/// keys, bound in three different component projects with no compile-time relationship
@@ -23,7 +23,7 @@ namespace ZB.MOM.WW.ScadaBridge.Host;
/// KpiHistoryRepository.FoldHourlyRollupsAsync's per-hour Sum over those
/// samples is correct for any value of the other two keys — a widened
/// DeliveredKpiWindow can no longer silently over-count the hourly rollup the way
-/// it did before P5-5.
+/// it did before the rescale.
///
///
/// What still depends on it. The rescale trades exact per-sample fidelity for
@@ -54,6 +54,8 @@ public sealed class KpiRateFoldIntervalCrossValidator : OptionsValidatorBase.
///
+ /// Notification Outbox options supplying DeliveredKpiWindow.
+ /// Site Call Audit options supplying KpiInterval.
public KpiRateFoldIntervalCrossValidator(
IOptions notificationOutboxOptions,
IOptions siteCallAuditOptions)
diff --git a/src/ZB.MOM.WW.ScadaBridge.Host/Program.cs b/src/ZB.MOM.WW.ScadaBridge.Host/Program.cs
index 2db51c2b..4bf0a7ba 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Host/Program.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Host/Program.cs
@@ -77,7 +77,7 @@ var configuration = new ConfigurationBuilder()
// The expander MUST read the SAME store the running node will serve — SecretsRegistration.
// UsesCentralSharedStore is the single predicate deciding that, shared with the runtime
// registration in AddScadaBridgeSecrets, so the two can never disagree. On a Central node with
-// Secrets:Store:Provider=SqlServer that is the SHARED SQL-Server store (Task 5): an expander left
+// Secrets:Store:Provider=SqlServer that is the SHARED SQL-Server store: an expander left
// on SQLite there would resolve pre-host ${secret:} references (e.g. ScadaBridge:Security:
// JwtSigningKey) from a store the node does not actually serve, so a value rotated through the
// shared store would never reach the pre-host config, and two central nodes could disagree on
@@ -312,7 +312,7 @@ try
// for ScriptArtifactsChanged. The Transport bundle importer publishes
// post-commit; the Inbound API's ScriptArtifactChangeSubscriber (a hosted
// service registered by AddInboundAPI below) consumes ApiMethod notifications
- // and invalidates the compiled-handler cache (wired in plan R2-06 T1; the
+ // and invalidates the compiled-handler cache (the
// per-request revision check remains the correctness fallback).
// SharedScript/Template notifications currently have NO consumer — nothing at
// central caches compiled artifacts of those kinds. Central-only.
@@ -345,7 +345,7 @@ try
// the KpiHistoryRecorderActor cluster singleton (started in
// AkkaHostedService). Observability/best-effort: NOT readiness-gated.
builder.Services.AddKpiHistory(builder.Configuration);
- // P5-5 (arch-review Low): cross-validates the rate-fold interval triple
+ // Cross-validates the rate-fold interval triple
// (KpiHistoryOptions.SampleInterval, NotificationOutboxOptions.DeliveredKpiWindow,
// SiteCallAuditOptions.KpiInterval) at boot. Rides KpiHistoryOptions' existing
// ValidateOnStart trigger set by AddKpiHistory above.
@@ -388,7 +388,7 @@ try
// Local SQLite store by default. With Secrets:Store:Provider=SqlServer this central node's
// store becomes the SHARED SQL-Server database instead — no hub, no follower sweep, just
// both central nodes reading and writing the same rows, so a clustered pair can never ship
- // sites a drifted secret set (Task 5). The Layer-A pre-host expander above resolves this
+ // sites a drifted secret set. The Layer-A pre-host expander above resolves this
// same predicate (SecretsRegistration.UsesCentralSharedStore) so it always reads the store
// this node actually serves. See SecretsRegistration for why the SQL gate exists (eager
// options validation) and why the connection string can never itself be a ${secret:}
@@ -569,7 +569,7 @@ try
builder.Services.AddSingleton(sp =>
sp.GetRequiredService().GetOrCreateActorSystem());
- // Simultaneous-cold-start split-brain guard (Gitea #33, dark switch
+ // Simultaneous-cold-start split-brain guard (dark switch
// ScadaBridge:Cluster:BootstrapGuard:Enabled, default off). Registered AFTER the
// AkkaHostedService/ActorSystem bridge so its StartAsync resolves the system the main path
// built; when the guard is off it no-ops (Akka auto-joined from config seeds), so registering
@@ -727,7 +727,7 @@ try
// connection to the right pipeline by listener/protocol.
app.MapGrpcService();
- // The pull-only secrets hub that used to be mapped here (scadaproj#3) is retired — Task 11.
+ // The pull-only secrets hub that used to be mapped here is retired.
// Sites receive secrets exclusively via Deploy Artifacts; there is no hub endpoint to map
// on central and nothing left for a site to sweep. See SecretsRegistration.
diff --git a/src/ZB.MOM.WW.ScadaBridge.Host/SecretsRegistration.cs b/src/ZB.MOM.WW.ScadaBridge.Host/SecretsRegistration.cs
index 768ccff5..2af1b624 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Host/SecretsRegistration.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Host/SecretsRegistration.cs
@@ -28,7 +28,7 @@ public enum SecretsNodeRole
///
///
///
-/// Task 11 retired ALL secrets replication in ScadaBridge — both the pull-only gRPC hub
+/// ALL secrets replication is retired in ScadaBridge — both the pull-only gRPC hub
/// (central hosts, sites sweep) and the shared-SQL-Server ReplicatingSecretStore path that
/// preceded it. ScadaBridge is hub-and-spoke — one central cluster plus N separate site clusters —
/// and every topology now gives each SITE node a permanently LOCAL, undecorated SQLite store, so a
@@ -293,7 +293,7 @@ public static class SecretsRegistration
///
/// A Site node ( = ) always resolves
/// to the plain local SQLite store — there is no replicating or decorated store left for a
- /// site to be given. This is deliberate (Task 11): DeployedSecretsMirror deletes a name
+ /// site to be given. This is deliberate: DeployedSecretsMirror deletes a name
/// from the local store directly, and a replication decorator would have published that
/// delete fleet-wide.
///
diff --git a/src/ZB.MOM.WW.ScadaBridge.Host/Services/CentralSharedSeamAuditWriter.cs b/src/ZB.MOM.WW.ScadaBridge.Host/Services/CentralSharedSeamAuditWriter.cs
index de687b45..a2ee9923 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Host/Services/CentralSharedSeamAuditWriter.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Host/Services/CentralSharedSeamAuditWriter.cs
@@ -24,7 +24,10 @@ namespace ZB.MOM.WW.ScadaBridge.Host.Services;
public sealed class CentralSharedSeamAuditWriter(ICentralAuditWriter central)
: ZB.MOM.WW.Audit.IAuditWriter
{
- ///
+ /// Forwards the audit event to the central direct-write audit path.
+ /// The audit event to write.
+ /// Token to cancel the write.
+ /// A task that represents the asynchronous operation.
public Task WriteAsync(AuditEvent auditEvent, CancellationToken cancellationToken = default)
=> central.WriteAsync(auditEvent, cancellationToken);
}
diff --git a/src/ZB.MOM.WW.ScadaBridge.Host/SiteKestrelSetup.cs b/src/ZB.MOM.WW.ScadaBridge.Host/SiteKestrelSetup.cs
index eb74d00c..ec02bde5 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Host/SiteKestrelSetup.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Host/SiteKestrelSetup.cs
@@ -25,7 +25,7 @@ internal static class SiteKestrelSetup
/// this node holding a half-open stream that will never produce another byte and never fail
/// either. For LocalDb sync that is the expensive one: the handler sits inside its single-stream
/// guard, so the rebooted peer's reconnects are refused while this node still reports itself
- /// replication-connected (ScadaBridge arch-review 2026-08-19, P4-3).
+ /// replication-connected.
///
///
/// 60s costs one frame per idle minute per connection and bounds detection at roughly
@@ -46,6 +46,9 @@ internal static class SiteKestrelSetup
/// governs the gRPC port and the metrics port alike. That is the intent: the metrics listener
/// also speaks HTTP/2, and a scrape connection has nothing to lose from a ping.
///
+ /// The Kestrel server options to configure.
+ /// Port for the HTTP/2 gRPC listener.
+ /// Port for the metrics listener.
public static void Configure(KestrelServerOptions options, int grpcPort, int metricsPort)
{
options.Limits.Http2.KeepAlivePingDelay = Http2KeepAlivePingDelay;
diff --git a/src/ZB.MOM.WW.ScadaBridge.Host/SiteLocalDbLegacyMigrator.cs b/src/ZB.MOM.WW.ScadaBridge.Host/SiteLocalDbLegacyMigrator.cs
index e1fd1506..76d44e1d 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Host/SiteLocalDbLegacyMigrator.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Host/SiteLocalDbLegacyMigrator.cs
@@ -154,6 +154,8 @@ public static class SiteLocalDbLegacyMigrator
/// falling back to the code default. Relative paths resolve against the process working
/// directory — NOT the data volume — because that is where the old code actually put them.
///
+ /// The application configuration.
+ /// The absolute path of the legacy tracking database.
internal static string ResolveTrackingPath(IConfiguration config)
{
var connectionString =
@@ -184,6 +186,8 @@ public static class SiteLocalDbLegacyMigrator
}
/// Resolves the legacy event-log path from the OLD key, falling back to the code default.
+ /// The application configuration.
+ /// The absolute path of the legacy event-log database.
internal static string ResolveEventLogPath(IConfiguration config)
{
var path = config["ScadaBridge:SiteEventLog:DatabasePath"] ?? DefaultEventLogPath;
@@ -203,6 +207,8 @@ public static class SiteLocalDbLegacyMigrator
/// mounted data volume (./data/), so on the docker rig there is a real file here
/// with real buffered messages — this migration is not the usual no-op.
///
+ /// The application configuration.
+ /// The absolute path of the legacy store-and-forward database.
internal static string ResolveStoreAndForwardPath(IConfiguration config)
{
var path = config["ScadaBridge:StoreAndForward:SqliteDbPath"] ?? DefaultStoreAndForwardPath;
@@ -222,6 +228,8 @@ public static class SiteLocalDbLegacyMigrator
/// to the code default. Like store-and-forward — and unlike the two Phase 1 paths — this
/// default is inside the mounted data volume, so a real deployment has real config here.
///
+ /// The application configuration.
+ /// The absolute path of the legacy site configuration database.
internal static string ResolveSiteStoragePath(IConfiguration config)
{
var path = config["ScadaBridge:Database:SiteDbPath"] ?? DefaultSiteStoragePath;
@@ -471,10 +479,12 @@ public static class SiteLocalDbLegacyMigrator
///
/// This never shows up on Linux or macOS: POSIX rename does not care about open handles,
/// so the docker rig and developer machines migrate cleanly. It reproduces on the first
- /// Windows site node that boots with a pre-Phase-2 database beside it — which is every
+ /// Windows site node that boots with a pre-consolidation database beside it — which is every
/// Windows install being upgraded, exactly once.
///
///
+ /// The path of the legacy SQLite file.
+ /// A read-only, non-pooled SQLite connection string for the file.
internal static string LegacyReadOnlyConnectionString(string legacyPath)
=> $"Data Source={legacyPath};Mode=ReadOnly;Pooling=False";
diff --git a/src/ZB.MOM.WW.ScadaBridge.Host/SiteLocalDbSetup.cs b/src/ZB.MOM.WW.ScadaBridge.Host/SiteLocalDbSetup.cs
index 6d27556d..1b56f73a 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Host/SiteLocalDbSetup.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Host/SiteLocalDbSetup.cs
@@ -70,7 +70,7 @@ public static class SiteLocalDbSetup
/// masquerade as the other's.
///
///
- /// secret_mirror_status is the twelfth entry (review finding M1). It is per-node
+ /// secret_mirror_status is the twelfth entry. It is per-node
/// DATA — one row per node, keyed by node_name, each written only by its own node —
/// but it is replicated precisely so the active node can read the standby's row and put
/// it on the site health report (only the active node sends one). Explicit primary key,
@@ -95,7 +95,7 @@ public static class SiteLocalDbSetup
///
/// Creates the site node's tables, opts them into change capture when this node
- /// replicates, and migrates any pre-Phase-1 databases in.
+ /// replicates, and migrates any pre-consolidation databases in.
///
/// The LocalDb instance being initialized.
///
@@ -148,8 +148,7 @@ public static class SiteLocalDbSetup
// ELEVENTH entry to ReplicatedTables on an already-replicating site streams a
// snapshot of only that one table, where through 0.2.x the flag was a single
// per-database bit and the same edit would have re-streamed all eleven in full, in
- // both directions (the same holds for secret_mirror_status, the twelfth, added by
- // review finding M1). Nothing about the first boot changes: enabling replication on a
+ // both directions (the same holds for secret_mirror_status, the twelfth). Nothing about the first boot changes: enabling replication on a
// site that has been running without it seeds all twelve at once, the flagged set is
// therefore every registered table, and the library sends that as an ordinary full
// snapshot. A hypothetical "register the whole list in one call" API would silently
@@ -169,7 +168,7 @@ public static class SiteLocalDbSetup
else
{
// Not merely "do not register": actively remove capture this node must not pay for.
- // The file may have been registered by an EARLIER build (every build before WP1.3
+ // The file may have been registered by an EARLIER build (every earlier build
// registered unconditionally), in which case its triggers are still there, still
// running two INSERTs plus a json_object of the full row inside every write
// transaction, still appending to an oplog with no reader. DeregisterReplicated is
@@ -202,7 +201,7 @@ public static class SiteLocalDbSetup
// AFTER registration, so migrated rows enter the oplog and reach the peer like
// any other write. Before it, they would be invisible to replication forever.
// Deliberately OUTSIDE the guard: an unreplicated node still has to absorb its
- // pre-Phase-1 files, and there is no peer for its rows to be invisible to.
+ // pre-consolidation files, and there is no peer for its rows to be invisible to.
SiteLocalDbLegacyMigrator.Migrate(db, config);
}
@@ -252,6 +251,8 @@ public static class SiteLocalDbSetup
/// docs/deployment/topology-guide.md.
///
///
+ /// The node configuration to inspect.
+ /// when replication is configured on this node; otherwise .
internal static bool ReplicationIsConfigured(IConfiguration config)
{
var section = config.GetSection("LocalDb:Replication");
@@ -261,8 +262,7 @@ public static class SiteLocalDbSetup
}
///
- /// Sets auto_vacuum = INCREMENTAL on the consolidated LocalDb file — arch-review
- /// 2026-08-19 P4-6 — but ONLY when that file does not exist yet.
+ /// Sets auto_vacuum = INCREMENTAL on the consolidated LocalDb file, but ONLY when that file does not exist yet.
///
///
///
diff --git a/src/ZB.MOM.WW.ScadaBridge.Host/SiteServiceRegistration.cs b/src/ZB.MOM.WW.ScadaBridge.Host/SiteServiceRegistration.cs
index 267397c0..973ca7bf 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Host/SiteServiceRegistration.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Host/SiteServiceRegistration.cs
@@ -92,7 +92,7 @@ public static class SiteServiceRegistration
// The shim is gone; SiteLocalDbDirectoryTests still pins the outcome here, since
// what this host needs is the guarantee, not any particular owner of it.
//
- // arch-review 2026-08-19 P4-6: MUST run before AddZbLocalDb — its singleton
+ // MUST run before AddZbLocalDb — its singleton
// factory is lazy, but the ordering still has to be enforced here rather than
// relied upon, because it is what makes "PRAGMA auto_vacuum = INCREMENTAL" take
// effect at all. SQLite only honors that pragma on a table-less database, and
@@ -116,7 +116,7 @@ public static class SiteServiceRegistration
services.AddDataConnectionLayer();
// A Site node's secret store is ALWAYS the local SQLite store — never a replication
- // decorator. Task 11 retired both the pull-only gRPC secrets hub (central hosts, sites
+ // decorator. Both were retired: the pull-only gRPC secrets hub (central hosts, sites
// sweep) and the shared-SQL-Server replication mode that preceded it. This is deliberate,
// not merely simplification: ZB.MOM.WW.ScadaBridge.SiteRuntime.Secrets.DeployedSecretsMirror
// deletes a name from the local store directly the moment a deploy artifact stops
@@ -199,7 +199,7 @@ public static class SiteServiceRegistration
services.AddSingleton(sp =>
sp.GetRequiredService().GetOrCreateActorSystem());
- // Simultaneous-cold-start split-brain guard (Gitea #33, dark switch
+ // Simultaneous-cold-start split-brain guard (dark switch
// ScadaBridge:Cluster:BootstrapGuard:Enabled, default off). This is the case the guard was
// built for — a shared power event that powers up both site VMs together. Registered AFTER
// the AkkaHostedService/ActorSystem bridge (mirrors the Central composition root in
@@ -221,8 +221,7 @@ public static class SiteServiceRegistration
return new AkkaClusterNodeProvider(akkaService, siteRole);
});
- // The EventLogPurgeService runs on every site host node and, as of arch-review
- // 2026-08-19 P4-4, purges its own site_events file on EVERY node — the old
+ // The EventLogPurgeService runs on every site host node and purges its own site_events file on EVERY node — the old
// early-exit-on-standby gate this delegate drove is gone (DataConnectionActor
// writes site_events on both site nodes, so a standby that never purged would grow
// unbounded on an unreplicated site). Register it to delegate to
diff --git a/src/ZB.MOM.WW.ScadaBridge.Host/StartupValidator.cs b/src/ZB.MOM.WW.ScadaBridge.Host/StartupValidator.cs
index 713048fa..b5f564ce 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Host/StartupValidator.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Host/StartupValidator.cs
@@ -27,7 +27,7 @@ public static class StartupValidator
var seedNodes = configuration.GetSection("ScadaBridge:Cluster:SeedNodes").Get>();
// Secrets replication (the gRPC hub, and the SQL-Server ReplicatingSecretStore mode that
- // preceded it) is retired in ScadaBridge (Task 11) — sites receive secrets exclusively via
+ // preceded it) is retired in ScadaBridge — sites receive secrets exclusively via
// Deploy Artifacts, and a replicated site store would publish DeployedSecretsMirror's local
// deletes fleet-wide. Checked unconditionally (both roles): a Site used to be the gRPC
// hub's follower half, so the flag was never Central-only.
@@ -56,7 +56,7 @@ public static class StartupValidator
.Require("ScadaBridge:Node:RemotingPort",
_ => portValid,
"must be 1-65535")
- // Retired for BOTH roles (Task 11) — a Site could set this too, as the gRPC hub's
+ // Retired for BOTH roles — a Site could set this too, as the gRPC hub's
// follower half. Deliberately NOT folded into a role-scoped block below.
.Require("Secrets:Replication:Enabled",
_ => !secretsReplicationEnabled,
@@ -192,8 +192,8 @@ public static class StartupValidator
+ "endpoint under ScadaBridge:Communication:CentralGrpcEndpoints "
+ "(e.g. http://scadabridge-central-a:8083). Central nodes leave it empty.");
- // The site hot-path audit writer's SQLite file (arch-review remediation
- // WP1.2). SqliteAuditWriterOptions.DatabasePath has no default (mirrors
+ // The site hot-path audit writer's SQLite file (arch-review remediation).
+ // SqliteAuditWriterOptions.DatabasePath has no default (mirrors
// LocalDb:Path) — an unset value would previously fall back to a bare
// "auditlog.db" resolved relative to the process CWD, which on the docker
// rig is the container's ephemeral overlayfs, not the mounted /app/data
@@ -245,6 +245,8 @@ public static class StartupValidator
}
/// Non-fatal configuration findings. Collected separately from because this runs before the logger exists; Program.cs logs each entry at Warning once Serilog is configured.
+ /// The application configuration to inspect.
+ /// The warning messages to log; empty when there are none.
public static IReadOnlyList CollectWarnings(IConfiguration configuration)
{
var role = configuration.GetSection("ScadaBridge:Node")["Role"];
@@ -270,7 +272,7 @@ public static class StartupValidator
// both central nodes to deploy the SAME secret set; a per-node store means each node has
// silently drifted the moment anyone edits a secret on only one of them.
//
- // No Secrets:Replication:Enabled gate here any more (Task 11): that flag is now a hard
+ // No Secrets:Replication:Enabled gate here any more: that flag is now a hard
// Validate() failure on either role (the gRPC hub and SQL-Server secrets replication are
// both retired), so CollectWarnings never runs with it true on a config that also passed
// Validate — the old gate that suppressed this warning when replication was on is dead code
@@ -284,8 +286,8 @@ public static class StartupValidator
+ "deploy its own secret set to sites; set Secrets:Store:Provider=SqlServer");
}
- // Review round 1, Minor #4: a config that still carries Secrets:Replication or
- // Secrets:GrpcHub child keys — left over from before Task 11 retired the hub — is not
+ // A config that still carries Secrets:Replication or
+ // Secrets:GrpcHub child keys — left over from before the hub was retired — is not
// itself an error (Validate already rejects Enabled=true outright), but every key under
// either section is now silently ignored. That is worth a Warning so an operator cleans
// QA/prod appsettings rather than assuming a still-present Mode/BearerToken/Endpoint value
@@ -307,7 +309,7 @@ public static class StartupValidator
///
/// Whether Secrets:Replication:Enabled is "set" — read as a raw string rather than
- /// IConfiguration.GetValue<bool> (review round 1, Minor #3), which throws a raw
+ /// IConfiguration.GetValue<bool> which throws a raw
/// binder on a non-boolean value like "yes" or
/// "1", so a malformed leftover key would surface as an unrelated parse crash instead of
/// the designed retirement message in . Any value other than absent,
diff --git a/src/ZB.MOM.WW.ScadaBridge.InboundAPI/ApiMethodCache.cs b/src/ZB.MOM.WW.ScadaBridge.InboundAPI/ApiMethodCache.cs
index 6322b3d0..0d4c4a14 100644
--- a/src/ZB.MOM.WW.ScadaBridge.InboundAPI/ApiMethodCache.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.InboundAPI/ApiMethodCache.cs
@@ -4,7 +4,7 @@ using ZB.MOM.WW.ScadaBridge.Commons.Entities.InboundApi;
namespace ZB.MOM.WW.ScadaBridge.InboundAPI;
///
-/// WP2.6b (arch-review misc — Inbound API per-request SQL): a short-TTL cache of
+/// A short-TTL cache of
/// resolved ApiMethod rows, sitting in front of IInboundApiRepository.GetMethodByNameAsync
/// so a repeated POST /api/{methodName} burst for the same method does not each pay a
/// database round-trip.
@@ -60,6 +60,7 @@ public sealed class ApiMethodCache
/// The method name to resolve.
/// Loads the current row from the repository on a cache miss.
/// Cancellation token for the fetch.
+ /// The cached or freshly fetched , or null when the method does not exist.
public async Task GetOrFetchAsync(
string methodName,
Func> fetch,
diff --git a/src/ZB.MOM.WW.ScadaBridge.InboundAPI/EndpointExtensions.cs b/src/ZB.MOM.WW.ScadaBridge.InboundAPI/EndpointExtensions.cs
index 65d1ab2a..ddd8679a 100644
--- a/src/ZB.MOM.WW.ScadaBridge.InboundAPI/EndpointExtensions.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.InboundAPI/EndpointExtensions.cs
@@ -151,7 +151,7 @@ public static class EndpointExtensions
// "Echo" does not grant "echo". This is the intended invariant: method
// names are case-sensitive identifiers, and a key's granted scopes must be
// provisioned with the exact casing of the methods they authorize.
- // WP2.6b: the per-request repository round-trip is fronted by a short-TTL
+ // The per-request repository round-trip is fronted by a short-TTL
// ApiMethodCache (invalidated by name on an ApiMethod ScriptArtifactsChanged
// notification, and self-healing via TTL expiry otherwise — see ApiMethodCache
// and ScriptArtifactChangeSubscriber). Only reached after the in-scope check
diff --git a/src/ZB.MOM.WW.ScadaBridge.InboundAPI/HttpStandbyRequestForwarder.cs b/src/ZB.MOM.WW.ScadaBridge.InboundAPI/HttpStandbyRequestForwarder.cs
index ca22f24d..4c2817ce 100644
--- a/src/ZB.MOM.WW.ScadaBridge.InboundAPI/HttpStandbyRequestForwarder.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.InboundAPI/HttpStandbyRequestForwarder.cs
@@ -233,7 +233,7 @@ public sealed class HttpStandbyRequestForwarder : IStandbyRequestForwarder
Body = body;
}
- ///
+ /// The peer's HTTP status code, replayed verbatim.
public int? StatusCode { get; }
/// The peer's Content-Type, replayed verbatim.
@@ -242,7 +242,9 @@ public sealed class HttpStandbyRequestForwarder : IStandbyRequestForwarder
/// The peer's response body, replayed verbatim.
public byte[] Body { get; }
- ///
+ /// Writes the peer's status, content type and body to the response.
+ /// The HTTP context to write the relayed response to.
+ /// A task that represents the asynchronous operation.
public async Task ExecuteAsync(HttpContext httpContext)
{
httpContext.Response.StatusCode = StatusCode ?? StatusCodes.Status200OK;
diff --git a/src/ZB.MOM.WW.ScadaBridge.InboundAPI/InboundApiOptions.cs b/src/ZB.MOM.WW.ScadaBridge.InboundAPI/InboundApiOptions.cs
index 127b4ddc..18ead25a 100644
--- a/src/ZB.MOM.WW.ScadaBridge.InboundAPI/InboundApiOptions.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.InboundAPI/InboundApiOptions.cs
@@ -36,7 +36,7 @@ public class InboundApiOptions
public string ApiKeyPepper { get; set; } = string.Empty;
///
- /// WP2.6b (arch-review misc — Inbound API per-request SQL): how long a resolved
+ /// How long a resolved
/// ApiMethod row (found OR not-found) is served from
/// before the next request re-fetches it from the repository. The
/// ScriptArtifactChangeSubscriber/IScriptArtifactChangeBus pipeline
diff --git a/src/ZB.MOM.WW.ScadaBridge.InboundAPI/InboundDatabaseHelper.cs b/src/ZB.MOM.WW.ScadaBridge.InboundAPI/InboundDatabaseHelper.cs
index d25f189c..252321a1 100644
--- a/src/ZB.MOM.WW.ScadaBridge.InboundAPI/InboundDatabaseHelper.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.InboundAPI/InboundDatabaseHelper.cs
@@ -44,8 +44,7 @@ namespace ZB.MOM.WW.ScadaBridge.InboundAPI;
///
///
///
-/// Audited (Audit Log #23, outbound DB scope; Flow Debugger outbound-calls plan
-/// 2026-09-06 D7). Every call emits exactly ONE DbOutbound/DbWrite row
+/// Audited (outbound DB scope). Every call emits exactly ONE DbOutbound/DbWrite row
/// through the central using the site's
/// AuditingDbCommand vocabulary: Extra = {"op":"write","rowsAffected":N} for
/// , {"op":"read","rowsReturned":N} for the two query
diff --git a/src/ZB.MOM.WW.ScadaBridge.InboundAPI/InboundScriptExecutor.cs b/src/ZB.MOM.WW.ScadaBridge.InboundAPI/InboundScriptExecutor.cs
index 8d37d21f..c07829bc 100644
--- a/src/ZB.MOM.WW.ScadaBridge.InboundAPI/InboundScriptExecutor.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.InboundAPI/InboundScriptExecutor.cs
@@ -152,6 +152,9 @@ public class InboundScriptExecutor
public void RemoveHandler(string methodName) => InvalidateMethod(methodName);
/// Checks compilation without publishing or invalidating a live handler.
+ /// The API method whose script is compiled.
+ /// The compilation diagnostics, empty on success.
+ /// true when the script compiles; otherwise false.
public bool ValidateCompilation(ApiMethod method, out IReadOnlyList errors)
{
var (handler, diagnostics) = Compile(method);
@@ -332,6 +335,7 @@ public class InboundScriptExecutor
/// script execution points back at this inbound request. Null when the script
/// runs outside an inbound API request flow.
///
+ /// When true, the run is traced for the Flow Debugger.
/// The result of executing the script.
public async Task ExecuteAsync(
ApiMethod method,
diff --git a/src/ZB.MOM.WW.ScadaBridge.InboundAPI/InboundScriptLogger.cs b/src/ZB.MOM.WW.ScadaBridge.InboundAPI/InboundScriptLogger.cs
index 852c17ed..105206fa 100644
--- a/src/ZB.MOM.WW.ScadaBridge.InboundAPI/InboundScriptLogger.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.InboundAPI/InboundScriptLogger.cs
@@ -212,7 +212,7 @@ public sealed class InboundScriptLogger
///
/// Applies the per-message cap, marking a truncated message with a trailing ellipsis.
///
- /// Final review M2 — mirrors the site runtime's ScriptLogger.Clip: cuts one char
+ /// Mirrors the site runtime's ScriptLogger.Clip: cuts one char
/// short when the cap would land between the two halves of a surrogate pair, so an emoji
/// straddling the boundary is dropped whole rather than leaving a lone surrogate that breaks
/// JSON serialization downstream.
diff --git a/src/ZB.MOM.WW.ScadaBridge.InboundAPI/Middleware/AuditWriteMiddleware.cs b/src/ZB.MOM.WW.ScadaBridge.InboundAPI/Middleware/AuditWriteMiddleware.cs
index c74c9595..9edb24b6 100644
--- a/src/ZB.MOM.WW.ScadaBridge.InboundAPI/Middleware/AuditWriteMiddleware.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.InboundAPI/Middleware/AuditWriteMiddleware.cs
@@ -129,6 +129,10 @@ public sealed class AuditWriteMiddleware
/// so existing tests and composition roots without the central health snapshot
/// wired still construct without the counter; a NoOp is used when absent.
///
+ ///
+ /// Live-reloadable inbound API options, read per-request. Optional; when absent the
+ /// defaults apply.
+ ///
public AuditWriteMiddleware(
RequestDelegate next,
ICentralAuditWriter auditWriter,
diff --git a/src/ZB.MOM.WW.ScadaBridge.InboundAPI/RelayProvenance.cs b/src/ZB.MOM.WW.ScadaBridge.InboundAPI/RelayProvenance.cs
index 8935e96e..6f05783c 100644
--- a/src/ZB.MOM.WW.ScadaBridge.InboundAPI/RelayProvenance.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.InboundAPI/RelayProvenance.cs
@@ -15,10 +15,21 @@ internal static class RelayProvenance
private sealed record Proof(long Time, Guid ExecutionId, string Ip, string Binding);
private static readonly byte[] Domain = "ScadaBridge.InboundRelay.v1"u8.ToArray();
+ /// Whether a valid relay proof has been verified for this request.
+ /// The current HTTP context.
+ /// True when the request carried a verified relay proof.
internal static bool IsVerified(HttpContext context) => context.Items.ContainsKey(VerifiedKey);
+ /// Gets the effective caller IP: the verified relayed IP, otherwise the connection's remote IP.
+ /// The current HTTP context.
+ /// The caller IP address, or null when unknown.
internal static string? CallerIp(HttpContext context)
=> context.Items.TryGetValue(VerifiedKey, out var ip) ? ip as string : context.Connection.RemoteIpAddress?.ToString();
+ /// Builds a signed relay proof header value binding the caller IP, execution id and request.
+ /// The current HTTP context.
+ /// The request body being relayed.
+ /// The shared secret used to compute the MAC.
+ /// The proof header value, or null when it cannot be produced.
internal static string? Sign(HttpContext context, byte[] body, string pepper)
{
if (pepper.Length < 16 || context.Connection.RemoteIpAddress is null) return null;
@@ -30,6 +41,10 @@ internal static class RelayProvenance
return Convert.ToBase64String(bytes) + "." + Convert.ToHexString(Mac(bytes, pepper));
}
+ /// Validates an incoming relay proof header and, when valid, records the relayed caller IP and execution id on the request.
+ /// The current HTTP context.
+ /// The inbound API options.
+ /// A task that represents the asynchronous operation.
internal static async Task VerifyAsync(HttpContext context, InboundApiOptions options)
{
var header = context.Request.Headers[Header].ToString();
@@ -57,6 +72,11 @@ internal static class RelayProvenance
context.Items[AuditWriteMiddleware.InboundExecutionIdItemKey] = proof.ExecutionId;
}
+ /// Reads a stream fully, failing if it exceeds the given byte limit.
+ /// The stream to read.
+ /// The maximum number of bytes allowed.
+ /// Token to cancel the read.
+ /// The bytes read.
internal static async Task ReadBoundedAsync(Stream body, long limit, CancellationToken ct)
{
using var buffer = new MemoryStream();
diff --git a/src/ZB.MOM.WW.ScadaBridge.InboundAPI/ScriptArtifactChangeSubscriber.cs b/src/ZB.MOM.WW.ScadaBridge.InboundAPI/ScriptArtifactChangeSubscriber.cs
index 8d8bd5c1..7b516a02 100644
--- a/src/ZB.MOM.WW.ScadaBridge.InboundAPI/ScriptArtifactChangeSubscriber.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.InboundAPI/ScriptArtifactChangeSubscriber.cs
@@ -35,7 +35,7 @@ public sealed class ScriptArtifactChangeSubscriber : IHostedService
/// Initializes the subscriber.
/// The compiled-handler cache to invalidate.
///
- /// WP2.6b: the short-TTL resolved-ApiMethod-row cache to invalidate alongside
+ /// The short-TTL resolved-ApiMethod-row cache to invalidate alongside
/// the compiled handler.
///
/// Logger instance.
@@ -52,7 +52,9 @@ public sealed class ScriptArtifactChangeSubscriber : IHostedService
_bus = bus;
}
- ///
+ /// Subscribes to the change bus, when one is registered, to invalidate cached handlers.
+ /// Cancellation token (unused).
+ /// A completed task.
public Task StartAsync(CancellationToken cancellationToken)
{
if (_bus is null)
@@ -66,7 +68,9 @@ public sealed class ScriptArtifactChangeSubscriber : IHostedService
return Task.CompletedTask;
}
- ///
+ /// Disposes the change-bus subscription.
+ /// Cancellation token (unused).
+ /// A completed task.
public Task StopAsync(CancellationToken cancellationToken)
{
_subscription?.Dispose();
diff --git a/src/ZB.MOM.WW.ScadaBridge.InboundAPI/ServiceCollectionExtensions.cs b/src/ZB.MOM.WW.ScadaBridge.InboundAPI/ServiceCollectionExtensions.cs
index 07111e34..2ccf6406 100644
--- a/src/ZB.MOM.WW.ScadaBridge.InboundAPI/ServiceCollectionExtensions.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.InboundAPI/ServiceCollectionExtensions.cs
@@ -20,7 +20,7 @@ public static class ServiceCollectionExtensions
services.AddSingleton();
services.AddScoped();
- // WP2.6b: short-TTL cache of resolved ApiMethod rows, sitting in front of the
+ // Short-TTL cache of resolved ApiMethod rows, sitting in front of the
// per-request repository fetch in EndpointExtensions. Invalidated by name via
// ScriptArtifactChangeSubscriber (below) and self-heals via TTL expiry for
// changes the bus does not cover.
diff --git a/src/ZB.MOM.WW.ScadaBridge.KpiHistory/KpiSourceAdmission.cs b/src/ZB.MOM.WW.ScadaBridge.KpiHistory/KpiSourceAdmission.cs
index ffa1da49..aabceb62 100644
--- a/src/ZB.MOM.WW.ScadaBridge.KpiHistory/KpiSourceAdmission.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.KpiHistory/KpiSourceAdmission.cs
@@ -12,6 +12,13 @@ internal sealed class KpiSourceAdmission
private readonly object _gate = new();
private readonly Dictionary>> _tasks = new();
+ ///
+ /// Starts a collection for the given registration unless an earlier one is still running, in which case the running one is returned.
+ ///
+ /// Stable index of the source registration.
+ /// Delegate that performs the collection when a new one is started.
+ /// The new collection task, or the still-running earlier one.
+ /// if a new collection was started; if an earlier one is still running.
internal bool TryStart(
int registrationIndex,
Func>> collect,
diff --git a/src/ZB.MOM.WW.ScadaBridge.ManagementService/AuditEndpoints.cs b/src/ZB.MOM.WW.ScadaBridge.ManagementService/AuditEndpoints.cs
index 55193d04..1c5f983b 100644
--- a/src/ZB.MOM.WW.ScadaBridge.ManagementService/AuditEndpoints.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.ManagementService/AuditEndpoints.cs
@@ -763,7 +763,7 @@ public static class AuditEndpoints
// ─────────────────────────────────────────────────────────────────────
// CSV helpers — 24 columns, same order/naming as the JSONL export
- // (AuditExportRow property declaration order; arch-review 2026-08-19 P6-7)
+ // (AuditExportRow property declaration order)
// ─────────────────────────────────────────────────────────────────────
public const string CsvHeader =
@@ -783,7 +783,7 @@ public static class AuditEndpoints
/// , ) are run
/// through before RFC 4180 quoting so a leading
/// =/+/-/@/TAB/CR can't be interpreted as a formula or DDE
- /// trigger by a spreadsheet application (CWE-1236). GUID/enum/numeric/date/bool columns the
+ /// trigger by a spreadsheet application (CSV formula injection). GUID/enum/numeric/date/bool columns the
/// exporter formats itself are never neutralized — none of them can start with a trigger
/// character, and neutralizing a signed numeric column would corrupt it.
///
@@ -833,7 +833,7 @@ public static class AuditEndpoints
return;
}
- // OWASP CSV-injection neutralization (CWE-1236): a free-text cell whose
+ // OWASP CSV-injection neutralization (CSV formula injection): a free-text cell whose
// first character would be interpreted by a spreadsheet application as a
// formula/DDE trigger is prefixed with a literal single quote. Applied
// BEFORE RFC 4180 quoting, and only to free-text columns — never to the
diff --git a/src/ZB.MOM.WW.ScadaBridge.ManagementService/ConfigSecretScrubber.cs b/src/ZB.MOM.WW.ScadaBridge.ManagementService/ConfigSecretScrubber.cs
index 6ecdbe2d..8d85e1aa 100644
--- a/src/ZB.MOM.WW.ScadaBridge.ManagementService/ConfigSecretScrubber.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.ManagementService/ConfigSecretScrubber.cs
@@ -18,6 +18,9 @@ internal static class ConfigSecretScrubber
private static readonly string[] SecretNameFragments =
["password", "secret", "token", "apikey", "credential", "passphrase"];
+ /// Determines whether a property name looks like it holds a secret.
+ /// The property name to test.
+ /// True when the name contains a secret-like fragment.
internal static bool IsSecretName(string propertyName)
{
foreach (var fragment in SecretNameFragments)
diff --git a/src/ZB.MOM.WW.ScadaBridge.ManagementService/DebugStreamHub.cs b/src/ZB.MOM.WW.ScadaBridge.ManagementService/DebugStreamHub.cs
index 87758fd7..7b72e1d9 100644
--- a/src/ZB.MOM.WW.ScadaBridge.ManagementService/DebugStreamHub.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.ManagementService/DebugStreamHub.cs
@@ -193,7 +193,7 @@ public class DebugStreamHub : Hub
CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default);
});
- // arch-review 2026-08-19 P6-6: the client may have disconnected while
+ // The client may have disconnected while
// StartStreamAsync was in flight (up to 30s). OnDisconnectedAsync already ran
// and found no session id to release, so publishing it here would leave the
// session registered in the singleton DebugStreamService — whose keepalive
@@ -255,7 +255,7 @@ public class DebugStreamHub : Hub
///
/// Atomically removes and returns the session id registered for this connection, so a
/// release path and the late publish in cannot both
- /// claim (or both miss) the same session — see P6-6 there.
+ /// claim (or both miss) the same session — see there.
///
/// The session id that was registered, or if none was.
private string? TakeSessionId()
@@ -274,7 +274,7 @@ public class DebugStreamHub : Hub
///
public override async Task OnDisconnectedAsync(Exception? exception)
{
- // P6-6: mark the connection gone BEFORE releasing, so a SubscribeInstance still
+ // Mark the connection gone BEFORE releasing, so a SubscribeInstance still
// awaiting StartStreamAsync sees the flag and stops its own session rather than
// registering it against a connection nothing will ever unsubscribe.
lock (Context.Items)
diff --git a/src/ZB.MOM.WW.ScadaBridge.ManagementService/FlowDebug/FlowDebugRunner.cs b/src/ZB.MOM.WW.ScadaBridge.ManagementService/FlowDebug/FlowDebugRunner.cs
index 678b6556..37079fc2 100644
--- a/src/ZB.MOM.WW.ScadaBridge.ManagementService/FlowDebug/FlowDebugRunner.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.ManagementService/FlowDebug/FlowDebugRunner.cs
@@ -96,12 +96,16 @@ public sealed class FlowDebugRunner
private readonly Func _now;
/// Creates the runner.
+ /// Logger for run diagnostics.
public FlowDebugRunner(ILogger logger)
: this(logger, Task.Delay, () => DateTimeOffset.UtcNow)
{
}
/// Creates the runner with an injectable delay and clock (tests).
+ /// Logger for run diagnostics.
+ /// Delay function used for polling waits.
+ /// Clock returning the current UTC time.
public FlowDebugRunner(
ILogger logger,
Func delay,
@@ -125,6 +129,11 @@ public sealed class FlowDebugRunner
/// registered none (a test composition root) collects raw but is still capped.
///
///
+ /// The request-scoped service provider.
+ /// The command to execute.
+ /// The authenticated operator.
+ /// Cancellation token.
+ /// The run result with the collected trace rows.
public Task RunMethodAsync(
IServiceProvider sp, FlowDebugRunMethodCommand cmd, AuthenticatedUser user, CancellationToken ct)
=> WithinCommandBudgetAsync(sp, (deadline, token) => RunMethodCoreAsync(sp, cmd, user, deadline, token), ct);
@@ -233,6 +242,11 @@ public sealed class FlowDebugRunner
/// Live run of a value-change trigger (design §5.3/§5.4): traced routed write through the
/// site's normal path, then poll the site's trace rows for the spawned run(s).
///
+ /// The request-scoped service provider.
+ /// The command to execute.
+ /// The authenticated operator.
+ /// Cancellation token.
+ /// The run result with the collected trace rows.
public Task RunValueChangeAsync(
IServiceProvider sp, FlowDebugRunValueChangeCommand cmd, AuthenticatedUser user, CancellationToken ct)
=> WithinCommandBudgetAsync(sp, (deadline, token) => RunValueChangeCoreAsync(sp, cmd, user, deadline, token), ct);
@@ -269,6 +283,11 @@ public sealed class FlowDebugRunner
/// rather than through is deliberate: the helper emits its own
/// always-on RouteCall row with no operator actor, which would give the graph two roots.
///
+ /// The request-scoped service provider.
+ /// The command to execute.
+ /// The authenticated operator.
+ /// Cancellation token.
+ /// The run result with the collected trace rows.
public Task RunScriptAsync(
IServiceProvider sp, FlowDebugRunScriptCommand cmd, AuthenticatedUser user, CancellationToken ct)
=> WithinCommandBudgetAsync(sp, (deadline, token) => RunScriptCoreAsync(sp, cmd, user, deadline, token), ct);
@@ -570,6 +589,11 @@ public sealed class FlowDebugRunner
}
/// Replay (design §7.1): the stored ScriptTrace chain for any execution id.
+ /// The request-scoped service provider.
+ /// The replay command naming the execution.
+ /// Cancellation token.
+ /// The authenticated operator.
+ /// The reconstructed run result read from the stored trace rows.
public async Task ReplayAsync(
IServiceProvider sp, FlowDebugReplayCommand cmd, CancellationToken ct, AuthenticatedUser user)
{
@@ -627,8 +651,11 @@ public sealed class FlowDebugRunner
private sealed class TraceReadBatch(Guid[] executionIds, DateTime sinceUtc)
{
+ /// Execution ids whose trace rows are being read.
public Guid[] ExecutionIds { get; } = executionIds;
+ /// Lower bound (UTC) for the next read.
public DateTime SinceUtc { get; set; } = sinceUtc;
+ /// Keyset cursor: the last event id already consumed, or null.
public string? AfterEventId { get; set; }
}
diff --git a/src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementActor.cs b/src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementActor.cs
index cbb28e01..63d0dbfb 100644
--- a/src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementActor.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementActor.cs
@@ -190,6 +190,11 @@ public class ManagementActor : ReceiveActor
}
/// Audited database-only application boundary shared by UI and management envelopes.
+ /// Scoped service provider used to resolve handlers and repositories.
+ /// The configuration command to execute.
+ /// The authenticated user the command runs as.
+ /// Optional client IP address recorded in the audit entry.
+ /// The command's result object, or null when the command produces none.
public static async Task ExecuteConfigurationCommandAsync(IServiceProvider services, object command, AuthenticatedUser user, string? clientIp = null)
{
if (!IsConfigurationCommand(command)) throw new ArgumentException("Not a configuration command.", nameof(command));
@@ -1496,8 +1501,8 @@ public class ManagementActor : ReceiveActor
// submit device writes to their permitted sites — same rule C2 applies to
// DeployArtifacts, on a higher-consequence path. Checked before the row exists.
//
- // The unresolvable case is fail-CLOSED and answered BEFORE the "not found"
- // (arch-review 2026-08-19 P6-5): resolving first and reporting not-found made
+ // The unresolvable case is fail-CLOSED and answered BEFORE the "not found":
+ // resolving first and reporting not-found made
// this handler a site-identifier existence oracle for an out-of-scope caller,
// who now gets the same scope denial whether or not the site exists.
var siteRepo = sp.GetRequiredService();
@@ -1560,7 +1565,7 @@ public class ManagementActor : ReceiveActor
// the row's target site — checked BEFORE the TTL/self-approval/CAS chain so an
// out-of-scope verifier can neither consume the Pending transition nor relay.
//
- // It is also checked BEFORE the status test (arch-review 2026-08-19 P6-5): the
+ // It is also checked BEFORE the status test: the
// "is 'Executed', not Pending" message told an out-of-scope caller both that the
// id existed and what had happened to it. An out-of-scope caller now gets the
// same scope denial for every id, existing or not.
@@ -1701,7 +1706,7 @@ public class ManagementActor : ReceiveActor
row.ExecutionError = error;
// UpdateAsync overwrites all columns -> pass the fully-populated entity.
- // Safe as a blind write (arch-review 2026-08-19 P6-1): winning the
+ // Safe as a blind write: winning the
// Pending->Approved CAS above gives this leg EXCLUSIVE ownership of the row from
// that point on. Every other writer transitions only out of Pending —
// TryMarkApprovedAsync, TryMarkRejectedAsync and TryMarkExpiredAsync (both the
@@ -1738,8 +1743,7 @@ public class ManagementActor : ReceiveActor
// Site scope (arch-review R2 N3): the rejecting Verifier must be permitted on
// the row's target site — checked BEFORE the TTL/self-approval chain, and (as on
// approve) BEFORE the status test, so the not-found/status errors stop being an
- // existence-and-status oracle for an out-of-scope caller (arch-review 2026-08-19
- // P6-5). The CAS below is unchanged.
+ // existence-and-status oracle for an out-of-scope caller. The CAS below is unchanged.
await EnforceSiteScopeForIdentifier(sp, user, entity.SiteId);
if (!string.Equals(entity.Status, "Pending", StringComparison.Ordinal))
@@ -1759,7 +1763,7 @@ public class ManagementActor : ReceiveActor
"A secured write cannot be verified by its submitter.");
// Compare-and-swap: guards the approve-vs-reject race exactly as approve's own
- // CAS guards the two-verifier race (arch-review 2026-08-19 P6-1). The status
+ // CAS guards the two-verifier race. The status
// check above is a stale read — commands run concurrently, each on its own DI
// scope and across both central nodes on one shared MS SQL — so a concurrent
// approve can win its CAS and enter its multi-second WriteTagAsync relay in the
@@ -2003,8 +2007,8 @@ public class ManagementActor : ReceiveActor
/// Lists data connections, constrained to the caller's permitted sites.
///
///
- /// Arch-review 2026-08-19 P6-4. The list used to take no user at all and return
- /// EVERY site's connections, which nullified the row-98 fail-closed gate on
+ /// The list used to take no user at all and return
+ /// EVERY site's connections, which nullified the fail-closed gate on
/// — Get denied exactly what List handed
/// over, and the secret-elided still
/// leaked site topology plus an id-existence oracle. Scoping now mirrors
@@ -3872,7 +3876,7 @@ public class ManagementActor : ReceiveActor
private static async Task HandleDebugSnapshot(IServiceProvider sp, DebugSnapshotCommand cmd, AuthenticatedUser user)
{
- // Fail-CLOSED before the not-found (arch-review 2026-08-19 P6-5): reporting
+ // Fail-CLOSED before the not-found: reporting
// "Instance {id} not found" ahead of the scope check made this handler an
// instance-id existence oracle for an out-of-scope caller. The scope denial is
// now the same answer whether or not the instance exists.
diff --git a/src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementEndpoints.cs b/src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementEndpoints.cs
index b6cf44aa..f96e0398 100644
--- a/src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementEndpoints.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementEndpoints.cs
@@ -19,7 +19,7 @@ public static class ManagementEndpoints
///
/// Shared options for the per-request command payload JSON parse in
- /// (perf remediation, arch-review WP1.5) — avoids
+ /// — avoids
/// allocating a new per inbound management
/// request. Settings preserved exactly.
///
diff --git a/src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/Delivery/EmailNotificationDeliveryAdapter.cs b/src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/Delivery/EmailNotificationDeliveryAdapter.cs
index fad7c9be..be61eb67 100644
--- a/src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/Delivery/EmailNotificationDeliveryAdapter.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/Delivery/EmailNotificationDeliveryAdapter.cs
@@ -357,7 +357,7 @@ public sealed class EmailNotificationDeliveryAdapter : INotificationDeliveryAdap
}
// OAuth2 XOAUTH2 requires the user identity (FromAddress)
- // to be sent alongside the access token; an empty user is rejected by M365.
+ // to be sent alongside the access token; an empty user is rejected by Microsoft 365.
await smtp.AuthenticateAsync(
config.AuthType,
credentials,
diff --git a/src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/Kpi/NotificationOutboxKpiSampleSource.cs b/src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/Kpi/NotificationOutboxKpiSampleSource.cs
index ab533dc6..b8b3eeae 100644
--- a/src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/Kpi/NotificationOutboxKpiSampleSource.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/Kpi/NotificationOutboxKpiSampleSource.cs
@@ -36,7 +36,7 @@ namespace ZB.MOM.WW.ScadaBridge.NotificationOutbox.Kpi;
/// omitted when the age is null.
///
///
-/// deliveredLastInterval rescale (P5-5, arch-review Low).
+/// deliveredLastInterval rescale.
/// classifies this metric as a
/// : the central hourly rollup fold sums every
/// sample recorded in the hour, which is only correct if each sample's value is a
@@ -79,7 +79,7 @@ public sealed class NotificationOutboxKpiSampleSource : IKpiSampleSource
/// Outbox options carrying the stuck-age and delivered-window cutoffs.
///
/// KPI History options carrying — the
- /// recorder's sampling cadence the deliveredLastInterval sample is rescaled onto (P5-5),
+ /// recorder's sampling cadence the deliveredLastInterval sample is rescaled onto,
/// so the hourly rollup fold's per-sample sum stays correct independent of
/// .
///
diff --git a/src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/NotificationOutboxActor.cs b/src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/NotificationOutboxActor.cs
index ef30fbaf..193cd001 100644
--- a/src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/NotificationOutboxActor.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/NotificationOutboxActor.cs
@@ -1111,7 +1111,7 @@ public class NotificationOutboxActor : ReceiveActor, IWithTimers
notification.NextAttemptAt = null;
notification.LastError = null;
- // Compare-and-swap on Status == Parked (arch-review 2026-08-19 P5-4). The
+ // Compare-and-swap on Status == Parked. The
// read above and this write are two separate round trips; a detached
// PipeTo task can race the dispatcher's own write (or a second
// concurrent operator action) in between, so the in-memory check above
@@ -1203,8 +1203,7 @@ public class NotificationOutboxActor : ReceiveActor, IWithTimers
notification.Status = NotificationStatus.Discarded;
- // Same compare-and-swap on Status == Parked as the retry path (arch-review
- // 2026-08-19 P5-4): a purge between the read and the write leaves nothing
+ // Same compare-and-swap on Status == Parked as the retry path: a purge between the read and the write leaves nothing
// to discard, and a dispatcher/other-operator write landing in that same
// window (e.g. a delivery succeeding) must not be silently clobbered by a
// blind write of this stale in-memory copy. Re-read on a lost CAS to
diff --git a/src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/NotificationOutboxOptionsValidator.cs b/src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/NotificationOutboxOptionsValidator.cs
index 1cb1d9ee..bebeb0b7 100644
--- a/src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/NotificationOutboxOptionsValidator.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/NotificationOutboxOptionsValidator.cs
@@ -3,9 +3,9 @@ using ZB.MOM.WW.Configuration;
namespace ZB.MOM.WW.ScadaBridge.NotificationOutbox;
///
-/// Validates at host startup (arch-review
-/// §1.5 eager-options-validation pattern — this options class had no registered
-/// validator before P5-5). The durations feed the dispatch sweep, stuck-call
+/// Validates at host startup (eager
+/// options validation — this options class previously had no registered
+/// validator). The durations feed the dispatch sweep, stuck-call
/// detection, terminal-row purge, and the delivered-throughput KPI window; a
/// zero/negative cadence would spin an Akka scheduler, and a non-positive
/// would collapse the
diff --git a/src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/ServiceCollectionExtensions.cs b/src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/ServiceCollectionExtensions.cs
index bd4c599d..f396339a 100644
--- a/src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/ServiceCollectionExtensions.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/ServiceCollectionExtensions.cs
@@ -56,7 +56,7 @@ public static class ServiceCollectionExtensions
{
ArgumentNullException.ThrowIfNull(services);
- // Eager startup validation (arch-review §1.5 / P5-5): this options class had no
+ // Eager startup validation (arch-review §1.5): this options class had no
// registered validator before — ValidateOnStart makes a bad
// ScadaBridge:NotificationOutbox section fail fast at boot with a clear,
// key-naming message instead of spinning a scheduler or, for DeliveredKpiWindow,
diff --git a/src/ZB.MOM.WW.ScadaBridge.NotificationService/Ews/EwsSoapEnvelope.cs b/src/ZB.MOM.WW.ScadaBridge.NotificationService/Ews/EwsSoapEnvelope.cs
index e8484279..686be9d5 100644
--- a/src/ZB.MOM.WW.ScadaBridge.NotificationService/Ews/EwsSoapEnvelope.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.NotificationService/Ews/EwsSoapEnvelope.cs
@@ -88,6 +88,7 @@ public static class EwsSoapEnvelope
///
private sealed class Utf8StringWriter : StringWriter
{
+ ///
public override Encoding Encoding => Encoding.UTF8;
}
}
diff --git a/src/ZB.MOM.WW.ScadaBridge.NotificationService/MailKitSmtpClientWrapper.cs b/src/ZB.MOM.WW.ScadaBridge.NotificationService/MailKitSmtpClientWrapper.cs
index 95ce27e5..3d855e9b 100644
--- a/src/ZB.MOM.WW.ScadaBridge.NotificationService/MailKitSmtpClientWrapper.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.NotificationService/MailKitSmtpClientWrapper.cs
@@ -6,7 +6,7 @@ namespace ZB.MOM.WW.ScadaBridge.NotificationService;
///
/// MailKit-based SMTP client wrapper.
-/// Supports OAuth2 Client Credentials (M365) and Basic Auth.
+/// Supports OAuth2 Client Credentials (Microsoft 365) and Basic Auth.
/// BCC delivery, plain text.
///
///
@@ -110,7 +110,7 @@ public class MailKitSmtpClientWrapper : ISmtpClientWrapper, IDisposable
case "oauth2":
// The XOAUTH2 SASL initial response embeds a `user=`
- // field that M365 (and most OAuth2-enabled SMTP relays) require to
+ // field that Microsoft 365 (and most OAuth2-enabled SMTP relays) require to
// match the mailbox identity the token was issued for. An empty user
// gets rejected with `535 5.7.3`. The token (credentials) is
// pre-fetched by OAuth2TokenService; the user identity is the SMTP
diff --git a/src/ZB.MOM.WW.ScadaBridge.ScriptAnalysis/FlowShapePredictor.cs b/src/ZB.MOM.WW.ScadaBridge.ScriptAnalysis/FlowShapePredictor.cs
index 7b5840eb..8fb28308 100644
--- a/src/ZB.MOM.WW.ScadaBridge.ScriptAnalysis/FlowShapePredictor.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.ScriptAnalysis/FlowShapePredictor.cs
@@ -45,7 +45,7 @@ public sealed record PredictedFlow(IReadOnlyList Nodes, IReadOnly
/// script API exposes (Route.To(x).Call(y), Scripts.Call(y),
/// ….Attributes.WriteBatchAndWaitAsync(…), ….Attributes[x] = v, …) by text with
/// whitespace stripped, exactly as 's hardening walker does.
-/// The nine-argument reset-aware WriteBatchAndWaitAsync overload (Gitea #36) additionally
+/// The nine-argument reset-aware WriteBatchAndWaitAsync overload additionally
/// predicts a reset attribute write + echo wait ahead of the batch write + wait pair.
/// String-literal arguments resolve to titles; anything else is flagged
/// . Branching is not modelled: every recognised call
@@ -83,6 +83,7 @@ public static class FlowShapePredictor
/// known expression, and a test resolver may answer for anything).
///
/// Recursion cap; nested script bodies beyond it are not expanded.
+ /// The predicted flow, degraded to a request/response pair when the script cannot be parsed.
public static PredictedFlow PredictInboundMethod(
string methodName,
string methodScript,
@@ -117,6 +118,7 @@ public static class FlowShapePredictor
/// The script name (titles the root node).
/// The script's C# body.
/// Recursion cap.
+ /// The predicted flow of the site script.
public static PredictedFlow PredictSiteScript(string scriptName, string scriptBody, int maxDepth = DefaultMaxDepth)
{
var b = new Builder((_, _) => null, Math.Max(1, maxDepth));
@@ -146,20 +148,32 @@ public static class FlowShapePredictor
private string? _previous;
private string? _pendingNote;
+ /// Initializes the builder.
+ /// Resolves (instanceCode, scriptName) to a script body, or null.
+ /// Recursion cap for nested script expansion.
internal Builder(Func resolve, int maxDepth)
{
_resolve = resolve;
_maxDepth = maxDepth;
}
+ /// Snapshots the accumulated nodes and edges into a .
+ /// The predicted flow built so far.
internal PredictedFlow Build() => new(_nodes.ToArray(), _edges.ToArray());
///
/// Adds a node, chains it to the previous one, and consumes any pending early-return note.
/// An node hangs off the previous node on an
/// edge and does NOT become the new chain head — the next node still links from the previous
- /// non-async node (D4: a cached call or notification returns immediately, so the run continues without it).
+ /// non-async node (a cached call or notification returns immediately, so the run continues without it).
///
+ /// The kind of step the node represents.
+ /// The swimlane the node belongs to.
+ /// The node title.
+ /// True when the title could not be resolved from a literal.
+ /// An optional note for the node.
+ /// True when the node hangs off the chain on an async edge.
+ /// The id of the new node.
internal string AddNode(FlowStepKind kind, FlowLane lane, string title, bool inputDependent, string? note, bool async = false)
{
var id = "n" + (_nodes.Count + 1).ToString(CultureInfo.InvariantCulture);
@@ -180,6 +194,9 @@ public static class FlowShapePredictor
return id;
}
+ /// Appends a note to an existing node.
+ /// The id of the node to annotate.
+ /// The note to append.
internal void Annotate(string id, string note)
{
var i = _nodes.FindIndex(n => n.Id == id);
@@ -189,6 +206,7 @@ public static class FlowShapePredictor
}
/// Drops everything after the first nodes (fault recovery).
+ /// The number of leading nodes to keep.
internal void Reset(int keep)
{
if (_nodes.Count > keep) _nodes.RemoveRange(keep, _nodes.Count - keep);
@@ -202,6 +220,11 @@ public static class FlowShapePredictor
/// Walks one script body in the given lane. Returns false when the body has parse
/// errors (nothing is emitted for it), true otherwise.
///
+ /// The script body to parse and walk.
+ /// The lane the body runs in.
+ /// The target instance code, or null.
+ /// The current recursion depth.
+ /// false when the body has parse errors; otherwise true.
internal bool Walk(string code, FlowLane lane, string? instanceCode, int depth)
{
var tree = CSharpSyntaxTree.ParseText(code, new CSharpParseOptions(kind: SourceCodeKind.Script));
@@ -213,10 +236,15 @@ public static class FlowShapePredictor
return true;
}
+ /// Queues a note to attach to the next node added.
+ /// The note text.
internal void QueueNote(string note)
=> _pendingNote = _pendingNote == null ? note : $"{_pendingNote}; {note}";
/// Expands a routed / nested script call one level, if the resolver can supply its body.
+ /// The target instance code (or its source text).
+ /// The name of the script to expand.
+ /// The current recursion depth.
internal void ExpandScript(string instanceCode, string scriptName, int depth)
{
if (depth >= _maxDepth)
@@ -251,6 +279,11 @@ public static class FlowShapePredictor
private readonly string? _instanceCode;
private readonly int _depth;
+ /// Initializes the walker.
+ /// The builder that receives nodes.
+ /// The lane the walked body runs in.
+ /// The target instance code, or null.
+ /// The current recursion depth.
internal Walker(Builder b, FlowLane lane, string? instanceCode, int depth)
{
_b = b;
@@ -261,12 +294,11 @@ public static class FlowShapePredictor
private static string Strip(string text) => new(text.Where(c => !char.IsWhiteSpace(c)).ToArray());
- ///
- /// An if (…) { … return …; } guard becomes a note on the node that FOLLOWS it.
- /// The condition is visited first (it may itself contain a traced call — the Delmia
- /// site script's if (!await …WriteBatchAndWaitAsync(…)) return …), then the
- /// body, and only then is the note queued for whatever comes next.
- ///
+ // An `if (…) { … return …; }` guard becomes a note on the node that FOLLOWS it.
+ // The condition is visited first (it may itself contain a traced call — the Delmia
+ // site script's `if (!await …WriteBatchAndWaitAsync(…)) return …`), then the
+ // body, and only then is the note queued for whatever comes next.
+ ///
public override void VisitIfStatement(IfStatementSyntax node)
{
Visit(node.Condition);
@@ -280,6 +312,7 @@ public static class FlowShapePredictor
_b.QueueNote($"early return: {Condense(node.Condition.ToString())}");
}
+ ///
public override void VisitAssignmentExpression(AssignmentExpressionSyntax node)
{
// recv.Attributes["X"] = v / Attributes["X"] = v
@@ -294,6 +327,7 @@ public static class FlowShapePredictor
base.VisitAssignmentExpression(node);
}
+ ///
public override void VisitInvocationExpression(InvocationExpressionSyntax node)
{
// Visit inner invocations first? No — source order for a chained call like
@@ -372,7 +406,7 @@ public static class FlowShapePredictor
case "WriteBatchAndWaitAsync":
{
// (batch, triggerAttr, triggerValue, waitAttr, waitValue, timeout
- // [, resetAttr, resetValue, resetTimeout]) — Gitea #36: the reset-aware
+ // [, resetAttr, resetValue, resetTimeout]) — the reset-aware
// overload writes + waits on the reset attribute BEFORE the batch.
var (trigger, triggerDependent) = LiteralOrExpr(args.ElementAtOrDefault(1)?.Expression);
var (wait, waitDependent) = LiteralOrExpr(args.ElementAtOrDefault(3)?.Expression);
diff --git a/src/ZB.MOM.WW.ScadaBridge.ScriptAnalysis/ScriptCompileSurface.cs b/src/ZB.MOM.WW.ScadaBridge.ScriptAnalysis/ScriptCompileSurface.cs
index f547d529..142dc305 100644
--- a/src/ZB.MOM.WW.ScadaBridge.ScriptAnalysis/ScriptCompileSurface.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.ScriptAnalysis/ScriptCompileSurface.cs
@@ -373,7 +373,7 @@ public sealed class ScriptCompileSurface
/// true if the response value was observed before the timeout elapsed; otherwise false.
public Task WriteBatchAndWaitAsync(IReadOnlyDictionary values, string flagKey, object? flagValue, string responseKey, object? responseValue, TimeSpan timeout) => throw new NotSupportedException(CompileOnly);
- /// Mirrors the reset-aware AttributeAccessor.WriteBatchAndWaitAsync overload (Gitea #36).
+ /// Mirrors the reset-aware AttributeAccessor.WriteBatchAndWaitAsync overload.
/// The set of attribute values to write as a batch.
/// The name of the flag attribute signaling the write.
/// The value written to the flag attribute.
diff --git a/src/ZB.MOM.WW.ScadaBridge.Security/CookieSessionValidator.cs b/src/ZB.MOM.WW.ScadaBridge.Security/CookieSessionValidator.cs
index 3e6be60b..98f21a5a 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Security/CookieSessionValidator.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Security/CookieSessionValidator.cs
@@ -86,6 +86,7 @@ public sealed class CookieSessionValidator
/// Security options carrying the idle and role-refresh thresholds.
/// Clock source; injected so tests can advance time deterministically.
/// Logger instance.
+ /// Optional tracker that records session activity for idle-timeout accounting.
public CookieSessionValidator(
IGroupRoleMapper roleMapper,
IOptions options,
@@ -106,6 +107,9 @@ public sealed class CookieSessionValidator
///
/// The current cookie principal under validation.
/// Cancellation token (the request-aborted token in the pipeline).
+ /// True to record this validation as session activity.
+ /// True to refresh role/scope claims regardless of the refresh interval.
+ /// True to propagate role-lookup errors instead of preserving current grants.
/// The action the caller must take and any replacement principal.
public async Task ValidateAsync(ClaimsPrincipal? principal, CancellationToken ct = default, bool recordActivity = true, bool forceRefresh = false, bool failClosed = false)
{
diff --git a/src/ZB.MOM.WW.ScadaBridge.Security/ServiceCollectionExtensions.cs b/src/ZB.MOM.WW.ScadaBridge.Security/ServiceCollectionExtensions.cs
index 8c7c5b36..d2a9f4c8 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Security/ServiceCollectionExtensions.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Security/ServiceCollectionExtensions.cs
@@ -225,6 +225,8 @@ public static class ServiceCollectionExtensions
}
/// Requests that cannot by themselves establish user activity.
+ /// The HTTP context of the request.
+ /// true when the request is a passive session request.
internal static bool IsPassiveSessionRequest(HttpContext context)
=> context.Request.Path.StartsWithSegments("/auth/ping")
|| context.Request.Path.StartsWithSegments("/auth/activity")
diff --git a/src/ZB.MOM.WW.ScadaBridge.Security/SessionActivityTracker.cs b/src/ZB.MOM.WW.ScadaBridge.Security/SessionActivityTracker.cs
index 36017a8f..5f9ebdbf 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Security/SessionActivityTracker.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Security/SessionActivityTracker.cs
@@ -16,6 +16,8 @@ public sealed class SessionActivityTracker
private readonly Dictionary _activity = new(StringComparer.Ordinal);
/// Returns the newer of the protected cookie anchor and this node's observed activity.
+ /// The principal whose session activity is queried.
+ /// The most recent activity time, or when the principal carries no activity claim.
public DateTimeOffset? LastActivity(ClaimsPrincipal principal)
{
if (!DateTimeOffset.TryParse(principal.FindFirst(JwtTokenService.LastActivityClaimType)?.Value, out var stamp))
@@ -26,6 +28,8 @@ public sealed class SessionActivityTracker
}
/// Records a validated interaction without changing another login's activity.
+ /// The principal whose session is recorded.
+ /// The time of the interaction.
public void Record(ClaimsPrincipal principal, DateTimeOffset now)
{
var id = principal.FindFirst(SessionIdClaim)?.Value;
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteCallAudit/Kpi/SiteCallAuditKpiSampleSource.cs b/src/ZB.MOM.WW.ScadaBridge.SiteCallAudit/Kpi/SiteCallAuditKpiSampleSource.cs
index a7b28087..41a4c97b 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteCallAudit/Kpi/SiteCallAuditKpiSampleSource.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteCallAudit/Kpi/SiteCallAuditKpiSampleSource.cs
@@ -36,7 +36,7 @@ namespace ZB.MOM.WW.ScadaBridge.SiteCallAudit.Kpi;
/// scope-per-message repository access.
///
///
-/// failedLastInterval / deliveredLastInterval rescale (P5-5, arch-review Low).
+/// failedLastInterval / deliveredLastInterval rescale (arch-review Low).
/// classifies both as
/// : the central hourly rollup fold sums every
/// sample recorded in the hour, which is only correct if each sample's value is a
@@ -82,7 +82,7 @@ public sealed class SiteCallAuditKpiSampleSource : IKpiSampleSource
///
/// KPI History options carrying — the
/// recorder's sampling cadence the failedLastInterval/deliveredLastInterval samples are
- /// rescaled onto (P5-5), so the hourly rollup fold's per-sample sum stays correct
+ /// rescaled onto, so the hourly rollup fold's per-sample sum stays correct
/// independent of .
///
public SiteCallAuditKpiSampleSource(
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteCallAudit/SiteCallAuditActor.cs b/src/ZB.MOM.WW.ScadaBridge.SiteCallAudit/SiteCallAuditActor.cs
index 46d88b91..ab9759e5 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteCallAudit/SiteCallAuditActor.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteCallAudit/SiteCallAuditActor.cs
@@ -266,7 +266,7 @@ public class SiteCallAuditActor : ReceiveActor
// their own per-site / per-tick try/catch (mirroring the ingest path);
// the timers are only started when their collaborators are available.
//
- // OFF-MAILBOX (WP2.2). Both passes run as PipeTo-completed background
+ // OFF-MAILBOX. Both passes run as PipeTo-completed background
// tasks behind a single-flight guard rather than as ReceiveAsync bodies.
// A ReceiveAsync handler occupies the actor for its whole duration, and a
// reconciliation pass is unbounded work — every site, up to
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteEventLogging/EventLogPurgeService.cs b/src/ZB.MOM.WW.ScadaBridge.SiteEventLogging/EventLogPurgeService.cs
index c81c8f49..b9b022fc 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteEventLogging/EventLogPurgeService.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteEventLogging/EventLogPurgeService.cs
@@ -9,7 +9,7 @@ namespace ZB.MOM.WW.ScadaBridge.SiteEventLogging;
///
/// Predicate the consults at the top of every purge
-/// tick. As of arch-review 2026-08-19 P4-4 it no longer GATES the purge — DataConnectionActor
+/// tick. It no longer GATES the purge — DataConnectionActor
/// writes site_events on both site nodes (register rows 89/90/97), so a standby that
/// skipped its own purge would grow its file unboundedly on an unreplicated site. Every node
/// now purges its own file every tick; this predicate is consulted only to annotate the tick's
@@ -116,7 +116,7 @@ public class EventLogPurgeService : BackgroundService
/// Logger instance.
///
/// Optional active-node check, consulted only to annotate the purge tick's log line
- /// (arch-review 2026-08-19 P4-4 removed the early-exit this used to drive — every node
+ /// (there is no early-exit — every node
/// purges its own site_events file every tick, since DataConnectionActor writes
/// on both site nodes and a standby that skipped its own purge would grow unbounded on
/// an unreplicated site). When null every tick logs as active.
@@ -144,18 +144,16 @@ public class EventLogPurgeService : BackgroundService
_isReplicationConfigured = isReplicationConfigured ?? (static () => false);
}
+ // Runs the periodic purge loop until the host stops. Deliberately a delay loop rather
+ // than a PeriodicTimer: the wait between ticks is no longer a constant. A tick that
+ // stopped at SiteEventLogOptions.MaxPurgedRowsPerTick still has expired rows to delete,
+ // and waiting a full SiteEventLogOptions.PurgeInterval (a day) before resuming would make
+ // a paced purge take longer to converge than the retention window it enforces.
+ // Consequence worth knowing: the cadence is anchored to the END of the previous purge
+ // rather than to process start, so a long purge shifts the next tick by its own
+ // duration. At the daily interval and a measured ~17 s for a full 250 000-row tick that
+ // drift is immaterial.
///
- ///
- /// Deliberately a delay loop rather than a : the wait between
- /// ticks is no longer a constant. A tick that stopped at
- /// still has expired rows to delete,
- /// and waiting a full (a day) before
- /// resuming would make a paced purge take longer to converge than the retention window it
- /// enforces. Consequence worth knowing: the cadence is now anchored to the END of the
- /// previous purge rather than to process start, so a long purge shifts the next tick by its
- /// own duration. At the daily interval and a measured ~17 s for a full 250 000-row tick that
- /// drift is immaterial.
- ///
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation(
@@ -164,7 +162,7 @@ public class EventLogPurgeService : BackgroundService
_options.RetentionDays, _options.MaxStorageMb, _options.PurgeInterval,
_options.MaxPurgedRowsPerTick > 0 ? _options.MaxPurgedRowsPerTick.ToString() : "unlimited");
- // arch-review 2026-08-19 P4-5: yield BEFORE the initial (potentially ~17s
+ // Yield BEFORE the initial (potentially ~17s
// ceiling-limited, ~285s unpaced worst case) RunPurge() rather than calling it
// as the first synchronous statement. .NET 8+'s BackgroundService already invokes
// ExecuteAsync via Task.Run, so a synchronous prelude here does not actually block
@@ -205,7 +203,7 @@ public class EventLogPurgeService : BackgroundService
{
try
{
- // arch-review 2026-08-19 P4-4: purge no longer gates on the active-node check.
+ // Purge no longer gates on the active-node check.
// DataConnectionActor writes site_events on BOTH site nodes (register rows
// 89/90/97), so the old "standby receives no writes" premise this gate was
// built on is stale — a standby that never purges its own file grows
@@ -226,8 +224,8 @@ public class EventLogPurgeService : BackgroundService
}
catch (Exception checkEx)
{
- // arch-review 2026-08-19 P4-5: fail CLOSED, not "active". This predicate no
- // longer gates any work (P4-4 — both nodes purge their own file every tick
+ // Fail CLOSED, not "active". This predicate no
+ // longer gates any work (both nodes purge their own file every tick
// regardless), so a wrong value here cannot skip a purge; but silently
// reporting "active" for a check that just threw is dishonest, and an
// operator correlating this line against the real active-node state would be
@@ -536,7 +534,7 @@ public class EventLogPurgeService : BackgroundService
var rows = cmd.ExecuteNonQuery();
// Reclaim free pages so page_count/freelist measurement reflects the delete.
- // arch-review 2026-08-19 P4-6: this is effective ONLY on a consolidated
+ // This is effective ONLY on a consolidated
// LocalDb file that was FRESH when SiteLocalDbSetup.EnsureAutoVacuumOnFreshFile
// ran (Host/SiteLocalDbSetup.cs) — auto_vacuum can only be enabled before any
// table exists, and SqliteLocalDb's constructor creates LocalDb's own
@@ -676,7 +674,7 @@ public class EventLogPurgeService : BackgroundService
///
/// Clamped by the file. The whole-file logical size is a true upper bound on any one
/// table's pages, so the model is capped by it. That can only ever correct an
- /// over-estimate downward; it cannot reintroduce the row-81 defect, because on a replicated
+ /// over-estimate downward; it cannot reintroduce the whole-file measurement defect, because on a replicated
/// node the file is larger than site_events, not smaller, and so never binds.
///
///
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteEventLogging/ServiceCollectionExtensions.cs b/src/ZB.MOM.WW.ScadaBridge.SiteEventLogging/ServiceCollectionExtensions.cs
index 565e6a91..d9d92355 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteEventLogging/ServiceCollectionExtensions.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteEventLogging/ServiceCollectionExtensions.cs
@@ -24,8 +24,8 @@ public static class ServiceCollectionExtensions
services.AddSingleton();
// The purge service registers on every host node and purges its own
- // site_events file on every tick, on every node (arch-review 2026-08-19 P4-4 —
- // the standby writes site_events too, so it can no longer skip its own purge).
+ // site_events file on every tick, on every node (the
+ // standby writes site_events too, so it can no longer skip its own purge).
// It still consults an optional SiteEventLogActiveNodeCheck, but only to annotate
// the tick's log line, not to gate it. The Host registers the real active-node
// check on site nodes; tests and non-clustered hosts leave it unregistered, and
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteEventLogging/SiteEventLogOptions.cs b/src/ZB.MOM.WW.ScadaBridge.SiteEventLogging/SiteEventLogOptions.cs
index 1feeee3e..d34e8797 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteEventLogging/SiteEventLogOptions.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteEventLogging/SiteEventLogOptions.cs
@@ -34,10 +34,10 @@ public class SiteEventLogOptions
///
public int MaxStorageMb { get; set; } = 1024;
///
- /// MIGRATION-ONLY as of LocalDb Phase 1. no longer reads
+ /// MIGRATION-ONLY now that no longer reads
/// this: site_events lives in the consolidated site database at
/// LocalDb:Path. The only remaining consumer is
- /// SiteLocalDbLegacyMigrator, which uses it to locate a pre-Phase-1
+ /// SiteLocalDbLegacyMigrator, which uses it to locate a pre-consolidation
/// site_events.db and copy it in once. Safe to delete from a config once that
/// node's migration has run.
///
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteEventLogging/SiteEventLogSchema.cs b/src/ZB.MOM.WW.ScadaBridge.SiteEventLogging/SiteEventLogSchema.cs
index 6c778946..0af60396 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteEventLogging/SiteEventLogSchema.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteEventLogging/SiteEventLogSchema.cs
@@ -36,7 +36,7 @@ public static class SiteEventLogSchema
{
ArgumentNullException.ThrowIfNull(connection);
- // arch-review 2026-08-19 P4-6: this method used to also set
+ // This method used to also set
// "PRAGMA auto_vacuum = INCREMENTAL" here, on the theory that setting it right
// before CREATE TABLE would make it take effect on a fresh database. That was
// wrong in every real call path: SQLite only honors an auto_vacuum change on a
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/AlarmActor.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/AlarmActor.cs
index b3fe3f39..00399854 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/AlarmActor.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/AlarmActor.cs
@@ -42,34 +42,34 @@ public class AlarmActor : ReceiveActor
private readonly IServiceProvider? _serviceProvider;
///
- /// Script-execution scheduler seam (#18): the process-wide
+ /// Script-execution scheduler seam: the process-wide
/// when null, or an injected instance so this
/// alarm's launched on-trigger scripts run on a caller-owned pool. Resolved lazily at
/// each use so the null (host) path is unchanged.
///
- /// WP3.1: trigger-expression evaluation no longer uses this scheduler — see
- /// . That split is the whole point of finding #4: an alarm whose
+ /// Trigger-expression evaluation no longer uses this scheduler — see
+ /// . That split is the whole point: an alarm whose
/// Expression trigger should raise in milliseconds must not queue behind blocking
/// script bodies.
///
private readonly ScriptExecutionScheduler? _scheduler;
///
- /// WP3.1 (finding #4): the concurrency gate for trigger-expression evaluation, or null
+ /// The concurrency gate for trigger-expression evaluation, or null
/// for the process-wide . Evaluations run as plain
/// async work on the shared .NET thread pool behind this gate.
///
private readonly TriggerEvalGate? _evalGate;
///
- /// WP3.1: on-trigger runs launched but not yet completed. Incremented at launch,
+ /// On-trigger runs launched but not yet completed. Incremented at launch,
/// decremented on , which every terminal path
/// emits — including the launch-path catch. Touched only on the actor thread.
///
private int _runsInFlight;
///
- /// WP3.1: when the last shed Warning site event was emitted for this alarm. Sheds are
+ /// When the last shed Warning site event was emitted for this alarm. Sheds are
/// always counted; the event is rate-limited to one per alarm per minute.
///
private DateTimeOffset _lastShedEventUtc = DateTimeOffset.MinValue;
@@ -130,8 +130,7 @@ public class AlarmActor : ReceiveActor
private bool _evalPending;
///
- /// Audit Log #23 (ParentExecutionId tag-cascade): the
- /// of the most recent
+ /// The of the most recent
/// change folded into . Expression triggers
/// evaluate a whole snapshot off the dispatcher, so there is no single
/// "triggering change" in scope when the boolean result comes back — this
@@ -150,8 +149,7 @@ public class AlarmActor : ReceiveActor
internal IReadOnlyDictionary? SeedAttributesReference { get; }
///
- /// Audit Log #23 (ParentExecutionId tag-cascade): the
- /// parentExecutionId handed to the most recently spawned
+ /// The parentExecutionId handed to the most recently spawned
/// on-trigger run — i.e. the execution whose attribute
/// write fired this alarm, or null when the firing change came from
/// the Data Connection Layer (external data has no spawning execution).
@@ -185,8 +183,8 @@ public class AlarmActor : ReceiveActor
/// The on-trigger script's per-script
/// execution timeout in seconds (from its ),
/// or null/non-positive to use the global default.
- /// Optional script-execution scheduler override (#18); null uses the process-wide shared scheduler.
- /// Optional trigger-expression concurrency gate override (WP3.1); null uses the process-wide shared gate.
+ /// Optional script-execution scheduler override; null uses the process-wide shared scheduler.
+ /// Optional trigger-expression concurrency gate override; null uses the process-wide shared gate.
public AlarmActor(
string alarmName,
string instanceName,
@@ -202,9 +200,9 @@ public class AlarmActor : ReceiveActor
IServiceProvider? serviceProvider = null,
// Per-script timeout for the on-trigger script (null = global).
int? onTriggerExecutionTimeoutSeconds = null,
- // Script-execution scheduler seam (#18); null uses the process-wide shared scheduler.
+ // Script-execution scheduler seam; null uses the process-wide shared scheduler.
ScriptExecutionScheduler? scheduler = null,
- // WP3.1 trigger-eval gate seam; null uses the process-wide shared gate.
+ // Trigger-eval gate seam; null uses the process-wide shared gate.
TriggerEvalGate? evalGate = null)
{
_alarmName = alarmName;
@@ -248,7 +246,7 @@ public class AlarmActor : ReceiveActor
// Handle attribute value changes
Receive(HandleAttributeValueChanged);
- // Handle alarm execution completion (also releases the WP3.1 in-flight slot)
+ // Handle alarm execution completion (also releases the in-flight slot)
Receive(HandleAlarmExecutionCompleted);
// Handle the off-dispatcher trigger-expression evaluation result (P1).
@@ -268,7 +266,7 @@ public class AlarmActor : ReceiveActor
_alarmName, _instanceName, _triggerType);
}
- // WP3.1: the Stop-on-failure SupervisorStrategy override is gone with the per-run
+ // The Stop-on-failure SupervisorStrategy override is gone with the per-run
// AlarmExecutionActor child it supervised. This actor has no children left; a
// launch-path throw is caught in SpawnAlarmExecution so it can never escalate to
// InstanceActor, which continues to supervise this actor with Resume exactly as before.
@@ -613,7 +611,7 @@ public class AlarmActor : ReceiveActor
// mis-attribute the raise this evaluation produces.
var sourceExecutionId = _latestSourceExecutionId;
var gate = _evalGate ?? TriggerEvalGate.Shared(_options);
- // WP3.1 (finding #4): the deadline clock starts AT ENQUEUE, on the actor thread —
+ // The deadline clock starts AT ENQUEUE, on the actor thread —
// gate-wait time burns the same budget, so a saturated gate yields a timely false
// instead of an unbounded stall. And the work runs on the shared .NET thread pool,
// NOT the blocking script pool, so it can never queue behind a blocked script body.
@@ -747,7 +745,7 @@ public class AlarmActor : ReceiveActor
}
///
- /// WP3.1: releases the in-flight slot for a completed on-trigger run. Every terminal
+ /// Releases the in-flight slot for a completed on-trigger run. Every terminal
/// path emits exactly one (success / timeout /
/// failure / launch failure), so the counter tracks reality.
///
@@ -760,7 +758,7 @@ public class AlarmActor : ReceiveActor
}
///
- /// WP3.1: on-trigger runs launched but not yet completed. Exposed for regression
+ /// On-trigger runs launched but not yet completed. Exposed for regression
/// coverage of the shed cap.
///
internal int RunsInFlight => _runsInFlight;
@@ -770,7 +768,7 @@ public class AlarmActor : ReceiveActor
/// Passes the firing alarm's level/priority/message so the script can
/// branch on severity via the Alarm global.
///
- /// WP3.1: launched directly via rather than
+ /// Launched directly via rather than
/// through a short-lived AlarmExecutionActor child, and bounded by
/// — a raise arriving while
/// the cap is reached is shed (counted, rate-limited site event) rather than piling
@@ -828,7 +826,7 @@ public class AlarmActor : ReceiveActor
// Context.Parent.Tell from the execution actor.
Self,
_logger,
- // arch-review 2026-08-19 P1-3: same health collector + service provider as
+ // Same health collector + service provider as
// the instance-script path, so the alarm on-trigger run gets the same
// stuck-script watchdog, script-error counting, and run-summary threading.
_healthCollector,
@@ -838,13 +836,13 @@ public class AlarmActor : ReceiveActor
_onTriggerExecutionTimeoutSeconds,
// The firing execution's id — null for DCL-originated changes.
parentExecutionId,
- // Scheduler seam (#18): share this alarm's scheduler override with the
+ // Scheduler seam: share this alarm's scheduler override with the
// launched on-trigger script body (null = process-wide shared).
_scheduler, Context.System.ActorSelection("/user/site-communication"));
}
catch (Exception ex)
{
- // WP3.1 supervision parity: mirrors the removed OneForOneStrategy's warn-and-stop
+ // Supervision parity: mirrors the removed OneForOneStrategy's warn-and-stop
// for a per-run child that failed to construct. The alarm continues.
_logger.LogWarning(ex,
"Alarm on-trigger execution launch for {Alarm} on {Instance} failed, stopping",
@@ -854,7 +852,7 @@ public class AlarmActor : ReceiveActor
}
///
- /// WP3.1 shed policy for alarm on-trigger runs: refuses the newest run when
+ /// Shed policy for alarm on-trigger runs: refuses the newest run when
/// are already in flight.
/// There is no Ask caller on this path (an on-trigger run is never awaited), so the
/// shed surfaces as a counter plus a rate-limited Warning site event.
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/DeploymentManagerActor.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/DeploymentManagerActor.cs
index d3cf67b6..796f6305 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/DeploymentManagerActor.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/DeploymentManagerActor.cs
@@ -43,7 +43,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
private readonly DeployCompileValidator _deployCompileValidator;
private readonly SharedScriptLibrary _sharedScriptLibrary;
///
- /// WP2.6a: the shared cache backing SiteExternalSystemRepository reads.
+ /// The shared cache backing SiteExternalSystemRepository reads.
/// Invalidated wholesale after applies
/// external-system changes so a script's next call re-reads fresh definitions/method
/// lists instead of serving a stale cached snapshot. Null in tests that do not wire
@@ -51,7 +51,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
///
private readonly ExternalSystemDefinitionCache? _externalSystemCache;
///
- /// Task 10 (site-secrets artifact deploy): per-node mirror reconciled synchronously by
+ /// Per-node mirror reconciled synchronously by
/// right after the shipped secrets are written to
/// deployed_secrets, so the reply's SecretsApplied reflects this node's own
/// apply/verify outcome rather than the next periodic pass. Optional/null on nodes/tests
@@ -97,7 +97,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
/// test can hold the read open mid-flight (row 101's ensure-started race tests).
///
///
- /// Register P1-4: the row-67 ensure-started heal used to read through
+ /// The ensure-started heal used to read through
/// — the same full-table-including-ConfigJson load the boot
/// sequence uses — just to FirstOrDefault one instance. A volume-restore reconcile
/// that raises hundreds of s in a row turned that
@@ -202,7 +202,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
private readonly HashSet _initFailedPendingRowRemoval = new();
///
- /// WP3.1 warm-then-gate: instances whose deploy compile is being warmed off the actor
+ /// Warm-then-gate: instances whose deploy compile is being warmed off the actor
/// thread, keyed by instance name. Presence is the per-instance in-flight guard that keeps
/// same-instance command ordering intact while OTHER instances' commands flow freely.
/// Entries are added in and removed in
@@ -212,7 +212,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
private readonly Dictionary _deployWarms = new();
///
- /// WP3.1: script-execution scheduler seam (#18) — the pool this actor grows as instances
+ /// Script-execution scheduler seam — the pool this actor grows as instances
/// deploy. Null selects the process-wide shared scheduler; tests inject their own so
/// sizing assertions do not perturb (or depend on) the process-wide singleton.
///
@@ -236,7 +236,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
///
/// The stored revision hash the instance reconcile sweep has already ACTED on for an
/// instance, keyed by instance name — one repair attempt per (instance, revision)
- /// pair, for both the row-78 start and the row-72 restart.
+ /// pair, for both the reconcile-sweep start and the snapshot-driven restart.
///
/// This is what keeps a periodic self-heal from becoming a restart loop. An instance whose
/// Instance Actor dies during init leaves its config row in place, so without this the
@@ -281,7 +281,6 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
/// Site runtime options (startup batch size, stagger interval, etc.).
/// Logger instance.
/// Optional Data Connection Layer manager actor.
- /// Optional standby replication actor.
/// Optional site health collector.
/// Optional service provider for resolving per-instance services.
/// Optional logger factory for creating Instance Actor loggers.
@@ -295,13 +294,13 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
/// Optional override for loading all deployed configurations
/// at startup; defaults to reading from . Primarily for tests.
///
- /// WP2.6a: the shared backing
+ /// The shared backing
/// SiteExternalSystemRepository reads, invalidated after this actor applies
/// external-system changes. Optional/null in tests that do not exercise external-system
/// caching.
///
///
- /// WP3.1: optional script-execution scheduler override (#18). This actor grows the pool
+ /// Optional script-execution scheduler override. This actor grows the pool
/// towards on every instance-
/// count change; null uses the process-wide shared scheduler.
///
@@ -312,12 +311,12 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
/// drive a read failure.
///
///
- /// Register P1-4: optional override for the single-row deployed-config read the row-67
+ /// Optional override for the single-row deployed-config read the
/// ensure-started heal uses; defaults to reading from . Primarily
- /// for tests that need to hold the read open mid-flight to drive the row-101 race.
+ /// for tests that need to hold the read open mid-flight to drive the freshness race.
///
///
- /// Task 10 (site-secrets artifact deploy): this node's ,
+ /// This node's ,
/// reconciled synchronously by right after a shipped
/// command.Secrets set is written to deployed_secrets. Optional/null on
/// nodes/tests that never register secret deployment.
@@ -378,10 +377,10 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
Receive(cmd => HandleEnable(cmd, Sender));
Receive(cmd => HandleDelete(cmd, Sender));
- // WP3.1 warm-then-gate: the off-thread compile warm for a deploy has finished, so
+ // Warm-then-gate: the off-thread compile warm for a deploy has finished, so
// the (now all-cache-hit) synchronous compile gate can run on the actor thread.
Receive(HandleDeployCompileWarmed);
- // WP3.1: a staggered startup batch's compiles have been pre-warmed off-thread;
+ // A staggered startup batch's compiles have been pre-warmed off-thread;
// create the batch's Instance Actors now that their PreStart compiles are hits.
Receive(msg => CreateInstanceActorBatch(msg.Batch));
@@ -732,7 +731,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
// Rows 89 + 90: seed the DCL arbitration overlay BEFORE any Instance Actor is created,
// so the boot batch's EnsureDclConnections already resolves to the definition-owned
- // config. This is the failover half of the row-89 fix: a singleton incarnation starting
+ // config. This is the failover half of the fix: a singleton incarnation starting
// on the peer node rebuilds the whole DCL from flattened snapshots, and without the
// overlay in place first it would rebuild them STALE and only correct itself a sweep
// tick later — a needless create-then-diverge on every failover.
@@ -764,7 +763,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
if (msg.EnabledConfigs.Count == 0)
{
// Nothing to create, so boot-time creation is trivially complete and the instance
- // reconcile sweep may run. This is exactly the row-78 live case: the node booted
+ // reconcile sweep may run. This is exactly the live case: the node booted
// with an empty deployed_configurations table and CDC replication seeds it moments
// later, with no boot load left to notice.
_startupInstanceCreationComplete = true;
@@ -777,7 +776,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
}
///
- /// WP3.1: pre-warms one staggered startup batch's script and trigger-expression compiles
+ /// Pre-warms one staggered startup batch's script and trigger-expression compiles
/// off the actor thread, then pipes so
/// can create the batch's Instance Actors with
/// every PreStart compile already a cache hit.
@@ -814,7 +813,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
catch
{
// Best-effort warm — a throw here just means that instance's PreStart
- // compiles the hard way, exactly as it did before WP3.1.
+ // compiles the hard way, exactly as it did before the warm-then-gate change.
}
}
@@ -896,7 +895,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
}
- // WP3.1 warm-then-gate. The S3 gate below must stay synchronous on the actor thread
+ // Warm-then-gate. The S3 gate below must stay synchronous on the actor thread
// (mailbox FIFO is what makes redeploy-supersede and delete-during-redeploy correct),
// but the Roslyn compile it performs used to hold the whole singleton — every OTHER
// instance's commands stalled behind one instance's compile. So the compile is WARMED
@@ -941,7 +940,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
/// back so the authoritative gate can run on the actor
/// thread against a warm cache. The warm is best-effort: its verdict is discarded and any
/// exception swallowed, because re-derives the
- /// verdict (and reproduces any failure) exactly as it did before WP3.1.
+ /// verdict (and reproduces any failure) exactly as it did before the warm-then-gate change.
///
private void StartDeployCompileWarm(DeployInstanceCommand command)
{
@@ -1044,7 +1043,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
// synchronously on the actor thread as a pure prefix step of the deploy
// application, so the existing redeploy-supersede / delete-during-redeploy
// ordering (which depends on strict mailbox FIFO) is preserved exactly.
- // WP3.1 removed the COST rather than the ordering: the same compile has
+ // The warm-then-gate change removed the COST rather than the ordering: the same compile has
// already been warmed off-thread (StartDeployCompileWarm), so this call is
// pure cache hits and no longer holds the singleton — while the per-instance
// warm guard keeps this instance's own command ordering intact. Redeploys and
@@ -1345,7 +1344,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
// Static overrides and mirrored native alarm state are reset on redeployment per
// design decision — the new config may bind different sources, and the source snapshot
// re-seeds the mirror on (re)subscribe. Dispatched HERE, BEFORE the successor exists,
- // and handed to it as a gate (arch-review 2026-08-19 P1-2). It used to run inside the
+ // and handed to it as a gate. It used to run inside the
// persistence task below, i.e. after the actor was already created AND queued behind a
// full config-JSON write, so the successor's PreStart override load routinely won the
// race and applied the PREDECESSOR's overrides — including keys absent from the new
@@ -1557,7 +1556,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
{
var instanceName = command.InstanceUniqueName;
- // WP3.1 warm-then-gate: a deploy for this instance is mid-compile-warm; queue this
+ // Warm-then-gate: a deploy for this instance is mid-compile-warm; queue this
// command so same-instance ordering is preserved (see TryBufferDuringDeployWarm).
if (TryBufferDuringDeployWarm(instanceName, command, replyTo)) return;
if (DeferLifecycleStop(instanceName, command.CommandId, false, replyTo)) return;
@@ -1567,7 +1566,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
var sender = replyTo;
// Row 101 (initiation half): the actor has just stopped the Instance Actor while the
- // durable row still reads enabled, which is exactly the shape the row-78 sweep repairs.
+ // durable row still reads enabled, which is exactly the shape the reconcile sweep repairs.
// Any store read in flight would therefore "prove" a divergence that does not exist and
// restart the instance the operator just disabled.
BumpStoreWriteGeneration("disable dispatch");
@@ -1617,7 +1616,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
}
- // WP3.1 warm-then-gate: see TryBufferDuringDeployWarm.
+ // Warm-then-gate: see TryBufferDuringDeployWarm.
if (TryBufferDuringDeployWarm(instanceName, command, replyTo)) return;
var sender = replyTo;
@@ -1628,7 +1627,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
Task.Run(async () =>
{
await _storage.SetInstanceEnabledAsync(instanceName, true);
- // Row P1-4: a single-row read — this used to materialize every deployed config
+ // A single-row read — this used to materialize every deployed config
// (including every ConfigJson blob) just to FirstOrDefault this one instance.
var config = await _storage.GetDeployedConfigAsync(instanceName);
return new EnableResult(command, config, null, sender);
@@ -1722,7 +1721,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
var generationAtRead = _storeWriteGeneration;
Task.Run(async () =>
{
- // Register P1-4: read a single row through _deployedConfigLoader (defaults to
+ // Read a single row through _deployedConfigLoader (defaults to
// _storage.GetDeployedConfigAsync — a WHERE instance_unique_name = @name read, not
// the full-table-including-ConfigJson load the boot sequence uses), which still lets
// a test hold the read open and land the result behind a committed disable/delete.
@@ -1821,7 +1820,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
///
/// The one place an advisory self-heal turns a stored, enabled config into a running
- /// Instance Actor — shared by the row-67 ensure-started handler and the row-78 sweep so
+ /// Instance Actor — shared by the ensure-started handler and the reconcile sweep so
/// both produce identical bookkeeping and identical operator-facing surfacing. Callers
/// are responsible for the guards; this method assumes they have passed.
///
@@ -1915,7 +1914,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
// Row 101, sweep half: this node mutated one of the two tables while the read was in
// flight, so the snapshot predates state the actor has already advanced past. Discard it
- // WHOLE — no overlay apply, no row-78 start, no row-72 restart. Every repair this sweep
+ // WHOLE — no overlay apply, no sweep start, no snapshot restart. Every repair this sweep
// makes is derived from "the store says X, the runtime says Y", and a snapshot older
// than the runtime inverts that comparison: it would restart an instance BACKWARD onto
// the revision a deploy just replaced, or resurrect one a disable/delete just stopped.
@@ -2099,7 +2098,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
{
var instanceName = command.InstanceUniqueName;
- // WP3.1 warm-then-gate: see TryBufferDuringDeployWarm.
+ // Warm-then-gate: see TryBufferDuringDeployWarm.
if (TryBufferDuringDeployWarm(instanceName, command, replyTo)) return;
if (DeferLifecycleStop(instanceName, command.CommandId, true, replyTo)) return;
@@ -2191,7 +2190,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
var sender = Sender;
var instanceName = request.InstanceUniqueName;
- // Row P1-4: a single-row, ConfigJson-free projection — this used to materialize every
+ // A single-row, ConfigJson-free projection — this used to materialize every
// deployed config (including every ConfigJson blob) just to FirstOrDefault this one
// instance's DeploymentId/RevisionHash. Neither is ever read here.
_storage.GetDeployedConfigMetadataAsync(instanceName).ContinueWith(t =>
@@ -2493,13 +2492,13 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
/// DEFINITIONS half: absences "prove" just-applied names disappeared, tombstoning and
/// STOPPING live connections that nothing re-creates until an instance redeploy; rows push
/// superseded configs onto live connections and flip-flop with the next fresh read.
- /// CONFIGS half, row-72 leg: a snapshot carrying the revision a deploy has just
+ /// CONFIGS half, deploy-revision leg: a snapshot carrying the revision a deploy has just
/// replaced makes "repair" the instance BACKWARD —
/// it redeploys the snapshot's own (old) config JSON through the redeploy path.
/// does not cover this: it only holds while
/// the deploy is in flight, and the stale snapshot can land after the pipeline released
/// ownership.
- /// CONFIGS half, row-78 leg: a snapshot read before an undeploy/delete or a disable
+ /// CONFIGS half, stale-snapshot leg: a snapshot read before an undeploy/delete or a disable
/// committed still shows an enabled row while the actor is already stopped, so
/// RESURRECTS a just-stopped instance.
///
@@ -2543,7 +2542,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
}
///
- /// The row-101 freshness test both snapshot consumers run BEFORE touching anything the
+ /// The freshness test both snapshot consumers run BEFORE touching anything the
/// snapshot carries. means this actor mutated one of the two tables
/// while the read was in flight, so the snapshot predates state the actor has already
/// advanced past and must be discarded whole (see for
@@ -3231,7 +3230,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
Task.Run(async () =>
{
- // M2 (final review): the DCL push for this deploy's data connections is captured
+ // The DCL push for this deploy's data connections is captured
// here and sent only AFTER the shipped secrets are written and mirrored (in the
// finally below), so a connection whose config references a secret this same deploy
// ships resolves it on its FIRST connect instead of failing "not found" and waiting
@@ -3279,7 +3278,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
await _storage.DeleteExternalSystemsExceptAsync(
command.ExternalSystems.Select(es => es.Name).ToList());
- // WP2.6a: drop the shared read cache so the next script call sees
+ // Drop the shared read cache so the next script call sees
// the freshly-applied definitions/method lists instead of a stale
// snapshot loaded before this deploy.
_externalSystemCache?.InvalidateAll();
@@ -3335,7 +3334,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
// artifact bundle. Reconciled removals ride the same
// message so the live DCL connection actor is stopped
// on the actor thread alongside the hash-cache eviction.
- // Sent after the secrets step — see pendingDclApply above (M2).
+ // Sent after the secrets step — see pendingDclApply above.
pendingDclApply = new ApplyArtifactDataConnectionsToDcl(
command.DataConnections, removedConnections);
}
@@ -3343,7 +3342,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
// SMTP configuration is
// central-only and is never stored on a site (see the purge above).
- // Site-secrets artifact deploy (Task 10): write the shipped secrets to the
+ // Site-secrets artifact deploy: write the shipped secrets to the
// replicated deployed_secrets table, then reconcile THIS node's own mirror
// synchronously — the reply's SecretsApplied must reflect this node's own
// apply/verify outcome, not whatever the next periodic pass finds. A null
@@ -3465,7 +3464,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
///
///
/// The deploy path's static-override / native-alarm reset, dispatched BEFORE this call so the
- /// new actor's PreStart override load can be ordered after it (arch-review 2026-08-19 P1-2).
+ /// new actor's PreStart override load can be ordered after it.
/// on every non-deploy start (boot batch, enable, self-heal), where
/// nothing is being reset and the load runs immediately as before.
///
@@ -3479,13 +3478,13 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
//
// This used to be the caller's job, and only TWO of the four callers did it: the
// boot-time batch and ApplyDeployment. The two self-heal starts did not —
- // StartStoredInstance (the shared row-67 ensure-started / row-78 sweep path) and
+ // StartStoredInstance (the shared ensure-started / reconcile sweep path) and
// HandleEnableResult. Both therefore created an instance whose every tag subscribe was
// answered "Unknown connection: X" by the DCL manager, forever, because nothing else on
// the node would ever create that connection actor:
//
// - Row 85's live symptom (2026-08-16): a replication-seeded deployed_configurations row
- // is started by the row-78 sweep, but the connection actor is only ever created as a
+ // is started by the reconcile sweep, but the connection actor is only ever created as a
// side effect of a deploy or the boot load, so tag subscribes failed for 6+ minutes
// until a second pair restart let the boot load create it.
// - The same defect is reachable with no replication at all: the boot load reads only
@@ -3587,7 +3586,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
internal int InstanceActorCount => _instanceActors.Count;
///
- /// Updates the health collector with current instance counts, and (WP3.1) grows the
+ /// Updates the health collector with current instance counts, and grows the
/// script-execution pool to match.
/// Total deployed = _deployedInstanceNames.Count, enabled = running actors, disabled = difference.
///
@@ -3598,7 +3597,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
enabled: _instanceActors.Count,
disabled: _deployedInstanceNames.Count - _instanceActors.Count);
- // WP3.1: the blocking script pool scales with the number of running instances rather
+ // The blocking script pool scales with the number of running instances rather
// than sitting at a fixed 8 forever. This is the single call site because it already
// runs on every deploy / undeploy / enable / disable and once per staggered startup
// batch. Growth is idempotent and one-way — see ScriptExecutionScheduler.EnsureCapacity.
@@ -3672,7 +3671,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
List EnabledConfigs, int CompiledCount, int TotalCount);
///
- /// WP3.1 warm-then-gate: piped back to self once a deployment's scripts have been
+ /// Warm-then-gate: piped back to self once a deployment's scripts have been
/// compiled off the actor thread into the process-wide compile cache. Carries the exact
/// command instance it warmed so a result superseded mid-warm is recognised by reference
/// and dropped.
@@ -3680,7 +3679,7 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
internal sealed record DeployCompileWarmed(DeployInstanceCommand Command);
///
- /// WP3.1: piped back to self once a staggered startup batch's compiles have been
+ /// Piped back to self once a staggered startup batch's compiles have been
/// pre-warmed, carrying the original batch message so actor creation resumes unchanged.
///
internal sealed record BatchCompileWarmed(StartNextBatch Batch);
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/InstanceActor.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/InstanceActor.cs
index 933bedac..77c64d2c 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/InstanceActor.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/InstanceActor.cs
@@ -105,11 +105,11 @@ public class InstanceActor : ReceiveActor
// deserialized configuration (last-wins on duplicate canonical names,
// mirroring the rest of the actor's by-name dictionaries). Each entry
// additionally carries its DataType/ElementDataType parsed ONCE at
- // config-apply time (perf remediation, arch-review WP1.5) so the hot
+ // config-apply time so the hot
// TagValueUpdate path never re-Enum.TryParse's a string per update.
private readonly Dictionary _resolvedAttributeByName = new();
- // Static-override write coalescing (perf remediation, arch-review WP1.5):
+ // Static-override write coalescing:
// mirrors NativeAlarmActor's MarkDirtyUpsert/FlushDirtyUpserts (P4) shape.
// A burst of rapid SetAttribute calls on the same or different attributes
// collapses into one batched SQLite transaction on a short single-shot
@@ -124,7 +124,7 @@ public class InstanceActor : ReceiveActor
///
/// The deploy path's static-override / native-alarm reset, supplied by
/// on a (re)deploy and
- /// on every other start (arch-review 2026-08-19 P1-2). When set,
+ /// on every other start When set,
/// starts the override load only once it has completed, so a
/// redeploy can never load the PREDECESSOR's overrides into the successor's memory.
///
@@ -135,8 +135,7 @@ public class InstanceActor : ReceiveActor
internal sealed record StaticOverridesFlushed(IActorRef Actor, Exception? Error);
///
- /// Cached string forms of (perf remediation, arch-review
- /// WP1.5) — the TagValueUpdate ingest path calls this once or twice per update, so a
+ /// Cached string forms of — the TagValueUpdate ingest path calls this once or twice per update, so a
/// static lookup replaces repeated reflection.
///
private static readonly Dictionary QualityStrings = new()
@@ -150,7 +149,7 @@ public class InstanceActor : ReceiveActor
QualityStrings.TryGetValue(quality, out var s) ? s : quality.ToString();
///
- /// O(1) attribute-resolution index entry (perf remediation, arch-review WP1.5): wraps a
+ /// O(1) attribute-resolution index entry: wraps a
/// together with its /
/// element / IsList classification, parsed ONCE when the entry
/// is built (constructor) instead of re-parsed via
@@ -280,7 +279,7 @@ public class InstanceActor : ReceiveActor
{
// Parse DataType/ElementDataType ONCE here (config-apply time) and index
// the wrapping entry for O(1) lookup on the hot TagValueUpdate ingest path
- // (last-wins on duplicate names) — perf remediation, arch-review WP1.5.
+ // (last-wins on duplicate names)
var dataType = ParseDataType(attr.DataType);
var elementDataType = ParseElementDataType(attr.ElementDataType);
var isList = dataType == DataType.List;
@@ -398,8 +397,7 @@ public class InstanceActor : ReceiveActor
// Asynchronously load static overrides from SQLite and pipe to self. On a (re)deploy
// the read is chained behind the reset the Deployment Manager dispatched before this
- // actor existed, so it can only ever observe the CLEARED table (arch-review
- // 2026-08-19 P1-2). A faulted reset does not block the load — the deploy itself fails
+ // actor existed, so it can only ever observe the CLEARED table. A faulted reset does not block the load — the deploy itself fails
// on that fault through the persistence join, which stops this actor anyway.
var self = Self;
var instanceName = _instanceUniqueName;
@@ -519,7 +517,7 @@ public class InstanceActor : ReceiveActor
private void HandleSetStaticAttribute(SetStaticAttributeCommand command)
{
// O(1) lookup via the existing hot-path index instead of a linear scan over
- // _configuration.Attributes (perf remediation, arch-review WP1.5).
+ // _configuration.Attributes.
_resolvedAttributeByName.TryGetValue(command.AttributeName, out var resolved);
// Reject writes targeting an attribute that does not exist
@@ -640,7 +638,7 @@ public class InstanceActor : ReceiveActor
///
/// Records the latest value per attribute name and arms the coalesced flush timer
- /// (perf remediation, arch-review WP1.5) — mirrors NativeAlarmActor's
+ /// — mirrors NativeAlarmActor's
/// MarkDirtyUpsert/FlushDirtyUpserts (P4) shape. A burst of rapid static writes
/// collapses into one batched SQLite transaction instead of a write per call.
///
@@ -1157,7 +1155,7 @@ public class InstanceActor : ReceiveActor
/// actor.
///
/// Takes the already-parsed /
- /// (perf remediation, arch-review WP1.5) rather than re-parsing from the attribute's
+ /// rather than re-parsing from the attribute's
/// raw DataType strings — every call site now resolves these once via
/// at config-apply time.
///
@@ -1185,8 +1183,7 @@ public class InstanceActor : ReceiveActor
/// non-enumerable value, or any element that cannot be coerced — the caller then
/// marks the attribute quality Bad. Never throws.
///
- /// Takes the already-parsed (perf remediation,
- /// arch-review WP1.5) — see .
+ /// Takes the already-parsed — see .
///
private bool TryCoerceListValue(DataType? elementType, object? incoming, out object? typedList)
{
@@ -1325,13 +1322,18 @@ public class InstanceActor : ReceiveActor
}
///
- /// Pure backoff step for the tag-subscribe retry (Lever 1, 2026-08-28): double the
+ /// Pure backoff step for the tag-subscribe retry: double the
/// interval just used, apply ±20 % jitter from (a
/// uniform sample in [0,1]; 0.5 = no jitter) so N instances on one connection do not
/// re-storm in lockstep, then clamp to [floor, ceiling]. A ceiling below the floor
/// degrades to a fixed floor interval rather than shrinking below it — same contract
/// as DataConnectionActor.NextTagResolutionInterval.
///
+ /// The interval just used.
+ /// The minimum interval.
+ /// The maximum interval; a value below degrades to the floor.
+ /// A uniform sample in [0,1] used to apply jitter; 0.5 means no jitter.
+ /// The next retry interval, clamped to the allowed range.
internal static TimeSpan NextTagSubscribeInterval(TimeSpan current, TimeSpan floor, TimeSpan max, double jitterUnit)
{
var ceiling = max < floor ? floor : max;
@@ -1658,7 +1660,7 @@ public class InstanceActor : ReceiveActor
{
var now = DateTimeOffset.UtcNow;
- // Alarms-only projection (WP2.3): the central live-alarm cache seeds/reconciles
+ // Alarms-only projection: the central live-alarm cache seeds/reconciles
// through this same query surface and throws every attribute row away. Building
// and shipping them is pure waste — one instance with a few hundred attributes
// costs more on the wire than the whole alarm set of the site. The flag is
@@ -2153,7 +2155,7 @@ public class InstanceActor : ReceiveActor
/// Execution id of the writer that took the arm.
private sealed record DisarmTrace(string AttributeName, Guid SourceExecutionId);
- /// Self-tell that fires the coalesced static-override persist flush (P4, arch-review WP1.5).
+ /// Self-tell that fires the coalesced static-override persist flush.
private sealed class FlushStaticOverrides
{
public static readonly FlushStaticOverrides Instance = new();
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/ScriptActor.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/ScriptActor.cs
index ab76e35c..4ca5689d 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/ScriptActor.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/ScriptActor.cs
@@ -20,7 +20,7 @@ namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Actors;
/// Holds compiled script delegate, manages trigger configuration, and launches script
/// runs per invocation. Does not block on run completion.
///
-/// WP3.1: runs are launched directly via rather than
+/// Runs are launched directly via rather than
/// through a short-lived ScriptExecutionActor child (which had no Receive
/// handler, no PostStop, and whose IActorRef was never a message target — pure
/// per-run actor-cell overhead). Concurrent runs are bounded by
@@ -51,19 +51,19 @@ public class ScriptActor : ReceiveActor, IWithTimers
private readonly IServiceProvider? _serviceProvider;
///
- /// Script-execution scheduler seam (#18): the process-wide
+ /// Script-execution scheduler seam: the process-wide
/// when null, or an injected instance so
/// this actor's launched script bodies run on a caller-owned pool instead of the
/// shared one. Resolved lazily at each use so the null (host) path stays
/// byte-for-byte identical to the previous static call.
///
- /// WP3.1: trigger-expression evaluation no longer uses this scheduler — see
+ /// Trigger-expression evaluation no longer uses this scheduler — see
/// .
///
private readonly ScriptExecutionScheduler? _scheduler;
///
- /// WP3.1 (finding #4): the concurrency gate for trigger-expression evaluation, or null
+ /// The concurrency gate for trigger-expression evaluation, or null
/// for the process-wide . Evaluations run as plain
/// async work on the shared .NET thread pool behind this gate, NOT on
/// — that is what stops an Expression trigger from queueing
@@ -72,7 +72,7 @@ public class ScriptActor : ReceiveActor, IWithTimers
private readonly TriggerEvalGate? _evalGate;
///
- /// WP3.1: runs launched but not yet completed (queued or executing) for this script.
+ /// Runs launched but not yet completed (queued or executing) for this script.
/// Incremented at launch, decremented on , which
/// every terminal path emits — including the launch-path catch, so the counter cannot
/// leak. Touched only on the actor thread.
@@ -80,7 +80,7 @@ public class ScriptActor : ReceiveActor, IWithTimers
private int _runsInFlight;
///
- /// WP3.1: when the last shed Warning site event was emitted for this script. Sheds are
+ /// When the last shed Warning site event was emitted for this script. Sheds are
/// ALWAYS counted on the health collector; the site event is rate-limited to one per
/// script per minute so a hot trigger against a saturated cap cannot flood
/// site_events.
@@ -152,8 +152,8 @@ public class ScriptActor : ReceiveActor, IWithTimers
/// Initial attribute snapshot used to seed expression trigger evaluation state.
/// Optional health metrics collector.
/// Optional DI service provider for script execution context services.
- /// Optional script-execution scheduler override (#18); null uses the process-wide shared scheduler.
- /// Optional trigger-expression concurrency gate override (WP3.1); null uses the process-wide shared gate.
+ /// Optional script-execution scheduler override; null uses the process-wide shared scheduler.
+ /// Optional trigger-expression concurrency gate override; null uses the process-wide shared gate.
public ScriptActor(
string scriptName,
string instanceName,
@@ -246,7 +246,7 @@ public class ScriptActor : ReceiveActor, IWithTimers
_scriptName, _instanceName);
}
- // WP3.1: the Stop-on-failure SupervisorStrategy override is gone with the per-run
+ // The Stop-on-failure SupervisorStrategy override is gone with the per-run
// ScriptExecutionActor child it supervised. This actor has no children left; a
// launch-path throw is caught in SpawnExecution (which replies to the Ask caller and
// releases the in-flight slot) so it can never escalate to InstanceActor, which
@@ -348,7 +348,7 @@ public class ScriptActor : ReceiveActor, IWithTimers
/// pending, so a storm of changes collapses to the latest snapshot without
/// unbounded task fan-out.
///
- /// WP3.1 (finding #4) changed WHERE it runs and WHEN its clock starts. It used
+ /// Trigger evaluation runs off the script pool and its clock starts at enqueue. It used
/// to run on the dedicated — the same bounded
/// pool as blocking script bodies — so N blocked scripts stalled every Expression
/// trigger on the node indefinitely. It now runs as plain async work on the shared
@@ -556,14 +556,14 @@ public class ScriptActor : ReceiveActor, IWithTimers
/// ; beyond that the newest
/// trigger is shed (see ).
///
- /// WP3.1: the run is launched directly on the script-execution scheduler via
+ /// The run is launched directly on the script-execution scheduler via
/// — the actor's mailbox stays on the default dispatcher,
/// but the script body runs on the bounded set of dedicated threads, so blocking script
/// I/O is contained there and cannot starve the shared .NET thread pool. No per-run child
/// actor is created.
///
/// The cap governs NEW work only ( 0 — trigger-driven
- /// runs and depth-0 Ask calls). A nested CallScript is exempt: the calling run is
+ /// runs and top-level Ask calls). A nested CallScript is exempt: the calling run is
/// itself still counted in while it awaits its callee (the
/// slot is released only by , sent after the body
/// returns), and a script calling ITSELF routes back to this same actor — so counting the
@@ -625,7 +625,7 @@ public class ScriptActor : ReceiveActor, IWithTimers
parentExecutionId,
// Per-script timeout override (null = use global).
_executionTimeoutSeconds,
- // Scheduler seam (#18): thread this actor's scheduler override down so
+ // Scheduler seam: thread this actor's scheduler override down so
// launched script bodies share the same pool (null = process-wide shared).
_scheduler,
// Flow Debugger (design §5.2): trace flag + value-change trigger detail.
@@ -634,7 +634,7 @@ public class ScriptActor : ReceiveActor, IWithTimers
}
catch (Exception ex)
{
- // WP3.1 supervision parity: the only failure the removed per-run child could
+ // Supervision parity: the only failure the removed per-run child could
// surface was a constructor throw (e.g. queueing onto a disposed scheduler),
// which the old OneForOneStrategy logged and stopped — leaving an Ask caller to
// hang with no reply. Same log shape, same "coordinator unaffected" outcome, plus
@@ -652,11 +652,11 @@ public class ScriptActor : ReceiveActor, IWithTimers
}
///
- /// WP3.1 shed policy: refuses the incoming run because
+ /// Shed policy: refuses the incoming run because
/// runs are already in flight.
/// The four already queued/running are kept — they are closest to their own deadlines and
/// already charged against them — so nothing is ever reordered. A trigger-driven run is
- /// simply not launched; a depth-0 Ask (an inbound-API route, or a CallScript from
+ /// simply not launched; a top-level Ask (an inbound-API route, or a CallScript from
/// an unrelated script's run) gets an explicit error so the caller fails fast rather than
/// hanging to its Ask timeout. Nested (callDepth > 0) launches never reach here —
/// see .
@@ -709,7 +709,7 @@ public class ScriptActor : ReceiveActor, IWithTimers
private void HandleExecutionCompleted(ScriptExecutionCompleted msg)
{
- // WP3.1: release the in-flight slot. Every terminal path emits exactly one of these
+ // Release the in-flight slot. Every terminal path emits exactly one of these
// (success / timeout / failure / launch failure), so the counter tracks reality.
if (_runsInFlight > 0) _runsInFlight--;
@@ -719,7 +719,7 @@ public class ScriptActor : ReceiveActor, IWithTimers
}
///
- /// WP3.1: runs launched but not yet completed. Exposed for regression coverage of the
+ /// Runs launched but not yet completed. Exposed for regression coverage of the
/// shed cap — the counter is otherwise invisible from outside the actor.
///
internal int RunsInFlight => _runsInFlight;
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Persistence/SiteStorageService.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Persistence/SiteStorageService.cs
index 9b0bb4cd..986a39e6 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Persistence/SiteStorageService.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Persistence/SiteStorageService.cs
@@ -116,7 +116,7 @@ public class SiteStorageService
/// or if no row exists for it.
///
///
- /// Register P1-4: plus a FirstOrDefault was
+ /// plus a FirstOrDefault was
/// materializing every deployed config — including every ConfigJson blob — just to find
/// one row. A volume-restore reconcile healing hundreds of instances turned that into hundreds
/// of full-table reads. Callers that need the full row (including ConfigJson) for exactly
@@ -162,7 +162,7 @@ public class SiteStorageService
/// config_json column, or if no row exists for it.
///
///
- /// Register P1-4: for a caller that only needs staleness/identity metadata (e.g. the
+ /// For a caller that only needs staleness/identity metadata (e.g. the
/// central deployment-state query, which reports only DeploymentId/RevisionHash),
/// reading the config_json blob is pure waste. This projection is that lighter read.
///
@@ -432,8 +432,8 @@ public class SiteStorageService
}
///
- /// Batch counterpart of (perf remediation,
- /// arch-review WP1.5): upserts a set of static attribute overrides for one instance
+ /// Batch counterpart of (perf remediation):
+ /// upserts a set of static attribute overrides for one instance
/// in a SINGLE connection + transaction with one prepared command re-bound per row.
/// Used by the InstanceActor's coalesced flush — mirrors NativeAlarmActor's
/// MarkDirtyUpsert/FlushDirtyUpserts (P4) pattern — so a burst of rapid SetAttribute
@@ -802,7 +802,7 @@ public class SiteStorageService
/// Returns every row in deployed_secrets like ,
/// but never throws for a single bad row: a row whose row_json does not deserialize
/// is returned in Unreadable (name, removal flag and the parse error — never the
- /// row's content) instead of failing the whole read (final review M4, per-row isolation).
+ /// row's content) instead of failing the whole read (per-row isolation).
///
/// The parsed rows and the rows that could not be parsed.
public async Task<(IReadOnlyList Rows, IReadOnlyList Unreadable)>
@@ -925,7 +925,7 @@ public class SiteStorageService
await command.ExecuteNonQueryAsync();
}
- // ── Secret mirror status (review finding M1 — LocalDb-replicated, one row per node) ──
+ // ── Secret mirror status (LocalDb-replicated, one row per node) ──
///
/// Records 's current deployed-secrets mirror failures in the
@@ -1418,7 +1418,7 @@ public class DeployedInstance
/// Deployment-identity metadata for one deployed instance — the projection of
/// that omits ConfigJson. Returned by
/// for callers that only need to
-/// answer "is this instance deployed, and at what revision" (register P1-4).
+/// answer "is this instance deployed, and at what revision".
///
/// The unique name of the instance.
/// The unique deployment identifier.
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Repositories/ExternalSystemDefinitionCache.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Repositories/ExternalSystemDefinitionCache.cs
index b4f6b512..4b0b41a1 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Repositories/ExternalSystemDefinitionCache.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Repositories/ExternalSystemDefinitionCache.cs
@@ -3,7 +3,7 @@ using ZB.MOM.WW.ScadaBridge.Commons.Entities.ExternalSystems;
namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Repositories;
///
-/// WP2.6a (arch-review misc — site external-system resolution): the shared,
+/// The shared,
/// redeploy-invalidated snapshot backing 's
/// reads.
///
@@ -62,6 +62,7 @@ public sealed class ExternalSystemDefinitionCache
///
/// Loads a fresh snapshot from storage; invoked at most once per miss.
/// Cancellation token for the load.
+ /// The cached or freshly loaded snapshot.
internal async Task GetOrLoadAsync(
Func> loader, CancellationToken cancellationToken)
{
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Repositories/SiteExternalSystemRepository.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Repositories/SiteExternalSystemRepository.cs
index fe4b738d..fd9d0653 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Repositories/SiteExternalSystemRepository.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Repositories/SiteExternalSystemRepository.cs
@@ -12,7 +12,7 @@ namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Repositories;
/// Write operations throw because site-local
/// artifacts are managed exclusively through deployment from Central.
///
-/// WP2.6a (arch-review misc): every ExternalSystemDefinition/ExternalSystemMethod
+/// Every ExternalSystemDefinition/ExternalSystemMethod
/// read is served from an snapshot instead of
/// a fresh SQLite scan per call — the by-ID lookups in particular used to re-fetch every
/// row (and, for methods, re-parse every system's JSON method list) just to find one
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/FilesAccessor.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/FilesAccessor.cs
index 2ffd8522..ffd6dd43 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/FilesAccessor.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/FilesAccessor.cs
@@ -63,6 +63,9 @@ public sealed class FilesAccessor
?? throw new ScriptFileAccessException(ScriptFileAccessFailure.Disabled,
"Script file access is not available on this node.");
+ /// Splits text into lines on CRLF, LF or CR boundaries.
+ /// The text to split.
+ /// The lines of ; empty when the text is empty.
internal static IReadOnlyList SplitLines(string text)
{
if (text.Length == 0)
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScopeAccessors.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScopeAccessors.cs
index 884527ea..80139a65 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScopeAccessors.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScopeAccessors.cs
@@ -112,7 +112,7 @@ public class AttributeAccessor
}
///
- /// Reset-aware handshake (Gitea #36): writes =,
+ /// Reset-aware handshake: writes =,
/// waits (event-driven, bounded by , default = )
/// for the device to ECHO that reset, and only then performs the ordinary
///
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScriptExecutionScheduler.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScriptExecutionScheduler.cs
index 353d6433..4b2ed75e 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScriptExecutionScheduler.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScriptExecutionScheduler.cs
@@ -36,7 +36,7 @@ public enum WorkerDetachOutcome
/// of dedicated threads, so script blocking is contained to those threads and cannot
/// exhaust the global pool.
///
-/// WP3.1 changed three things about that pool:
+/// The pool differs from a fixed-size pool in three ways:
///
/// It is no longer fixed-size. grows it towards
/// (instance-scaled, clamped between the configured
@@ -48,7 +48,7 @@ public enum WorkerDetachOutcome
/// Trigger-expression evaluations no longer run here at all — they are non-blocking
/// by construction and now run on the shared thread pool behind
/// , so an alarm's Expression trigger can never queue behind
-/// eight blocking script bodies (finding #4).
+/// eight blocking script bodies.
///
///
/// The scheduler is process-wide (one set of threads for all instances) and is sized
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScriptLogger.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScriptLogger.cs
index 743d314d..fe5f36c8 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScriptLogger.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScriptLogger.cs
@@ -157,7 +157,7 @@ public sealed class ScriptLogger
try
{
- // Final review M3: worded identically to the central InboundScriptLogger's closing row,
+ // Worded identically to the central InboundScriptLogger's closing row,
// so the two surfaces read the same in the step-log panel (only the option key differs).
_emit("warn", "runtime",
$"log line cap reached - {n} further log line(s) were not captured " +
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScriptRunEventPolicy.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScriptRunEventPolicy.cs
index 66603f9e..db106f7a 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScriptRunEventPolicy.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScriptRunEventPolicy.cs
@@ -1,7 +1,7 @@
namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts;
///
-/// WP3.2 (site_events volume policy — docs/plans/2026-08-15-site-events-policy-design.md):
+/// Site-events volume policy (docs/plans/2026-08-15-site-events-policy-design.md):
/// decides whether an instance script run should emit the legacy per-run Started/Completed
/// Info site events, or stay silent and let 's interval
/// aggregate carry the signal instead. Pure and side-effect-free — no DI, no actor context —
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScriptRunLauncher.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScriptRunLauncher.cs
index 53e6e18d..81d6cb94 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScriptRunLauncher.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScriptRunLauncher.cs
@@ -21,7 +21,7 @@ using ZB.MOM.WW.ScadaBridge.StoreAndForward;
namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts;
///
-/// WP3.1: launches one script or alarm on-trigger run directly from its owning coordinator
+/// Launches one script or alarm on-trigger run directly from its owning coordinator
/// ( / ), replacing the short-lived
/// ScriptExecutionActor and AlarmExecutionActor.
///
@@ -76,7 +76,7 @@ internal static class ScriptRunLauncher
/// Optional DI service provider for script execution services.
/// ExecutionId of the spawning execution for audit correlation; null for root runs.
/// Per-script execution timeout in seconds. Null or non-positive falls back to the global value.
- /// Script-execution scheduler seam (#18); null selects the process-wide shared instance.
+ /// Script-execution scheduler seam; null selects the process-wide shared instance.
/// Flow Debugger trace flag (design 2026-09-05 §5.2): when true the run emits ScriptTrace rows and returns them inline on the .
/// Flow Debugger: trigger detail for a traced value-change run (stamped into the ScriptRun row's Extra); null for calls.
public static void LaunchScript(
@@ -146,13 +146,12 @@ internal static class ScriptRunLauncher
/// The owning , which receives the completion message.
/// Logger for execution diagnostics.
///
- /// Optional health collector for recording execution metrics (arch-review 2026-08-19
- /// P1-3): the alarm on-trigger run shares the script-error counter with
+ /// Optional health collector for recording execution metrics: the alarm on-trigger run shares the script-error counter with
/// so a wedged/failed alarm body is visible the same way a
/// wedged/failed instance script is.
///
///
- /// Optional DI service provider (arch-review 2026-08-19 P1-3): resolves the singleton
+ /// Optional DI service provider: resolves the singleton
/// (stuck-watchdog reporting) and
/// (aggregate run counters), exactly like the
/// instance-script path.
@@ -164,7 +163,7 @@ internal static class ScriptRunLauncher
/// write fired this alarm. Null when the firing value came from the Data Connection Layer
/// (external data has no spawning execution) — that on-trigger run is a tree ROOT.
///
- /// Script-execution scheduler seam (#18); null selects the process-wide shared instance.
+ /// Script-execution scheduler seam; null selects the process-wide shared instance.
/// Site transport for notification status queries, resolved on the actor thread.
public static void LaunchAlarmScript(
string alarmName,
@@ -268,7 +267,7 @@ internal static class ScriptRunLauncher
// it is available to the catch blocks regardless of scope state.
var siteEventLogger = serviceProvider?.GetService();
- // WP3.2 (site_events volume policy): both are singletons, resolved from the root
+ // site_events volume policy: both are singletons, resolved from the root
// provider up front so they too are available to the catch blocks. optionsMonitor is
// null in tests that pass no serviceProvider (SingleServiceProvider et al.) — the
// constructor-passed `options` is the fallback, exactly like siteEventLogger's null
@@ -282,7 +281,7 @@ internal static class ScriptRunLauncher
var emitPerRunEvents = ScriptRunEventPolicy.ShouldEmitPerRun(effectiveOptions, instanceName, scriptName);
var runStopwatch = Stopwatch.StartNew();
- // WP3.1: identify the worker this body is holding, so the watchdog can detach and
+ // Identify the worker this body is holding, so the watchdog can detach and
// replace it if the body wedges. Read in the FIRST synchronous segment — a script
// that later hops threads on an await no longer holds this worker, and the run-stamp
// guard in TryDetachWorker will (correctly) refuse to detach it.
@@ -299,7 +298,7 @@ internal static class ScriptRunLauncher
try
{
- // WP3.1 shed-at-dequeue: the deadline was armed at ENQUEUE, so an already-cancelled
+ // Shed-at-dequeue: the deadline was armed at ENQUEUE, so an already-cancelled
// token here means this run spent its entire budget queueing. Running it now would
// burn a scarce script thread on work that is already stale — throw straight into
// the existing timeout path instead, BEFORE the "started" event and before the body.
@@ -421,7 +420,7 @@ internal static class ScriptRunLauncher
Scope = scope
};
- // WP3.2: the aggregate ALWAYS records this run (that's the point of the interval
+ // The aggregate ALWAYS records this run (that's the point of the interval
// summary), but the per-run Info event itself is gated by ScriptRunEventPolicy —
// off by default, on globally via PerRunScriptEvents, or on for named scripts via
// PerRunScriptEventScripts. Fire-and-forget (the `_ =` discards the task) so the
@@ -478,7 +477,7 @@ internal static class ScriptRunLauncher
var errorMsg = $"Script '{scriptName}' on instance '{instanceName}' timed out after {timeout.TotalSeconds}s";
logger.LogWarning("{Message} (run {RunId})", errorMsg, runId);
- // Failures recorded to site event log; script NOT disabled after failure. WP3.2:
+ // Failures recorded to site event log; script NOT disabled after failure.
// Error-level emissions are unconditional — never gated by ScriptRunEventPolicy.
_ = siteEventLogger?.LogEventAsync(
"script", "Error", instanceName, $"ScriptActor:{scriptName}", errorMsg);
@@ -504,7 +503,7 @@ internal static class ScriptRunLauncher
{
healthCollector?.IncrementScriptError();
summaryRecorder?.RecordFailed(instanceName, scriptName, runStopwatch.ElapsedMilliseconds);
- // Failures recorded to site event log; script NOT disabled after failure. WP3.2:
+ // Failures recorded to site event log; script NOT disabled after failure.
// Error-level emissions are unconditional — never gated by ScriptRunEventPolicy.
var errorMsg = $"Script '{scriptName}' on instance '{instanceName}' failed: {ex.Message}";
logger.LogError(ex, "Script execution failed: {Script} on {Instance} (run {RunId})",
@@ -556,6 +555,13 @@ internal static class ScriptRunLauncher
/// the ACTOR thread, outside any launch scope. elapsedMs is 0 — the run never ran.
/// Best-effort and never throws: a no-op when there is no provider or no writer.
///
+ /// Root service provider used to resolve the audit writer and identity providers; no-op when null.
+ /// Logger for best-effort failure reporting.
+ /// Name of the script whose run was refused.
+ /// Name of the instance owning the script.
+ /// ExecutionId of the spawning execution; null for root runs.
+ /// Human-readable guard reason the run was refused.
+ /// Trigger detail stamped into the row's Extra for a traced value-change run; null otherwise.
internal static void EmitSuppressedRun(
IServiceProvider? serviceProvider,
ILogger logger,
@@ -668,7 +674,7 @@ internal static class ScriptRunLauncher
CancellationTokenSource cts,
ICanTell? siteCommunicationActor)
{
- // arch-review 2026-08-19 P1-3: singletons resolved from the root provider up front,
+ // Singletons resolved from the root provider up front,
// exactly like RunScriptAsync, so they are available to the catch blocks regardless
// of scope state. Null in tests that pass no serviceProvider — degrades the same way
// the instance-script path does (no site event log / no aggregate counters).
@@ -681,7 +687,7 @@ internal static class ScriptRunLauncher
var scriptName = $"AlarmActor:{alarmName}";
var runStopwatch = Stopwatch.StartNew();
- // arch-review 2026-08-19 P1-3: identify the worker this body is holding, so the
+ // Identify the worker this body is holding, so the
// stuck-script watchdog can detach and replace it if the body wedges — same
// mechanism, same gauge, as the instance-script path. Read in the FIRST synchronous
// segment, before any await.
@@ -716,7 +722,7 @@ internal static class ScriptRunLauncher
timeout,
instanceName,
logger,
- // Final review M6: name the source so ScriptRuntimeContext's `_logTarget` derives
+ // Name the source so ScriptRuntimeContext's `_logTarget` derives
// the alarm this on-trigger run belongs to ("AlarmActor:Foo" -> "Foo") — without
// it every Log.* line from an alarm script was attributed to the INSTANCE, not the
// alarm. `scriptName` above is already the "AlarmActor:{alarmName}" source key, so
@@ -795,7 +801,7 @@ internal static class ScriptRunLauncher
}
///
- /// Arms the stuck-script watchdog (S2, extended by WP3.1).
+ /// Arms the stuck-script watchdog, which can also detach and replace a wedged worker.
///
/// The CTS firing only REQUESTS cooperative cancellation; it does NOT free a thread
/// blocked in synchronous I/O. When the deadline elapses, this waits
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScriptRunSummaryFlushService.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScriptRunSummaryFlushService.cs
index 26767e8a..0a368f35 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScriptRunSummaryFlushService.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScriptRunSummaryFlushService.cs
@@ -6,7 +6,7 @@ using ZB.MOM.WW.ScadaBridge.SiteEventLogging;
namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts;
///
-/// WP3.2 (site_events volume policy — docs/plans/2026-08-15-site-events-policy-design.md):
+/// Site-events volume policy (docs/plans/2026-08-15-site-events-policy-design.md):
/// site-only hosted service that periodically flushes
/// into one aggregate "script" Info site event. Registered by
/// ServiceCollectionExtensions.AddSiteRuntime, which only the site composition root
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScriptRunSummaryRecorder.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScriptRunSummaryRecorder.cs
index c07354db..0e6e0113 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScriptRunSummaryRecorder.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScriptRunSummaryRecorder.cs
@@ -6,7 +6,7 @@ using ZB.MOM.WW.ScadaBridge.SiteEventLogging;
namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts;
///
-/// WP3.2 (site_events volume policy — docs/plans/2026-08-15-site-events-policy-design.md):
+/// Site_events volume policy (docs/plans/2026-08-15-site-events-policy-design.md):
/// accumulates per-(instance, script) run counters between flushes and emits ONE aggregate
/// "script" Info site event per interval instead of the two per-run Started/Completed rows
/// that were the dominant site_events writer. Registered as a process-wide singleton
@@ -54,6 +54,8 @@ public sealed class ScriptRunSummaryRecorder
Volatile.Read(ref _counters).GetOrAdd((instanceName, scriptName), static _ => new Counters());
/// Records that a script run began executing (past the shed-at-dequeue check).
+ /// The instance the script belongs to.
+ /// The script that ran.
public void RecordStarted(string instanceName, string scriptName)
{
var counters = GetOrAddCounters(instanceName, scriptName);
@@ -61,6 +63,9 @@ public sealed class ScriptRunSummaryRecorder
}
/// Records that a script run completed successfully.
+ /// The instance the script belongs to.
+ /// The script that ran.
+ /// The run duration in milliseconds.
public void RecordCompleted(string instanceName, string scriptName, long durationMs)
{
var counters = GetOrAddCounters(instanceName, scriptName);
@@ -69,6 +74,9 @@ public sealed class ScriptRunSummaryRecorder
}
/// Records that a script run threw an unhandled exception.
+ /// The instance the script belongs to.
+ /// The script that ran.
+ /// The run duration in milliseconds.
public void RecordFailed(string instanceName, string scriptName, long durationMs)
{
var counters = GetOrAddCounters(instanceName, scriptName);
@@ -80,6 +88,9 @@ public sealed class ScriptRunSummaryRecorder
/// Records that a script run timed out — including a run shed at dequeue (its deadline
/// was already spent queueing, so it never reached ).
///
+ /// The instance the script belongs to.
+ /// The script that timed out.
+ /// The run duration in milliseconds.
public void RecordTimedOut(string instanceName, string scriptName, long durationMs)
{
var counters = GetOrAddCounters(instanceName, scriptName);
@@ -202,7 +213,7 @@ public sealed class ScriptRunSummaryRecorder
return true;
}
- // Built once per assembly (WP1.5 convention) rather than per flush.
+ // Built once per assembly rather than per flush.
private static readonly JsonSerializerOptions DetailsJsonOptions =
new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScriptRuntimeContext.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScriptRuntimeContext.cs
index 50c379da..e01fb170 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScriptRuntimeContext.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScriptRuntimeContext.cs
@@ -310,6 +310,7 @@ public class ScriptRuntimeContext
/// , so an inline shared script shares its
/// caller's line cap and anchor. Null (the default) creates this run's own logger.
///
+ /// Host-mediated reader backing the script Files global; null disables file access.
public ScriptRuntimeContext(
IActorRef instanceActor,
SharedScriptLibrary sharedScriptLibrary,
@@ -422,6 +423,9 @@ public class ScriptRuntimeContext
/// Wraps a trace sink so a throwing observer can neither fail the script call nor skip the
/// durable audit write that follows it. Null in, null out — an untraced run allocates nothing.
///
+ /// The trace sink to wrap, or null for an untraced run.
+ /// Logger used to report a rejecting sink.
+ /// The guarded sink, or null when is null.
internal static Action? GuardTraceSink(Action? sink, ILogger logger)
{
if (sink is null) return null;
@@ -973,7 +977,7 @@ public class ScriptRuntimeContext
.ConfigureAwait(false)).Matched;
///
- /// Reset-aware handshake (Gitea #36, design 2026-09-10 §5) backing the nine-argument
+ /// Reset-aware handshake (design 2026-09-10 §5) backing the nine-argument
/// Attributes.WriteBatchAndWaitAsync overload. Legs, in a FIXED order:
/// (1) write = through
/// (throws on a device-write failure, like the batch leg);
@@ -986,7 +990,7 @@ public class ScriptRuntimeContext
/// Why the reset must be OBSERVED before the trigger is written: a subscription reports a
/// change from the last reported value, so a device that flips reset→done inside one
/// sampling interval never reports the reset — the cached previous-run "done" value would
- /// then satisfy the response wait instantly (the #36 race), and an edge-triggered wait
+ /// then satisfy the response wait instantly (the stale-echo race), and an edge-triggered wait
/// would never resolve at all. Holding the trigger until the echo lands makes the existing
/// level-triggered fast-path correct again. Each leg is already a traced Flow Debugger
/// step, so a traced run shows all four.
@@ -1006,7 +1010,7 @@ public class ScriptRuntimeContext
/// Scope-resolved attribute written FIRST (usually the response attribute).
///
/// Codec-encoded reset value the device must echo before anything else is written. NON-NULL by
- /// contract: a null would write an empty string on leg 1 and then give the leg-2 waiter a null
+ /// contract: a null would write an empty string on leg 1 and then give the second-leg waiter a null
/// target — "any change" — which fast-paths on the stale cached value and reinstates the very
/// race this overload exists to close. The accessor rejects a null reset value before it gets here.
///
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScriptSchedulerStatsReporter.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScriptSchedulerStatsReporter.cs
index 943d81b8..f6a8f121 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScriptSchedulerStatsReporter.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScriptSchedulerStatsReporter.cs
@@ -25,7 +25,7 @@ public sealed class ScriptSchedulerStatsReporter : BackgroundService
private readonly TimeSpan _pollInterval;
///
- /// Script-execution scheduler seam (#18): the process-wide scheduler when null, or
+ /// Script-execution scheduler seam: the process-wide scheduler when null, or
/// an injected instance so the reporter samples the same pool the actors run on.
///
private readonly ScriptExecutionScheduler? _scheduler;
@@ -35,7 +35,7 @@ public sealed class ScriptSchedulerStatsReporter : BackgroundService
/// Site runtime options supplying the shared scheduler's thread count.
/// Logger instance.
/// Poll interval override; defaults to (10 s).
- /// Optional script-execution scheduler override (#18); null samples the process-wide shared scheduler.
+ /// Optional script-execution scheduler override; null samples the process-wide shared scheduler.
public ScriptSchedulerStatsReporter(
ISiteHealthCollector collector,
SiteRuntimeOptions options,
@@ -81,7 +81,7 @@ public sealed class ScriptSchedulerStatsReporter : BackgroundService
scheduler.QueueDepth,
scheduler.BusyThreadCount,
scheduler.OldestBusyAge?.TotalSeconds,
- // WP3.1: workers detached and replaced by the stuck-script watchdog.
+ // Workers detached and replaced by the stuck-script watchdog.
scheduler.DetachedThreadCount);
}
catch (Exception ex)
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/SiteScriptCompileCache.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/SiteScriptCompileCache.cs
index 728879b9..09729720 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/SiteScriptCompileCache.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/SiteScriptCompileCache.cs
@@ -41,7 +41,7 @@ namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts;
/// 4096 because entries pin compiled assemblies, not just verdict strings.
///
///
-/// WP3.1 — approximate LRU replaced the overflow cliff. The cache used to
+/// Approximate LRU replaced the overflow cliff. The cache used to
/// clear itself wholesale on overflow. Instance scripts AND trigger expressions share this
/// cache, so on a site large enough to cross 1024 entries every overflow discarded up to 1023
/// live compiled scripts and the next deploy or Instance-Actor start paid a full recompile
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/TraceCapturingLogger.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/TraceCapturingLogger.cs
index ee485e09..a8925668 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/TraceCapturingLogger.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/TraceCapturingLogger.cs
@@ -41,7 +41,10 @@ public sealed class TraceCapturingLogger : ILogger
_sink = sink;
}
- ///
+ /// Begins a logical scope on the wrapped logger.
+ /// The type of the scope state.
+ /// The scope state.
+ /// A disposable that ends the scope, or null if the inner logger returns none.
public IDisposable? BeginScope(TState state) where TState : notnull => _inner.BeginScope(state);
///
@@ -52,7 +55,13 @@ public sealed class TraceCapturingLogger : ILogger
/// when the entry should be produced.
public bool IsEnabled(LogLevel level) => level >= LogLevel.Information || _inner.IsEnabled(level);
- ///
+ /// Forwards the entry to the wrapped logger and captures it into the trace sink.
+ /// The type of the entry state.
+ /// The entry's log level.
+ /// The entry's event id.
+ /// The entry state.
+ /// The associated exception, if any.
+ /// Formats the state and exception into the message.
public void Log(LogLevel level, EventId eventId, TState state, Exception? exception, Func formatter)
{
// Forward first, and only if the inner logger actually wants it: IsEnabled above lies
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/TraceStepScope.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/TraceStepScope.cs
index 09945bf2..0a23f9e1 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/TraceStepScope.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/TraceStepScope.cs
@@ -38,7 +38,7 @@ public static class TraceStepScope
/// Restores the ambient step id captured at time.
private sealed class Restore(Guid? prev) : IDisposable
{
- ///
+ /// Restores the previous ambient step id.
public void Dispose() => _current.Value = prev;
}
}
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/TriggerEvalGate.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/TriggerEvalGate.cs
index 27e045a8..66340a8f 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/TriggerEvalGate.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/TriggerEvalGate.cs
@@ -1,9 +1,9 @@
namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts;
///
-/// WP3.1 (finding #4): the process-wide concurrency gate for trigger-expression evaluation.
+/// The process-wide concurrency gate for trigger-expression evaluation.
///
-/// Before WP3.1, ScriptActor and AlarmActor evaluated their compiled
+/// Previously, ScriptActor and AlarmActor evaluated their compiled
/// Expression triggers on the — the same bounded pool
/// of dedicated threads that runs script bodies. Eight scripts blocked in synchronous I/O
/// therefore stalled EVERY Expression trigger on the node behind them, for an unbounded time,
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Secrets/DeployedSecretsMirror.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Secrets/DeployedSecretsMirror.cs
index ebce66df..5e439dcc 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Secrets/DeployedSecretsMirror.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Secrets/DeployedSecretsMirror.cs
@@ -22,7 +22,7 @@ namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Secrets;
///
/// The secret name behind each entry, in the SAME order (index-for-
/// index — each pass reports at most one failure per row, so the two lists always stay in
-/// lockstep). Added for DeploymentManagerActor.HandleDeployArtifacts (Task 10): this
+/// lockstep). Added for DeploymentManagerActor.HandleDeployArtifacts: this
/// mirror reconciles the WHOLE site-wide deployed_secrets table every pass, not only the
/// names a particular deploy just shipped, so a caller that needs to know whether a failure
/// belongs to ITS OWN shipped names — as opposed to some other, previously-deployed secret, or a
@@ -54,7 +54,7 @@ public sealed record MirrorResult(
/// master key.
///
///
-/// Apply semantics and the confirm-then-ledger fix (fix round 1, review Important #1).
+/// Apply semantics and the confirm-then-ledger fix.
/// applies last-writer-wins strictly on
/// (UpdatedUtc, Revision) — see
/// — and a row that is not
@@ -106,7 +106,7 @@ public sealed record MirrorResult(
/// a later pass's "is this ledgered at the shipped revision" check stays correct.
///
///
-/// Per-row isolation (review Important #2). Each row's apply/verify body runs in its own
+/// Per-row isolation. Each row's apply/verify body runs in its own
/// try/catch; an exception — an unrecognised ContentType from a newer central, a
/// per-row store fault, a malformed name — is caught, reported as a
/// entry naming the secret and the exception type/message (never a value), logged with the same
@@ -114,7 +114,7 @@ public sealed record MirrorResult(
/// caller's own cancellation (ct.IsCancellationRequested) propagates.
///
///
-/// Cache invalidation (review Important #3). The concrete resolver
+/// Cache invalidation. The concrete resolver
/// (DefaultSecretResolver) caches decrypted plaintext for a TTL and also implements
/// ; every ZB.MOM.WW.Secrets write path invalidates after a
/// write for exactly this reason. This mirror does the same: after every successful apply
@@ -124,10 +124,10 @@ public sealed record MirrorResult(
/// can observe a stale pre-rotation plaintext for up to the cache TTL.
///
///
-/// Node-local store assumption (review Minor #5; Task 11 removed every replication mode).
+/// Node-local store assumption (every replication mode has been removed).
/// This mirror assumes the site's is always plain, node-local, local
-/// SQLite — the ONLY shape a Site node's store can be after Task 11 retired both the pull-only
-/// gRPC secrets hub and the SQL-Server ReplicatingSecretStore mode that preceded it (see
+/// SQLite — the ONLY shape a Site node's store can be now that both the pull-only
+/// gRPC secrets hub and the SQL-Server ReplicatingSecretStore mode that preceded it are retired (see
/// SecretsRegistration). It would NOT have been safe to run this mirror against a store
/// decorated by either retired replication mode: DeleteAsync would have published the
/// tombstone to every other node sharing the decorator, so a site merely un-referencing a secret
@@ -156,7 +156,7 @@ public sealed record MirrorResult(
/// is in-memory only (per node process), which matches the ledger's own node-local scope.
///
///
-/// Status row for central (review finding M1). A failure on the STANDBY node used to be
+/// Status row for central. A failure on the STANDBY node used to be
/// visible only in that node's own log — site health reports are sent by the active node only.
/// After each pass this mirror records its node's current failure set (secret name + message,
/// never a value) in the LocalDb-replicated secret_mirror_status table, keyed by node
@@ -190,7 +190,7 @@ public sealed class DeployedSecretsMirror : BackgroundService
///
/// The failure set (sorted name+message pairs) this process last wrote successfully to
/// secret_mirror_status, or before the first successful write
- /// — which is what forces the once-per-start-up write (review finding M1).
+ /// — which is what forces the once-per-start-up write.
///
private IReadOnlyList? _lastWrittenStatus;
@@ -273,7 +273,7 @@ public sealed class DeployedSecretsMirror : BackgroundService
await _gate.WaitAsync(ct).ConfigureAwait(false);
try
{
- // Tolerant read (final review M4): a row whose row_json cannot be parsed is handed
+ // Tolerant read: a row whose row_json cannot be parsed is handed
// back separately instead of throwing out of the whole pass, so it is reported
// against its own name like any other per-row failure.
var (rows, unreadable) = await _storage.GetDeployedSecretsTolerantAsync().ConfigureAwait(false);
@@ -372,7 +372,7 @@ public sealed class DeployedSecretsMirror : BackgroundService
/// or the stored row no longer holds what this process wrote (deleted or overwritten by a
/// replicated change).
/// Best-effort: never throws, so the pass result is unaffected by a status-write fault.
- /// See the class remarks (review finding M1).
+ /// See the class remarks.
///
private async Task RecordStatusIfChangedAsync(IReadOnlyList failedNames, IReadOnlyList failed)
{
@@ -672,7 +672,7 @@ public sealed class DeployedSecretsMirror : BackgroundService
{
// Covers every other failure, including an OperationCanceledException NOT
// attributable to this service's own shutdown token (e.g. a store/command
- // timeout) — that must not silently end the loop (review Minor #4).
+ // timeout) — that must not silently end the loop.
_logger.LogWarning(ex, "Deployed secrets mirror pass failed; retrying next interval.");
}
} while (await timer.WaitForNextTickAsync(stoppingToken).ConfigureAwait(false));
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Secrets/SecretMirrorStatusReporter.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Secrets/SecretMirrorStatusReporter.cs
index f98b5ee2..06c4a556 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Secrets/SecretMirrorStatusReporter.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Secrets/SecretMirrorStatusReporter.cs
@@ -7,7 +7,7 @@ using ZB.MOM.WW.ScadaBridge.SiteRuntime.Persistence;
namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Secrets;
///
-/// Review finding M1: site-side hosted service that periodically reads EVERY node's row from
+/// Site-side hosted service that periodically reads EVERY node's row from
/// the LocalDb-replicated secret_mirror_status table and pushes the list into
/// , so the next health report
/// carries each site node's deployed-secrets mirror failures — the standby's included.
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/ServiceCollectionExtensions.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/ServiceCollectionExtensions.cs
index f85b322c..37b2fc1c 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/ServiceCollectionExtensions.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/ServiceCollectionExtensions.cs
@@ -40,7 +40,7 @@ public static class ServiceCollectionExtensions
// SiteScriptCompileCache, so "one Roslyn compile per script body per process"
// (N4) is exactly the statement that there is one of these per host. Downgrading
// it to Scoped/Transient would silently give every consumer its own cache and
- // reintroduce the recompile storms WP3.1 removed — no test would fail on
+ // reintroduce the recompile storms that were removed — no test would fail on
// behaviour, only the registration pin in SiteRuntimeServiceRegistrationTests.
services.AddSingleton();
@@ -81,7 +81,7 @@ public static class ServiceCollectionExtensions
services.AddSingleton();
// Site-local repository implementations backed by SQLite.
- // WP2.6a: ExternalSystemDefinitionCache is a singleton shared by every scoped
+ // ExternalSystemDefinitionCache is a singleton shared by every scoped
// SiteExternalSystemRepository instance (one per script-execution DI scope), so
// resolving the repository repeatedly does not each pay a fresh SQLite
// fetch-all — DeploymentManagerActor invalidates the shared cache after an
@@ -106,14 +106,14 @@ public static class ServiceCollectionExtensions
sp.GetRequiredService>().Value,
sp.GetRequiredService>()));
- // WP2.6d: periodically lift SiteStreamManager's alarm-publish-queue drop count
+ // Periodically lift SiteStreamManager's alarm-publish-queue drop count
// onto the site health report.
services.AddHostedService(sp => new Streaming.SiteStreamAlarmDropReporter(
sp.GetRequiredService(),
sp.GetRequiredService(),
sp.GetRequiredService>()));
- // WP3.2 (site_events volume policy): the recorder is a singleton so ScriptRunLauncher
+ // Site-events volume policy: the recorder is a singleton so ScriptRunLauncher
// can resolve it from the root service provider on every run, the same way it already
// resolves ISiteEventLogger. The flush service is registered ONLY here — central
// composition roots never call AddSiteRuntime(), which is what makes the periodic
@@ -125,7 +125,7 @@ public static class ServiceCollectionExtensions
sp.GetRequiredService>(),
sp.GetRequiredService>()));
- // Site-secrets artifact deploy (Task 9): per-node reconcile of the site-wide,
+ // Site-secrets artifact deploy: per-node reconcile of the site-wide,
// LocalDb-replicated deployed_secrets table into THIS node's own ZB.MOM.WW.Secrets
// store. Runs on every site node — not a singleton, since mirrored_secrets (which
// names this node has already applied) is deliberately node-local, not replicated.
@@ -140,7 +140,7 @@ public static class ServiceCollectionExtensions
services.AddSingleton();
services.AddHostedService(sp => sp.GetRequiredService());
- // Review finding M1: forward every node's replicated secret_mirror_status row onto the
+ // Forward every node's replicated secret_mirror_status row onto the
// site health report, so the ACTIVE node's report carries the STANDBY's mirror failures.
services.AddHostedService(sp => new SecretMirrorStatusReporter(
sp.GetRequiredService(),
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/SiteRuntimeOptions.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/SiteRuntimeOptions.cs
index bee680c1..a7b4f2dd 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/SiteRuntimeOptions.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/SiteRuntimeOptions.cs
@@ -42,7 +42,7 @@ public class SiteRuntimeOptions
/// Script and alarm on-trigger bodies run on these threads instead of the shared
/// .NET thread pool, so blocking script I/O cannot starve the global pool.
///
- /// WP3.1: this was a fixed size and is now the lower bound of a grow-only,
+ /// This was a fixed size and is now the lower bound of a grow-only,
/// instance-scaled pool — see
/// . Existing
/// configurations keep exactly the previous behaviour at or below
@@ -53,7 +53,7 @@ public class SiteRuntimeOptions
public int ScriptExecutionThreadCount { get; set; } = 8;
///
- /// WP3.1: CEILING for the instance-scaled script-execution pool. The pool grows
+ /// CEILING for the instance-scaled script-execution pool. The pool grows
/// towards ceil(enabledInstances / 8) threads but never past this value; beyond
/// it, is the real regulator. A 1 MB-stack
/// dedicated thread is cheap, so 32 (≈ 256 instances at the /8 ratio) is a generous
@@ -63,7 +63,7 @@ public class SiteRuntimeOptions
public int ScriptExecutionMaxThreadCount { get; set; } = 32;
///
- /// WP3.1 (finding #4): maximum number of trigger-expression evaluations allowed to run
+ /// Maximum number of trigger-expression evaluations allowed to run
/// concurrently across the whole process. Trigger expressions are non-blocking by
/// construction ( exposes only reads over an
/// in-memory snapshot, and the script trust gate has already denied I/O, network, and
@@ -75,7 +75,7 @@ public class SiteRuntimeOptions
public int TriggerEvalMaxConcurrency { get; set; } = Math.Max(2, Environment.ProcessorCount);
///
- /// WP3.1: timeout (seconds) for a single trigger-expression evaluation. Previously
+ /// Timeout (seconds) for a single trigger-expression evaluation. Previously
/// hardcoded at 2 s in both ScriptActor and AlarmActor. The deadline is now
/// armed when the evaluation is ENQUEUED, not when it is dequeued, so time spent waiting
/// on burns the same budget — a saturated gate
@@ -85,7 +85,7 @@ public class SiteRuntimeOptions
public int TriggerEvalTimeoutSeconds { get; set; } = 2;
///
- /// WP3.1: maximum number of concurrent runs (queued or executing) for any single script
+ /// Maximum number of concurrent runs (queued or executing) for any single script
/// or alarm on-trigger script. A trigger that arrives while the cap is reached is SHED —
/// the newest run is refused, the four already in flight (which are closest to their own
/// deadlines) are kept, and no reordering occurs. A shed increments
@@ -125,7 +125,7 @@ public class SiteRuntimeOptions
///
/// Ceiling (ms) of the tag-subscribe retry backoff. Must be >=
/// ; equal makes the interval fixed
- /// (pre-Lever-1 behaviour). Default: 60000ms.
+ /// (the original fixed-interval behaviour). Default: 60000ms.
///
public int TagSubscribeRetryMaxIntervalMs { get; set; } = 60000;
@@ -160,7 +160,7 @@ public class SiteRuntimeOptions
public int StuckScriptGraceMs { get; set; } = 30000;
///
- /// WP2.6d (arch-review misc — site stream alarm-vs-attribute shared buffer):
+ /// Site stream alarm-vs-attribute shared buffer:
/// capacity of the bounded hand-off queue feeding 's
/// dedicated alarm publish path. Alarm state changes are staged through this
/// queue into their OWN broadcast source — separate from the (far higher-volume)
@@ -173,7 +173,7 @@ public class SiteRuntimeOptions
public int AlarmPublishQueueCapacity { get; set; } = 2000;
///
- /// WP3.2 (site_events volume policy — docs/plans/2026-08-15-site-events-policy-design.md):
+ /// Site_events volume policy (docs/plans/2026-08-15-site-events-policy-design.md):
/// global switch restoring the legacy per-run Started/Completed Info site events for
/// EVERY instance script run. Off by default: those two rows per run were the dominant
/// site_events writer (~12 physical row-writes across a replicated pair per run,
@@ -188,7 +188,7 @@ public class SiteRuntimeOptions
public bool PerRunScriptEvents { get; set; }
///
- /// WP3.2: per-script opt-in restoring the legacy per-run Started/Completed Info site
+ /// Per-script opt-in restoring the legacy per-run Started/Completed Info site
/// events for NAMED scripts only, for live debugging without flipping the global
/// switch (and therefore without paying its volume
/// cost for every other script). Effective only while the global switch is off.
@@ -201,9 +201,9 @@ public class SiteRuntimeOptions
public List PerRunScriptEventScripts { get; set; } = new();
///
- /// WP3.2: flush interval (seconds) for 's
+ /// Flush interval (seconds) for 's
/// interval aggregate — one "script" Info site event per interval summarizing every
- /// instance script run since the last flush (headline totals + a top-50-scripts
+ /// instance script run since the last flush (headline totals + a top 50 scripts
/// breakdown, capped so a pathological deployment cannot mint an oversized row). An
/// interval with zero activity emits no row. 0 disables the summary flush
/// entirely (the recorder still accumulates counters; they are just never emitted or
@@ -229,7 +229,7 @@ public class SiteRuntimeOptions
/// central believes it replaced).
///
/// The sweep is silent when there is nothing to repair. 0 disables it entirely
- /// (the row-67 EnsureInstanceStartedCommand path is unaffected). Default: 60.
+ /// (the EnsureInstanceStartedCommand path is unaffected). Default: 60.
///
public int InstanceSweepIntervalSeconds { get; set; } = 60;
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Streaming/SiteStreamAlarmDropReporter.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Streaming/SiteStreamAlarmDropReporter.cs
index f3784c50..6f0dafad 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Streaming/SiteStreamAlarmDropReporter.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Streaming/SiteStreamAlarmDropReporter.cs
@@ -5,7 +5,7 @@ using ZB.MOM.WW.ScadaBridge.HealthMonitoring;
namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Streaming;
///
-/// WP2.6d (arch-review misc — site stream alarm-vs-attribute shared buffer): site-side
+/// Site-side
/// hosted service that periodically reads
/// and pushes it into so
/// the next carries a fresh snapshot on
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Streaming/SiteStreamManager.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Streaming/SiteStreamManager.cs
index 5a91247b..8b4760fd 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Streaming/SiteStreamManager.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Streaming/SiteStreamManager.cs
@@ -20,8 +20,7 @@ namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Streaming;
/// to instance events without referencing SiteRuntime directly.
///
///
-/// WP2.6d (arch-review misc — site stream alarm-vs-attribute shared buffer): attribute
-/// and alarm events used to share ONE upstream Source.ActorRef + BroadcastHub
+/// Attribute and alarm events used to share ONE upstream Source.ActorRef + BroadcastHub
/// pair, so a burst of attribute changes could fill that shared buffer and evict a
/// pending alarm transition before any subscriber ever saw it. Attribute and alarm
/// events now travel through entirely SEPARATE publish sources/hubs
@@ -48,7 +47,7 @@ public class SiteStreamManager : ISiteStreamSubscriber
// Attribute-only publish source/hub. By far the higher-volume of the two —
// deliberately kept on Source.ActorRef's built-in bounded-mailbox + DropHead
- // behavior (unchanged from before WP2.6d); losing an occasional attribute update
+ // behavior (unchanged from before the alarm hub split); losing an occasional attribute update
// under a storm is the accepted, pre-existing trade-off.
private IActorRef? _attributeSourceActor;
private Source? _attributeHubSource;
@@ -68,13 +67,13 @@ public class SiteStreamManager : ISiteStreamSubscriber
///
/// Cumulative count of alarm state changes dropped because
- /// was at capacity (WP2.6d) — i.e. the site-wide
+ /// was at capacity — i.e. the site-wide
/// alarm hand-off queue itself fell behind, not the downstream per-subscriber
/// buffers. Surfaced on the site health report.
///
public long AlarmPublishDroppedCount => Interlocked.Read(ref _alarmPublishDroppedCount);
- // Per-source subscriber counts (WP2.6d "skip publish at zero subscribers").
+ // Per-source subscriber counts (skip publish at zero subscribers).
// A per-instance Subscribe touches BOTH sources (it needs both attribute and alarm
// events for that instance); SubscribeSiteAlarms touches only the alarm source.
private int _attributeSubscriberCount;
@@ -159,6 +158,9 @@ public class SiteStreamManager : ISiteStreamSubscriber
/// wiring can be verified deterministically, without racing the async pump/actor
/// system against a burst of publishes.
///
+ /// Maximum number of queued alarm events before the oldest is evicted.
+ /// Callback invoked on every eviction.
+ /// The bounded alarm hand-off channel.
internal static Channel CreateAlarmPublishQueue(int capacity, Action onDropped) =>
Channel.CreateBounded(
new BoundedChannelOptions(Math.Max(1, capacity))
@@ -172,7 +174,7 @@ public class SiteStreamManager : ISiteStreamSubscriber
///
/// Publishes an attribute value change to the attribute broadcast hub.
/// Fire-and-forget — never blocks the calling actor. Skipped entirely when no
- /// subscriber is currently interested in attribute events (WP2.6d).
+ /// subscriber is currently interested in attribute events.
///
/// The attribute value change event to publish.
public void PublishAttributeValueChanged(AttributeValueChanged changed)
@@ -188,7 +190,7 @@ public class SiteStreamManager : ISiteStreamSubscriber
/// from the (far higher-volume) attribute path, so an attribute storm can never
/// evict a pending alarm transition. Fire-and-forget — never blocks the calling
/// actor. Skipped entirely when no subscriber is currently interested in alarm
- /// events (WP2.6d).
+ /// events.
///
/// The alarm state change event to publish.
public void PublishAlarmStateChanged(AlarmStateChanged changed)
@@ -247,17 +249,7 @@ public class SiteStreamManager : ISiteStreamSubscriber
return subscriptionId;
}
- ///
- /// Subscribe to ALARM events for ALL instances on the site (no per-instance
- /// filter). The dedicated alarm hub carries only
- /// events (the Where below is a defensive no-op, not a load-bearing filter —
- /// see the type-level remarks); the aggregated Alarm Summary never sees attribute
- /// events. Same broadcast-hub wiring as , and the returned
- /// subscription id is torn down via exactly like the
- /// per-instance variant.
- ///
- /// The actor that receives forwarded events.
- /// A subscription id to pass to .
+ ///
public string SubscribeSiteAlarms(IActorRef subscriber)
{
if (_alarmHubSource is null || _materializer is null)
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Tracking/OperationTrackingOptions.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Tracking/OperationTrackingOptions.cs
index 76979eeb..8f1f0e40 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Tracking/OperationTrackingOptions.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Tracking/OperationTrackingOptions.cs
@@ -10,7 +10,7 @@ public class OperationTrackingOptions
/// MIGRATION-ONLY as of LocalDb Phase 1. no longer
/// reads this: the tracking table lives in the consolidated site database at
/// LocalDb:Path. The only remaining consumer is
- /// SiteLocalDbLegacyMigrator, which uses it to locate a pre-Phase-1
+ /// SiteLocalDbLegacyMigrator, which uses it to locate a pre-LocalDb
/// site-tracking.db and copy it in once. Safe to delete from a config once that
/// node's migration has run.
///
diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Tracking/OperationTrackingStore.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Tracking/OperationTrackingStore.cs
index 7a9ec2c2..00563aca 100644
--- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Tracking/OperationTrackingStore.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Tracking/OperationTrackingStore.cs
@@ -278,6 +278,8 @@ public class OperationTrackingStore : IOperationTrackingStore, IAsyncDisposable,
/// take this barrier implicitly; it is public for callers that go around the
/// store to the file (tests, tooling) and for anyone who wants "durable now".
///
+ /// Token to cancel the wait.
+ /// A task that represents the asynchronous operation.
public Task FlushAsync(CancellationToken ct = default) => WaitForQueuedWritesAsync(ct);
/// Queues a lifecycle write and returns once it is queued (not applied).
diff --git a/src/ZB.MOM.WW.ScadaBridge.StoreAndForward/ServiceCollectionExtensions.cs b/src/ZB.MOM.WW.ScadaBridge.StoreAndForward/ServiceCollectionExtensions.cs
index 314b5522..5486468a 100644
--- a/src/ZB.MOM.WW.ScadaBridge.StoreAndForward/ServiceCollectionExtensions.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.StoreAndForward/ServiceCollectionExtensions.cs
@@ -18,7 +18,7 @@ public static class ServiceCollectionExtensions
{
// The buffer now lives in the consolidated LocalDb database at LocalDb:Path, not
// at StoreAndForwardOptions.SqliteDbPath — that option survives only as the
- // migrator's source location (Task 8).
+ // migrator's source location.
services.AddSingleton(sp => new StoreAndForwardStorage(
sp.GetRequiredService(),
sp.GetRequiredService>()));
diff --git a/src/ZB.MOM.WW.ScadaBridge.StoreAndForward/StoreAndForwardOptions.cs b/src/ZB.MOM.WW.ScadaBridge.StoreAndForward/StoreAndForwardOptions.cs
index f91c8335..21f5e9fc 100644
--- a/src/ZB.MOM.WW.ScadaBridge.StoreAndForward/StoreAndForwardOptions.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.StoreAndForward/StoreAndForwardOptions.cs
@@ -8,8 +8,8 @@ public class StoreAndForwardOptions
///
/// Path to the legacy standalone store-and-forward SQLite file. Migration-only.
/// The buffer itself lives in the consolidated LocalDb database (LocalDb:Path)
- /// as of LocalDb Phase 2; this path is read once at boot by
- /// SiteLocalDbLegacyMigrator to drain a pre-Phase-2 file, and is otherwise
+ /// now; this path is read once at boot by
+ /// SiteLocalDbLegacyMigrator to drain a pre-consolidation file, and is otherwise
/// unused. A node that has already migrated may leave it set or unset.
///
public string SqliteDbPath { get; set; } = "./data/store-and-forward.db";
@@ -40,7 +40,7 @@ public class StoreAndForwardOptions
public int SweepTargetParallelism { get; set; } = 4;
///
- /// WP2.6c (arch-review misc — unbounded S&F observer queue): capacity of the
+ /// Capacity of the
/// cached-call audit-observer pump's queue. It was the one unbounded
/// Channel<T> left in the system — a slow/stuck
/// ICachedCallLifecycleObserver (a SQLite audit write) could not stretch
diff --git a/src/ZB.MOM.WW.ScadaBridge.StoreAndForward/StoreAndForwardSchema.cs b/src/ZB.MOM.WW.ScadaBridge.StoreAndForward/StoreAndForwardSchema.cs
index cc533206..8f9ab17b 100644
--- a/src/ZB.MOM.WW.ScadaBridge.StoreAndForward/StoreAndForwardSchema.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.StoreAndForward/StoreAndForwardSchema.cs
@@ -102,7 +102,7 @@ public static class StoreAndForwardSchema
// sort/scan step to satisfy that ordering on a large due-sweep. This index's
// leading (status, created_at) column order lets the planner walk matching
// rows already in created_at order and stop at LIMIT without touching
- // non-pending rows or re-sorting (arch-review WP1.4). idx_sf_messages_status_due
+ // non-pending rows or re-sorting. idx_sf_messages_status_due
// is left in place — it still backs status+due-time lookups that don't order by
// created_at.
using (var orderIndex = connection.CreateCommand())
diff --git a/src/ZB.MOM.WW.ScadaBridge.StoreAndForward/StoreAndForwardService.cs b/src/ZB.MOM.WW.ScadaBridge.StoreAndForward/StoreAndForwardService.cs
index 97adcbae..f2ea0a85 100644
--- a/src/ZB.MOM.WW.ScadaBridge.StoreAndForward/StoreAndForwardService.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.StoreAndForward/StoreAndForwardService.cs
@@ -123,7 +123,7 @@ public class StoreAndForwardService
/// channel). Before starts the pump, posts fall back
/// to inline processing (see ).
///
- /// WP2.6c: bounded (,
+ /// Bounded (,
/// default 10,000) with — this was
/// the one unbounded channel left in the system; a pump that falls behind (a stuck
/// observer) now sheds the oldest unprocessed notification instead of growing
@@ -146,7 +146,7 @@ public class StoreAndForwardService
///
/// Cumulative count of cached-call audit-observer notifications dropped because
- /// was at capacity (WP2.6c). Not reset across
+ /// was at capacity. Not reset across
/// / cycles — a diagnostic total for
/// the lifetime of this service instance. Incremented unconditionally on every drop
/// by , independent of that method's log throttling.
@@ -486,7 +486,7 @@ public class StoreAndForwardService
// StopAsync completes the channel, so a restarted instance needs a fresh
// one. The pump is best-effort: an observer that throws is logged and
// swallowed so a failing audit pipeline never corrupts retry bookkeeping.
- // WP2.6c: bounded + DropOldest, sized from options; a drop increments
+ // Bounded + DropOldest, sized from options; a drop increments
// _observerQueueDroppedCount (surfaced via ObserverQueueDroppedCount) and is
// logged at Warning so a stuck observer is visible, not just silently lossy.
// F3: logging itself is rate-limited by LogObserverQueueDrop (first-drop Warning
diff --git a/src/ZB.MOM.WW.ScadaBridge.Transport/Import/ArtifactDiff.cs b/src/ZB.MOM.WW.ScadaBridge.Transport/Import/ArtifactDiff.cs
index a62817af..8702317a 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Transport/Import/ArtifactDiff.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Transport/Import/ArtifactDiff.cs
@@ -170,7 +170,7 @@ public sealed class ArtifactDiff
// previewed as "Identical". Mirrors CompareDatabaseConnection below.
AddIfDifferent(changes, "MaxRetries", existing.MaxRetries, incoming.MaxRetries);
AddIfDifferent(changes, "RetryDelay", existing.RetryDelay.ToString(), incoming.RetryDelay.ToString());
- // arch-review 2026-08-19 P7-1 (row-27 shape): TimeoutSeconds is operative at
+ // TimeoutSeconds is operative at
// runtime and the importer has always APPLIED it, but the diff omitted it — same
// "silently changed and invisible to preview" defect as the MaxRetries fix above.
// A null incoming value (bundle written before this field existed) compares as
diff --git a/src/ZB.MOM.WW.ScadaBridge.Transport/Import/BundleImporter.cs b/src/ZB.MOM.WW.ScadaBridge.Transport/Import/BundleImporter.cs
index ebefc4ae..9f7438cc 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Transport/Import/BundleImporter.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Transport/Import/BundleImporter.cs
@@ -1764,17 +1764,6 @@ public sealed class BundleImporter : IBundleImporter
}
///
- ///
- /// Register row 43: every abandonment path (Central UI wizard reset /
- /// back-to-upload / circuit teardown, CLI preview, CLI import that aborts
- /// before Apply) funnels here so a session that will never be applied stops
- /// holding one of the MaxConcurrentImportSessions slots — and stops
- /// holding its decrypted plaintext — for the rest of its TTL. Applies the same
- /// zero-then-remove ordering as the T-007 release inside
- /// : zeroing first means a caller still holding the
- /// session reference (the Razor page keeps one for the Result step) observes
- /// the cleared buffer too.
- ///
public void Abandon(Guid sessionId)
{
// Get() returns null for an unknown or already-expired id, which makes the
diff --git a/src/ZB.MOM.WW.ScadaBridge.Transport/Import/BundleSessionEvictionService.cs b/src/ZB.MOM.WW.ScadaBridge.Transport/Import/BundleSessionEvictionService.cs
index 030f18e7..0648b1e1 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Transport/Import/BundleSessionEvictionService.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Transport/Import/BundleSessionEvictionService.cs
@@ -58,28 +58,22 @@ internal sealed class BundleSessionEvictionService : BackgroundService
_openSessionCountProvider = () => _sessionStore.OpenSessionCount;
}
- ///
- /// Register row 53: registers the open-import-sessions gauge provider before the
- /// sweep loop starts. This service is the Transport component's only lifecycle seam
- /// (the session store itself is a plain singleton), and it is registered by
- /// AddTransport, which only the Central branch of the host calls — so the
- /// gauge reports 0 on a site node, where there are no import sessions to count.
- ///
- /// Host startup cancellation token.
- /// A task that completes when the base has started.
+ // Registers the open-import-sessions gauge provider before the
+ // sweep loop starts. This service is the Transport component's only lifecycle seam
+ // (the session store itself is a plain singleton), and it is registered by
+ // AddTransport, which only the Central branch of the host calls — so the
+ // gauge reports 0 on a site node, where there are no import sessions to count.
+ ///
public override Task StartAsync(CancellationToken cancellationToken)
{
ScadaBridgeTelemetry.SetImportSessionCountProvider(_openSessionCountProvider);
return base.StartAsync(cancellationToken);
}
- ///
- /// Deregisters this instance's gauge provider on graceful stop so the gauge falls
- /// back to 0 rather than reading through a dead store. The clear is identity-checked,
- /// so a late stop cannot clobber a successor that already re-registered.
- ///
- /// Host shutdown cancellation token.
- /// A task that completes when the base has stopped.
+ // Deregisters this instance's gauge provider on graceful stop so the gauge falls
+ // back to 0 rather than reading through a dead store. The clear is identity-checked,
+ // so a late stop cannot clobber a successor that already re-registered.
+ ///
public override async Task StopAsync(CancellationToken cancellationToken)
{
ScadaBridgeTelemetry.ClearImportSessionCountProvider(_openSessionCountProvider);
diff --git a/src/ZB.MOM.WW.ScadaBridge.Transport/Serialization/EntityDtos.cs b/src/ZB.MOM.WW.ScadaBridge.Transport/Serialization/EntityDtos.cs
index 9f308a00..8a77c043 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Transport/Serialization/EntityDtos.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Transport/Serialization/EntityDtos.cs
@@ -256,7 +256,7 @@ public sealed record ExternalSystemDto(
TimeSpan RetryDelay,
IReadOnlyList Methods,
SecretsBlock? Secrets,
- // Per-system HTTP round-trip timeout (arch-review 2026-08-19 P7-1, row-27 shape).
+ // Per-system HTTP round-trip timeout (arch-review 2026-08-19).
// Additive trailing field; null distinguishes a bundle written before this field
// existed from a bundle that explicitly carries the entity's own "0 = unset"
// sentinel. BundleImporter treats a null incoming value as "not specified by this