feat(relay): rework NIP-37 drafts to channel-bound contract

Draft wraps (kind:31234) now require exactly one `h` UUID tag binding
them to a specific Buzz channel or DM. The relay enforces:

- Exactly one valid UUID `h` tag on every kind:31234 event
- Channel existence: the `h` UUID must resolve to a live channel
- Membership: author must be a member of that channel at write time
- Immutable binding: once a (author, d_tag) draft is written to
  channel A, replacement events must carry the same h=A; rebinding
  to a different channel is rejected at the ingest layer

The previous channel-less/global-state design is removed. Draft fan-out
already applied the author-only gate (AUTHOR_ONLY_KINDS); with channel_id
now non-NULL for kind:31234, the existing channel visibility/membership
filter in fan-out applies naturally with no additional changes.

E2E test suite rewritten for the channel-bound contract:
- h-tag validation: missing, duplicate, non-UUID, nonexistent channel
- Non-member author rejection + removed-member regression
- Immutable binding: rebind rejected, same-channel replacement accepted
- Author-only reads: WS REQ/COUNT, HTTP /query, /count, live fan-out
- known-#d privacy tripwires (exclusive and kindless)
- Tombstone head queryable by author, tombstone replaces live draft
- NIP-01 same-second tie-break (distinct candidates enforced)
- Stale write cannot supersede current head
- Workflow / channel kindless query exclusion
- Tenant confinement (alien channel rejected)
- FTS exclusion (NULL search_tsv confirmed)
- NIP-11 advertises NIP-37, not NIP-40

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
This commit is contained in:
npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7
2026-07-14 00:19:32 -04:00
committed by Will Pfleger
co-authored by Will Pfleger
parent 79a9a89a4d
commit 49fce89952
5 changed files with 906 additions and 276 deletions
+6 -5
View File
@@ -611,11 +611,12 @@ jobs:
env:
RELAY_URL: ws://localhost:3000
- name: NIP-37 draft wrap e2e
# Feature e2e for NIP-37 draft wraps (kind:31234): write-path
# validation, NIP-01 replacement/tombstone ordering, author-only privacy
# across all read paths (WS REQ, WS COUNT, HTTP /query, /count, live
# fan-out), known-d privacy tripwires, FTS/NIP-50 exclusion, and NIP-11
# advertisement.
# Feature e2e for NIP-37 draft wraps (kind:31234), channel-bound
# contract: h-tag validation, channel existence + membership gates,
# immutable channel binding, author-only privacy (WS REQ, WS COUNT,
# HTTP /query, /count, live fan-out), known-d privacy tripwires,
# FTS/NIP-50 exclusion, workflow exclusion, tenant confinement, and
# NIP-11 advertisement.
run: cargo test --profile ci -p buzz-test-client --test e2e_nip37_draft -- --ignored
env:
RELAY_URL: ws://localhost:3000