Files
buzz/migrations/0012_draft_wrap_fts.sql
T
npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7andWill Pfleger 5c3ac62860 fix(relay): close write-path id-oracle and complete Q2-Q16 review fixes
Add actor_can_reference_target helper to mask author-only event ids
on all five write-path target-resolution sites (reaction channel
derivation, NIP-10 thread parent, stream-edit, forum-vote, kind:5
e-tag deletion), preventing non-authors from distinguishing a real
draft/reminder id from a random id via differential error responses.

Q2: DbError::DraftChannelRequired + channel_id=None DB-layer rejection
Q3: participates_in_thread_metadata split; outer e-tag rejection on drafts
Q4: reader_can_receive_event canonical read gate in buzz-core/filter.rs;
     wire into req.rs / count.rs / bridge.rs / search path
Q5: CI step for buzz-search FTS integration tests (ignored suite now runs)
Q6: HTTP catchall kindless privacy e2e test_draft_not_returned_in_kindless_channel_http_query
Q7: draft_kind_is_excluded_from_workflow_dispatch_by_author_only_guard
Q8: a-tag defensive-guard comment rewrite in side_effects.rs
Q9: e2e smoke for outer-e-tag rejection (test_draft_rejected_outer_e_tag)
Q10: fan-out author-side positive control (test_draft_live_fanout_reaches_author_own_subscription)
Q11: mixed-kinds /count test (test_draft_attacker_mixed_kinds_count_excludes_drafts)
Q12: derive exclusion list from AUTHOR_ONLY_KINDS in thread.rs
Q13: DM privacy recipient-side test (test_dm_draft_not_readable_by_dm_recipient)
Q14: migration 0007 deploy-window note
Q15: actor_can_reference_target post-lookup helper applied at reaction channel
     derivation (all actors rejected), NIP-10 thread parent (all actors
     rejected), stream-edit / forum-vote (non-author masked, author falls
     through), kind:5 e-tag deletion (non-author masked before authz,
     false comment corrected); generalizes over AUTHOR_ONLY_KINDS so
     kind:30300 reminders are also protected; unit tests confirm membership
Q16: channel-window cursor-boundary e2e (test_channel_window_cursor_boundary_excludes_draft)
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
2026-07-14 00:21:25 -04:00

44 lines
2.4 KiB
SQL

-- Exclude kind 31234 (NIP-37 draft wraps) from full-text search.
--
-- NIP-37 draft wraps carry NIP-44-v2 ciphertext in `content` (or empty string
-- for deletion tombstones). Indexing ciphertext would waste storage and violate
-- the "channel/DM context lives only inside the encrypted payload" invariant
-- that makes draft wraps author-private. The relay also must not index
-- plaintext compose context through any search surface.
--
-- Additive migration: previously applied files must not change checksum.
-- We must DROP the generated column and re-ADD it with the extended exclusion
-- list; ALTER COLUMN cannot change a GENERATED expression in Postgres.
--
-- DEPLOY NOTE: the DROP + re-ADD below acquires ACCESS EXCLUSIVE on `events`
-- for the duration of the migration, blocking all reads and writes to the
-- table. The GIN index rebuild that follows is a full table scan. On large
-- deployments this migration should run during a scheduled maintenance window.
-- This is the same shape as migration 0005 and was accepted as precedent.
--
-- Final kind exclusion list after this migration:
-- 1059 = KIND_GIFT_WRAP (NIP-17 ciphertext)
-- 30300 = KIND_EVENT_REMINDER (AUTHOR_ONLY_KINDS — defense in depth)
-- 30622 = KIND_DM_VISIBILITY (per-viewer private hide state)
-- 31234 = KIND_DRAFT (NIP-37: AUTHOR_ONLY_KINDS — ciphertext or tombstone)
-- 44100 = KIND_MEMBER_ADDED_NOTIFICATION (p-gated membership notice)
-- 44101 = KIND_MEMBER_REMOVED_NOTIFICATION (p-gated membership notice)
-- 44200 = KIND_AGENT_TURN_METRIC (NIP-AM: p-gated encrypted turn metrics)
-- Constants kept in `buzz_core::kind`; inlined here because a sqlx migration
-- is frozen SQL and cannot import the Rust constant. If a new privacy-sensitive
-- kind is added there, add a new additive migration following this pattern and
-- add a regression test in `buzz-search/tests/fts_integration.rs`.
--
-- NULL tsvector never matches `@@`, so excluded rows are storage-level
-- unsearchable.
ALTER TABLE events DROP COLUMN search_tsv;
ALTER TABLE events ADD COLUMN search_tsv TSVECTOR GENERATED ALWAYS AS (
CASE WHEN kind IN (1059, 30300, 30622, 31234, 44100, 44101, 44200) THEN NULL::tsvector
ELSE to_tsvector('simple', content)
END
) STORED;
-- Recreate the GIN index dropped with the column.
CREATE INDEX idx_events_search_tsv ON events USING GIN (search_tsv);