docs(comments): strip internal task/milestone/bundle bookkeeping from code comments
Remove project bookkeeping citations from shipped code comments across the solution: hyphenated task IDs (WP-14, StoreAndForward-025), milestone/task/ issue refs (M3, Task 4, Audit Log #23, #21), Bundle X task-bundle labels, and C/D/K/S/T phase labels. Comment text only — no code logic, string/log literals, or XML-doc structure changed. Genuine descriptions are preserved (only the citation is stripped), and technical lookalikes are retained (UTF-8, SHA-256, T00:00:00, M365, UTC-5, pre-C4/pre-C5 schema versions). Flagged by the new CommentChecker TaskReferenceInComment / TrackingReferenceInComment checks plus targeted grep passes; full solution builds clean, append-only guard tests pass.
This commit is contained in:
@@ -18,7 +18,7 @@ namespace ZB.MOM.WW.ScadaBridge.AuditLog.Site;
|
||||
/// <para>
|
||||
/// Each primary failure increments <see cref="IAuditWriteFailureCounter"/> so
|
||||
/// Site Health Monitoring can surface a sustained outage as
|
||||
/// <c>SiteAuditWriteFailures</c> (Bundle G).
|
||||
/// <c>SiteAuditWriteFailures</c>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Errors raised by the ring drain on recovery are logged and silently dropped
|
||||
@@ -36,7 +36,7 @@ public sealed class FallbackAuditWriter : IAuditWriter
|
||||
private readonly SemaphoreSlim _drainGate = new(1, 1);
|
||||
|
||||
/// <summary>
|
||||
/// Bundle C (M5-T6) wires the singleton <see cref="IAuditRedactor"/>
|
||||
/// Wires the singleton <see cref="IAuditRedactor"/>
|
||||
/// here so every event written via the site hot path is truncated +
|
||||
/// header/body/SQL-param redacted before it hits both the primary SQLite
|
||||
/// writer AND the ring fallback. The parameter is optional (defaults to
|
||||
@@ -62,8 +62,8 @@ public sealed class FallbackAuditWriter : IAuditWriter
|
||||
_ring = ring ?? throw new ArgumentNullException(nameof(ring));
|
||||
_failureCounter = failureCounter ?? throw new ArgumentNullException(nameof(failureCounter));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
// AuditLog-008: never default to a null redactor — over-redact instead.
|
||||
// C3 (Task 2.5): wired via the canonical IAuditRedactor seam.
|
||||
// Never default to a null redactor — over-redact instead.
|
||||
// Wired via the canonical IAuditRedactor seam.
|
||||
// SafeDefaultAuditRedactor performs HTTP header redaction with the
|
||||
// hard-coded sensitive defaults (Authorization, X-Api-Key, Cookie,
|
||||
// Set-Cookie) on the DetailsJson summaries so a test composition root
|
||||
@@ -82,7 +82,7 @@ public sealed class FallbackAuditWriter : IAuditWriter
|
||||
// and (on failure) to the ring buffer — so a primary outage that
|
||||
// drains later still hands the SqliteAuditWriter a row that has
|
||||
// already been truncated and redacted. The redactor contract is
|
||||
// "MUST NOT throw". AuditLog-008: _redactor is now non-null (defaults
|
||||
// "MUST NOT throw". _redactor is now non-null (defaults
|
||||
// to SafeDefaultAuditRedactor so header redaction is always applied
|
||||
// even in composition roots that don't wire the real redactor).
|
||||
var filtered = _redactor.Apply(evt);
|
||||
|
||||
+7
-8
@@ -4,7 +4,7 @@ using ZB.MOM.WW.ScadaBridge.HealthMonitoring;
|
||||
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Site;
|
||||
|
||||
/// <summary>
|
||||
/// Audit Log (#23) M5 Bundle C — bridges
|
||||
/// Bridges
|
||||
/// <see cref="IAuditRedactionFailureCounter"/> (incremented by
|
||||
/// <see cref="ZB.MOM.WW.ScadaBridge.AuditLog.Redaction.ScadaBridgeAuditRedactor"/> every time
|
||||
/// a header / body / SQL parameter redactor stage throws and the redactor has
|
||||
@@ -22,16 +22,15 @@ namespace ZB.MOM.WW.ScadaBridge.AuditLog.Site;
|
||||
/// redaction failures must NEVER abort the user-facing action, alog.md §7).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Mirrors the M2 Bundle G <see cref="HealthMetricsAuditWriteFailureCounter"/>
|
||||
/// Mirrors the <see cref="HealthMetricsAuditWriteFailureCounter"/>
|
||||
/// shape one-for-one so the two health-metric bridges age together.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Site-side only for M5: the redaction filter also runs on the central
|
||||
/// writers (CentralAuditWriter + AuditLogIngestActor), but the central
|
||||
/// health-metric surface that would expose <c>AuditRedactionFailure</c>
|
||||
/// alongside the existing central counters ships in M6. Until then, the
|
||||
/// central composition root keeps the NoOp default — the redactions still
|
||||
/// happen, they just don't get counted into a health report.
|
||||
/// Site-side only: the redaction filter also runs on the central
|
||||
/// writers (CentralAuditWriter + AuditLogIngestActor); the central
|
||||
/// health-metric surface exposing <c>AuditRedactionFailure</c> is bridged
|
||||
/// separately by <c>CentralAuditRedactionFailureCounter</c>, which the
|
||||
/// central composition root registers in place of the NoOp default.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class HealthMetricsAuditRedactionFailureCounter : IAuditRedactionFailureCounter
|
||||
|
||||
@@ -3,7 +3,7 @@ using ZB.MOM.WW.ScadaBridge.HealthMonitoring;
|
||||
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Site;
|
||||
|
||||
/// <summary>
|
||||
/// Audit Log (#23) M2 Bundle G — bridges <see cref="IAuditWriteFailureCounter"/>
|
||||
/// Bridges <see cref="IAuditWriteFailureCounter"/>
|
||||
/// (incremented by <see cref="FallbackAuditWriter"/> every time the primary
|
||||
/// SQLite writer throws) into <see cref="ISiteHealthCollector"/> so the count
|
||||
/// surfaces in the site health report payload as
|
||||
|
||||
@@ -3,7 +3,7 @@ namespace ZB.MOM.WW.ScadaBridge.AuditLog.Site;
|
||||
/// <summary>
|
||||
/// Lightweight counter sink invoked by <see cref="FallbackAuditWriter"/> every
|
||||
/// time the primary <see cref="SqliteAuditWriter"/> throws on an audit write.
|
||||
/// Bundle G (M2-T11) implements this as a thread-safe Interlocked counter
|
||||
/// Implements this as a thread-safe Interlocked counter
|
||||
/// bridged into the Site Health Monitoring report payload as
|
||||
/// <c>SiteAuditWriteFailures</c>.
|
||||
/// </summary>
|
||||
|
||||
@@ -3,7 +3,7 @@ namespace ZB.MOM.WW.ScadaBridge.AuditLog.Site;
|
||||
/// <summary>
|
||||
/// Default <see cref="IAuditWriteFailureCounter"/> registered by
|
||||
/// <see cref="ZB.MOM.WW.ScadaBridge.AuditLog.ServiceCollectionExtensions.AddAuditLog"/> on
|
||||
/// every node. Bundle G replaces this binding with a real counter that bridges
|
||||
/// every node. This binding will be replaced with a real counter that bridges
|
||||
/// into the Site Health Monitoring report payload as
|
||||
/// <c>SiteAuditWriteFailures</c> — until then,
|
||||
/// <see cref="FallbackAuditWriter"/> emits to a silent sink rather than NRE-ing
|
||||
@@ -19,7 +19,7 @@ public sealed class NoOpAuditWriteFailureCounter : IAuditWriteFailureCounter
|
||||
/// <inheritdoc/>
|
||||
public void Increment()
|
||||
{
|
||||
// Intentionally empty. Bundle G overrides this binding with the real
|
||||
// Intentionally empty. This binding is replaced with the real
|
||||
// counter once Site Health Monitoring is wired.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace ZB.MOM.WW.ScadaBridge.AuditLog.Site;
|
||||
/// count at capacity, exactly one event has been dropped.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Per the M2 plan: the ring is the absolute-last-resort buffer for the
|
||||
/// The ring is the absolute-last-resort buffer for the
|
||||
/// hot-path; it is NOT a substitute for the bounded
|
||||
/// <see cref="SqliteAuditWriter"/> write queue.
|
||||
/// </para>
|
||||
|
||||
@@ -6,7 +6,7 @@ using ZB.MOM.WW.ScadaBridge.HealthMonitoring;
|
||||
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Site;
|
||||
|
||||
/// <summary>
|
||||
/// Audit Log (#23) M6 Bundle E (T6) — site-side hosted service that
|
||||
/// Site-side hosted service that
|
||||
/// periodically pulls a backlog snapshot from <see cref="ISiteAuditQueue"/>
|
||||
/// and pushes it into <see cref="ISiteHealthCollector"/> so the next
|
||||
/// <see cref="ISiteHealthCollector.CollectReport"/> emits a fresh
|
||||
@@ -26,7 +26,7 @@ namespace ZB.MOM.WW.ScadaBridge.AuditLog.Site;
|
||||
/// across many reports, fine enough that the central dashboard never lags by
|
||||
/// more than one health-report interval. Tunable via
|
||||
/// <see cref="ZB.MOM.WW.ScadaBridge.AuditLog.Site.SqliteAuditWriterOptions"/> in a follow-up
|
||||
/// if ops needs a different cadence; for M6 we hard-code the value because the
|
||||
/// if ops needs a different cadence; for now we hard-code the value because the
|
||||
/// brief calls it out explicitly.
|
||||
/// </para>
|
||||
/// <para>
|
||||
|
||||
@@ -12,7 +12,7 @@ using AuditOutcome = ZB.MOM.WW.Audit.AuditOutcome;
|
||||
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Site;
|
||||
|
||||
/// <summary>
|
||||
/// Site-side SQLite hot-path writer for Audit Log (#23) events. Mirrors the
|
||||
/// Site-side SQLite hot-path writer for Audit Log events. Mirrors the
|
||||
/// <see cref="ZB.MOM.WW.ScadaBridge.SiteEventLogging.SiteEventLogger"/> design — a single
|
||||
/// owned <see cref="SqliteConnection"/> serialised behind a write lock, fed by a
|
||||
/// bounded <see cref="Channel{T}"/> drained on a dedicated background writer
|
||||
@@ -20,7 +20,7 @@ namespace ZB.MOM.WW.ScadaBridge.AuditLog.Site;
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <b>C4 (Task 2.5) — two-table schema.</b> The site store is now two tables:
|
||||
/// <b>Two-table schema.</b> The site store is now two tables:
|
||||
/// the append-only canonical <c>audit_event</c> (the 10 canonical
|
||||
/// <see cref="AuditEvent"/> fields stored directly — NO 24-column decompose) and
|
||||
/// the mutable operational <c>audit_forward_state</c> sidecar that carries the
|
||||
@@ -31,10 +31,10 @@ namespace ZB.MOM.WW.ScadaBridge.AuditLog.Site;
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>Ephemeral reset.</b> The site SQLite store is ephemeral (≈7-day retention,
|
||||
/// recreated per deployment), so C4's schema change is an in-place RESET: the new
|
||||
/// recreated per deployment), so this schema change is an in-place RESET: the new
|
||||
/// tables are created and the old single 24-column <c>AuditLog</c> table is
|
||||
/// DROP-ped if present. No SQLite data migration is performed (and none is
|
||||
/// needed) — any rows in a pre-C4 <c>AuditLog</c> table are within the retention
|
||||
/// needed) — any rows in the old <c>AuditLog</c> table are within the retention
|
||||
/// window and are discarded by the drop.
|
||||
/// </para>
|
||||
/// <para>
|
||||
@@ -56,7 +56,7 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable
|
||||
private const int SqliteErrorConstraint = 19;
|
||||
|
||||
private readonly SqliteConnection _connection;
|
||||
// AuditLog-005: dedicated read-only connection used by GetBacklogStatsAsync,
|
||||
// Dedicated read-only connection used by GetBacklogStatsAsync,
|
||||
// ReadPendingAsync, ReadPendingSinceAsync, and ReadForwardedAsync so a slow
|
||||
// backlog scan (COUNT(*) over hundreds of thousands of Pending rows under a
|
||||
// central outage) never parks the hot-path writer behind _writeLock.
|
||||
@@ -102,7 +102,7 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable
|
||||
|
||||
InitializeSchema();
|
||||
|
||||
// AuditLog-005: open a second connection for read-only callers
|
||||
// Open a second connection for read-only callers
|
||||
// (GetBacklogStatsAsync, ReadPendingAsync, ReadPendingSinceAsync,
|
||||
// ReadForwardedAsync). InitializeSchema set journal_mode=WAL on the
|
||||
// writer connection, which is a database-level setting that persists
|
||||
@@ -128,7 +128,7 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable
|
||||
new BoundedChannelOptions(_options.ChannelCapacity)
|
||||
{
|
||||
// The hot-path enqueue must back-pressure if the background
|
||||
// writer falls behind; a higher-level fallback (Bundle B-T4)
|
||||
// writer falls behind; a higher-level fallback
|
||||
// handles truly catastrophic primary failure with a drop-oldest
|
||||
// ring buffer.
|
||||
FullMode = BoundedChannelFullMode.Wait,
|
||||
@@ -150,7 +150,7 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable
|
||||
pragmaCmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
// AuditLog-005: enable WAL so a second connection on the same file can
|
||||
// Enable WAL so a second connection on the same file can
|
||||
// serve read-only callers (GetBacklogStatsAsync, ReadPendingAsync,
|
||||
// ReadPendingSinceAsync, ReadForwardedAsync) concurrently with the
|
||||
// batched writer, decoupling those reads from _writeLock. WAL is a
|
||||
@@ -185,14 +185,14 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable
|
||||
pragmaCmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
// C4 (Task 2.5) — in-place reset. The site store is EPHEMERAL (≈7-day
|
||||
// In-place reset. The site store is EPHEMERAL (≈7-day
|
||||
// retention, recreated per deployment), so we do NOT migrate the old
|
||||
// single 24-column AuditLog table to the new two-table shape: any rows
|
||||
// it holds are within the retention window and discarded. DROP it if a
|
||||
// pre-C4 deployment left it behind, then CREATE the two new tables. This
|
||||
// it holds are within the retention window and discarded. DROP it if an
|
||||
// earlier deployment left it behind, then CREATE the two new tables. This
|
||||
// is safe precisely BECAUSE the site store is ephemeral — never do this
|
||||
// on a durable store (the central SQL Server side keeps its shim until
|
||||
// C5 and is migrated, not reset).
|
||||
// it is migrated, not reset).
|
||||
using (var dropCmd = _connection.CreateCommand())
|
||||
{
|
||||
dropCmd.CommandText = "DROP TABLE IF EXISTS AuditLog;";
|
||||
@@ -442,10 +442,10 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable
|
||||
}
|
||||
}
|
||||
|
||||
// AuditLog-001: cached-lifecycle audit kinds that ride the combined-telemetry
|
||||
// Cached-lifecycle audit kinds that ride the combined-telemetry
|
||||
// drain (joined with the operational tracking row + pushed via
|
||||
// IngestCachedTelemetryAsync into the central dual-write transaction).
|
||||
// C4: this is the SAME set the pre-C4 ReadPendingCachedTelemetryAsync query
|
||||
// This is the SAME set the earlier ReadPendingCachedTelemetryAsync query
|
||||
// filtered on (Kind IN (...)); it is now precomputed into the sidecar's
|
||||
// IsCachedKind flag at INSERT (see IsCachedKind) so the read split is a cheap
|
||||
// integer predicate, not a JSON parse. ReadPendingAsync drains everything
|
||||
@@ -459,7 +459,7 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// C4: precomputes the sidecar's <c>IsCachedKind</c> flag from a canonical
|
||||
/// Precomputes the sidecar's <c>IsCachedKind</c> flag from a canonical
|
||||
/// row's <c>DetailsJson</c>. Parses the <see cref="AuditDetails.Kind"/>
|
||||
/// discriminator via <see cref="AuditDetailsCodec"/> and returns <c>true</c>
|
||||
/// iff it is one of the cached-lifecycle kinds
|
||||
@@ -489,14 +489,14 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable
|
||||
throw new ArgumentOutOfRangeException(nameof(limit), "limit must be > 0.");
|
||||
}
|
||||
|
||||
// AuditLog-005: read via the dedicated _readConnection so this scan
|
||||
// Read via the dedicated _readConnection so this scan
|
||||
// (which can be expensive when the backlog grows under a central
|
||||
// outage) does not block the batched writer on _writeLock. WAL mode
|
||||
// gives us a stable snapshot of the tables while writes proceed on the
|
||||
// writer connection. _readLock serialises this connection across
|
||||
// multiple concurrent read callers since SqliteConnection itself is
|
||||
// not thread-safe.
|
||||
// C4: JOIN the sidecar and filter on IsCachedKind=0 — the cached-
|
||||
// JOIN the sidecar and filter on IsCachedKind=0 — the cached-
|
||||
// lifecycle kinds (IsCachedKind=1) flow through
|
||||
// ReadPendingCachedTelemetryAsync + the combined-telemetry drain. The
|
||||
// split is a precomputed integer predicate on the indexed sidecar, not
|
||||
@@ -539,7 +539,7 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable
|
||||
throw new ArgumentOutOfRangeException(nameof(limit), "limit must be > 0.");
|
||||
}
|
||||
|
||||
// AuditLog-001 / C4: dedicated read surface for the cached-call lifecycle
|
||||
// Dedicated read surface for the cached-call lifecycle
|
||||
// drain — symmetric to ReadPendingAsync but filtered to IsCachedKind=1.
|
||||
// Same _readConnection + _readLock pattern so the hot-path writer is not
|
||||
// contended.
|
||||
@@ -586,9 +586,9 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable
|
||||
throw new ArgumentOutOfRangeException(nameof(limit), "limit must be > 0.");
|
||||
}
|
||||
|
||||
// AuditLog-005: mirror ReadPendingAsync — read via _readConnection /
|
||||
// Mirror ReadPendingAsync — read via _readConnection /
|
||||
// _readLock so this query never contends with the batched writer on
|
||||
// _writeLock. C4: JOIN the sidecar and filter on ForwardState='Forwarded'
|
||||
// _writeLock. JOIN the sidecar and filter on ForwardState='Forwarded'
|
||||
// (no IsCachedKind split — both cached and non-cached Forwarded rows are
|
||||
// returned, as before).
|
||||
lock (_readLock)
|
||||
@@ -626,7 +626,7 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
using var cmd = _connection.CreateCommand();
|
||||
// C4: flip the sidecar — UPDATE audit_forward_state, not the canonical
|
||||
// Flip the sidecar — UPDATE audit_forward_state, not the canonical
|
||||
// audit_event (which is append-only / write-once). Bump AttemptCount +
|
||||
// stamp LastAttemptUtc so operators can see how many drain passes a row
|
||||
// took to forward. Build a single IN (...) parameter list so we issue
|
||||
@@ -667,7 +667,7 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable
|
||||
/// <summary>
|
||||
/// Returns up to <paramref name="batchSize"/> pending or forwarded audit events
|
||||
/// with <see cref="AuditEvent.OccurredAtUtc"/> >= <paramref name="sinceUtc"/>, oldest first.
|
||||
/// Used by the M6 reconciliation-pull handler.
|
||||
/// Used by the reconciliation-pull handler.
|
||||
/// </summary>
|
||||
/// <param name="sinceUtc">Lower bound timestamp (UTC) for event occurrence.</param>
|
||||
/// <param name="batchSize">Maximum number of rows to return.</param>
|
||||
@@ -681,8 +681,8 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable
|
||||
throw new ArgumentOutOfRangeException(nameof(batchSize), "batchSize must be > 0.");
|
||||
}
|
||||
|
||||
// AuditLog-005: read via _readConnection / _readLock — same lock-
|
||||
// decoupling as ReadPendingAsync. C4: JOIN the sidecar; the range scan
|
||||
// Read via _readConnection / _readLock — same lock-
|
||||
// decoupling as ReadPendingAsync. JOIN the sidecar; the range scan
|
||||
// is on the sidecar's duplicated OccurredAtUtc so it stays on IX_fwd.
|
||||
// Both Pending and Forwarded rows are returned (the central reconciliation
|
||||
// puller dedups on EventId; re-shipping a Forwarded-but-not-yet-ingested
|
||||
@@ -729,7 +729,7 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
using var cmd = _connection.CreateCommand();
|
||||
// C4: flip the sidecar from Pending/Forwarded → Reconciled. Rows
|
||||
// Flip the sidecar from Pending/Forwarded → Reconciled. Rows
|
||||
// already Reconciled are left untouched (idempotent re-call), and the
|
||||
// canonical audit_event row is never modified.
|
||||
var sb = new System.Text.StringBuilder();
|
||||
@@ -759,13 +759,13 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable
|
||||
int pendingCount;
|
||||
DateTime? oldestPending;
|
||||
|
||||
// AuditLog-005: read via the dedicated _readConnection (under
|
||||
// Read via the dedicated _readConnection (under
|
||||
// _readLock) so this probe — polled every 30 s by SiteAuditBacklogReporter
|
||||
// — never blocks the batched hot-path writer on _writeLock. Under a
|
||||
// central outage the Pending backlog can grow to hundreds of thousands
|
||||
// of rows and the COUNT(*) scan correspondingly stretches; that no
|
||||
// longer adds tail latency to user-facing audit writes.
|
||||
// C4: count over the sidecar (audit_forward_state) — the canonical
|
||||
// Count over the sidecar (audit_forward_state) — the canonical
|
||||
// audit_event table carries no ForwardState. The IX_fwd index makes both
|
||||
// aggregates cheap (count is a covering scan, min is the first key).
|
||||
lock (_readLock)
|
||||
@@ -843,7 +843,7 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// C4: builds the canonical <see cref="AuditEvent"/> DIRECTLY from the 10
|
||||
/// Builds the canonical <see cref="AuditEvent"/> DIRECTLY from the 10
|
||||
/// stored <c>audit_event</c> columns — no 24-column <c>Recompose</c>, because
|
||||
/// <c>audit_event</c> already holds the canonical fields + <c>DetailsJson</c>.
|
||||
/// <c>Outcome</c> is stored as the enum's name; the safe
|
||||
@@ -875,7 +875,7 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable
|
||||
/// Disposes the audit writer and releases resources.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// AuditLog-006: prefer <see cref="DisposeAsync"/> when possible (DI honours
|
||||
/// Prefer <see cref="DisposeAsync"/> when possible (DI honours
|
||||
/// <see cref="IAsyncDisposable"/> on singletons). The sync path remains for
|
||||
/// callers that only know about <see cref="IDisposable"/> (e.g. legacy
|
||||
/// composition roots, <c>using</c> statements without <c>await</c>). To
|
||||
@@ -938,7 +938,7 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable
|
||||
_connection.Dispose();
|
||||
}
|
||||
|
||||
// AuditLog-005: dispose the dedicated read connection after the writer
|
||||
// Dispose the dedicated read connection after the writer
|
||||
// is fully drained and closed. _readLock is taken to fence out any
|
||||
// in-flight read caller that grabbed the lock before _disposed flipped
|
||||
// — they observe ObjectDisposedException on the next attempt.
|
||||
|
||||
@@ -8,7 +8,7 @@ using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
|
||||
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Site.Telemetry;
|
||||
|
||||
/// <summary>
|
||||
/// Audit Log #23 (M3 Bundle E — Tasks E4/E5): translates per-attempt
|
||||
/// Translates per-attempt
|
||||
/// notifications from the store-and-forward retry loop into one (or two)
|
||||
/// <see cref="CachedCallTelemetry"/> packets and pushes them through
|
||||
/// <see cref="ICachedCallTelemetryForwarder"/>.
|
||||
@@ -40,7 +40,7 @@ public sealed class CachedCallLifecycleBridge : ICachedCallLifecycleObserver
|
||||
private readonly ILogger<CachedCallLifecycleBridge> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// SourceNode-stamping (Task 14): the local node identity provider used to
|
||||
/// SourceNode-stamping: the local node identity provider used to
|
||||
/// stamp <c>SiteCallOperational.SourceNode</c> on every cached-call
|
||||
/// lifecycle row this bridge emits. Optional — when null (legacy hosts /
|
||||
/// tests that don't register the provider) SourceNode stays null and
|
||||
@@ -93,8 +93,8 @@ public sealed class CachedCallLifecycleBridge : ICachedCallLifecycleObserver
|
||||
// Per-attempt row: kind discriminates channel; status is always
|
||||
// Attempted regardless of outcome (success vs. failure is captured
|
||||
// by the companion HttpStatus / ErrorMessage fields, NOT by flipping
|
||||
// the status — CachedResolve carries the terminal Status). Per the
|
||||
// M3 brief and alog.md §4.
|
||||
// the status — CachedResolve carries the terminal Status). Per
|
||||
// alog.md §4.
|
||||
var kind = ChannelToAttemptKind(context.Channel);
|
||||
var status = AuditStatus.Attempted;
|
||||
|
||||
@@ -148,22 +148,22 @@ public sealed class CachedCallLifecycleBridge : ICachedCallLifecycleObserver
|
||||
occurredAtUtc: DateTime.SpecifyKind(context.OccurredAtUtc, DateTimeKind.Utc),
|
||||
target: context.Target,
|
||||
correlationId: context.TrackedOperationId.Value,
|
||||
// Audit Log #23 (ExecutionId Task 4): the originating script
|
||||
// The originating script
|
||||
// execution's per-run correlation id, threaded through the S&F
|
||||
// buffer; null on rows buffered before Task 4 (back-compat).
|
||||
// buffer; null on rows buffered before ExecutionId support was added (back-compat).
|
||||
executionId: context.ExecutionId,
|
||||
// Audit Log #23 (ParentExecutionId Task 6): the spawning
|
||||
// The spawning
|
||||
// inbound-API request's ExecutionId, threaded through the S&F
|
||||
// buffer alongside ExecutionId so the retry-loop cached rows
|
||||
// correlate back to the cross-execution chain. Null for a
|
||||
// non-routed run and on rows buffered before Task 6.
|
||||
// non-routed run and on rows buffered before ParentExecutionId support was added.
|
||||
parentExecutionId: context.ParentExecutionId,
|
||||
sourceSiteId: string.IsNullOrEmpty(context.SourceSite) ? null : context.SourceSite,
|
||||
sourceInstanceId: context.SourceInstanceId,
|
||||
// Audit Log #23 (ExecutionId Task 4): SourceScript is now
|
||||
// SourceScript is now
|
||||
// threaded through the S&F buffer alongside ExecutionId — the
|
||||
// retry-loop cached rows carry the same provenance the
|
||||
// script-side cached rows do. Null on pre-Task-4 buffered rows.
|
||||
// script-side cached rows do. Null on rows buffered before ExecutionId support was added.
|
||||
sourceScript: context.SourceScript,
|
||||
httpStatus: httpStatus,
|
||||
durationMs: context.DurationMs,
|
||||
@@ -173,7 +173,7 @@ public sealed class CachedCallLifecycleBridge : ICachedCallLifecycleObserver
|
||||
Channel: context.Channel,
|
||||
Target: context.Target,
|
||||
SourceSite: context.SourceSite,
|
||||
// SourceNode-stamping (Task 14): the local cluster node name
|
||||
// SourceNode-stamping: the local cluster node name
|
||||
// (node-a/node-b on a site). Stamped from the injected
|
||||
// INodeIdentityProvider; null when no provider was wired so
|
||||
// central persists SiteCalls.SourceNode as NULL.
|
||||
|
||||
@@ -9,15 +9,15 @@ using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
|
||||
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Site.Telemetry;
|
||||
|
||||
/// <summary>
|
||||
/// Site-side dual emitter for cached-call lifecycle telemetry (Audit Log #23 /
|
||||
/// M3). Sister to <see cref="SiteAuditTelemetryActor"/>: where the M2 actor
|
||||
/// Site-side dual emitter for cached-call lifecycle telemetry. Sister to
|
||||
/// <see cref="SiteAuditTelemetryActor"/>: where that actor
|
||||
/// drains audit-only events, this forwarder takes a combined
|
||||
/// <see cref="CachedCallTelemetry"/> packet and fans it out to the two
|
||||
/// site-local stores in a single call:
|
||||
/// <list type="bullet">
|
||||
/// <item><description>The <see cref="AuditEvent"/> row is written via
|
||||
/// <see cref="IAuditWriter"/> (the site <c>FallbackAuditWriter</c> +
|
||||
/// <c>SqliteAuditWriter</c> chain established in M2).</description></item>
|
||||
/// <c>SqliteAuditWriter</c> chain).</description></item>
|
||||
/// <item><description>The operational <see cref="SiteCallOperational"/> half
|
||||
/// updates the site-local <c>OperationTracking</c> SQLite store via
|
||||
/// <see cref="IOperationTrackingStore"/>, with the per-lifecycle method
|
||||
@@ -54,7 +54,7 @@ public sealed class CachedCallTelemetryForwarder : ICachedCallTelemetryForwarder
|
||||
private readonly ILogger<CachedCallTelemetryForwarder> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// SourceNode-stamping (Task 14): local node identity provider used to
|
||||
/// SourceNode-stamping: local node identity provider used to
|
||||
/// stamp the tracking-store row's <c>SourceNode</c> column on
|
||||
/// <c>RecordEnqueueAsync</c>. Optional — when null (legacy / test hosts)
|
||||
/// the column stays NULL on the tracking row.
|
||||
@@ -64,11 +64,11 @@ public sealed class CachedCallTelemetryForwarder : ICachedCallTelemetryForwarder
|
||||
/// <summary>
|
||||
/// Construct the forwarder. <paramref name="trackingStore"/> is optional —
|
||||
/// when null only the audit half of the packet is emitted, which matches
|
||||
/// the M3 Bundle F composition-root contract on Central nodes: the
|
||||
/// the composition-root contract on Central nodes: the
|
||||
/// AuditLog DI surface registers the forwarder unconditionally (mirroring
|
||||
/// the IAuditWriter chain) but the site-only tracking store has no central
|
||||
/// registration. Production site nodes wire both — the central lazy
|
||||
/// resolution is a no-op path kept symmetric with the M2 writer chain.
|
||||
/// resolution is a no-op path kept symmetric with the writer chain.
|
||||
/// </summary>
|
||||
/// <param name="auditWriter">Writer used to persist audit events from the telemetry packet.</param>
|
||||
/// <param name="trackingStore">Optional store for updating operation tracking state; null on central nodes.</param>
|
||||
@@ -111,7 +111,7 @@ public sealed class CachedCallTelemetryForwarder : ICachedCallTelemetryForwarder
|
||||
// FallbackAuditWriter) handles transient writer failures upstream;
|
||||
// a throw bubbling up here means the writer's own swallow contract
|
||||
// failed, which is itself best-effort-handled.
|
||||
// C3: Kind/Status are domain fields carried in DetailsJson — decompose to log them.
|
||||
// Kind/Status are domain fields carried in DetailsJson — decompose to log them.
|
||||
var d = AuditRowProjection.Decompose(telemetry.Audit);
|
||||
_logger.LogWarning(ex,
|
||||
"CachedCallTelemetryForwarder: audit emission threw for EventId {EventId} (Kind {Kind}, Status {Status})",
|
||||
@@ -130,7 +130,7 @@ public sealed class CachedCallTelemetryForwarder : ICachedCallTelemetryForwarder
|
||||
return;
|
||||
}
|
||||
|
||||
// C3: the audit half's domain fields (Kind/SourceInstanceId/SourceScript)
|
||||
// The audit half's domain fields (Kind/SourceInstanceId/SourceScript)
|
||||
// ride inside DetailsJson — decompose once for this packet.
|
||||
var audit = AuditRowProjection.Decompose(telemetry.Audit);
|
||||
try
|
||||
@@ -141,7 +141,7 @@ public sealed class CachedCallTelemetryForwarder : ICachedCallTelemetryForwarder
|
||||
// Enqueue — insert-if-not-exists with the operational
|
||||
// channel as the kind discriminator. RetryCount is fixed
|
||||
// at 0 by the tracking store's INSERT contract.
|
||||
// SourceNode-stamping (Task 14): stamp the local node
|
||||
// SourceNode-stamping: stamp the local node
|
||||
// name (node-a/node-b) from the injected
|
||||
// INodeIdentityProvider; null when no provider was wired
|
||||
// so the tracking row's SourceNode column stays NULL.
|
||||
|
||||
@@ -24,8 +24,8 @@ namespace ZB.MOM.WW.ScadaBridge.AuditLog.Site.Telemetry;
|
||||
/// The <see cref="SiteAuditTelemetryActor"/> drain loop treats a thrown
|
||||
/// exception as transient and leaves the rows <c>Pending</c> for the next tick.
|
||||
/// Swallowing the fault into an empty ack would be indistinguishable from "zero
|
||||
/// rows accepted" and would silently lose the retry signal. Task 1 confirmed
|
||||
/// the central receiving end does not collapse an ingest fault into an empty
|
||||
/// rows accepted" and would silently lose the retry signal. It has been confirmed
|
||||
/// that the central receiving end does not collapse an ingest fault into an empty
|
||||
/// ack either, so a site-side Ask through the whole path faults cleanly on a
|
||||
/// central-side timeout.
|
||||
/// </para>
|
||||
|
||||
@@ -26,7 +26,7 @@ public interface ISiteStreamAuditClient
|
||||
Task<IngestAck> IngestAuditEventsAsync(AuditEventBatch batch, CancellationToken ct);
|
||||
|
||||
/// <summary>
|
||||
/// Forwards the combined <see cref="CachedTelemetryBatch"/> (Audit Log #23)
|
||||
/// Forwards the combined <see cref="CachedTelemetryBatch"/>
|
||||
/// to the central cached-telemetry ingest path. Each packet carries both the
|
||||
/// audit row and the operational <c>SiteCalls</c> upsert; central writes both
|
||||
/// in a single MS SQL transaction. Returns the same <see cref="IngestAck"/>
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace ZB.MOM.WW.ScadaBridge.AuditLog.Site.Telemetry;
|
||||
/// which case the cached-drain scheduler is never armed.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Per Bundle D's brief, audit-write paths must be fail-safe — a thrown
|
||||
/// Audit-write paths must be fail-safe — a thrown
|
||||
/// exception inside the actor MUST NOT crash it. Both Drain handlers wrap
|
||||
/// their pipelines in a top-level try/catch that logs and re-schedules; the
|
||||
/// actor's <see cref="SupervisorStrategy"/> defaults to
|
||||
@@ -54,7 +54,7 @@ namespace ZB.MOM.WW.ScadaBridge.AuditLog.Site.Telemetry;
|
||||
/// matters.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// AuditLog-001: wires the previously-unreachable combined-telemetry transport.
|
||||
/// Wires the previously-unreachable combined-telemetry transport.
|
||||
/// Prior to this the cached audit rows flowed through the audit-only drain via
|
||||
/// <c>IngestAuditEventsAsync</c> and the central <c>OnCachedTelemetryAsync</c>
|
||||
/// dual-write handler was dead production code; the operational <c>SiteCalls</c>
|
||||
@@ -70,7 +70,7 @@ public class SiteAuditTelemetryActor : ReceiveActor
|
||||
private readonly ILogger<SiteAuditTelemetryActor> _logger;
|
||||
private ICancelable? _pendingTick;
|
||||
private ICancelable? _pendingCachedTick;
|
||||
// AuditLog-010: per-actor lifecycle CTS so an in-flight drain (queue read,
|
||||
// Per-actor lifecycle CTS so an in-flight drain (queue read,
|
||||
// gRPC push, mark-forwarded write) is actually cancelled when the actor is
|
||||
// stopped — without it, a stuck IngestAuditEventsAsync would hold the
|
||||
// continuation through CoordinatedShutdown's actor-system terminate window.
|
||||
@@ -132,7 +132,7 @@ public class SiteAuditTelemetryActor : ReceiveActor
|
||||
{
|
||||
_pendingTick?.Cancel();
|
||||
_pendingCachedTick?.Cancel();
|
||||
// AuditLog-010: cancel any in-flight drain so a stuck queue read or
|
||||
// Cancel any in-flight drain so a stuck queue read or
|
||||
// gRPC push does not hold the continuation past actor stop.
|
||||
try
|
||||
{
|
||||
@@ -149,7 +149,7 @@ public class SiteAuditTelemetryActor : ReceiveActor
|
||||
private async Task OnDrainAsync()
|
||||
{
|
||||
var nextDelay = TimeSpan.FromSeconds(_options.BusyIntervalSeconds);
|
||||
// AuditLog-010: route every async dependency call through the
|
||||
// Route every async dependency call through the
|
||||
// per-actor lifecycle token so PostStop cancellation actually
|
||||
// propagates into the queue read, the gRPC push, and the
|
||||
// mark-forwarded write. OperationCanceledException is swallowed by
|
||||
@@ -178,7 +178,7 @@ public class SiteAuditTelemetryActor : ReceiveActor
|
||||
catch (Exception ex)
|
||||
{
|
||||
// gRPC fault — leave the rows in Pending so the next drain
|
||||
// retries. Bundle D's brief: "On gRPC exception (any), log
|
||||
// retries — "On gRPC exception (any), log
|
||||
// Warning, schedule next Drain in BusyIntervalSeconds."
|
||||
_logger.LogWarning(ex,
|
||||
"IngestAuditEvents push failed for {Count} pending events; will retry next drain.",
|
||||
@@ -201,7 +201,7 @@ public class SiteAuditTelemetryActor : ReceiveActor
|
||||
}
|
||||
finally
|
||||
{
|
||||
// AuditLog-010: if the actor is already shutting down, do not
|
||||
// If the actor is already shutting down, do not
|
||||
// arm another tick — the scheduler would fire after PostStop and
|
||||
// the message would land in dead letters.
|
||||
if (!_lifecycleCts.IsCancellationRequested)
|
||||
@@ -212,7 +212,7 @@ public class SiteAuditTelemetryActor : ReceiveActor
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AuditLog-001: combined-telemetry drain. Reads cached-lifecycle audit
|
||||
/// Combined-telemetry drain. Reads cached-lifecycle audit
|
||||
/// rows, joins each with the matching <see cref="IOperationTrackingStore"/>
|
||||
/// snapshot, builds a <see cref="CachedTelemetryBatch"/>, and pushes via
|
||||
/// <see cref="ISiteStreamAuditClient.IngestCachedTelemetryAsync"/>. Rows
|
||||
@@ -354,7 +354,7 @@ public class SiteAuditTelemetryActor : ReceiveActor
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AuditLog-001: build the combined wire packet from one cached audit row
|
||||
/// Build the combined wire packet from one cached audit row
|
||||
/// + its matching operational tracking snapshot. The operational state
|
||||
/// reflects the latest tracking row at emission time (not the per-event
|
||||
/// status the audit row implies) because central's <c>SiteCalls</c>
|
||||
@@ -364,7 +364,7 @@ public class SiteAuditTelemetryActor : ReceiveActor
|
||||
private static CachedTelemetryPacket BuildCachedPacket(
|
||||
AuditEvent auditRow, TrackingStatusSnapshot snapshot)
|
||||
{
|
||||
// C3: SourceSiteId + Channel ride inside the canonical record's
|
||||
// SourceSiteId + Channel ride inside the canonical record's
|
||||
// DetailsJson — decompose to read them.
|
||||
var audit = AuditRowProjection.Decompose(auditRow);
|
||||
var sourceSite = audit.SourceSiteId ?? string.Empty;
|
||||
@@ -457,7 +457,7 @@ public class SiteAuditTelemetryActor : ReceiveActor
|
||||
|
||||
/// <summary>
|
||||
/// Self-tick message that triggers a combined-telemetry drain cycle.
|
||||
/// AuditLog-001: introduced alongside the cached-drain to keep the two
|
||||
/// Introduced alongside the cached-drain to keep the two
|
||||
/// paths' cadences independent — a stall on one does not block the other.
|
||||
/// </summary>
|
||||
private sealed class CachedDrain
|
||||
|
||||
@@ -2,7 +2,7 @@ namespace ZB.MOM.WW.ScadaBridge.AuditLog.Site.Telemetry;
|
||||
|
||||
/// <summary>
|
||||
/// Tuning knobs for the site-side <see cref="SiteAuditTelemetryActor"/> drain
|
||||
/// loop. Defaults mirror Bundle D's plan: drain every 5 s while rows are
|
||||
/// loop. Defaults: drain every 5 s while rows are
|
||||
/// flowing (busy), every 30 s when the queue is empty (idle).
|
||||
/// </summary>
|
||||
public sealed class SiteAuditTelemetryOptions
|
||||
|
||||
Reference in New Issue
Block a user