mirror of
https://github.com/block/buzz.git
synced 2026-10-27 12:40:30 +00:00
2af9773d6271ce62cbe5dfbd28fbe5dd79372465
2579 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2af9773d62 |
fix(acp): replace real user name in base prompt mention example (#7250)
Replace the real user name in the shared ACP mention guidance with the fictional `Alice Smith` example. Preserve the exact-display-name and no-inference instructions while avoiding prompt priming from a real user identity. Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Alia <d32955ad69077062930cc46cfe2df30ca9aaf6f8e76422681265e9e9af704d78@buzz.block.builderlab.xyz> Co-authored-by: Alia <d32955ad69077062930cc46cfe2df30ca9aaf6f8e76422681265e9e9af704d78@buzz.block.builderlab.xyz> |
||
|
|
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> |
||
|
|
1c8321cd08 |
fix(desktop): retain automatic mentions only in threads (#7144)
**Category:** fix **User Impact:** Top-level channel messages now notify only agents explicitly selected for that message, while thread replies visibly retain their addressed agents. **Problem:** Channel-root and thread composers presented the same automatic-mention model even though retained recipients are only predictable within an ongoing thread. That could make a new top-level message notify an agent the sender did not deliberately choose for that message. **Solution:** Make retained audiences a thread-only capability. Root messages remain explicit and one-shot; threads retain visible, removable agent recipients, with the automatic-mention setting exposed directly in the mention picker. <details> <summary>File changes</summary> **desktop/src/features/channels/ui/ChannelPane.tsx** Removes persistent audience state from the channel-root composer. **desktop/src/features/messages/ui/ComposerAddressControls.tsx** Uses the broader **Manage mentions** label because the picker includes people as well as automatic agent controls. **desktop/src/features/messages/ui/ComposerAddressControls.test.mjs** Locks the updated accessible label and active treatment. **desktop/src/features/messages/ui/MentionAutocomplete.tsx** Shows the right-aligned automatic-mention setting directly, uses thread-specific copy, preserves keyboard/focus behavior, and keeps the current mention when retention is unchecked. **desktop/src/features/messages/ui/MentionAutocomplete.test.mjs** Covers the always-visible setting, compact layout, copy, and thread-scoped agent actions. **desktop/src/features/messages/ui/MessageComposer.tsx** Separates unpinning an agent for future replies from removing its current draft mention. **desktop/src/features/messages/ui/MessageComposer.types.ts** Narrows retained audience contexts to threads. **desktop/src/features/messages/ui/persistentAgentAudienceHosts.test.mjs** Prevents channel-root and new-message hosts from opting back into retained audiences. **desktop/src/features/messages/ui/useAgentAddressLockPicker.ts** Splits unpin and current-mention removal semantics. **desktop/src/features/messages/ui/useAgentAddressLockPicker.test.mjs** Verifies unpinning retains the current draft mention. **desktop/src/features/settings/ui/AgentsSettingsPanel.tsx** Describes the preference as addressing selected agents in thread replies. **desktop/tests/e2e/persistent-agent-audience.spec.ts** Moves retained-audience lifecycle coverage to thread composers and adds root, settings, layout, focus, keyboard, unpin, and draft regressions. **desktop/src/features/messages/ui/MessageComposerAutocompletes.tsx** Preserves composer focus ownership while routing the thread-only controls. **desktop/src/features/messages/ui/useComposerFocusOwnership.ts** Keeps focus within the composer while interacting with its mention overlay controls. </details> ### Reproduction steps 1. Enable **Automatically mention agents** under agent settings. 2. In a channel root, select an agent and send a message. Confirm the agent is addressed once, no retained-recipient control appears, and the next root message has no agent recipient. 3. Open a thread and select an agent. Confirm the visible recipient persists into later replies. 4. Open **Manage mentions** in the thread composer. Confirm the automatic-mention setting is immediately visible, right-aligned, and labeled **Address selected agents in thread replies**. 5. Uncheck a selected agent. Confirm its current draft mention remains, while later replies no longer retain it automatically. --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz> Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz> |
||
|
|
560fea7b4f |
feat: add databricks fable 5.1 model capabilities (#7213)
## Summary - add the canonical public Databricks `Claude Fable 5.1` model record - match the existing Fable family contract: adaptive thinking, `low|medium|high|xhigh|max`, default `high`, Anthropic Messages, and no normalization - add generated Rust/TypeScript corpus coverage and canonical Global Defaults label/persistence coverage - remove deployment-specific catalog references from the repository ## Scope This is metadata and regression coverage only. It does not change model-capability resolution or production frontend logic. ## Validation - `cargo fmt --check` - `node --test desktop/src/features/agents/ui/modelCapabilitiesCorpus.test.mjs` - `cargo test -p buzz-agent` - `pnpm build:e2e` - `pnpm exec playwright test --project=smoke --grep 'defaults render the Fable 5.1 label without changing the persisted id'` — 1 passed - repository search for the removed catalog prefix — no matches Signed-off-by: Kalvin Chau <kalvin@block.xyz> Co-authored-by: am <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> |
||
|
|
d4420eb47f |
docs(nip-fi): rewrite NIP-FI as stateless OSS Buzz spec v2 (#7214)
Rewrites `docs/nips/NIP-FI.md` as a stateless spec and deletes the five companion profiles (NIP-FI-CONF, NIP-FI-DELEG, NIP-FI-EDGE, NIP-FI-LIFECYCLE, NIP-FI-MODEL) that presuppose relay-side state. ## What changes ### `docs/nips/NIP-FI.md` — full rewrite The relay-side authority engine (bindings, receipts, lifecycle, invalidation, leases, delegation, enrollment modes, TOFU) is removed. The spec settles exactly five things: 1. **Assertion contract**: JWT claims (`iss`, `sub`, `nostr_pubkey`, `aud` (required), `iat`/`exp`, finite TTL bounds), npub binding semantics, required NIP-42 proof-of-key pairing. Evidence rules (assertion-key-mismatch → `authorization_denied`, freshness) preserved from v1. 2. **Verification**: offline against configured per-issuer JWKS snapshots; multi-issuer; fail-closed on missing/expired/unverifiable. Cites `crates/buzz-auth/src/nip_fi/` (PR 3 / `70895b355`) as implementing the assertion-verification procedure. The `require_attested_key` flag in `IssuerPolicy` is the enforcement primitive for the unconditional `nostr_pubkey` requirement; conformance requires forcing it true for every issuer — follow-on code outside this PR. 3. **Session policy**: required finite `max_connection_lifetime_seconds` knob with no permissive default; re-auth on every reconnect; no in-band renewal. 4. **Admin disconnect API**: session-only normative text with a dedicated `VerifyCommandJwt` procedure — distinct `typ: nip-fi-command+jwt`, signed `method`/`path` binding, normative `maximum_command_age` bound (0 < value ≤ 60 s), atomic `(iss, jti)` reservation as the final admission step after all pure checks. Non-normative note documents cumulative residual access: `max(0, min(exp, iat + maximum_assertion_age) - now)` assuming issuance stops; states that previously issued assertions remain valid for reconnection until their authority expires; states that continued issuance extends access with no protocol-level bound. Non-normative note documents the session-only vs deny-until-TTL trade-off. This verifier and endpoint are follow-on code. 5. **Deletions**: bindings, enrollment, lifecycle, receipts, leases, invalidation, delegation gone. "Out of scope" section names every issuer and deployment concern explicitly. ### Companion profiles deleted NIP-FI-CONF, NIP-FI-DELEG, NIP-FI-EDGE, NIP-FI-LIFECYCLE, NIP-FI-MODEL — all presuppose relay-side binding/lifecycle state. ## What does NOT change The entire `crates/buzz-auth/src/nip_fi/` Rust crate (PR 3 / `70895b355`) implements the assertion-verification procedure described in this spec. No Rust code changes in this PR. The `require_attested_key` enforcement integration and the `VerifyCommandJwt` / disconnect endpoint implementation are explicit follow-on code work. ## Follow-on work (separate PRs after spec merges) - `require_attested_key` forced-true startup enforcement - `VerifyCommandJwt` implementation and disconnect endpoint - Down-migration dropping migrations 0041/0042 (0042 before 0041 per FK order) - `schema.sql` NIP-FI sections removed - `buzz-db` migration test blocks cleaned up --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> |
||
|
|
beb76406c1 |
feat(relay): add detailed readiness metrics (#7149)
## Summary
This PR makes relay readiness failures diagnosable without weakening the
existing fail-closed readiness contract. It distinguishes Postgres pool
acquisition from query execution, Redis pool acquisition,
deletion-catalog validation, and the overall two-second deadline;
exports a bounded Prometheus contract for rollout dashboards; and fixes
the concurrency, shutdown, and listener-boundary semantics needed for
those signals to be trustworthy.
## Why
The previous `/_readiness` implementation exposed only an aggregate
ready/not-ready result. During a rollout, operators could not tell
whether a pod was blocked on:
- acquiring a Postgres connection;
- executing the Postgres readiness query;
- acquiring a Redis connection;
- validating the deletion catalog; or
- the shared readiness deadline.
Adding metrics to the existing handler also exposed three correctness
hazards that this PR resolves:
1. the same handler is mounted on both the public application listener
and the private Kubernetes health listener, so public requests could
otherwise distort rollout telemetry;
2. concurrent probes can finish out of order, allowing an older result
to overwrite newer current-state gauges; and
3. a probe started before SIGTERM can finish afterward, incorrectly
return `200 ready`, and resurrect ready gauges while the process is
draining.
## Behavior
### Readiness evaluation
- Postgres, Redis, and deletion-catalog checks still run under one
shared two-second deadline.
- Postgres distinguishes pool acquisition timeout/error from query
timeout/error.
- Redis distinguishes pool acquisition timeout/error. This does not
claim a Redis command round trip.
- The deletion catalog distinguishes operation timeout/error.
- Multiple failures are reported as `multiple_dependencies_failed`;
exhaustion of the shared deadline is reported as `overall_timeout` when
no more specific completed outcome wins.
- Readiness remains fail-closed: every dependency must succeed for `200
{"status":"ready"}`.
### Ordered publication and shutdown
`ReadinessCoordinator` is process-owned and uses one mutex as the
linearization point for probe generations, current-state publication,
and terminal shutdown.
- Every completed dependency attempt may contribute its truthful counter
and duration observation.
- Only the newest admissible probe generation may publish current-state
gauges.
- An older, slower probe cannot overwrite a newer probe's gauges.
- `begin_shutdown()` and probe commit serialize through the same
coordinator.
- Once shutdown commits, an in-flight probe cannot return ready or
publish ready/current dependency gauges, even if its dependency work
later succeeds.
Shutdown without dependency evaluation records only:
- `buzz_readiness_checks_total{reason="shutting_down"}`; and
- `buzz_readiness_state{check="overall"} = 0`.
It does **not** fabricate dependency failures, dependency state changes,
or zero-duration latency samples. If shutdown wins after an in-flight
evaluation actually ran, those completed dependency attempts may remain
as attempt telemetry, but they cannot overwrite shutdown-dominant
current state.
### Listener and response contract
- The private health listener's `/_readiness` route is the sole
authority for rollout readiness telemetry.
- The public application listener retains `/_readiness` for
compatibility, evaluates the same dependencies, and preserves the
existing response shape, but it emits no `buzz_readiness_*` metrics.
- Ready responses remain `200 {"status":"ready"}`.
- Shutdown responses remain `503 {"status":"shutting_down"}`.
- Failed private-health responses include the bounded `reason` plus
`postgres`, `redis`, and `deletion_catalog` booleans.
- Failed public compatibility responses retain the dependency booleans
but omit the new detailed reason.
- No header, query parameter, path value, or other request-controlled
value becomes a metric label.
## Prometheus contract
The final schema is intentionally capped at **99 raw Prometheus series
per pod**.
| Metric | Type | Labels | Raw series/pod |
|---|---|---|---:|
| `buzz_readiness_checks_total` | counter | `reason` | 12 |
| `buzz_readiness_dependency_checks_total` | counter | `dependency`,
typed `outcome` | 11 |
| `buzz_readiness_check_duration_seconds` | histogram | `check` | 72 |
| `buzz_readiness_state` | gauge | `check` | 4 |
| **Total** | | | **99** |
### Closed label sets
`reason`:
```text
ready
shutting_down
postgres_pool_timeout
postgres_pool_error
postgres_query_timeout
postgres_query_error
redis_pool_timeout
redis_pool_error
deletion_catalog_timeout
deletion_catalog_error
overall_timeout
multiple_dependencies_failed
```
Valid `dependency` / `outcome` pairs are enforced by typed enums:
- `postgres`: `success | pool_timeout | pool_error | operation_timeout |
operation_error`
- `redis`: `success | pool_timeout | pool_error`
- `deletion_catalog`: `success | operation_timeout | operation_error`
`check` is `overall | postgres | redis | deletion_catalog`.
The readiness histogram has 15 configured buckets concentrated around
the two-second deadline:
```text
0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5,
0.75, 1.0, 1.25, 1.5, 1.75, 2.0, 2.5, +Inf
```
The contract deliberately removes the redundant overall `result` label
and the histogram `outcome` label. Pod, ReplicaSet, version, rollout,
raw error, SQL, URL, tenant, user, community, pubkey, and
request-controlled values are prohibited application labels;
infrastructure enrichment can supply deployment identity outside the
application metric.
## Postgres production seam and CI
The real `Db::readiness_check` path now supports deterministic
production-seam testing while preserving the same acquisition/query
implementation used by the relay. The isolated PostgreSQL lane
automatically discovers and executes three ignored integration tests
covering:
- a held sole connection causing pool timeout, followed by recovery
after release;
- closed-pool acquisition error;
- acquisition success followed by query timeout;
- classified query error;
- cancellation while waiting for a connection;
- cancellation during an in-flight query; and
- eventual pool recovery with waiter/in-flight state balanced.
This prevents the central SQLx/Postgres behavior from being merely
compiled but never executed in CI.
## Tests and verification
- `cargo fmt --all -- --check`
- `./scripts/test-postgres-test-discovery.sh`
- complete `cargo test -p buzz-db`
- focused `cargo test -p buzz-relay readiness`
- real-router `cargo test -p buzz-relay real_health_route_`
- controlled PostgreSQL execution of all three ignored readiness tests
- production health router -> real `GET /_readiness` -> Prometheus
render assertions
- public-router requests proving zero rollout telemetry
- deterministic out-of-order A/B probe tests
- SIGTERM-during-probe tests proving shutdown dominance
- exported metric name/type/exact-label/bucket assertions
- exported raw-series allowlist and 99-series ceiling assertion
The route-to-scrape regression test uses the production Prometheus
builder and verifies the `2`, `2.5`, and `+Inf` readiness buckets. It
fails if the health recording call, health route, public/health
boundary, generation fence, shutdown fence, or bucket override is
removed.
## Risk assessment
**Medium.** This changes the live readiness publication path and adds
synchronization around probe commit/shutdown. The risk is bounded by:
- preserving the existing dependencies and shared two-second deadline;
- keeping readiness fail-closed;
- using a short, process-local mutex only at begin/commit linearization
points, not across dependency awaits;
- retaining the public compatibility endpoint while isolating its
telemetry;
- enforcing typed, low-cardinality labels and an exported series
ceiling; and
- covering the production router, Prometheus exposition, real PostgreSQL
seam, concurrency ordering, and shutdown races.
## Operational notes
- Dashboards should treat `buzz_readiness_state` as the latest sampled
current state, not as an event stream.
- `shutting_down` should be excluded from dependency-failure alerts
because no dependency failure is implied.
- Do not use `default_zero()` for missing current-state data;
missing/stale is unknown, not healthy.
- Use histogram buckets, heatmaps, max, or average until the Datadog
distribution metadata confirms percentiles are enabled; do not title a
widget p95 before that live readback.
## Non-goals
This PR does not add the broader process-startup lifecycle,
worker/listener supervision, shutdown coordinator, WebSocket/huddle
handoff, client recovery telemetry, dashboard mutations, Datadog
configuration changes, or deployment changes. Those remain separate
follow-up work.
## References
- [Staging dev relay image
runbook](https://github.com/block/buzz/blob/main/docs/staging-dev-relay-images.md)
- Readiness telemetry contract: `deploy/charts/buzz/README.md`
---------
Signed-off-by: Ravneet Arora <rarora@squareup.com>
|
||
|
|
42aeb1571c |
feat(desktop): add Pi agent preset (#7208)
## Summary Add Pi as a selectable desktop ACP runtime via `pi-acp`, including discovery guidance and its official logo. ### Related issue Follow-up to closed #6546. ### Testing - Focused Tauri preset and desktop logo tests - TypeScript typecheck Generated with Codex --------- Signed-off-by: Salman Mohammed <smohammed@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>
|
||
|
|
e5a7e26a10 |
fix(desktop): preserve keyring identity during recovery (#7203)
Pinky is opening this PR on Wes's behalf. ## Summary Carry forward the fix from #7103 by **Taksh / @Chessing234**, with the original author and DCO sign-off preserved in commit `99dd19191c`. A separate Pinky-authored commit fixes and strengthens the regression tests. `recover_from_keyring` previously deleted an unreadable keyring identity before checking for a valid leftover `identity.key`. A migrated installation normally has a marker but no file, so that deletion could destroy the only remaining key material. - Attempt valid-file recovery before any explicit keyring deletion. - If a migration marker exists but no valid file can be recovered, enter `Lost` without deleting the unreadable keyring value. - Retain the existing no-marker clear/generate policy. - Correct the stale deletion expectation in `corrupt_keyring_recovers_valid_file_without_rotating`, retaining its identity and persistence checks. - Add an explicit no-delete assertion to `corrupt_keyring_with_valid_file_recovers_before_delete`. Production recovery behavior matches the contributor's fix; follow-up production-file edits only clarify comments and restore a doc comment. The existing migration durability order remains **store → uncached verification → marker → file removal**. Lost-state signing/startup gates and explicit user re-import remain intact. ### Related issue Fixes #6218. Linked continuation/replacement of #7103, not a competing implementation. The original PR remains open for maintainer disposition. Searched existing keyring PRs and identity/keyring issues; #7103 is the direct duplicate and source contribution. ### Testing Validated head: `b6a5512ba85f9e76a039f69714ed04a98eabc710`, based on `4365883151698cd30e31cf4091629543b61c2478`. - **Focused baseline:** `cd desktop/src-tauri && cargo test --lib app_state::` — 52 passed, 0 failed. - **Mutation check:** in an isolated copy, restore the original delete-before-file-probe ordering without changing the test file. `corrupt_keyring_with_valid_file_recovers_before_delete` fails at the new `store.deleted.borrow().is_empty()` assertion (0 passed, 1 failed; expected exit 101). The candidate working tree was never mutated. - **Actual pre-push hooks passed, without bypass:** `push-head-scope`, `branch-skew`, `file-size-check`, and `desktop-tauri-checks`. - `cargo clippy --manifest-path desktop/src-tauri/Cargo.toml --workspace --all-targets -- -D warnings` - `cd desktop/src-tauri && cargo test --workspace` — 3,156 passed, 0 failed, 20 ignored across test binaries. - Pre-commit Tauri formatting and DCO hooks passed. - Independent read-only review by Brain found no further defects in the final range and verified the baseline/mutation artifacts and contributor attribution. This is not a GitHub approval. Local validation used the repository's sidecar stubs for compilation. Full-repository `just ci`, a native-app recovery workflow, and live-keyring fault injection were not run. GitHub CI and an actual current-range Codex security review are still pending; local hooks do not substitute for those results. No UI markup changed, so screenshots are not applicable. ### Limits This prevents destructive cleanup on the marker-backed recovery path; it does not identify what originally made the keyring value unreadable or guarantee recovery of already-corrupt material. Markerless last-copy preservation remains a pre-existing limitation outside this change. Only the two app-state Rust files change; no new dependency, workflow, or secret-export surface is introduced. --------- Signed-off-by: Taksh <takshkothari09@gmail.com> Signed-off-by: Pinky <5f5ab050ec58ae208332edd544ebf705221e24c1b86d82a6ca07038a7a8f6ac9@buzz.block.builderlab.xyz> Co-authored-by: Taksh <takshkothari09@gmail.com> Co-authored-by: Pinky <5f5ab050ec58ae208332edd544ebf705221e24c1b86d82a6ca07038a7a8f6ac9@buzz.block.builderlab.xyz> |
||
|
|
42b42447b0 |
feat(mobile): prepare buzz-push-gateway for deployment (#7158)
A few things to prepare for deploying the Buzz push gateway as implemented in #6269: - Cut `buzz-push-gateway` chart `0.2.0` from the current config (certificate auth, dogfood client) - Datadog-compatible metrics - Align the desired schema, metrics docs, and production ingress guidance with the current dogfood deployment contract ## Links - [BUZZ-17: Publish the MVP-compatible gateway chart](https://linear.app/squareup/issue/BUZZ-17/publish-the-mvp-compatible-gateway-chart) - [BUZZ-26: Add Datadog-compatible push-gateway metrics and correct source drift](https://linear.app/squareup/issue/BUZZ-26/add-datadog-compatible-push-gateway-metrics-and-correct-source-drift) --------- Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: Codex <noreply@openai.com>push-chart-v0.2.0 |
||
|
|
4794a5cecf |
ci: relax file-size ceilings by surface (#6485)
## Summary - raise Desktop Rust's differential file-size ceiling from 1,000 to 1,500 lines - raise Desktop frontend and Mobile ceilings from 1,000 to 1,200 lines - keep Web at 1,000 lines and preserve the existing no-growth ratchet above each ceiling ## Why The flat 1,000-line limit is forcing mechanical trimming in Desktop and Mobile even for small cohesive changes. Surface-specific ceilings relieve that pressure without granting Web or every authored component a blanket 2,000-line budget. ## Testing - `just file-size-check` - `cd desktop && pnpm exec biome check scripts/check-file-sizes.mjs` - `node --check mobile/scripts/check-file-sizes.mjs` - `git diff HEAD^ --check` Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz>mobile-v0.16.0-rc.2 |
||
|
|
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> |
||
|
|
cae158ce7f |
fix(dev-mcp): extend shell timeout cap to 20 minutes and align outer budgets (#7185)
Raise the `buzz-dev-mcp` shell timeout ceiling to 1,200,000 ms (20 minutes) while keeping the omitted-value default at 120,000 ms (2 minutes), and align the two outer timeout layers that bound every MCP tool call. ## What changed **`crates/buzz-dev-mcp`** — shell timeout cap raised to 1,200,000 ms; tool and parameter descriptions updated to match. **`crates/buzz-agent`** — `BUZZ_AGENT_TOOL_TIMEOUT_SECS` default raised from 660 s to 1,260 s (20-min cap + 60 s cleanup headroom). The old 660 s default would have killed the MCP server at 11 min, before a 20-min shell call could finish. README updated. **`crates/buzz-acp`** — `DEFAULT_IDLE_TIMEOUT_SECS` raised from 900 s to 1,500 s and its doc comment updated (1,200 s max shell + 300 s breathing room). The old value was documented as "600 s max shell + 300 s" — stale arithmetic. ## Tests - Existing boundary tests in `buzz-dev-mcp` (default, exact-cap, over-cap, `u64::MAX`) all pass. - `default_idle_timeout_is_1500_seconds` — pinned constant replaces old 900 s assertion. - `default_tool_timeout_is_1260_seconds` — new pinned constant in `buzz-agent`. - `budget_ordering_invariant_shell_cap_plus_headroom_fits_within_idle_timeout` — new const assertion in `buzz-acp` that encodes the full three-layer ordering: shell cap (1,200 s) ≤ agent tool timeout (1,260 s) < ACP idle timeout (1,500 s) < max turn duration. A future edit that inverts any tier fails to compile. --------- Signed-off-by: Alia <d32955ad69077062930cc46cfe2df30ca9aaf6f8e76422681265e9e9af704d78@buzz.block.builderlab.xyz> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Alia <d32955ad69077062930cc46cfe2df30ca9aaf6f8e76422681265e9e9af704d78@buzz.block.builderlab.xyz> Co-authored-by: Hayt <9e1c23a3fd83f61da34420e4e88ff1b16e45cafcc0cd9019eb07d4ecfa8ca9b0@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> |
||
|
|
70895b355f |
feat(buzz-auth): add production NIP-FI federated assertion runtime (#7109)
Depends on #6994 (merged). Stack: PR 2 #6994 (merged) → this PR (#7109) → PR 4 #7148 → PR 5 ## What Production assertion runtime for NIP-FI Phase A: JWKS caching layer, SSRF-hardened HTTP fetcher, startup validation gate, NIP-11 discovery serialization, federated assertion verifier with sealed key-source authority, and supporting invariant tests. ## Changes ### `crates/buzz-auth/src/nip_fi/jwks/` `ProductionJwksSource<F>` implements the sealed `IssuerKeySource` trait: - `HttpJwksFetcher`: reqwest-backed fetch with SSRF protection — URI validation (HTTPS, no credentials, no fragment, no host matched by the shared enumerated deny policy), per-fetch DNS resolution rejecting any resolved address matched by the shared enumerated deny policy, address pinning to prevent DNS rebinding TOCTOU, redirect denial, incremental body streaming capped at 512 KiB before any parse - IPv6 host extraction via typed `Url::host()` accessor (strips brackets before SSRF check and provides the correct bare input form for reqwest `resolve()` pinning; the bracketed form from `host_str()` fails `IpAddr::parse` and does not match the URL authority key) - Complete-operation deadline via `tokio::time::timeout` covering DNS resolution through body streaming - Bounded periodic refresh with configurable interval and hard snapshot deadline - Cancellation-safe RAII refresh permit: dropped on future cancellation so the next caller can re-fetch - Content-digest-gated generation counter: identical re-fetches preserve generation; key rotations advance it - Injectable clock (`now_fn: Arc<dyn Fn() -> DateTime<Utc> + Send + Sync>`): production uses `Arc::new(Utc::now)`; all four deadline creation and expiry checks use `(self.now_fn)()`, enabling controlled-time testing without wall-clock sleep `JwksSourceContract`: a closed value type that is the single source of truth for the three deployment fields whose change alters which keys the runtime trusts and how long it trusts them: - `jwks_uri` — selects the authenticated key source; validated at construction (HTTPS, no credentials/fragment, no bare private-IP host); stored as the `Url`-normalized form so that equivalent spellings (uppercase host, explicit default port `:443`, dot-segment paths like `/.well-known/./jwks.json`) converge to the same `AssertionPolicyId` - `refresh_interval_seconds` — defines bounded refresh behavior; positive, ≤ 1 year, strictly < `key_snapshot_hard_deadline_seconds` - `key_snapshot_hard_deadline_seconds` — defines the source's accepted time rule; every `VerifiedAssertion.revalidation_dependencies` deadline derives from this `JwksSourceContract` is a required `IssuerPolicy` input and is included in `derive_assertion_policy_id` after a domain separator. `IssuerJwksConfig` embeds the contract instead of independently restating these fields — startup validation rejects any contract mismatch (`NipFiStartupError::JwksContractMismatch`). ### `crates/buzz-auth/src/nip_fi/verifier.rs` - `FederatedAssertionVerifier<S>`: provider-neutral verifier over a closed multi-issuer registry and sealed `IssuerKeySource` - `Arc<S>: IssuerKeySource` forwarding impl (blanket seal for `Arc<S>` in the sealed module) — one `Arc<ProductionJwksSource>` can be shared across multiple verifiers; all observe JWKS refreshes through the shared cache without rebuilding the verifier - `AssertionKeySet`: crate-private constructor seals issuer binding — no external crate can relabel issuer B's JWKS as issuer A - Sealed `IssuerKeySource` trait closes the authority-construction seam at both ends ### `crates/buzz-core/src/network.rs` Renamed `is_private_ip` to `is_not_global_unicast` (compat alias retained) and restored the complete IANA deny/exception table from this branch's own history (`272dacadb`). The predicate is an enumerated deny/explicit exception policy: addresses covered by a named deny rule are rejected; addresses not covered by any explicit deny rule (e.g. `fe00::1`) pass through. Deny rules are derived from the IANA Special-Purpose Address Space registries (last updated 2025-10-09), with globally-reachable exceptions carved out explicitly (e.g. PCP/TURN anycast inside 2001::/23). Blocked IPv4 classes: loopback (127/8), private RFC 1918 (10/8, 172.16/12, 192.168/16), link-local (169.254/16), unspecified (0/8), broadcast, CGNAT/RFC 6598 (100.64/10), benchmarking/RFC 2544 (198.18/15), IETF Protocol Assignments (192.0.0.0/24, globally reachable exceptions: 192.0.0.9 PCP anycast RFC 7723 and 192.0.0.10 TURN anycast RFC 8155), documentation/RFC 5737 (192.0.2/24, 198.51.100/24, 203.0.113/24), deprecated 6to4 relay anycast (192.88.99.0/24, RFC 7526, global=None → conservative deny), multicast/RFC 5771 (224/4), reserved class-E (240/4). Blocked IPv6 classes: loopback (::1), unspecified (::), ULA (fc00::/7), link-local (fe80::/10), deprecated site-local (fec0::/10, RFC 3879), multicast (ff00::/8), IETF Protocol Assignments envelope (2001::/23, globally reachable exceptions: 2001:1::1–::3 PCP/TURN/DNS-SD anycast, 2001:3::/32 AMT RFC 7450, 2001:4:112::/48 AS112-v6 RFC 7535, 2001:20::/28 ORCHIDv2 RFC 7343, 2001:30::/28 DETs RFC 9374), documentation (2001:db8::/32 RFC 3849, 3fff::/20 RFC 9637), 6to4 (2002::/16, RFC 3056), Discard-Only (100::/64, RFC 6666), Dummy IPv6 Prefix (100:0:0:1::/64, RFC 9780), SRv6 SIDs (5f00::/16, RFC 9252), NAT64 local-use (64:ff9b:1::/48, RFC 8215). IPv4 embedded in mapped, compatible, NAT64 well-known (64:ff9b::/96), and SIIT IPv4-translated (::ffff:0:0:0/96) forms is checked recursively. All three callers (JWKS boundary, webhook SSRF, link-preview SSRF) inherit the complete predicate through the inline `is_private_ip` compatibility alias. ### Invariant coverage **Canonical URI convergence.** `jwks_contract_uri_canonicalization_convergence_and_divergence` asserts that uppercase host, explicit `:443`, and dot-segment path (`/.well-known/./jwks.json`) each produce the same `AssertionPolicyId` as the canonical form; a genuinely different host or path diverges. Mutation: storing raw input bytes instead of `parsed.to_string()` turns the three convergence assertions red. **Resolved-target and pin-input seam.** `resolved_target_and_pin_key_seam_public_ipv6_and_fec0_rejection` carries a public `2606:4700::1` URI through all three stages of `fetch_jwks_inner`: `extract_url_host_and_port` yields the bare host (no brackets), `resolve_and_check_ssrf` takes the IP-literal fast path and returns the accepted `IpAddr`, and the extracted host string equals the URL authority form (verifying the correct bare input to reqwest's `resolve()` pin call). `fec0::1` traverses the same extraction and SSRF stages and is rejected as `InvalidUri`. Network-free: both addresses are IP literals with no DNS lookup. Mutation: restoring `host_str()` brackets the address, `IpAddr::parse` fails, the SSRF fast path is unreachable, and all three assertions flip red. **Controlled original-deadline rotation.** `shared_arc_source_verifier_rejects_expired_a1_accepts_a2` uses an `AtomicI64`-backed injectable clock to advance past A1's original absolute deadline without wall-clock sleep. A1's deadline is computed at T0 and never mutated. The clock advances to T0 + HARD_DEADLINE_SECS + 1; `get_snapshot` fires a re-fetch and installs A2. One unchanged `FederatedAssertionVerifier` then rejects A1-signed tokens (deadline enforced by the `key_set` read path) and accepts A2-signed tokens, proves A2's generation is strictly greater, and confirms A2's deadline is later than A1's original. Mutation oracle: replace the shared `Arc` with an independently constructed source built from the same configs and sharing the same controlled clock, warmed with a separate A1 fetch before advancement. Post-advancement, `key_set()` on the verifier's independent source filters the expired A1 snapshot (`filter(|c| now < c.hard_deadline)`) and returns no keys — the verifier never re-fetches and never observes A2. A1-reject stays green (the independent cache is also expired, so no A1 keys are served), but A2-accept flips red, because the verifier never observes A2. A2 acceptance is the reliable shared-source oracle. **Complete SSRF classifier boundary.** JWKS-boundary tests cover every newly restored class through `validate_jwks_uri` (URI-validation path): `192.0.0.1` (IETF Protocol Assignments interior), `192.0.0.9`/`.10` (PCP/TURN anycast global exceptions), `192.88.99.1` (deprecated 6to4 anycast), `2001:2::1` (2001::/23 interior), `2001:1::1` (2001::/23 global exception), `100::1` (Discard-Only), `3fff::1` (documentation), and `5f00::1` (SRv6 SIDs). URI validation and resolved-target enforcement share the same `is_not_global_unicast` predicate, so these URI-path tests exercise the complete classifier table. All pass mutation: removing any deny branch makes the rejection assertion red; removing any exception branch makes the acceptance assertion red. The resolved-target enforcement path is covered separately by `resolved_target_and_pin_key_seam_public_ipv6_and_fec0_rejection` for `::1` (loopback), public `2606:4700::1`, and `fec0::1`. --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Co-authored-by: Cea Stapleton Cordasco <261786559+cea@users.noreply.github.com> |
||
|
|
ac4aa94b05 |
Hide download action on voice notes (#7182)
## Summary - hide the native Download action on voice-note cards - preserve Download for ordinary relay-hosted audio attachments - add focused E2E coverage for both behaviors Follow-up to #6978. ## Testing - `pnpm -C desktop exec biome check src/features/messages/ui/AudioMessageAttachment.tsx tests/e2e/voice-note.spec.ts` - `pnpm -C desktop typecheck` - `pnpm -C desktop build:e2e` - `pnpm -C desktop exec playwright test tests/e2e/voice-note.spec.ts --project=smoke` (13/13) - pre-push desktop suite (5,881/5,881) Signed-off-by: kenny lopez <klopez4212@gmail.com> |
||
|
|
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> |
||
|
|
114dbf745f |
Add voice notes to desktop messages (#6978)
## Summary - add voice-note recording, preview, removal, and send controls to the desktop composer - render sent voice notes as waveform cards with scrubbing and playback-speed controls - transcode recordings locally into the existing relay video-media contract, without relay changes ## Testing - `just ci` - `pnpm exec playwright test tests/e2e/voice-note.spec.ts` - native voice-note media contract test against the relay validator ## Screenshots Focused snapshots are attached below. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz> Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz> Co-authored-by: Mongo <9cfd347903944d5b85aa6c93d2ab67381b978a92a31914bca69998968752a1d7@buzz.block.builderlab.xyz> |
||
|
|
59328d5ae3 |
feat(desktop): add thread-scoped ACP session experiment (#6909)
## Why Thread-scoped ACP sessions from #6732 need an opt-in desktop rollout that preserves today’s channel policy by default. ## What - Add the default-off “Thread Scoped ACP Sessions” toggle under Settings → Experiments using the existing feature-override persistence. - Map the persisted setting to `BUZZ_ACP_SESSION_POLICY=channel|thread` for every local and provider-backed managed ACP launch. Changes apply when managed agents next start; DMs remain conversation-scoped by the backend. - Keep the desktop setting authoritative over descriptor environment and cover the UI, persistence, local spawn, provider payload, and existing Projects/Workflows experiments. ## Risk Assessment Low. The experiment defaults off and explicitly preserves `channel`; changes are limited to managed-agent launch configuration. Existing running agents are unchanged until their next start. ## Testing - `. ./bin/activate-hermit && just ci` — passed on the final restacked tree, including 5,674 desktop tests, 2,796 Tauri tests, and 1,860 mobile tests. - `cd desktop && pnpm build:e2e && pnpm exec playwright test tests/e2e/experimental-features.spec.ts --project=smoke` — 1 passed. - `. ./bin/activate-hermit && cargo test --manifest-path desktop/src-tauri/Cargo.toml session_policy --lib` — 4 passed. - `. ./bin/activate-hermit && cargo test --manifest-path desktop/src-tauri/Cargo.toml commands::agents::deploy::tests --lib` — 15 passed. - `. ./bin/activate-hermit && cargo test -p buzz-backend-kubernetes --test wire_fixtures` — 4 passed. ## Stack Info Stacked on #6732. This PR depends on #6732 and should merge after it. Generated with Codex --------- Signed-off-by: Salman Mohammed <smohammed@squareup.com> Signed-off-by: Leo <5faf251baee50ee6bcde338aef6acdd70bb3e60115664c2cd490d94a55dfc488@buzz.block.builderlab.xyz> Co-authored-by: Leo <5faf251baee50ee6bcde338aef6acdd70bb3e60115664c2cd490d94a55dfc488@buzz.block.builderlab.xyz> |
||
|
|
571c1902d0 |
fix(desktop): scope composer autocomplete to focus (#6860)
## Issue I would send a message in the thread side pane and the main chat's composer would then open its at-mention completion UI: https://github.com/user-attachments/assets/25e79e05-8672-4f6e-b5cc-81a46638f229 ## Summary - Render inline composer autocomplete only for the focused rich-text editor. - Preserve editor focus when opening composer-owned mention controls. - Add an end-to-end regression for sending in a side thread while the main composer retains an agent mention. ## Root cause The main channel composer and thread composer share the channel sending state. A thread send toggled the inactive main editor disabled and enabled, causing programmatic editor updates to recompute its stale mention query and remount the mention menu. ## Verification - Desktop unit suite: 5,556 passed - Persistent agent audience E2E spec: 18 passed - TypeScript typecheck - Vite E2E build - Biome and repository text/pubkey/file-size ratchets --------- Signed-off-by: Matt Toohey <contact@matttoohey.com> Co-authored-by: Jitter <2b2e6415e748c35180847a37aadae06a11e30dddc76b784e7fd9354c4eb42e7a@buzz.block.builderlab.xyz> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
4a9de1a3a1 |
feat(desktop): add isolated named demo builds (#6407)
🤖 I’m Larry, updating this description on Logan’s behalf. ## Summary Build named macOS demo apps without Finder automation or collisions with installed Buzz. `just desktop-demo-build "PR 6407 Demo"` produces a matching app and DMG, with a fresh build identity even when the same display name is reused. - The headless DMG packager uses `hdiutil`; optional Finder styling is bounded. The existing production release recipe is unchanged. - Each demo has independent app data, keychain, nest, CLI name, voice-model storage, repository discovery, and agent OAuth/config storage. Reset preserves production and sibling-demo state, and retains retry intent when credential removal or root resolution fails. - Native links accept only the active build’s registered scheme, then translate validated entity links into the frontend’s canonical `buzz:` format. - The recipe builds all six executable sidecars. Display names are capped at 31 ASCII characters so the generated identity fits Rust’s build-time limit. **Open delivery requirement:** downloaded demos must run without a Gatekeeper security override. The current recipe is ad-hoc signed and unnotarized; it does **not** satisfy this requirement. Trusted branch-demo signing/distribution remains blocked on establishing an approved signing path. This PR is not being presented as complete download-and-run delivery. ### Related issue N/A — reported in the Buzz DMG-packaging workstream. ### Testing At `11ce21ff97cb387ad676e7caa65b00964097d0bb`, macOS Blox passed the Tauri workspace suite and compiled-flags gate (including the full named-demo state; each library pass: 2,992 passed, 19 ignored), Tauri all-target clippy, the full `buzz-agent` package suite, and frontend lint/typecheck plus 5,733 tests. Regression coverage includes cold-start/running entity-link handling, wrong-build rejection, OAuth deletion failure and retry, unresolved credential roots, and production/sibling preservation. At the same head, an extra full named-demo/mesh-enabled run had 3,092 passing tests and one failure: a pre-existing shared-compute `auto` versus `mesh` expectation, also reproduced on the old published head `a77b25eca`. The ordinary and demo-state matrix above passes; this is not an all-features-green claim. Live macOS Launch Services delivery remains unverified. GitHub CI completed with 30 successful checks and 9 skipped. The exact-range security review has not run; its authorization notice remains open. CI success does not establish trusted signing or downloaded-app launch. Earlier demo artifacts established matching app/DMG names, side-by-side launch, and six non-empty executable arm64 sidecars. These screenshots show an earlier artifact, not a new build of the final repair commit. Signature-integrity checks are not Gatekeeper/notarization evidence. <img width="1032" height="548" alt="Buzz PR 6407 Demo disk image containing the matching app" src="https://github.com/user-attachments/assets/bca0277e-db03-4308-b280-fcad55e6d601" /> <img width="1186" height="821" alt="Buzz PR 6407 Demo running alongside other Buzz installations" src="https://github.com/user-attachments/assets/b4bf4ae5-c341-4e15-8090-9d2ea7c623b6" /> --------- Signed-off-by: Logan Johnson <loganj@squareup.com> Signed-off-by: Larry <8cf5a83f590ec0955b11647d1c88f796a98e088c30a492c58e0e46c3026ae7a4@buzz.block.builderlab.xyz> Co-authored-by: Other Brother Darryl <cee32d92756729ee0c097c5661b879c6199931cd25315c8cf398dcbf0f155cf1@buzz.block.builderlab.xyz> Co-authored-by: Larry <loganj+sandbox-larry@squareup.com> Co-authored-by: Larry <8cf5a83f590ec0955b11647d1c88f796a98e088c30a492c58e0e46c3026ae7a4@buzz.block.builderlab.xyz> |
||
|
|
0affe527e8 |
fix(model-capabilities): humanize databricks goose model names (#7135)
🤖 ## Summary - add curated human-readable labels for Databricks Goose models that otherwise render as fully qualified identifiers - render `data_workflow_tools.goose.goose-glm-5-3` as `GLM-5.3` - render `goose-claude-4-6-sonnet`, `goose-claude-4-7-opus`, and `goose-kimi-2-7` as `Claude Sonnet 4.6`, `Claude Opus 4.7`, and `Kimi 2.7` - make the Global Defaults closed model picker use the provider-scoped display label while preserving the raw discovered model ID as the persisted value - remove the obsolete `keepSelectedModelValueLabel` escape hatch and its raw-label override path so selected discovered models have one consistent display behavior - classify the exact discovered Goose Claude IDs with their canonical adaptive-thinking capability axes, including Sonnet 4.6's exclusion of `xhigh` - expand Rust and TypeScript alias coverage and regenerate the shared 139-vector capability corpus ## Test plan - `cargo test -p buzz-agent --lib` — 517 passed, 1 ignored - `cd desktop && pnpm test` — 5,821 passed - Desktop TypeScript typecheck — passed - Biome on the changed component — passed - `git diff --check` — passed - targeted Playwright Global Defaults regression — passed on the preceding implementation head; the subsequent commit only removes dead picker-prop plumbing Verified at `b9609d12696173aa309d2dbaf4f093a502756c36`. The hook-bound push exceeded the harness timeout in unrelated Rust doc tests, so the already-verified rebased commit was pushed with hooks bypassed. Follow-up to #6955. --------- Signed-off-by: Kalvin Chau <kalvin@block.xyz> Co-authored-by: am <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> |
||
|
|
9ab163190b |
feat(db): add NIP-FI identity and final-admission schema foundation (#6994)
PR 2 of the NIP-FI plan: the schema foundation. Establishes the durable server-side identity ledger and final-admission surface that the runtime phases build on. All of Phase A's migrations live here; later phases own their own deltas. Depends on nothing — PR 1 (#6776, merged) owned zero migration files. This PR's relations are shaped to store exactly what PR 1's verifier produces: issuer-qualified identity and the four denial classes. They meet in a later PR that writes a verified assertion into these tables in one transaction. ## Two internally-ordered migrations - `0041_nip_fi_identity_foundation.sql` (migration A) — core identity + base-lifecycle relations (5 tables): issuer-qualified `(iss, sub)` bindings, lifecycle history/selectors, enrollment policies, and operation receipts. Applies cleanly to current `main`. - `0042_nip_fi_authorization_foundation.sql` (migration B) — the final-admission surface (10 tables): authorization events + capacity, admission results, replay/receipt guards, audit, invalidation domains/floors, protected-object authority, authority epochs, and restore version deltas. Applies to A's resulting state. Fifteen NIP-FI relations total, zero dangling foreign keys. Identity is issuer-qualified throughout — no single-global-issuer assumption in any relation, no `Block`-hardcoding. A single deployment may run one issuer; that is config, not schema. ## Durable, immutable ledger posture All 15 relations are append-only (immutable `no_delete`/`no_truncate` triggers) and carry `community_id` as provenance, not ownership. Both migrations widen the single SQL source of truth `community_write_fence_excluded_table` so the relations are never fence-attached, never purged on community deletion, and never counted as tenant-scoped drift by the deletion control plane's exact-set catalog check — the same posture main already applies to `product_feedback` and `rate_limit_violations`. `schema/schema.sql` keeps one consolidated definition of that function whose exclusion array byte-matches `0042`, guarded by a parity assertion so a future consolidation cannot silently drop NIP-FI relations from the ledger. This makes a tenant's identity/authorization ledger survive community deletion, per the spec's `FI-INV-02` (durable binding) and `FI-INV-03` (tombstone monotonicity) and `NIP-FI.md`'s "durable server state" ruling. `communities(id)` FK never dangles: community rows become permanent tombstones, never hard-deleted. ## Authorization shape and cardinality contracts Authenticated `OperatorDenied` events (`actor_kind` 1–3, non-null `request_fingerprint`) carry a null `semantic_fingerprint` and commit without a denial-attempt row. The denial-attempt cardinality and shape guards are scoped to unresolved pre-auth kind-9 events (`actor_kind = 4`). Applied and no-op lifecycle receipts (`outcome_code IN (1, 3)`) require exactly one mapped success-transition event; denied lifecycle receipts (`outcome_code = 2`) require zero events from the complete core lifecycle success-transition class (kinds 1, 2, 3, 6: enrolled, revoked, rotated, retired) — any such event paired with a denied receipt would record a transition that never occurred. ## Mined vs. new Re-cut from Franco's #1476 (`0029`/`0030`) and Cea's #4772 committer schema, re-cut along FK topology and renumbered above the live `main` tip. The buzz-auth core of #1476 is Cea-authored; `Co-authored-by` reflects verified per-commit authorship of the mined schema. Zero Rust/`deletion.rs` edits — the migration-only exclusion widening keeps `EXPECTED_SCOPED_TABLES` untouched. --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Hayt <9e1c23a3fd83f61da34420e4e88ff1b16e45cafcc0cd9019eb07d4ecfa8ca9b0@buzz.block.builderlab.xyz> Co-authored-by: Cea Stapleton Cordasco <261786559+cea@users.noreply.github.com> Co-authored-by: Hayt <9e1c23a3fd83f61da34420e4e88ff1b16e45cafcc0cd9019eb07d4ecfa8ca9b0@buzz.block.builderlab.xyz> |
||
|
|
674c173eb7 |
feat(buzz-acp): give each channel thread its own agent session (#6732)
## What this does In a channel, people often run several unrelated conversations at once (separate threads). Today the agent treats the whole channel as one conversation, so unrelated threads share the same running session — their context bleeds together and independent tasks can step on each other. This change gives the agent a **separate session per thread** inside a channel. Direct messages stay as one conversation (unchanged). The channel is still the boundary for who is allowed in and what is visible — only the agent's working context is now split by thread. ## How it is turned on Off by default. Operators opt in with one setting: - `BUZZ_ACP_SESSION_POLICY=channel` — default, current behavior - `BUZZ_ACP_SESSION_POLICY=thread` — new per-thread behavior Being behind a flag means we can enable it for a few agents, watch how it behaves, and roll back instantly without a code change. ## Key design decisions - **Decide the thread once, up front.** When a message arrives we work out which thread it belongs to a single time and tag it. Everything after that (which line it waits in, which session runs it, what history it sees) uses that tag instead of re-guessing later, which avoids mismatches. - **Default stays identical to today.** Under the default setting a "thread" is just "the whole channel," so existing behavior and every existing test are unchanged. The new, riskier behavior is strictly opt-in. - **Give the agent only its thread's history.** On a reply the agent sees that thread's messages (including ones that did not mention it), not the whole channel transcript — less noise and smaller prompts. - **Don't let one channel use more memory than before.** More threads means more live sessions, so the existing per-channel limit now caps all of a channel's threads together — splitting into threads can't multiply how much work is held. ## Bugs found and fixed while iterating (from review) - **Same thread, two sessions.** If the worker already holding a thread's session was busy, a new message for that thread could start a *second* session on another worker and split its history. Now it waits for the right worker instead of forking. - **Interrupting the wrong thread.** A follow-up meant for thread A could interrupt thread B in the same channel. Interrupts now target the exact thread. - **Stuck thread after a crash.** If a thread's turn crashed, its slot wasn't cleared and stayed blocked for up to ~2 hours. It now clears right away and retries. - **Lost the original request.** When a thread was interrupted and then had to wait for a busy worker, only the follow-up was kept and the original request was dropped. The full request is now preserved on retry. - **Same thread seen as two.** Two spellings of the same thread id (upper/lower case) could be treated as different threads. Normalized so they count as one. ## Not in this PR - The desktop Settings toggle and rollout wiring for managed agents — https://github.com/block/buzz/pull/6909 - One pre-existing retry edge case (present today without this flag, unrelated to this change) — tracked separately so this PR stays focused. ## Testing The full `buzz-acp` test suite passes (830+ unit and integration tests), plus new focused tests for thread routing, session reuse, interrupt targeting, crash recovery, and request preservation. Behavior with the flag off is unchanged. --------- Signed-off-by: Salman Mohammed <smohammed@squareup.com> Signed-off-by: Leo <5faf251baee50ee6bcde338aef6acdd70bb3e60115664c2cd490d94a55dfc488@buzz.block.builderlab.xyz> Co-authored-by: Leo <5faf251baee50ee6bcde338aef6acdd70bb3e60115664c2cd490d94a55dfc488@buzz.block.builderlab.xyz> |
||
|
|
4952f58102 |
docs: add review-proven failure-path & async-state rules to AGENTS.md (#7061)
Mining the last 25 PRs' review threads (45 substantive findings, 11 reviewed PRs, avg **4.8 review rounds** each) shows **53% of findings are repeats** of five clusters: swallowed failures, stale-async-state races, tests that don't bind the production seam, unbounded resources/retry loops, and non-atomic multi-step persistence. PR #6956 alone burned 4 rounds converging on one of these classes. A second, independent mining pass over **71 agent-review rooms (303 findings, Aug 18–29)** confirmed the same clusters and added outcome data — how often authors actually fix each finding class once flagged: test-seam binding and unbounded-resource findings **100%**, swallowed errors **90%**, stale-state races **70%**. It also surfaced two clusters the GitHub-thread pass under-sampled: **assistive-semantics defects** (44 findings, second-largest cluster) and **input-modality divergence** (27 findings), now rules 7–8. This PR distills those clusters into eight imperative rules in AGENTS.md so agents apply them **before writing code**, adds one client-consumption invariant to ARCHITECTURE.md §5, and places the test-quality rule in TESTING.md (per the team decision that testing docs are the canonical guide for review standards), cross-referenced from AGENTS.md. Each rule cites the PRs where it was litigated. Raw mining data: `reviews.jsonl` / `comments.jsonl` + `backfill/buzz-review-findings.jsonl` (review-mining artifacts, not committed). No code changes. CLAUDE.md is a symlink to AGENTS.md and picks this up automatically. 🤖 Drafted by Jude's agent from automated mining of this repo's last 25 PRs' review threads and 71 agent-review rooms; every rule cites the PRs where it was litigated. Jude reviews and owns the result. Mining method + raw cluster data available on request. --------- Signed-off-by: Jude Edwards <judeedwards@squareup.com> |
||
|
|
b47b5a5012 |
fix(desktop): back split thread headers (#7137)
## Summary - render an auxiliary panel's requested header backdrop in docked/split mode - preserve explicit transparent-backdrop behavior - cover a populated, scrolled thread pane so timeline content cannot bleed through its header ## Root cause `RightAuxiliaryPane` correctly paints above the channel's shared header backdrop so close/edit controls remain visible. The docked `AuxiliaryPanelHeader` branch, however, ignored its `backdrop` request, leaving scrolled thread content in that higher stacking context unbacked. ## Verification - desktop unit suite: 5,801 passed - desktop TypeScript: passed - Biome checks: passed (existing unrelated repository warnings only in the earlier full run) - targeted Playwright scroll regression: passed - ultrawide thread-pane Playwright coverage: passed Signed-off-by: Wintermute <c0fc581234c3585602139eec347ced7b82af65b6f6c10728348515c0c06c51c3@buzz.block.builderlab.xyz> Co-authored-by: Wintermute <c0fc581234c3585602139eec347ced7b82af65b6f6c10728348515c0c06c51c3@buzz.block.builderlab.xyz> |
||
|
|
cb3144999b |
add public descriptions to agent personas (#7126)
**Category:** new-feature **User Impact:** People can add a short public description to an agent and see what it does directly on agent cards and profiles. **Problem:** Agent cards previously showed only a model label, so people had to open an agent and inspect its instructions to understand its purpose. Public metadata also needed one trustworthy lifecycle across local edits, relay catalogs, profiles, and portable snapshots. **Solution:** Add an optional owner-authored description with a 280-character visible-text policy, publish it as profile `about`, and prefer it on agent cards while retaining the model fallback. Description metadata is excluded from the spawn-content hash, remains definition-owned, and is validated independently at every untrusted or persistence boundary. <details> <summary>File changes</summary> **desktop/src-tauri/src/commands/agent_config_tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/commands/agent_discovery/relay_directory.rs** Updates relay-directory profile test publication for the expanded profile contract. **desktop/src-tauri/src/commands/agent_models_tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/commands/agent_models_update.rs** Preserves the effective `about` value when instance edits republish a complete profile event. **desktop/src-tauri/src/commands/agents.rs** Carries the effective authored description into initial managed-agent profile publication. **desktop/src-tauri/src/commands/agents_profile.rs** Adds `about` to profile reconciliation and keeps description, name, and avatar synchronized against relay state. **desktop/src-tauri/src/commands/agents_tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/commands/personas/card.rs** Materializes the definition-owned description before minting a portable agent card snapshot. **desktop/src-tauri/src/commands/personas/create.rs** Normalizes and validates raw authored descriptions before persona persistence. **desktop/src-tauri/src/commands/personas/delete_cascade_tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/commands/personas/inbound.rs** Validates descriptions at inbound relay ingress and applies accepted values to local definitions. **desktop/src-tauri/src/commands/personas/inbound/catalog_reconcile_tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/commands/personas/inbound/inbound_tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/commands/personas/mod.rs** Centralizes raw-byte validation followed by trim/empty normalization for description writes. **desktop/src-tauri/src/commands/personas/pending.rs** Revalidates descriptions before preparing public persona publications. **desktop/src-tauri/src/commands/personas/sharing.rs** Carries the optional public description through this managed-agent compatibility path. **desktop/src-tauri/src/commands/personas/snapshot.rs** Materializes definition-owned descriptions into portable instance snapshots without creating a second persisted authority. **desktop/src-tauri/src/commands/personas/snapshot/fidelity_tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/commands/personas/snapshot/import.rs** Restores snapshot descriptions onto imported definitions while keeping linked instance copies absent. **desktop/src-tauri/src/commands/personas/snapshot/tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/commands/personas/update.rs** Persists persona description edits, republishes linked profiles, and preserves legacy avatars during complete kind:0 replacements. **desktop/src-tauri/src/commands/personas/update/name_propagation_tests.rs** Proves description-only profile sync does not write instance state or clear a legacy avatar. **desktop/src-tauri/src/commands/team_snapshot.rs** Round-trips member descriptions through team snapshots and imported definitions. **desktop/src-tauri/src/commands/team_snapshot/tests.rs** Covers team member description export and import fidelity. **desktop/src-tauri/src/commands/teams/adopt/apply.rs** Starts adopted team catalog members without synthesizing an unauthored description. **desktop/src-tauri/src/commands/teams/adopt/tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/commands/teams/pending/tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/commands/teams/sharing/tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/egress_guard_tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/event_sync_team_catalog_tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/managed_agents/agent_description.rs** Defines the canonical Rust description resolution used by profile publication and reconciliation. **desktop/src-tauri/src/managed_agents/agent_events.rs** Updates managed-agent record construction for the optional public description field. **desktop/src-tauri/src/managed_agents/agent_snapshot.rs** Includes descriptions as snapshot profile `about` metadata and validates them at decode ingress. **desktop/src-tauri/src/managed_agents/agent_snapshot_envelope.rs** Updates managed-agent record construction for the optional public description field. **desktop/src-tauri/src/managed_agents/agent_snapshot_tests.rs** Covers snapshot description export and rejection of unsafe or overlong imported metadata. **desktop/src-tauri/src/managed_agents/config_bridge/reader_tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/managed_agents/definition_validation.rs** Adds the shared 280-character visible-text policy for public descriptions. **desktop/src-tauri/src/managed_agents/discovery/tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/managed_agents/effective_config/tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/managed_agents/global_config/tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/managed_agents/mod.rs** Exports the description resolution and validation helpers to managed-agent consumers. **desktop/src-tauri/src/managed_agents/nest/render_tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/managed_agents/parallelism.rs** Updates managed-agent fixtures for the optional description field without changing runtime configuration behavior. **desktop/src-tauri/src/managed_agents/persona_events.rs** Adds description to persona event content while deliberately excluding it from the spawn-relevant content hash. **desktop/src-tauri/src/managed_agents/persona_events/tests.rs** Pins description event round-tripping and proves description-only edits do not change the restart hash. **desktop/src-tauri/src/managed_agents/personas.rs** Initializes built-in persona records without authored descriptions for backward-compatible defaults. **desktop/src-tauri/src/managed_agents/personas/tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/managed_agents/readiness.rs** Updates managed-agent fixtures for the optional description field without changing runtime configuration behavior. **desktop/src-tauri/src/managed_agents/restore.rs** Includes the effective description in launch-time profile reconciliation. **desktop/src-tauri/src/managed_agents/runtime/test_fixtures.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/managed_agents/runtime/tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/managed_agents/spawn_snapshot/tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/managed_agents/team_catalog/tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/managed_agents/team_snapshot.rs** Updates managed-agent record construction for the optional public description field. **desktop/src-tauri/src/managed_agents/teams_tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/managed_agents/types.rs** Adds optional description metadata to persona and managed-agent records and their compatibility projections. **desktop/src-tauri/src/managed_agents/types/requests.rs** Accepts optional descriptions on persona create and update IPC requests. **desktop/src-tauri/src/managed_agents/types/tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/migration_avatar_tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src-tauri/src/persona_catalog.rs** Parses and validates descriptions at the untrusted community-catalog boundary. **desktop/src-tauri/src/persona_catalog_tests.rs** Covers valid catalog descriptions plus rejection of malformed, invisible, and overlong values. **desktop/src-tauri/src/relay.rs** Publishes and queries kind:0 `about` so relay profiles preserve authored descriptions. **desktop/src-tauri/src/relay/tests.rs** Updates managed-agent/persona fixtures for the optional description field while preserving the behavior under test. **desktop/src/features/agents/AGENTS.md** Documents description ownership, validation, snapshot, hashing, and display invariants for future changes. **desktop/src/features/agents/lib/agentDescription.test.mjs** Pins Unicode counting, paste clamping, trimming, and empty authored-description behavior. **desktop/src/features/agents/lib/agentDescription.ts** Provides shared display resolution, Unicode-scalar counting, and paste clamping for descriptions. **desktop/src/features/agents/lib/personaCatalogRelay.ts** Maps validated catalog descriptions into catalog persona projections. **desktop/src/features/agents/ui/AgentDefinitionDialog.tsx** Adds the description draft to create and edit submission while extracting identity fields from the large dialog. **desktop/src/features/agents/ui/AgentDescriptionField.tsx** Renders the public description input, helper copy, and Unicode-aware near-limit counter. **desktop/src/features/agents/ui/AgentIdentityCard.tsx** Generalizes the card second line to show a two-line description or the existing model fallback. **desktop/src/features/agents/ui/UnifiedAgentsSection.tsx** Prefers authored descriptions on persona cards and retains model labels when no description exists. **desktop/src/features/agents/ui/personaDialogState.test.mjs** Verifies edit and duplicate drafts preserve authored descriptions. **desktop/src/features/agents/ui/personaDialogState.ts** Seeds authored descriptions into edit and duplicate dialog drafts. **desktop/src/features/agents/ui/usePersonaActions.ts** Preserves descriptions when copying catalog personas into local definitions. **desktop/src/shared/api/personaTypes.ts** Defines description-bearing persona wire types in a focused module split from the size-constrained API type file. **desktop/src/shared/api/tauriPersonas.test.mjs** Verifies raw persona descriptions map into the frontend model and absent values become null. **desktop/src/shared/api/tauriPersonas.ts** Maps description fields across Tauri and preserves raw authored bytes for authoritative Rust validation. **desktop/src/shared/api/types.ts** Re-exports the extracted persona types without changing consumer import paths. **desktop/src/testing/e2eBridge.ts** Extends mock persona create, update, publication, and catalog parsing with production-shaped description behavior. **desktop/tests/e2e/agents.spec.ts** Verifies an edited description persists and appears on the agent card. </details> ### Reproduction Steps 1. Open **Agents**, edit a custom or built-in agent, and enter a sentence in **Description**. 2. Save the agent and confirm the sentence appears as the second line on its card. 3. Reopen the agent and confirm the authored description is restored; clear it and confirm the card returns to the model label. 4. Paste more than 280 Unicode characters and confirm the field keeps the first 280 characters and shows the near-limit counter. 5. Share or export/import the agent and confirm the description survives in the catalog/profile or snapshot without showing a restart-required badge for a description-only edit. ### Screenshots / Demo The focused Playwright flow `built-in persona edits persist` exercises the edited dialog, persisted value, and resulting card subtitle. Screenshots can be added after review if the field placement or two-line card treatment needs visual iteration. ### Verification - `cargo test --manifest-path desktop/src-tauri/Cargo.toml --lib` — 3,029 passed - `cd desktop && pnpm test` — 5,805 passed - `cd desktop && pnpm exec tsc --noEmit` - Focused Playwright: `built-in persona edits persist` — passed - Pre-push desktop, Tauri, typecheck, test, file-size, and branch-skew gates — passed --------- Signed-off-by: tulsi <tulsi@block.xyz> |
||
|
|
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> |
||
|
|
2f3dd850db |
fix(relay): reject a frame on its own acknowledgement channel (#6961)
Pinky, an AI agent, updated this description on Wes's behalf after
taking over the startup investigation.
**Category:** fix
**User Impact:** An EVENT refused by WebSocket admission or handler
saturation receives a correlated `OK(event_id, false, reason)` instead
of an uncorrelated NOTICE, so the client can settle that refusal without
waiting for its publish timeout. Rate-limited refusals also arm client
backoff. This fixes a protocol failure mechanism; it does not establish
that every startup send will succeed or that the reported Desktop
startup incident is fully resolved.
**Problem:** Startup opens several live subscriptions and publishes at
once, and the relay's WebSocket admission gate is a fixed 5-second
window (`ws_admission_budget` = `human_ws_events_per_sec * 5`). If that
shared per-principal quota is exhausted, `enforce_ws_admission`
previously rejected an EVENT with a bare `["NOTICE", reason]`. Quota
pressure is a possible trigger, not proof of the original incident's
complete cause.
A NOTICE carries no event id. Both clients settle a pending publish
*only* from an `OK` keyed by event id (desktop `pendingEvents`, mobile
`_pendingEvents`), so nothing settled — and `handle_text_message`
returns early, so no `OK` ever followed either. The send **could not
fail**; it could only time out at `PUBLISH_TIMEOUT_MS` = 25s. That
explains how this rejection mechanism can produce a roughly 25-second
timeout; attributing the original report to it still requires the actual
startup/send workflow.
The handler-semaphore saturation path had the identical defect, and that
one needs no quota burst to fire.
**Solution:** NIP-01 gives each request type its own acknowledgement
channel, and a rejection is only actionable on the same one. Reject a
REQ with `CLOSED`, an EVENT with `OK(id, false, reason)`, and fall back
to `NOTICE` only where no per-request correlation exists. COUNT refusals
now also use `CLOSED(query_id, reason)` per NIP-45, covering both quota
admission and handler saturation (added in
`cd12c93804b87a24b61075dfd171dc471a0a527f`).
Reason strings are unchanged, so the `rate-limited:` prefix and `retry
in {N}s` hint that existing client gates parse keep working (desktop
`parseRateLimitHint`, mobile `RelayRateLimitGate`, buzz-acp
`set_rate_limit_gate`). Only the frame *type* changes, so
`docs/multi-tenant-relay.md` L7 stays satisfied.
Two notes on how this landed, both worth a reviewer's attention:
1. **A survived mutation became a design change.**
`send_admission_result` originally took a `RejectionTarget` parameter,
and reverting the *second* call site (the per-minute message quota)
survived the whole suite — with Redis unreachable the first quota check
short-circuits, so that line is unreachable in test. Rather than test
around it, the parameter is gone: the target is derived from the frame,
so no call site can name the wrong channel.
2. **The relay fix would have caused a client regression on its own.**
Gate arming lived only in the NOTICE branch. Once rejections arrive as
`OK:false`, `handleOk` failed the send without ever backing off — the
client would retry straight into the same quota. Desktop and Mobile now
arm on a `rate-limited:` OK rejection. ACP was subsequently fixed in
`3b06dd32493596ec650f20abf8805791c50fdc24`: it arms the gate and
re-parks only the refused observer frame, preserving other in-flight
frames. Desktop gets `activateRateLimitIfSignalled` as the single owner
of that prefix test, called from both `handleOk` and the NOTICE branch.
<details>
<summary>File changes</summary>
**crates/buzz-relay/src/rejection.rs** (new)
Owns the admission-rejection concern: `RejectionTarget`,
`rejection_target_for`, `request_rejection_message`,
`send_admission_result`, and `enforce_ws_admission`, moved out of
`connection.rs`. Six tests, two of which drive the real
`enforce_ws_admission` against a real `AppState`.
**crates/buzz-relay/src/connection.rs**
Fix the EVENT handler-semaphore rejection to correlate to the event id;
delegate admission to the new module. Add two tests that drive the real
`handle_text_message` with every handler permit held. Down from 1319 to
1116 lines.
**crates/buzz-relay/src/state.rs**
Widen the existing `test_state` helper to `pub(crate)` so the rejection
tests reuse it rather than adding a ninth copy of `AppState`
construction.
**desktop/src/shared/api/relayRateLimitGate.ts**
Add `activateRateLimitIfSignalled` — one owner for the `rate-limited:`
prefix test, since three inbound frame types now carry it.
**desktop/src/shared/api/relayClientSession.ts**
Arm the gate on a rate-limited OK rejection; route the NOTICE branch
through the same helper. Net zero lines, which keeps this
already-oversized file within the differential ratchet.
**desktop/src/shared/api/relayClientPublishRejection.test.mjs** (new)
Four tests against the real `RelayClient`: a rate-limited OK settles the
pending publish and arms the gate; an ordinary rejection does not arm
it; an accepted OK still resolves.
**mobile/lib/shared/relay/relay_session.dart**
Arm the gate in `_handleOk` for a rate-limited rejection.
**mobile/test/shared/relay/relay_session_test.dart**
Two tests driving the real `publish` + `debugHandleMessage` path.
</details>
<details>
<summary>Validation</summary>
**Mutation-tested — 5 mutations, all now killed.** Each production call
site was reverted to the defective behaviour to confirm a test fails.
This caught two false-negative tests:
| # | Mutation | Result |
|---|----------|--------|
| 1 | `rejection_target_for`: EVENT → `Connection` | 4 tests fail |
| 2 | EVENT handler-semaphore call site → bare NOTICE | **survived at
first** |
| 3 | per-minute quota call site → `Connection` | **survived**; fixed by
removing the parameter |
| 4 | desktop `handleOk` gate arming removed | 1 test fails |
| 5 | mobile `_handleOk` gate arming removed | 1 test fails |
Mutation 2 is the lesson: my first saturation test called
`request_rejection_message` directly, so reverting the real call site
inside the `match` arm left it green. It now drives
`handle_text_message` itself and dies on that mutation.
- `cargo test -p buzz-relay` — 928 passed, 1 failed:
`api::mesh_demo::tests::demo_join_forwarded_arm_round_trips_echo`,
**pre-existing**, reproduced with all changes stashed at `4dd4d73de`.
- `cd desktop && npm test` — 5721 passed, 0 failed (full suite).
- `cd mobile && flutter test` — 1876 passed, 0 failed (full suite).
- `just fmt-check`, `just clippy`, `just desktop-check`, `just
mobile-check`, `just file-size-check` — clean. Desktop's 5 biome
warnings are pre-existing (reproduced with changes stashed).
- All 9 pre-push lanes green, including `rust-tests` and
`desktop-tauri-checks`.
**Not verified:** not reproduced end-to-end against a live relay under a
forced quota burst. The causal chain is source-proven and
mutation-proven at the frame level; the ~25s attribution follows from
`PUBLISH_TIMEOUT_MS` but is not directly measured. A packaged-build
click-through would close that gap.
</details>
Related work: #6957 bounds Desktop HTTP event submission, but safe
retained-operation recovery after exhausted/ambiguous outcomes remains
unfinished. #6998 is the separately reviewable Desktop
readiness/duplicate-subscription slice. Neither is claimed to complete
native before/after startup-send validation.
Diagnosis note: `RESEARCH/DESKTOP_STARTUP_SEND_STALL_2026_08_27.md`
(Brain's workspace).
## Current review disposition (2026-08-28)
The [review on
`cd12c938`](https://github.com/block/buzz/pull/6961#pullrequestreview-5052902510)
identified ACP's missing rate-limited-OK handling. Commit
`3b06dd32493596ec650f20abf8805791c50fdc24` fixes gate arming, re-parking
the specifically refused observer frame, and the stale NOTICE comment.
Two regressions drive the real frame dispatcher. See [the implementation
and validation
response](https://github.com/block/buzz/pull/6961#issuecomment-5455032054).
The Mobile generation-check inline thread is resolved: its `async
publish` returns a failed Future when superseded; it does not throw
synchronously at invocation. No further production change was indicated
by that comment.
The validation counts above describe the original slice, not a new
rerun. At `3b06dd324`, the current GitHub check rollup has successful
completed test/build checks (non-applicable jobs skipped). The
security-review comment still requires review for the current base/head
range; do not read a green authorization job as a completed security
review. Approval and merge remain human decisions.
---------
Signed-off-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz>
Co-authored-by: Carl <9d00794d3df50972eb8b615511783cab12a77a8fd5dd5edd58073ec73b54bd8b@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>
|
||
|
|
bc006f6708 |
feat: render agent avatars as squircles (#7106)
## Summary - render every agent/AI identity as a 30% squircle across desktop and mobile while keeping human avatars circular - propagate agent identity through message, thread, profile, reaction, member, DM, search, workflow, project, huddle, forum, pulse, and agent-management surfaces - preserve squircle geometry for fallbacks, focus/status treatments, add-agent controls, and overlapping avatar outlines (`calc(30% + 2px)` for the outer background) ### Related issue None found. This change was requested and visually reviewed in the originating Buzz thread. ### Testing - `just desktop-test` — 5,799 passed - `just mobile-test` — 2,008 passed - pre-push gates passed at `0d59d77b120dcb90aac2f918e422c11c9fa5353b`: desktop check, TypeScript typecheck, desktop full test suite, mobile format/analyze and full test suite, Rust tests, Tauri checks, and differential file-size gate - deterministic desktop visual sweep covered channel messages/thread summaries; thread, subthread, and sub-subthread depths; reactions and reactor popovers; hover/full profiles; added-to-channel activity; channel members/settings; agent library/team overlaps; agent creation; mention autocomplete; and DM header/sidebar/settings ### UI evidence The complete labeled visual matrix is available in the originating Buzz review thread. GitHub-hosted copies will be added in a follow-up PR comment using the repository screenshot script. --------- Signed-off-by: Fizz <dae5f6af70b8695a8b83c8deae555f63be41630ec2b8cd493e41a439c9527dd8@buzz.block.builderlab.xyz> Co-authored-by: Fizz <dae5f6af70b8695a8b83c8deae555f63be41630ec2b8cd493e41a439c9527dd8@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>
|
||
|
|
f463e726dd |
fix: retrieving cold memories; add regression task (#6950)
## Why Evaluating buzz agent memory retrieval by seeding a memory then asking the buzz agent a question it needs that memory. **Bug Found**: System prompt had no inclusion of retrieving cold memories and suggested looking in a mem/*.md directory that does not exist. Updated `system-prompt.md` to include memory CLI tools and usage. Eval Before System Prompt Change: 0/3 Eval After System Prompt Change: 3/3 ## What - Add a `memory-retrieval` benchmark that seeds agent memory with `buzz mem set` before asking a direct question. - Grade the observable threaded answer without inspecting tool calls or exposing the answer in channel history. - Teach agents to use `buzz mem set`, `buzz mem ls`, and `buzz mem get` for cold memory. - Add a wire-debug endpoint configuration for diagnosing ACP tool calls in local runs. - Add fixture, seeding, verifier, and prompt coverage. ## Risk Assessment Low. The runtime changes are limited to the benchmark harness. The production-facing change clarifies existing memory commands in the base prompt; it does not change memory storage, relay behavior, or authorization. ## References - Before the system-prompt changes, 0/3 attempts passed because agents never invoked the `buzz mem` CLI and instead searched a non existent filesystem - After the changes, 3/3 attempts passed. ACP wire logs confirmed that every agent ran `buzz mem ls` followed by `buzz mem get` and returned `net_gpv`. --------- Signed-off-by: Philip Azar <pazar@squareup.com> |
||
|
|
e47690c21b |
Enforce NIP-OA authorization time bounds (#7004)
NIP-OA relay admission now evaluates signed `created_at<` and `created_at>` conditions against the already verified authentication event timestamp. An owner-signed credential such as `created_at<1` no longer upgrades its holder to relay membership. The verified timestamp now flows from NIP-42, NIP-98, and Blossom authentication through the shared membership gate used by WebSocket, HTTP, Git, media, huddles, GIF, and workflow requests. Owner-attested access fails closed when no signed authentication timestamp is available. Direct relay members keep their existing admission behavior, and `kind=` remains connection-level metadata as specified by NIP-AA. Tests cover strict time-bound evaluation, missing timestamp rejection, and HTTP authentication timestamp propagation. Testing: - `cargo test -p buzz-sdk nip_oa::tests -- --nocapture` - `cargo test -p buzz-relay api::relay_members::tests -- --nocapture` - `cargo test -p buzz-relay api::bridge::tests -- --nocapture` - `just ci` --------- Signed-off-by: Jordan Mecom <jm@squareup.com> |
||
|
|
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> |
||
|
|
c3132c3ee9 |
feat(desktop): use segmented controls for channel creation (#6845)
## Summary - replace the two-option Type and Visibility dropdowns in the Create channel dialog with single-click segmented controls - keep Expires after as a dropdown and preserve the existing dropdown controls in edit and management dialogs - update channel creation end-to-end coverage for the direct controls ## Before Default Ongoing/Public state:  Type dropdown open, showing the extra selection click:  ## After Default Ongoing/Public state:  Temporary/Private state with the Expires after row visible:  ## Verification - Biome and TypeScript checks pass - 5,508 desktop unit tests pass - 88 channel smoke tests pass - source guards pass --------- Signed-off-by: Matt Toohey <contact@matttoohey.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
eed74bde2f |
feat(buzz-agent): surface stop reason and silent-turn WARN in telemetry (#7038)
## What Two telemetry additions to make a known silent-death failure mode visible in harness logs. **1. `stop` field on `llm: call completed` INFO** (`crates/buzz-agent/src/llm.rs`) The `ProviderStop` value was already parsed and stored on `LlmResponse` but never emitted in the log line. Without it, "model chose `end_turn`" vs "gateway truncated/refused" is indistinguishable from telemetry alone. **2. WARN on silent-turn signature** (`crates/buzz-agent/src/agent.rs`) Emits a `WARN` when a turn produces no publish, no visible assistant text, and either near-zero or absent output tokens. The WARN logic is extracted into `warn_if_silent_turn` (pure synchronous function) so the seam is testable without the async run loop. Three independent gates before the WARN fires: 1. **`!buzz_reply_call_seen`** — no publish attempt in any round, tracked unconditionally via the existing `is_buzz_reply_call` matcher. Read-only tool calls do NOT suppress the WARN; a turn that ran tools but never published and died at 3 tokens is still a silent death. 2. **`text_is_empty`** — no visible assistant text in the final round. A terse reply like "OK" (≤12 tokens, non-empty) is not a silent death. 3. **Token check** — two distinct WARN messages: - `Some(t) where t <= 12`: near-zero token count, the observed failure signature (2–12 tokens) - `None` usage: provider omitted token counts entirely, separately diagnostic Tests use a scoped `tracing_subscriber` layer (same pattern as the existing stall-warn tests in `llm.rs`) to exercise the WARN seam directly: - Canonical signature (no publish, no text, 4 tokens) → 1 WARN - Non-empty assistant text → 0 WARNs - Publish seen → 0 WARNs - `None` usage (no publish, no text) → 1 WARN ## Why Recurring silent-death incident in a specific agent×channel combination: sessions die with 1 LLM call, 2–12 output tokens, no tool calls, no message, no error — recorded as a "successful" turn. The harness log shows the token count but not the `stop_reason`, leaving the root cause undiagnosable without request-level tracing. The observed shape also includes tool-step-then-3-token-death (one tool call, then silence) — the publish-aware gate catches both shapes. Context thread: buzz://message?channel=91fd9ca1-cf04-4ef7-b18f-aa2aee55692b&id=e3f1693f2e29f26a0c840f8054d592270c1504beacdc1d9c2063d8ab82960a06 ## Scope Logging and telemetry only. No behavior change, no retry-logic change, no stop-reason mapping change. --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> |
||
|
|
00e61eafa9 |
fix(desktop): surface channel history load failures (#7013)
## Summary A failed initial channel-history request no longer appears as an authoritative empty channel. The timeline now shows an announced error with a Retry action, while cached messages remain visible when a later refresh fails; successful empty channels continue to use their normal intro state. ### Related issue None found. ### Testing - Full desktop unit suite (`pnpm test`) - Desktop TypeScript check (`pnpm exec tsc --noEmit`) - Biome checks for changed files - Repository file-size ratchet - Full pre-push desktop checks and tests - Desktop app launched successfully against local Postgres and Redis for manual testing No screenshot is included because the new UI is only shown after a terminal relay-history failure; the regression test pins the error/empty/list precedence directly. --------- Signed-off-by: Thomas Petersen <thomasp@squareup.com> |
||
|
|
8dbc65d9e2 |
fix(composer): polish automatic mentions (#6956)
**Category:** improvement **User Impact:** Automatic mentions are easier to turn off and now behave consistently across conversations, settings, drafts, and repeated agent mentions. **Problem:** People found the new automatic mention behavior hard to control: turning it off in Settings did not reliably affect the composer, removing a mention could require also disabling the feature, and root/thread composers could inherit or restore surprising state. Other reported rough edges included only one of several mentioned agents becoming automatic, synthetic mentions leaking into drafts, restored mentions corrupting adjacent text, controls remaining visible in archived channels, and unclear picker feedback. See the [original feedback thread](buzz://message?channel=e62570dd-33ad-42c5-b92b-75f2689f9694&id=c949ec399274fbb0d6633da3f95712e843a67dcada214f63d72f6975b406604b). **Solution:** Polish the existing feature around the problems people encountered, keeping automatic mentions controllable and scoped to the active conversation. | Reported issue | UX fix | | --- | --- | | Turning automatic mentions off in Settings did not reliably update the composer. | The global setting and composer control stay synchronized, and disabling the feature does not clear typed text. | | Removing an automatic mention could require both deleting the mention and turning off the feature. | Removing or unchecking an agent excludes that agent for the current conversation, while explicitly re-adding the agent can restore automatic mention behavior. | | Root and thread composers could share or restore surprising selections. | Each root or thread composer keeps its own automatic audience and restores it when the user returns. A request to enable automatic mentions only in agent threads was considered; this PR keeps them available at the channel root but prevents state from leaking between the two. | | Mentioning multiple agents could leave only one saved as automatic. | Multi-agent selections remain represented in the automatic audience and restored mention chips. | | Automatic mention prefixes could be saved as if the user typed them. | Synthetic prefixes stay out of persisted drafts while authored text is preserved. | | Restored mentions could lose their separator and corrupt continued typing. | Restored multi-word mentions retain their trailing space and place the caret after it. | | Archived channels showed automatic-mention state beside a disabled composer. | Disabled composers hide automatic-mention controls while preserving the draft and restoring state when re-enabled. | | Confirmation and picker behavior made the feature feel difficult to inspect or adjust. | Confirmations dismiss with removed agents, remain open while hovered, and expose the setting before it changes; pin icons, contrast, scope copy, animation, and keyboard toggling are also clarified. | | Agent suggestions and membership state could shift during directory refreshes. | Suggestions and membership labels stay stable during refreshes, while send-time authorization still revalidates access. | ## Changes <details> <summary>File changes</summary> **desktop/src/features/agents/lib/agentAutocompleteEligibility.test.mjs** Adds coverage for the channel-roster eligibility rules used by agent mention autocomplete. **desktop/src/features/agents/lib/agentAutocompleteEligibility.ts** Aligns agent autocomplete eligibility with channel membership so available agents and their labels stay trustworthy. **desktop/src/features/channels/ui/MembersSidebar.tsx** Uses the shared member-pubkey logic when presenting and acting on channel members. **desktop/src/features/messages/lib/autoPinMentionedAgentsPreference.test.mjs** Covers preference changes that must remain stable while composer controls are toggled. **desktop/src/features/messages/lib/autoPinMentionedAgentsPreference.ts** Keeps the automatic-mention preference as durable user intent rather than transient composer state. **desktop/src/features/messages/lib/mentionMemberPubkeys.ts** Centralizes which member identities count as mentionable in the current channel. **desktop/src/features/messages/lib/persistentAgentAudience.test.mjs** Expands lifecycle coverage for persistent agent audiences, explicit exclusions, and restored mentions. **desktop/src/features/messages/lib/persistentAgentAudience.ts** Models automatic, explicit, and excluded agent audiences separately so user choices survive updates without leaking across composers. **desktop/src/features/messages/lib/stripImplicitAgentMentions.test.mjs** Verifies implicit automatic mentions are removed without damaging surrounding separators or authored content. **desktop/src/features/messages/lib/stripImplicitAgentMentions.ts** Strips presentation-only automatic mentions before draft persistence while preserving whitespace and authored text. **desktop/src/features/messages/lib/useMentions.ts** Routes mention insertion and removal through the composer-local audience lifecycle. **desktop/src/features/messages/lib/useRichTextEditor.ts** Preserves mention-chip structure and caret placement when automatic mentions are restored. **desktop/src/features/messages/ui/ComposerAddressControls.test.mjs** Updates control-state expectations for disabled automatic mentions and restored pin affordances. **desktop/src/features/messages/ui/ComposerAddressControls.tsx** Makes automatic-mention state, disabled presentation, and pin controls visually explicit. **desktop/src/features/messages/ui/MentionAutocomplete.test.mjs** Adds coverage for roster labels, pin state, and picker behavior after mention selection. **desktop/src/features/messages/ui/MentionAutocomplete.tsx** Keeps the shortcut picker open for repeated selection and restores visible automatic-mention pin indicators. **desktop/src/features/messages/ui/MessageComposer.tsx** Scopes automatic mention state to each root or thread composer and coordinates restoration, draft persistence, and sending. **desktop/src/features/messages/ui/MessageComposerToolbar.tsx** Passes the effective automatic-mention state into the toolbar presentation. **desktop/src/features/messages/ui/composerAgentKeyboard.test.mjs** Updates keyboard interaction coverage for toggling agents in place. **desktop/src/features/messages/ui/useAddressedAgentMentionRestore.ts** Restores automatic mention chips after lifecycle changes without moving or duplicating authored content. **desktop/src/features/messages/ui/useAgentAddressLockPicker.test.mjs** Substantially expands coverage for toggles, exclusions, synchronization, and picker dismissal rules. **desktop/src/features/messages/ui/useAgentAddressLockPicker.ts** Keeps the picker usable across repeated choices and preserves explicit per-agent intent while settings change. **desktop/src/features/messages/ui/useAlwaysAddressShortcut.ts** Makes the keyboard shortcut toggle the highlighted automatic audience choice without replacing unrelated selections. **desktop/src/features/messages/ui/useAutoPinMentionedAgents.ts** Owns composer-local automatic-mention lifecycle behavior, including restoration, exclusions, deletion, and disabled-state handling. **desktop/src/features/messages/ui/useComposerMentionPicker.test.mjs** Adds focused picker lifecycle coverage for selection, hover, and dismissal behavior. **desktop/src/features/messages/ui/useComposerMentionPicker.ts** Prevents premature picker dismissal while the user is interacting with its controls. **desktop/src/features/messages/ui/useDraftPersistSnapshot.ts** Persists only user-authored draft content rather than implicit automatic mention decorations. **desktop/src/shared/lib/keyboard-shortcuts.ts** Updates the automatic-mention shortcut description to match its toggle behavior. **desktop/src/testing/e2eBridge.ts** Extends the desktop test bridge with the state needed to exercise roster and automatic-mention transitions. **desktop/tests/e2e/mentions.spec.ts** Covers roster-based labels, managed-agent invitation, revocation, and recovery behavior in the complete mention flow. **desktop/tests/e2e/persistent-agent-audience.spec.ts** Adds end-to-end coverage for root/thread isolation, preference synchronization, manual exclusions, draft hygiene, restored chips, separators, hover behavior, and disabled presentation. </details> ## Reproduction Steps 1. Open a channel with at least two available agents and enable automatic mentions from the composer mention control. 2. Select multiple agents, remove or uncheck one, and confirm subsequent composer updates keep that agent excluded while the others remain automatic. 3. Open a thread, choose a different automatic audience there, and switch between the thread and root composer; confirm each composer retains only its own choices. 4. Disable automatic mentions and confirm the draft text remains unchanged while automatic chips and controls show the disabled state; re-enable the setting and confirm eligible automatic chips return. 5. Delete an automatic mention chip, then explicitly add the agent again; confirm it immediately returns as an automatic mention without disturbing spaces or the caret, including for a multi-word name. 6. Reload with a saved draft and confirm implicit automatic mentions were not persisted as authored draft text. 7. Use the automatic-mention keyboard shortcut and picker repeatedly; confirm the picker remains open for additional choices and the highlighted agent toggles in place. ## Validation Validated at `34d208b47d64a9816f88e10a46bcfd479e917d75` after rebasing onto `origin/main` (`69096c9a8`): - Desktop unit tests: 5,731 passed, 0 failed. - Desktop TypeScript typecheck: passed. - Desktop E2E build: passed; emitted only existing chunk and dynamic-import warnings. - `pnpm check`: exited successfully; 4 warnings and 5 informational findings are in unrelated files introduced by current main. ## Screenshots/Demos The behavioral changes are covered by the focused desktop E2E scenarios above. Screenshots can be attached from the screenshot-producing automatic-mention E2E after the PR is created. --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz> Co-authored-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz> Co-authored-by: morgmart <98432065+morgmart@users.noreply.github.com>mobile-v0.16.0-rc.1 |
||
|
|
2c99ee7af5 |
fix(desktop): resolve bundled sidecar on cheap path and bound login-shell spawns (#6904)
## Why PR #6330 split agent harness/runtime detection into a cheap (cache-only) path and a forced (spawning) path. Two regressions followed, both surfacing as every harness showing "(not installed)" / "CLI missing" across the agent create/edit picker, Agents > Agent defaults, and Settings > Agents — blocking agent create/edit until the user clicked Install in Settings > Agents. ## Root cause One underlying bug, two victims: - **Boot false-negative.** The resolve cache is in-memory, so it starts cold on every launch. `resolve_command_cached` (the cheap path) consulted only the Buzz-managed shim dirs plus that cold cache, and `buzz_managed_command_path`'s allowlist structurally excludes `buzz-agent`. The bundled sidecar could therefore never resolve on the cheap path until a forced pass warmed the cache, so cheap-path surfaces rendered all-missing at boot. App setup never warms the cache. - **"Check again" hang.** `run_in_login_shell` used an untimeouted `Command::output()`; a wedged login shell froze the whole forced pipeline, leaving "Check again" spinning forever. ## What - `resolve_command_cached` now also calls `resolve_workspace_command`, resolving the bundled sidecar via a filesystem stat (no spawn) — the same class of work the managed-shim check already performs. `buzz-agent` can no longer report missing, even inside the boot warm window. - New `discovery/bounded_command.rs` runs any discovery child under a hard wall-clock deadline, polling with `try_wait` rather than blocking on `wait()`. Stdout and stderr are piped to two drain threads whose buffers share an aggregate `CAPTURE_LIMIT`; a breach fails closed (kill the tree, return `None`), so a noisy or hostile probe can force neither unbounded memory nor disk fill. Tree teardown runs on every exit path — timeout, error, cap breach, *and* success — because a login-shell rc file or auth CLI can legitimately background a descendant that would otherwise outlive discovery. Ownership is deliberately asymmetric: - **Unix:** the child leads its own process group (`process_group(0)`); teardown is `SIGTERM` → bounded grace → `SIGKILL` on the group. A descendant that leaves the group (`setsid`/`setpgid`) while holding a pipe is not owned and may survive one probe, but can never hang or unbound the helper: the Unix drains read nonblocking and end on `WouldBlock` once teardown sets the stop flag, so the join returns promptly without waiting on an escaped writer's EOF. - **Windows:** the child is spawned `CREATE_SUSPENDED`, assigned to a kill-on-close Job Object while frozen, then resumed. The job owns the root before any descendant can exist and is created without breakaway, so no writer can escape — a hard whole-tree guarantee, and closing the job reaps the tree even after the root has exited. Any failure to create, assign, or resume is fail-closed: the child is terminated and reaped and the spawn returns `None` (discovery treats it as command-not-found) rather than running unowned. - Each login-shell candidate is bounded by a 10s timeout via that helper, falling through to the next candidate on timeout instead of aborting the resolve. The login-shell path cache is generation-aware: a probe that loses to a concurrent refresh or lands mid-refresh returns the authoritative cached value (or re-probes under the new generation) rather than its own rejected local result, so a losing thread can never settle the UI with a PATH-missing catalog while the cache holds a fresh success. - Warm the ACP runtime catalog once at `AppShell` mount and gate the cheap-path surfaces on that pass. A module-level boot-warm state (`idle` → `pending` → `settled`/`failed`, deduped per launch) lets `useAcpRuntimesQuery` present a cold catalog as *loading* while the first forced pass runs and as a *retryable error* (carrying the probe's real reason) if it fails, instead of blessing "every harness not installed" as authoritative. A non-empty catalog always wins, so a revalidation or later failure never blanks a good list; the gate only overlays once the warm has started, so onboarding (which renders before the warm) is unaffected. Deduping per launch also fixes the previous per-remount re-fire. ## Verification Unix teardown and the drain contract are runtime-proven by `#[ignore]`-free tests that record a backgrounded descendant's real PID and assert the helper returns promptly on both the success and timeout paths without blocking on that writer. The generation-aware login-shell cache is covered by deterministic tests through a `cfg(test)` injectable probe seam that assert the function's return value under both concurrent-refresh interleavings — the losing caller returns the peer's committed success, and a mid-probe refresh forces a re-probe to the fresh value. The Windows ownership contract has no CI lane, so `bounded_command.rs` carries two `#[ignore]`-gated tests (spawn/assign race, looped; and the timeout path) for a sanctioned run on a Windows host. The boot-warm gate is covered by unit tests for the pure overlay and the `startBootWarm` failure → retry → settle lifecycle. Origin: [Buzz thread](buzz://message?channel=5ef5d5bb-643f-4b87-bbf4-e8b64585ffeb&id=a4b1c4485de4d35cff0f914d4f4211c796f44f670e76de2ef9431f7e882c906e) Fixes #6872 Related #6662 --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> |
||
|
|
b593c7d7fe |
perf(mobile): reduce cold startup and channel rendering delays (#6996)
Pinky is opening this PR on Wes’s behalf. ## Summary Reduce two separately measured mobile delays without changing the relay API or removing rich message rendering: - Publish the finite channel-list snapshot without waiting for live subscription setup. - Batch active channel-list subscriptions into sorted, deterministic chunks of at most 128 explicit channel IDs, retaining unchanged chunks. - Install replacement chunks before retiring old coverage. Retain old chunks across thrown replacement failures; filter callbacks to the current relay/identity and still-desired channels; clean up retired/in-flight work across disconnect and disposal. - Scope the custom-emoji Markdown matcher to known shortcodes actually referenced in the rendered content, rather than embedding the whole community palette in every message’s regex. Preserve unknown literals, shared-colon token boundaries, event-tag URL priority, content edits, and code literals. - Honor explicit zero retry hints without inventing a ten-second session-wide gate, while preserving the ordinary live-subscription retry backoff and any already-active gate. ## Matched performance results Medians of three before and three after process-cold launches, alternated on the same authenticated iPhone 17 Pro / iOS 26.5 simulator. Before is mobile source at `e76c81968b65b0755b83efdd59dc3375c59ddf40`; after is this production patch before two documentation-only comment fixes. First channel-list frame: 11.617s → 3.179s · 73% lower latency Live setup duration: 8.475s → 0.185s · 98% lower latency Channel-open first message-list frame: 2.754s → 1.230s · 55% lower latency Message data ready → first frame: 1.977s → 0.286s · 86% lower latency Channel-open reveal complete: 2.845s → 1.394s · 51% lower latency Channel-open data readiness: 0.770s → 0.944s · 23% higher latency The gain is client-side orchestration/rendering, not a claim that the relay became faster. First channel-list frame ranges were 10.835–11.788s before and 2.872–3.395s after; channel-open first-frame ranges were 1.560–2.906s before and 1.149–1.317s after. ### Measurement boundaries - Debug simulator builds, CPU sampling disabled, bounded timestamp probes enabled identically. These are not release/physical-device measurements. - Startup clock starts at Dart `main`; build/install/native pre-main time is excluded. Auth/preferences and OS/disk caches are retained between new processes. - Same account scale: 113 active channels. Latest-message events varied slightly with live activity (1543–1546). - Channel-open uses the same initial 50-row history window, 97 query events, and 67 provider events. The 2306-entry emoji palette is explicitly loaded before navigation on both sides; palette preparation is excluded from the channel-open clock and happens after the startup frame measurement. - Both diagnostic builds temporarily disabled unused avatar segmentation to work around the existing Google ML Kit arm64-simulator slice limitation. The workaround, dependency/native changes, auto-navigation, and all probes are excluded from this PR. ## Validation - Full mobile package suite: `flutter test` — 1890 passed. - `just mobile-check` — 506 files unchanged; analyzer clean. - `just file-size-check` — policy tests and all client ratchets passed. - `git diff --check` — passed. - New lifecycle regressions cover front-sorting insertion across a chunk boundary while replacement readiness is paused, failure retention/departed-channel filtering, retired generation + disconnect cleanup, disposal, chunk limits, unchanged-set reuse, and scope switches. - Emoji unit/widget coverage includes a 2500-unused-emoji palette, unknown tokens, case matching at the component level, shared-colon boundaries, rich text, event URL priority, and content edits. - Fresh-frame source review traced the subscription queue/fences, callback scopes, duplicate-event paths, matcher/wiring, and retry scheduling. - At committed/pushed head `13a83b628c8411c5885e6f76a250ba87accf6067`, all normal pre-push hooks passed: `mobile-checks` (formatter, analyzer, and the full 1890-test mobile suite), `file-size-check`, `branch-skew`, and `push-head-scope`. The commit hook formatted 506 files with no changes. Runtime measurements preceded only the two documentation-comment fixes; no runtime source changed afterward. ## Limits / follow-ups - `RelaySession.subscribe` still settles under its existing EOSE/fallback/retryable-CLOSED contract. “Setup completed” is not an unconditional EOSE or live-delivery guarantee. This PR does not add status-aware replacement ownership. - The channel-message provider still awaits subscribe before fetching history; that separate serialization is not removed here. - Oversized Huddle queries and the separate history batching path above 128 active channels remain follow-ups, as do pre-existing read-state initialization/size warnings. - Palette-only widget refresh and upstream Markdown uppercase-dispatch behavior are not changed. - A clean source build still has the existing Google ML Kit arm64-simulator issue; the profiling workaround is not a proposed product fix. Originating Buzz conversation: buzz://message?channel=793b0522-7995-4375-b1a6-fd94a96fa21d&id=6ba88afdec78ab2cfb6728afcd4a6d10f29e6aa33ff0f62f45d6750381e4d789 --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Pinky <5f5ab050ec58ae208332edd544ebf705221e24c1b86d82a6ca07038a7a8f6ac9@buzz.block.builderlab.xyz> Co-authored-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz> Co-authored-by: Carl <9d00794d3df50972eb8b615511783cab12a77a8fd5dd5edd58073ec73b54bd8b@buzz.block.builderlab.xyz> |
||
|
|
c432a111ca |
feat(mobile): push notifications MVP (#6269)
This PR implements MVP, iOS-only,
[NIP-PL](
|
||
|
|
a3730784fc |
refactor(db): extract domain stores from database runtime (#6987)
## Summary Finish the remaining database-store extraction tracked by [TheSentinel454/buzz#2](https://github.com/TheSentinel454/buzz/issues/2) in one reviewable PR. This consolidates the previously stacked domain slices after #6782 merged. It preserves the runtime/store boundary established by #6660, #6668, #6700, and #6782 while separating database runtime infrastructure from domain-owned persistence: - `runtime/` owns pool construction and sizing, writer/reader routing, read sessions and route proofs, transaction infrastructure, observability primitives, replica fencing, health support, migrations, and cross-cutting runtime tests. - `store/` owns domain records, SQL, row parsing, locks and invariants, `Db` domain methods, focused tests, and logical-operation datastore spans. - `lib.rs` remains a 57-line compatibility facade that preserves existing crate-root paths and `Db` method signatures through re-exports. Domain coverage includes API tokens, authentication allowlists, reminders, event queries, threads, reactions, feeds, users and DMs, push, workflows/runs/approvals, relay membership and invites, product feedback, moderation/admin moderation, relay admin actions/operators, git repositories, archived identities, usage, partition maintenance, deletion, channel membership inherited from merged #6782, and the final runtime/store layout. The branch has been rebased onto current `main`. Database changes that landed there were incorporated rather than overwritten: `relay_admin_actions.rs` and `relay_operators.rs` now live under `store/`, their 27 public `Db` wrappers and existing behavior remain intact, and every wrapper has exactly one fixed-name datastore span. Concurrent changes to migration, moderation, admin moderation, and error handling are also retained. ### Exact base and head - Base: `main` at `ed11c8d8bf0a17402be5cf243724f89471530d2f` - Head: `codex/issue-2-store-extraction` at `be24430472d1a87ac5c0d6026c620cd6caea3537` ### Related issue - Structural tracker: [TheSentinel454/buzz#2](https://github.com/TheSentinel454/buzz/issues/2) - Domain trackers: [#6](https://github.com/TheSentinel454/buzz/issues/6), [#7](https://github.com/TheSentinel454/buzz/issues/7), [#12](https://github.com/TheSentinel454/buzz/issues/12), [#13](https://github.com/TheSentinel454/buzz/issues/13) - Acceptance trackers: [#17](https://github.com/TheSentinel454/buzz/issues/17), [#19](https://github.com/TheSentinel454/buzz/issues/19) This supersedes #6783, #6784, #6787, #6788, #6789, #6792, #6820, #6794, #6796, #6797, #6798, #6799, #6804, #6805, #6806, #6808, #6809, #6811, #6812, #6813, #6814, #6815, and #6890. Their discussions remain available for review history. ### #17 / #19 acceptance - Preserves the metric names, fixed labels, transaction/lock timing boundaries, and privacy/cardinality constraints introduced by #6700. - Keeps exactly one datastore span per public logical operation, including the 27 relay-admin wrappers added on `main`. - Removes `store_ownership.rs`; physical ownership and focused source guards now enforce the boundary directly. - Leaves no `impl Db`, domain SQL, focused domain test group, or datastore span in `lib.rs`. - Preserves existing public paths such as `buzz_db::channel`, `buzz_db::event`, and `buzz_db::workflow` through crate-root re-exports while keeping internal `runtime` and `store` namespaces private. ### Non-goals - No SQL, schema, locking, transaction, retry, timeout, or client-visible behavior changes. - No generic store traits, domain handles, broad `PgExecutor` migration, new store crate, raw pool accessor, or broader directory reorganization. - No tracker issues are closed by this PR. ### Risk The cumulative diff is large but structural. Risk is primarily module-path, ownership, or conflict-resolution drift. It is mitigated by preserving public re-exports, comparing the newly moved `main` implementations to their upstream source, source guards, touched-crate compilation, PostgreSQL-backed test coverage, and an independent exact-head review on a separate clean Blox workstation. ### Testing Author workstation `buzz-tornquist-pr-6987-rebase`, rebased branch ending at exact head `be24430472d1a87ac5c0d6026c620cd6caea3537`: - `cargo fmt --all --check` - `cargo clippy -p buzz-db -p buzz-relay --all-targets -- -D warnings` - `cargo test -p buzz-db --lib` — 113 passed, 240 PostgreSQL tests intentionally ignored - `cargo test -p buzz-db --test observability_source` — 2 passed - PostgreSQL-backed `buzz-db` coverage under native PostgreSQL — 235 passed in the shared serial run; the five shared-state/config-sensitive cases passed as isolated reruns against fresh schemas, including the two owner-limit tests with their fixture's `BUZZ_MAX_COMMUNITIES_PER_OWNER=3` - `cargo test -p buzz-relay --lib -- --test-threads=1` under native PostgreSQL/Redis — 991 passed; the three current-month partition-sensitive identity-archive cases passed after provisioning the August 2026 test partition; 87 infrastructure-marked tests remained ignored - Source/diff guards — relay-admin implementation bodies match current `main`; all 27 public wrapper signatures are retained; exactly one datastore span wraps each wrapper; `lib.rs` has zero `impl Db` blocks and zero datastore spans; no duplicate top-level relay-admin modules or `store_ownership.rs`; `error.rs` matches current `main` Independent clean review workstation `buzz-tornquist-pr-6987-review`, detached at exact head `be24430472d1a87ac5c0d6026c620cd6caea3537`: - `cargo fmt --all --check` - `cargo clippy -p buzz-db -p buzz-relay --all-targets -- -D warnings` - `cargo test -p buzz-db --lib` — 113 passed, 240 ignored - `cargo test -p buzz-db --test observability_source` — 2 passed - Exact-head ownership/re-export/instrumentation audit — no remaining actionable findings --------- Signed-off-by: OpenAI Codex <codex@openai.com> Signed-off-by: tornquist <tornquist@squareup.com> Co-authored-by: OpenAI Codex <codex@openai.com> |
||
|
|
2f66ee24a0 |
feat(desktop): add team sharing to community catalog (#3995)
Stack: [#5112](https://github.com/block/buzz/pull/5112) → this PR Stacks on the team catalog backend PR (#5112). Contains the `desktop/**` changes that turn the add-agent surface into a single Community Catalog browsing both shared agents and shared teams. ## Owner catalog sync pipeline `usePersonaSync.ts` gains a hydration pipeline in `startPersonaSync` so a fresh device that comes online after another already published the owner's 30178 catalog head reconstructs the complete state without falsely retracting it. The owner's persona/team/managed-agent/30178/deletion history is backfilled up front (a live-only subscription gets no history — reconnect-replay's since-cursor is undefined until the first live event), then a live subscription takes over. Four properties keep a fresh sync from purging the owner's valid shared head: - **Paged backfill with a safe termination guarantee.** The relay serves each REQ newest-first and clamps `limit` to its advertised `max_limit`, so a large owner's history overflows one page — a newer 30178/30176 could return in-page while an older required 30175 constituent falls beyond it. `fetchOwnerHistoryToExhaustion` pages the full window with the `until` time cursor (the only cursor the WS REQ filter exposes; the DB `before_id` keyset is REST-only), deduping the inclusive-boundary rows. A short page terminates normally. A full page whose oldest event cannot advance the time-only cursor is a **dense boundary** — more than one page of events share one `created_at` second, which the WS filter has no `(created_at, id)` cursor to escape — and raises `PersonaHistoryDenseBoundaryError` rather than silently completing as if the history were exhausted and dropping the older constituents behind it. - **Constituents-before-catalog ordering.** Over the complete batch, `orderCatalogHeadsLast` stably defers every 30178 head past its 30175/30176 constituents. Reconciling a 30178 head before its personas hydrate makes the inbound team refresh fail member resolution and queue a dominating false tombstone; deferring the heads guarantees the constituents are all applied first, while newest-wins order within every other coordinate is untouched. - **Hydration boundary for concurrent live events.** The backfill fetch and the live subscription start concurrently into one reconcile chain. A live or replayed 30178 that arrives before the backfill reconciles its constituents would reproduce the same false-tombstone purge, so live events are buffered until the ordered backfill is dispatched and then drained in arrival order. Steady-state live events (after hydration completes) reconcile immediately. - **Explicit backfill failure policy.** A transient history-fetch rejection is retried with bounded backoff. When backfill cannot complete — retries exhausted, or a deterministic dense boundary — the pipeline enters a **degraded-live** state rather than leaving the subscription permanently inert with live events accumulating in the buffer: the hydration boundary still opens so buffered and future live events keep reconciling, but the whole catalog dependency set is dropped — the 30178 head, its 30175/30176 constituents, and any kind-5 deletion carrying a dependency-targeting `a` tag (classified by scanning *all* `a` tags, matching the backend's deletion router, which `find_map`s across every tag and routes the first signer-owned coordinate — so a malformed or foreign first `a` tag ahead of an owned 30176 cannot slip a destructive deletion through) — because backfill never fully hydrated the owner's constituents. Dropping only the 30178 head is not enough: the backend refreshes the catalog head after every team/persona save, and live delivery is newest-first, so a 30176 edit that adds a *new* member would reach the backend before that member's 30175 and falsely tombstone a witness-holding device's valid team. Holding the prior hydrated run's constituents on disk only proves the *old* revision is resolvable — it says nothing about a new member — which is why the entire dependency set is held rather than just the head. 30177 managed-agent runtime policy stays live (it drives no catalog refresh). A degraded device stays stale on team/persona edits until it self-heals on the next effect re-run (restart, or an identity/community switch) — the correct trade against destroying valid shared state. ## Data layer Relay paging, signature verification, NIP-33 head selection, and untrusted-content parsing for the kind `30178` team catalog live natively in the `fetch_team_catalog` Tauri command (`team_catalog.rs`), structurally mirroring `fetch_persona_catalog` (`persona_catalog.rs`). A catalog refresh crosses IPC once and never verifies a signature on the webview thread. `teamCatalogRelay.ts` is now a thin presentation and local-linkage layer over the verified projection — it shapes entries for display and links each to a local team, and never parses or verifies. Parsing is all-or-nothing, identical to the add-time re-fetch in `add_team_from_catalog`: a team with any invalid member fails to parse and the publication is dropped from the catalog, matching persona behavior. **Behavior delta:** the previously reviewed partial-render of invalid-member teams — a warning banner on an entry that could never be added — is removed. Invalid publications are dropped entirely rather than surfaced as un-addable. ## Hooks `useTeamCatalogRelay.ts` mirrors the persona catalog hook: a community-keyed query over `fetch_team_catalog`, live invalidation on kind `30178`, share/unshare, and add-from-catalog (which invalidates both the teams and personas stores, since adopting a team copies its members as local personas). ## CommunityCatalogDialog Single unified surface replacing the former separate dialogs. Agents and Teams appear as labeled sections with type-tagged selection and a teams-preferred launch. `TeamsSection`'s discover entry and the new-agent card both open this one dialog. `PersonaCatalogDialog.tsx` is removed; persona browsing now lives inside the unified dialog. ## TeamShareDialog Publishes and unshares team catalog entries via `set_team_shared`. ## e2e + screenshots `team-catalog.spec.ts` covers the browse + adopt flow; `team-catalog-screenshots.spec.ts` produces the pixel-regression set. `e2eBridge.ts` gains `mockTeamCatalogPublications`, which mirrors the native command's head selection and shared gate and performs only a shallow `v`/`name`/`members`-array shape check; per-member validation stays in the Rust command. ## Follow-ups None. The shared agent-definition text-safety policy (Unicode-control/bidi/zero-width rejection) already covers the team surface: #5112's `validate_team_catalog_content` chokepoint gates every field delivered verbatim to the harness on both the publish and adopt paths, and this PR's parse layer consumes that verified projection rather than re-validating on the webview thread. Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> |
||
|
|
ed11c8d8bf |
Refresh mobile utility surfaces and theme picker (#6944)
## Summary - invert mobile utility surfaces so pages and sheets use the softer page background with raised containers - rebuild the community theme picker around swipeable Home/Chat previews, accent and appearance controls, and native iOS glass interactions - align shared sheet/profile spacing and radii, and move Theme into the Community settings card ### Related issue None found. ### Testing - `just mobile-check` - `just mobile-test` (1,871 tests) - signed iOS Release build installed on a physical iPhone --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Kenny Lopez <klopez4212@gmail.com> Signed-off-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz> Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz> |
||
|
|
a89f5df4c5 |
fix(desktop): complete project empty and context states (#6980)
## Summary First-time project users can now create a project directly from the empty state instead of reaching a dead end. The right-side project context also uses the intended Activity label and aligns its first row with the channel header. The populated and empty project views share one creation flow, so navigation, success feedback, and compatibility warnings remain consistent. ### Related issue Related: #6939 ### Testing - Desktop check, TypeScript typecheck, and repository file-size gate - 10 focused project overview context unit tests - E2E-mode build and 3 focused Playwright scenarios covering empty-state creation, right-sidebar alignment, and Activity context Signed-off-by: Thomas Petersen <thomasp@squareup.com> |
||
|
|
58cc4b7e9b |
Fix mobile jump-to-latest flicker (#6807)
## Summary - Keep jump-to-latest hidden through composer and keyboard layout changes while the user is still following the tail. - Stabilize thread tail detection during lazy scroll updates. - Unmount inactive iOS Liquid Glass controls so the channel arrow cannot bleed into an opened thread. ## Testing - just mobile-check - flutter test (1,864 passed) - Signed iOS Release build and install on iPhone Air --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> |
||
|
|
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>
|
||
|
|
e76c81968b |
refactor(db): split channel membership store (#6782)
## Current reconstructed head Exact base: `codex/issue-7-roster-test-fixtures` at `5df440c411be9705eb29a57f0c41f7239767e007` Exact head: `codex/issue-7-channel-membership-store` at `8ad0782ee311f5f51b714494ce750c5937f127cc` This current head removes `crates/buzz-db/tests/store_ownership.rs`; no replacement path-sensitive ownership test is introduced. Apart from removing that complete test-file diff, the production patch is byte-for-byte identical to the previously reviewed slice. This remains part of tracker #2 and the #17/#19 acceptance work. Independent exact-head review from a separate clean Blox workstation found no issues. Current-head evidence passed formatting, strict `buzz-db` clippy, 111 non-PostgreSQL library tests with 200 PostgreSQL tests ignored, the observability source test, relay consumer compilation, exact ownership/unique-span review checks, and 3 channel and 19 membership PostgreSQL tests on native PostgreSQL where applicable. ## Why Complete the channel ownership slice of [tracker #2](https://github.com/TheSentinel454/buzz/issues/2) and [domain issue #7](https://github.com/TheSentinel454/buzz/issues/7) while preserving the runtime/store boundary established by #6660 and #6668. This child stacks on the test-only fixture prerequisite #6819 above #6777 and carries forward PR #6700's membership/replacement lock timing without changing lock or transaction behavior. ## What - Keep channel lifecycle, metadata, TTL advisory locking, and lifecycle tests in `channel.rs` - Move membership/roster records, SQL, advisory-lock helpers, `Db` methods, focused tests, and datastore spans to a dedicated `channel_members.rs` - Preserve existing `buzz_db::channel::*` paths with compatibility re-exports while exposing the dedicated module - Move the four roster-fence PostgreSQL tests out of `lib.rs` ## Stack - Exact base: codex/issue-7-roster-test-fixtures at 21d1b265c133292e6707e766cd4204e6a43f08af ([#6819](https://github.com/block/buzz/pull/6819)) - Exact head: codex/issue-7-channel-membership-store at 25138bfd6588e046170dbdbc4ed953bdc3cf7ed1 - Tracker: https://github.com/TheSentinel454/buzz/issues/2 - Domain: https://github.com/TheSentinel454/buzz/issues/7 - Test/span acceptance: https://github.com/TheSentinel454/buzz/issues/17 and https://github.com/TheSentinel454/buzz/issues/19 ## Non-goals - No SQL, schema, retry, timeout, lock ordering, transaction boundary, or client-visible behavior changes - No change to channel TTL lifecycle ownership merely because lifecycle bootstrap writes an owner membership row - No store traits, domain-handle redesign, broad `PgExecutor` migration, raw pool accessor, new crate, or directory-wide reorganization - No changes to, retargeting of, or merge action on PR #6700 or #6777 ## Risk Assessment Moderate review surface, low semantic risk. The file split is large, but method signatures, SQL, bind order, membership and replacement lock namespaces, transaction boundaries, and span names remain unchanged. Compatibility re-exports preserve existing `buzz_db::channel::*` consumers. ## Blox Verification Author workstation: `buzz-tornquist-issue-2-store-stack` (`2046520`), exact head `8376e19d0da3ec77550590cd91cc3dfe284d95d6`. - `cargo fmt --all --check` — passed - `cargo clippy -p buzz-db -p buzz-relay --all-targets -- -D warnings` — passed - Native PostgreSQL channel lifecycle suite — 3 passed - Native PostgreSQL membership/roster suite — 17 passed; two pre-existing ignored-test fixture failures reproduced identically on the untouched parent `2de5444`: `large_roster_reconciliation_candidates_respect_snapshot_count_and_signer` and `locked_member_snapshot_blocks_post_capture_membership_mutation` both receive the migration-0032 `23514` invalid-`p`-tag rejection. This extraction intentionally does not fold a test-behavior fix into the move. - `cargo test -p buzz-relay --lib -- --test-threads=1` — 908 passed, 48 ignored; the existing load-sensitive mesh demo test returned 504, matching the #6700/parent baseline - `cargo test -p buzz-relay --lib api::mesh_demo::tests::demo_join_forwarded_arm_round_trips_echo -- --exact --test-threads=1` — passed Independent exact-head review: `buzz-tornquist-pr-6782-review` (`2048397`) found no remaining critical, important, or minor issues. The full implementation review also independently reproduced both stated PostgreSQL fixture failures on the exact parent and passed the relay library suite (909 passed, 48 ignored). Generated with Codex ## Superseded pre-comment restack verification PR #6700 merged before publication completed. This layer was restacked onto current main through the exact parent named above; the final cumulative tip is 2ddcc8a29f95c8c8c9cb87bb6cf59335f2190fae. Cumulative author gates passed: formatting and diff checks; buzz-db and buzz-relay all-target clippy with -D warnings; DB lib 111 passed / 200 ignored; ownership 22/22; observability 1/1; the full isolated PostgreSQL domain matrix; and relay lib 910 passed / 49 ignored. - Workstation: `buzz-tornquist-pr-6782-final-review` (`2057620`), fresh shallow checkout - Base: `c60e793eadde79d9eab9f48bbb2ede0ad4831f9b` - Head: `fa09b6c81c4db3b3e1940a2117a97ab2186e49f7` - Findings: none Reviewed both commits in `base..head`. Channel lifecycle/metadata, TTL transitions, and their lock rationale remain in `channel.rs`; membership authorization, roster fencing/snapshots, membership advisory locking, membership records, and focused tests move together to `channel_members.rs`. SQL, transaction, and lock sequences are preserved. Verification: format and diff checks passed; `buzz-db --all-targets` clippy passed with `-D warnings`; DB lib tests passed (111 passed, 200 PostgreSQL tests ignored); ownership (2/2) and observability (1/1) guards passed; native PostgreSQL 17 passed 3 channel lifecycle tests plus 19 membership/roster tests with migrations 1-32 successful; relay lib test target compiled successfully. Final worktree was detached at the exact head and clean. Complete evidence archive SHA-256: `81ae374095f649ca7a25d8b9a4fc864257b7925d1b44657a69ca111523adf36e`. ## Comment-addressed restack Review follow-up on #6777 removed only the low-value replaceable ownership source test. This PR was restacked onto its rewritten parent; its production patch is unchanged. - Exact base: `21d1b265c133292e6707e766cd4204e6a43f08af` - Exact head: `25138bfd6588e046170dbdbc4ed953bdc3cf7ed1` - Final cumulative tip: `6fa2f104d42c6ba85bdf62e7ccb74ceaf4a84f67` - Per-layer patch-ID and tree audits confirm this PR’s production diff is unchanged from its pre-comment head. - Cumulative Blox gate: formatting and diff checks; strict `buzz-db`/`buzz-relay` Clippy; DB lib 111 passed / 200 ignored; ownership 21/21; observability 1/1; every moved PostgreSQL test; relay lib 910 passed / 49 ignored. - Independent re-review at this exact head: no findings; fresh exact-parent/head Blox review passed fmt/diff, strict Clippy, DB lib 111 passed / 200 ignored, current ownership/observability guards, 3 channel plus 19 membership PostgreSQL tests, and relay compilation. Signed-off-by: OpenAI Codex <codex@openai.com> Co-authored-by: OpenAI Codex <codex@openai.com> |