Sandro/mesh compute view - #5085
Open
sandro-sq wants to merge 18 commits into
Open
Conversation
This change enables a Tauri content security policy that limits executable content to the packaged application and does not allow inline scripts. Relay, media, asset, and Tauri IPC schemes remain available for desktop compatibility. The policy contains the impact of a future renderer injection; it does not itself remove an injection bug. ## Testing - `git diff --check origin/main...codex/security-desktop-csp` - Rebased onto `origin/main` at `5c98932` - Full CI pending Originating Buzz thread: `buzz://message?channel=3928fe05-df61-4b5d-b9c7-d623b9b10ea1&id=3c6c02312f763fbe0d2bfc33a6c1a362f91d0354f3d18b039cf7a0558c1439d1` --------- Signed-off-by: Jordan Mecom <jm@squareup.com> Signed-off-by: Eli Foster <efoster@squareup.com> Co-authored-by: Eli Foster <efoster@squareup.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…opes (#4917) ## Problem Observer telemetry is the noisiest client of the relay: the old pacer (167ms spacing + 90/min rolling cap) let a busy session bill up to 6 events/second against the owner's message quota, and the rolling cap silently *dropped* frames once exceeded. Ruling from the rate-limiting investigation thread (channel `826fc99b-1472-40e7-a529-6b9db8943b8c`): pace at 1/s, always emit, minimal PR. **Review round 1 (Max, Sami)** found the first cut wrong in three ways — tick burst (all pending frames per tick), startup burst (`interval` fires at t=0), and per-channel quota arithmetic. All fixed and mutation-verified in round 1. **Review round 2 (Sami, Max)** found two more against the round-1 head: 1. **Drain-rate collapse (Sami, blocker):** front-run-only packing meant a frame held ONE event whenever channels interleaved — measured 275 B/s vs 63.5 KB/s, so an ordinary 2-channel session fell minutes behind with zero drops and no warning. Silent unbounded latency. 2. **Coalescer byte-cap bypass (Max):** chunks pending in `ObserverChunkCoalescer` were unbounded and outside the 4 MiB cap — 500 distinct-`messageId` 50KB chunks retained ~48MB with `pending_bytes == 0` and zero drops. **Review round 3 (Max)** found the drop accounting undercounted merged chunks: a coalescer entry that merged N same-`messageId` chunks counted as **1** in `dropped_events` when evicted (50 merged 1KB chunks evicted → counter read 1, 49 generated events unaccounted). Fixed: accounting is now denominated in **source (generated) observer events** end to end — each pending entry tracks how many chunks it absorbed, eviction charges that count, and the count survives flush into the publish FIFO. **Review round 4 (Sami, Max)** found three more against the round-3 head: 1. **Coalescer byte undercount (both, independently):** a pending merged entry retains its first chunk's text **twice** until flush — once inside the serialized event skeleton and once in the extracted text accumulator — but was charged only `serialized_len`, so true retention overshot the 4 MiB cap ~2× (measured 8.3 MB). Fixed: `push_pending` charges `serialized_len(&event) + text.len()`. 2. **Cap regressions asserted the accumulator against itself,** which is how the undercount hid. All three cap tests now assert on independently **walked** retained bytes (`serialized_len` per FIFO entry + `serialized_len + text.len()` per coalescer entry), with a secondary `accumulator >= walked` sanity check. Reverting the fix makes them fail at exactly 8,328,386 / 8,328,272 bytes. 3. **FIFO-arm source accounting was implemented but untested (Sami M13; Max reproduced at `cc9333b7c` with 102/151):** the round-3 regression only evicted a merged entry while still in the coalescer. New test forces a merged entry (50×1KB, `source_events=50`) through flush into the publish FIFO, then evicts it from there — mutating the FIFO eviction to `dropped += 1` fails with the reviewers' exact numbers (102 vs 151). **Review round 5 (Sami 9/9/9, Max 9/9/9)** — production judged merge-safe by both; remaining items are tests only, all landed at `63d821620`: 1. **The walker instrument was itself unverified (Sami M17–M20; Max independently confirmed the `return 0` mutant survives):** every cap test asks `walked_retained_bytes()` only for `<= CAP`, so a blinded walker passes everything — and paired with a reverted `push_pending` fix the two mutations cancel, hiding exactly the 8.3 MB overshoot it exists to detect. New two-sided pin: the walker must SEE the first chunk's text twice, and must agree with the accumulator EXACTLY while both stores are non-empty. Kills M17, M18, M19, M20. 2. **Two pre-existing snapshot-clone siblings (Sami D5b/D5c; byte-identical at merge-base `7334ad1e1` — not this PR's regression, but the PR made the class visible):** aliasing the inner turns map leaks a post-save turn into the snapshot; aliasing the inner tombstones map leaks a post-save terminal that blocks a legitimate post-restore resurrection. Two isolation tests with in-test controls — all three inner-map clones in `saveActiveAgentTurnsForCommunity` are now pinned. ## Change **Harness (`crates/buzz-acp`)** - **Global pacer: AT MOST ONE relay frame per second**, regardless of channel count or backlog size. `interval_at(now + 1s)` restores the no-startup-burst property; `MissedTickBehavior::Skip` is now pinned by a paused-time test (a stalled tick arm fires one catch-up frame, not one per missed deadline). At 1 frame/s telemetry spends ≤60/min of the shared 120/min quota; `OBSERVER_PUBLISH_TICK` documents the tradeoff as the knob. - **`ObserverPublishQueue` with gather-packing:** events wait as byte-accounted events (FIFO). `next_frame()` packs the front event's channel **gathered queue-wide in FIFO order** — frames never mix channels, and each channel's events keep their FIFO order, but cross-channel frame order MAY differ from arrival order. That is what keeps the drain rate in **bytes per slot** (one ~64KB frame/s) instead of front-run-length events per slot. **Null-channel events (`agent_panic`-class) are packing barriers** nothing gathers across, so causally-global events keep exact order against every channel. - **One byte cap over BOTH stores:** the event FIFO and the coalescer's pending chunk buffer count against the 4 MiB budget together; eviction is oldest-first across both (queue front, then coalescer front — structural age order) with accounting (warn + counter). A high-cardinality chunk flood is bounded exactly like a plain event flood. Coalescer entries are charged their **true** retention (`serialized_len + text.len()` — the first chunk's text lives in both the serialized skeleton and the extracted accumulator until flush). - **Shutdown is not a burst bypass:** paced one-frame-per-tick drain until empty. **Desktop** - `unwrapObserverBatch` expands envelopes on the live relay path and archive-ingest seam (round 1, unchanged). - **`activeAgentTurnsStore` watermark re-keyed per (agent, channel)** with a dedicated null-channel bucket: the per-agent `(timestamp, seq)` gate would silently skip a delayed channel's frames as stale under gather-packing's intentional cross-channel reorder. Safe because every turn-mutating path is channel-scoped by the event's own `channelId` (endTurn's null-turnId fallback matches `turn.channelId`; resurrectTurn keys on `event.channelId`), so per-channel serialization preserves each guard the per-agent gate provided. The tombstone-cap justification is rewritten for the new keying (worst case for an evicted tombstone is a ghost badge the prune reaps — bounded cosmetic staleness, not corruption). Community-switch save/restore deep-clones the nested map. Other per-agent maps stay agent-keyed: the clock offset is a running minimum (order-insensitive); turns/tombstones mutate only through channel-scoped paths. ## Version skew — old desktop + new harness Gather-packing *intentionally* emits cross-channel-reordered frames. An **old desktop** (per-agent watermark) against a **new harness** will silently skip a delayed channel's turn-state events as stale — working badges on that channel can go stale/missing until its next fresh event. Transcript and archive are unaffected (the transcript store sorts + rebuilds on out-of-order arrival; the archive is per-channel by construction). Ship desktop and harness together; skew degrades badges only, not data at rest. ## Throughput ceiling — "lossless" is qualified Sustained lossless rate is what fits in one ~64KB frame per second, now genuinely in bytes under interleaving: | event payload | events per frame | sustained ceiling | |---|---|---| | 100 B | 250 | 250 ev/s | | 500 B | 99 | 99 ev/s | | 2 KB | 30 | 30 ev/s | | 10 KB | 6 | 6 ev/s | With C channels producing concurrently, publish slots round-robin between them: per-channel drain is ~64KB/C per second and the 4 MiB burst budget (~64s single-channel) shortens accordingly. Beyond budget, oldest-first drops **with accounting** — visible, designed loss. **Accounting semantics:** `dropped_events` counts SOURCE (generated) observer events, not retained entries — evicting a coalesced entry that merged N chunks charges N. On the published side, a merged entry ships all N sources' text in ONE event, so the reconciliation invariant is `ingested == dropped_events + Σ source_events over published events` (for unmerged events, source_events = 1). ## Verification At `63d821620d3513505e8766ac691a8002f9d4a96f` (this head; `git rev-parse HEAD` matched in the same shell as every run), rustc 1.95.0: - `cargo test -p buzz-acp`: **689 lib + 9 integration, 0 failed** — regressions: interleaved 2-channel drain packs into ≤4 frames not 200 slots; null-channel barrier; queue-wide gather with within-channel FIFO; distinct-key 50KB chunk flood bounded by the cap with event-level accounting (published + dropped == ingested, survivors newest); paused-time `MissedTickBehavior::Skip` pin (verified to fail under `Burst`: 3 frames vs 1); merged-key eviction accounts every absorbed source chunk in BOTH arms — coalescer-side (Max's round-3 probe) and post-flush FIFO-side (Sami M13 / Max's round-4 probe: fails 102 vs 151 under `+= 1`). All three cap tests assert on independently walked retained bytes, not the accumulator (verified to fail without the `+text.len()` fix: 8,328,386 / 8,328,272 vs 4 MiB); the walker itself is pinned two-sided against the accumulator (all four blinding mutants M17–M20 verified to fail it, including the walker+fix cancellation pair). - `cargo clippy -p buzz-acp --all-targets -- -D warnings` clean, `cargo fmt --check` clean - Desktop: `tsc --noEmit` clean; node tests **4366 passed, 0 failed** — snapshot-clone family fully pinned: watermark aliasing (round 4), turns aliasing and tombstone aliasing (round 5, pre-existing gaps; each mutant verified to fail exactly its target test with an in-test control). Prior rounds: cross-channel reorder processed, cross-channel-delayed null-turnId `turn_error` evicts only its own channel's turn, null-bucket replay idempotency, same-channel stale/duplicate still skipped, watermark survives community-switch save/restore - All pre-push hooks green at the pushed commit (branch-skew, desktop-check, desktop-test, rust-tests, desktop-tauri-checks) Part of the rate-limiting fix stack; independent of `eva/rate-limit-fixes` by design (separate minimal PR per Tyler's ruling). --------- Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> Signed-off-by: Sami <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@buzz.block.builderlab.xyz> Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> Co-authored-by: Sami <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@buzz.block.builderlab.xyz>
**Category:** fix **User Impact:** Pull requests can once again pass the Desktop smoke test suite. **Problem:** The inbox attachment-edit smoke test still looked for the composer's former “Attach image” label after the shared action was renamed to “Attach file,” causing shard 3 and the aggregate Desktop CI job to fail on every PR. **Solution:** Update the stale accessible-name selector to match the current composer control while preserving the test's media-tag coverage. <details> <summary>File changes</summary> **desktop/tests/e2e/inbox-edit.spec.ts** Updates the attachment button selector to use the current accessible label so the existing attachment-edit regression test reaches the behavior it is meant to verify. </details> ## Reproduction steps 1. Build the Desktop E2E application with `pnpm -C desktop build:e2e`. 2. Run `cd desktop && pnpm exec playwright test --project=smoke tests/e2e/inbox-edit.spec.ts -g "editing an immediate attachment reply preserves its media tags"`. 3. Confirm the test locates the “Attach file” control and passes. Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
## Problem Managed agents in internal Buzz builds should answer only their owner. Previously, an agent could keep a broader access setting and respond to other people, which did not match the access policy for internal builds. This PR makes owner-only access effective for every managed agent in internal builds and makes that restriction clear in the Desktop UI. Open source builds remain configurable. ## Changes - Enforce owner-only access when any managed agent starts or is deployed from an internal build. - Show the agent access control as locked to **Only me** in Desktop, with an explanation of why it cannot be changed. - Keep Welcome teammates working under the same rule without triggering unnecessary restarts. - Leave open source build behavior unchanged. This changes effective runtime access without rewriting stored or relay-advertised settings. The companion [#4064](#4064) explains the restriction in-thread when someone without access mentions an agent. The enforcement will remain inactive in shipped builds until [squareup/buzz-releases#74](squareup/buzz-releases#74) marks internal releases during the build. ## Screenshots | Before | After | | --- | --- | |  |  | ## Tests Added coverage for: - Runtime enforcement for [locally run agents](https://github.com/block/buzz/blob/e0165f52b52741a74184c9899e2b51eeec40c939/desktop/src-tauri/src/managed_agents/runtime/tests.rs#L196) and [deployed agents](https://github.com/block/buzz/blob/e0165f52b52741a74184c9899e2b51eeec40c939/desktop/src-tauri/src/commands/agents_tests.rs#L510). - The [current-build deployment path](https://github.com/block/buzz/blob/e0165f52b52741a74184c9899e2b51eeec40c939/desktop/src-tauri/src/commands/agents_tests.rs#L455), [invalid stored access](https://github.com/block/buzz/blob/e0165f52b52741a74184c9899e2b51eeec40c939/desktop/src-tauri/src/managed_agents/access_policy.rs#L98), and the [local startup guard](https://github.com/block/buzz/blob/e0165f52b52741a74184c9899e2b51eeec40c939/desktop/src-tauri/src/managed_agents/env_vars/tests.rs#L149). - Consistent enforcement across [both agent backends](https://github.com/block/buzz/blob/e0165f52b52741a74184c9899e2b51eeec40c939/desktop/src-tauri/src/managed_agents/access_policy.rs#L112). - Welcome teammates created as [locally run](https://github.com/block/buzz/blob/e0165f52b52741a74184c9899e2b51eeec40c939/desktop/src/features/onboarding/welcomeGuide.test.mjs#L384) or [deployed](https://github.com/block/buzz/blob/e0165f52b52741a74184c9899e2b51eeec40c939/desktop/src/features/onboarding/welcomeGuide.test.mjs#L393) agents, including [access-only](https://github.com/block/buzz/blob/e0165f52b52741a74184c9899e2b51eeec40c939/desktop/src/features/onboarding/welcomeKickoff.test.mjs#L202) and [runtime-related](https://github.com/block/buzz/blob/e0165f52b52741a74184c9899e2b51eeec40c939/desktop/src/features/onboarding/welcomeKickoff.test.mjs#L225) restart behavior. The full Desktop Rust and JavaScript suites, type checks, formatting, clippy, and file-size checks passed. Playwright E2E was not run. --- Originated from Buzz channel [buzz-agent-control](buzz://channel?id=cf5dada7-e26a-4887-ae41-b3bd5f42d3b2). Supersedes #2537. --------- Signed-off-by: Tom Brow <tomb@block.xyz> Signed-off-by: npub1tquskdu6yc4h8l7xxtceculxw600grekeq0xg2ukqfrwl7vrzg3quz3gmp <58390b379a262b73ffc632f19c73e6769ef40f36c81e642b960246eff9831222@buzz.block.builderlab.xyz> Co-authored-by: npub1tquskdu6yc4h8l7xxtceculxw600grekeq0xg2ukqfrwl7vrzg3quz3gmp <58390b379a262b73ffc632f19c73e6769ef40f36c81e642b960246eff9831222@buzz.block.builderlab.xyz> Co-authored-by: Amp <amp@ampcode.com>
## Summary - virtualize the unfiltered channel member roster instead of eagerly mounting every member card - retain the existing member search/add flow and archived-member behavior - cover a 500-member roster, bounded mounted rows, and scrolling to the final member in E2E ## Cause The members sidebar rendered every active member card at once. On large channels this mounted hundreds or thousands of avatars, profile/presence consumers, menus, and DOM rows, blocking the renderer even though fetching the roster itself is fast. ## Testing - `pnpm typecheck` - `pnpm exec biome check src/features/channels/ui/MembersSidebar.tsx tests/e2e/channels.spec.ts` - `pnpm build:e2e` - `pnpm exec playwright test tests/e2e/channels.spec.ts --grep 'members sidebar (virtualizes large channel rosters|can invite relay-authorized agents|can invite and remove managed agents|collapses same-persona managed agents)'` (4 passed) - pre-push: `desktop-check`, full `desktop-test` (4,371 passed), branch-skew Implemented by Carl on Wes's behalf. Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
…ny — with real nodes (#3862) ## Summary CI now proves the full Buzz shared-compute join story end to end: a member can discover another member's served model **through the Buzz relay alone** and run inference over the mesh, while a non-member gets nothing — the relay rejects its auth, and the mesh refuses to route for it even holding a leaked endpoint address. This is deliberately different from mesh-llm's own CI smokes (which bootstrap two nodes with a hand-carried invite token / mdns): here the **relay is the control plane**, exactly like the desktop app: 1. **Membership** — identities A and B are added via `buzz-admin` (kind:13534 NIP-43 roster); C is not. 2. **Advertise** — each member publishes a client-signed kind:30003 discovery note carrying its MeshLLM owner binding and (for the serve node) `serveTargets[].endpointAddr`, covered by an endpoint-binding signature — the exact payload shape the desktop coordinator publishes. 3. **Trust** — the serve node derives its admission allowlist from the relay (statuses ∩ roster) and requires the **exact expected {A, B} owner-id set** before starting with `TrustPolicy::Allowlist`. 4. **Join** — the client verifies owner + endpoint bindings and membership, then dials the relay-discovered endpoint (the desktop join-watcher's `dial_endpoint_addr` step). No out-of-band token. 5. **Infer** — a chat completion against the client's local OpenAI endpoint routes over QUIC to the serve node's model (CPU, SmolLM2-135M, ~105MB). 6. **Deny (differential)** — the stranger's NIP-42 auth must fail with the relay's own membership rejection (`restricted: not a relay member` — successful auth or any unrelated connect error fails the run), and dialing the leaked endpoint must not produce a routed inference — **while the trusted client re-proves inference immediately afterwards**, so a dead serve node can't masquerade as an admission denial. ## What's in the PR - `crates/buzz-relay/examples/mesh_relay_lifecycle_smoke.rs` — the harness. One process per node (mesh-llm keeps process-global state under `~/.mesh-llm`), orchestrator + serve/client/stranger roles, byte-identical binding payloads to `desktop/src-tauri/src/mesh_llm/identity.rs` (called out with keep-in-sync comments). Child stdout is pumped through a reader thread so every wait has a hard deadline; timed-out children are killed; exit statuses are checked. - `scripts/ci-mesh-lifecycle-smoke.sh` — provisions a membership-gated relay (throwaway owner + signing identities via `buzz-admin generate-key`), runs the harness, cleans up. Fails fast if :3000 is already occupied (a stale open relay would mask gating). - `scripts/start-relay-for-tests.sh` — gains opt-in NIP-43 membership env passthrough (`BUZZ_REQUIRE_RELAY_MEMBERSHIP` + `RELAY_OWNER_PUBKEY` + `BUZZ_RELAY_PRIVATE_KEY`). Default behavior unchanged. - `.github/workflows/mesh-lifecycle.yml` — separate, path-filtered, non-required workflow (mesh paths, the harness's dependency crates, `Cargo.lock`, dispatch), pinned to `ubuntu-24.04`. Caches the mesh native runtime + HF model keyed on the lockfile hash, so a mesh pin bump rolls the runtime cache. Uploads relay + harness logs on failure. ## Scope This is an **independent protocol harness**: it speaks the same wire protocol and payload shapes as the desktop but re-implements the binding/verification logic (the desktop crate is outside the workspace). Regressions inside the desktop's own discovery filtering are the desktop unit tests' job; what this smoke proves is that the relay + mesh-llm SDK + admission stack support the lifecycle end to end. ## Relationship to mesh-llm's CI Follows the shape mesh-llm's own CI proved stable (tiny CPU model, one runner, multiple real mesh-llm processes over real QUIC — cf. their `ci-two-node-client-serving-smoke.sh`), but swaps the token bootstrap for the relay-driven lifecycle, which is the part only Buzz can test. ## Validation Green on GitHub Actions (ubuntu-24.04) across three runs, including after rebases onto the mesh v0.74 upgrade (#3467) and latest main: ``` PASS 1/6: relay-derived allowlist is exactly {A, B} PASS 2/6: serve member ready + advertised model: jc-builds/SmolLM2-135M-Instruct-Q4_K_M-GGUF:Q4_K_M PASS 3/6: client member discovered + joined via relay PASS 4/6: inference routed over the mesh: "PONG" PASS 5/6: relay rejected the stranger's NIP-42 auth (membership gate) PASS 6/6: stranger denied (gossip visible, inference rejected: 503 all tunnels failed) while trusted inference still routes PASS: full relay-driven mesh lifecycle verified ``` Also validated locally on macOS. `cargo fmt --all --check` and `cargo clippy -p buzz-relay --all-targets -- -D warnings` pass. ## Notes - The harness follows the repo's mesh `[dev-dependencies]` pin automatically, so it doubles as a canary for future mesh upgrades (it already caught the v0.73.1 → v0.74.0 bump during development). - The stranger "deny" accepts either shape mesh-llm exhibits: no model visibility at all, or gossip visibility with inference refused — mesh-llm applies the receiving node's owner policy after the gossip handshake, so admission gates *routing*, not gossip. The differential trusted-inference re-check (PASS 6/6) is what makes that a real denial rather than a dead server. - Model-visibility windows are tunable via `MESH_CLIENT_WINDOW_SECS` / `MESH_STRANGER_WINDOW_SECS` if shared runners prove slow — pin a longer window in the workflow env rather than re-running the job. --------- Signed-off-by: Michael Neale <michael.neale@gmail.com>
## Summary - require the macOS process to be running from an actual `.app` bundle before initializing `UNUserNotificationCenter` - keep the existing bundle-identifier requirement - cover packaged, case-insensitive `.app`, raw `target/debug`, and extensionless paths ## Why PR #4799 guarded native notification initialization with `NSBundle.mainBundle.bundleIdentifier != nil`. Tauri embeds a bundle identifier in raw development executables, so `tauri dev` passed that guard and `UNUserNotificationCenter.current()` raised an uncaught `NSInternalInconsistencyException` because LaunchServices had no bundle proxy. ## Validation - focused macOS notification tests: 6 passed - direct raw debug executable no longer raises the notification-center exception - pre-commit formatting hook passed - pre-push package checks passed on pushed commit `f29a6664d2a863e7b8aa527f6149fd00b183e4de` The first push attempt hit an unrelated timing-test failure in `relay_admission::tests::concurrent_429_extends_the_window_for_parked_waiters`; its focused rerun passed, and the complete pre-push package suite passed on the next push. Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
…the authenticated socket (#4990) ## Problem Users on v0.5.5 report "Can't reach the relay" toggling with brief "connected" flashes (field reports; also the macOS confirmation in #4908). #4737 closed the stuck-reconnect gaps; this is the opposite failure: the client redials fine, but then kills its own healthy socket. Mechanism (all on `main`): 1. AUTH succeeds → session emits `connected` (`relayClientSession.ts:583`), then awaits `replayLiveSubscriptions()`. 2. Paged channel backfill issues history REQs (`relayReconnectReplay.ts`, page limit 500). 3. A `CLOSED rate-limited:` on a **history** REQ arms the rate-limit gate but still rejects the history promise (`relayClosedRecovery.ts:38-51`). 4. The rejection escapes `replayLiveSubscriptions()` → `resetConnection()` tears down the authenticated socket. 5. Reconnect → AUTH OK → replay rate-limited again → loop. Each iteration re-spends the rate-limit budget, so the loop is self-sustaining. ## Fix Contain backfill failures inside the replay. Each subscription's paged backfill now retries behind the rate-limit gate up to `PAGE_REPLAY_MAX_ATTEMPTS` (3), then degrades to live-only **for this connection**. Socket health no longer depends on backfill success. Nothing is lost: the replay cursor (`lastSeenCreatedAt`) only advances on delivered events, so the next reconnect replays the same missed window. ## Red/green proof - Commit 1 (Pinky): e2e injecting `CLOSED rate-limited:` into the mid-replay history REQ — **red on main** (expected 1 reconnect dial, observed 2; connected-flash then teardown). - Commit 2 (this fix): same test **green unchanged** — one dial, state stays `connected` through the rate-limit hint plus the next backoff window. Why existing coverage missed it: the prior rate-limit e2e pre-armed the gate *before* replay (replay politely waits), and the CLOSED-injection test targeted a *live* subscription (which has its own retry path). Nobody injected back-pressure from the history REQ itself. ## Verification - `pnpm test`: 4374/4374 pass. - `playwright test tests/e2e/relay-reconnect.spec.ts`: 14/14 pass, including the new spec. - `tsc --noEmit` clean; Biome clean on touched files (pre-existing warnings on main in `personaCatalogRelay.test.mjs` / `terminal.css` untouched). ## Not addressed here (follow-ups from the same field reports) - AUTH terminal latch is too aggressive for relay-internal `error:` rejections (3 strikes during a relay bad window → stuck until click/relaunch; #4908). - Server-side: `relay.drainJitterMs` (#4542) defaults to 0 — enabling it on the hosted relay removes the deploy thundering herd that triggers these rate-limit storms. --------- Signed-off-by: Pinky <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@buzz.block.builderlab.xyz> Signed-off-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@buzz.block.builderlab.xyz> Co-authored-by: Pinky <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@buzz.block.builderlab.xyz> Co-authored-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@buzz.block.builderlab.xyz>
## Summary - add a persistent vertical pill beside the active community - keep the selected state visually distinct from unread dots and mention badges - preserve the existing `aria-current` selection semantics ## Screenshot  ## Test plan - `pnpm exec biome check src/features/sidebar/ui/CommunityRail.tsx tests/e2e/community-rail.spec.ts` - `pnpm test` (4,387 passed) - `pnpm build:e2e && pnpm exec playwright test tests/e2e/community-rail.spec.ts --project=smoke` (20 passed) - pre-push hooks: desktop check and 4,387 desktop tests passed on `c1e80c66d12f73c4eb5c03a19e932439b14caf2d` Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
## Summary - add stable three-step guidance to desktop mobile pairing - move code confirmation inline and show animated completion states - preserve pairing reset behavior and reduced-motion support ## Test plan - `pnpm --dir desktop check` - `pnpm --dir desktop exec tsc --noEmit` - `pnpm --dir desktop exec playwright test tests/e2e/mobile-pairing-qr.spec.ts --project=smoke` - pre-push desktop suite: 4,387 tests passed --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Co-authored-by: Fizz <50a12680c76f1a52c0b7af8dbb17e02c583227c290fb93b9a3defb456114223f@buzz.block.builderlab.xyz>
## Why The focus/split E2E test could capture the thread root before its programmatic middle-thread scroll had settled, then incorrectly report a scroll-restoration failure. ## What - Poll until the requested middle-thread scroll position is applied - Require the captured anchor to intersect the thread viewport and differ from the root - Preserve the existing focus-to-split-to-focus viewport assertions ## Risk Assessment Low — test-only synchronization change with no production behavior changes. ## References - Original failure: https://github.com/block/buzz/actions/runs/30231271427/job/89870533541 - Buzz thread: buzz://message?channel=12dd513d-45fd-48ff-80ac-8596d2fcc9d3&id=87ce6024b4bf74bfac2fa75d9f7bbbcc8f8fe2df460afe534152c495929f51ba - Reproduced confidence: 20 consecutive targeted passes, full spec pass, `just desktop-ci`, and `just ci` Generated with Codex Signed-off-by: npub1x4hk035p3p9q39a3fcrd2fe30lpkrhr5dwe0cqzzjphxyyh8m0gsq4vqap <356f67c681884a0897b14e06d527317fc361dc746bb2fc0042906e6212e7dbd1@buzz.block.builderlab.xyz> Co-authored-by: npub1x4hk035p3p9q39a3fcrd2fe30lpkrhr5dwe0cqzzjphxyyh8m0gsq4vqap <356f67c681884a0897b14e06d527317fc361dc746bb2fc0042906e6212e7dbd1@buzz.block.builderlab.xyz> Co-authored-by: Wes <wesbillman@users.noreply.github.com>
## Summary - replace ambiguous avatar play controls with centered Start and Restart pills - preserve avatar clipping while smoothly morphing actions into the running status dot - use accessible warning contrast and real restart behavior without a duplicate status badge ## Validation - `just ci` - focused Playwright coverage for morphing, shared geometry, and light/dark contrast Signed-off-by: kenny lopez <klopez4212@gmail.com>
…ges (#4959) ## Problem When `buzz-agent` exhausts retries on a stalled LLM call, the error message reads: ``` transport: error sending request for url (...) (cumulative 721s, 3 attempts) ``` That text is reqwest's generic pre-response failure string — identical whether the cause is a TLS abort, a reset connection, or a `read_timeout` fire. An operator reading the log cannot tell whether something broke or whether the LLM generation legitimately took longer than the configured timeout. ## Root cause (probe-confirmed) A live probe against `goose-claude-fable-5` with a 900s client timeout completed in **370s** — well past the default `BUZZ_AGENT_LLM_TIMEOUT_SECS=240`. Extended-thinking models emit zero bytes on non-streaming calls until generation is complete, so reqwest's `read_timeout` fires on byte-silence regardless of whether the server is healthy. The 46× exact-721s stall signatures in production logs (3 × 240s + backoff) are deterministic self-inflicted timeouts, not network faults. ## Fix ### Pure classifier over `{is_connect, llm_timeout, phase}` A new `timeout_message(is_connect: bool, llm_timeout: Duration, phase: TimeoutPhase)` pure function produces factual messages with the configured duration value embedded verbatim. Two thin wrappers (`classify_transport_error`, `classify_body_read_error`) extract the reqwest flags and delegate. The duration reaches the classifiers through a new `read_timeout: Duration` parameter on `post()` and `openrouter_post()`; callers pass `cfg.llm_timeout`. ### Messages emitted | Case | Message | |---|---| | Connect-phase timeout (`is_connect && is_timeout`) | `connect timeout: no connection established within 10s` | | Transport read-timeout | `read timeout: no response bytes received within 240s (consider raising BUZZ_AGENT_LLM_TIMEOUT_SECS)` | | Body-read timeout | `read timeout: no further response bytes received within 240s (consider raising BUZZ_AGENT_LLM_TIMEOUT_SECS)` | | Non-timeout | `transport: {reqwest text}` / `body read: {reqwest text}` (unchanged) | `LLM_CONNECT_TIMEOUT` is now a named `const` (was inline `from_secs(10)`). **Out of scope by explicit decision:** streaming support, changes to `MAX_RETRIES` or backoff. ## Files changed - `crates/buzz-agent/src/llm.rs` — `timeout_message` pure fn + `TimeoutPhase` enum + `LLM_CONNECT_TIMEOUT` const; two classifier wrappers updated; `post()` and `openrouter_post()` gain `read_timeout` param; tests replaced. ## Tests `cargo test -p buzz-agent`: **397 passed, 0 failed** at `294ce5897`. **Pure-function tests (no network):** - `timeout_message_connect_true_shows_connect_timeout` — `is_connect=true` → connect-flavored text with 10s value; both phases checked - `timeout_message_transport_phase_shows_read_timeout_and_duration` — transport phase includes 240s and config knob - `timeout_message_body_read_phase_says_no_further_bytes_and_duration` — body phase says "no further", shows 300s - `timeout_message_duration_is_not_hardcoded` — 600s supplied → 600s in output, not 240s **Loopback reqwest integration tests:** - `classify_transport_error_read_timeout_is_loopback_verified` — TCP connect succeeds, server sends no bytes; verifies reqwest sets `is_timeout && !is_connect` and message contains 50ms value - `classify_transport_error_non_timeout_preserves_reqwest_text` — controlled accept-then-close on an owned loopback listener → non-timeout error; asserts exact `transport: {err}` output equality - `classify_body_read_error_timeout_says_no_further_bytes` — loopback server sends headers + 4 bytes of a declared-1024-byte body, then holds; verifies `is_timeout`, "no further", 100ms value, config knob No test performs egress beyond loopback (`127.0.0.1`). The TEST-NET-3 dial is deleted. --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Signed-off-by: Alessandro Joabar <sandro@squareup.com>
* origin/main: fix(buzz-agent): classify read timeouts distinctly in LLM error messages (#4959) Refine agent runtime controls (#5026) test(desktop): await thread scroll anchor (#3174) Improve desktop mobile pairing flow (#5024) feat(desktop): show selected community in rail (#5000) fix(desktop): stop rate-limited reconnect backfill from tearing down the authenticated socket (#4990) fix(desktop): skip native notifications outside app bundles (#5004) ci: prove the relay-driven mesh lifecycle — discover, join, infer, deny — with real nodes (#3862) fix(desktop): virtualize channel member lists (#4991) fix(desktop): enforce owner-only access in internal builds (#4053) test(desktop): match attachment button label (#4993) fix(acp): pace observer telemetry at 1/s with per-channel batch envelopes (#4917) fix(desktop): enable the content security policy (#4614) Signed-off-by: Alessandro Joabar <sandro@squareup.com>
|
|
||
| - uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1 | ||
|
|
||
| - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 |
Collaborator
Author
|
🤖 Compute tutorial demo 500-deployment breathing mosaicThe tutorial’s large-company fixture rendered as a looping GIF. Full scenario walkthroughCycles through empty, single-device, small-company, and 500-deployment scenarios, holding each for four seconds. |
**Category:** new-feature **User Impact:** People who lose a desktop identity can securely restore it from a signed-in Buzz phone without creating a replacement identity. **Problem:** A fresh or identity-lost desktop could not recover its existing full Buzz identity from an already-authorized phone. **Solution:** Add a SAS-confirmed reverse NIP-AB transfer, durable desktop import, a dedicated mobile recovery entry point, and clearer desktop recovery dialogs with tested loading, drag-and-drop, and failure states. https://github.com/user-attachments/assets/e9215c9c-80d0-462f-9161-0fa184ca2f74 <details> <summary>File changes</summary> **crates/buzz-core/src/pairing/session.rs** Adds the reverse encrypted payload and source-completion state transitions used for phone-to-desktop recovery. **desktop/src-tauri/src/commands/identity.rs** Exposes the existing guarded identity commit path for recovery imports. **desktop/src-tauri/src/commands/pairing.rs** Adds recovery-mode pairing, durable nsec import, start serialization, stale-task protection, and explicit rejection of unsupported recovery payloads. **desktop/src-tauri/src/lib.rs** Registers the recovery pairing command. **desktop/src/app/App.tsx** Refreshes the recovered identity before continuing onboarding. **desktop/src/features/onboarding/machineOnboarding.ts** Adds recovery transitions to the onboarding state machine. **desktop/src/features/onboarding/ui/BackupPasswordTimeline.tsx** Adds the visual backup-to-password-to-unlock progression. **desktop/src/features/onboarding/ui/IdentityRecoveryPairing.tsx** Implements QR generation, copy fallback, SAS confirmation, cancellation, expiry, and completion UI. **desktop/src/features/onboarding/ui/MachineOnboardingFlow.tsx** Connects private-key, phone, and backup recovery paths to the onboarding flow. **desktop/src/features/onboarding/ui/NostrKeyImportForm.tsx** Polishes recovery dialogs, backup drag-and-drop, loading stability, and security copy. **desktop/src/shared/api/tauri.ts** Keeps the existing pairing API surface focused on standard desktop-to-mobile pairing. **desktop/src/shared/api/tauriPairing.ts** Adds the recovery pairing invoke without growing the ratcheted shared API file. **desktop/src/testing/e2eBridge.ts** Mocks recovery pairing commands and lifecycle events for browser tests. **desktop/tests/e2e/identity-lost.spec.ts** Covers lost-identity entry, QR/copy recovery, SAS, cancellation, expiry, success, errors, backup import, drag-and-drop, and screenshots. **desktop/tests/e2e/onboarding.spec.ts** Verifies recovered identities continue through harness setup without replacement-key side effects. **mobile/lib/features/pairing/pairing_page.dart** Adds recovery-only scanning and explicit identity-handoff warnings. **mobile/lib/features/pairing/pairing_provider.dart** Recognizes recovery codes, returns the signed-in nsec after mutual SAS approval, and waits for desktop completion. **mobile/lib/features/settings/settings_page.dart** Accepts the recovery route builder at the app composition boundary to preserve feature isolation. **mobile/lib/features/settings/settings_page/connection_section.dart** Adds the signed-in “Send identity to desktop” settings action. **mobile/test/features/pairing/pairing_page_test.dart** Covers recovery-only validation and handoff messaging. **mobile/test/features/pairing/pairing_provider_test.dart** Covers reverse payload encryption, confirmation ordering, success, failure, timeout, and cleanup. </details> ## Reproduction steps 1. Launch Buzz Desktop with identity-lost state and choose **Recover from your phone**. 2. Confirm the QR and persistent **Copy pairing code** fallback appear without layout shift. 3. On a signed-in phone, open **Settings → Send identity to desktop**, scan or paste the recovery code, and compare the six-digit SAS on both devices. 4. Confirm on both sides and verify Desktop restores the identity and continues to harness setup. 5. Repeat from identity-lost state with **Recover from a backup file**; verify picker and drag-and-drop both advance to password entry and restore the encrypted backup. 6. Exercise cancellation, mismatched/unsupported codes, expired sessions, and an invalid backup; verify each returns actionable, non-stuck UI. ## Screenshots ### Desktop phone recovery — complete flow | Recovery entry | Pairing QR | Code match | Receiving identity | |---|---|---|---| |  |  |  |  | ### iOS Simulator — complete handoff flow | Settings entry | Recovery scanner | Manual recovery code | Code confirmation | |---|---|---|---| |  |  |  |  | ### Encrypted backup recovery — adjusted file flow | File picker | Drag-and-drop target | Password step | |---|---|---| |  |  |  | ## Verification - `cargo test -p buzz-core pairing` — 71 passed - `just mobile-test` — 1,169 passed - `pnpm build:e2e && pnpm exec playwright test identity-lost.spec.ts --project=smoke` — 15 passed - Full pre-push gates — desktop checks, desktop unit tests, Rust tests, Tauri checks, and mobile tests passed --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz> Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
Signed-off-by: Alessandro Joabar <sandro@squareup.com>
* origin/main: feat(identity): recover desktop identity from a signed-in phone (#4845) Signed-off-by: Alessandro Joabar <sandro@squareup.com>
sandro-sq
marked this pull request as ready for review
August 6, 2026 20:33
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
Adds a dedicated Mesh Compute experience for viewing and managing shared compute across a community.
Related issue
N/A — none found.
Testing
Verified with the full pre-push suite after merging the latest
origin/main:desktop-checkrust-testsmobile-testdesktop-testdesktop-tauri-checksbranch-skewAlso added unit and end-to-end coverage for:
Screenshots