fix(site-call-audit): composite keyset cursor eliminates the single-timestamp reconciliation pin; pinned state now a published event

Reconciliation cursor becomes composite (UpdatedAtUtc, TrackedOperationId);
IPullSiteCallsClient/GrpcPullSiteCallsClient forward the Task-15 after_id keyset
(additive param, null preserves the legacy inclusive >= contract). A burst
sharing one exact UpdatedAtUtc now drains via the id tiebreak instead of pinning
forever. A legacy site that ignores after_id is latched + published as
SiteCallReconciliationPinnedChanged on the EventStream (transition-only),
replacing the prior silent log line.
This commit is contained in:
Joseph Doherty
2026-07-09 08:44:07 -04:00
parent 1f4c0b67ca
commit 20098c6108
9 changed files with 324 additions and 66 deletions
@@ -80,6 +80,7 @@ public sealed class GrpcPullSiteCallsClient : IPullSiteCallsClient
public async Task<PullSiteCallsResponse> PullAsync(
string siteId,
DateTime sinceUtc,
string? afterId,
int batchSize,
CancellationToken ct)
{
@@ -100,6 +101,10 @@ public sealed class GrpcPullSiteCallsClient : IPullSiteCallsClient
// EnsureUtc keeps Timestamp.FromDateTime happy (it requires UTC kind).
SinceUtc = Timestamp.FromDateTime(EnsureUtc(sinceUtc)),
BatchSize = batchSize,
// Composite-keyset tiebreak (Task 16). proto3 has no nullable string —
// an unset/empty AfterId is the site's signal to keep the legacy
// inclusive-timestamp contract (also what a first pull sends).
AfterId = afterId ?? string.Empty,
};
ProtoPullResponse reply;