mirror of
https://github.com/block/buzz.git
synced 2026-10-27 12:40:30 +00:00
4e1770aa3ee7552e2bb50e897e113c22e30488de
199 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
4e1770aa3e |
Add mobile VISION (#7710)
## Summary Add the Buzz Mobile vision covering intended experience, architecture, shared client behavior, verification, release cadence, and critical functionality. Link it from the mobile README; the existing agent instruction to read relevant `VISION_*.md` documents makes it discoverable during planning and review. ### Related issue No duplicate issue or PR found. ### Testing Documentation-only change. Diff checks and pre-push hooks passed. Repository-wide `just ci` is running locally; Rust formatting has passed. --------- Signed-off-by: Tom Brow <tomb@block.xyz> |
||
|
|
01b6174a1c |
fix(ci): don't run desktop tests for purely mobile client changes (#7709)
Mobile-only PRs currently trigger desktop CI because the desktop test filter's glob inadvertently matches every path outside Tauri. Fix that filter so that pure mobile changes skip desktop builds, tests, and their relay artifact producer. ### Verification - 12 regression tests exercise the exact pinned paths-filter action on mobile, desktop, Tauri, backend, mixed, workflow, and documentation changes. - Restoring the bad glob makes the mobile-only regression test fail. - The filter tests, existing required-context isolation contract, workflow syntax validation, and Biome pass locally. --------- Signed-off-by: Tom Brow <tomb@block.xyz> |
||
|
|
76f7e5ef0c |
feat(push): make gateway origin configurable (#7261)
Make the push gateway origin configurable across the relay, gateway, mobile client, and deployment chart. This is a fresh deployment. The gateway has only run on personal development infrastructure, and push has no existing enabled users. Legacy migration, configured-to-unconfigured mobile transitions, and rolling upgrades from pre-launch gateway binaries are not supported. Populated pre-launch databases are rejected with an informative error before migrations run; their data is untouched. Mobile builds may omit the gateway URL, leaving push unavailable. Once push is enabled, subsequent builds must retain that configuration. ### Validation - All applicable CI checks pass at `5f546cfaf403f0386f146d213adfb4392f4ec29c`, including Rust, mobile/Swift, desktop/integration, security, image builds, and Helm checks. An unrelated agent cancellation test passed on retry. - PostgreSQL CI passed 384 tests, including the production-entry-point regression for refusing populated pre-launch databases before migrations, fresh initialization, and subsequent initialized deployments. - Focused local validation passed all 10 gateway PostgreSQL tests under the CI discovery filter and all 3 unconfigured-mobile tests. Repository-wide PostgreSQL discovery validation and the complete pre-push hooks passed. - Physical-device APNs/App Attest behavior and deployment to a live cluster were not verified in this work. --------- Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: Codex <noreply@openai.com> |
||
|
|
4d08194ea9 |
Deduplicate thread context by ACP session delivery (#7620)
## Summary Buzz can repeat a thread reply in later prompts after the provider already received that event as context. The old fix treated every message signed by the agent key as retained by the current provider session. That could hide an unseen heartbeat reply because independent sessions share the same signing key. This PR now removes only exact event IDs already delivered to the live ACP session. It uses bounded overfetch so removed events do not shrink the new-context window, and it preserves unseen same-key replies from other sessions. The context header reports `truncated=true` whenever session history was omitted. The harness calls `/count` only when the relay query finds a sentinel beyond its fetch window. This deliberately does not parse event IDs from shell output. A reply that the agent just published may appear once in the next context delta; after that event reaches the provider as context, later prompts omit it. --------- Signed-off-by: Salman Mohammed <smohammed@squareup.com> |
||
|
|
7c789dee09 |
Fix Apple Silicon iOS simulator builds (#7646)
Apple Silicon simulator builds fail because MLImage and MLKit contain device-labelled ARM64 objects. Enable the Flutter ML Kit package's upstream compatibility helper, which restores device labels for iPhone builds. This preserves the MLKit-based functionality (avatar background removal) and complements the notification-extension linker isolation in #7187. Adds a complete iOS simulator build to CI to catch this build regression. --------- Signed-off-by: Tom Brow <tomb@block.xyz> |
||
|
|
6146c4fd16 |
feat(git): add default-branch management to relay and CLI (#7562)
Authored by Brain and opened on behalf of Wes (`wesbillman`).
## Summary
Add `buzz repos default-branch get/set` and the relay operation it
needs. Git push changes branch tips but cannot select the server's
symbolic HEAD. This selects an existing branch without deleting
branches, moving refs, or changing packs.
- Reuse immutable Git manifests and captured-ETag CAS; concurrent
pushes/default changes conflict, including stale no-ops.
- Require request-specific NIP-98, body binding, fail-closed replay
protection, host tenancy, current channel membership and
repository-management authority. Push/project roles alone are
insufficient.
- Send the CLI mutation once, without redirects; ambiguous delivery
retains the observed digest and is non-retryable.
- Document the narrow HTTP exception to Nostr-first, delegation scope
and admission-time ACL revocation. Kind:30618 remains derived; the
manifest CAS is authoritative.
- Register the Postgres/Redis/MinIO route and fresh-clone tests in the
existing Backend Integration lane.
## Review fixes — `7457bda50afa6b52e36315572fa96928b2e21db6`
- Reject signed valueless `payload` tags when verifying a body; preserve
the shared verifier's intentional absent-payload compatibility. The real
settings route now proves 401, unchanged manifest pointer, and unchanged
kind:30618 event IDs for malformed and other invalid credentials.
- Require a nonempty returned branch and `head == refs/heads/{branch}`;
SET additionally requires the returned branch to equal the attempted
branch. Malformed/mismatched mutation responses remain non-retryable
`DeliveryUnknown`, retaining the attempted branch and original digest.
Invalid reads fail before any POST.
- Strengthen the later-push/fresh-clone regression to select
`release/v1` while `main` exists.
## Verification
At `7457bda50afa6b52e36315572fa96928b2e21db6`:
- Full pre-push passed (all 14 Rust test groups, serialized Tauri
tests/clippy, file-size and branch-skew gates; 522 seconds). No hooks
disabled.
- All four isolated Postgres/Redis/MinIO settings tests pass, including
real Smart HTTP `ls-remote --symref` and fresh clone on `release/v1`.
- All 15 focused NIP-98 verifier tests and three default-branch CLI
tests pass; malformed credentials and missing-branch responses were
reproduced failing before the fixes.
- Five targeted mutants are killed: restoring the valueless-payload
bypass, weakening canonical HEAD validation, accepting an empty branch,
dropping requested-branch confirmation, and omitting hydrated HEAD for
the selected non-main branch. Mutations ran in a separate worktree and
target directory; original patch bytes were verified restored.
- `cargo clippy -p buzz-auth -p buzz-cli -p buzz-relay --all-targets --
-D warnings`, formatting, diff and file-size checks pass on the fixed
tree.
Earlier validation at `bb195b140`: seven Git authorization database
regressions and 28 existing transport tests passed (three infrastructure
cases in that module not selected); four mutations killed
management/replay/CAS/redirect regressions. Full pre-push passed with
`RUST_TEST_THREADS=1` (all 14 Rust groups, Tauri clippy and 3,173 tests,
size/branch-skew gates). Serial execution avoids an unrelated
process-global Desktop discovery counter flake; no hooks disabled or
Desktop changes.
Carl and Jude's review findings are addressed in the new head; **fresh
reviewer and exact-head security confirmation remain required**. The
earlier source review is not approval of this revision. No claim of
green new-head CI.
## Rollout
Merge and **deploy the relay support first**, then build/use the updated
CLI and explicitly set/verify the desired default. No schema migration.
This PR does not deploy anything or change a live repository.
Originating conversation (channel
`8e2fda80-9142-4df8-8e14-da288bea6471`):
buzz://message?channel=8e2fda80-9142-4df8-8e14-da288bea6471&id=8a7a9cf12737abc1e16605f241300be940a1691ef0992b35197afe26c563108d
---------
Signed-off-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz>
Co-authored-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz>
|
||
|
|
ad9591c43b |
fix(desktop): order unnamed roster members by full canonical npub (#7503)
🤖 ## Summary - Channel members appear in the Members sidebar. A member who has never set a display name is listed under an abbreviated form of their public key (npub), and the sidebar previously sorted those unnamed members by that short label. Short labels are not unique — different keys can share one — so the order of unnamed members could look arbitrary or unstable. Unnamed members now sort by their full public key, so the order is deterministic. - When two members display the same name, the previous tiebreak was membership order (who joined first), which is not visible to a reader and can shift as roster data loads in. The tiebreak is now the full public key, so identical display names always land in the same order. - Nothing gets noisier on screen: the full key is used only for sorting, and the sidebar still shows the compact abbreviated form. Priorities are unchanged — authored (custom) names still outrank fallback labels, and role/current-user grouping still applies. - Scope is the desktop app's Members sidebar and member management: the two existing sort comparators. Mobile and other lists in the app are untouched. ### Related issue Based on #7495 (introduced the abbreviated npub labels this follows up on). The original five presentation PRs remain independently reviewable. No closer duplicate found. ### Testing - 6469 desktop unit tests, typecheck, and check pass. - The 3 existing consumer-seam E2E tests still pass; a new E2E test asserts the sidebar lists unnamed members in full-key order, with fixture members deliberately inserted in the opposite order so incoming membership order cannot mask the sort. - Negative check: reverting only this change makes the new ordering assertion fail, so it genuinely binds the new sort. - CI has not run on this PR yet. Buzz provenance: channel 1f0e4a3d-7e01-4efe-bb16-843b357f85c9 / task 340c3de9b27dbedb8453c0c7652220f9080d30fcc70a7c4f6e27fdd4fa378056 --------- Signed-off-by: Logan Johnson <loganj@squareup.com> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> |
||
|
|
44316ff72f |
feat(mesh): upgrade to mesh-llm 0.76.0-rc8 and recommend Qwen3.8 27B (#6189)
Upgrades Buzz's mesh-llm dependencies through the released `v0.76.0-rc8` tag (`2040765d`), including the Qwen3.8 curated recommendation and rc8's scheduler/runtime improvements. **Scope note:** the earlier open-relay/unenforced-admission mode has been removed from this PR at Mic's direction — it is not a product mode we want. Mesh admission remains roster/allowlist driven, exactly as on `main`: on a relay with no NIP-43 membership snapshot the mesh runs self-only. No NIP-11 mode probing, no mode-transition restarts. A future perimeter/admission strategy for open relays will be designed separately. This PR also: - seeds `BUZZ_AGENT_LLM_TIMEOUT_SECS=660` for mesh agents, above MeshLLM's 600-second backend timeout; - makes `desktop-tauri-clippy` lint both default and `mesh-llm` cfg graphs; - runs the feature-enabled desktop test suite in CI; - recommends Qwen3.8 27B Q4_K_M for 64 GB-and-larger machines, then ladders down through Gemma 4 E4B and Qwen 9B for smaller machines; - keeps stored shared-compute `auto` translated to MeshLLM's supported wire model `mesh`. RC8 verification: - `just ci` passed locally at `92ecc7ec933bdd4df804cc9f28a2b51efa5313c5`. - Pre-push differential gates passed, including both desktop Tauri cfg graphs and package tests. - A prior isolated runtime smoke used the RC8 binary's OpenAI endpoint for a Buzz-shaped system/user/tool/tool-result/final-response loop; all assertions passed and the isolated process was shut down. Perf previously measured on M5 Metal, Qwen3.8-27B-Q4_K_M: TTFT 0.22–0.32 s, ~25 tok/s streaming; agent-shaped turns ~1 s to first token after the first (prefix cache). --------- Signed-off-by: Michael Neale <michael.neale@gmail.com> Signed-off-by: Jimmy <1fe240cd1a8cf775f6f3060f115e5a303181f3abf28ad4cb0c2515f4a02b36a8@meshllm.communities.buzz.xyz> Signed-off-by: Alessandro Joabar <sandro@squareup.com> Co-authored-by: Michael Neale <michael.neale@gmail.com> Co-authored-by: Jimmy <1fe240cd1a8cf775f6f3060f115e5a303181f3abf28ad4cb0c2515f4a02b36a8@meshllm.communities.buzz.xyz> Co-authored-by: Mic Neale <305999590+micspiral@users.noreply.github.com> Co-authored-by: Alessandro Joabar <sandro@squareup.com> |
||
|
|
cd02b693aa |
refactor(relay): extract NIP-29 membership authorization (#7285)
## Why NIP-29 membership authorization for kinds 9000, 9001, and 9022 was embedded in a large async handler, requiring Postgres and Redis to test and repeating the last-owner rule across five call sites. This addresses [TheSentinel454/buzz#24](https://github.com/TheSentinel454/buzz/issues/24) without adding the broader state/repository abstraction suggested there because HTTP and WebSocket writes already share ingestion. ## What - Extract pure, typed membership authorization decisions while leaving database reads and mutations in `validate_admin_event`. - Preserve existing client-visible errors and independent database last-owner safeguards. - Collapse five last-owner policy restatements into one predicate and share the identical self-departure policy. - Include the relay decision modules in `just test-unit`, so these tests execute in CI. ## Risk Assessment Medium-low. This touches production relay authorization, but intentionally preserves wire behavior and database defense in depth; exhaustive decision tables and relay-backed tests cover the affected paths. ## Simplification This removes repeated policy from the orchestration path and makes the rule set directly testable without introducing a repository trait or second transport path. ## Verification Verified at `15255a090797f85874921120003c645962fefaed`: - `just test-unit` — all 10 package summaries passed. An initial run hit two unrelated timing-sensitive `buzz-acp` failures; the complete retry passed 905/905 in that package. - `cargo fmt --all -- --check` - `cargo clippy --workspace --all-targets -- -D warnings` - `just file-size-check` — 10/10 policy tests passed. - `just security-review-check` — 13/13 tests passed. - Push preflight — `push-head-scope`, `branch-skew`, `file-size-check`, `rust-tests`, and `desktop-tauri-checks` passed. - Blox Postgres relay lane — 84/84 at the byte-identical source patch; full live `e2e_relay` behavior matched the clean base (45 pass and the same pre-existing kind:9002 failure on each). Generated with Claude Code --------- Signed-off-by: tornquist <tornquist@squareup.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Codex <noreply@openai.com> |
||
|
|
0e878664b0 |
ci: split CI into reusable workflows (#7168)
## Why The monolithic CI workflow is a frequent merge-conflict hotspot. Splitting cohesive domains into same-repository reusable workflows keeps one centrally filtered entry point while letting Rust, desktop, relay/PostgreSQL, client, and security CI evolve independently. ## What - Keep `ci.yml` as the only push/pull-request orchestrator with unchanged concurrency and path detection. - Move 18 execution jobs into five `workflow_call`-only domain workflows without changing their runners, steps, matrices, caches, artifacts, permissions, or timeouts. - Keep the relay artifact producer with desktop integration, the complete PostgreSQL lane, and relay E2E consumers. - Preserve all 12 existing required GitHub Actions contexts through lightweight top-level compatibility gates, so the repository ruleset does not need to change. - Update the Rust-cache contract to follow Unit Tests into `_ci-rust.yml`. ## Risk Assessment CI-only change with moderate workflow-orchestration risk. The main risks are reusable-workflow output propagation, skip behavior, and visible check naming; the old required names remain explicit top-level jobs, and the draft will stay open until an exact-head GitHub Actions run and independent review are complete. Generated with Codex --------- Signed-off-by: Luke Tornquist <tornquist@squareup.com> Signed-off-by: tornquist <tornquist@squareup.com> |
||
|
|
5aed49b505 |
feat(buzz-agent): add DatabricksAuthCoordinator single-flight OAuth (#5545)
## What
Consolidates all Databricks OAuth acquisition behind one coordinator on
`PkceOAuthTokenSource`. Every entry point — the four `TokenSource`
methods (`bearer`, `bearer_no_browser`, `refresh_now`,
`interactive_login`) plus the public `acquire_with_intent` — routes
through a single `acquire()`/`acquire_locked()` core that owns browser
and cooldown policy.
Before this, acquisition logic was scattered across those methods with
no coordination: concurrent callers (Desktop discovery, the saved-agent
model picker, managed-runtime inference) could each pop their own
browser, and a just-denied attempt would immediately re-prompt on the
next passive read.
## How
- **Intent policy.** `AuthIntent::{Auto, UserInitiated, Headless}`
decides whether a caller may open a browser and whether it honors the
cooldown. `Headless` never browses; `Auto` browses but honors an
unexpired cooldown; `UserInitiated` browses and bypasses+clears the
cooldown.
- **Two-layer single-flight per cache key.** An in-process registry
(`INFLIGHT`) coalesces same-key, same-intent callers onto one leader's
attempt before the file lock. The slot key is `(lock_path, AuthIntent)`,
so a `UserInitiated` sign-in never inherits an `Auto` leader's result. A
joined result is revalidated against the waiter's own contract. Across
processes, callers serialize on a `flock`-based advisory lock and share
success through the on-disk cache. RAII `Drop` releases both lock and
leader slot.
- **Joiner credential-state reconciliation.** `SlotPublish` carries the
full `CachedToken` on success. Each joiner reconciles its own
independent `state` cell under `state.lock().await` before returning:
adopt when absent, expired, or matching the joiner's rejected
credential; preserve any distinct newer usable credential. On a matching
shared failure, neutralize the joiner's in-memory rejected entry under
lock via `expire_rejected_memory` — durable disk mutation is reserved
for `acquire_locked` under the cross-process file lock. Without this, a
joining source's state remains stale or empty and subsequent plain
`bearer()` calls resurface the rejected or absent credential.
- **Validate-before-persist boundary.** `finish()` is the
candidate-token persistence boundary for refresh and browser results.
Before a token is written to cache or the cooldown is cleared, a bearer
equal to the caller's rejected bytes yields a typed failure.
- **Token neutralization.** When `acquire_locked` enters with `rejected
= Some(bytes)`, it calls `expire_rejected()` under the state lock before
any cache check.
- **Cross-process failure single-flight.** An `AttemptRecord` sidecar
records a monotonically-increasing generation, intent, result code, and
SHA-256 digest of the completing caller's rejected token. Adoption is
temporal (pre-queue snapshot predates current generation) and
digest-matched.
- **Typed outcomes.** `AuthError` with stable `code()`/`from_code()`
replaces display-text matching.
- **Durable cooldown sidecar.** Every failed browser attempt is recorded
next to the cache key. 5-minute expiry.
- **Windows disk persistence disabled.** On non-Unix platforms,
`persist()` is a no-op. Lock, cooldown, and attempt sidecars are active
on all platforms. Tests that seed or assert on the on-disk token cache
are `#[cfg(unix)]`-gated.
- **Injected browser opener** invoked while the localhost callback
listener is live.
## Tests
- `crates/buzz-agent/tests/databricks_auth_coordinator.rs`:
browser/cooldown/classification acceptance matrix with a scripted
`BrowserOpener` and stub OIDC provider. P1 regressions exercise the full
`finish()` → `acquire_locked()` → `acquire_leader()` →
`LeaderGuard::complete()` → joiner wiring:
- `test_inprocess_joiner_reconciles_stale_state_after_shared_success`
(Unix): two real sources both loaded locally-fresh-but-rejected X; after
shared success Y, subsequent plain `bearer()` on both returns Y, not X.
-
`test_inprocess_joiner_neutralizes_rejected_on_matching_shared_failure`
(Unix): B's matching rejected X is force-expired in memory after shared
`RefreshRejected`; subsequent read cannot return X.
- `test_inprocess_joiner_populates_empty_state_no_second_acquisition`
(non-Unix): empty A/B join a browser success; B's subsequent headless
read returns Y without a second browser (no disk fallback on non-Unix
exposes the regression).
- `test_crossprocess_userinitiated_waiter_adopts_predecessor_denial`
(snapshot-marker barrier replacing an earlier sleep for deterministic
generation ordering).
- `auth.rs` in-crate tests: lock-primitive edges, disk-recheck on shared
failure (`#[cfg(unix)]`), and:
- `test_joiner_reconciliation_blocked_until_state_lock_released`:
deterministic direct-poll proof that awaited reconciliation requires
`state.lock().await`. The test task holds B's state mutex and manually
polls a pinned real `acquire()` future — Poll 2 (slot published, mutex
still held) must return `Pending` because `lock().await` blocks; with
`try_lock` instead, Poll 2 returns `Ready`, failing the assertion.
- `test_joiner_preserve_distinct_newer_credential`: deterministic direct
polling parks B at `slot.wait()`, then writes Z directly into B's state
in the same task, then publishes Y and awaits completion. B must return
Y but leave state == Z. Mutation check: unconditional adoption
overwrites Z with Y, failing the state assertion.
- `test_joiner_shared_failure_recovers_disk_replacement` (Unix): the
matching-failure joiner enters the recovery branch — after
`expire_rejected_memory` (in-memory, empty state no-op) it reads a
sibling-written disk replacement via `usable_from_disk` and returns it.
Mutation check: removing the `usable_from_disk` recovery branch returns
`Err(RefreshRejected)`.
- `test_joiner_failure_does_not_write_disk` (Unix): byte-for-byte
disk-invariance regression — a matching-failure joiner calls
`expire_rejected_memory` and must not touch the on-disk cache. An
independent process C may write a valid replacement between A's failure
and B's reconciliation; this guard ensures B's unfenced in-memory
neutralization cannot overwrite C's concurrent disk write. Mutation
check: reverting to `expire_rejected` rewrites the file (`expires_at =
0`), changing the bytes and failing the assertion.
- `test_lock_timeout_leaves_cooldown_sidecar_byte_for_byte_untouched`: a
waiter past its deadline returns `LockTimeout` before entering
`acquire_locked`; the cooldown sidecar bytes are unchanged. Documents a
pre-lock limitation: `LockTimeout` callers do not neutralize state or
sidecars.
## Scope / follow-ups
- **Runtime 401 handling is deferred.** This PR owns acquisition
single-flight and policy.
- **Desktop wiring is Phase 2** (not in this PR's boundary). Confined to
`crates/buzz-agent/`.
- **Windows DACL** is a follow-up once the `windows-sys` binding is
available.
## Stack
Built on [#5534](https://github.com/block/buzz/pull/5534)
(`hayt/databricks-oauth-cache-hardening`), now merged. Retargeted to
`main`.
---------
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
|
||
|
|
b270437a62 |
fix(mobile): isolate extension linker flags; complete iOS build in CI (#7187)
### What changed? Clear inherited linker flags on the NotificationService target in Debug, Release, and Profile. The extension continues to link BuzzPushKit through its target framework phase, while Runner retains its CocoaPods plugin flags. Extend the macOS mobile CI lane to install Flutter dependencies and build the complete unsigned iOS Release app. This exercises the production Flutter, CocoaPods, Runner, BuzzPushKit, and NotificationService build graph on every mobile change. ### Why? The iOS 0.16 RC archive exposed that NotificationService inherits Runner-only CocoaPods linker flags through the shared Flutter xcconfig. That made the extension link Flutter plugins without the Flutter engine and fail on unresolved Flutter symbols. The previous CI coverage built only the standalone BuzzPushKit package, so it could not detect an app or extension linker regression. ### How is it tested? - A complete unsigned Release build of the Runner scheme succeeded with Xcode and linked, embedded, and validated `NotificationService.appex`. - The new GitHub Actions lane builds the full unsigned iOS Release app with `flutter build ios --release --no-codesign --no-pub`. - Pre-commit and pre-push hooks passed. --------- Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: Codex <noreply@openai.com> |
||
|
|
4365883151 |
chore(ci): lower Codex security review effort (#7179)
Changes `CODEX_REASONING_EFFORT` from `max` to `high` in the `security-review` job. `max` increases both normal review latency and exposure to the upstream PTY-shutdown hang ([openai/codex-action#169](https://github.com/openai/codex-action/issues/169)) where Codex finishes writing its output file but holds stdio open. The composite `uses:` action ignores the 30-minute step timeout and continues running until the 40-minute job timeout fires; at that point GitHub cancels the entire job and the `always()` salvage step never gets to run. `high` retains a high reasoning setting while trading some depth for speed, reducing typical completion time and shrinking the window during which a hung process blocks the salvage path. No other behavior changes. Action pin, CLI version, model, output schema, prompt, and timeout values are unchanged. Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> |
||
|
|
e17a0d44c3 |
fix(dev): keep the canonical profile when launching from desktop/ (#7143)
## Summary Carl, an automated agent, implementing on Wes's request. Fix ordinary Desktop dev checkouts being misclassified as linked worktrees when launched through `just production`, `staging`, or other recipes that source `instance-env.sh` from `desktop/`. From the repository root, Git reports both directories as `.git`. From `desktop/`, it can report an absolute `--git-dir` and `../.git` for `--git-common-dir`. The textual comparison treats them as different and chooses a branch-suffixed app profile (for example `.dev.main`) instead of the canonical `.dev` profile populated by the agent-adoption script. - Request `--path-format=absolute` for both paths. Actual linked worktrees still have distinct Git/common directories and retain their existing isolated app identities. - Add eight isolated fixture cases executing the real environment script with real Git: ordinary root/subdirectory on main and feature branches, linked root/subdirectory, detached linked subdirectory, and symlinked ordinary subdirectory. Paths contain spaces. Only native icon generation is stubbed; no user profile, keychain, app, or relay is accessed. - Run the regression in the existing always-run CI contract job. No agent migration, global-settings copy, keychain changes, profile merging, branch-label redesign, or packaged-release behavior changes. Existing Git `--path-format` support is required (Git 2.31+). ### Related issue Searched open PRs for `instance-env` and worktree titles, and open issues for dev-profile/adoption reports. No matching path-normalization fix found. - #7142 touches `instance-env.sh` for descriptive labels. This fix is independent; it uses a distinct test filename to avoid colliding with that PR's new `test-instance-env.sh`. - #6915 proposes a separate release-profile launch recipe; this fix preserves the current dev-profile contract instead. ### Testing At exact head `0a09a2f3716b0378546b66be2302b750ed4330b9`: - `scripts/test-desktop-instance-detection.sh`: **8/8 passed**. - Before the production change, the same regression failed on the ordinary checkout's `desktop/` case: actual `xyz.block.buzz.app.dev.main`, expected `xyz.block.buzz.app.dev`. - `bash -n scripts/instance-env.sh scripts/test-desktop-instance-detection.sh`: passed. - `git diff --check`: passed before commit; pre-commit and commit-msg hooks completed. - Applicable pre-push gates passed: branch-skew, push-head-scope, and file-size policy (6 tests plus all three entrypoints). Rust/Desktop/Mobile package lanes were correctly inapplicable to this shell/CI-only diff. No native app launch or first-boot key import was performed. The validated boundary is the generated launch environment/profile identity, not successful agent startup. Hosted CI has not been checked or represented as green. Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz> |
||
|
|
bd73490418 |
ci: run PostgreSQL tests in isolated lane (#6730)
## Why PR #6660 introduced four PostgreSQL-backed persistence tests and a focused CI selector, but an exact test-name list does not automatically cover future database tests. Broad ignored-test execution also exposed shared-schema races and ambiguity between desired-state and migration-applied schema expectations. ## What - Establish a discoverable convention: PostgreSQL unit modules use `postgres_tests`, PostgreSQL integration binaries use a `postgres_` prefix, and unrelated external-infrastructure tests use an `external_infra_` prefix. - Add a dedicated nextest PostgreSQL profile and archive-backed CI job covering the relevant crates without enumerating test names. - Create a run-scoped desired-state source database and a unique PostgreSQL database per test process for parallel-safe isolation, with cleanup on success, failure, or interruption. - Route destructive migration and migration-parity tests to clean `template0` databases while desired-state tests clone the desired-state database. - Document discovery, schema modes, required role privileges, and a bounded portable local runner. - Keep the existing infrastructure-free unit-test jobs unchanged. This is intentionally limited to test and CI harness behavior. It does not move production database code, change persistence semantics, or implement issue #20's broader shared test-utility refactor. ## Risk Low production risk because all changes are confined to tests, CI configuration, documentation, and test harness scripts. Remaining operational risks: - Cleanup retries `dropdb --force` five times across roughly four seconds. Exhaustion warns with the database name but deliberately does not mask the test result; individual PostgreSQL diagnostics are suppressed. - The seven-package boundary is duplicated between the runner and CI archive and must remain synchronized if PostgreSQL tests move to a new crate. - Portable SHA fallback branches were exercised on Linux; no macOS Blox workstation was needed for this Linux CI artifact. - GitHub's unchanged generic Unit Tests job currently fails while cold-linking `buzz-voice` because `sherpa-onnx-c-api` is absent. The branch changes no voice/build/toolchain inputs; the exact parent previously passed that job, and Blox passes the same infrastructure-free suite (1,450/1,450). A prior-head one-job retry and the final-head run both reproduced the hosted-runner failure. ## Verification Author Blox workstation `2020088`: - `cargo fmt --all -- --check` - `just clippy` - `just test-unit`: 1,450 passed; PostgreSQL tests remain skipped in fast jobs - PR #6660 focused tests: 4/4 passed through the final runner - Full lane: 282/282 passed in 10.087s with a non-superuser role limited to `CREATEDB`, `CREATEROLE`, and `pg_read_all_stats` - 272 desired-state tests - 10 migration-applied tests - 6 explicitly filtered external-infrastructure tests - Cleanup fault injection: a deliberately underprivileged 282-test run produced the expected 277 passes and 5 failures, and the post-run catalog audit found zero lane databases after retry cleanup. The restored successful run also left zero lane databases. GitHub exact final head `92c231e48f299ea2af23817763f5ae80ac013d68`: - [PostgreSQL Tests](https://github.com/block/buzz/actions/runs/32789030203/job/97630827823): 282/282 passed across 10 binaries in 98.886s; 1,240 skipped, including 6 via the profile filter - The shared relay/PostgreSQL archive predecessor completed successfully - [Unit Tests](https://github.com/block/buzz/actions/runs/32789030203/job/97626930420): unrelated `sherpa-onnx-c-api` native-link failure described above - Independent exact-head review on separate Blox workstation `2022762`: no substantive findings after all initial findings were addressed. The reviewer independently verified cleanup retry behavior, exit-status preservation, shell portability, syntax, and a clean exact-head worktree. ## References - Stacked on #6660 at exact head `561de54be4d9c2b622b7c2aa5b61bc3068f47e2a` - https://github.com/TheSentinel454/buzz/issues/20 - https://github.com/TheSentinel454/buzz/issues/20#issuecomment-5398526792 Generated with Codex. ## Update — August 24, 2026 review follow-up - Centralized the repeated PostgreSQL test URL resolution in crate-local, test-only helpers without introducing the broader shared utility refactor from issue #20. - Restored descriptive hybrid/Redis test function names. Structural `external_infra_*_tests` modules now own exclusion, and the nextest filter only recognizes module path segments. - Added a three-second source guard that scans every Rust file and fails CI when an ignored PostgreSQL test would be omitted or an external-infrastructure test would be included. Fixture tests cover accepted modules/binaries and both failure modes. - Removed the two implementation-plan documents. Performance profiling on Blox workstation `2027352` (same prebuilt archive and cargo-nextest 0.9.143 for every comparison): - Current per-test database model, 8 workers: 10.15–10.40s across three runs. The 283 `createdb` calls and 284 `dropdb` calls consumed 28.85–29.46 aggregate seconds, about 39–40% of aggregate test-process duration. - Reusing one database per worker without cleanup: 6.55–6.84s, about 35% faster, but all three runs failed because global matcher-queue tests inherited a quiesced community from an earlier test; the failing test varied with scheduling. - Reusing workers with `TRUNCATE … RESTART IDENTITY CASCADE`: all 282 tests passed, but total time regressed to 10.47–10.67s. The 265 truncate calls consumed 24.34–25.45 aggregate seconds, so truncation merely replaced most clone/drop cost. - Raising concurrency to 16 workers reduced one isolated-database run to 8.79s, but 24 workers exposed a cluster-global `pg_stat_activity` race. The profile remains at the proven-safe 8 workers. Decision: retain unique per-test databases. Harness cleanup uses `dropdb --if-exists --force`; it drops the database rather than truncating tables or deleting rows. Verification at local branch head `60086c1f6ee34a193c342255a5e8e6293b60e988`: - Discovery inventory: exactly 282 intended tests; all 6 hybrid/Redis tests present and structurally excluded. - PR #6660 focused persistence tests: 4/4 passed. - Full PostgreSQL lane: 282/282 passed three times; 1,240 skipped each run; wall time 9.72–9.96s without profiling shims. - Non-ignored tests with a desired-state database: 1,234/1,234 passed; one mesh-demo timeout from the first run passed immediately in isolation. - `cargo fmt --all -- --check` and all-target/all-feature clippy for `buzz-db`, `buzz-deletion`, and `buzz-relay` passed. Final independent re-review at exact head `42097c0136ab3fa8f0efe5720ae829c04575b9b6` on separate Blox workstation `2028455`: no substantive residual findings. The reviewer independently exercised ordinary strings, zero/one/three-hash raw strings, line/block comments, and string-contained lookalikes, then reran the full 353-file scan, shell syntax, Python AST parsing, and diff check. Independent review found that the source guard's initial regular expression could miss raw-string ignore reasons and treat a commented attribute as real. Final head `42097c0136ab3fa8f0efe5720ae829c04575b9b6` parses valid ordinary/raw Rust string literals only at attributes found in comment-sanitized source; regression fixtures cover both cases. The fixture suite, Python compilation, shell syntax, diff check, and full 353-file repository scan pass on Blox. ## Update — August 25, 2026 restack PR #6660 merged, so this follow-up was rebased from its former exact parent onto current `origin/main` (`8d2d0ff5ad42733e9949442c4b6358d0ba87f9a8`). The final candidate head is `05dcc2ab28948e3ab79bb44839a69f2ba44648a2`; the PR no longer carries #6660's pre-squash history. Fresh exact-head verification on Blox workstation `2028572`: - Discovery guard passed across 353 Rust files; inventory remained exactly 282 intended PostgreSQL tests. - PR #6660 focused persistence tests: 4/4 passed. - Full PostgreSQL lane: 282/282 passed three times; 1,243 skipped each run; wall time 9.70–9.87s. - `cargo fmt --all -- --check` and all-target/all-feature clippy for `buzz-db`, `buzz-deletion`, and `buzz-relay` passed. ## Update — August 25, 2026 final hosted verification - [GitHub PostgreSQL Tests](https://github.com/block/buzz/actions/runs/32801679226/job/97667835080): 282/282 passed in 53.787s; 1,243 skipped. - The final guard scanned all 353 Rust files successfully before the hosted lane ran. - The shared relay/archive prerequisite and infrastructure-free Unit Tests job both passed at exact final head `42097c0136ab3fa8f0efe5720ae829c04575b9b6`. --------- Signed-off-by: Luke Tornquist <tornquist@squareup.com> Signed-off-by: tornquist <tornquist@squareup.com> |
||
|
|
5673c3307e |
feat(desktop): add protected-build Bestie experiment (#6902)
## Summary Introduces a protected-build boundary for the default-off Bestie experiment without adding any Bestie product surface. - Official OSS builds select an empty protected-feature module and emit no Bestie/Chief metadata or implementation content. - Protected internal builds select a separate module graph containing the Bestie experiment definition. - Within an internal build, Bestie remains disabled until the user opts in under Settings → Experiments. - The production build runs an artifact matrix and fails if OSS output contains protected content or internal output lacks the Bestie manifest. ## Build contract | Build variant | User opt-in | Result | | --- | --- | --- | | Official OSS | Any/forged | Bestie absent from the compiled artifact | | Protected internal | Off | Bestie available but disabled | | Protected internal | On | Bestie enabled | The companion protected-release change is squareup/buzz-releases#91. It sets `VITE_BUZZ_BESTIE=1`, requires that exact value, forwards it into the signed macOS build, and asserts the contract in release validation. ## Why this is separate This gives later Bestie PRs one build-selected import seam. Protected implementations must be reachable only from the internal module so they never enter the official OSS module graph. ## Non-goals - No Bestie persona or provisioning - No sidebar, app-chrome, or message-toolbar UI - No entitlement or secrecy claim: the source is public; this boundary controls official Block artifacts ## Verification - Exact commit `523cf49ced03cba9be43836a54d6aa5d6923cc82` - Full `just ci`: 5,673 Desktop tests, 2,773 Tauri tests, 1,860 mobile tests, Rust/Tauri/web/mobile static checks and builds - OSS production artifact: scanner confirms no `Bestie`, `Chief of Staff`, or `builtin:bestie` content - Internal production artifact: scanner confirms the protected Bestie manifest is emitted - Both build orders verified; `dist` retains the requested variant for Vite/Tauri packaging --------- Signed-off-by: Arjun Mahanti <arjun@squareup.com> Signed-off-by: Fizz <fizz@buzz.local> Signed-off-by: Fizz <dae5f6af70b8695a8b83c8deae555f63be41630ec2b8cd493e41a439c9527dd8@buzz.block.builderlab.xyz> Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Fizz <fizz@buzz.local> Co-authored-by: Fizz <dae5f6af70b8695a8b83c8deae555f63be41630ec2b8cd493e41a439c9527dd8@buzz.block.builderlab.xyz> |
||
|
|
93237b4a74 |
fix(acp): wake agents from workflow messages (#6953)
> Pinky, an AI agent, is opening this PR on Wes's behalf.
## Summary
Workflow-generated messages can contain a valid agent mention but still
fail the ACP inbound author gate because the relay signs the event. This
keeps the existing wake policy and gives ACP a narrowly verified
effective author:
- preserve the workflow owner's existing `p` tag and all
rendered-mention `p` tags
- add explicit `["buzz:workflow-owner", <owner hex>]` provenance to
relay-generated workflow messages
- add `["buzz:workflow-mention", <agent hex>]` authority only for
mentions resolved from the stored, unrendered workflow step template
- accept that owner only for a verified kind-9 event signed by the
relay's current NIP-11 `self` key, with unique canonical workflow
metadata and an explicit workflow mention for the receiving agent
- route the verified owner through the existing author and in-flight
mode policies in both normal and setup listeners
- refresh relay identity after reconnects, retaining the last verified
key on transient fetch errors while treating a successful response
without `self` as definitive removal
Malformed, duplicate, forged, tampered, wrong-kind, and wrong-relay
attribution all fail closed to the raw event signer. `respond-to=nobody`
remains absolute. Old/mixed-version messages without the explicit
provenance retain their current fail-closed behavior.
## Trust boundary
The workflow owner means **“scheduled by,” not “authored every rendered
word.”** Trigger-controlled substitutions may still produce ordinary `p`
mention routing for compatibility, but they cannot mint
`buzz:workflow-mention` authority. Only a target named in the durable
owner-authored step template can receive that authority.
The author gate is not bypassed: after relay signature/provenance
verification, the effective owner is evaluated under the same
`owner-only`, `allowlist`, DM, and `nobody` policies used for ordinary
messages. Owner control commands continue to use the raw event signer.
## Why this PR
This is the focused immediate fix for waking an **online** agent from a
stored workflow mention. Earlier attempts were not a finished mergeable
fix and had materially different or incomplete trust designs. Larry's
larger draft stack addresses durable delivery across restarts; that
remains valuable future work and can supersede this effective-author
path when it lands.
## Validation
At exact clean commit `fe5b55619fe44176343eefb4cb7fe180df45a7d8`:
- `buzz-relay workflow_sink`: 25/25 passed, including all four ignored
PostgreSQL cases
- `buzz-acp --lib`: 845/845 passed
- `buzz-workflow --lib`: 169/169 passed (2 unrelated PostgreSQL tests
ignored)
- warnings-denied Clippy passed for the changed Rust packages
- `cargo fmt --all -- --check` passed
- `git diff --check` passed
- repository pre-push gates passed, including branch-scoped Rust tests
- CI now selects the ACP library tests and the relay's pure + PostgreSQL
workflow-sink tests so these guards cannot silently remain unexecuted
The production event-to-author gate is shared by normal and setup
listeners and has biting regression tests for accepted explicit
attribution, legacy owner-`p` rejection, and forged-attribution
rejection.
## Exact-head local relay + ACP proof
Following the release-binary/local-relay shape in `TESTING.md`, the
exact commit above passed a fresh isolated real-process matrix using:
- a freshly recreated Postgres database with migrations
- isolated Redis
- exact-head release `buzz-relay`, `buzz`, `buzz-admin`, and `buzz-acp`
binaries
- newly provisioned owner, channel, and bot member through the CLI
- workflow creation and triggering through the running relay
- a deterministic ACP protocol subprocess capturing actual
`session/prompt` dispatches
- a NIP-11 `self` value verified against the running relay signer
Cases:
1. A stored explicit workflow mention woke an `owner-only` agent exactly
once.
2. A workflow message without an agent mention did not wake it.
3. A non-relay signer forging every workflow authority tag did not wake
it.
4. Trigger-controlled `{{trigger.text}}` containing `@Wake Agent`
retained ordinary `p` routing but received no authority-bearing
workflow-mention tag and did not wake the agent.
5. `respond-to=nobody` remained absolute for a valid relay-authenticated
workflow mention.
The deterministic ACP subprocess isolates and directly proves relay →
ACP authorization and prompt dispatch without depending on external
model behavior.
## Deployment and residual risk
Relay and ACP changes must be deployed together for the new wake
behavior; mixed versions fail closed. Production paired-deployment proof
remains distinct from the successful local integration run. Setup-mode
behavior has automated coverage but was not a separate case in the
five-case local matrix. Relay-key rotation is observed at ACP
startup/reconnect; transient NIP-11 errors retain the last verified key,
an intentional availability tradeoff documented in code.
---------
Signed-off-by: Pinky <5f5ab050ec58ae208332edd544ebf705221e24c1b86d82a6ca07038a7a8f6ac9@buzz.block.builderlab.xyz>
Signed-off-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Pinky <5f5ab050ec58ae208332edd544ebf705221e24c1b86d82a6ca07038a7a8f6ac9@buzz.block.builderlab.xyz>
Co-authored-by: LioLionel <62820906+LioLionel@users.noreply.github.com>
Co-authored-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz>
Co-authored-by: Carl <9d00794d3df50972eb8b615511783cab12a77a8fd5dd5edd58073ec73b54bd8b@buzz.block.builderlab.xyz>
|
||
|
|
17ecf0b8c9 |
fix(ci): salvage Codex review output on PTY-shutdown hang (#7042)
Codex CLI can leave a PTY descendant holding the action's inherited
stdio after the turn completes. The `runCodexExec.ts` wrapper waits on a
`close` event that never fires, so the `Review pull request` step hangs
until the job timeout kills it — discarding the finished review the CLI
already wrote to disk.
The CLI writes the completed review to the `--output-last-message` file
(exposed as `output-file`) **before** the hang. This PR adds a salvage
step that recovers it, and sets the step and job timeouts to preserve
the full 30-minute Codex execution budget.
**Changes (`codex-security-review.yml`):**
- Add `output-file: ${{ runner.temp }}/codex-review.json` to the `Review
pull request` step so the CLI writes the result before the hang.
(`runner` context is valid in `steps.with`; not in `jobs.env`.)
- Add `timeout-minutes: 30` and `continue-on-error: true` to the Codex
step — a hang now costs ≤30 minutes instead of 40, and the salvage step
still runs.
- Set job `timeout-minutes: 40` to give setup, step cancellation, and
salvage sufficient headroom without colliding with the Codex execution
budget. The original 30-minute job timeout was too narrow: evidence from
run
[33114428326](https://github.com/block/buzz/actions/runs/33114428326/job/98665369165)
shows completed output appearing 28m46s after step start, meaning a
20-minute step timeout could kill a legitimate review before the salvage
file exists.
- Add a `Salvage review output` step with `if: always()`: prefers
`steps.run_codex.outputs.final-message` on a clean exit; falls back to
the output file when the step timed out. The output file path is set in
the step's own `env` block (`CODEX_OUTPUT_FILE: ${{ runner.temp
}}/codex-review.json`), where `runner` is valid. Validates shape
(non-empty JSON object, has `overall_risk`); fails the job hard if
neither source is present.
- Wire the job `outputs.review_json` to
`steps.salvage.outputs.review_json`.
**Changes (`Justfile`, `ci.yml`):**
- Add `actionlint .github/workflows/codex-security-review.yml` to
`security-review-check` so expression-validity errors are caught
locally.
- Provision `actionlint` via Hermit (pinned v1.7.12) rather than a
one-off `Install actionlint` curl step, so the same binary is used
locally and in CI.
**Security posture is unchanged:** the salvage step reads the action's
own output and a file written to `runner.temp` — neither is
PR-controlled. Credential-stripping env block on the Codex step is
untouched.
Note this is a temporary workaround until
https://github.com/openai/codex-action/issues/169 is addressed
---------
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
|
||
|
|
3ed623bb21 |
feat(db): configurable writer session timeouts (lock, idle-txn, statement) (#6229)
## Why A wedged relay boot pod holding a relation lock can park every other writer in the fleet behind it: DB load pins at pool capacity in `Lock:relation` waits while CPU stays flat, and nothing server-side releases the lock until the holder dies. We hit exactly this in production — ~1,400 sessions queued behind one crash-looping pod's boot transaction for ~20 minutes until kubelet killed the container. ## What Applies session-level Postgres timeouts to every **writer** connection inside the existing single `after_connect` hook in `buzz-db`, all env-tunable through the same `Config::from_env → DbConfig` path as the existing pool-size knobs: | Env var | GUC | Default | Effect | |---|---|---|---| | `BUZZ_DB_LOCK_TIMEOUT_MS` | `lock_timeout` | 5000 | statements waiting on any lock fail fast instead of parking behind a wedged holder | | `BUZZ_DB_IDLE_TXN_TIMEOUT_MS` | `idle_in_transaction_session_timeout` | 60000 | reaps wedged clients idling inside an open transaction while holding locks | | `BUZZ_DB_STATEMENT_TIMEOUT_MS` | `statement_timeout` | 0 (off) | opt-in runaway-statement cap; off by default because startup migrations/backfills legitimately run long statements | `0` disables a timeout (Postgres semantics) and deliberately passes through the env parsing — unlike the pool-size knobs where `0` falls back to the default. The reader pool is untouched: replica sessions never take contended locks and already fail acquire in 150 ms. Deployers tune these via plain env vars (`.env`, or `relay.extraEnv` in the Helm chart) — no code changes needed. ## Behavior change to note With the 5 s default `lock_timeout`, a boot-time migration or backfill that waits >5 s on a lock now errors (surfacing in logs / crash-looping the pod) instead of stalling silently. That is the intended visible-failure-over-fleet-stall tradeoff; deployers with slow contended migrations can set `BUZZ_DB_LOCK_TIMEOUT_MS=0`. ## Testing - `cargo test -p buzz-db -p buzz-relay` — buzz-db green; buzz-relay has 9 failures that also fail on clean `main` in this environment (api::admin/api::media/mesh_demo — unrelated, pre-existing). - New config test covers override / `0`-passthrough / invalid-fallback for all three env vars. - Extended the existing `writer_pool_safety_hook_is_single_and_composed` source-shape test so the timeouts can't drift out of the single `after_connect` hook (SQLx replaces hooks — a second hook would silently disarm the floor guard). - `cargo fmt --check` and `cargo clippy --all-targets` clean for the touched crates. Closest existing PR/issue: none found. --- **Update Aug 28, 17:06 EDT:** Rebased onto `main` at `a3730784fc` and addressed the latest correctness review. - Ported the timeout policy onto the refactored `buzz-db::runtime` pool constructor and kept the shared env overlay for relay, admin, deletion, and audit writers. - Migration/schema-destruction connections now disable `lock_timeout` and `statement_timeout` for their intentional long wait/DDL path. This supersedes the earlier “Behavior change to note”: contended boot migrations wait for the current migration owner rather than crash-looping after five seconds. - The audit worker now preserves and retries the same entry on PostgreSQL `55P03` lock timeouts, using exponential backoff capped at one second. Other database errors retain the existing terminal error behavior, and retries emit `buzz_audit_log_lock_retries_total`. - Added CI-backed PostgreSQL regressions for writer GUC installation/migration exemption, audit-pool lock timeouts, and worker recovery. The worker regression holds the real audit advisory lock past `lock_timeout`, observes a retry, releases the lock, and proves the original entry is appended exactly once. Current verification supersedes the earlier testing notes: workspace Rust clippy passed with warnings denied; all nine infrastructure-free backend unit-test lanes passed; all three focused PostgreSQL regressions passed against PostgreSQL 17; formatting, diff checks, file-size guards, and desktop frontend checks passed. The Linux Blox workstation could not run the unrelated Tauri native lane because `glib-2.0` is absent, so that platform check is left to PR CI. --- **Update Aug 31, 11:09 EDT:** Rebased onto current `main` at `c3132c3ee9` and reran the requested audit-lock contention scenario on Blox at head `896c3fe9ed`. - `git range-diff` reports both PR commits unchanged by the rebase; the branch remains two commits and the worktree is clean. - `cargo fmt --all -- --check` and clippy with warnings denied passed for `buzz-db`, `buzz-relay`, `buzz-admin`, and `buzz-deletion`. - All three focused PostgreSQL 17 regressions passed: writer session timeout/migration exemption, audit writer timeout bounds, and audit worker recovery of the original entry exactly once. - Live protocol verification used a head-built relay and CLI, native PostgreSQL 17/Redis, `BUZZ_DB_LOCK_TIMEOUT_MS=300`, and an eight-second hold on the community audit advisory lock. The real message was accepted and persisted once while the lock was held; its audit-row count remained zero during contention while retries accumulated. After release, exactly one `event_created` audit row appeared and remained exactly one after an additional two-second duplicate check. The run recorded nine lock-timeout retries, zero audit failures, and event ID `7f8c4ffae28e78555fcf2d56396d6e6c01b3712e5411288dc79e9a54af9d9444`. Generated with Codex --------- Signed-off-by: Luke Tornquist <tornquist@squareup.com> |
||
|
|
c432a111ca |
feat(mobile): push notifications MVP (#6269)
This PR implements MVP, iOS-only,
[NIP-PL](
|
||
|
|
86b9142a09 |
refactor(relay): NIP-98 admin auth with Operator/Moderator roles and NIP-11 discovery (#3777)
Adds authenticated, role-based moderation to the relay admin API. On
`main` the admin API is read-only and gated only by `Host`/`Origin`
matching; this branch adds NIP-98 authentication, a two-tier
Operator/Moderator principal model, mutation and staffing endpoints, and
NIP-11 auto-discovery so clients never type the admin URL by hand.
## Authentication (`BUZZ_ADMIN_AUTH`)
`BUZZ_ADMIN_AUTH` accepts `nip98` or `disabled`. Leaving it unset
defaults to `nip98` (fail-secure). Configuration fails closed: any other
value aborts startup, while a lingering `BUZZ_ADMIN_TOKEN` is ignored
with a startup warning — token (bearer) authentication is not supported.
`Host`/`Origin` matching is retained in every mode as defense-in-depth.
- **`nip98`** (default) — per-request signed NIP-98 (kind 27235) events,
resolved to an Operator or Moderator principal with per-person
attribution and individual revocability. Read-write per resolved
principal.
- **`disabled`** — no credential; relies entirely on network-layer
controls (reverse proxy, VPN, firewall) and logs a `WARN` on every boot.
Always read-only: `authorize()` resolves no principal, so mutation and
staffing routes always `403`.
## Roles
Buzz has two independent authority axes after this change.
**Relay-level** roles (new here) are deployment-global: they act across
every community on the relay, through the admin API. **Community-level**
roles (pre-existing, unchanged by this PR) are tenant-scoped: they act
inside one community, through signed Nostr moderation commands.
### Relay level (new)
| Role | Description |
|---|---|
| **Operator** | Full control of the deployment's moderation surface:
read all reports, feedback, and attachments across every community;
resolve reports with enforcement (`delete`/`kick`/`ban`/`timeout`) or
decisions (`dismiss`/`escalate`); reopen and cancel; update feedback
status; and manage the Operator/Moderator roster via the staffing
endpoints. |
| **Moderator** | Day-to-day triage: everything an Operator can do
except staffing — cannot view or change the roster. |
How a pubkey acquires a relay role (resolution order; config always
outranks DB):
1. Listed in `RELAY_OPERATOR_PUBKEYS` → **Operator** (source `config`)
2. Equals `RELAY_OWNER_PUBKEY` while `RELAY_OPERATOR_PUBKEYS` is empty →
**Operator** (source `owner_fallback`, a break-glass grant for
self-hosters that deactivates once any operator is configured)
3. Row in the `relay_operators` table → **Operator** or **Moderator**
(source `db`, managed via the staffing endpoints)
4. No match → `403`
### Community level (pre-existing, unchanged)
| Role | Description |
|---|---|
| **Owner** (community) | Full authority within their community: every
moderation action (delete, kick, ban/unban, timeout/untimeout, resolve
reports, view queue) plus member, role, and invite management. No guard
rails. |
| **Admin** (community) | Same community-wide moderation capabilities as
owner, except an admin cannot ban or time out the owner or a fellow
admin — only the owner may action an admin. Manages members and invites;
only the owner grants the admin role. |
| **Member** (community) | Standard participant; no moderation
capability. |
| **Owner / Admin** (channel) | Channel-local authority only: delete
messages and kick users within their own channel. |
| **Member / Guest / Bot** (channel) | No moderation authority. |
There is no community-level Moderator tier in v1; relay-level Moderator
is the only role by that name.
## Escalation scoping
The operator report queue is an escalation backstop, not the community's
day-to-day triage surface (per `VISION_MODERATION`, the severe class is
the platform's to review rather than the community's). Two rules enforce
that:
- **Escalated-by-default listing.** `GET /reports` with no `status`
parameter returns only `escalated` reports. An explicit
`status=<open|resolved|dismissed|escalated>` filter is always honored as
given, and full visibility across every status stays available for
platform-safety and legal review via `scope=all` (which lists reports
regardless of status). `scope` accepts only `all` and is ignored when an
explicit `status` is present.
- **Auto-escalated `illegal` reports.** Member reports whose category is
`illegal` are ingested with `status=escalated` rather than `open`, so
the severe class reaches the operator backstop without waiting for a
community admin to forward it. Every other category still lands `open`.
Auto-escalation only sets the queue status — it records no moderator
decision and stamps no resolver, so an auto-escalated report is
indistinguishable downstream from an admin-escalated one: the reopen
route returns it to `open` on the same terms, keyed only on status,
never on how the report became escalated.
## Principal resolution and NIP-98 admission
`resolve_admin_principal()` returns `AdminPrincipal { pubkey, role,
source }` per the resolution order above; `None` never falls through as
a role. Admission is ordered so the replay guard is a privilege, not a
public surface: signature/URL/method/payload-hash verification first,
roster check second, and only then is the deployment-scoped replay id
atomically consumed — a validly-signing but unrostered key never
allocates a replay slot. Redis failure fails closed.
## Report resolution, recovery, and enforcement provenance
`POST /reports/{id}/resolve` is a crash-safe enforcement state machine:
decision-only outcomes (`dismiss`/`escalate`) are a single
CAS-plus-audit transaction; enforcement
(`delete`/`kick`/`ban`/`timeout`) claims the report
(`open`→`processing`), runs the durable mutation, then finalizes — a
re-drive resumes at the step marker and converges to exactly-one
enforcement, fenced by a lease and an outbox claim token.
Person-directed enforcement on an `event`-kind report derives its target
from the stored event's author (server-owned truth, never the reporter's
`p` tag) via a single `derive_enforcement_target` shared by the HTTP
driver and the recovery worker. If the reported event was purged before
its author could be read, person-directed actions are rejected pre-claim
and the report stays `open`; `delete` needs only the event id and is
exempt.
`GET /reports/{id}` and the resolve response carry an `activeAction`
field surfacing the enforcement that actually executed — a report
dismissed after a reopen still reports the ban that ran. `POST
/reports/{id}/reopen` returns a terminal report to `open` (idempotent on
`requestId`). `POST /reports/{id}/cancel` is the sole recovery path for
a pre-mutation `failed` action, attributed via
`relay_admin_actions.cancelled_by`.
## Feedback
`GET /feedback` and `/feedback/{id}` survive a tenant purge: provenance
columns are severed to `NULL` rather than cascade-deleted, and the
attachment path fails closed to `404` on a severed row. `PATCH
/feedback/{id}` updates lifecycle `status`
(`new`/`reviewed`/`archived`).
## Staffing and probe
`GET/PUT/DELETE /operators/{pubkey}` are Operator-only; mutating a
config-backed pubkey returns `409 Conflict`. `GET /operators` returns
the union of config and DB principals with per-entry `source`. `GET
/probe` reports auth mode, role, source, `canAct`, and `canStaff` for
the desktop console.
## NIP-11 auto-discovery
The NIP-11 relay-information document gains an optional `admin_api`
field carrying the canonical admin origin (`scheme://host[:port]`, no
path), present iff `BUZZ_ADMIN_HOST` is set and omitted otherwise. The
scheme follows the same loopback rule as NIP-98 `u`-tag verification via
a shared `scheme_for_host` helper, so the advertised origin and the
origin the relay verifies against can never diverge.
## Operator API origin decoupling
`RELAY_OPERATOR_API_ORIGIN` is no longer required at boot when
`RELAY_OPERATOR_PUBKEYS` is set — it is used only by the
community-provisioning endpoints, which fail closed at request time
(with a boot-time `WARN`) until it is set. The admin console needs no
origin.
## Admin-web adaptation
The standalone `admin-web` dashboard signs each request as a NIP-98
event via a NIP-07 browser extension, discovers the auth mode with a
single unauthenticated probe (`200` → `disabled`, anything else →
`nip98`, fail-secure), and carries no token entry surface. Playwright
coverage exercises the NIP-98 and CSP paths.
## Security hardening
Three findings from security review are folded in:
- **Append-only roster audit.** `PUT`/`DELETE /operators/{pubkey}`
mutate the deployment-wide root of trust, but the upsert overwrites
`role`/`added_by` in place and the delete removes the only row — so a
grant→revoke sequence left no trace of who was ever granted or by whom.
Each mutation now writes an `relay_operator_audit` row (actor, target,
`grant`/`revoke`, pre-image `prev_role`, `new_role`, timestamp) inside
the same transaction as the mutation. A per-target transaction-scoped
advisory lock serializes concurrent mutations of the same pubkey before
the pre-image read, so the recorded `prev_role` is always the true
predecessor even under a concurrent-grant race. Chronology is keyed on a
`BIGINT GENERATED ALWAYS AS IDENTITY` `seq` column, not the wall clock:
the serializing lock guarantees insertion order and `seq` captures it,
so ordered reads (`ORDER BY seq`) follow the true privilege chain even
across a backward NTP step that a `clock_timestamp()` ordering would
invert. `created_at` (`clock_timestamp()`) is retained as informational
occurrence time only. Append-only by construction — no `UPDATE`/`DELETE`
path and no API surface. A no-op delete writes nothing.
- **`expirationSecs` overflow.** The timeout path built `Utc::now() +
Duration::seconds(secs as i64)` from an attacker-controlled `u64`:
`i64::MAX` panicked the handler, and a wrapped-negative magnitude minted
a *past* expiry that still passed validation. `compute_timeout_until`
now rejects zero, rejects magnitudes above a documented
`MAX_TIMEOUT_SECS` (365 days), and uses checked
`try_seconds`/`checked_add_signed` so no input can panic or produce a
past expiry — over-cap, zero, `i64::MAX`, and wrapping-negative inputs
all return a clean `4xx`.
- **Uppercase-hex config-backed bypass.** Config pubkeys are lowercased
at parse, but the `409` immutability check raw-string-compared the path
param while `decode_hex_pubkey` accepted uppercase — so `PUT
/operators/{UPPERCASE}` skipped the guard and wrote a shadow row for the
same 32 bytes. The validated param is now canonicalized (lowercased)
before the `409` check, DB write, `DELETE`, and response body.
## Migrations
- `0035_relay_operators.sql` — `relay_operators` roster table
(deployment-global), `actor_authority` on `moderation_actions`,
`processing` status plus `active_action_id` on `moderation_reports`,
`status` on `product_feedback`.
- `0036_relay_admin_actions.sql` — enforcement-action table with a
`request_id` idempotency key, a `step_marker` for crash recovery, and a
`cancelled_by` attribution column.
- `0037_relay_admin_action_lease.sql` — lease fencing for the action
worker.
- `0038_relay_admin_outbox_claim_token.sql` — fenced claim token on the
outbox worker.
- `0039_relay_operator_audit.sql` — append-only `relay_operator_audit`
trail for roster mutations (see Security hardening).
`docs/admin/README.md` documents the full principal model, NIP-98 event
requirements, capabilities by role, the startup error matrix, and the
discovery field.
## Production blast radius
A relay without `BUZZ_ADMIN_HOST` is completely unaffected — the admin
surface stays disabled and `BUZZ_ADMIN_AUTH` is ignored; a lingering
`BUZZ_ADMIN_TOKEN` logs a startup warning and must be removed. Where
`BUZZ_ADMIN_HOST` **is** set, unset `BUZZ_ADMIN_AUTH` defaults to
`nip98` (per-person signed auth); `BUZZ_ADMIN_AUTH=disabled` reproduces
`main`'s prior `Host`/`Origin`-only gating but is read-only (mutation
routes `403`). The five migrations add tables and columns without
touching existing data.
---
Related: [block/buzz#4768](https://github.com/block/buzz/pull/4768)
(desktop admin console consuming the `admin_api` field),
[squareup/bb-public#339](https://github.com/squareup/bb-public/pull/339)
(Phase 4 rollout config)
---------
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
|
||
|
|
80177e4c8e |
fix(ci): bump Codex CLI to 0.150.1 to unhang security review jobs (#6962)
Codex CLI 0.149.x can leave a PTY descendant holding
`openai/codex-action`'s inherited stdio after the review turn completes.
The action's runner waits on the child's stdio *streams closing* rather
than on process exit
([`runCodexExec.ts`](https://github.com/openai/codex-action/blob/86365089/src/runCodexExec.ts#L322)),
so the `Review pull request` step never returns — the job idles until
its 30-minute `timeout-minutes` kills it and the already-written review
result is discarded.
Every `Run Codex Security Review` job since the workflow merged has hung
this way: the final JSON result and `tokens used` count are the last log
lines, with no step-end marker. Heavy runs (`gpt-5.6-sol` at `max`
effort, ~295k tokens) sit firmly in the failing regime.
Upstream:
[openai/codex-action#150](https://github.com/openai/codex-action/issues/150),
fixed in Codex CLI 0.150.0 by
[openai/codex@bf3eb2e](
|
||
|
|
745ff6066c |
Fix Codex security review authorization (#6913)
The Codex security review workflow now authorizes automatic reviews from the live pull request instead of the webhook snapshot. This prevents a stale author association from sending an internal PR down the external-contributor invalidation path. Internal PRs with no previous review now exit without attempting a comment or label mutation. The jobs that update PR comments and labels use `pull-requests: write`, and the review job runs only after the trusted prepare step emits `authorized=true`. Regression tests cover stale author associations in both directions, the no-op invalidation path, and the mutation-job permission contract. Checked with `just ci`, `just security-review-check`, and `actionlint .github/workflows/codex-security-review.yml`. The failing `Mark Previous Review Stale` check on this PR runs the pre-fix `pull_request_target` workflow from `main`. GitHub will not execute this branch’s trusted workflow until it is merged; the new workflow contract and state-machine tests exercise the fix before then. Signed-off-by: Jordan Mecom <jm@squareup.com> |
||
|
|
0ccf934b88 |
fix(db): disable heartbeat vacuum truncation (#6898)
## Why `replica_heartbeat` is updated continuously, but autovacuum heap truncation can take an `ACCESS EXCLUSIVE` lock whose replay cancels concurrent hot-standby reads. Truncating a fixed, single-row table provides no meaningful space benefit. ## What - Add migration 0034 to set `vacuum_truncate = false` on `replica_heartbeat` - Keep the desired-state schema aligned for fresh installations - Extend migration coverage so both paths retain the reloption ## Risk Assessment Low — this disables only autovacuum's heap-truncation phase for a single-row table; normal vacuum cleanup remains enabled. The tradeoff is retaining at most the table's otherwise-truncatable tail pages. ## References - [Investigation thread](https://sq-block.slack.com/archives/C0B2WT43BDH/p1787760831375989) - [PostgreSQL table storage parameters](https://www.postgresql.org/docs/current/sql-createtable.html#SQL-CREATETABLE-STORAGE-PARAMETERS) Generated with Codex --- **Update Aug 27, 2026:** Hardened fresh `pgschema` bootstraps after review. - Renamed the shared post-apply script to `reconcile-schema-after-pgschema.sql` and made it restore the heartbeat reloption and singleton row idempotently. - Added live catalog and row assertions that stop bootstrap when the database does not match the required state. - Updated every repository `pgschema apply` caller, including the desktop release smoke path. - Added a regression test that requires future `pgschema apply` callers to run reconciliation. - Documented the `pgschema` DML and storage-parameter limitation in `AGENTS.md` and at the reconciliation entry point. - Merged current `main` and renumbered this migration to 0034 after `main` added migration 0033. --------- Signed-off-by: Luke Tornquist <tornquist@squareup.com> |
||
|
|
c363ce13c0 |
chore(deps): update rui314/setup-mold digest to 7e4f20a (#6663)
This PR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
| [rui314/setup-mold](https://redirect.github.com/rui314/setup-mold)
([changelog](
|
||
|
|
82e748085e |
chore(deps): update ubuntu:24.04 docker digest to 33ceb71 (#6664)
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [ubuntu](https://hub.docker.com/_/ubuntu) ([source](https://git.launchpad.net/cloud-images/+oci/ubuntu-base)) | container | digest | `561618e` → `33ceb71` | --- > [!WARNING] > Some dependencies could not be looked up. Check the [Dependency Dashboard](../issues/1) for more information. --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/block/buzz). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4zOS4wIiwidXBkYXRlZEluVmVyIjoiNDQuMzkuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==--> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> |
||
|
|
cdab765748 |
Add gated security reviews (#6816)
Add a gated Codex security review for Buzz pull requests. Reviews run automatically for Block organization members. External PRs require a `@buzz-security-review` comment from a Block member, and every new external push invalidates the previous result. Codex reviews the PR SHA in a read-only, network-restricted job with no write GitHub token. A separate trusted job validates and sanitizes structured findings before posting them. The workflow uses the dedicated `CODEX_REVIEW_API_KEY` secret. --------- Signed-off-by: Jordan Mecom <jm@squareup.com> |
||
|
|
cae7f826b3 |
fix(ci): check out source in docker.yml merge job (#6833)
## Problem Every `docker.yml` run on `main` has failed since [#6781](https://github.com/block/buzz/pull/6781) merged. That PR added a "Create deployment eligibility predicate" step to the `merge` job, which runs `jq` against `$GITHUB_WORKSPACE/scripts/create-deployment-eligibility-predicate.jq`. But the `merge` job has no `actions/checkout` step — the workspace is empty, so `jq` can't open the file and exits `2` (`jq: Could not open ... No such file or directory`). The `build` and `qualify` jobs each check out the source; `merge` never needed one until this step was added. ## Fix - `.github/workflows/docker.yml`: add `actions/checkout` (same pinned SHA as the other jobs, `df4cb1c` / v6.0.3) as the first step of the `merge` job. - `scripts/test-relay-image-eligibility-workflow.sh`: guard the regression by asserting the `merge` job checks out the source before building the predicate. ## Verification CI cannot exercise the `merge` job on a pull request — the job is gated `if: github.event_name != 'pull_request'`, so it only runs on push to `main`. The proof is the root cause (missing checkout for a step that reads a repo file) plus the eligibility workflow test: `scripts/test-relay-image-eligibility-workflow.sh` passes with the fix and fails with the new guard's specific message (`merge job must check out the source before building the eligibility predicate`) when the checkout is removed. Signed-off-by: Will Pfleger <pfleger.will@gmail.com> |
||
|
|
ee6ca5fa28 |
Remove public relay signing key fallback (#6729)
Require `BUZZ_RELAY_PRIVATE_KEY` on every relay startup and remove the shared public fallback key completely. For local development, `just bootstrap` now generates a random key once in the gitignored `.env` file. Re-running bootstrap preserves that key, so relay-authored events retain the same identity across restarts. The local relay recipes reload `.env`, while isolated CI and E2E launchers pass explicit per-run keys. Startup fails immediately when the key is missing or invalid, before connecting to Postgres or Redis. Deployed relays continue to receive their stable key from the existing chart-managed secret. Validated with: - `./scripts/test-ensure-local-relay-key.sh` - `cargo test -p buzz-relay --bin buzz-relay` - `cargo clippy -p buzz-relay --bin buzz-relay -- -D warnings` - `cargo fmt --all -- --check` - `shellcheck scripts/ensure-local-relay-key.sh scripts/test-ensure-local-relay-key.sh` Signed-off-by: Jordan Mecom <jm@squareup.com> |
||
|
|
f249710331 |
Qualify canonical relay images for staged delivery (#6781)
## Why Phase 1 of progressive delivery needs an explicit eligibility boundary before deployment automation can safely consume relay artifacts. Today the canonical Docker workflow can publish independently of the same-SHA CI result, the relay does not expose its source/build identity, and the Helm chart only renders mutable tags. The existing `ghcr.io/block/buzz-staging-dev` workflow in #6709 remains a manual pre-merge preview lane. It is deliberately not a canonical promotion input. ## What changed - Compile the full source SHA and GitHub Actions build identity into canonical relay binaries and expose it at `/_status`. - Gate creation of tagged multi-architecture manifests on the latest exact-SHA `CI` push run succeeding on `main` or `release`. - Preserve the signed SLSA provenance and add a signed, release-only deployment-eligibility predicate with source, build run, qualifying CI run, and exact compatible Buzz chart version. - Keep failed or pending builds untagged and without an eligibility predicate; preview-package artifacts are excluded by package and signer contract. - Add optional `image.digest` support to Buzz chart `0.1.8`, retaining the existing tag fallback. - Document verification and the intentional boundary between image/chart compatibility and backwards-compatible database migrations. ## Risk and rollout Canonical tag publication now waits for same-SHA CI and fails closed when the newest matching run fails. Per-architecture blobs may remain untagged after a failed qualification; they are not promotion inputs. Debug manifests are also CI-gated but do not receive the release eligibility predicate. There are no migration or schema-orchestration changes. The companion BuilderBot infrastructure PR will pin a separately verified canonical main digest and expose a read-only running-deployment inspection command. Signed eligibility is available only for artifacts published after this workflow lands. ## Verification - Blox: workflow contract fixtures, including stale-green rejection and predicate structure - Blox: `actionlint` for `ci.yml` and `docker.yml` - Blox: 46 Helm unit tests, chart lint, and production/quickstart render fixtures - Blox: relay status unit test, workspace Rust formatting, and workspace all-target clippy - Live evidence: exact-SHA CI selector chose successful run `32857345153` for source `9d1e4b257657f382d3111ce748f3da8d063b7671` - Existing canonical digest provenance verified with `gh attestation verify` ## References - #6709 — developer-preview precursor; intentionally separate from canonical eligibility - `docs/deployment-identity.md` *Generated with Codex* --------- Signed-off-by: Luke Tornquist <tornquist@squareup.com> |
||
|
|
113a33b7e4 |
Add database pressure observability (#6700)
## Why Database pressure currently collapses several distinct delays into one symptom. This adds the evidence layer needed to distinguish pool acquisition wait, logical database operation time, advisory-lock wait, and selected transaction duration before changing timeout or retry policy. This is the phase 2 Lane A observability bundle for [#26](https://github.com/TheSentinel454/buzz/issues/26), [#28](https://github.com/TheSentinel454/buzz/issues/28), and [#33](https://github.com/TheSentinel454/buzz/issues/33). It is stacked on #6668. ## What - Record explicit reader/writer checkout wait and acquisition outcomes with `buzz_db_pool_acquire_wait_seconds` and `buzz_db_pool_acquisitions_total`. - Extend the compile-time `#[datastore_span(name = "...")]` seam with `buzz_db_operation_duration_seconds`, so operation labels remain static source literals instead of request data. - Route correctness-critical replacement, membership, push-gate, deletion, and migration/schema-safety advisory locks through one observer without changing their SQL, order, scope, or blocking behavior. - Measure six internally owned transaction lifetimes with `buzz_db_transaction_duration_seconds`, starting after `BEGIN` succeeds and ending after explicit commit/rollback or scope exit. - Emit root slow-operation warnings at 500 ms, logging the first slow completion and then 1/100 per call site with only `operation`, `outcome`, and `elapsed_ms`. - Document names, units, fixed label vocabularies, measurement boundaries, and blind spots in this PR description. Fixed labels are deliberately small: - `pool_role`: `writer`, `reader` - `lock_type`: `replacement`, `membership`, `push_gate`, `deletion`, `migration_schema_safety` - `outcome`: `success`, `error`, `timeout` where SQLx/PostgreSQL can distinguish it accurately - `operation`: compile-time datastore names plus the six closed transaction operation names documented in the runbook No metric or slow warning contains community IDs, event IDs, event kinds, coordinates, d-tags, SQL/query text, query IDs, returned errors, or event content. ## Coverage boundaries - Operation duration is the complete annotated logical function body, not pure SQL execution; it may include implicit checkout, lock wait, nested operations, and application work. Cancelled futures do not reach its completion hook. - Pool timing covers explicit helper checkouts, including proved-reader routing and selected writer-owned transactions. Implicit SQLx checkout through `&PgPool` remains folded into operation duration. - Lock timing covers application-side blocking locks in the five named families. Trigger/stored-procedure locks, channel-TTL locking, the usage try-lock, and the audit service session lock remain outside this slice. - Transaction timing covers only the six wholly owned boundaries documented in the runbook. It excludes pool wait, `BEGIN`, asynchronous rollback cleanup after an early return, and caller-owned `Db::begin_transaction` lifetime. ## Relationship to #6229 #6229 is the incident-driven timeout precursor. This PR does not add or change `statement_timeout`, `lock_timeout`, `idle_in_transaction_session_timeout`, retries, audit durability, or client-visible conflicts. It provides the missing distributions needed to evaluate those policies later and intentionally leaves #6229's open audit retry/durability finding untouched. The branches overlap in `crates/buzz-db/src/lib.rs` and `crates/buzz-db/src/migration.rs`, so a later rebase may need textual conflict resolution, but the behavior is complementary rather than duplicated. ## Risk assessment Moderate-low. The primary risk is instrumentation overhead and added static series. Cardinality is source-bounded, slow logs are sampled/redacted root events, and the lock/transaction changes wrap existing awaits without changing policy or ordering. ## Verification Author workstation: `buzz-tornquist-db-pressure-observability` (`2010927`), exact head `d7cf833e26c528adfcde3917ded80daf6f4ddac9`, parent `6f50e6b2b2a996349149af61d35bdd6a355f77fd`. - `cargo fmt --all --check` — passed - `cargo clippy -p buzz-datastore-tracing -p buzz-db -p buzz-audit -p buzz-search -p buzz-relay --all-targets -- -D warnings` — passed - `cargo test -p buzz-datastore-tracing --quiet` — 4 passed - `cargo test -p buzz-db --quiet` — 109 passed, 200 ignored - `cargo test -p buzz-audit -p buzz-search --quiet` — 16 passed, 25 ignored - `cargo test -p buzz-relay --lib --quiet -- --test-threads=1` — 906 passed, 48 ignored - Native PostgreSQL focused tests for pool success/timeout/error, lock success/contention/timeout/error, replacement, membership serialization, push ordering, deletion fencing, migration/schema exclusion, and reader fallback — 8 passed The default-parallel relay run passed once; subsequent runs exposed the existing load-sensitive `api::mesh_demo::tests::demo_join_forwarded_arm_round_trips_echo` 504 at the end of the suite. That test passes in isolation and the full relay suite passes serially. Independent exact-head review workstation: `buzz-tornquist-db-pressure-observability-review` (`2013067`). Formatting, the same all-target clippy command, datastore instrumentation tests, DB unit tests, source privacy guards, and diff/non-goal audits passed; no review findings. Generated with Codex --------- Signed-off-by: tornquist <tornquist@squareup.com> |
||
|
|
931747c9c4 |
Add staging dev relay image workflow (#6709)
## Summary Adds a manual, collaborator-triggered workflow for publishing pre-merge Buzz relay runtime images for bb-block staging. - Resolves a canonical `block/buzz` branch or tag to an immutable commit SHA before checkout. - Builds the relay runtime image for `linux/amd64` and `linux/arm64` and publishes a single OCI index. - Tags each publication uniquely as `dev-sha-<full SHA>-run-<run_id>-<run_attempt>` so no tag is ever reused against the immutable-tag utility ECR pull-through cache (rebuilds of the same SHA aren't byte-identical, given mutable base tags and `apt-get update`). - Uses the staging-only `ghcr.io/block/buzz-staging-dev` namespace, which maps to a distinct utility ECR pull-through path. - Restricts dispatch to `block/buzz` on `refs/heads/main`; job permissions are narrowly scoped to `contents: read` plus `packages: write`. - Emits a deployment summary with the exact BPCI `repository` and unique `tag` values, plus the merged manifest digest. - Keeps the existing production/release image workflow unchanged. The first real workflow run must confirm GHCR package creation/access and utility ECR pull-through import for the new package. --------- Signed-off-by: Brad Seiler <seiler@squareup.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: tornquist <tornquist@squareup.com> Co-authored-by: coder 0 <d97ebdbb198c7237c94f84ea8bb8a73583ea067407eebd0062abbb3962527fb1@buzz.block.builderlab.xyz> Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: tornquist <tornquist@squareup.com> |
||
|
|
8d2d0ff5ad |
Centralize replaceable event persistence (#6660)
## Why Command persistence duplicated NIP-33 replacement SQL in the relay and obscured the boundary between database runtime concerns and domain-store behavior. This proving slice establishes that boundary inside the existing `buzz-db` crate. ## What - Centralize parameterized-replaceable coordinate locking, ordering, replacement, watermark, and mention-indexing behavior in `buzz-db` - Expose transaction-required replacement and ordinary-insertion seams while keeping transaction ownership and workflow conflict messages in the relay - Cover concurrency, same-second ties, stale writes, replay, caller rollback, nested rollback recovery, and mention-index atomicity with focused PostgreSQL tests ## Risk Assessment Medium — this changes core event persistence and command idempotency paths, while intentionally preserving NIP-33, NIP-RS, mesh, and workflow conflict semantics. ## References - Architecture guardrail: https://github.com/TheSentinel454/buzz/issues/34 - Proving slice: https://github.com/TheSentinel454/buzz/issues/3, https://github.com/TheSentinel454/buzz/issues/4, https://github.com/TheSentinel454/buzz/issues/6 Generated with Codex --------- Signed-off-by: tornquist <tornquist@squareup.com> |
||
|
|
69b1225923 |
fix(ci): prevent poisoned Rust caches (#6618)
## Summary - roll every `Swatinem/rust-cache` use back from v2.9.2 to the last known-good v2.9.1 digest - give Unit Tests a new `sherpa-cache-v1` key so it cannot restore the existing poisoned artifact - pin Renovate to v2.9.1 and add CI contracts that reject unsafe cache actions or a misplaced generation key ## Why After #5441 upgraded rust-cache to v2.9.2, warm-cache `main` Unit Tests runs began failing while linking `buzz-voice` with `could not find native static library sherpa-onnx-c-api`. The failed run at `db5617dd1` restored the same 1.4 KB cache generation that had already failed at `01091c15a`; the preceding cold run at `26f4c3ed3` downloaded sherpa 1.13.4 and passed. v2.9.2 changed target cleanup, while `sherpa-onnx-sys` treats its prebuilt `lib/` directory as proof that the native archive exists. Rolling back the action and invalidating the affected key removes both sides of that failure state without disabling target caching. ## Validation At `6da0037a0407fc498cd482fcbbb74c7a15907e9f`: - `scripts/test-rust-cache-contract.sh` - `scripts/test-rust-cache-contract-regressions.sh` - negative fixtures reject a bad digest in a newly named `.yaml` workflow and a generation key moved outside the cache action's `with` block - YAML parse for all workflows - release, desktop candidate, mobile release, mobile candidate, and mobile worktree source contracts - `just file-size-check` - pre-commit and pre-push hooks The PR Unit Tests run proves the cold-cache path because pull requests restore but do not save Rust caches. The first successful `main` run after merge will save the new Unit Tests key; the following `main` run will exercise the warm restore. ## Related issue None found. Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz> |
||
|
|
02dc49f0e6 |
docs(security): route reports through private advisories (#6728)
Routes security researchers to GitHub's private vulnerability reporting workflow instead of a public issue or email-first disclosure. - Makes the private advisory form the primary path in `SECURITY.md`, with email retained as a fallback. - Adds private-reporting links to the contributor guide, issue chooser, and bug template. Checked with `git diff --check`, Ruby YAML parsing, and confirmation that private vulnerability reporting is enabled for `block/buzz`. Signed-off-by: Jordan Mecom <jm@squareup.com> |
||
|
|
9008a4d172 |
chore(deps): update ubuntu:24.04 docker digest to 561618e (#5442)
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [ubuntu](https://hub.docker.com/_/ubuntu) ([source](https://git.launchpad.net/cloud-images/+oci/ubuntu-base)) | container | digest | `4fbb8e6` → `561618e` | --- > [!WARNING] > Some dependencies could not be looked up. Check the [Dependency Dashboard](../issues/1) for more information. --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/block/buzz). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMi4wIiwidXBkYXRlZEluVmVyIjoiNDQuMjkuNSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==--> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> |
||
|
|
694d804b24 |
chore(deps): update swatinem/rust-cache digest to 6323deb (#5441)
This PR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
| [Swatinem/rust-cache](https://redirect.github.com/Swatinem/rust-cache)
([changelog](
|
||
|
|
a9640c7cc4 |
Add Buzz-native collaboration benchmarks (#6264)
## Why Terminal-Bench can show task correctness while missing Buzz-specific collaboration failures, such as posting a final answer outside the user's thread. Add a small Buzz-native suite that scores relay behavior directly. ## What - Add Harbor tasks for direct thread replies, event-level callback mentions to a three-word user identity, targeted reads of explicitly named paths outside the workspace, and exact temporary-channel membership against a seeded directory of 50 users and 10 bots - Export sanitized post-agent relay evidence and extend solo runtime/provisioning for deterministic verification - Expose channel TTL in CLI search output and cover positive and negative transcript/state fixtures ## Risk Assessment Low to medium — this primarily adds benchmark and testbed machinery. The CLI change is an additive `ttl_seconds` field in channel-search JSON; relay behavior is unchanged. ## References - Motivated by the Buzz-specific behavior observed while benchmarking https://github.com/block/buzz/pull/6128 - The named-path regression case covers the issue being fixed by https://github.com/block/buzz/pull/6261 - Local live Sonnet runs: `reply-to-thread`, `user-mention`, and `create-channel-invite-users` each scored reward 1.0 with zero exceptions - The current branch prompt scored 3/5 on a concurrent `read-named-path-outside-workspace` batch: one explicit workspace-boundary refusal and one missing final response Generated with Codex --------- Signed-off-by: Salman Mohammed <smohammed@squareup.com> |
||
|
|
6d45f98665 |
ci: make file-size policy a first-class gate (#6187)
## Summary - make the differential file-size ratchet a first-class repository gate - run the same unfiltered gate from pre-push, `just check`, and CI - remove hidden file-size coupling from Desktop, Web, and Mobile lint commands - isolate ratchet Git subprocesses from hook-exported repository state ## Why The Desktop ratchet grew to govern `desktop/src-tauri/crates/**`, but the pre-push `desktop-check` command remained path-filtered to non-Tauri files. That contract drift allowed a Tauri Rust file-size regression through local validation. The ratchet already computes its own merge-base diff, so duplicating governed paths in Lefthook and CI adds drift risk without meaningful runtime savings. One root gate owns the policy now. ## Testing - `just file-size-check` - oversized untracked probe under `desktop/src-tauri/crates/**` fails with the 1,000-line ceiling - file-size core tests with hook-style `GIT_DIR` / `GIT_WORK_TREE` environment - `lefthook dump` confirms the unfiltered pre-push command - mandatory pre-push suite passed on `3217003db10c84d8a0c5f636ec4c92936777a002` - `git diff --check` Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
1b7e5ac1be |
feat(model-capabilities): drive model capabilities and labels from one manifest (#5597)
## Summary Centralizes model capability knowledge — thinking mode, supported effort levels, wire routes, and human-readable labels — into a single manifest, `scripts/model-capabilities.json`. Rust and TypeScript each get a small interpreter that reads the same manifest, replacing hand-maintained tables scattered across both languages that had already drifted apart. A capability change is now a data edit, not parallel edits to two code paths. Supersedes the codegen approach explored in #3603. A cross-language contract keeps the two interpreters honest: `scripts/normative-corpus.json` is a golden snapshot generated from the Rust resolver (103 vectors covering all six capability axes) and replayed natively in TS. CI fails if either language disagrees with the corpus or the corpus drifts from the resolver. Regenerate with `just regen-model-corpus`. ## Behavior changes - **Effort dropdown for `openai-compat` providers** no longer offers `max`. The request path always clamped `max` to `xhigh` on the wire, so the UI stops offering a value that was silently rewritten. UI-only, wire-identical. - **Databricks v2 routing (wire-visible):** uncurated endpoint names carrying a bare Claude code-name segment (e.g. `goose-opus-5`) now route to the MLflow chat wire instead of Anthropic Messages — they lose Anthropic prompt caching but still succeed on a valid OpenAI-compatible wire. Curated `databricks-claude-*` records and any name starting with `claude` are unchanged. A handful of other uncurated/adversarial name shapes similarly fall back to MLflow chat instead of pattern-matched routes; every curated model resolves identically to before, all axes. - **Curated model labels on the real discovery path.** The Databricks API returns no display name, so discovery emits the raw endpoint id as the model `name` (`{id, name: id}`) on every path. `ModelEntry.name` is now curated at all four construction seams in `buzz-agent` — v2 discovery, v1 parse, the auth-empty default catalog, and the configured-model fallback — via a read-only `databricks_registry_label` lookup over the manifest's `databricks_v2` exact records; `id` stays the raw wire/config value. A known id renders its curated label (`databricks-gpt-5-5` → `GPT-5.5`), an unknown id passes through unchanged, and the default-catalog row reads `GPT-5.5 (default catalog)`. As a defense against older `buzz-agent` binaries and any harness that echoes ids, `resolveModelLabel` treats a discovered name equal to the trimmed id as absent and falls through to the registry tier; a genuinely distinct name (including the suffixed default-catalog label) still wins. ## Cleanup Deletes the duplicated capability tables and their tests: the `config.rs` gpt5 matchers, effort tables, and clamp logic; the legacy segment-based Databricks v2 route classifier in `llm.rs`; and the TS hand tables plus `effortTable.fixture.json`. All are replaced by manifest lookups through the shared resolver — no line of capability data exists in two places. --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> |
||
|
|
1b3dbcaaea |
ci(release): remove desktop smoke gate (#5914)
## Summary - remove the GitHub-hosted desktop smoke job from the desktop release workflow - remove the smoke result from manifest assembly dependencies and promotion conditions - retain the local smoke tooling for future repair and targeted validation The first release execution of this gate spent its full 10-minute Playwright timeout traversing the 10,000-row fixture, then produced a 987 MB diagnostics upload. All signed platform builds succeeded, but the smoke prevented manifest publication. This restores the previously established release boundary while the harness is made suitable for CI separately. ### Testing - parsed `.github/workflows/release.yml` with Ruby Psych and asserted the smoke job/dependencies are absent - `scripts/test-release-ref-contract.sh` - exact pushed commit passed the repository pre-push hook Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
09768100ec |
fix(ci): read Playwright version without nested shell quoting (#5910)
## Summary - replace the nested one-line shell quoting used to read the Playwright package version - write the resolved version to `GITHUB_OUTPUT` from a multiline shell step ## Why The `desktop-v0.5.12` release smoke job failed before executing tests because Bash received escaped quotes inside command substitution and parsed the Node expression as shell syntax. ## Validation - `bash scripts/test-release-ref-contract.sh` - isolated execution of the new shell fragment with a fixture `@playwright/test/package.json`, producing `version=1.58.2` - `git diff --check` Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
76f114a252 |
test: add deterministic desktop release smoke (#5699)
## Summary - add `just desktop-release-smoke`, a deterministic desktop correctness/reachability smoke against an ephemeral real local relay - preserve existing DM history when the first live DM enters a pageless query window, the desktop-v0.5.10 disappearing-DM regression - enforce foreground JS ordering: a frame and actionable sidebar input must dispatch before mounted stale queries begin resume refetches, while separately requiring the navigation to commit promptly - seed a 10,000-event dense-second fixture and verify exact event-ID reachability, SHA-256 identity, ordering, duplicate absence, bounded mounted rows, and drained render work - isolate Postgres per run, serialize the shared Redis DB, retain phase/relay/Playwright diagnostics, and gate desktop release manifest assembly on the smoke This is deliberately **not a performance-regression gate**. CDP and action timing fields are informational only. There is no candidate/baseline comparison or threshold. A future performance lane needs repeated equivalent fixtures, discrete interaction samples, and an explicit comparator/noise policy. The diagnostics record the fixture version, row count, wall-clock base timestamp (`fixtureSecond`), expected event-ID hash, observed state, and measurements. Because the created-at floor requires a current timestamp, paired comparison remains disabled. The release job runs on an isolated GitHub-hosted runner. The script also guards automatic local runs with a Redis allocation lock. Its remaining direct-PID cleanup and free-port selection race mean it should not be repurposed onto a persistent concurrent shared runner without first hardening process-group cleanup and port reservation. ### Related issue N/A ### Testing - `pnpm --dir desktop typecheck` - focused real-local-relay release smoke passed after adversarial review fixes - identical DM witness passed current and failed `desktop-v0.5.10` with the history-loss signature - identical foreground witness bytes (`2c1e97df04c9b8ca0304b66bbbe9bdb4d08924ad8ce0f68a9c490458fcc3aca8`) failed `desktop-v0.5.10` structurally: the first resume fetch was marker 1, before first frame/sidebar dispatch at marker 8 - with PR #5696 (`59f613c40`) merged, the witness showed focus at 951.3 ms, first frame at 951.6 ms, click dispatch at 952.1 ms, first resume fetch at 968.9 ms, and route commit at 992.4 ms - the gate therefore protects first paint and actionable input dispatch; route commit is a bounded responsiveness witness, not a prerequisite for resume work - the corrected focused foreground scenario passed at `6d9b5be40da58bbee92a856b04c3558946d0a950`; the prior merged-tree full run passed DM retention and 10k reachability before exposing this contract mismatch - pre-push passed on exact pushed head `6d9b5be40da58bbee92a856b04c3558946d0a950`, including desktop checks, typecheck, desktop tests, Rust tests, mobile tests, and Tauri checks - full 10,000-event scenario reached 10,000/10,000 exact IDs with matching SHA-256, 199 continuation requests, and 95 mounted rows in about 4.4 minutes - reduced-row review run passed in 18.4 seconds ### Foreground witness boundary The Chromium test is a deterministic JS policy gate. Headless Chromium does not expose an honest blur/focus transition in this fixture, so the test drives the production focus listener and `document.hasFocus()` predicate together and records that simulation explicitly. It proves refetch fan-out ordering, not AppKit activation, WKWebView paint, or an activating physical click. A packaged macOS native lane is still required before claiming the actual desktop activation experience is certified. --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
5a3b3d2322 |
perf(ci): experiment with sccache for relay builds (#5224)
## Summary - add a SHA-pinned sccache action to reuse unchanged Rust compilation units when the exact relay artifact cache misses - keep pull requests read-only while preserving cache writes for trusted `main` and `release` pushes - stop saving isolated exact relay-artifact caches from PRs, reducing cache churn - preserve the exact artifact cache as the zero-build fast path ## Why this is an experiment The relay artifact job currently misses its exact cache whenever any file under `crates/**` changes, forcing a full workspace rebuild. PR #4975 spent roughly 21 minutes in that job for a one-file `buzz-sdk` change. sccache targets the relevant reuse boundary—individual compiler inputs—but the repository cache pool is already under heavy eviction pressure, so this PR does **not** claim a proven timing win yet. ## Safety - `Mozilla-Actions/sccache-action` is pinned to commit `fc920bf0ec8de6ee65d409111f7ec508035751ba` - `RUSTC_WRAPPER` is scoped only to `Build relay artifacts` - PRs use `READ_ONLY`; trusted `push` runs (`main` and `release`) use `READ_WRITE` - the existing exact finished-artifact cache remains the first/fast path - finished artifacts are saved only by trusted pushes, preserving the former trust boundary - workflow permissions remain `contents: read`; no `pull_request_target` path is introduced - the pinned action automatically emits sccache hit/miss/error/write/duration statistics in its post-job hook ## Validation - `actionlint .github/workflows/ci.yml` - `git diff --check` - desktop release-cache contract test - release-ref contract test - independent code-shape reviews from Princess Donut and Mongo: 9/10, no remaining findings ## Measurement plan 1. purge obsolete PR-scoped `relay-artifacts-*` cache entries before measurement 2. merge/push a trusted writer to populate sccache 3. run a representative one-crate PR 4. compare relay job duration and automatic sccache statistics against the 21–22 minute baseline 5. retain this only if the warm run demonstrates material improvement --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
43573d114b |
ci(release): gate OSS desktop auto-update promotion (#5398)
## Summary Separate OSS desktop artifact publication from fleet-wide auto-update promotion. - retain the exact generated updater manifest as `updater-manifest.json` on each immutable `desktop-vX.Y.Z` release - stop the tag-triggered build from mutating `buzz-desktop-latest/latest.json` - add a `main`-only manual promotion workflow with one global concurrency group - validate stable semver, release/tag commit identity, draft/prerelease state, exact platform set, signatures, version-bound asset URLs, asset existence, monotonicity, idempotent retries, and a final stale-state check before writing - document the operator flow and pin the split with focused contract tests ## Safety behavior Publishing a versioned GitHub release no longer exposes it through the in-app updater. Operators can install and test those exact signed/notarized artifacts, then manually run **Promote OSS Desktop Auto-Update** with the stable version. Promotion rejects downgrades. A same-version retry succeeds only when the rolling and candidate manifests are byte-identical. The workflow re-reads the current rolling version immediately before its only write and records the actor, source tag commit, previous version, manifest digest, and run URL. ## Verification Verified at commit `39caf1603be06bb476905225ec55f7bbbe86b237`: ```text scripts/test-oss-desktop-promotion.sh OSS desktop promotion contract passed scripts/test-release-ref-contract.sh release ref contract passed git diff --check origin/main...HEAD (clean) ``` The repository pre-push hook also passed `branch-skew` for the exact pushed head; package suites were correctly skipped because this change only touches release workflows, scripts, and documentation. Originating conversation: Buzz channel `separate-publish-step-release`, thread `8857ce8bbe928e891165eddcf06c666cf6eae16181c3f02a6d8c396d8a536026`. Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
856cdb848b |
chore(deps): update all non-major dependencies (#3049)
This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | Type | Update | |---|---|---|---|---|---| | [@isomorphic-git/lightning-fs](https://redirect.github.com/isomorphic-git/lightning-fs) | [`4.6.2` → `4.6.3`](https://renovatebot.com/diffs/npm/@isomorphic-git%2flightning-fs/4.6.2/4.6.3) |  |  | dependencies | patch | | [@vitejs/plugin-react](https://redirect.github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react#readme) ([source](https://redirect.github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react)) | [`6.0.3` → `6.0.5`](https://renovatebot.com/diffs/npm/@vitejs%2fplugin-react/6.0.3/6.0.5) |  |  | devDependencies | patch | | [@vitejs/plugin-react](https://redirect.github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react#readme) ([source](https://redirect.github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react)) | [`6.0.3` → `6.0.5`](https://renovatebot.com/diffs/npm/@vitejs%2fplugin-react/6.0.3/6.0.5) |  |  | dependencies | patch | | [dorny/paths-filter](https://redirect.github.com/dorny/paths-filter) | `v4.0.2` → `v4.0.3` |  |  | action | patch | | [isomorphic-git](https://isomorphic-git.org/) ([source](https://redirect.github.com/isomorphic-git/isomorphic-git)) | [`1.38.7` → `1.38.10`](https://renovatebot.com/diffs/npm/isomorphic-git/1.38.7/1.38.10) |  |  | dependencies | patch | | [postcss](https://postcss.org/) ([source](https://redirect.github.com/postcss/postcss)) | [`8.5.19` → `8.5.26`](https://renovatebot.com/diffs/npm/postcss/8.5.19/8.5.26) |  |  | devDependencies | patch | --- > [!WARNING] > Some dependencies could not be looked up. Check the [Dependency Dashboard](../issues/1) for more information. --- ### Release Notes <details> <summary>isomorphic-git/lightning-fs (@​isomorphic-git/lightning-fs)</summary> ### [`v4.6.3`](https://redirect.github.com/isomorphic-git/lightning-fs/releases/tag/v4.6.3) [Compare Source](https://redirect.github.com/isomorphic-git/lightning-fs/compare/v4.6.2...v4.6.3) ##### Bug Fixes - IDB interface ([#​127](https://redirect.github.com/isomorphic-git/lightning-fs/issues/127)) ([035e472]( |
||
|
|
769ac70b74 |
fix(media): require authenticated reads (#4610)
This change requires a valid signed Blossom authorization request and current relay membership for every media GET and HEAD request. It removes the unauthenticated compatibility path and updates desktop reads to send the required authorization. This blocks anonymous retrieval and access after relay-membership revocation. It does not yet bind a blob to its originating channel, so someone removed from a private channel can still read a known blob while remaining a relay member. That channel-ACL follow-up remains required before closing the full finding. ## Testing - `git diff --check origin/main...codex/security-media-read-auth` - 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: Alex Rosenzweig <arosenzweig@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> |
||
|
|
38bf642fcf |
ci: prove the relay-driven mesh lifecycle — discover, join, infer, deny — 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>
|
||
|
|
4674750b7e |
fix(release): tag immutable desktop candidates (#4811)
## Summary Redesign the permanent Desktop release flow so unrelated merges to `main` cannot invalidate an already reviewed, green release candidate. - Tag the immutable, API-confirmed release PR head instead of its later squash commit. - Treat the merged PR—including an authorized owner/admin bypass—as publication authorization, while requiring trusted check evidence that was complete at merge time. - Make tag creation idempotent and collision-safe: an existing tag succeeds only at the exact candidate SHA, and create races refetch before accepting equality. - Replace ancestry-based previous-release discovery with a validated metadata ledger for side-history candidate tags. - Compute the next release from the prior frozen base to the new frozen base, excluding only the prior release squash SHA so unrelated commits remain in the changelog. - Preserve schema-1 production-tag migration and reject malformed metadata or equal/decreasing versions. - Update operator documentation for the normal squash-merge workflow. This is the reusable release process for `0.5.6` onward, not the retired one-shot `0.5.5` recovery path. ### Invariants covered - Candidate creation → unrelated `main` merge → authorized squash merge → immutable candidate tag. - Trusted producer IDs and merge-time completion timestamps; DCO's bounded post-merge exception remains isolated. - Missing/spoofed checks, tampered candidates, ambiguous PR associations, conflicting tags, and equal/decreasing versions fail closed. - Same-SHA retries succeed; different-SHA collisions fail. - Legacy schema-1 tag-on-main migration and schema-2 side-history accounting both preserve the correct next-release changelog. ### Related issue N/A — follows the Desktop release failures in #4788 and #4800 and the recovery revert in #4808. ### Testing At clean commit `6a91fbed8147a48cf174997de0c3e4cb2fb26474`: - `scripts/test-desktop-release-candidate.sh` - `scripts/test-release-ref-contract.sh` Both focused suites passed with HEAD unchanged. Princess Donut cleared the security/provenance surface, including the hostile merge-time timestamp cases. Mongo cleared the side-history ledger, migration, version-order, documentation, and contract-test surface. --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
631b05c883 |
feat: ship Buzz Term (#4347)
## Summary - ship **Buzz Term** end to end: the terminal engine/runtime, mounted desktop substrate, and user-visible naming - add Quinn's tape-deck-inspired banner: a beveled chassis filled by the `buzz term` wordmark, surrounded by a complete-hex field - derive the wordmark's three-stop sweep from each theme's terminal palette so primary, secondary, and accent roles remain visibly distinct across all 62 shipped themes, including light themes - paint the banner once on its own pointer-transparent canvas; PTY rendering beneath it remains unchanged ## Banner behavior - uses the renderer's shared `8.4 × 17` cell metrics and production aspect ratio `2.0238` - regenerates only for viewport/theme changes; palette switches repaint correctly while the banner is visible - dismisses on non-empty output from the active terminal session; empty output and inactive sessions do not dismiss it - fails closed below **70 columns** rather than squeezing or clipping the wordmark - adds **8 lines** to `terminalRenderer.ts` for shared cell metrics and **zero lines inside `paint()`** ## Screenshots | Buzz (light) | Buzz Dark | |---|---| |  |  | | Kanagawa Lotus (light) | Red | |---|---| |  |  | Additional production-aspect finals: [Vesper](https://buzz.block.builderlab.xyz/media/9ca6514b63f8cfb2107a85ca46f16a940c0883848e6fbc718e411af94aa13100.png), [Min Dark](https://buzz.block.builderlab.xyz/media/f67bd2970e5d64ffb07b1ae78ab58c847e6ebc23e7e7a48e067eb024dba64ec8.png), and [Dark Plus](https://buzz.block.builderlab.xyz/media/290fee08924f37d064abc687ecf3e9526ab05b87e8e56d610f23048949793dbe.png). The screenshot harness was checked against the shipped painter at this exact head: all **2,541 draw calls** matched on color, glyph, x, and y; four deliberate divergence controls fired. ## Verification at `98ebc8f9048bd5f0ceb7e843b67874d642f0b7fd` - desktop tests: **3,946 / 3,946** - TypeScript: clean - checks: pass (two pre-existing informational `useTemplate` notices only) - integration/e2e: PASS (independent exact-SHA lane; artifacts recorded in the originating Buzz thread) - artifact/dead-path sweep: clean - redteam G1–G7: PASS - all six named banner emitter-deletion mutants die - independent handwritten five-row full-wordmark fixture kills Quinn's seven-mutant battery, including a one-pixel glyph change - real `112 × 46` canvas-rect dismissal tests separately cover active non-empty, active empty, and inactive non-empty output - layer-drop and zero-draw painter mutants die; z-order and pointer-events verified - CI's `tsc && vite build` includes all three banner modules - performance at DPR 2 (worst-case measured envelope): - one-time content paint: **~0.7–0.8 ms**, paid only when the banner is built or its palette changes - busy compositor, CSS `1277 × 697`, backing `2554 × 1394`: **470–497 µs/frame** for the full banner (**2.82–2.98%** of a 60 Hz frame) - busy compositor, CSS `1920 × 1080`, backing `3840 × 2160`: **1,139–1,212 µs/frame** (**6.83–7.27%**) - empty, one-glyph, and full-banner controls converge: compositor cost follows backing-layer area and DPR rather than painted-cell count - in the actual idle welcome state, cost is below both vsync-clamped rigs' resolution; it is not claimed as zero - **Pane cross-rig spread: resolved at matched loop rate.** Two independent rigs initially differed 2.3× (58–68 vs 136 µs/Mpx of backing store; pane, CSS 1277×697 / backing 2554×1394, DPR 2). The cause of *that* spread is rAF loop rate: the higher figure came from a free-running loop at ~1600fps. Throttled to ~200–236fps, both rigs read 58–68 µs/Mpx (1.25–1.44% of a 60Hz frame). The busy-composite figures quoted above remain the **unthrottled worst case** and are conservative by ~2.3× at the pane. Not established: the mechanism and sign of free-running distortion (one rig under-charges ~15%, the other over-charges 2.3×), and the 1080p figure has not been re-measured throttled. - the layer paints only on generation/theme/resize and dismisses on first non-empty active-session output, so the measurable busy cost is a short-lived worst case rather than a persistent PTY paint-path tax ## Follow-ups in this PR These are intentionally subsequent commits after the certified static-banner head, not claims about `98ebc8f90`: 1. close the compositor metrology: remeasure the 1080p point throttled and characterize the opposite-sign free-running rAF distortion, with each measurement regime stated 2. add Tyler's animated honeycomb color waves, gated by `prefers-reduced-motion`, a full 62-theme phase-sweep contrast check, and DPR-2 per-tick performance certification 3. land the already-proven mounted theme-switch regression probe from `RESEARCH/BUZZ_TERM_G3A_PROBE/` 4. bound the slow/hang-shaped G1-c mutant `waitFor` 5. optionally trim the generator to its ink bounding box, reducing the minimum viewport from 70 to 62 columns --------- Signed-off-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com> Signed-off-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz> Signed-off-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@buzz.block.builderlab.xyz> Signed-off-by: npub17jjz49l9jjmhhk7cac63j8yt9z555n9cw8vk7v5jz4vzw4ppld5qgj57cc <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@buzz.block.builderlab.xyz> Signed-off-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> Co-authored-by: Dawn (sprout agent) <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@sprout-oss.stage.blox.sqprod.co> Co-authored-by: npub1t2tgm7d8f995uqvmnm8h88sg3wnpp9a5xysjf6dg3tjmgt3ltulqdp8ehr <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@buzz.block.builderlab.xyz> Co-authored-by: npub1cc3ha7z055mu0rwwu7806t2wt8mj3pvu0uv5mfp2c50dahaqhczshdalg6 <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@buzz.block.builderlab.xyz> Co-authored-by: npub17jjz49l9jjmhhk7cac63j8yt9z555n9cw8vk7v5jz4vzw4ppld5qgj57cc <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@buzz.block.builderlab.xyz> Co-authored-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@buzz.block.builderlab.xyz> Co-authored-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz> Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> |