Merge branch 'worktree-agent-a7084b23177344196' into arch-review-remediation

This commit is contained in:
Joseph Doherty
2026-08-14 21:15:31 -04:00
30 changed files with 1941 additions and 482 deletions
+28 -10
View File
@@ -317,9 +317,19 @@ still replied while the outer Asks are still waiting.
A central `SiteAuditReconciliationActor` periodically (default 5 min per site)
asks each site for its oldest `Pending` row and pending count; if backlog is
non-draining (e.g., telemetry actor wedged), central issues a
`PullAuditEvents(sinceUtc, batchSize)` and inserts-if-not-exists. Accepted rows
are flipped to `ForwardState = 'Reconciled'` site-side. Same self-healing
pattern as Site Call Audit's reconciliation of `SiteCalls`.
`PullAuditEvents(sinceUtc, batchSize[, afterId])` and inserts-if-not-exists.
Same self-healing pattern as Site Call Audit's reconciliation of `SiteCalls`.
**The pull is at-least-once (WP2.3).** Serving a row is not proof that central
received it, so rows are **not** flipped when they are served. The site flips
`ForwardState = 'Reconciled'` for everything at or before the cursor carried by
the **next** pull — the only evidence central actually consumed them
(`ISiteAuditQueue.MarkReconciledUpToAsync`, run before the read so retired rows
do not consume the batch budget). A fault between the response leaving the site
and central committing it therefore re-serves the batch instead of losing it;
central dedups on `EventId`, so a re-ship is a no-op. Previously the site flipped
each served batch immediately, and a central-side fault in that window dropped
those rows permanently — `ReadPendingSinceAsync` would never return them again.
**Endpoint resolution & NodeB failover.** Each pull dials the site's `NodeA`
gRPC address first; if `NodeA` is blank the site's `NodeB` address becomes the
@@ -333,13 +343,21 @@ mapping/unexpected fault collapses to empty without a second dial (the other
node would hit the same fault). The same resolution + failover applies to Site
Call Audit's `PullSiteCalls`.
> **Cursor keyset (tracked follow-up).** The `PullAuditEvents` cursor is still a
> single `sinceUtc` timestamp. Site Call Audit's pull now uses a composite
> `(UpdatedAtUtc, TrackedOperationId)` keyset to avoid a single-timestamp pin
> (see Component-SiteCallAudit.md → Reconciliation). The same keyset should be
> applied here, but it is lower urgency because the audit cursor already re-pulls
> idempotently on `EventId` — a saturated single-timestamp window re-inserts
> harmless no-ops rather than losing rows.
**Cursor keyset.** The site side of the composite `(OccurredAtUtc, EventId)`
keyset now exists (WP2.3): `PullAuditEventsRequest.after_id` (additive field 3)
mirrors `PullSiteCallsRequest.after_id`, and `ISiteAuditQueue.ReadPendingSinceAsync`
switches from the inclusive `OccurredAtUtc >= since` to a strict composite
comparison when it is set, so a burst sharing one instant drains via the id
tiebreak instead of pinning the cursor.
> **Central still sends a bare timestamp (tracked follow-up).**
> `IPullAuditEventsClient`/`SiteAuditReconciliationActor` do not yet populate
> `after_id`, so today's cursor remains a single `sinceUtc` under the legacy
> inclusive read. Two consequences, both benign: a window whose rows all share one
> instant re-serves rather than advancing (idempotent on `EventId`, as before), and
> the rows **at** the cursor instant cannot be proven received, so they stay
> servable until a newer row moves the cursor. Wiring `after_id` at central makes
> the retirement exact; the site accepts it already.
### Central direct-write (central-originated events)