mirror of
https://github.com/block/buzz.git
synced 2026-11-07 09:52:03 +00:00
Round-six review found three holes in the tenant-scope guard: 1. Relay URL and signing keys mutate under SEPARATE locks during a workspace switch, so a switch landing between the relay check and the key read signed (and NIP-98-authenticated) the old tenant's content under the NEW tenant's identity. Commands now read ONE key snapshot, assert it against a caller-captured `expected_signer_pubkey` (`assert_expected_signer` in relay/scope.rs, fail-closed, None = unscoped), and use that exact snapshot for every signature and NIP-98 auth: `open_dm` pins the submit and the metadata read (`query_relay_at_with_keys`); `send_channel_message` pins the submit and threads the snapshot through `resolve_thread_ref`; `submit_event_at_created_at` takes explicit `&Keys` instead of re-reading state. 2. `start_managed_agent` was an unscoped side effect — a stale Projects callback suspended on the startup await could activate the (agent, relay) pair in the new tenant. It now takes `expected_relay_url`/`expected_signer_pubkey`, asserts both up front, re-asserts the relay scope after the mesh-preflight await before the local spawn, and asserts the provider deploy against the relay embedded in the deploy payload rather than a re-read. 3. `restoreProjectsAgentConversation` rejected strangers but never required the signed-in user's own membership; a stale pointer naming an agent-only channel had no stranger to reject. `hasSelf` is now required alongside `hasAgent` and the stranger check. The scope pair rides all three side effects of a Projects agent send (start/openDm/send) in projectAgentConversation.ts; both UI callers capture `signerScope` from the identity query before the first await. The e2e bridge mirrors `assertExpectedSigner` on all three handlers and gains `startManagedAgentDelayMs` for racing startup in specs. Unit regressions: signer match/mismatch/absent in scope.rs; an identity-swap-racing-the-send test and a blocked-startup rewrite plus scope-pair assertions in projectAgentConversation.test.mjs; an agent-only-channel restore rejection. The retention-queue helpers (retain/tombstone/archive pending, build_agent_archive_request) move verbatim from agents.rs to commands/agents_pending.rs to satisfy the file-size ratchet agents.rs would otherwise breach. Co-authored-by: Thomas Petersen <thomasp@squareup.com> Signed-off-by: Thomas Petersen <thomasp@squareup.com>