Skip to content

fix(cli): stamp repo-announcement updates with max(now, head+1) (#2876) - #4363

Open
iroiro147 wants to merge 1 commit into
block:mainfrom
iroiro147:fix/2876-repo-announcement-freshness
Open

iroiro147 wants to merge 1 commit into
block:mainfrom
iroiro147:fix/2876-repo-announcement-freshness

Conversation

@iroiro147

Copy link
Copy Markdown
Contributor

Problem

buzz repos protect set / protect remove / bind permanently fail 15 minutes after a repo is announced (#2876). build_updated_repo_announcement stamped the replacement announcement at existing.created_at + 1, but the relay rejects any event whose created_at drifts more than MAX_TIMESTAMP_DRIFT_SECS (±900s) from server time. Since the advance is +1 and never catches up, once an announcement is >15 min stale every mutation is rejected deterministically — branch protection becomes permanently unchangeable, and repos bind (the documented recovery for #2877) is unreachable. Independent repro by @A-PachecoT confirmed all three commands hit the same path.

Fix

Stamp the replacement with max(now, existing + 1):

  • The +1 floor is preserved. A delayed writer still can't leapfrog an intervening update and silently erase metadata — if it observed a stale head, the floor keeps its replacement ahead of that head. Replay-protection intent unchanged.
  • The wall-clock floor restores freshness. A writer acting on a fresh observation now lands inside the relay's drift window. This is the single shared choke point, so protect set, protect remove, and bind are all fixed by the same change.

crates/buzz-cli/src/commands/repos.rs — the only file touched.

Tests

  • Two pre-existing asserts (assert_eq!(created_at, 101)) encoded the old semantics; relaxed to >= 101 (the floor invariant they actually pin down — wall-clock now (2026+) always exceeds the 100 base, so behavior on a live writer is unchanged).
  • New updated_announcement_stays_within_relay_drift_window_for_stale_head: announcement made 2h ago → replacement drifts ≤900s from wall clock and advances past the stale head. This is the buzz repos protect set and repos protect remove are rejected by the relay's timestamp drift check on any repo announced more than 15 minutes ago #2876 regression, pinned.
  • New protection_set_preserves_metadata_and_protections: auth tag stripped, the matching refs/heads/main rule replaced, unrelated protections + future metadata preserved, and the leapfrog guard verified.
  • cargo test -p buzz-cli --lib repos:: → 17/17 green; full buzz-cli --lib baseline clean; cargo clippy -p buzz-cli --lib clean.

Behavior is timestamp-only — no relay change, no API change. Host: macOS aarch64-apple-darwin; the path is platform-independent Rust.

…k#2876)

build_updated_repo_announcement stamped replacements at existing.created_at+1,
which the relay's ±900s MAX_TIMESTAMP_DRIFT_SECS check rejects once the head is
>15min stale — making protect set/remove and bind permanently fail. max(now,
head+1) keeps the leapfrog floor while restoring wall-clock freshness on a live
writer. Single shared choke point fixes all three commands.

Signed-off-by: Sarthak Singh <[email protected]>
@iroiro147
iroiro147 requested a review from a team as a code owner August 2, 2026 17:10
@chessing234

Copy link
Copy Markdown
Contributor

this is the right fix for #4431/#4432 — head+1 alone drifts outside the ±15m window on old announcements; max(now, head+1) keeps replaceable ordering without the leapfrog risk of always-now.

@iroiro147

Copy link
Copy Markdown
Contributor Author

@chessing234 thanks — that's exactly the reasoning: head+1 alone drifts outside the ±15m window on older announcements, and max(now, head+1) preserves replaceable ordering without the always-now leapfrog. Appreciate you checking the edge case.

This branch has not been deployed

No deployments
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.

2 participants