Skip to content

feat(guardian): epoch CAS on guardian binding writes - #796

Open
WiktorStarczewski wants to merge 1 commit into
wiktor/guardian-seam-a-verdictfrom
wiktor/guardian-seam-b-epoch-cas
Open

feat(guardian): epoch CAS on guardian binding writes#796
WiktorStarczewski wants to merge 1 commit into
wiktor/guardian-seam-a-verdictfrom
wiktor/guardian-seam-b-epoch-cas

Conversation

@WiktorStarczewski

Copy link
Copy Markdown
Collaborator

Guardian binding writes (endpoint + commitment baseline) are now guarded by a per-account guardianEpoch compare-and-swap, closing the last-write-wins race the #786 review verified and deferred as its top residual risk (F-220): a drift repair that snapshots the account, spends minutes in HTTP probes, then writes could land after a rotation and resurrect the dead operator's endpoint with an in-sync status.

  • Vault.updateGuardianBinding(pk, expectedEpoch | 'force', patch) — the one binding write path; a mismatched epoch returns stale with no write, an applied patch bumps the epoch, and endpoint + baseline land in one encryptAndSaveMany (the torn endpoint-then-baseline sequences and their hand-maintained ordering comments are gone).
  • The legacy setters become force-with-bump wrappers, serving the authoritative writers — rotation completion must never lose, and its bump is exactly what turns every in-flight stale repair's write into a refusal. By construction, not by probability.
  • resolveGuardianDrift and backfillGuardianEndpoints carry their snapshot's epoch; a stale outcome discards the repair whole and releases the probe cooldown so the next tick re-derives from fresh state (the rounds-17–20 "fix creates the next wedge" guard — a losing repair cannot re-arm its own delay).
  • applyUserGuardianEndpoint gains a fifth outcome, 'stale': the URL verified against a commitment that changed mid-verification asks for one retry instead of binding stale evidence; new guardianUrlStaleRetry copy in all 14 locales.
  • Status writes (guardianSyncStatus) stay last-write-wins on purpose: advisory, self-correcting, and gating them risks healthy loops starving each other. Only binding writes carry the CAS.
Test coverage
  • Vault: CAS applies/refuses/bumps; absent epoch reads as 0 (no migration write for existing vaults); the F-220 scenario as a literal test (repair snapshots → rotation completes → repair's write returns stale, endpoint intact); status writes neither consume nor bump the epoch.
  • Reconciler: a stale-discarded repair leaves status untouched; the released cooldown is proven by an immediate re-probe completing the repair; applyUserGuardianEndpoint returns 'stale' with no status write.
  • 604 suites / 9,998 tests, ts, lint, i18n all green.

Part 2 of 4 of the seam plan from #786's review. Stacked on #795 (Seam A).

Reviewers: the force-vs-CAS split is the part worth your time — completion and user-typed applies go through force-with-bump wrappers, snapshot-based repairs through the CAS; a writer on the wrong side of that line either can lose a rotation or can resurrect one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant