镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

feat(relay): index reactions under their target author for #p reads - #9285

Open
bostonaholic wants to merge 7 commits into
mainfrom
bostonaholic--relay-reaction-recipients
Open

bostonaholic wants to merge 7 commits into
mainfrom
bostonaholic--relay-reaction-recipients

Conversation

@bostonaholic

@bostonaholic bostonaholic commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Why

buzz-app's Activity → Reactions tab (block/buzz-app#2638) finds reactions to your messages with {kinds:[7], "#p":[you]}. A reaction shows up there only if the client that sent it tagged the message author (p, per NIP-25). Today buzz-app and its MCP tool do this. Mobile does it since #9141 (merged); the CLI change is #9121. Agents and third-party clients may never do it. With this PR the relay works out the recipient from the reacted-to message, so the tab works for reactions from every client.

What

  • When the relay stores a reaction, it now also indexes the reacted-to message's author as a #p recipient. That author is the message's effective author: the actor (or leading p) for relay-signed messages, otherwise the signer. The relay looks up the target message during ingest; it never reads the author from the reaction.
  • #p reads and COUNT return those reactions to the message author, whether the filter has one #p value or several. Nothing is added to the signed event, so clients receive exactly what was sent.
  • Derived recipients apply to kind 7 only. Old p index rows for other kinds stay hidden, as they are today (fix: don't notify workflow owners through their attribution tag #8694).
  • scripts/maintenance/backfill_reaction_target_mentions.sql repairs reactions stored before this change, under docs/database.md Rule 3:
    • walks stored kind:7 events, so reactions with no reactions.reaction_event_id link are covered
    • batches of 5,000
    • takes the community-deletion lock
    • idempotent, with a validation query
    • Run it once this relay version is fully deployed.
  • New counter buzz_relay_reaction_recipient_derived_total{outcome} with outcomes inserted, duplicate and fallback_pubkey.
  • NOSTR.md kind 7 now notes the derived recipient. Clients should still tag p, per NIP-25.

Design: relay-derived reaction recipients (slices 1, 2, 4 and the block/buzz docs). The buzz-app side is block/buzz-app#3115. The Activity reactions tab this serves is block/buzz-app#2638.

Testing

  • Matcher (unit): #p matches a derived recipient with no p tag. It does not match when there is none or it is a different pubkey. Literal p tags still match, and the #h fallback is unchanged.
  • Postgres (via ingest, REQ and COUNT):
    • An untagged reaction is returned to the message author and not to the reactor.
    • A relay-signed message with actor resolves to the actor.
    • A reaction that also tags p produces exactly one index row.
    • COUNT matches REQ.
    • A two-value #p REQ and COUNT return the same reactions as a single-value read.
    • Kind-7-only matching is guarded by its own test.
    • Each test fails without its change. Mutation checks turned the tests red when the derived insert was skipped, when the raw signer was used, when the kind-7 restriction was dropped, and when the multi-value #p recipient load was skipped.
  • Backfill against local Postgres: 12,005 seeded reactions covered user-signed, relay-signed (actor / p / neither) and soft-deleted targets.
    • The first run inserted 12,009 rows; the second inserted 0.
    • The validation query returned 0 after both runs.
    • After walking events directly: a reaction with no source-event link was indexed under its target's author, the last valid e tag chose the target, a soft-deleted target was skipped, and a batch size of 1 paged through reactions sharing one timestamp without gaps. Rerun inserted 0; validation returned 0.
    • A quiescing community was untouched.
    • A missing or invalid relay_pubkey_hex exits before writing anything.
  • Checks:
    • cargo fmt --check and cargo clippy --workspace --all-targets -D warnings pass.
    • cargo test passes for buzz-core, for buzz-db (unit tests plus the Postgres tests for event, reaction and feed), and for the buzz-relay handlers and subscription modules.
  • Not run:
    • 2 cluster_global_* relay Postgres tests. They need a database role with CREATEROLE, which the local buzz role lacks; Docker isn't available here.
    • The relay Postgres suite in parallel. It interferes with itself on one shared database; run serially, everything else passed.
  • Not yet done: a human test against a running relay.

Known limits

  • Across relay nodes, a live #p + #h subscription on another node does not receive untagged reactions: the Redis fan-out rebuilds the event without the derived recipient. buzz-app subscribes by #h only, so Activity is not affected.

Rollout (docs/database.md Rule 3 order)

  1. Deploy the relay write path. New reactions are indexed from then on. There is no flag; the change only adds index rows.
  2. Once every older relay is drained, run scripts/maintenance/backfill_reaction_target_mentions.sql, passing relay_pubkey_hex. Its validation query (stored reactions with a live target but no index row for the target's author) must return 0. Re-run the script if it doesn't: it is idempotent.
  3. Ship feat(activity): show reactions that omit the viewer's p tag buzz-app#3115. Until it ships, buzz-app still requires the literal p tag.

Rollback: revert the relay. The extra index rows are inert, because the previous binary's re-check drops them.

🤖 Generated with Claude Code

bostonaholic and others added 4 commits November 9, 2026 09:17
Slice 1 of relay-derived reaction recipients: matcher knows derived
recipients.

StoredEvent gains derived_recipients, hex pubkeys the relay computed
from stored data. A #p filter now matches an event when any filter value
is a literal p tag or a derived recipient. The field is never sent to
clients; the wire event stays the signed event. #h channel fallback is
unchanged.

Co-authored-by: mboston Implementer <932d3b6ebea6cc83e091551ae46d1c7e7f8f7d0fa8e9cef5e6facc8544af413b@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Matthew Boston <mboston@squareup.com>
Slice 2 of relay-derived reaction recipients: write and read path.

Ingest resolves a reaction target's effective author (actor tag for
relay-signed rows, else the signer) and buzz-db indexes the reaction in
event_mentions under that author, in the same transaction as the event,
with ON CONFLICT DO NOTHING so a reaction that already tags the author
keeps one row. The rows are written for channel-less targets too.

The #p read path returns the matched m.pubkey_hex, and a kind:7 row
carries it in derived_recipients, so the REQ and COUNT re-check accepts
a reaction without a p tag. Other kinds still match #p by literal tags
only: their mention rows can name a pubkey no tag matches, such as a
workflow owner's attribution indexed before that stopped.

Adds buzz_relay_reaction_recipient_derived_total{outcome=inserted|
duplicate|fallback_pubkey}; fallback_pubkey is a relay-signed target
with no resolvable author, indexed under the relay key.

Co-authored-by: mboston Implementer <932d3b6ebea6cc83e091551ae46d1c7e7f8f7d0fa8e9cef5e6facc8544af413b@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Matthew Boston <mboston@squareup.com>
Slice 4 of relay-derived reaction recipients: backfill script.

scripts/maintenance/backfill_reaction_target_mentions.sql indexes every
stored kind:7 reaction under its target's effective author in
event_mentions, for reactions stored before ingest wrote that row. It
follows docs/database.md Rule 3: a top-level DO block commits 5,000
reactions per batch, each batch takes the shared community deletion lock
for its communities and writes only to active, undeleted ones, inserts
use ON CONFLICT DO NOTHING, and a final validation query must print 0.
The author resolution matches effective_message_author: actor, then p,
then the signer for relay-signed targets.

Run it after every serving relay writes the row at ingest. Local run
against 12,005 seeded reactions in an active community and 5 in a
quiescing one: first run inserted 12,003 rows in the active community
(plus 6 for older local test reactions), second run inserted 0,
validation printed 0 both times, and the quiescing community kept 0
mention rows.

Co-authored-by: mboston Implementer <932d3b6ebea6cc83e091551ae46d1c7e7f8f7d0fa8e9cef5e6facc8544af413b@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Matthew Boston <mboston@squareup.com>
Slice 5 of relay-derived reaction recipients (block/buzz half): docs.

The kind:7 row in NOSTR.md now says the relay indexes the target's
author as a #p recipient of the reaction, and that clients SHOULD still
tag p per NIP-25.

Co-authored-by: mboston Implementer <932d3b6ebea6cc83e091551ae46d1c7e7f8f7d0fa8e9cef5e6facc8544af413b@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Matthew Boston <mboston@squareup.com>
@bostonaholic

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-09T17:38:59.955501Z c957585 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9dcc9c90e7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/buzz-db/src/store/event.rs Resolved
A multi-value `#p` filter is not pushed into SQL, so reactions came back
without derived recipients and the `#p` post-filter dropped untagged
reactions in REQ history and the COUNT fallback. The store now loads the
matching mention rows for returned reactions when the filter has several
`#p` values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Matthew Boston <mboston@squareup.com>
@bostonaholic

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a3bfb0ea43

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

The backfill walked `reactions` rows with a `reaction_event_id`, so a
reaction stored while `set_reaction_event_id` was best-effort never got
its target-author mention, and the validation query skipped it too.

Walk stored kind:7 events per community instead, resolving the target
from the last 64-hex `e` tag as ingest does. Validation now counts every
stored kind:7.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Matthew Boston <mboston@squareup.com>
@bostonaholic

Copy link
Copy Markdown
Contributor Author

@codex review

@bostonaholic
bostonaholic marked this pull request as ready for review November 9, 2026 17:21
@bostonaholic
bostonaholic requested a review from a team as a code owner November 9, 2026 17:21

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5b25d76715

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/buzz-core/src/filter.rs Resolved
A global {kinds:[7], "#p":[author]} subscription now receives an
untagged reaction to a channel-less event live, matching what a
historical REQ returns.

Co-authored-by: mboston Implementer <932d3b6ebea6cc83e091551ae46d1c7e7f8f7d0fa8e9cef5e6facc8544af413b@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Matthew Boston <mboston@squareup.com>
@bostonaholic

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

Reviewed commit: c95758507e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

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.

1 participant