How Buzz Handles Multi-Community Mode and Data Isolation for FTS Queries
Buzz enforces strict data isolation across communities by scoping every Full-Text Search (FTS) query to a specific community identifier at the PostgreSQL database level, ensuring that events and search indexes from one community never leak into another.
Buzz is an open-source Nostr-based communication platform developed by block/buzz that implements multi-community mode by binding each community to a unique relay URL and its own isolated data slice. When users switch communities, the desktop, web, and mobile clients re-initialize their entire state to guarantee that only the active community’s events, personas, and FTS indexes remain visible. This architecture prevents cross-contamination of data between distinct community contexts.
How Buzz Structures Multi-Community Mode
In Buzz, a community represents a distinct Nostr relay with its own PostgreSQL schema and file system paths. The system treats the relay URL as the canonical community identifier, passing it through every WebSocket connection, HTTP request, and database transaction. When a user executes a command like buzz --community https://relay.example.com community switch, the frontend hook useCommunityInit.ts and the backend resetCommunityState() function collaborate to purge all community-scoped caches—including agent runtimes, persona catalogs, message threads, and FTS result caches—before loading the new community’s data.
Three Layers of Data Isolation
Buzz guarantees data isolation through three distinct architectural layers that intercept requests at different stages of the data lifecycle.
Relay-Level Schema Scoping
At the ingress layer, the buzz-relay crate inspects every incoming WebSocket or HTTP request to extract the target relay URL. According to the source code in crates/buzz-relay/src/relay.rs (lines 70-84), the relay uses this URL to invoke core::db::community_schema, which selects the appropriate PostgreSQL schema for that specific community before any query executes. This ensures that database connections are physically segregated by schema at the network boundary.
Retention-Level DB Isolation
Durable storage isolation is handled by the managed_agents::retention module in the desktop backend. The function scoped_retention_db_path in desktop/src-tauri/src/managed_agents/retention.rs (lines 483-516) constructs database file paths that embed the community identifier directly into the directory structure. This guarantees that events arriving from community A are physically written to a distinct path from community B, preventing filesystem-level collisions or misrouting of persisted data.
FTS Query Isolation at the SQL Layer
The buzz-search crate builds a separate FTS index per community and enforces isolation at query time. When a POST /search request arrives, the handler defined in crates/buzz-search/src/lib.rs (lines 112-138) extracts the active community from the RequestContext and prepends a WHERE community_id = $community clause to the generated SQL. The query then delegates to PostgreSQL’s tsvector engine using plainto_tsquery, but only scans documents where the community_id column matches the request context.
pub async fn search_fts(
ctx: &RequestContext,
query: &str,
) -> Result<Vec<SearchResult>, SearchError> {
let community_id = ctx.community_id(); // pulled from the request’s relay URL
let sql = format!(
"SELECT * FROM fts_index WHERE community_id = $1 AND document @@ plainto_tsquery($2)"
);
sqlx::query_as::<_, SearchResult>(&sql)
.bind(community_id)
.bind(query)
.fetch_all(&ctx.db)
.await
}
State Reset During Community Switching
Community transitions trigger a coordinated cleanup process to eliminate stale data. The frontend orchestrator desktop/src/app/useCommunityInit.ts signals the Tauri backend to invoke resetCommunityState() in desktop/src-tauri/src/managed_agents/retention.rs (lines 20-36). This function terminates active agent runtimes, closes file handles, and drops in-memory caches before re-initializing the connection pool against the new community’s schema. This reset ensures that indexed search data from the previous community cannot persist in memory or influence subsequent queries.
Verifying Isolation with Integration Tests
The project validates cross-community isolation through dedicated integration tests located in crates/buzz-search/tests/fts_integration.rs (lines 25-64). These tests seed two separate communities with identical document content and verify that a search_fts call scoped to community A returns zero results from community B. The test suite uses sqlx to assert that the community_id filter effectively partitions the tsvector index, providing empirical proof that the isolation logic is impervious to content collisions.
Practical CLI Examples
You can observe this isolation behavior directly through the Buzz CLI:
# Switch to a different community (desktop, web, or mobile)
buzz --community https://relay.example.com community switch
# Perform an FTS search limited to the active community
buzz --format compact search "project roadmap"
# The same query on a different community yields a distinct result set
buzz --community https://other-relay.example.com --format compact search "project roadmap"
Even when both communities contain identical content matching "project roadmap," the second command returns only the results indexed under https://other-relay.example.com, demonstrating the per-community scoping of the FTS engine.
Summary
- Buzz implements multi-community mode by mapping each community to a unique Nostr relay URL and dedicated PostgreSQL schema.
- Data isolation is enforced at three layers: relay-level schema selection (
buzz-relay/src/relay.rs), retention-level database path scoping (scoped_retention_db_path), and FTS-level SQL filtering (buzz-search/src/lib.rs). - Every FTS query automatically includes a
community_idpredicate that restricts PostgreSQL’stsvectorsearch to the active community’s index slice. - Community switching triggers a complete state reset via
resetCommunityState()anduseCommunityInit.ts, ensuring no cached data leaks between contexts. - Integration tests in
fts_integration.rsempirically verify that cross-community data leakage is impossible even with identical content.
Frequently Asked Questions
How does Buzz prevent cross-community data leaks during FTS searches?
Buzz prevents leaks by injecting a mandatory WHERE community_id = $1 clause into every FTS SQL query before execution. As implemented in crates/buzz-search/src/lib.rs, the search_fts function extracts the community identifier from the request context and binds it as a prepared statement parameter, ensuring PostgreSQL’s tsvector engine only scans documents belonging to that specific community.
What happens to cached data when I switch communities in Buzz?
When switching communities, the UI immediately clears all community-scoped caches including the agent runtime, persona catalog, message threads, and FTS result buffers. The frontend hook useCommunityInit.ts and the backend function resetCommunityState() in desktop/src-tauri/src/managed_agents/retention.rs orchestrate this purge to guarantee no stale indexes or events persist in memory.
Where is the community identifier stored during an FTS query?
The community identifier is derived from the request’s relay URL and stored within the RequestContext object that accompanies every search request. According to the source code, ctx.community_id() retrieves this value in crates/buzz-search/src/lib.rs, which the handler then binds to the SQL query as a scoping parameter.
How does Buzz test FTS isolation between communities?
The project includes integration tests in crates/buzz-search/tests/fts_integration.rs that populate multiple communities with identical content and assert that queries remain strictly partitioned. These tests verify that the community_id filter in the generated SQL prevents results from one community appearing in another’s search results, even when the underlying text matches exactly.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →