Commit Graph
1081 Commits
Author SHA1 Message Date
npub1jmc9dt2lyvzu3h0kxlwxt5zg4fxp9476awyxw6gwxn72g6cw7exqs64whmandTyler Longwell 04e5ed9ae9 docs(auth_wire): fix doc drift — tests are in-module, P5 grep-lint not yet shipped
Eva [11] code-verify catch: two docstrings in auth_wire.rs referenced state
that doesn't exist on this branch.

- AuthErrorWireCategory::message pointed at "tests/auth_error_payload_oracle.rs"
  for the byte-identity property test. The tests are actually in-module
  (#[cfg(test)] mod tests at the bottom of auth_wire.rs); no separate file
  exists. Updated to name the two specific tests that prove the invariant
  (verification_class_all_coalesce, internal_two_communities_byte_identical).

- auth_error_wire's docstring said "the CI grep-lint enforces" — but the
  grep-lint (P5 in the audit note) is a planned follow-up, not yet shipped.
  Replaced with an accurate description of what actually fences regressions
  today: the wildcard-free match arm. Adding any AuthError variant fails to
  compile here until a wire-class decision is made — that catches the cause,
  not just the symptom. P5 grep-lint is correctly described as planned
  belt-and-suspenders, not currently in CI.

Doc-only diff. No behavior change.

Validation:
- cargo fmt -p buzz-relay --check 
- cargo test -p buzz-relay --lib auth_wire  (6/6 property tests green)

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
2026-06-26 13:46:15 -04:00
npub1jmc9dt2lyvzu3h0kxlwxt5zg4fxp9476awyxw6gwxn72g6cw7exqs64whmandTyler Longwell 41602ab4d2 docs(auth): annotate AuthError variants with WIRE class — doc-at-source for P1+P2
Pairs with c31307d40 (the audit branch's wire mapper + property tests) and
Eva's [13] ruling: one wire categorization lives in buzz-relay::auth_wire,
and AuthError variants carry doc annotations pointing at it. Doc-only diff;
no behavior change.

The enum-level docstring spells out the wire-mapping contract — the four
AuthErrorWireCategory targets, what each invariant guards (byte-identity
on verification-class collapses an existence oracle; Internal must never
stringify on the wire), and a pointer at the compile-time exhaustiveness
fence in auth_error_wire's match that catches variant-add-then-forget.

Per-variant: each variant carries a "WIRE class:" line naming its
AuthErrorWireCategory target. Nip98Replay calls out the byte-identity
requirement against Nip98Invalid explicitly (community-scoped seen-set
presence oracle). Internal calls out the no-stringify rule explicitly
(community-prefixed Redis keys can ride the inner String).

No intra-doc link to AuthErrorWireCategory: buzz-auth cannot depend on
buzz-relay (would cycle), so the type is referenced by name in prose.

Validation:
- cargo fmt -p buzz-auth --check 
- cargo test -p buzz-auth  (40 passed)
- cargo clippy -p buzz-auth --no-deps --lib --tests  (one pre-existing
  warning at nip98_replay.rs:162, not from this diff)
- cargo doc -p buzz-auth --no-deps  (zero new doc warnings; two
  pre-existing broken links in rate_limit.rs/nip98_replay.rs unchanged)
- cargo test -p buzz-relay  in isolation; one known-flaky Redis
  presence test (pubsub_fanout::global_presence_*) intermittently fails
  in full-suite runs and passes when run alone — same flake Eva flagged
  on ed33878b7's push. Doc-only diff cannot affect Redis fanout timing.

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
2026-06-26 13:41:00 -04:00
npub1jmc9dt2lyvzu3h0kxlwxt5zg4fxp9476awyxw6gwxn72g6cw7exqs64whmandTyler Longwell 7fc43fb391 audit(relay): collapse AuthError to category-only on wire — close existence-oracle
Per Eva's [12] handoff: the multi-tenant Redis keys (buzz:{community}:nip98:{id},
buzz:{community}:ratelimit:{hex}:{kind}) leak through AuthError::Internal(_) when
the relay wire converter stringifies the underlying redis::RedisError. The
bridge.rs:52 site forwarded the whole chain as the user-visible body —
`api_error(401, &format!("NIP-98: {e}"))` — turning the auth path into a
cross-tenant existence oracle on community-prefixed keys.

Two policy decisions land here as code + tests:

P1 — AuthError::Internal(_) NEVER reaches the wire as a string. The new
crate::auth_wire::auth_error_wire mapper maps Internal(_) -> InternalRedacted,
which produces a fixed "internal error" body regardless of the inner
String. The construction sites still log the detail (tracing::warn! at
construction is unchanged); only the wire converter redacts.

P2 — Nip98Invalid and Nip98Replay are byte-indistinguishable on the wire.
Returning a distinct error for replay tells an attacker that a guessed
event id has been seen in THIS community (the seen-set is community-scoped
per the S1 isolation fence). Both variants -> AuthErrorWireCategory::AuthFailed
-> identical status + body. Same reasoning collapses InvalidSignature,
ChallengeMismatch, RelayUrlMismatch, EventExpired, PubkeyMismatch into the
same category — all are "the auth artifact didn't verify."

Authorization-class outcomes (InsufficientScope, ChannelAccessDenied) stay
distinguishable on the wire — they're remediation-different (re-auth vs
re-request access) and don't carry tenant-scoped detail. If that ever
changes, authorization_class_distinguishable will need re-thinking.

The bridge.rs:52 site is the only current consumer; Sami's pending NIP-98
HTTP wire-up will use the same mapper. CI lint to forbid raw AuthError
stringification in response-construction is P5 in the audit note —
proposing that as a follow-up grep-lint Eva can wire into the §5 gate set.

Files:
- crates/buzz-relay/src/auth_wire.rs (new, +281 lines incl 6 property tests)
- crates/buzz-relay/src/api/bridge.rs:52 (use mapper, log detail)
- crates/buzz-relay/src/lib.rs (declare module)

Tests:
- replay_indistinguishable_from_invalid_on_wire (P2)
- verification_class_all_coalesce (P2 extended — 7 variants byte-identical)
- internal_redacted_does_not_leak_inner_string (P1 — synthesizes a leaked
  Redis error containing buzz:<uuid>:nip98:<id> and asserts none of those
  substrings appear on the wire)
- internal_two_communities_byte_identical (P1 — two distinct communities'
  inner strings produce the same wire bytes; closes the cross-tenant oracle)
- authorization_class_distinguishable (negative — InsufficientScope and
  ChannelAccessDenied stay distinct)
- ws_notice_redacts_internal_and_coalesces_verification (WS NOTICE parity)

Validation:
- cargo test -p buzz-relay --lib: 384 passed, 0 failed (378 prior + 6 new)
- cargo fmt -p buzz-relay --check: clean
- cargo clippy -p buzz-relay --no-deps --lib --tests: 0 new warnings
  (one pre-existing on publish_nipia_unarchived at side_effects.rs:2619,
  unrelated to this change — confirmed by git stash)

Base: rewrite/relay-wiring @ 87d5a8e35 (Eva's local merge of auth-tenant-scope
into relay-wiring; Sami's Nip98Replay variant is present here, NOT yet in
origin/rewrite/relay-wiring's pushed tip).

Holding for Eva's push of 87d5a8e35 + review per the audit lane handoff.
Audit note: RESEARCH/RELAY_REWRITE_AUTH_ERROR_ORACLE_AUDIT.md

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
2026-06-26 13:34:03 -04:00
npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67dandTyler Longwell ed33878b7d feat(relay): wire shared NIP-98 replay guard + per-IP connection fence
Land the auth fences from buzz-auth/buzz-pubsub into the relay's request
paths, replacing the per-pod moka NIP-98 cache that does not carry the
freshness proof under any-pod-any-connection (bus-scoping B).

- AppState: add Arc<RedisNip98ReplayGuard> + Arc<RedisRateLimiter>,
  constructed from the existing redis_pool. Drop the dead nip98_seen moka
  cache (its only caller was the bridge replay check).
- bridge.rs: check_nip98_replay is now async + tenant-scoped, calling the
  shared seen-set's try_mark under the resolved community (the seen-set is
  community-scoped per S1). Resolve the tenant BEFORE the replay check in
  all three NIP-98 handlers; DRY the duplicated host-resolve into
  resolve_request_tenant. /count now resolves a tenant (required for the
  per-community replay check). Replay and invalid responses are
  wire-indistinguishable (Quinn P2) and fail closed on Redis error.
