63c16d6912
Phase 4 of the ClusterClient -> gRPC migration deleted the Akka transports but left the naming behind: `ClusterClientSiteAuditClient` was transport- agnostic and worked unchanged, so it survived the deletion under a name that now describes a transport the repo no longer has. Same for a scatter of doc-comments still framing gRPC as "the new transport" beside an Akka one that is gone. Renames it to `SiteCommunicationAuditClient` (and its test file) and rewrites the stale comments to describe the single transport that exists. Also tightens CLAUDE.md: drops the self-describing directory listing and the 27-component enumeration in favour of the non-obvious parts only. Behaviour-neutral: names and prose only. Recorded as the Phase 4 follow-up in docs/plans/2026-07-22-clusterclient-to-grpc-plan.md.
145 lines
7.1 KiB
C#
145 lines
7.1 KiB
C#
namespace ZB.MOM.WW.ScadaBridge.SiteCallAudit;
|
|
|
|
/// <summary>
|
|
/// Configuration options for the Site Call Audit: stuck-call detection +
|
|
/// KPI windowing for the read-side, plus the cadence/retention knobs for the
|
|
/// two central-singleton schedulers — the periodic per-site reconciliation
|
|
/// pull (self-heal for lost telemetry) and the daily terminal-row purge.
|
|
/// Mirrors the KPI-relevant subset of <c>NotificationOutboxOptions</c> and the
|
|
/// scheduler-cadence shape of <c>SiteAuditReconciliationOptions</c> /
|
|
/// <c>AuditLogPurgeOptions</c>.
|
|
/// </summary>
|
|
public class SiteCallAuditOptions
|
|
{
|
|
/// <summary>
|
|
/// Age past which a non-terminal cached call (<c>Pending</c>/<c>Retrying</c>)
|
|
/// is considered stuck. Display-only — surfaced as the Stuck KPI and a row
|
|
/// badge, with no escalation. Default 10 minutes, matching
|
|
/// <c>NotificationOutboxOptions.StuckAgeThreshold</c>.
|
|
/// </summary>
|
|
public TimeSpan StuckAgeThreshold { get; set; } = TimeSpan.FromMinutes(10);
|
|
|
|
/// <summary>
|
|
/// Trailing window used to compute the delivered- and failed-last-interval
|
|
/// throughput KPIs. Default 1 minute, matching
|
|
/// <c>NotificationOutboxOptions.DeliveredKpiWindow</c>.
|
|
/// </summary>
|
|
public TimeSpan KpiInterval { get; set; } = TimeSpan.FromMinutes(1);
|
|
|
|
/// <summary>
|
|
/// Ask timeout for the central→site Retry/Discard relay. When
|
|
/// the owning site does not ack a <c>RetryParkedOperation</c> /
|
|
/// <c>DiscardParkedOperation</c> within this window — site offline, no
|
|
/// route to the site, or central buffering deliberately absent — the relay
|
|
/// reports a <c>SiteUnreachable</c> outcome. Default 10 seconds: long enough
|
|
/// to absorb a healthy cross-cluster round-trip, short enough that an
|
|
/// operator clicking Retry on an offline site gets a fast, honest answer.
|
|
/// <para>
|
|
/// <b>Ordering invariant:</b> <c>RelayTimeout</c> must stay below
|
|
/// <c>CommunicationOptions.QueryTimeout</c> (default 30s), the timeout the
|
|
/// outer <c>CommunicationService.RetrySiteCallAsync</c>/<c>DiscardSiteCallAsync</c>
|
|
/// Ask of the <c>SiteCallAuditActor</c> uses. The outer Ask must outlive this
|
|
/// inner site relay Ask so the inner relay times out first and yields the
|
|
/// distinct <c>SiteUnreachable</c> outcome; if the outer Ask expired first,
|
|
/// that outcome would be lost to a generic Ask-timeout exception. The
|
|
/// defaults (10s < 30s) satisfy this — keep the gap when tuning either.
|
|
/// </para>
|
|
/// </summary>
|
|
public TimeSpan RelayTimeout { get; set; } = TimeSpan.FromSeconds(10);
|
|
|
|
// ── Reconciliation tick: periodic per-site self-heal pull ──
|
|
|
|
/// <summary>
|
|
/// Period of the reconciliation tick. Each tick visits every known site
|
|
/// once, pulls changed <c>SiteCall</c> rows since a per-site cursor, and
|
|
/// upserts them idempotently — the documented self-heal when best-effort
|
|
/// push telemetry is lost. Default 5 minutes, matching the sibling
|
|
/// <c>SiteAuditReconciliationOptions</c> cadence. Clamped to at least
|
|
/// <see cref="MinReconciliationInterval"/> via <see cref="ReconciliationInterval"/>.
|
|
/// </summary>
|
|
public TimeSpan ReconciliationInterval { get; set; } = TimeSpan.FromMinutes(5);
|
|
|
|
/// <summary>
|
|
/// Test-only override for the reconciliation tick cadence — bypasses the
|
|
/// <see cref="MinReconciliationInterval"/> clamp so unit tests can drop the
|
|
/// cadence to milliseconds. Production config never sets this; leave null.
|
|
/// </summary>
|
|
public TimeSpan? ReconciliationIntervalOverride { get; set; }
|
|
|
|
/// <summary>
|
|
/// Maximum number of <c>SiteCall</c> rows requested per <c>PullSiteCalls</c>
|
|
/// RPC. Default 500. A <c>MoreAvailable=true</c> response signals the cursor
|
|
/// advanced and the next tick should keep draining the backlog.
|
|
/// </summary>
|
|
public int ReconciliationBatchSize { get; set; } = 500;
|
|
|
|
/// <summary>
|
|
/// Minimum interval the config-bound <see cref="ReconciliationInterval"/> can
|
|
/// resolve to. Clamps a misconfigured <c>0</c> (or negative) value away from
|
|
/// <see cref="TimeSpan.Zero"/>, which would make Akka's
|
|
/// <c>ScheduleTellRepeatedlyCancelable</c> spin — the exact footgun flagged in
|
|
/// a prior review of the sibling reconciliation options.
|
|
/// </summary>
|
|
private static readonly TimeSpan MinReconciliationInterval = TimeSpan.FromSeconds(1);
|
|
|
|
/// <summary>
|
|
/// Resolves the effective reconciliation tick interval: the test override
|
|
/// when set (bypassing the clamp), otherwise <see cref="ReconciliationInterval"/>
|
|
/// clamped to at least <see cref="MinReconciliationInterval"/> so a
|
|
/// zero/negative config value can never yield <see cref="TimeSpan.Zero"/>.
|
|
/// </summary>
|
|
public TimeSpan ResolvedReconciliationInterval =>
|
|
ReconciliationIntervalOverride is { } o
|
|
? o
|
|
: ReconciliationInterval < MinReconciliationInterval
|
|
? MinReconciliationInterval
|
|
: ReconciliationInterval;
|
|
|
|
// ── Purge scheduler: daily terminal-row purge ──
|
|
|
|
/// <summary>
|
|
/// Period of the purge tick. Each tick drops terminal <c>SiteCalls</c> rows
|
|
/// older than the retention window via
|
|
/// <see cref="ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories.ISiteCallAuditRepository.PurgeTerminalAsync"/>.
|
|
/// Default 24 hours, matching <c>AuditLogPurgeOptions</c>. Clamped to at
|
|
/// least <see cref="MinPurgeInterval"/> via <see cref="ResolvedPurgeInterval"/>.
|
|
/// </summary>
|
|
public TimeSpan PurgeInterval { get; set; } = TimeSpan.FromHours(24);
|
|
|
|
/// <summary>
|
|
/// Test-only override for the purge tick cadence — bypasses the
|
|
/// <see cref="MinPurgeInterval"/> clamp so unit tests can drop the cadence
|
|
/// to milliseconds. Production config never sets this; leave null.
|
|
/// </summary>
|
|
public TimeSpan? PurgeIntervalOverride { get; set; }
|
|
|
|
/// <summary>
|
|
/// Retention window for terminal rows. On each purge tick a row whose
|
|
/// <c>TerminalAtUtc</c> is older than <c>UtcNow - RetentionDays</c> is
|
|
/// deleted; non-terminal rows are never purged. Default 365 days, matching
|
|
/// the central audit-store retention policy.
|
|
/// </summary>
|
|
public int RetentionDays { get; set; } = 365;
|
|
|
|
/// <summary>
|
|
/// Minimum interval the config-bound <see cref="PurgeInterval"/> can resolve
|
|
/// to. Clamps a misconfigured <c>0</c> (or negative) value away from
|
|
/// <see cref="TimeSpan.Zero"/> for the same scheduler-spin reason as
|
|
/// <see cref="MinReconciliationInterval"/>; the purge is daily so the floor
|
|
/// is a more generous 1 minute.
|
|
/// </summary>
|
|
private static readonly TimeSpan MinPurgeInterval = TimeSpan.FromMinutes(1);
|
|
|
|
/// <summary>
|
|
/// Resolves the effective purge tick interval: the test override when set
|
|
/// (bypassing the clamp), otherwise <see cref="PurgeInterval"/> clamped to at
|
|
/// least <see cref="MinPurgeInterval"/>.
|
|
/// </summary>
|
|
public TimeSpan ResolvedPurgeInterval =>
|
|
PurgeIntervalOverride is { } o
|
|
? o
|
|
: PurgeInterval < MinPurgeInterval
|
|
? MinPurgeInterval
|
|
: PurgeInterval;
|
|
}
|