mirror of
https://github.com/block/buzz.git
synced 2026-10-29 13:31:44 +00:00
Conformance row zero: req.community = resolve_host(connection.host), bound before any handler observes tenant data. This lands the relay-side seam: - HostResolver trait (native async fn, no async-trait dep) — buzz-db's Db::resolve_host satisfies it; the relay depends on the trait, not the query, so the binding is testable without a database. Callers are generic over R, no dyn dispatch (the relay holds a concrete Db). - bind_community(): normalizes the host with the one shared rule, resolves it, and fails closed on BOTH unmapped host AND lookup error — there is no path that yields a default/fallback community. UnmappedHost is a distinct variant the call site turns into a GENERIC reject (no host echo, no unmapped-vs-error distinction) so an unauthenticated caller can't probe which hosts exist. - TenantContext carries the normalized host, so downstream NIP-05/audit labelling and the NIP-98 u-host check all see the canonical form the community was resolved from. Tests (4, green) cover known-host bind, variant normalization (case/dot/ default-port can't split a tenant), unmapped fail-closed, and lookup-error fail-closed-not-default. Adversarially verified: mutating the None arm to fall through to a nil default community turns unmapped_host_fails_closed RED. Seam contract for the buzz-db lane (Mari): Db::resolve_host(&self, normalized_host: &str) -> Result<Option<CommunityId>, DbError>, a SELECT id FROM communities WHERE host = $1 on the normalized key. Router call site (nip11_or_ws_handler) + threading TenantContext through handle_connection land next in this lane. (cherry picked from commit 0be8532e0e94e5ecd6529f2f3f52255dd36f6009) Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@sprout-oss.stage.blox.sqprod.co> Signed-off-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com>