- router.rs: per-IP connection fence (check_ip_connection) runs in the WS
  upgrade path BEFORE host resolution, so an unmappable Host cannot bypass
  the cap. Operator-global, tenant-free; fail-closed -> 429.
- config.rs: BUZZ_MAX_CONNECTIONS_PER_IP (default 60) +
  BUZZ_IP_CONNECTION_WINDOW_SECS (default 60).

cargo test -p buzz-relay: 378 passed / 0 failed. clippy clean (one
pre-existing unrelated warning in side_effects.rs).

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
2026-06-26 13:29:28 -04:00
npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67dandTyler Longwell 87d5a8e357 Merge auth-tenant-scope: community-scoped RateLimiter + NIP-98 replay guard
Integrates Sami's auth lane (15dd8dfc3) into relay-wiring: community-scoped
rate_limit_key, RedisNip98ReplayGuard, and the IpConnections fence primitives.
Brings RateLimiter + Nip98ReplayGuard into the workspace so the relay can wire
the IP-fence-before-host ordering and the NIP-98 verify-then-mark call site.

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>

* origin/rewrite/auth-tenant-scope:
  feat(auth): NIP-98 replay seen-set — shared, community-scoped, atomic
  feat(auth): community-scope RateLimiter pubkey quotas
2026-06-26 13:12:23 -04:00
npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67dandTyler Longwell 67b85c2920 feat(relay): wire retain_topic/release_topic into subscription lifecycle
The dynamic refcounted Redis subscriber (Max's pubsub seam) only SUBSCRIBEs
a community-scoped topic once a pod declares local interest via retain_topic,
and debounce-UNSUBSCRIBEs on the last release. Until now nothing drove those
calls from real subscription activity, so cross-pod delivery only worked via
a manual retain in the presence test setup.

The SubscriptionRegistry stays synchronous and tenant-agnostic — it does not
grow a PubSubManager or TenantContext. Instead each mutator now reports the
routing topic(s) it added/removed:
  - register     -> Option<EventTopic> displaced by an in-place sub_id replace
  - remove_subscription -> Option<EventTopic> removed
  - remove_connection   -> Vec<EventTopic>, one per removed sub

The call sites — where &conn.tenant and state.pubsub already live — apply the
delta: retain the new topic on REQ, release on CLOSE / connection drop /
channel-access revocation. The pubsub manager's own (community, topic)
refcount collapses N releases to one debounced UNSUBSCRIBE and keeps two
communities sharing a channel UUID isolated, since the community is bound at
the call site via ctx. Every subscription contributes exactly one retain at
creation and one release at teardown — balanced, no registry-side counter.

The presence cross-pod test now drives retain through the real register()
path (register_global_sub is async) instead of a manual workaround, proving
delivery from genuine subscription interest. Five new registry unit tests
pin the topic-reporting contract including the replace-displaces-old case.

cargo test -p buzz-relay: 378 passed, 0 failed. Redis round-trip presence
test exercised against local Redis.

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
2026-06-26 12:55:12 -04:00
npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67dandTyler Longwell 8250d6c03e feat(relay): thread TenantContext through mesh + internal producers
Completes the &TenantContext threading for the relay-wiring lane so
buzz-relay compiles against Mari's buzz-db tenant seam and Max's
community-scoped pubsub seam. The remaining unscoped publish/dispatch
sites are now request- or startup-tenant bound:

- mesh_signaling: ctx threaded through handle_mesh_event_http,
  handle_connect_request, handle_status_report, and
  publish_channelless_ephemeral. publish_event(uuid::nil) ->
  publish_event(ctx, EventTopic::Global); echo-cache invalidate keyed
  by (community, event_id). WS callers pass &conn.tenant; the HTTP
  bridge now resolves the tenant once before mesh/ingest routing.
- mesh_status_publisher: ctx threaded through publish_mesh_status* into
  dispatch_persistent_event.
- workflow_sink: cron has no request host, so it resolves the
  configured-host startup tenant (N=1) for dispatch; side effects stay
  best-effort, logging+skipping on resolve failure. TODO(multi-tenant):
  resolve per-channel community for N>1.
- main.rs: ephemeral reaper and NIP-ER reminder scheduler resolve the
  startup tenant per tick (TODO multi-tenant per-channel); the cache-
  invalidation receiver unwraps ScopedCacheInvalidation and threads
  community_id into apply_cache_invalidation (moka cache keys still need
  community-prefixing, flagged as follow-up).
- bridge.rs: hoisted tenant resolution above the mesh branch so one
  resolve_tenant call binds both paths.

Tests updated for the new pubsub contract: mesh handler tests pass a
test_ctx(); the cross-pod presence round-trip test now retain_topic()s
the Global topic on both relays (the dynamic-subscription seam the
registry wiring will drive). cargo test -p buzz-relay: 373 passed.

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
2026-06-26 12:39:27 -04:00
npub17jjz49l9jjmhhk7cac63j8yt9z555n9cw8vk7v5jz4vzw4ppld5qgj57ccandTyler Longwell a4f3179af3 feat(auth): NIP-98 replay seen-set — shared, community-scoped, atomic
Adds the §5 pre-build gate for multi-tenant replay protection.

buzz-auth gains a Nip98ReplayGuard trait plus the
nip98_replay_key(ctx, event_id) helper. The trait's try_mark contract
requires atomic set-if-absent semantics; an in-process cache (moka,
DashMap) does not carry the freshness proof across pods under the
"any pod, any connection" architecture (§4B), so the production
implementation MUST be shared state. The Redis-backed impl lives in
buzz-pubsub as RedisNip98ReplayGuard and uses a single SET key 1 NX
EX <ttl> per claim.

Key shape: buzz:{community}:nip98:{event_id_hex}. Event ids are
content-addressed so natural cross-community collision is zero, but
the gate is fail-closed isolation — a same-id replay across
communities must consult two distinct seen-set rows, not one shared
row. Tests pin both the prefix and the cross-community isolation
guarantee.

TTL floor is DEFAULT_REPLAY_TTL_SECS = 120, matching the §5 gate
requirement and the doubled NIP-98 ±60s timestamp tolerance.
Implementations MAY clamp sub-floor TTLs up to the floor; they MUST
NOT honor smaller values. The Redis impl clamps.

Caller contract documented in the trait: verify first, then mark.
Burning a seen-set slot on a forgery would let an attacker who learns
a future event id DoS the legitimate event. On Err (Redis
unreachable) callers MUST fail closed.

Not wired into a call site in this commit — there is no NIP-98 HTTP
handler in Lane 0 yet. Eva's relay-wiring lane will consume the trait
when the HTTP path lands; the contract is documented for that
integration.

Validation:
- cargo test -p buzz-auth --lib  40 passed (4 new in nip98_replay).
- cargo test -p buzz-pubsub --lib  3 passed, 9 Redis-required
  ignored (3 new in nip98_replay).
- cargo test -p buzz-pubsub --lib nip98_replay -- --ignored against
  local Redis  3 passed: first-claim/replay, cross-community
  isolation, sub-floor TTL lifted to floor.
- Workspace check not run locally (sqlx 0.9.0 / rustc 1.94 vs local
  1.89); CI catches it.

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
2026-06-26 12:00:13 -04:00
npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d 830c04bfb0 Merge remote-tracking branch 'origin/rewrite/db-community-scope' into rewrite/relay-wiring
* origin/rewrite/db-community-scope:
  feat(db): add community host lookup seam
2026-06-26 11:56:21 -04:00
npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf 1adbfbe210 feat(db): add community host lookup seam
Co-authored-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@sprout-oss.stage.blox.sqprod.co>
2026-06-26 11:54:35 -04:00
npub17jjz49l9jjmhhk7cac63j8yt9z555n9cw8vk7v5jz4vzw4ppld5qgj57ccandTyler Longwell 493f944c45 feat(auth): community-scope RateLimiter pubkey quotas
RateLimiter::check_and_increment now takes &TenantContext, and
rate_limit_key emits buzz:{community}:ratelimit:{pubkey_hex}:{suffix}.
Same pubkey active in two communities consumes two independent quotas,
matching the S1 cross-community isolation fence in the buzz-relay
rewrite spec.

check_ip_connection stays operator-global by design. The IP fence runs
at connection acceptance, before host->community resolution has
completed (or, on resolve failure, instead of it). Threading
&TenantContext through it would invert the order of operations. Per-
(community, IP) caps, if ever needed as a tenant-fairness signal,
belong in an additive LimitType keyed on (community, ip) — not in this
trait.

RedisRateLimiter in buzz-pubsub follows the new trait signature.
AlwaysAllowRateLimiter test impl mirrors it. Two new tests pin the
behavior: the key includes the community prefix, and same-pubkey-two-
communities yields two distinct Redis keys.

Local cargo test -p buzz-auth: 36 passed. Local cargo test -p
buzz-pubsub: 3 passed, 6 Redis-required ignored. Workspace-wide check
not run locally (sqlx 0.9.0 requires rustc 1.94, local toolchain is
1.89 — same constraint Max hit on the pubsub lane); relying on CI for
the full integration compile.

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
2026-06-26 11:53:45 -04:00
npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d 269fb8313c Merge remote-tracking branch 'origin/rewrite/pubsub-community-scoped' into rewrite/relay-wiring
* origin/rewrite/pubsub-community-scoped:
  feat(pubsub): scope redis topics by community
2026-06-26 11:51:03 -04:00
npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67dandTyler Longwell b6f162bb31 feat(relay): bind TenantContext from connection host at WS upgrade
Resolve the community from the connection host BEFORE the WebSocket upgrade
(conformance row-zero) and carry it as ConnectionState.tenant for the whole
connection lifetime. Handlers read &conn.tenant and pass it into scoped
DB/pub-sub calls; nothing downstream can mint or change it.

- RelayError::HostNotMapped: fail-closed, generic message (no host oracle).
- normalize_host(&HeaderMap, fallback): request Host header, lowercased,
  port-stripped; falls back to the configured relay_url host (N=1 parity).
- AppState::resolve_tenant: the ONLY mint site, via the buzz-db lookup seam.

Integration seam: depends on buzz-db Db::lookup_community_by_host (Mari's lane).
Until that lands on rebase, the crate has exactly that one unresolved symbol;
everything else (normalize_host + tests, the field plumbing) compiles clean.

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
2026-06-26 11:47:25 -04:00
npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta 59d9a309f2 feat(pubsub): scope redis topics by community
Co-authored-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@sprout-oss.stage.blox.sqprod.co>
2026-06-26 11:47:08 -04:00
npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67dandTyler Longwell ab4b518d5d feat(core): TenantContext + CommunityId — the server-resolved tenant fence
buzz-core gets the zero-I/O tenant identity types every scoped layer
shares. TenantContext encodes conformance row-zero in the type system:
no Default, no Deserialize, no public constructor except resolved(),
which is meant to be called only from host resolution. Downstream code
holds &TenantContext and can read but not mint a community, so
client-chosen-community cannot type-check outside resolution.

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
2026-06-26 11:31:25 -04:00
2ecdcce7bd Multi-tenant relay: spec + mechanized formal proof (S1–S8) (#1285)
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Co-authored-by: Mari <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1jmc9dt2lyvzu3h0kxlwxt5zg4fxp9476awyxw6gwxn72g6cw7exqs64whm <96f056ad5f2305c8ddf637dc65d048aa4c12d7daeb8867690e34fca46b0ef64c@sprout-oss.stage.blox.sqprod.co>
2026-06-26 11:15:59 -04:00
34b2d3e8b4 Polish side panel motion and composer alignment (#1294)
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Pinky <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@sprout-oss.stage.blox.sqprod.co>
2026-06-25 18:16:56 -07:00
59b21592c9 feat(mobile): harden unread badges and float tabs (#1298)
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Pinky <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@sprout-oss.stage.blox.sqprod.co>
2026-06-25 17:40:00 -07:00
4fce5aab2e feat(desktop): restore archive identity UI in profile panel (#961)
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@sprout-oss.stage.blox.sqprod.co>
2026-06-25 20:34:49 -04:00
4481f8fd5a fix(sidebar): non-selectable channel names + copy/leave context menu actions (#1260)
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@sprout-oss.stage.blox.sqprod.co>
2026-06-25 14:25:50 -07:00
f072032aaf fix(runtime): sweep node wrapper processes hosting managed agent shims (#1296)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1fgdl5qqnh3k3f2xkqrvt7cujalhm623x4s7fdjdj5yrtp5fzjl9qrjpucw <4a1bfa0013bc6d14a8d600d8bf6392efefbd2a26ac3c96c9b2a106b0d12297ca@sprout-oss.stage.blox.sqprod.co>
2026-06-25 17:06:59 -04:00
8717ddf2eb fix(buzz-agent): follow symlinks when discovering skill directories (#1295)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1fgdl5qqnh3k3f2xkqrvt7cujalhm623x4s7fdjdj5yrtp5fzjl9qrjpucw <4a1bfa0013bc6d14a8d600d8bf6392efefbd2a26ac3c96c9b2a106b0d12297ca@sprout-oss.stage.blox.sqprod.co>
2026-06-25 16:44:10 -04:00
dfa864fc45 chore: add grab-emoji.sh to register Slack emoji in Buzz (#1292)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
2026-06-25 16:30:11 -04:00
e1c51d71d4 Fix cross-pod membership notification fanout (#1291)
Signed-off-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@sprout-oss.stage.blox.sqprod.co>
2026-06-25 16:09:15 -04:00
ccdb8975d3 fix(buzz-acp): strengthen agent communication rules in base prompt (#1293)
Signed-off-by: Will Pfleger <wpfleger@block.xyz>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1fgdl5qqnh3k3f2xkqrvt7cujalhm623x4s7fdjdj5yrtp5fzjl9qrjpucw <4a1bfa0013bc6d14a8d600d8bf6392efefbd2a26ac3c96c9b2a106b0d12297ca@sprout-oss.stage.blox.sqprod.co>
2026-06-25 15:19:24 -04:00
WesandGitHub 116a445aab chore(release): release Buzz Desktop version 0.3.34 (#1289) v0.3.34 2026-06-25 18:42:01 +00:00
9a59e30817 feat(desktop): refresh Agents tab live on inbound relay sync (#1256)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
2026-06-25 14:10:06 -04:00
0379247eba fix(buzz-acp): inject Codex network allowlist for relay hostname at spawn time (#1287)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
2026-06-25 13:59:13 -04:00
dd5592e34f refactor(desktop): consolidate notification helpers and add channel names to toasts (#1286)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
2026-06-25 13:07:04 -04:00
25396c06d6 feat(buzz-agent): lazy skill loading via load_skill tool (#1283)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
2026-06-25 12:55:04 -04:00
6c920d21a8 fix(buzz-acp): human-aware reply anchoring to keep threads flat (#1281)
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Pinky <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@sprout-oss.stage.blox.sqprod.co>
2026-06-25 16:40:26 +00:00
c996be395e feat(desktop): add 'Follow system' theme mode (#1262)
Signed-off-by: Alec Thomas <aat@block.xyz>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Goose <opensource@block.xyz>
Co-authored-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Pinky <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@sprout-oss.stage.blox.sqprod.co>
2026-06-25 16:35:08 +00:00
227518c234 Fix mention autocomplete layout in narrow threads (#1282)
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Pinky <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@sprout-oss.stage.blox.sqprod.co>
2026-06-25 09:26:00 -07:00
2499d33971 Clean up screenshot e2e tests (#1284)
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Pinky <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@sprout-oss.stage.blox.sqprod.co>
2026-06-25 09:25:34 -07:00
e366372183 fix(desktop): DM close button replaces unread badge on hover (#1280)
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
Co-authored-by: Eva <eva@buzz.local>
Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
2026-06-25 12:21:22 -04:00
5fef9b727f Add NIP-34 git pull request CLI support (#1279)
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Co-authored-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@sprout-oss.stage.blox.sqprod.co>
2026-06-25 11:39:54 -04:00
d05f122d8a fix(desktop): preserve timeline scroll when opening threads (#1278)
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Pinky <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@sprout-oss.stage.blox.sqprod.co>
2026-06-25 08:35:35 -07:00
73cc31cc52 chore: remove LLM-slop comments across the codebase (#1277)
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Mari <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Quinn <96f056ad5f2305c8ddf637dc65d048aa4c12d7daeb8867690e34fca46b0ef64c@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Sami <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Perci <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
2026-06-25 11:03:05 -04:00
996a3f89d5 fix(desktop): allow saving personas with an empty system prompt (#1276)
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
2026-06-25 10:05:41 -04:00
Will PflegerandGitHub 5306735986 chore(release): release Buzz Desktop version 0.3.33 (#1270) v0.3.33 2026-06-25 00:26:20 -04:00
e7c3638fe9 fix(desktop): always use legacy keyring for blob entry on macOS (#1271)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
2026-06-25 00:25:37 -04:00
Will PflegerandGitHub 68a0cc8506 chore(release): release Buzz Relay version 0.1.1 (#1269) relay-v0.1.1 2026-06-24 23:49:09 -04:00
fa942cb51d perf(desktop): consolidate keychain secrets into a single blob entry (#1267)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
2026-06-24 23:39:27 -04:00
048e8fdc00 feat(desktop): re-snapshot persona config on every agent spawn (#1268)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1fgdl5qqnh3k3f2xkqrvt7cujalhm623x4s7fdjdj5yrtp5fzjl9qrjpucw <4a1bfa0013bc6d14a8d600d8bf6392efefbd2a26ac3c96c9b2a106b0d12297ca@sprout-oss.stage.blox.sqprod.co>
2026-06-24 23:32:40 -04:00
0cee0435f7 feat(relay): add buzz-admin member management CLI with NIP-43 roster publish (#1265)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
2026-06-24 23:13:31 -04:00
958ac7aaaa fix(desktop): fall back to old keychain when DPK unavailable (unsigned builds) (#1266)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
2026-06-24 22:53:19 -04:00
9f35b01880 Align channel management panel with profile (#1066)
Signed-off-by: Thomas Petersen <thomasp@squareup.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: npub14vtk7pvazqrq9639qu7e560wnqtl0d53ca4gjuvq6jzf3k2el23qqlwa7f <ab176f059d100602ea25073d9a69ee9817f7b691c76a897180d48498d959faa2@sprout-oss.stage.blox.sqprod.co>
2026-06-24 19:28:35 -07:00
6284454298 fix(relay): multi-pod subscription coherence (one access-gated fan-out path + cross-pod cache invalidation + REQ/COUNT DB guard) (#1261)
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Co-authored-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@sprout-oss.stage.blox.sqprod.co>
2026-06-24 22:17:25 -04:00
35522311a9 fix(desktop): switch macOS keychain to Data Protection Keychain (#1264)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
2026-06-24 21:46:50 -04:00
cee2c5f263 fix(desktop): use IPv4 loopback for media proxy URLs (#1245)
Signed-off-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@sprout-oss.stage.blox.sqprod.co>
2026-06-24 20:20:27 -04:00