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

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`
This commit is contained in:
Jarrod Sibbison
2026-09-28 13:04:40 +10:00
committed by GitHub
parent b0d6fb8ad2
commit c4c86006f2
18 changed files with 2587 additions and 46 deletions

View File

@@ -1494,7 +1494,7 @@ LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE AS $$
'community_deletion_requests', 'community_deletion_approvals',
'community_deletion_checkpoints', 'community_serving_write_leases',
'community_deletion_executor_heartbeats', 'product_feedback',
'rate_limit_violations'
'rate_limit_violations', 'operator_listener_outbox'
]::TEXT[])
$$;
@@ -1878,6 +1878,51 @@ CREATE INDEX idx_relay_admin_outbox_pending
INSERT INTO _operator_global_tables (table_name, reason) VALUES
('relay_admin_outbox', 'deployment-global enforcement artifact delivery queue');
-- ── Operator-listener mention delivery ──────────────────────────────────────
-- Listener registrations are deployment-global. The outbox records community
-- provenance for the event, but is intentionally not tenant-owned.
CREATE TABLE operator_listener_pubkeys (
listener_pubkey BYTEA NOT NULL CHECK (length(listener_pubkey) = 32),
target_pubkey BYTEA NOT NULL CHECK (length(target_pubkey) = 32),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (listener_pubkey, target_pubkey)
);
CREATE INDEX operator_listener_pubkeys_target
ON operator_listener_pubkeys (target_pubkey, listener_pubkey);
CREATE INDEX operator_listener_pubkeys_created_at
ON operator_listener_pubkeys (created_at);
CREATE TABLE operator_listener_outbox (
id UUID NOT NULL PRIMARY KEY DEFAULT gen_random_uuid(),
listener_pubkey BYTEA NOT NULL CHECK (length(listener_pubkey) = 32),
target_pubkey BYTEA NOT NULL CHECK (length(target_pubkey) = 32),
community_id UUID NOT NULL,
event_id BYTEA NOT NULL CHECK (length(event_id) = 32),
event_kind INTEGER NOT NULL,
event_created_at TIMESTAMPTZ NOT NULL,
state TEXT NOT NULL DEFAULT 'pending'
CHECK (state IN ('pending', 'sending')),
attempts INTEGER NOT NULL DEFAULT 0 CHECK (attempts >= 0),
next_attempt_at TIMESTAMPTZ NOT NULL DEFAULT now(),
lease_until TIMESTAMPTZ,
claim_id UUID,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (listener_pubkey, target_pubkey, community_id, event_id)
);
CREATE INDEX operator_listener_outbox_due
ON operator_listener_outbox (next_attempt_at, created_at, id)
WHERE state = 'pending';
CREATE INDEX operator_listener_outbox_recovery
ON operator_listener_outbox (lease_until, created_at, id)
WHERE state = 'sending';
CREATE INDEX operator_listener_outbox_created_at
ON operator_listener_outbox (created_at);
INSERT INTO _operator_global_tables (table_name, reason) VALUES
('operator_listener_pubkeys', 'deployment-global target registrations for operator listeners'),
('operator_listener_outbox', 'deployment-global mention delivery queue; community_id is event provenance');
-- ── Relay operator audit (append-only roster mutation trail) ─────────────────
-- One row per PUT/DELETE /operators/{pubkey} mutation. The roster is the
-- deployment-wide root of trust and its mutations overwrite/remove in place;