fix(auditlog): reconciliation pull serves only unacked rows (PushAbandoned/Rejected push-exit states), central pages within a tick with set-based inserts; refused push rows leave the queue after N attempts (review 2026-10-21 06/PERF-1, 06/COR-3, 06/IMPL-1)
This commit is contained in:
@@ -488,12 +488,16 @@ public class SqliteAuditWriterWriteTests
|
||||
|
||||
// ----- Reconciliation pull surface ----- //
|
||||
|
||||
/// <summary>Verifies that the since-read returns pending and forwarded rows oldest first, limited to the requested count.</summary>
|
||||
/// <summary>
|
||||
/// Verifies that the since-read returns only rows central has NOT acknowledged, oldest first,
|
||||
/// limited to the requested count — Forwarded rows are no longer re-shipped
|
||||
/// (review 2026-10-21 06/PERF-1).
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReadPendingSinceAsync_Returns_PendingAndForwarded_OldestFirst_LimitedToN()
|
||||
public async Task ReadPendingSinceAsync_Returns_OnlyUnackedRows_OldestFirst_LimitedToN()
|
||||
{
|
||||
var (writer, dataSource) = CreateWriter(nameof(ReadPendingSinceAsync_Returns_PendingAndForwarded_OldestFirst_LimitedToN));
|
||||
var (writer, dataSource) = CreateWriter(nameof(ReadPendingSinceAsync_Returns_OnlyUnackedRows_OldestFirst_LimitedToN));
|
||||
await using var _ = writer;
|
||||
|
||||
var baseTime = new DateTime(2026, 5, 20, 12, 0, 0, DateTimeKind.Utc);
|
||||
@@ -504,11 +508,11 @@ public class SqliteAuditWriterWriteTests
|
||||
NewEvent(occurredAtUtc: baseTime.AddSeconds(3)),
|
||||
NewEvent(occurredAtUtc: baseTime.AddSeconds(2)),
|
||||
NewEvent(occurredAtUtc: baseTime.AddSeconds(4)),
|
||||
NewEvent(occurredAtUtc: baseTime.AddSeconds(6)),
|
||||
};
|
||||
foreach (var e in evts) await writer.WriteAsync(e);
|
||||
|
||||
// Flip half to Forwarded — they must still surface in the reconciliation pull
|
||||
// because central hasn't confirmed they were ingested yet.
|
||||
// Central acked these over the push: the pull has nothing to repair for them.
|
||||
await writer.MarkForwardedAsync(new[] { evts[0].EventId, evts[2].EventId });
|
||||
|
||||
var rows = await writer.ReadPendingSinceAsync(sinceUtc: DateTime.MinValue, batchSize: 3);
|
||||
@@ -516,7 +520,7 @@ public class SqliteAuditWriterWriteTests
|
||||
Assert.Equal(3, rows.Count);
|
||||
Assert.Equal(baseTime.AddSeconds(1), rows[0].OccurredAtUtc);
|
||||
Assert.Equal(baseTime.AddSeconds(2), rows[1].OccurredAtUtc);
|
||||
Assert.Equal(baseTime.AddSeconds(3), rows[2].OccurredAtUtc);
|
||||
Assert.Equal(baseTime.AddSeconds(4), rows[2].OccurredAtUtc);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the since-read excludes rows older than the supplied cutoff.</summary>
|
||||
@@ -740,17 +744,17 @@ public class SqliteAuditWriterWriteTests
|
||||
Assert.Equal(lateStamped.EventId, Assert.Single(reread).EventId);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that forwarded rows are retired by reconciling up to a cursor even if they were never pulled.</summary>
|
||||
/// <summary>
|
||||
/// Verifies that a reconciliation cursor leaves Forwarded rows alone: central acked them over
|
||||
/// the push, they are already purge-eligible, and the pull never served them
|
||||
/// (review 2026-10-21 06/PERF-1).
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task MarkReconciledUpToAsync_ForwardedRowsRetire_EvenWithoutHavingBeenPulled()
|
||||
public async Task MarkReconciledUpToAsync_LeavesForwardedRowsAlone()
|
||||
{
|
||||
// The bound applies to PENDING rows (nothing proves central saw them but the pull).
|
||||
// A FORWARDED row was ACKED by central through the telemetry push path, so the cursor
|
||||
// may retire it whether or not this node has ever served it in a pull — otherwise a
|
||||
// site node that never serves a pull would accumulate acked rows forever.
|
||||
var (writer, dataSource) = CreateWriter(
|
||||
nameof(MarkReconciledUpToAsync_ForwardedRowsRetire_EvenWithoutHavingBeenPulled));
|
||||
nameof(MarkReconciledUpToAsync_LeavesForwardedRowsAlone));
|
||||
await using var _w = writer;
|
||||
|
||||
var t0 = new DateTime(2026, 5, 20, 12, 0, 0, DateTimeKind.Utc);
|
||||
@@ -764,8 +768,8 @@ public class SqliteAuditWriterWriteTests
|
||||
|
||||
var flipped = await writer.MarkReconciledUpToAsync(t0.AddSeconds(30), afterId: null);
|
||||
|
||||
Assert.Equal(1, flipped);
|
||||
Assert.Equal(AuditForwardState.Reconciled.ToString(), ReadForwardState(dataSource, pushed.EventId));
|
||||
Assert.Equal(0, flipped);
|
||||
Assert.Equal(AuditForwardState.Forwarded.ToString(), ReadForwardState(dataSource, pushed.EventId));
|
||||
Assert.Equal(AuditForwardState.Pending.ToString(), ReadForwardState(dataSource, neverShipped.EventId));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user