Issue 3 reopen (Max): buzz-admin's resolve_admin_tenant derived its
lookup host with Url::host_str(), which drops an explicit non-default
port and IPv6 brackets. For the default RELAY_URL ws://localhost:3000 the
admin would look up community host `localhost` while startup seeding (and
live request resolution) bind `localhost:3000` — so the admin CLI's
membership writes would miss, or hit, the wrong deployment community.
Lift relay_url_authority into buzz-core::tenant as the single canonical
helper so the relay's host-resolution seam (startup seeding,
bind_deployment_community) and the buzz-admin CLI derive a byte-identical
authority: host plus explicit non-default port, IPv6 brackets preserved,
default ports collapsed — exactly as normalize_host shapes an inbound
Host header. The relay tenant module now `pub use`-re-exports it (no
behavior change at the relay seam); buzz-admin calls it directly.
Adds 4 buzz-core unit tests pinning the authority shape:
non-default-port retention (localhost:3000, relay.example:8443),
default-port collapse (:443/:80), IPv6 brackets ([::1]:3000), and
unparseable/empty -> empty (callers fail closed on empty).
Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
NIP-43 admission confinement (Max review #3 on PR #1321). The
`relay_members` table is keyed `(community_id, pubkey)`, but every DB
access keyed on `pubkey` alone. In closed mode a pubkey admitted to
community A was therefore admitted to community B — the exact M9
mutation #1285 targets.
Request-path scoping:
- thread `CommunityId` through all 9 `buzz-db::relay_members` functions
and their `Db` wrappers; every query/insert/list/bootstrap now binds
`community_id` (PK `ON CONFLICT (community_id, pubkey)`, WHERE clauses
carry community).
- `check_/enforce_relay_membership` take the server-resolved community;
pass `tenant.community()` at every entrypoint that already binds a
tenant: bridge (events/query/count), media upload, git transport,
audio handle, WS auth (`conn.tenant`), mesh `require_mesh_member`
(connect + status), relay-admin events, leave-request ingest,
identity-archive consent, NIP-43 list publish, and the buzz-admin CLI
(via `resolve_admin_tenant`).
Startup seeding (the bootstrap half of the same fix): membership
backfill and owner bootstrap previously ran with no community. They now
run against the deployment's own community, seeded via
`ensure_configured_community` under the *same* normalized host that live
request resolution derives (`relay_url_authority` → `normalize_host`, now
`pub`), so the bootstrapped owner lands in exactly the community requests
for this host resolve to. An unparseable `relay_url` fails fast when
membership is enforced rather than seeding an unreachable empty-host
community.
Regression (Postgres-backed, `#[ignore]`):
- `membership_is_confined_to_its_community`: A admits a pubkey, B does
not — `is_/get_/list_relay_members` confine it to A.
- `owner_bootstrap_is_confined_to_its_community`: owner bootstrapped in A
is not a member of B.
cargo test -p buzz-db -p buzz-relay -p buzz-admin green; both new tests
pass against live Postgres; clippy clean on all three crates.
Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
Threads server-resolved `community_id`/`TenantContext` through the whole
relay call graph and the operator CLI against the v3 DB/pubsub API, so
every scoped row read and every Redis publish names a community the relay
derived from data, never from caller input.
Relay (`crates/buzz-relay`):
- Read-path caches take `CommunityId`; write/invalidate publishers take
`&TenantContext` (the Redis topic key needs the host). The cross-node
fan-out path only has the community, so caches stay constructible there.
- Doors fail closed: WS/bridge/media/NIP-05 bind community from the request
host via `bind_community`, falling through to an empty/404 response on an
unmapped host — no default tenant, no host echo.
- Background loops get tenant from the DB row they act on: the reaper builds
`TenantContext::resolved(row.community_id, row.host)` per archived channel
from the reaper RETURNING; the dev/CI reconciler and reminder scheduler
resolve the one configured community from `relay_url`, fail-closed.
- Deployment-community cases with no connection tenant (git hook/finalize,
workflow sink) resolve via the same host-resolution seam.
- Drop the Typesense-only `reindex_kind0` backfill binary, obsolete under
the Postgres FTS migration and referenced nowhere.
Admin CLI (`crates/buzz-admin`):
- New `resolve_admin_tenant` reads `RELAY_URL` host (the CLI runs
`compose exec relay buzz-admin`, sharing the relay's env) and resolves it
via `lookup_community_by_host`, fail-closed on an unmapped host.
- Scope the NIP-43 membership-list publish (`EventTopic::Global`), channel
reconcile, `get_members`, and the kind:39000 existence `EventQuery`
(`..EventQuery::for_community`). Drop the now-dead `uuid` dep.
Workspace gate: `cargo check --workspace` green; buzz-db 97/97, buzz-audit
13/13, buzz-relay 375 + main 1 (`--include-ignored --test-threads=1`),
buzz-admin compiles, fmt + buzz-admin clippy clean.
Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com>