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
@@ -98,6 +98,21 @@ reconnect — pulls "all tracking rows changed since cursor X" from each site.
Gaps left by lost telemetry self-heal. Central converges to the site; the site
never depends on central.
The per-site cursor is a **composite `(UpdatedAtUtc, TrackedOperationId)`
keyset**, not a single timestamp. Each pull asks for rows strictly greater than
the cursor pair and advances it to the maximum row seen; a burst of more rows
than one batch all sharing one exact `UpdatedAtUtc` therefore drains via the
`TrackedOperationId` tiebreak instead of pinning the timestamp forever. The
`after_id` keyset field is additive on the pull contract — a first pull (or a
**legacy** site that predates it) sends no `after_id` and keeps the inclusive
`>=` timestamp behaviour. When such a legacy site keeps reporting
`MoreAvailable` yet the composite cursor cannot advance, the actor latches the
site as *pinned* and publishes `SiteCallReconciliationPinnedChanged(siteId,
Pinned)` on the EventStream (transition-only, mirroring
`SiteAuditTelemetryStalledChanged`) — the un-drainable tail is a
health-observable condition rather than a silent log line, and the latch clears
with `Pinned=false` once a later tick makes progress.
## Retry / Discard Relay
Parked cached calls live in the owning site's S&F buffer. Operator Retry/Discard