fix(audit): write-through on completed channel, poison-batch isolation, drain-fault fallback
This commit is contained in:
@@ -5,41 +5,6 @@ using ZB.MOM.WW.Auth.ApiKeys.Sqlite;
|
||||
|
||||
namespace ZB.MOM.WW.MxGateway.Server.Security.Audit;
|
||||
|
||||
/// <summary>
|
||||
/// Durable sink the audit pipeline persists canonical <see cref="AuditEvent"/>s through.
|
||||
/// It exists so the write path (<see cref="CanonicalAuditWriter"/>) and the batching drain
|
||||
/// (<see cref="AuditDrainService"/>) depend on the storage contract rather than on the
|
||||
/// concrete <see cref="SqliteCanonicalAuditStore"/>.
|
||||
/// </summary>
|
||||
public interface IAuditEventSink
|
||||
{
|
||||
/// <summary>
|
||||
/// Bootstraps the backing storage. Called once at startup so no write path pays a schema
|
||||
/// round-trip; implementations must be idempotent and safe to call concurrently.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Token to observe for cancellation.</param>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
Task EnsureInitializedAsync(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>Persists a single canonical audit event.</summary>
|
||||
/// <param name="auditEvent">The canonical event to persist.</param>
|
||||
/// <param name="cancellationToken">Token to observe for cancellation.</param>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
Task InsertAsync(AuditEvent auditEvent, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>Persists a batch of canonical audit events as one unit of work.</summary>
|
||||
/// <param name="auditEvents">The canonical events to persist.</param>
|
||||
/// <param name="cancellationToken">Token to observe for cancellation.</param>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
Task InsertBatchAsync(IReadOnlyList<AuditEvent> auditEvents, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>Deletes every audit row that occurred strictly before <paramref name="cutoffUtc"/>.</summary>
|
||||
/// <param name="cutoffUtc">The retention cutoff; rows older than this are removed.</param>
|
||||
/// <param name="cancellationToken">Token to observe for cancellation.</param>
|
||||
/// <returns>The number of rows deleted.</returns>
|
||||
Task<int> DeleteOlderThanAsync(DateTimeOffset cutoffUtc, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MxGateway-owned, append-only SQLite store for canonical
|
||||
/// <see cref="AuditEvent"/>s. It writes to a NEW <c>audit_event</c> table in the
|
||||
@@ -178,10 +143,14 @@ public sealed class SqliteCanonicalAuditStore(AuthSqliteConnectionFactory connec
|
||||
/// <inheritdoc />
|
||||
/// <remarks>
|
||||
/// The comparison goes through SQLite's <c>datetime()</c> rather than comparing the stored
|
||||
/// ISO-8601 text directly: rows written from a non-UTC <see cref="DateTimeOffset"/> (the
|
||||
/// library's <c>CreatedUtc</c> is caller-supplied) do not sort lexicographically against a
|
||||
/// UTC cutoff. A row whose timestamp <c>datetime()</c> cannot parse yields NULL and is
|
||||
/// therefore never deleted — audit that cannot be dated is kept, not swept.
|
||||
/// ISO-8601 text directly. Text comparison is only correct while every row is UTC-normalized
|
||||
/// ISO-8601 — which <see cref="AuditEvent.OccurredAtUtc"/> guarantees for rows written through
|
||||
/// this store, but not for rows that entered the table any other way (a repair script, an
|
||||
/// older schema, a future producer). On a mixed-format column a text comparison silently
|
||||
/// deletes live audit: <c>2026-05-17T09:00:00-05:00</c> is two hours AFTER a
|
||||
/// <c>2026-05-17T12:00:00+00:00</c> cutoff yet sorts before it. Comparing instants is correct
|
||||
/// regardless of how the text got there, and anything <c>datetime()</c> cannot parse yields
|
||||
/// NULL and is therefore never deleted — audit that cannot be dated is kept, not swept.
|
||||
/// </remarks>
|
||||
public async Task<int> DeleteOlderThanAsync(DateTimeOffset cutoffUtc, CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user