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:
@@ -11,10 +11,16 @@ public static class AuditWireBudget
|
||||
public const int InitialBytes = 7;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The protobuf message type of the rows.</typeparam>
|
||||
/// <param name="rows">The repeated field to append to.</param>
|
||||
/// <param name="row">The row to append.</param>
|
||||
/// <param name="wireBytes">The running wire size of the batch; increased by the row's size when appended.</param>
|
||||
/// <param name="rowIdentity">Identity of the row, used in the error raised when it exceeds the hard ceiling.</param>
|
||||
/// <returns><c>true</c> when the row was appended; <c>false</c> when it does not fit in the current batch.</returns>
|
||||
public static bool TryAppend<T>(RepeatedField<T> rows, T row, ref int wireBytes, string rowIdentity)
|
||||
where T : IMessage<T>
|
||||
{
|
||||
|
||||
@@ -268,7 +268,7 @@ public sealed class CentralChannelProvider : IDisposable
|
||||
return TimeSpan.FromTicks(scaled >= cap || scaled < 0 ? cap : scaled);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>Stops the failback timer and disposes all cached gRPC channels.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
lock (_gate)
|
||||
|
||||
@@ -74,7 +74,7 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc;
|
||||
public static class CentralControlDtoMapper
|
||||
{
|
||||
// -----------------------------------------------------------------------
|
||||
// Notification Outbox (#21)
|
||||
// Notification Outbox
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
/// <summary>Projects a <see cref="NotificationSubmit"/> onto its wire DTO.</summary>
|
||||
@@ -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
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
@@ -437,7 +437,7 @@ public static class CentralControlDtoMapper
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Health Monitoring (#11)
|
||||
// Health Monitoring
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
/// <summary>Projects a <see cref="SiteHealthReport"/> onto its wire DTO.</summary>
|
||||
@@ -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();
|
||||
|
||||
@@ -277,21 +277,15 @@ public sealed class CentralControlGrpcService : CentralControlService.CentralCon
|
||||
return CentralControlDtoMapper.ToDto(ack);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Application heartbeat — <b>always answers OK</b>, even when this node is not ready.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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 <see cref="RequireReady"/>: a heartbeat arriving
|
||||
/// before the actor exists is logged and dropped, exactly as the actor itself drops one
|
||||
/// that arrives before <c>ICentralHealthAggregator</c> is resolvable. Liveness is still
|
||||
/// detected — the aggregator's offline timeout fires when the heartbeats stop landing.
|
||||
/// </remarks>
|
||||
/// <param name="request">The heartbeat.</param>
|
||||
/// <param name="context">The gRPC call context.</param>
|
||||
/// <returns>An empty reply, always.</returns>
|
||||
// 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.
|
||||
/// <inheritdoc />
|
||||
public override Task<Empty> Heartbeat(HeartbeatDto request, ServerCallContext context)
|
||||
{
|
||||
var central = _central;
|
||||
|
||||
@@ -8,14 +8,20 @@ public static class CentralControlSiteIdentity
|
||||
private static readonly object IdentityKey = new();
|
||||
|
||||
/// <summary>Stores an identity only after the site's credential has been verified.</summary>
|
||||
/// <param name="context">The call context to record the identity on.</param>
|
||||
/// <param name="siteId">The verified site identifier.</param>
|
||||
public static void SetVerified(ServerCallContext context, string siteId) => context.UserState[IdentityKey] = siteId;
|
||||
|
||||
/// <summary>Requires a credential-verified identity on the current call.</summary>
|
||||
/// <param name="context">The call context carrying the verified identity.</param>
|
||||
/// <returns>The verified site identifier.</returns>
|
||||
public static string RequireVerified(ServerCallContext context) =>
|
||||
context.UserState.TryGetValue(IdentityKey, out var value) && value is string site && !string.IsNullOrWhiteSpace(site)
|
||||
? site : throw Denied();
|
||||
|
||||
/// <summary>Checks every ownership field before dispatching any part of a central control request.</summary>
|
||||
/// <param name="request">The inbound request whose ownership fields are checked.</param>
|
||||
/// <param name="context">The call context carrying the verified site identity.</param>
|
||||
public static void ValidateRequest(object request, ServerCallContext context)
|
||||
{
|
||||
var site = RequireVerified(context);
|
||||
|
||||
@@ -9,14 +9,20 @@ public static class GrpcRetrySafety
|
||||
public const string DispatchTrailer = "scadabridge-dispatch";
|
||||
|
||||
/// <summary>Creates a readiness rejection that proves no work was dispatched.</summary>
|
||||
/// <param name="detail">Human-readable rejection detail placed on the status.</param>
|
||||
/// <returns>An <see cref="RpcException"/> with <c>Unavailable</c> status and the not-dispatched trailer.</returns>
|
||||
public static RpcException NotDispatched(string detail) => new(
|
||||
new Status(StatusCode.Unavailable, detail), new Metadata { { DispatchTrailer, "not-started" } });
|
||||
|
||||
/// <summary>True only for an explicit rejection from the server before it dispatched work.</summary>
|
||||
/// <param name="error">The RPC failure to inspect.</param>
|
||||
/// <returns><c>true</c> when the trailer proves the server never dispatched the command.</returns>
|
||||
public static bool WasNotDispatched(RpcException error) => error.StatusCode == StatusCode.Unavailable
|
||||
&& error.Trailers.Any(e => e.Key == DispatchTrailer && e.Value == "not-started");
|
||||
|
||||
/// <summary>Reports an ambiguous mutation without inviting a blind replay.</summary>
|
||||
/// <param name="error">The original RPC failure whose trailers are preserved.</param>
|
||||
/// <returns>An <see cref="RpcException"/> with <c>Aborted</c> status marking the outcome as unknown.</returns>
|
||||
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);
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc;
|
||||
/// <para>
|
||||
/// <b>Per-call deadlines match today's Ask timeouts exactly</b> — see <see cref="ResolveDeadline"/>.
|
||||
/// An unknown/unconfigured site now fails fast: it surfaces to the caller as a
|
||||
/// <see cref="Status.Failure"/> immediately (WP1.6) instead of being warned-and-dropped to let the
|
||||
/// <see cref="Status.Failure"/> immediately instead of being warned-and-dropped to let the
|
||||
/// caller's Ask burn its full timeout. A transport fault takes the same <see cref="Status.Failure"/>
|
||||
/// path, which the S&F/audit layers already treat as transient.
|
||||
/// </para>
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/// <summary>Only read-only commands can replay after a response may have been lost.</summary>
|
||||
/// <param name="message">The command message about to be sent.</param>
|
||||
/// <returns><c>true</c> when the message is a read-only command safe to replay; otherwise <c>false</c>.</returns>
|
||||
internal static bool IsRetrySafe(object message) => message is
|
||||
DeploymentStateQueryRequest or BrowseNodeCommand or SearchAddressSpaceCommand or ReadTagValuesCommand
|
||||
or ListServerCertsCommand or EventLogQueryRequest or DebugSnapshotRequest or ParkedMessageQueryRequest
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc;
|
||||
/// Nullable <b>collections</b> are wrapped in a per-collection message, so
|
||||
/// the null/empty distinction becomes a message-presence question. proto3
|
||||
/// <c>repeated</c>/<c>map</c> 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.
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <c>Guid?</c> members ride as a <c>string</c> holding the "D" form, with
|
||||
|
||||
@@ -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
|
||||
/// <c>GrpcNodeAAddress</c> nor <c>GrpcNodeBAddress</c> configured. <see cref="GrpcSiteTransport"/>
|
||||
/// treats this as a known-dead send: it fails the caller's <c>Ask</c> fast with an
|
||||
/// <c>Akka.Actor.Status.Failure</c> wrapping this exception (WP1.6) instead of warning and dropping
|
||||
/// <c>Akka.Actor.Status.Failure</c> 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).
|
||||
/// </summary>
|
||||
@@ -51,8 +51,7 @@ public sealed class SitePairChannelProvider : IDisposable
|
||||
private static readonly TimeSpan FailbackSweepInterval = TimeSpan.FromSeconds(1);
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
internal static readonly TimeSpan ChannelDisposalGrace = TimeSpan.FromMinutes(5);
|
||||
|
||||
@@ -366,7 +365,7 @@ public sealed class SitePairChannelProvider : IDisposable
|
||||
|
||||
/// <summary>
|
||||
/// Parks a channel that is no longer reachable through <see cref="_sites"/> instead of
|
||||
/// disposing it inline (arch-review 2026-08-19 P3-2).
|
||||
/// disposing it inline.
|
||||
/// <para>
|
||||
/// <b>Why deferred.</b> <see cref="ExecuteAsync{T}"/> reads the channel under
|
||||
/// <c>pair.Gate</c> but invokes the RPC OUTSIDE it, so an inline
|
||||
@@ -472,7 +471,7 @@ public sealed class SitePairChannelProvider : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>Disposes every pooled channel and stops the failback sweep and parked-channel disposal.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
lock (_lifecycleGate)
|
||||
@@ -500,6 +499,7 @@ public sealed class SitePairChannelProvider : IDisposable
|
||||
|
||||
private sealed class SitePair(string siteId)
|
||||
{
|
||||
/// <summary>The site identifier this pair of node channels belongs to.</summary>
|
||||
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;
|
||||
|
||||
/// <summary>Sets the failback backoff to <paramref name="delay"/> and schedules the next probe that far ahead.</summary>
|
||||
/// <param name="delay">The delay before the next failback probe.</param>
|
||||
public void ScheduleFailback(TimeSpan delay)
|
||||
{
|
||||
_backoff = delay;
|
||||
NextProbeUtc = DateTime.UtcNow + _backoff;
|
||||
}
|
||||
|
||||
/// <summary>Doubles the failback backoff (capped at the maximum) and schedules the next probe.</summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>Resets the backoff to its initial value and clears the scheduled failback probe.</summary>
|
||||
public void ResetFailback()
|
||||
{
|
||||
_backoff = InitialBackoff;
|
||||
|
||||
@@ -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.
|
||||
/// </summary>
|
||||
/// <param name="options">Optional communication options; defaults are used when null.</param>
|
||||
/// <returns>A <see cref="SiteStreamGrpcClient"/> with no channel or client, for testing only.</returns>
|
||||
internal static SiteStreamGrpcClient CreateForTesting(CommunicationOptions? options = null) => new(options ?? new CommunicationOptions());
|
||||
|
||||
@@ -146,8 +147,8 @@ public class SiteStreamGrpcClient : IAsyncDisposable, IDisposable
|
||||
/// <see cref="SubscribeAsync"/> calls briefly share an ID), the prior CTS is
|
||||
/// <b>cancelled only</b> — never disposed from here. Internal for testability.
|
||||
/// <para>
|
||||
/// <b>Cancel-only is the contract</b> (arch-review 2026-08-19 P3-1, the client half of the
|
||||
/// row-47 class): the prior handler's <see cref="ConsumeStreamAsync"/> is still running and
|
||||
/// <b>Cancel-only is the contract</b> (the client half of the
|
||||
/// shared-token-source disposal race): the prior handler's <see cref="ConsumeStreamAsync"/> is still running and
|
||||
/// still reads <c>cts.Token</c> (opening the call, <c>ReadAllAsync</c>, the
|
||||
/// <c>IsCancellationRequested</c> checks), so disposing it from the REPLACING path throws
|
||||
/// <see cref="ObjectDisposedException"/> inside the prior stream — and, worse, out of the
|
||||
@@ -172,7 +173,7 @@ public class SiteStreamGrpcClient : IAsyncDisposable, IDisposable
|
||||
/// Cancels a subscription token source, swallowing <see cref="ObjectDisposedException"/>.
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="cts">The token source to cancel.</param>
|
||||
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
|
||||
/// <summary>
|
||||
/// Opens the site-wide, <b>alarm-only</b> server-streaming subscription
|
||||
/// (<c>SubscribeSite</c>) 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 <c>SubscribeSiteAlarms</c> hub drops the
|
||||
/// the central per-site feed that backs the aggregated Alarm Summary live cache:
|
||||
/// the site runtime's <c>SubscribeSiteAlarms</c> hub drops the
|
||||
/// per-instance filter and carries only <see cref="AlarmStateChanged"/> events for
|
||||
/// <em>all</em> instances on the site (attributes are deliberately excluded — the
|
||||
/// summary never shows them and they are far higher-volume).
|
||||
/// <para>
|
||||
/// The callback is deliberately <b>typed</b> as <see cref="AlarmStateChanged"/>
|
||||
/// rather than the generic <c>Action<object></c> used by
|
||||
/// <see cref="SubscribeAsync"/>: this stream is alarm-only by contract, so Task 4's
|
||||
/// <see cref="SubscribeAsync"/>: 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 <see cref="ConvertToDomainEvent"/>; a non-alarm
|
||||
/// event (which should never appear on this stream) is defensively ignored rather
|
||||
@@ -504,7 +505,7 @@ public class SiteStreamGrpcClient : IAsyncDisposable, IDisposable
|
||||
/// <summary>
|
||||
/// Cancels an active subscription by correlation ID. Cancel-only: the subscription's own
|
||||
/// <see cref="SubscribeAsync"/>/<see cref="SubscribeSiteAsync"/> 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.
|
||||
/// </summary>
|
||||
/// <param name="correlationId">Unique identifier of the subscription to cancel.</param>
|
||||
@@ -664,7 +665,7 @@ public class SiteStreamGrpcClient : IAsyncDisposable, IDisposable
|
||||
/// here is synchronous (CTS cancellation and <see cref="GrpcChannel.Dispose"/>), so a
|
||||
/// synchronous <see cref="Dispose"/> can release everything without sync-over-async blocking.
|
||||
/// <para>
|
||||
/// 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
|
||||
/// <see cref="SubscribeAsync"/>/<see cref="SubscribeSiteAsync"/> calls, which dispose them
|
||||
/// as their loops unwind in response to this cancellation; disposing them here raced those
|
||||
/// loops, and one <see cref="ObjectDisposedException"/> aborted the whole foreach — which in
|
||||
|
||||
@@ -131,7 +131,7 @@ public class SiteStreamGrpcClientFactory : IAsyncDisposable, IDisposable
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <returns>A value task that completes when all clients have been disposed.</returns>
|
||||
public async ValueTask DisposeAsync()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Retained wire method; ingest is served only by CentralControlService.</summary>
|
||||
/// <inheritdoc />
|
||||
public override Task<IngestAck> IngestAuditEvents(AuditEventBatch request, ServerCallContext context)
|
||||
=> Task.FromException<IngestAck>(new RpcException(new GrpcStatus(
|
||||
StatusCode.Unimplemented, "Use CentralControlService.IngestAuditEvents on central.")));
|
||||
|
||||
/// <summary>Retained wire method; combined ingest is served only by CentralControlService.</summary>
|
||||
/// <inheritdoc />
|
||||
public override Task<IngestAck> IngestCachedTelemetry(CachedTelemetryBatch request, ServerCallContext context)
|
||||
=> Task.FromException<IngestAck>(new RpcException(new GrpcStatus(
|
||||
StatusCode.Unimplemented, "Use CentralControlService.IngestCachedTelemetry on central.")));
|
||||
@@ -535,12 +535,11 @@ public class SiteStreamGrpcServer : SiteStreamService.SiteStreamServiceBase
|
||||
/// <summary>Flow Debugger PullTraceEvents: server-side id cap, matching the site queue's.</summary>
|
||||
public const int MaxTraceExecutionIdsPerPull = 64;
|
||||
|
||||
/// <summary>
|
||||
/// Flow Debugger (design 2026-09-05 §5.4): point read of this site's audit rows whose
|
||||
/// <c>ExecutionId</c> or <c>ParentExecutionId</c> is one of the requested ids. NOT a
|
||||
/// reconciliation path — moves no cursor, flips no ForwardState. Gated by the same
|
||||
/// <c>ControlPlaneAuthInterceptor</c> prefix as <see cref="PullAuditEvents"/>.
|
||||
/// </summary>
|
||||
// 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.
|
||||
/// <inheritdoc />
|
||||
public override async Task<PullTraceEventsResponse> 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
|
||||
|
||||
Reference in New Issue
Block a user