docs(src): fix CommentChecker findings across src/ — 1,032 → 5 (all false positives)

Comment/XML-doc changes only, no code logic:
- strip project-tracking references (WP*, P*-*, row-NN, #NN, Task N) from
  comments, keeping the explanatory rationale
- add missing summaries, <param>, <returns>, <typeparam> and <inheritdoc/>
- replace misused <inheritdoc/> with real docs; where <inheritdoc/> was
  combined with useful remarks, move the rationale into // comments
- drop stale replicationActor <param> on DeploymentManagerActor ctor

Remaining 5 are false positives (CWE-1236 x2, failures-1, year-0001, M365).
Build 0 errors / 0 warnings.
This commit is contained in:
Joseph Doherty
2026-10-07 14:12:25 -04:00
parent 3a217b9484
commit 0ffd4bdbea
217 changed files with 1224 additions and 695 deletions
@@ -292,6 +292,7 @@ public interface IAuditLogRepository
/// </remarks>
/// <param name="executionId">Any execution id in the chain; the implementation walks to the root and back down.</param>
/// <param name="ct">Cancellation token.</param>
/// <param name="permittedSiteIds">Optional site scope restricting which sites' rows are visible; <c>null</c> means unrestricted.</param>
/// <returns>A task that resolves to the full execution tree rooted at the topmost ancestor, one node per distinct execution.</returns>
Task<IReadOnlyList<ExecutionTreeNode>> GetExecutionTreeAsync(
Guid executionId,
@@ -307,6 +308,7 @@ public interface IAuditLogRepository
/// </summary>
/// <param name="executionId">Any execution id in the chain.</param>
/// <param name="ct">Cancellation token.</param>
/// <param name="permittedSiteIds">Optional site scope restricting which sites' rows are visible; <c>null</c> means unrestricted.</param>
/// <returns>The canonical audit events that make up the flow, oldest first.</returns>
Task<IReadOnlyList<AuditEvent>> GetTraceEventsAsync(
Guid executionId,
@@ -93,7 +93,7 @@ public interface INotificationOutboxRepository
/// is its own transaction.
/// </summary>
/// <remarks>
/// <b>Why this exists (arch-review 2026-08-19 P5-4).</b> The operator retry/
/// <b>Why this exists.</b> The operator retry/
/// discard handlers are read-check-write across two round trips:
/// <see cref="GetByIdAsync"/> reads the row, the handler checks its
/// in-memory <c>Status</c>, then a write persists the transition. A detached
@@ -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 <c>Rejected</c> while the plant
/// write physically executed (arch-review 2026-08-19 P6-1). The conditional
/// write physically executed. The conditional
/// <c>WHERE Status='Pending'</c> makes exactly one of approve/reject/expiry win.
/// The loser observes <c>false</c> and must record nothing — no status change and no
/// reject audit row.
@@ -252,6 +252,12 @@ public interface ITemplateEngineRepository
Task<IReadOnlyList<Instance>> GetInstancesBySiteIdAsync(int siteId, CancellationToken cancellationToken = default);
/// <summary>Reads one bounded, untracked page of topology instance rows for a site/area.</summary>
/// <param name="siteId">The site ID.</param>
/// <param name="areaId">The area ID to filter by, or null for every area.</param>
/// <param name="skip">The number of rows to skip.</param>
/// <param name="take">The maximum number of rows to return.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task that resolves to a read-only page of instances.</returns>
Task<IReadOnlyList<Instance>> GetTopologyInstancesAsync(int siteId, int? areaId, int skip, int take, CancellationToken cancellationToken = default);
/// <summary>Retrieves an instance by unique name.</summary>
@@ -4,6 +4,8 @@ namespace ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
public interface IConfigurationCommandTransaction
{
/// <summary>Each database retry supplies a fresh scope; the operation must not perform external side effects.</summary>
/// <param name="operation">The database-only operation to run within the transaction.</param>
/// <returns>The operation's result, if any.</returns>
Task<object?> ExecuteAsync(Func<IServiceProvider, Task<object?>> operation);
}
@@ -11,6 +13,7 @@ public interface IConfigurationCommandTransaction
public interface IConfigurationCommitActions
{
/// <summary>Queues an idempotent local maintenance action.</summary>
/// <param name="action">The action to run after a successful commit.</param>
void Add(Func<Task> action);
}
@@ -2,5 +2,9 @@ namespace ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
/// <summary>Private worker handoff for an existing configured SQL connection. Never log or expose the returned secret.</summary>
public interface ISandboxDatabaseConnectionSource
{
/// <summary>Resolves the connection string for a configured connection.</summary>
/// <param name="connectionName">The configured database connection name.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>The connection string; a secret that must never be logged or exposed.</returns>
Task<string> GetSandboxConnectionStringAsync(string connectionName, CancellationToken cancellationToken);
}
@@ -110,7 +110,7 @@ public interface ISiteAuditQueue
/// <param name="sinceUtc">Lower bound timestamp (UTC).</param>
/// <param name="batchSize">Maximum number of rows to return.</param>
/// <param name="afterId">
/// Composite-keyset tiebreak cursor (WP2.3), mirroring
/// Composite-keyset tiebreak cursor, mirroring
/// <see cref="IOperationTrackingStore.ReadChangedSinceAsync"/>: when non-null it is the
/// <see cref="AuditEvent.EventId"/> ("D" GUID form) of the last row already consumed at
/// <paramref name="sinceUtc"/>, and only rows strictly after the composite
@@ -125,7 +125,7 @@ public interface ISiteAuditQueue
DateTime sinceUtc, int batchSize, string? afterId = null, CancellationToken ct = default);
/// <summary>
/// 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 <c>(<paramref name="sinceUtc"/>, <paramref name="afterId"/>)</c>
/// from <see cref="ZB.MOM.WW.ScadaBridge.Commons.Types.Enums.AuditForwardState.Pending"/>/
/// <see cref="ZB.MOM.WW.ScadaBridge.Commons.Types.Enums.AuditForwardState.Forwarded"/> to
@@ -231,6 +231,12 @@ public interface ISiteAuditQueue
/// <c>(OccurredAtUtc, EventId)</c>, at most <paramref name="limit"/> rows. Independent of
/// forwarding state — moves no cursor and flips nothing. At most 64 ids per call.
/// </summary>
/// <param name="executionIds">Execution ids to match against <c>ExecutionId</c> or <c>ParentExecutionId</c>.</param>
/// <param name="sinceUtc">Lower bound on <c>OccurredAtUtc</c> (UTC).</param>
/// <param name="limit">Maximum number of rows to return.</param>
/// <param name="ct">Cancellation token.</param>
/// <param name="afterEventId">Optional <c>EventId</c> keyset cursor for paging past rows already consumed.</param>
/// <returns>A task that resolves to the matching audit events ordered by <c>(OccurredAtUtc, EventId)</c>.</returns>
Task<IReadOnlyList<AuditEvent>> ReadByExecutionIdsAsync(
IReadOnlyCollection<Guid> executionIds, DateTime sinceUtc, int limit, CancellationToken ct = default, string? afterEventId = null);
}
@@ -7,7 +7,7 @@ namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.DebugView;
/// <param name="CorrelationId">Correlation id echoed on the reply.</param>
/// <param name="AlarmsOnly">
/// When <c>true</c> the site builds ONLY the alarm half of the snapshot and returns an
/// empty <see cref="DebugViewSnapshot.AttributeValues"/> list (wire efficiency, WP2.3).
/// empty <see cref="DebugViewSnapshot.AttributeValues"/> list (wire efficiency).
/// Set by the central per-site live alarm cache, whose seed/reconcile fan-out discards
/// every attribute row anyway. Defaults to <c>false</c> so the Debug View and every other
/// caller keep the full snapshot; additive on the wire
@@ -2,7 +2,7 @@ namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Health;
/// <summary>
/// One site node's latest deployed-secrets mirror outcome, as carried on
/// <see cref="SiteHealthReport.SecretMirrorStatuses"/> (review finding M1).
/// <see cref="SiteHealthReport.SecretMirrorStatuses"/>.
/// </summary>
/// <remarks>
/// Every site node runs its own <c>DeployedSecretsMirror</c> and records its current
@@ -75,7 +75,7 @@ public record SiteHealthReport(
public double? ScriptOldestBusyAgeSeconds { get; init; }
/// <summary>
/// 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 <c>ScriptSchedulerStatsReporter</c>.
@@ -85,7 +85,7 @@ public record SiteHealthReport(
public int DetachedScriptThreads { get; init; }
/// <summary>
/// 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
/// <c>MaxConcurrentRunsPerScript</c> runs were already in flight for that script. Raw
/// per-interval count (drained on collect) like <see cref="ScriptErrorCount"/>. 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; }
/// <summary>
/// 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
/// <c>SiteStreamManager</c>'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(
/// </summary>
/// <remarks>
/// <para>
/// Tri-state, and the distinction is the whole point (deferred-work register row 73).
/// Tri-state, and the distinction is the whole point.
/// <b>Null = not reported</b>: a central self-report, where the notion does not apply,
/// or a site node running a pre-row-73 build. <b><see langword="true"/></b> = the
/// or a site node running an older build. <b><see langword="true"/></b> = the
/// reconciler is running. <b><see langword="false"/></b> = the node booted WITHOUT it,
/// because no <c>IDeploymentConfigFetcher</c> was resolvable.
/// </para>
@@ -175,12 +174,12 @@ public record SiteHealthReport(
public bool? StartupReconcilerActive { get; init; }
/// <summary>
/// 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 <c>secret_mirror_status</c>
/// table, including the STANDBY node, whose own health report is never sent.
/// </summary>
/// <remarks>
/// <b>Null = no data</b>: a central self-report, a pre-M1 site build, a site whose
/// <b>Null = no data</b>: 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
/// <see cref="SecretMirrorNodeStatus.Failures"/> list is a healthy mirror. Names and
@@ -10,7 +10,7 @@ namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Instance;
/// <param name="Value">Canonical string form of the new value.</param>
/// <param name="Timestamp">UTC timestamp of the command.</param>
/// <param name="SourceExecutionId">
/// Audit Log #23 (ParentExecutionId tag-cascade): the <c>ExecutionId</c> of the
/// ParentExecutionId tag-cascade: the <c>ExecutionId</c> of the
/// execution issuing this write — a site script run
/// (<c>ScriptRuntimeContext.SetAttribute</c>) or the inbound API request behind a
/// <c>Route.To(...).SetAttributes(...)</c>. Additive and nullable; <c>null</c>
@@ -59,7 +59,7 @@ public static class ScadaBridgeTelemetry
/// <summary>
/// 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.
/// </summary>
private static readonly Counter<long> _liveAlarmStreamReconnects =
Meter.CreateCounter<long>("scadabridge.site.alarm_cache.reconnects", unit: "1",
@@ -67,7 +67,7 @@ public static class ScadaBridgeTelemetry
/// <summary>
/// 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
/// <summary>
/// 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.
/// </summary>
private static readonly Counter<long> _debugPreSnapshotDrops =
@@ -79,7 +79,7 @@ public static class AuditQueryParamParsers
/// the filter dimension stays unconstrained.
/// </summary>
/// <remarks>
/// 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
/// <see cref="Enum.TryParse{TEnum}(string, bool, out TEnum)"/> does not:
/// <list type="number">
@@ -4,8 +4,15 @@ namespace ZB.MOM.WW.ScadaBridge.Commons.Types.Audit;
/// <summary>Bounded replay data with independently retained chain-root and truncation metadata.</summary>
public sealed record TraceEventBatch(Guid RootExecutionId, IReadOnlyList<AuditEvent> Events, bool Truncated) : IReadOnlyList<AuditEvent>
{
/// <summary>Gets the trace event at the given position.</summary>
/// <param name="index">Zero-based position in <see cref="Events"/>.</param>
/// <returns>The event at <paramref name="index"/>.</returns>
public AuditEvent this[int index] => Events[index];
/// <summary>Gets the number of events in the batch.</summary>
public int Count => Events.Count;
/// <summary>Returns an enumerator over the batch's events.</summary>
/// <returns>An enumerator over <see cref="Events"/>.</returns>
public IEnumerator<AuditEvent> GetEnumerator() => Events.GetEnumerator();
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
@@ -25,6 +25,8 @@ public static class OpcUaReferenceForm
/// prefix, or empty/whitespace (nothing to warn about). False only for a bare
/// server-namespace index (<c>ns=&lt;n&gt;;…</c> with n ≥ 1).
/// </summary>
/// <param name="reference">The OPC UA node reference string to classify.</param>
/// <returns><see langword="true"/> when the reference is index-durable; otherwise <see langword="false"/>.</returns>
public static bool IsDurable(string? reference)
{
if (string.IsNullOrWhiteSpace(reference))
@@ -1,7 +1,7 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Types.Debugging;
/// <summary>
/// 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 <c>Exception.ToString()</c> opens with
/// <c>Namespace.Type: message</c>, and a wrapper (an <c>AggregateException</c>, a rethrow)
/// chains its inner exceptions on that same first line with <c>" ---&gt; "</c>.
@@ -59,6 +59,10 @@ public static class FlowGraphBuilder
/// Builds the graph. <paramref name="truncated"/> and <paramref name="logsTruncated"/> are passed
/// through to <see cref="FlowGraph.Truncated"/> / <see cref="FlowGraph.LogsTruncated"/>.
/// </summary>
/// <param name="events">The audit events to project into the graph.</param>
/// <param name="truncated">Whether the event set was truncated.</param>
/// <param name="logsTruncated">Whether the log rows were truncated.</param>
/// <returns>The built flow graph.</returns>
public static FlowGraph Build(IReadOnlyList<AuditEvent> 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 (<c>ApiCall</c> / <c>DbWrite</c>), the same rule the
/// projector uses for the <c>sync</c> / <c>cached</c> subtitle. Such steps hang off the chain on an
/// <see cref="FlowEdge.Async"/> edge (D4).
/// <see cref="FlowEdge.Async"/> edge.
/// </summary>
/// <param name="step">The step to classify.</param>
/// <returns>True when the step is not awaited by the script.</returns>
public static bool IsAsync(FlowStep step)
{
if (step.Kind == FlowStepKind.Notify) return true;
@@ -352,9 +358,12 @@ public static class FlowGraphBuilder
/// <summary>
/// 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.
/// </summary>
/// <param name="kind">The audit row kind.</param>
/// <param name="status">The audit row status.</param>
/// <returns>The rank used to order the rows.</returns>
public static int LifecycleRank(string? kind, string? status)
{
var k = kind switch
@@ -444,6 +453,8 @@ public static class FlowGraphBuilder
}
/// <summary>Display rank used to break timestamp ties: spawners before what they spawn.</summary>
/// <param name="kind">The step kind.</param>
/// <returns>The display rank for the kind.</returns>
public static int KindRank(FlowStepKind kind) => kind switch
{
FlowStepKind.InboundRequest => 0,
@@ -14,12 +14,18 @@ namespace ZB.MOM.WW.ScadaBridge.Commons.Types.Debugging;
public static class FlowStepProjector
{
/// <summary>Projects an audit event onto a flow step.</summary>
/// <param name="evt">The audit event.</param>
/// <returns>The projected flow step.</returns>
public static FlowStep Project(AuditEvent evt)
{
ArgumentNullException.ThrowIfNull(evt);
return Project(evt, AuditDetailsCodec.Deserialize(evt.DetailsJson));
}
/// <summary>Projects an audit event and its already-deserialized details onto a flow step.</summary>
/// <param name="evt">The audit event.</param>
/// <param name="d">The deserialized audit details of <paramref name="evt"/>.</param>
/// <returns>The projected flow step.</returns>
internal static FlowStep Project(AuditEvent evt, AuditDetails d)
{
var extra = ScriptTraceExtra.TryParse(d.Extra);
@@ -52,6 +58,8 @@ public static class FlowStepProjector
}
/// <summary>True for a <c>ScriptTrace.ScriptLog</c> row — a log line, never a step.</summary>
/// <param name="evt">The audit event.</param>
/// <returns><see langword="true"/> when the event is a script log line.</returns>
public static bool IsLogLine(AuditEvent evt)
{
ArgumentNullException.ThrowIfNull(evt);
@@ -59,6 +67,9 @@ public static class FlowStepProjector
return IsLogLine(d);
}
/// <summary>True when the details describe a <c>ScriptTrace.ScriptLog</c> row.</summary>
/// <param name="d">The deserialized audit details of the event.</param>
/// <returns><see langword="true"/> when the details describe a script log line.</returns>
internal static bool IsLogLine(AuditDetails d)
{
return string.Equals(d.Channel, nameof(AuditChannel.ScriptTrace), StringComparison.Ordinal)
@@ -66,12 +77,18 @@ public static class FlowStepProjector
}
/// <summary>Projects a <c>ScriptLog</c> row onto its line. Level/source default to <c>info</c>/<c>script</c> when the extra is missing.</summary>
/// <param name="evt">The audit event.</param>
/// <returns>The projected log line.</returns>
public static FlowLogLine ToLogLine(AuditEvent evt)
{
ArgumentNullException.ThrowIfNull(evt);
return ToLogLine(evt, AuditDetailsCodec.Deserialize(evt.DetailsJson));
}
/// <summary>Projects a <c>ScriptLog</c> row and its already-deserialized details onto its line.</summary>
/// <param name="evt">The audit event.</param>
/// <param name="d">The deserialized audit details of <paramref name="evt"/>.</param>
/// <returns>The projected log line.</returns>
internal static FlowLogLine ToLogLine(AuditEvent evt, AuditDetails d)
{
var x = ScriptTraceExtra.TryParse(d.Extra);
@@ -89,12 +106,18 @@ public static class FlowStepProjector
}
/// <summary>Projects one outbound audit row onto its lifecycle entry (<see cref="FlowStep.Lifecycle"/>).</summary>
/// <param name="evt">The audit event.</param>
/// <returns>The projected lifecycle row.</returns>
public static FlowLifecycleRow ToLifecycleRow(AuditEvent evt)
{
ArgumentNullException.ThrowIfNull(evt);
return ToLifecycleRow(evt, AuditDetailsCodec.Deserialize(evt.DetailsJson));
}
/// <summary>Projects one outbound audit row and its already-deserialized details onto its lifecycle entry.</summary>
/// <param name="evt">The audit event.</param>
/// <param name="d">The deserialized audit details of <paramref name="evt"/>.</param>
/// <returns>The projected lifecycle row.</returns>
internal static FlowLifecycleRow ToLifecycleRow(AuditEvent evt, AuditDetails d)
{
return new FlowLifecycleRow(
@@ -109,6 +132,9 @@ public static class FlowStepProjector
}
/// <summary>Maps a channel/kind string pair onto a step kind.</summary>
/// <param name="channel">The audit channel name.</param>
/// <param name="kind">The audit kind name.</param>
/// <returns>The matching step kind, or <see cref="FlowStepKind.Unknown"/>.</returns>
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
}
/// <summary>Lane rule: route call / inbound / response → Central; script run / wait → Site; write → Tags; outbound API / DB / notify → External.</summary>
/// <param name="kind">The step kind.</param>
/// <param name="sourceSiteId">The originating site id, used to place unknown kinds.</param>
/// <returns>The lane the step is drawn on.</returns>
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
};
/// <summary>True for the three outbound kinds that live on the External lane.</summary>
/// <param name="kind">The step kind.</param>
/// <returns><see langword="true"/> for an API call, DB call or notification.</returns>
public static bool IsOutbound(FlowStepKind kind)
=> kind is FlowStepKind.ApiCall or FlowStepKind.DbCall or FlowStepKind.Notify;
@@ -92,6 +92,9 @@ public sealed class FlowTraceCollector
/// the inline copy is filtered through. A null <paramref name="redactor"/> — a host that
/// never called <c>AddAuditLog</c>, or a test — collects raw but is still capped.
/// </summary>
/// <param name="maxEvents">The maximum number of events kept inline.</param>
/// <param name="maxBytes">The maximum summed size of the events' details, in bytes.</param>
/// <param name="redactor">The redactor applied to the inline copy; null collects raw.</param>
public FlowTraceCollector(
int maxEvents = DefaultMaxEvents,
int maxBytes = DefaultMaxBytes,
@@ -142,6 +145,7 @@ public sealed class FlowTraceCollector
/// where it builds <c>ScriptCallResult.TraceEvents</c>. Idempotent: a second call returns
/// the same contents.
/// </summary>
/// <returns>The collected events in emission order.</returns>
public IReadOnlyList<AuditEvent> Complete()
{
lock (_gate)
@@ -159,6 +163,7 @@ public sealed class FlowTraceCollector
/// test-only peek. A reply path must use <see cref="Complete"/>, otherwise an un-awaited call
/// finishing after the reply could mutate the list the reply was built from.
/// </summary>
/// <returns>A copy of the events collected so far.</returns>
public IReadOnlyList<AuditEvent> Snapshot()
{
lock (_gate) return _events.ToArray();
@@ -68,9 +68,12 @@ public sealed record ScriptTraceExtra
};
/// <summary>Serializes to the compact camelCase JSON stored in <c>AuditDetails.Extra</c>.</summary>
/// <returns>The serialized JSON string.</returns>
public string ToJson() => JsonSerializer.Serialize(this, JsonOptions);
/// <summary>Parses <c>AuditDetails.Extra</c>; returns <c>null</c> for null/blank/invalid JSON — never throws.</summary>
/// <param name="json">The raw <c>AuditDetails.Extra</c> JSON text.</param>
/// <returns>The parsed value, or <c>null</c> when the input is null, blank or invalid.</returns>
public static ScriptTraceExtra? TryParse(string? json)
{
if (string.IsNullOrWhiteSpace(json)) return null;
@@ -2,7 +2,7 @@ namespace ZB.MOM.WW.ScadaBridge.Commons.Types;
/// <summary>
/// Which leg of a reset-aware <c>Attributes.WriteBatchAndWaitAsync</c> handshake a
/// <see cref="HandshakeResult"/> describes (Gitea #36, design 2026-09-10 §4).
/// <see cref="HandshakeResult"/> describes.
/// </summary>
public enum HandshakePhase
{
@@ -14,7 +14,7 @@ public enum HandshakePhase
}
/// <summary>
/// Result of the reset-aware <c>Attributes.WriteBatchAndWaitAsync</c> overload (Gitea #36):
/// Result of the reset-aware <c>Attributes.WriteBatchAndWaitAsync</c> overload:
/// like <see cref="WaitResult"/>, plus the <see cref="Phase"/> 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
@@ -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
/// <see cref="ZB.MOM.WW.ScadaBridge.Commons.Entities.Kpi.KpiSample"/> rows is
/// aggregated into a single <c>KpiRollupHourly</c> row ("KPI History &amp; 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.
/// </summary>
public enum KpiRollupAggregation
@@ -11,6 +11,8 @@ namespace ZB.MOM.WW.ScadaBridge.Commons.Types.Scripts;
public static class ScriptTriggerAttribute
{
/// <summary>True for the trigger kinds the Flow Debugger fires by WRITING the monitored attribute.</summary>
/// <param name="triggerType">The trigger type name.</param>
/// <returns>True when the trigger type is a write kind.</returns>
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.
/// </summary>
/// <param name="triggerType">The trigger type name.</param>
/// <param name="triggerConfigurationJson">The trigger configuration JSON.</param>
/// <param name="attribute">Receives the monitored attribute name, or null.</param>
/// <returns>True when a monitored attribute was read.</returns>
public static bool TryRead(string? triggerType, string? triggerConfigurationJson, out string? attribute)
{
attribute = null;
@@ -20,7 +20,7 @@ public static class SecretReferenceScanner
/// <summary>
/// The single parser for a <c>secret:&lt;name&gt;</c> 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 <c>secret:</c> prefix matches ordinal-ignore-case, and the remainder is
/// trimmed; an empty remainder is not a reference. The config-expander form
/// <c>${secret:…}</c> 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.
/// </summary>
/// <param name="connections">The data connection artifacts to scan.</param>
/// <returns>The distinct referenced secrets, each with the connections that reference it.</returns>
public static IReadOnlyList<SecretReference> Scan(IEnumerable<DataConnectionArtifact> connections)
{
var found = new SortedDictionary<string, SortedSet<string>>(StringComparer.Ordinal);