Skip to content

feat(relay): deliver pubkey mentions to relay companions - #7793

Open
jsibbison-square wants to merge 1 commit into
mainfrom
jsib-260922-operator-listener-mentions-v2
Open

jsibbison-square wants to merge 1 commit into
mainfrom
jsib-260922-operator-listener-mentions-v2

Conversation

@jsibbison-square

@jsibbison-square jsibbison-square commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

We are introducing a companion service to the relay that needs pubkey event mentions for pubkeys it manages. This PR introduces an outbox for pubkey mentions that will be sent to these "operator listener" services.

Summary

Deliver mentions of registered pubkeys to deployment-global operator listeners.

  • Configure listeners with BUZZ_OPERATOR_LISTENERS; listeners manage target pubkeys through NIP-98-authenticated POST/DELETE /operator/listener/pubkeys requests.
  • During supported event ingestion, match p tags to registrations and insert one outbox row per listener in the event transaction.
  • A background worker claims up to 10 rows, sends NIP-98-signed notifications, and retries transient failures with exponential backoff for up to 9 attempts (idle polling backs off from 250 ms to 2 s).
  • Outbox rows are ineligible for delivery after 15 minutes and are reaped every 5 minutes. Registered pubkeys live for 30 days; re-registering renews them, and a daily reaper removes expired registrations.
  • Keep persistence app-managed: no database triggers, functions, foreign keys, or separate match queue.

Changed behavior

Listener outbox rows are enqueued inside the shared event-insert and thread-metadata transactions; enqueue failure rolls back the event. event_mentions behavior is unchanged: ordinary event/reaction mention indexing remains post-commit best effort with warning-only failures.

Testing

  • cargo fmt --all
  • cargo test -p buzz-db --lib
  • cargo test -p buzz-relay --lib config::tests::operator_listener_routes
  • cargo check -p buzz-db -p buzz-relay

@github-actions

github-actions Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

🔐 Codex Security Review

Status: review required for the current range.

The current range is 20131488528e35e6c50f4ccdb0490a9135c28edf...398999b81a75fc4f0da28a64627db13caf9a3ddb.
A new review must complete for this exact range. When manual authorization
is required, a Block organization member must comment exactly
@buzz-security-review 398999b81a75fc4f0da28a64627db13caf9a3ddb to authorize a new review.
Any previous review applies only to its recorded range.

@jsibbison-square
jsibbison-square force-pushed the jsib-260922-operator-listener-mentions-v2 branch 11 times, most recently from 4eacb6c to 43597fb Compare September 23, 2026 03:18
@jsibbison-square
jsibbison-square marked this pull request as ready for review September 23, 2026 04:53
@jsibbison-square
jsibbison-square requested a review from a team as a code owner September 23, 2026 04:53
@TheSentinel454

Copy link
Copy Markdown
Contributor

I reviewed this at exact head 43597fbfedb7820f4ad5f3da136071bbfcf2d20d. Two issues look worth addressing before merge:

P1: deterministic HTTP failures consume the full retry budget

In crates/buzz-relay/src/operator_listener.rs:281,299-326, every non-2xx response enters retry_or_fail. That means permanent 400/401/403/404 responses are retried up to MAX_DELIVERY_ATTEMPTS (9 total sends), despite the intended contract being to retry transient failures. We reproduced this through the live relay-to-companion path: a deterministic 400 generated nine POSTs before terminal removal.

Could we classify outcomes so transport errors, 408, 429, and 5xx retry, while ordinary deterministic 4xx responses fail immediately? Tests should distinguish a one-call terminal 400 from retrying 429/503 responses.

P1: default redirect following can silently complete delivery at the wrong endpoint

The reqwest client at crates/buzz-relay/src/operator_listener.rs:64-66 uses the default redirect policy, while the NIP-98 event is signed once for the configured URL. A redirect can change the request method or replay the body at another destination; if the final response is 2xx, deliver_one treats it as accepted and deletes the outbox row even though the configured signed endpoint never accepted that request. This creates a silent delivery-loss path and expands the destination boundary for mention metadata.

Could we construct the client with reqwest::redirect::Policy::none() and treat 3xx as a terminal endpoint/configuration failure? A regression test using the real reqwest transport should prove a redirected endpoint receives nothing and the delivery is not completed.

Architecture question (not classified as a defect)

Is deployment-global listener registration—and therefore release of mention metadata across community boundaries—an intentional operator-trust exception?

Migration 0049 explicitly says registrations span communities, while the isolated-community direction in VISION.md:52-56, VISION_REMOTE_AGENTS.md:15, and docs/multi-tenant-relay.md:9-21 points toward community-scoped boundaries. If the global scope is intentional, could that exception and its trust model be documented explicitly? If it is not intentional, would registrations and delivery be safer scoped to the originating community?

@jsibbison-square
jsibbison-square force-pushed the jsib-260922-operator-listener-mentions-v2 branch 2 times, most recently from 8bd65ce to 291c1cd Compare September 24, 2026 00:35
@jsibbison-square

Copy link
Copy Markdown
Contributor Author

@TheSentinel454 fixed "deterministic HTTP failures consume the full retry budget" and "default redirect following can silently complete delivery at the wrong endpoint".

Regarding "Architecture question" yes this is deliberately operator level as our service needs to be pushed information without knowing all the communities that the agents may be in. Therefore we cannot register with community fencing upfront. It's not a subscription registration in a normal user sense.

@jsibbison-square
jsibbison-square force-pushed the jsib-260922-operator-listener-mentions-v2 branch from 291c1cd to 501f93e Compare September 24, 2026 01:26
Comment thread crates/buzz-relay/src/operator_listener.rs
@jsibbison-square
jsibbison-square force-pushed the jsib-260922-operator-listener-mentions-v2 branch from 501f93e to c6c9ee0 Compare September 24, 2026 22:22
Signed-off-by: jsibbison-square <jsibbison@squareup.com>
@jsibbison-square
jsibbison-square force-pushed the jsib-260922-operator-listener-mentions-v2 branch from c6c9ee0 to 398999b Compare September 24, 2026 23:45
() => typeof window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__ === "function",
);
await page.getByTestId("channel-deep-history").click();
await expect

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 — the new readiness wait can still admit the live-event race (async lifecycle/races; discriminating regression tests)

__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__ is called without kind or exactChannel. Its non-exact implementation treats any wildcard ("*") subscription as readiness for this channel, so this poll can resolve before the channel's kind-39005 live REQ and head refresh are actually settled. The following mock message can then be sent into that gap; the UI still shows Jump to latest but never increments it to 1. I reproduced that exact failure once in three no-retry repetitions at this head (2 passed / 1 failed, line 931 received "Jump to latest"), while a later repeat-10 happened to pass—classic race/flake behavior that CI's two retries can conceal.

Please use the existing waitForMockChannelHeadReady(page, "deep-history", "feedf00d-0000-4000-8000-000000000007") helper, which waits for the exact kind-39005 live REQ, the subsequent null-cursor head refresh, and idle query state; or make this check equivalently exact. Then run this case repeatedly with retries disabled and preserve the assertion that the buffered count reaches 1.

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