Files
buzz/crates
npub1jmc9dt2lyvzu3h0kxlwxt5zg4fxp9476awyxw6gwxn72g6cw7exqs64whm b02d767f24 test(conformance): fill search_fts row — two-host NIP-50 isolation + delete
Conformance matrix row `search_fts` (Quinn, buzz-search). Fills the
`pending_lane("buzz-search", ...)` stub at
`crates/buzz-test-client/tests/conformance_multitenant.rs::mod search_fts`
with a two-host A/B-isolation shape: one keypair shared across A and B,
same channel UUID reused in both communities (legal under the
`(community_id, id)` PK), the *same* unique FTS token posted to each
community as kind:9 events but with **community-distinct content**.
NIP-50 search on each host must return exactly one hit carrying that
host's community's content; NIP-09 kind:5 delete in A leaves B's row
intact.

Bar (by my own hands against a live two-host relay on the conformance
recipe — Eva's `:3100` `relay-mt` harness, a/b.localhost, shared
PG/Redis, base PR head `bf8a1a4fa`):

  Clean → GREEN.
  Mutate (both community fences on the search read path, simultaneously):
    crates/buzz-search/src/query.rs:160-161           (FTS WHERE community_id = $ctx)
    crates/buzz-db/src/event.rs:870-872               (get_events_by_ids WHERE community_id = $1)
  → RED on `hits_a.len() == 1` with the failure message listing both
    contents:
      ["A community probe ftsconf_…", "B community probe ftsconf_…"]
  Restore both → GREEN.

Two contract surprises discovered by running the row against the live
relay (the lesson Eva established with nip11_relay_info — obligation
text under-determines the layer):

1. The community fence is doubly defended on the search read path: FTS
   filters at the query layer, then `get_events_by_ids` re-filters at
   the read layer. Mutating either fence alone keeps the
   wire-observable property intact (the other defends). The honest
   mutate-bite is to drop both simultaneously; that's what makes the
   union load-bearing for the wire return. The test's doc comment
   names both layers and explains why the single-layer mutation would
   give a false-green.

2. With identical content in both communities, the Nostr event id is
   the SAME byte string in both rows (id = hash(pubkey, created_at,
   kind, tags, content); community is server-side provenance, not
   serialized into id). Under a leak, the wire returns "the row
   matching id" — which can be either community's row — and a count==1
   assertion can't tell A's row from B's. Earlier iterations of this
   test used identical content and discovered the hard way that
   single-hit-with-other-community's-row is indistinguishable from
   correct behavior at that assertion. Per-community-distinct content
   (`"A community probe {token}"` vs `"B community probe {token}"`)
   makes the leak observable: distinct content hashes to distinct ids
   (different rows), and the assertion `hits[0].content == content_a`
   pins which community's row came back.

Other discipline notes:

- Test is `#[ignore]` by default; selected with `-- --ignored`. Reads
  two env vars: `RELAY_URL_A` / `RELAY_URL_B`, both addressing the same
  relay process on different `Host` headers.
- Requires the two-host harness recipe (`BUZZ_HEALTH_PORT=8180
  BUZZ_METRICS_PORT=9202 BUZZ_RECONCILE_CHANNELS=false
  BUZZ_GIT_CONFORMANCE_PROBE=false`, two `communities` rows mapping
  `a.localhost:3100` and `b.localhost:3100` to distinct community
  UUIDs, one binary). Full recipe in the v2 dependency report at
  `RESEARCH/CONFORMANCE_MATRIX_STATUS_2026-06-27.md`.
- Requires Sami's NIP-42 per-tenant relay-tag fix on PR head
  (`bf8a1a4fa`) — without it, `BuzzTestClient::connect(&ws_a, &keys)`
  fails AUTH on the non-configured host. The row was pre-positioned on
  `809ff9faf` and rebased forward to PR head `bf8a1a4fa` exactly +1
  commit; clean rebase (different file regions from
  auth.rs/bridge.rs/nip42_host_binding_live.rs).
- Conformance lane: this row (`search_fts`) is one of fourteen in the
  file; per Eva's row-ownership contract, this commit touches only the
  `search_fts` module. No other rows modified.

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
2026-06-27 09:23:34 -04:00
..