mirror of
https://github.com/block/buzz.git
synced 2026-10-31 06:21:34 +00:00
## Summary **Merge order:** this lands and deploys **before** [#8688](https://github.com/block/buzz/pull/8688). That PR's buzz-acp resume cursor counts on a failed history read never ending in EOSE. Until #8688 ships, buzz-acp re-sends a REQ as soon as it gets an error CLOSED. The relay's REQ rate limit bounds that loop, and buzz-acp already parks on a rate-limited CLOSED. #8688 replaces this with a per-subscription backoff. When a historical query fails for a reason other than a statement timeout, the relay logs the error and still sends `EOSE`. EOSE means "you have every stored event" (`ARCHITECTURE.md` §5, NIP-01). A client that resumes from its last-seen point therefore treats the failed read as complete, advances past it, and never replays that history. This is RPR #1: a failure must not become an authoritative empty result. The history task now returns the CLOSED reason to send: - **Statement timeout:** `["CLOSED", sub_id, "error: query timed out"]`, unchanged. - **Any other DB error:** `["CLOSED", sub_id, "error: database error"]`. - **Cleanup:** CLOSED goes through the existing `close_if_owner` path, so the subscription, its sub-registry filters and its topic refcount are released, the same as on a timeout. - **Docs:** `ARCHITECTURE.md` §5 now states this contract. **Clients don't hot-loop on it.** Checked at `main`7c165c37: - **Desktop:** `relayClosedPolicy.ts` / `relayClosedRecovery.ts` classify `error: database error` as retryable. Live subscriptions back off from 1s to 30s and keep their `since`. History-mode requests reject. - **Mobile:** `relay_closed_policy.dart` / `relay_session.dart` do the same, with their own capped backoff. - **buzz-acp:** today it re-REQs at once, bounded by the relay's REQ rate limit. [#8688](https://github.com/block/buzz/pull/8688) adds a per-subscription backoff that also restores the loss record. ### What got simpler - **One constant for the CLOSED reason.** `"error: database error"` was a string literal repeated across `req.rs` (5×) and `count.rs` (4×). It is now one constant, `DATABASE_ERROR_CLOSED`. - **One mapping function.** `history_read_closed_reason` maps a DB error to its CLOSED reason in one place. ### Identified debt (not changed here) - **NIP-50 search path.** After a non-timeout full-text-search or hydrate error, the search path still sends `EOSE` with partial results. It needs the same treatment, but it has a separate result-assembly path, so it's left out of this PR. ### Related issue Adds the regression wpfleger96 asked for in [this review on #8653](https://github.com/block/buzz/pull/8653#pullrequestreview-5692776411): a history error followed by a reconnect still replays the history. ### Testing New Postgres test `api::bridge::postgres_tests::failed_history_read_closes_and_reconnect_replays_the_history`: 1. Lock the replica's `events` table past its 300ms `statement_timeout` and send a history REQ. It must get exactly `["CLOSED","t","error: query timed out"]`. 2. Break the `events` table on both the writer and replica pools and send a REQ. It must get exactly `["CLOSED","h","error: database error"]`. 3. After each CLOSED, the subscription map, the sub-registry filters and the topic refcount must all be cleared. 4. Restore the table and send the same REQ on a new connection. It must get the stored event, then `EOSE`. Run on Blox at95a8255a: - `cargo fmt --check` - `cargo clippy -p buzz-relay --all-targets -D warnings` - Postgres lane via `scripts/postgres-test-run.sh -p buzz-relay` (499/499) - The new test and `routed_read_cancel_maps_to_timeout_contract` 5× - **Mutation checks:** each of these makes the new test fail at its frame assertion. - Restoring `EOSE` for a non-timeout error. - Mapping every failure to `error: database error`. - **`just test` integration half:** run by hand, because `_ensure-services` can't start its Redis container on that workstation while a native redis-server holds the port. - Workspace `cargo test --test '*'`: every binary passes except two `buzz-acp` `git_bootstrap` tests. - Those two fail the same way on that workstation without this change, so they come from the environment. - `buzz-db` and `buzz-auth` integration tests pass. End-to-end against a locally built relay ate5621f94, with buzz-acp from #8688: - **While `events` was missing:** every channel REQ got `CLOSED error: database error`, and no channel EOSE was sent. - **After the table was restored:** the next retry replayed the missed mention, followed by EOSE. The later commit only changes tests and docs. buzz-review-completed 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Luke Tornquist <tornquist@squareup.com> Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>