block / block/buzz

Archive sync flush re-derives identity from current AppState, dropping in-flight events across an identity/community switch

Open
#6,149 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
32.7k
Forks
4.3k
Avg merge
1d 13h
Merged PRs (30d)
253

Description

## Summary

`AppIo::archive` re-reads identity and relay from the current global `AppState` at flush time, rather than using the scope the archive sync task was started with. An identity or community switch between "events buffered" and "buffer flushed" validates scope-A events under scope-B credentials, and the backend drops them.

Found by review (Carl) on #6024; confirmed against source and scoped there before deferring here.

## Mechanism

`archive/sync.rs:294-302`:

```rust
fn archive(&self, candidates: Vec) -> BoxFuture<'_, Result> {
Box::pin(async move {
let state: State<'_, AppState> = self.app.state();
super::archive_candidates(&state, candidates).await
})
}
```

`archive_candidates` (`archive/mod.rs:166-167`) then derives `identity_pubkey(state)` and `relay_ws_url_with_override(state)` from whatever is current *now*. `run_sync` flushes its buffered batch after `cancel` fires (`sync.rs:233`), which is exactly when a switch has already landed.

Meanwhile `start_archive_sync` already captures the scope it was started under (`sync.rs:505`) — it just isn't threaded into `AppIo`.

## Not a regression

Base `78cbffeb6` had the same race through the renderer: `archiveSyncManager.destroy()` did `void this.deps.archiveEvents(batch)`, unawaited, into the same backend-scoped `archive_candidates`, which re-read identity the same way.

What #6024 changed is the width of the window, not its existence. The JS flush went out synchronously in the cleanup tick; the native flush happens after `cancel` propagates to a tokio task that may be parked mid-`reconcile`. Wider, same shape.

Filed separately rather than added to #6024: pre-existing, and a different subsystem from the two blockers that PR fixes.

## Suggested fix

Capture `(identity_pubkey, relay_url)` into `AppIo` at construction — `start_archive_sync` already has both in `scope` — and pass them to an `archive_candidates` variant that takes them explicitly instead of re-deriving from `AppState`. A flush that finds the scope has moved should then drop or refuse rather than silently validate under the wrong identity.

## Impact

Events are dropped, not mis-attributed: the backend re-verifies scope claims, so scope-A events fail validation under B. Data loss is bounded by one in-flight batch at switch time, and only for the ephemeral (`owner_p`) scope is it unrecoverable — persistent scopes re-query the relay.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.