Skip to content

Relay client policy (signing, auth, scoping, retries, delivery outcomes) is locked inside buzz-cli and reimplemented per client #5471

Description

@MajorTal

Summary

The correctness-sensitive parts of talking to a Buzz relay — signing, NIP-42/98 authentication, community scoping, pagination, retries, and interpreting ambiguous delivery outcomes — currently live in crates/buzz-cli/src/client.rs. Anything that is not the CLI has to reimplement them.

buzz-ws-client sits below this as shared transport (connect, auth, publish), but the policy layer above it is CLI-private. So the desktop app, the mobile app, and any third-party client each end up with their own version of the same rules.

Why this matters

These are not conveniences; they are the parts where being subtly wrong is invisible until it is expensive:

  • Delivery outcomes are ambiguous by nature. A publish that times out may or may not have been stored. A client that guesses wrong either drops a message or double-sends.
  • Community scoping is a correctness boundary, not a formatting detail — h-tag scoping and the host-derived community boundary have to be applied consistently or a client can read across a boundary it should not.
  • Retry policy interacts with signing. Re-signing on retry versus reusing the signed event is exactly the kind of decision that gets made differently in each reimplementation.

Every independent reimplementation is a fresh opportunity to get one of these wrong, and a bug fixed in one client does not reach the others.

Concretely

I hit this trying to start a separate client: the logic I needed was reachable only by depending on the CLI crate, which drags in argument parsing, output formatting, and environment assumptions that have nothing to do with talking to a relay.

Proposed shape

Extract a crates/buzz-client library that owns the policy layer, with buzz-ws-client remaining the transport beneath it:

  • explicit community endpoint + configuration rather than ambient environment
  • an async signer abstraction, so key custody is the caller's decision (CLI env var, desktop keychain, mobile secure storage) instead of being baked in
  • typed errors and results, and semantic delivery outcomes rather than a bare Result that loses the stored-but-unacknowledged case
  • no CLI, Tauri, or environment concerns in the public API

The migration wants to be incremental rather than a big-bang move: establish the intended final boundary immediately, but move only a couple of operations across it first, keeping existing CLI behavior byte-stable and proving the same public API serves both the CLI and an independent consumer.

What I'd like acknowledged before more of it gets built

This is an architectural change and CONTRIBUTING.md asks for the approach to be agreed first, so the questions worth answering are:

  1. Is crates/buzz-client above buzz-ws-client the boundary you want, or should this land inside buzz-ws-client instead?
  2. Is incremental migration acceptable, with two client paths coexisting while operations move across one at a time?
  3. Is a public, semver-exposed client library something the project wants to own and support at all? A reasonable answer is "no, keep it internal" — better to hear that now than after the migration.

Status

Filed after the fact: #4717 already implements the first slice (channels list + messages send migrated, everything else left on the existing path) and is open as a draft. Filing here so the boundary question can be settled independently of reviewing that diff — if the answer to (1) or (3) is different from what I assumed, the diff is the wrong thing to be discussing.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions