mirror of
https://github.com/block/buzz.git
synced 2026-10-28 13:01:10 +00:00
571c1902d0ca55cfd4ccf6b91eeb731909cc10be
599 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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> |
||
|
|
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>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
953a1798b2 |
feat(auth): add NIP-FI canonical assertion verifier and contracts (#6776)
The closed, provider-neutral contract layer at the root of the NIP-FI federated-identity dependency graph — Phase A, PR 1 of the plan. It has no dependencies on any other PR and defines no database schema, migration, runtime JWKS fetching, binding resolution, enrollment, or request/proof binding; those belong to later PRs. ## What this adds - **Multi-issuer assertion-policy config** (`IssuerRegistry`, `IssuerPolicy`) keyed by exact `iss`. Identity is issuer-qualified `(iss, sub)` throughout — equal `sub` under different `iss` are distinct identities. The subject coordinate is fixed to the JWT `sub` claim (`SUBJECT_CLAIM`), never configurable, so no deployment can seal a mutable attribute like `email` as identity. Issuer URL and audience remain deployment configuration. - **The two deterministic semantic contract identities** — `AssertionPolicyId = H(canonical assertion-policy contract)` and `TransportContractId = H(canonical transport contract)` — derived by length-prefixed, domain-separated SHA-256 so a semantic change moves exactly its owning ID while benign JWKS rotation never changes policy lineage. Set-valued policy inputs (audiences, algorithms, subject-class values, scope capture) are canonicalized before derivation, so the ID is invariant under permutation and duplication. Config fields that a freshness class never reads are rejected at construction (an `offline-jwt` policy cannot carry `maximum_status_age`), so the canonical encoding stays total over valid configs and semantically identical policies always derive one ID. - **The single canonical verifier** (`FederatedAssertionVerifier`, `FI-INV-16`) producing the origin-sealed, provider-neutral `VerifiedAssertion` normalized result. Its constructor is crate-private, so unverified claims cannot be promoted into authority. The issuer→JWKS authority is entirely crate-owned: `AssertionKeySet` has no public constructor and `IssuerKeySource` is sealed, so no downstream crate can relabel one issuer's keys as another's. The authenticated key set is bounded (`MAX_JWKS_KEYS`) and the bound is folded into `AssertionPolicyId`, so an unbounded attacker-controlled `kid` scan cannot be driven; every snapshot requires a finite positive hard deadline. - **Sealed revalidation dependencies** — `RevalidationDependencies` carries the key-snapshot hard deadline and a `ConfidentialAssertion` handle to the exact compact JWS (no `Debug`/`Display`/`serde` leak, sole read path `compact_jws()`), so a changed snapshot can revalidate the same evidence: a retained key revalidates, a removed key denies. - **The privacy-preserving four-class denial contract** (`DenialClass`, `FI-INV-13`) with the byte-exact Nostr text, HTTP status, body, `Content-Type`, and `WWW-Authenticate` values fixed by the spec's rejection table. An unreadable required current dependency maps to `authorization_unavailable`/503, never to rejected evidence, and rejected evidence never masquerades as a 503 at either end of the pipeline: all bounded, dependency-independent checks (compact structure, header, signature shape, policy, algorithm, token class) precede key-source lookup, and all offline validation (token-class, key, signature, audience, claims, time) completes before any status-witness deferral. A wrong-`typ` or structurally malformed token, or malformed/invalidly-signed input naming a current-status issuer, therefore denies with `evidence_rejected`/403 rather than being reported as a 503 availability signal. ## Corrections applied to the mined source Mined from the `buzz-auth` verifier core in [#1476](https://github.com/block/buzz/pull/1476) and corrected to the settled spec (the merged `docs/nips/NIP-FI*.md`, [#5946](https://github.com/block/buzz/pull/5946)), which settled after #1476 was written: - Token-class selection with exact `typ` enforcement. Two classes are offered: `at+jwt` and `nip-fi+jwt`. There is deliberately no generic/absent-`typ` "named compatibility" class: it cannot be proven disjoint from an OIDC ID token by claim presence alone (an issuer can mint an ID token carrying `client_id`), and the only authenticated discriminator is `typ`, which such a mode declines to constrain. - OIDC ID-token denial — denies even when `iss`, `aud`, and `sub` match, via exact `typ` mismatch against every accepted class. - The fixed `nostr_pubkey` claim accepted only as lowercase hex of exactly one 32-byte key; bech32 and other aliases deny. - Resource-owner / client-subject ambiguity denial and required `client_id` for `at+jwt`. - JWK admissibility: a key's `use` and, when present, `key_ops` must authorize signature verification (a key restricted to other operations such as `encrypt` is rejected), and the selected JOSE algorithm is bound to the key's required family and curve (ES256↔EC/P-256, ES384↔EC/P-384, EdDSA↔OKP/Ed25519, RS/PS↔RSA). The JWK `alg` is advisory; the actual key material is what signs, so a JWK declaring a matching `alg` over mismatched material (a different family or curve) is rejected before signature verification. - Spec-exact time arithmetic (`now < exp`, `iat <= now + skew`, `now < iat + maximum_assertion_age`, equality at expiry is expired). `exp`/`iat`/`nbf` accept finite integer or fractional RFC 7519 `NumericDate` values with checked, overflow-safe conversion; non-finite and out-of-range values deny. ## Verification In-crate tests in `crates/buzz-auth/src/nip_fi/verifier/tests.rs` sign real ES256 assertions against a fixed test key and cover: the happy path, exact-wire-text for all four denial classes, deterministic and semantic contract IDs, canonicalization invariance, token-class enforcement including ID-token denial, JWK `key_ops` rejection, `nostr_pubkey` hex handling, time bounds, and multi-issuer selection (same subject across distinct issuers yields distinct identities and policy IDs). They also cover the round-3 contracts: the key-set bound (oversized rejected, at-bound accepted, empty rejected), fixed-`sub` identity (identity is `sub` not a configured `email`; a token without `sub` denies), offline-before-deferral (invalid-signature, wrong-audience, malformed-claim, and expired under a current-status issuer each deny 403/`evidence_rejected`), and the revalidation contracts (dependencies carry the deadline and exact JWS; a retained key revalidates, a removed key denies). And the round-4 contracts: algorithm↔key-material binding, covered exactly per accepted algorithm (a table-driven matrix asserts every policy-acceptable algorithm — ES256, ES384, EdDSA, RS256/384/512, PS256/384/512 — matches only its required family/curve and rejects every other, so each mapping mutation fails individually; and an ES256 token against P-384, RSA, and Ed25519 material each deny `InvalidKey`); malformed evidence classified before key lookup (wrong-`typ`, two-segment, four-segment, empty-signature, and non-base64url-signature tokens against an empty source deny 403 not 503); `maximum_status_age` inapplicable under offline-jwt rejected at construction while current-status still requires it; and fractional `NumericDate` (finite fractional `iat`/`exp`/`nbf` within bounds verify, non-finite and out-of-range deny). Compile-fail doctests guard the sealed authority seam (`AssertionKeySet::new`, `IssuerKeySource`) and the absence of the named-compatibility token class. ## Notes - `Co-authored-by` attribution is preserved for the mined author. The #1476 `buzz-auth` commits are authored by Cea Stapleton Cordasco (the salvage map records #1476 under Franco; the git history on that PR is Cea's — surfacing for the attribution/closure record). - Amends `docs/nips/NIP-FI.md` to remove the "named compatibility access token" class and its `FI-TRACE-TOKEN-CLASS` oracle reference, so the normative spec matches the two-class root implementation (a generic/absent `typ` cannot be proven disjoint from an OIDC ID token). Removal-only, no neighbor redesign; a `docs/nips/` grep confirms no dangling cross-reference to the class remains. - Adds `jsonwebtoken 10.4.0` (`aws_lc_rs`) as a workspace dependency. Dependencies: none. --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Co-authored-by: Cea Stapleton Cordasco <261786559+cea@users.noreply.github.com> Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> |
||
|
|
ae0ecd9a67 |
feat(model-capabilities): humanize Databricks UC model families (#6955)
Chat-eligible Unity Catalog models from the Gemini, DeepSeek, GLM, Grok, Llama, Qwen, Gemma, and Inkling families were rendering as raw FQNs such as `system.ai.gemini-3-5-flash` in the agent model picker. #6918 surfaces these models, but the capability manifest carried no records for most of them and the registry-label path could not strip their Unity Catalog or `goose-` prefixes. I added a display-only `label_family_tokens` list and `databricks_v2` exact records for the sixteen previously unlabeled endpoint stems. All 26 target FQNs and their `goose-` aliases now resolve to curated labels through `databricks_registry_label`, while capability resolution continues to use only the existing `family_tokens` (`claude-`, `gpt-`, and `kimi-`). This keeps label discovery from changing capability profiles for unrelated model IDs. Each new record includes reconciliation metadata against the pinned [models.dev catalog](https://models.dev/api.json). Nine records adopt first-party effort evidence that the Databricks MLflow Chat transport can express: Gemini 3.5 Flash, Gemini 3.5 Flash Lite, Gemini 3.6 Flash, Gemini 3 Pro Image, DeepSeek V4 Flash, DeepSeek V4 Pro, GLM-5.3 Flash, Grok 4.6, and Inkling. The remaining seven deliberately retain the `databricks_v2/concrete_unknown` fallback because upstream evidence is absent, identifies a non-reasoning model, or provides only toggle/token-budget controls that `reasoning_effort` cannot represent. The normative corpus adds the sixteen exact records, four Unity Catalog alias probes, and two capability-isolation probes. Existing base vectors remain unchanged, and Rust and TypeScript consume the same strict manifest and corpus. Related: #6918 --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Thufir <7ebdb0b67dab08a570b9faf7bbada97535673b4ccaba2cbd546ad3ba84c87fa6@buzz.block.builderlab.xyz> Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Co-authored-by: Thufir <7ebdb0b67dab08a570b9faf7bbada97535673b4ccaba2cbd546ad3ba84c87fa6@buzz.block.builderlab.xyz> |
||
|
|
be8d7756d5 |
feat(agent): discover Databricks Unity Catalog models (#6918)
## Summary Add Unity Catalog model-service discovery to `buzz-agent` Databricks v2, preserve full Unity Catalog FQNs through ACP model discovery, route model services through MLflow chat, and add the optional `DATABRICKS_MODEL_FILTER` visibility filter. The resilience follow-up bounds each catalog attempt across headers, bounded body reads, and JSON parsing; retries transient 499/5xx, transport/body failures, malformed JSON, and attempt timeouts; and emits only safe catalog/status diagnostics. ## Validation The live validation and test gates below were run on the pre-rewrite tip `1cf6c66e7c5bf9a00c357bda24df48e3beeb310b`. The rewritten tip `0364cf44f3eb5cfbd5771b0a0dd7868ce0ad2c19` has identical trees for both commits, so the attribution rewrite changes no tested code. - 31 catalog tests passed - 19 Databricks OAuth/routing integration tests passed - `cargo check -p buzz-agent` passed - `cargo clippy -p buzz-agent --all-targets -- -D warnings` passed - `cargo fmt --check` passed - `git diff --check` passed - Desktop Tauri validation passed through the repository-supported setup path, including 2,777 library tests and auxiliary terminal suites The full `buzz-agent` package has one separately reproduced pre-existing failure: `regressions::cancel_kills_inflight_tool_via_mcp_notification`, failing on both baseline `eb8e97cfe` and the follow-up worktree. No Databricks path reaches that test. The live validation proves discovery and ACP exposure, not inference against every listed model. --------- Signed-off-by: Kalvin Chau <kalvin@block.xyz> Signed-off-by: am <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> Co-authored-by: am <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> |
||
|
|
57216c942f |
test(db): use canonical channel roster fixtures (#6819)
## Summary
Fix two PostgreSQL channel-roster test fixtures so they satisfy
migration 0032's canonical kind-39002 fence before the
channel/membership store extraction moves them.
This is a test-only prerequisite in the issue #2 extraction stack. It
emits the canonical four-field `p` tags with authoritative roles, keeps
the stale-history mutation after insertion, and gives the colliding
other-tenant fixture its own matching roster.
## Stack
- Exact base: codex/issue-6-finish-replaceable-store at
|
||
|
|
69096c9a8d |
preserve channel description paragraph breaks (#6946)
**Category:** fix **User Impact:** Channel descriptions now keep their paragraph structure everywhere people and agents read them. **Problem:** Multi-paragraph channel descriptions were flattened in the desktop interface and in agent context, making operational instructions harder to scan and potentially changing their meaning. A running agent harness could also continue using a stale description after someone edited it. **Solution:** Preserve authored line and paragraph breaks across the desktop surfaces, and render descriptions as bounded, indented agent-context blocks with semantic delimiters escaped. Prompt-visible channel metadata now refreshes before each turn with cached fallback, while one shared desktop derivation keeps the header and channel intro consistent. <details> <summary>File changes</summary> **crates/buzz-acp/src/pool.rs** Refresh prompt-visible kind-39000 metadata with one bounded request per turn so edits reach running harnesses, while retaining cached metadata as a degradation fallback. Add regression coverage for description edits and update request-count fixtures for the refresh lifecycle. **crates/buzz-acp/src/prompt_framing.rs** Add semantic text escaping for untrusted values embedded inside paired prompt sections. **crates/buzz-acp/src/queue.rs** Render multi-paragraph descriptions as indented context blocks, preserve blank lines, retain the 500-character limit, and escape semantic delimiters. Add helper- and full-prompt tests for paragraph preservation, UTF-8-safe truncation, and adversarial input. **desktop/src/features/channels/lib/channelDescription.ts** Introduce one shared channel-detail derivation and preserve newlines between status context and authored detail text. **desktop/src/features/channels/lib/channelDescription.test.mjs** Cover shared field precedence, multiline descriptions, status-prefix separation, and fallback behavior. **desktop/src/features/channels/ui/ChannelPane.helpers.ts** Use the shared channel-detail derivation for the channel intro instead of maintaining a second field order. **desktop/src/features/channels/ui/ChannelPane.helpers.test.mjs** Verify description text wins consistently over legacy purpose text when both are present. **desktop/src/features/channels/ui/ChannelManagementSheetRows.tsx** Preserve paragraph whitespace in editable and read-only channel summaries and allow enough lines for compact multi-paragraph descriptions. **desktop/src/features/channels/ui/ChannelManagementSheetRows.test.mjs** Render both channel-summary variants and assert the multiline whitespace and four-line clamp contract. **desktop/src/features/messages/ui/ChannelIntroBlock.tsx** Display description line and paragraph breaks in the empty-channel intro. **desktop/src/features/messages/ui/ChannelIntroBlock.test.mjs** Add render-level coverage for the intro's multiline whitespace contract. </details> ## Reproduction steps 1. Run the desktop app and create an empty channel. 2. Give it a description with two paragraphs, including a blank line and a multi-line second paragraph, then save and reopen channel management. 3. Confirm the empty-channel intro preserves the blank line and subsequent line breaks. 4. Confirm the channel-management summary shows the paragraph structure for both an editor and a read-only member rather than a detached ellipsis. 5. Hover the channel title and confirm its tooltip keeps the description's line breaks. 6. Add an agent, send one prompt, edit the description, then send another prompt. Confirm the next generated agent context contains the updated description as an indented multiline block. ## Screenshot A real-app screenshot of the multiline channel intro and channel-management summary is attached in the PR comments below. Closes AIDA-1980. --------- Signed-off-by: tulsi <tulsi@block.xyz> |
||
|
|
0808ab485c |
fix(cli): enrich template cardinality error with per-candidate presence and profile hints (#4825)
## Problem
`buzz channels create --template` fails with a bare-pubkey
duplicate-instance error when a persona has more than one live instance.
When duplicate instances share the same display name and avatar, the
error gives the operator no signal to identify which instance is stale.
## What changed
**`crates/buzz-cli/src/commands/channels.rs`** (boundary) + one
visibility line in **`crates/buzz-cli/src/commands/users.rs`** (`fn
presence_subject` → `pub(crate)`), plus a relay behavior change in
**`crates/buzz-relay/src/api/bridge.rs`** and a supporting test in
**`crates/buzz-pubsub/src/presence.rs`**.
### Relay: presence-lookup failure is now surfaced as a failure
`synthesize_presence` previously collapsed a Redis `get_presence_bulk`
error into `unwrap_or_default()` → HTTP 200 `[]`, making a backend
outage indistinguishable from an authoritative all-offline snapshot. It
now returns `Option<Result<Vec<Value>, (StatusCode, Json<Value>)>>`:
`None` means the request is not a presence query (fall through),
`Some(Ok)` is a real snapshot (an empty vec is an authoritative
all-offline result), and `Some(Err)` is a backend failure surfaced as a
non-2xx response. The event tag-parse and sign paths were converted from
`.ok()?` fall-throughs to explicit `Some(Err(...))` for the same reason
— a silent fall-through would have reintroduced the fake-empty-success
anti-pattern. The function takes `pubsub` and `relay_keypair` directly
rather than the whole `AppState`, which keeps the Redis-error seam
unit-testable without live infrastructure; its single production caller
was updated accordingly, and no other caller consumes
`synthesize_presence`.
### Hint fetch architecture
**`assemble_roster_resolution<F, Fut>`** — post-fetch stage extracted
from `build_roster_resolution` as an injectable-fetcher async generic.
It contains: archive-filter-based duplicate detection, the conditional
`fetch_hints` call, and `finalize_roster_resolution` delegation.
Accepting the fetcher as a closure makes the wiring directly testable
without a relay.
`build_roster_resolution` reduces to: gather slugs → `tokio::join!(scan,
archive)` → delegate with the real `fetch_candidate_hints` closure.
**Zero hint queries on the happy path**: `assemble_roster_resolution`
only invokes the fetcher when duplicate live instances exist after
archive filtering. Untrusted archive snapshot (`Err`) conservatively
treats all found instances as live.
When duplicates exist, `fetch_candidate_hints` runs the presence
(kind:40902) and profile (kind:0) queries concurrently, bounding **each
independently** through `join_bounded_queries`: a per-query 3-second
timeout maps to `Err`, so a lookup that completes is never discarded
because its sibling hung. On total failure or timeout, bare pubkeys are
printed promptly.
### Trusted-snapshot boundary — `hints_from_results`
The relay drops the Redis presence key when an identity goes offline, so
the bulk presence snapshot never returns an event for an offline
instance — precisely the stale duplicate an operator needs flagged.
`hints_from_results` therefore treats a *trusted* presence snapshot as a
**complete snapshot** for the requested pubkeys: it seeds all of them
`offline`, then overlays returned statuses.
A response is trusted only when `trusted_presence_snapshot` validates
**every** array element as the actual snapshot contract: each must parse
as a complete signed `nostr::Event` of the presence-update kind carrying
exactly one `p` tag whose subject is one of the requested pubkeys.
Requiring the *sole* `p`-tag subject — the exact value `build_hint_map`
later reads via `presence_subject` — is what stops a mixed-tag event
(`[["p","<unrequested>"],["p","<requested>"]]`) from passing the gate
yet overlaying a different subject downstream. Any element that is not
such an event — a vacuous object like
`{"pubkey":"…","content":"online"}`, `[{}]`, `[null]`, an event of the
wrong kind, one for an unrequested subject, or one carrying more than
one `p` tag — makes presence enrichment untrusted: nothing is seeded
`offline`, so absence is never falsely inferred as offline. A failed,
timed-out, or non-array presence response is likewise untrusted. In
every untrusted case the successful profile sibling still contributes
its hints.
### Response-to-map conversion — `build_hint_map`
Sync production function taking the offline seed plus raw
presence/profile event slices. Relay-signed presence events carry the
agent pubkey in the `p` tag (not the event author); `build_hint_map`
calls `presence_subject` from `users.rs` (now `pub(crate)`) to resolve
the correct key.
### Hint display — `format_candidate`
Appends `[online/offline, profile updated YYYY-MM-DD]` (or subset) to
each candidate pubkey in the error. `profile_updated_at` names the field
correctly: kind:0 is replaceable state that desktop republishes on
rename and profile reconciliation, so `created_at` reflects the last
profile update, not provisioning time. Date formatted via
`chrono::DateTime::from_timestamp`; out-of-range timestamps omit the
date. Missing entries fall back to bare pubkeys.
### Cardinality rule
`apply_cardinality_rule` remains pure and relay-free; zero/one/many
semantics unchanged.
---------
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Carl <9d00794d3df50972eb8b615511783cab12a77a8fd5dd5edd58073ec73b54bd8b@buzz.block.builderlab.xyz>
Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Co-authored-by: npub1g8493u0xfsjrvflg4n08ezd7vec99mnwzlv0qgwpr9d7gvjwhuzqx59rhw <41ea58f1e64c243627e8acde7c89be667052ee6e17d8f021c1195be4324ebf04@buzz.block.builderlab.xyz>
Co-authored-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
|
||
|
|
0ccf934b88 |
fix(db): disable heartbeat vacuum truncation (#6898)
## Why `replica_heartbeat` is updated continuously, but autovacuum heap truncation can take an `ACCESS EXCLUSIVE` lock whose replay cancels concurrent hot-standby reads. Truncating a fixed, single-row table provides no meaningful space benefit. ## What - Add migration 0034 to set `vacuum_truncate = false` on `replica_heartbeat` - Keep the desired-state schema aligned for fresh installations - Extend migration coverage so both paths retain the reloption ## Risk Assessment Low — this disables only autovacuum's heap-truncation phase for a single-row table; normal vacuum cleanup remains enabled. The tradeoff is retaining at most the table's otherwise-truncatable tail pages. ## References - [Investigation thread](https://sq-block.slack.com/archives/C0B2WT43BDH/p1787760831375989) - [PostgreSQL table storage parameters](https://www.postgresql.org/docs/current/sql-createtable.html#SQL-CREATETABLE-STORAGE-PARAMETERS) Generated with Codex --- **Update Aug 27, 2026:** Hardened fresh `pgschema` bootstraps after review. - Renamed the shared post-apply script to `reconcile-schema-after-pgschema.sql` and made it restore the heartbeat reloption and singleton row idempotently. - Added live catalog and row assertions that stop bootstrap when the database does not match the required state. - Updated every repository `pgschema apply` caller, including the desktop release smoke path. - Added a regression test that requires future `pgschema apply` callers to run reconciliation. - Documented the `pgschema` DML and storage-parameter limitation in `AGENTS.md` and at the reconciliation entry point. - Merged current `main` and renumbered this migration to 0034 after `main` added migration 0033. --------- Signed-off-by: Luke Tornquist <tornquist@squareup.com> |
||
|
|
9176e34405 |
fix(projects): allow owners to delete agent projects (#6533)
## Summary - expose project deletion when the current user directly owns the project or owns/manages its author agent - publish a viewer-signed, project-only tombstone after fetching the exact live head, then detect concurrent replacement - honor relay-authorized owner-signed tombstones in desktop project read models ## Testing - pre-push hook: desktop Biome, TypeScript, 5,358 desktop tests, Rust tests, Tauri checks, and file-size ratchet passed at `a36f7a4cf` - `cargo check -p buzz-test-client --tests` - relay E2E coverage added as ignored; not run against a live relay --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Signed-off-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz> Signed-off-by: Carl <9d00794d3df50972eb8b615511783cab12a77a8fd5dd5edd58073ec73b54bd8b@buzz.block.builderlab.xyz> Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz> Co-authored-by: Carl <9d00794d3df50972eb8b615511783cab12a77a8fd5dd5edd58073ec73b54bd8b@buzz.block.builderlab.xyz> |
||
|
|
b3baa56ba8 |
fix(db): exclude kind:30179 ciphertext from brownfield FTS (#6822)
## Problem Kind `30179` (NIP-PMA private managed agent — NIP-44 ciphertext carrying the agent nsec, env vars, prompt) joined `AUTHOR_ONLY_KINDS` in #4593, but only **fresh** installs stopped indexing it: - `migrations/0008` installs the positive FTS allowlist only when `events` is empty; - `migrations/0014` wraps the retained brownfield expression for `30350` alone; - `schema/schema.sql` still carries the legacy negative skip-set without `30179`. So a relay upgraded in place keeps tokenizing `30179` ciphertext into `events.search_tsv`. Found by Wren while reviewing #4999 (which activates publication of 30179 from Desktop). No readable leak: `/query` applies `search_hit_accepted` + `event_visible_to_reader` before serialization and live foreign searches against builderlab returned `[]` — this is the storage-layer privacy invariant (`docs/nips/NIP-PMA.md` deployment step 2) plus wasted FTS work. ## Fix - `migrations/0033_private_managed_agent_fts.sql` — same shape as 0014: capture the current generated expression via `pg_get_expr`, drop/re-add `search_tsv` wrapped with `kind = 30179 → NULL`. Every other kind keeps whatever policy the database already had (fresh allowlist or brownfield skip-set). Rebuilds the GIN index; no heap-wide policy rewrite. - `schema/schema.sql` — add `30179` to the desired-state skip-set. ## Tests - `populated_upgrade_preserves_search_policy_except_for_private_kinds` (renamed from `…_push_leases`): provisions the **legacy negative expression** (migrations 1–7), inserts kind 1 / 30179 / 30350 rows, checkpoints after 0014 (30179 still searchable, 30350 not), then runs to head and asserts 30179 is NULL. **Proven red** with the 0033 file removed: `left: [(1, Some(true)), (30179, Some(true)), (30350, None)]`. - `embedded_migrator_contains_consolidated_initial_schema`: count 32→33, asserts 0033 shape and `schema.sql` parity. - `buzz-search/tests/fts_integration.rs` setup applies 0033 so the FTS tripwires run against the real chain. ## Verification (HEAD |
||
|
|
b622003f74 |
broker: define the agent-to-broker action contract (#6742)
Addresses the interface half of #6467. This is the contract for how a keyless agent asks a host to act on its behalf — and only that. No host, no transport, no signing, no Desktop code; the only implementation is a test double. An agent that holds no secret key still has to read its channels, answer a mention, react, keep its profile, address its memory, and create the agents it needs. This PR defines the vocabulary for those requests and the rules both sides must follow. **#6467's open question, answered: per-operation, not "sign this".** A raw signing primitive tells the host *who* is asking but not *what for*, so the only policies it can express are all-or-nothing. A closed set of named operations lets a host serve channel reads while refusing agent creation, and gives any later policy layer something to attach to. The cost is that adding an operation means changing the contract — which is the point. Signing, publishing, and the auth/encryption NIPs are deliberately *not* operations; they are mechanisms inside whoever holds the key, and a test pins that none of those names resolve. **Nine operations in v1:** read a channel (whole channel, one thread, or mentions-only — these differ by filter, not permission, so they share one operation), post, reply, react, set profile, derive a storage address, and create / update / delete agents. Presence and typing are deferred as housekeeping a host may decline regardless; streaming is deferred, so waking on a mention is a polled mentions-only read. Each operation has typed, validated arguments and a typed outcome. **What the contract enforces structurally rather than by instruction:** - *Retry means identical bytes.* A request is validated and serialized exactly once and the transport only ever sees that frozen form. There is no way to re-render it, so a retry can't drift and be mistaken for a conflicting request. - *Read cursors are opaque.* Host-issued strings, validated for shape only, never parsed or compared. The host owns ordering and paging stability instead of being locked into timestamps. - *Reads return signed Nostr events.* A keyless agent verifies authorship and content locally and trusts the host only for completeness and authorization. Reply ancestry and mentions are derived from the signed tags, so nothing can disagree with the signature. - *Validation is the only door to a response.* The transport primitive hands back an unjudged envelope; the only response a caller can actually obtain is one already checked against the request it answers — request id correlates, the outcome's operation matches, identifiers and cursors are well-formed. A mismatch counts as "no answer", not a failure verdict. The raw primitive can't be invoked from outside the crate (pinned by compile-failure checks), so skipping that step isn't an option. - *Authority is never asserted by a body.* The envelope has no requester, owner, scope, or credential field; all of that comes from the session the host authenticated. Ownership of a created agent is implicitly the requester, never a field — which is what lets agents own agents while the chain still ends at a human. - *No secret crosses the wire.* Agent creation returns a pubkey and handle, never the minted key. Every payload type rejects unknown fields, and a test pins the exact JSON key set of all eighteen argument and outcome types, so no field can be added without a reviewer touching that table. Two honest limits: a string can physically hold secret text, so keeping secrets out of message content is host policy; and nothing stops a host from *holding* keys — it just may not hand them over. - *Null is never legal; omission is the only spelling of absence.* Every optional member rejects an explicit null. Otherwise null and omitted would be indistinguishable, and a contradiction check on the response envelope could be skipped by sending one (a real hole in an earlier revision). Coverage is derived by walking every path in the real fixtures, so new optional members are covered automatically. Serializers that emit null by default (Go without `omitempty`, Python's default dump) need configuring — called out in the module docs. **Wire mapping.** One POST endpoint, JSON body is the frozen request, opaque bearer credential in the auth header. Every verdict the host reached — including failure and including a rejected credential — comes back as a well-formed envelope with 200, because a second copy of the verdict in the status line could only disagree with it. Clients still try to parse an envelope regardless of status (intermediaries may remap) and fall back to the status only when none is present. A rejected credential is a host verdict carrying the promise "the action did not run"; transport errors are reserved for "nothing can be concluded about side effects." Binding the credential to a specific agent and conversation is the host's job and deliberately unenforceable here. **Relationship to #6543.** Envelope, request-id rules, protocol version, the three-way succeeded / failed / indeterminate result, error codes, and the agent-management argument shapes carry over. Changed deliberately: pubkeys are a validated type rather than bare strings, "capability" became "action" throughout, and a new "unsupported" error lets a host decline a best-effort operation without it reading as a fault. Both PRs land in the same module path — whichever merges second needs a hand reconcile, which I own as the author of #6543. **Validation at `7c853e0fb`.** 299 tests in the sdk crate (full package run), workspace tests green apart from the pre-existing relay timeout flake, which I reproduced on unmodified `main`. Clippy with warnings denied, fmt, warning-free docs, and the pre-push gate all pass. Review findings were closed with mutation tests — each fix verified by reverting it and watching a named test fail. One of those exposed a gap in my own coverage (response-side fields weren't pinned); that's fixed and floored separately from the request side so neither can mask the other. **Size.** About 5,400 lines across 8 files, roughly half tests. Every source file is under the repo's 1,000-line cap except the test file; #6759 splits it mechanically, is stacked on this branch, retargets to `main` after merge, and lands before PR 2 of the stack. --------- Signed-off-by: ss-dev-00 <a02c4e0850e5e612b4ddf95dbe2f5c56467cf27c6552203bc833ff438fb31971@buzz.block.builderlab.xyz> Signed-off-by: Bradley Axen <baxen@squareup.com> Signed-off-by: ss-core-02 <cca2c695c61d409321106cc399009c2cc674d72cc8900b2999a68fe54577a411@buzz.block.builderlab.xyz> Co-authored-by: ss-dev-00 <a02c4e0850e5e612b4ddf95dbe2f5c56467cf27c6552203bc833ff438fb31971@buzz.block.builderlab.xyz> Co-authored-by: ss-core-02 <cca2c695c61d409321106cc399009c2cc674d72cc8900b2999a68fe54577a411@buzz.block.builderlab.xyz> |
||
|
|
8f3566ad9e |
Deduplicate ACP thread prompt context (#6706)
## Why This is a small isolated follow-up to [#6503](https://github.com/block/buzz/pull/6503): remove two proven duplicate prompt fragments without adopting its broader framing experiment or changing Buzz-native routing and identity behavior. ## What - Remove parsed `parent=<id>` only when it exactly duplicates parsed `root=<id>`; retain `root=<id>`, nested parents where parent differs from root, raw event tags, event IDs, author identity, mention fields and rules, and the explicit `--reply-to` instruction. - Remove the full-history retrieval hint only when complete, untruncated conversation or thread context is included. Thread context must include its root and cover every event rendered in the batch; mixed-thread batches and root-missing windows retain retrieval guidance. Context that is absent, previously delivered, or truncated also retains retrieval guidance, and the context header, count, and messages remain unchanged. ## Risk Assessment Low — the change is confined to Buzz ACP prompt rendering and preserves all routing, identity, event, and retrieval information outside the two exact duplicate cases. ## Testing - `cargo test -p buzz-acp` — 804 unit tests and 9 lifecycle integration tests passed on the clean rerun - Focused mixed-thread, same-thread, and missing-root prompt regressions passed - The timing-sensitive `keepalive_resets_idle_past_deadline` test passed in isolation after one unrelated full-suite flake, then passed in the clean full rerun - `cargo clippy -p buzz-acp --all-targets -- -D warnings` - `cargo fmt --all -- --check` - `just file-size-check` - Repository pre-push gate at `1b88793574e74e22f00a1fd19bfe9aa8e86150fa` — file-size, branch, Rust unit-test, and Desktop Tauri lanes passed - `git diff --check` Generated with Codex Signed-off-by: Salman Mohammed <smohammed@squareup.com> |
||
|
|
de188ebf06 |
feat(projects): add agent and CLI project-home support (#6590)
## Summary - inject bounded project-home identity and repository context into managed agent sessions - add project-aware CLI flows for creating projects, repositories, issues, and related channels - match project homes through the existing relay query surface, then filter channel metadata client-side This is Part 1 of the channel-first Projects stack. Part 2 contains project creation and model foundations. ## Testing - `cargo fmt --all -- --check` - `cargo clippy -p buzz-cli -p buzz-acp --all-targets -- -D warnings` - `cargo test -p buzz-cli -p buzz-acp` — 1,184 tests passed, 1 doc test ignored - full pre-push gate passed ## Post-Deploy Monitoring & Validation - validate project-home agent context and project-aware CLI commands against a staging relay - healthy signals: project context matches the active channel, explicit repo coordinates remain stable, and normal channels receive no project block - failure signals: cross-channel project context, duplicate project creation, or commands targeting an unrelated repository; mitigate by reverting this PR --------- Signed-off-by: Thomas Petersen <thomasp@squareup.com> Signed-off-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz> Co-authored-by: Wrench <0eabe6ea5758c1e4c5b68cea4ac42b32c479072883cb28da8110b4a47c32b9a1@buzz.block.builderlab.xyz> |
||
|
|
f177f4909d |
Use paired tags for standing & per-turn context (#6701)
## Why PR #6503 bundled a broader context compiler with several prompt-shape and telemetry changes, making it difficult to isolate whether paired XML-like top-level boundaries improve agent behavior. This keeps the follow-up experiment focused on framing only. ## What - Replace top-level standing and per-turn bracket headers with paired tags while preserving inner section content and event/message framing - Carry only metadata already present in dynamic headers as attributes, such as thread counts, truncation, event type, and batch count - Preserve every section body verbatim so the model executes the exact bytes reviewed in Desktop - Keep routing metadata, event content, batching, and delivery behavior unchanged - Teach Desktop diagnostics to parse the new framing while retaining compatibility with archived bracket-framed sessions; ambiguous paired boundaries render as the complete captured prompt ## Risk Assessment Medium — the change is limited to agent prompt framing and diagnostic parsing, but it affects both standing context and every dynamic agent turn. Bodies and delivery behavior remain unchanged, and compatibility parsing remains for prior sessions. ## References - Follow-up to #6503, scoped only to the paired-tag framing hypothesis ## Update — 2026-08-24 - Addressed review feedback by removing entity encoding/decoding from the execution and diagnostic paths. - Added regressions for literal closing tags, placeholders, entity-looking text, ampersands, and authored boundary whitespace. - Verified `cargo test -p buzz-acp` (805 unit + 9 integration), Desktop tests (5,412 before the main sync; 5,439 in the pre-push gate), Desktop typechecking, and Tauri clippy/tests. - Live local relay startup and message ingestion passed. The live ACP reply step is blocked locally because installed Goose 1.0.23 no longer provides the documented `goose acp` command and no alternate ACP adapter is installed. --- **Update Aug 25, 12:13 EDT:** Extended paired tags to per-turn top-level sections. - Converted context, conversation/thread context, event batches, and steer/interrupt boundaries without changing their inner bodies. - Preserved existing header metadata as attributes only; added no new prompt metadata. - Verified 806 Buzz ACP unit tests plus 9 integration tests, 5,441 Desktop tests, Desktop typechecking and formatting, and all differential pre-push lanes. --- **Update Aug 25, 13:58 EDT:** Addressed framing review feedback. - Updated operational base instructions to reference the emitted `<context>` section and added a regression guard against stale `[Context]` instructions. - Restored batched steer/interrupt cardinality in Desktop diagnostic titles with focused coverage for both paths. - Verified 807 Buzz ACP unit tests plus 9 integration tests, 5,442 Desktop tests, Desktop typechecking and formatting, and every differential pre-push lane including Tauri clippy/tests. --- **Update Aug 25, 15:37 EDT:** Hardened legacy transcript compatibility. - Semantic standing-context parsing now requires paired framing at the start and complete input consumption. - Historical bracket-framed personas keep literal balanced tag examples in their original section; malformed or mixed current framing displays as the complete captured prompt. - Added regressions for the reported legacy persona case and trailing unframed text; verified 60 focused parser tests, 5,444 full Desktop tests, formatting, and typechecking. --- **Update Aug 25, 16:31 EDT:** Preserved slash-command prompt block boundaries. - Desktop now sets aside a valid connector-facing slash-command block only when the following block begins recognized paired framing, parses the semantic blocks normally, and retains the command as visible Prompt metadata. - Added an end-to-end transcript regression using the production three-block ACP shape, including the user bubble, author pubkey, event ID, and grouped prompt context. - Verified 113 combined transcript tests, 5,445 full Desktop tests, formatting, typechecking, and all applicable pre-push lanes. Generated with Codex --------- Signed-off-by: Salman Mohammed <smohammed@squareup.com> |
||
|
|
583af02299 |
fix(cli): preserve signatures in event reads (#6884)
## Summary
- preserve string Nostr `sig` values in normal `messages get`, `messages
thread`, `messages search`, and `feed get` output
- omit absent or non-string signatures instead of fabricating empty
values
- keep compact output at exactly `{id, content, created_at}` and update
CLI/managed-agent contract docs
## Validation
- `cargo test -p buzz-cli --lib` (367 passed at final head
`a2498faa0e07f90efc0ab0165606ba3b048327c7`)
- release candidate live-relay matrix: 49/49 normal events independently
recomputed to their NIP-01 IDs and passed BIP-340 verification; all four
compact commands preserved exact three-key output and ordering
- `git diff origin/main...HEAD --check`
- independent adversarial review: clear, no findings
## Compatibility
The normal JSON change is additive for valid relay events. In-repo
consumers use tolerant field lookup. Compact output is unchanged and
locked by regressions for both message and feed formatting.
Generated by Carl, an automated contributor working with Wes.
---------
Signed-off-by: Carl <9d00794d3df50972eb8b615511783cab12a77a8fd5dd5edd58073ec73b54bd8b@buzz.block.builderlab.xyz>
Co-authored-by: Carl <9d00794d3df50972eb8b615511783cab12a77a8fd5dd5edd58073ec73b54bd8b@buzz.block.builderlab.xyz>
|
||
|
|
ef0d202568 |
refactor(db): finish replaceable event store extraction (#6777)
## Why Finish the replaceable-event slice of [tracker #2](https://github.com/TheSentinel454/buzz/issues/2) and [domain issue #6](https://github.com/TheSentinel454/buzz/issues/6) without disturbing the runtime/store boundary established by #6660 and #6668. PR #6700 has merged; this PR now targets current main containing its replacement lock and transaction observability. ## What - Move `Db::replace_addressable_event`, its SQL, lock/transaction instrumentation, and focused addressable/parameterized replacement tests from `lib.rs` to `replaceable.rs` - Preserve the transaction-required parameterized API, replacement ordering, rollback semantics, mention indexing, and exactly one datastore span per public operation - Keep implementation and focused PostgreSQL tests co-located; follow-up review removed the dedicated replaceable ownership source guard as low value ## Stack - Exact base: main at |
||
|
|
8471049c43 |
feat(desktop): add KLIPY GIF search to composers (#5554)
## Summary - Supersedes #1913 with a KLIPY-hosted URL implementation. - Adds KLIPY GIF search and trending results to desktop message and forum composers. - Keeps selected GIFs hosted by KLIPY; Buzz stores only the external URL and media metadata (no imeta tag, since relays only accept hash-backed local `/media/` entries). - Aligns the Emoji/GIF picker with Buzz's standard segmented control, theme surfaces, and motion behavior. ## Relay-to-provider boundary - The relay proxies KLIPY search/share so the `BUZZ_KLIPY_API_KEY` never reaches the desktop; the key stays server-side behind a redacted `Debug` impl. - The dedicated GIF `reqwest` client sets `redirect::Policy::none()`. Because the API key rides in the request path, following a provider `3xx` could replay a key-bearing URL to an attacker-chosen host (an SSRF/key-disclosure primitive). With redirects disabled, a `3xx` returns as a non-success status that the handlers map to a generic `502`; the `Location` target is never read or forwarded. - Admission reuses the established NIP-98, membership, replay, and per-pubkey rate-limit gates, with an upstream response-size cap and allowlisting so KLIPY error bodies never cross the relay boundary. ## Accessibility - Under `prefers-reduced-motion: reduce`, the picker grid renders a static provider poster (a normalized `jpg` asset) instead of the animated preview, or a named static placeholder when no poster is available. It reacts to preference changes while mounted. `no-preference` keeps the animated preview. - Selected GIFs carry their title through `ImetaMedia`'s `displayLabel`, so the composer thumbnail, preview dialog, editor, lightbox, and remove control all derive one non-empty accessible name instead of an empty `Attachment ` label. Ordinary hashed uploads keep their existing hash-derived names. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Kenny Lopez <klopez4212@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Signed-off-by: Hayt <9e1c23a3fd83f61da34420e4e88ff1b16e45cafcc0cd9019eb07d4ecfa8ca9b0@buzz.block.builderlab.xyz> Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz> Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Hayt <9e1c23a3fd83f61da34420e4e88ff1b16e45cafcc0cd9019eb07d4ecfa8ca9b0@buzz.block.builderlab.xyz> |
||
|
|
ee6ca5fa28 |
Remove public relay signing key fallback (#6729)
Require `BUZZ_RELAY_PRIVATE_KEY` on every relay startup and remove the shared public fallback key completely. For local development, `just bootstrap` now generates a random key once in the gitignored `.env` file. Re-running bootstrap preserves that key, so relay-authored events retain the same identity across restarts. The local relay recipes reload `.env`, while isolated CI and E2E launchers pass explicit per-run keys. Startup fails immediately when the key is missing or invalid, before connecting to Postgres or Redis. Deployed relays continue to receive their stable key from the existing chart-managed secret. Validated with: - `./scripts/test-ensure-local-relay-key.sh` - `cargo test -p buzz-relay --bin buzz-relay` - `cargo clippy -p buzz-relay --bin buzz-relay -- -D warnings` - `cargo fmt --all -- --check` - `shellcheck scripts/ensure-local-relay-key.sh scripts/test-ensure-local-relay-key.sh` Signed-off-by: Jordan Mecom <jm@squareup.com> |
||
|
|
f249710331 |
Qualify canonical relay images for staged delivery (#6781)
## Why Phase 1 of progressive delivery needs an explicit eligibility boundary before deployment automation can safely consume relay artifacts. Today the canonical Docker workflow can publish independently of the same-SHA CI result, the relay does not expose its source/build identity, and the Helm chart only renders mutable tags. The existing `ghcr.io/block/buzz-staging-dev` workflow in #6709 remains a manual pre-merge preview lane. It is deliberately not a canonical promotion input. ## What changed - Compile the full source SHA and GitHub Actions build identity into canonical relay binaries and expose it at `/_status`. - Gate creation of tagged multi-architecture manifests on the latest exact-SHA `CI` push run succeeding on `main` or `release`. - Preserve the signed SLSA provenance and add a signed, release-only deployment-eligibility predicate with source, build run, qualifying CI run, and exact compatible Buzz chart version. - Keep failed or pending builds untagged and without an eligibility predicate; preview-package artifacts are excluded by package and signer contract. - Add optional `image.digest` support to Buzz chart `0.1.8`, retaining the existing tag fallback. - Document verification and the intentional boundary between image/chart compatibility and backwards-compatible database migrations. ## Risk and rollout Canonical tag publication now waits for same-SHA CI and fails closed when the newest matching run fails. Per-architecture blobs may remain untagged after a failed qualification; they are not promotion inputs. Debug manifests are also CI-gated but do not receive the release eligibility predicate. There are no migration or schema-orchestration changes. The companion BuilderBot infrastructure PR will pin a separately verified canonical main digest and expose a read-only running-deployment inspection command. Signed eligibility is available only for artifacts published after this workflow lands. ## Verification - Blox: workflow contract fixtures, including stale-green rejection and predicate structure - Blox: `actionlint` for `ci.yml` and `docker.yml` - Blox: 46 Helm unit tests, chart lint, and production/quickstart render fixtures - Blox: relay status unit test, workspace Rust formatting, and workspace all-target clippy - Live evidence: exact-SHA CI selector chose successful run `32857345153` for source `9d1e4b257657f382d3111ce748f3da8d063b7671` - Existing canonical digest provenance verified with `gh attestation verify` ## References - #6709 — developer-preview precursor; intentionally separate from canonical eligibility - `docs/deployment-identity.md` *Generated with Codex* --------- Signed-off-by: Luke Tornquist <tornquist@squareup.com> |
||
|
|
113a33b7e4 |
Add database pressure observability (#6700)
## Why Database pressure currently collapses several distinct delays into one symptom. This adds the evidence layer needed to distinguish pool acquisition wait, logical database operation time, advisory-lock wait, and selected transaction duration before changing timeout or retry policy. This is the phase 2 Lane A observability bundle for [#26](https://github.com/TheSentinel454/buzz/issues/26), [#28](https://github.com/TheSentinel454/buzz/issues/28), and [#33](https://github.com/TheSentinel454/buzz/issues/33). It is stacked on #6668. ## What - Record explicit reader/writer checkout wait and acquisition outcomes with `buzz_db_pool_acquire_wait_seconds` and `buzz_db_pool_acquisitions_total`. - Extend the compile-time `#[datastore_span(name = "...")]` seam with `buzz_db_operation_duration_seconds`, so operation labels remain static source literals instead of request data. - Route correctness-critical replacement, membership, push-gate, deletion, and migration/schema-safety advisory locks through one observer without changing their SQL, order, scope, or blocking behavior. - Measure six internally owned transaction lifetimes with `buzz_db_transaction_duration_seconds`, starting after `BEGIN` succeeds and ending after explicit commit/rollback or scope exit. - Emit root slow-operation warnings at 500 ms, logging the first slow completion and then 1/100 per call site with only `operation`, `outcome`, and `elapsed_ms`. - Document names, units, fixed label vocabularies, measurement boundaries, and blind spots in this PR description. Fixed labels are deliberately small: - `pool_role`: `writer`, `reader` - `lock_type`: `replacement`, `membership`, `push_gate`, `deletion`, `migration_schema_safety` - `outcome`: `success`, `error`, `timeout` where SQLx/PostgreSQL can distinguish it accurately - `operation`: compile-time datastore names plus the six closed transaction operation names documented in the runbook No metric or slow warning contains community IDs, event IDs, event kinds, coordinates, d-tags, SQL/query text, query IDs, returned errors, or event content. ## Coverage boundaries - Operation duration is the complete annotated logical function body, not pure SQL execution; it may include implicit checkout, lock wait, nested operations, and application work. Cancelled futures do not reach its completion hook. - Pool timing covers explicit helper checkouts, including proved-reader routing and selected writer-owned transactions. Implicit SQLx checkout through `&PgPool` remains folded into operation duration. - Lock timing covers application-side blocking locks in the five named families. Trigger/stored-procedure locks, channel-TTL locking, the usage try-lock, and the audit service session lock remain outside this slice. - Transaction timing covers only the six wholly owned boundaries documented in the runbook. It excludes pool wait, `BEGIN`, asynchronous rollback cleanup after an early return, and caller-owned `Db::begin_transaction` lifetime. ## Relationship to #6229 #6229 is the incident-driven timeout precursor. This PR does not add or change `statement_timeout`, `lock_timeout`, `idle_in_transaction_session_timeout`, retries, audit durability, or client-visible conflicts. It provides the missing distributions needed to evaluate those policies later and intentionally leaves #6229's open audit retry/durability finding untouched. The branches overlap in `crates/buzz-db/src/lib.rs` and `crates/buzz-db/src/migration.rs`, so a later rebase may need textual conflict resolution, but the behavior is complementary rather than duplicated. ## Risk assessment Moderate-low. The primary risk is instrumentation overhead and added static series. Cardinality is source-bounded, slow logs are sampled/redacted root events, and the lock/transaction changes wrap existing awaits without changing policy or ordering. ## Verification Author workstation: `buzz-tornquist-db-pressure-observability` (`2010927`), exact head `d7cf833e26c528adfcde3917ded80daf6f4ddac9`, parent `6f50e6b2b2a996349149af61d35bdd6a355f77fd`. - `cargo fmt --all --check` — passed - `cargo clippy -p buzz-datastore-tracing -p buzz-db -p buzz-audit -p buzz-search -p buzz-relay --all-targets -- -D warnings` — passed - `cargo test -p buzz-datastore-tracing --quiet` — 4 passed - `cargo test -p buzz-db --quiet` — 109 passed, 200 ignored - `cargo test -p buzz-audit -p buzz-search --quiet` — 16 passed, 25 ignored - `cargo test -p buzz-relay --lib --quiet -- --test-threads=1` — 906 passed, 48 ignored - Native PostgreSQL focused tests for pool success/timeout/error, lock success/contention/timeout/error, replacement, membership serialization, push ordering, deletion fencing, migration/schema exclusion, and reader fallback — 8 passed The default-parallel relay run passed once; subsequent runs exposed the existing load-sensitive `api::mesh_demo::tests::demo_join_forwarded_arm_round_trips_echo` 504 at the end of the suite. That test passes in isolation and the full relay suite passes serially. Independent exact-head review workstation: `buzz-tornquist-db-pressure-observability-review` (`2013067`). Formatting, the same all-target clippy command, datastore instrumentation tests, DB unit tests, source privacy guards, and diff/non-goal audits passed; no review findings. Generated with Codex --------- Signed-off-by: tornquist <tornquist@squareup.com> |
||
|
|
d12dea4e67 | Support community deletion in versioned media buckets (#6738) | ||
|
|
a1219070fa |
feat(buzz-agent): gate LLM tool calls on session/request_permission (#5712)
## Summary
buzz-agent now authorizes every LLM-issued MCP tool call through
`session/request_permission` before it executes, instead of running
tools unconditionally. The agent **always asks**; the client applies
`BUZZ_ACP_PERMISSION_POLICY` and answers. buzz-agent never reads the
policy — this keeps the policy decision on the client side, matching the
layering of the other ACP harnesses, and avoids duplicating policy logic
that would drift.
### Broker
A crate-local `PermissionBroker`
(`crates/buzz-agent/src/permission.rs`), owned by `App` for the
connection lifetime, owns the full request-correlation lifecycle:
- **Process-wide admission.** A global `Semaphore`
(`BUZZ_AGENT_MAX_PENDING_PERMISSIONS`, default 32, validated `>= 1`) is
acquired *before* any correlation entry is inserted. This is the
security bound: the per-turn tool semaphore is constructed fresh per
turn and `max_sessions` is unbounded by default, so neither bounds
simultaneously-outstanding asks process-wide.
- **Abort-safe cleanup.** A successful admission returns a
`PendingPermission` lease that owns the admission permit and the
correlation id. Its `Drop` synchronously removes the still-pending entry
and releases the slot, covering task abort/panic that bypasses the
normal `run_prompt` tail.
- **At-most-once resolution.** `deliver` claims (removes) the entry
*before* waking the waiter, so each id resolves once and a later lease
`Drop` is a no-op. Unknown/late ids are logged and dropped; only ids the
broker minted (`perm-<n>`) are recognized.
- **Single absolute deadline.** Admission, request enqueue, and response
wait all share one deadline (`BUZZ_AGENT_PERMISSION_TIMEOUT_SECS`,
default 330s, validated `>= 1`), so a saturated call cannot outlive one
timeout window even when a stalled writer blocks the enqueue.
Cancellation races inside every wait — resolution never depends on the
outer abort drain. A writer that dies mid-connection (stdout closed, or
a blocking write that only surfaces its error at flush) is
connection-fatal: it cancels all sessions, which resolves any ask
waiting on a reply that can never be written.
### Wire
`request_permission_params` (`crates/buzz-agent/src/wire.rs`) is
version-aware, keyed on the protocol version negotiated at `initialize`
and stored on `App` for the connection lifetime (never derived from a
later mutable session field). v2 nests the tool call under `subject:
{type: "tool_call", toolCall}` with top-level `title`/`options`; v1 uses
the legacy top-level `toolCall`. No hybrid shape. Both offered options
(`allow_once`, `reject_once`) carry `optionId == kind`, so the client's
`kind`-based selection and this side's `optionId`-based predicate agree
without a lookup table.
### Gate
In each spawned tool task (`crates/buzz-agent/src/agent.rs`) the
sequence is: acquire per-turn permit → argument-shape validation →
broker admission + request + wait → cancellation recheck →
`emit_in_progress` → `mcp.call`. Argument-shape validation is hoisted
out of `mcp.rs::do_call` into `validate_arg_shape` so a malformed
non-object argument is rejected locally without prompting for a call
that could never execute.
Authorization is fail-closed, stated once in `evaluate`: execute IFF
`outcome.outcome == "selected"` **and** the selected `optionId` equals
the offered allow option. Every other shape (reject, cancelled, JSON-RPC
error, malformed, unknown outcome, wrong/unknown option, timeout,
wire-channel closure) denies with a synthetic tool error, and the turn
continues.
### Scope
Only LLM-issued MCP calls are gated. The built-in `load_skill` tool and
`call_hooks` lifecycle calls (`_Stop`, `_PostCompact`) are exempt — they
are not model-issued. `readOnlyHint` is never treated as a security
boundary.
First cut ships `allow_once`/`reject_once` only; session-scoped grants
are deliberately out of scope.
### Related issue
Part of #4938. This PR and
[#5106](https://github.com/block/buzz/pull/5106) jointly implement the
feature: #5106 is the client-side policy engine and permission cards;
this PR is buzz-agent's asking side (`session/request_permission`).
Neither closes #4938 alone.
---------
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Hayt <41ea58f1e64c243627e8acde7c89be667052ee6e17d8f021c1195be4324ebf04@buzz.block.builderlab.xyz>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
|
||
|
|
9d1e4b2576 |
Extract community persistence (#6668)
## Why Community persistence is the next incremental `buzz-db` store extraction, keeping tenant lifecycle SQL, records, tests, and instrumentation out of the database runtime module without changing behavior. ## What - Move community records and the existing `impl Db` operations into `community.rs` while preserving crate-root re-exports. - Move focused PostgreSQL tests with the implementation and enforce single ownership for each method and datastore span. ## Risk Assessment Low — this is a structural move of the existing records, SQL, method bodies, and focused tests; database runtime concerns, schema, and behavior remain unchanged. ## References - Architecture guardrail: https://github.com/TheSentinel454/buzz/issues/34 - Incremental tracker: https://github.com/TheSentinel454/buzz/issues/2 - Primary task: https://github.com/TheSentinel454/buzz/issues/5 - Stacked on: https://github.com/block/buzz/pull/6660 Generated with Codex Signed-off-by: tornquist <tornquist@squareup.com> |
||
|
|
8d2d0ff5ad |
Centralize replaceable event persistence (#6660)
## Why Command persistence duplicated NIP-33 replacement SQL in the relay and obscured the boundary between database runtime concerns and domain-store behavior. This proving slice establishes that boundary inside the existing `buzz-db` crate. ## What - Centralize parameterized-replaceable coordinate locking, ordering, replacement, watermark, and mention-indexing behavior in `buzz-db` - Expose transaction-required replacement and ordinary-insertion seams while keeping transaction ownership and workflow conflict messages in the relay - Cover concurrency, same-second ties, stale writes, replay, caller rollback, nested rollback recovery, and mention-index atomicity with focused PostgreSQL tests ## Risk Assessment Medium — this changes core event persistence and command idempotency paths, while intentionally preserving NIP-33, NIP-RS, mesh, and workflow conflict semantics. ## References - Architecture guardrail: https://github.com/TheSentinel454/buzz/issues/34 - Proving slice: https://github.com/TheSentinel454/buzz/issues/3, https://github.com/TheSentinel454/buzz/issues/4, https://github.com/TheSentinel454/buzz/issues/6 Generated with Codex --------- Signed-off-by: tornquist <tornquist@squareup.com> |
||
|
|
2d280376ad |
perf(desktop): persist channel heads, collapse thread reads and reply sends (#6572)
## Summary
Lands the build-now items from the desktop latency plan
(#ui-performance-deep-dive) as one change. Every perceived-latency hot
path a user hits on launch, channel open, thread open, and reply send
drops one or more round trips.
**A1 — persisted channel heads (the big one).** Native WAL SQLite cache
(`desktop/src-tauri/src/channel_head_cache.rs`) keyed by `{pubkey,
relayUrl}` scope, 32 rows/scope LRU, 1 MiB per-row drop cap,
schema-version reset, corrupt-row tolerance, checkpointed on shutdown.
Three blocking-pool commands: `channel_head_cache_load` / `_store` /
`_clear`. On the renderer side, `CommunityQueryProvider` kicks off
hydration of up to 12 heads when it constructs the query client — the
app, splash and relay preconnect mount immediately; only
`useChannelMessagesQuery` awaits the seed (`channelHeadHydration`), then
consumes a one-shot hydrated gate so a hydrated channel pays **zero**
`get_channel_window` calls on mount and exactly **one** on the
post-subscription refresh, whose response replaces page zero wholesale.
That refresh fires whether live-subscription setup succeeds or fails,
and is sequenced behind hydration so it is always a distinct
authoritative fetch (see Review follow-ups). Bounds-only persisted heads
(zero rows) are not hydrated and take the cold loading path. The
timeline loading latch recognizes native-hydrated rows as restart-safe
so they paint immediately instead of holding a skeleton. The cache is a
paint accelerator only — the relay response is always authoritative.
Replaces the legacy localStorage `messageSnapshot.ts` (removed, -401
lines).
Kill switch: `VITE_BUZZ_CHANNEL_HEAD_CACHE=off` at build time or
`localStorage["buzz-channel-head-cache"] = "off"` at runtime. Cache is
cleared on community removal and scoped per identity, so a replaced
signer never sees the previous identity's rows.
**B1 — thread aux in one response.** Relay thread filters accept
`include_aux`; the bridge appends the same authorized two-hop
reactions/edits/deletions closure a channel window gets
(`build_aux_query` shared with the window path). Renderer
`useThreadReplies` drops its two follow-up aux fetches. `next_cursor` is
computed from reply-kind rows only since aux rows are unpaged.
Documented in `docs/bridge-channel-window.md`. Thread queries keep
`staleTime: 0` (`bcfe04e2f`): an earlier revision raised it to 30s,
which CI's `thread-unread.spec.ts` caught — once the user leaves a
channel, the live subscription stops feeding that thread's cache, so a
reopen must always take the (now single) authoritative read.
**B2 — cached root on reply send.** `send_channel_message` gains
`root_event_id`; when the renderer already holds the parent (channel or
thread cache) it passes the NIP-10 root, and native signs without the
relay round trip that `resolve_thread_ref` used to make. Strict hex
parse; `root_event_id` requires `parent_event_id`; absent root falls
back to the existing relay resolution. The renderer never sends a
guessed root.
**B4** general HTTP pool idle 10s→300s, max idle per host 1→2. **B5**
relay preconnect fires as soon as identity is ready instead of waiting
for `requestIdleCallback`. One e2e test (`relay-reconnect.spec.ts`
"service restart close resets accumulated backoff") had been relying on
the idle-callback batching to skip past its own seeded dial failures
before the channel list painted; `8133d70bb` makes it wait for the
connected state instead (test-only, still fails with the 1012 backoff
reset disabled). **B6** profile freshness 60s→10 min (both the in-memory
entry check and the query `staleTime`). Tradeoff: another user's
display-name/avatar edit can take up to 10 min to propagate to a client
that already holds their profile (relay reconnect refetches
`users-batch` but resolves from the still-fresh per-pubkey entry); your
own edits still evict the entry immediately (`evictUsersBatchEntries` in
`useUpdateProfileMutation`).
### Related issue
Follows #6456/#6457/#6459/#6460 (already merged). #6455 is the
measurement instrument and is intentionally not folded in. No duplicate
PR found.
### Review follow-ups
Addressing Carl's reviews
[5001114109](https://github.com/block/buzz/pull/6572#pullrequestreview-5001114109)
and
[5002596542](https://github.com/block/buzz/pull/6572#pullrequestreview-5002596542),
each pushed as new commits (no rebase):
- `4f06b7770` fix(desktop): mount app while channel heads hydrate;
always revalidate — provider no longer gates children on the cache load;
`refreshAfterSubscribe` runs on subscribe failure too; bounds-only heads
skipped at seed; seed merges into an existing window store. +3 tests.
- `35834cb31` fix(relay): drain aux closure hops across the page clamp —
`query_all_pages` walks the `(created_at, id)` keyset via
`until`/`before_id` until a short page (`AUX_PAGE_LIMIT` =
`DEFAULT_MAX_PAGE_LIMIT`, `AUX_MAX_PAGES` = 64 warn+truncate) so
one-shot `limit: 1000` newest-first no longer drops the oldest
edits/deletions. +3 tests; `docs/bridge-channel-window.md` updated.
- `db21b0531` merge of `origin/main` `e23632941` (#6558, #6312 — no
overlap).
- `5a5566c0f` fix(desktop): sequence post-subscribe refresh behind
channel head hydration — `refreshChannelWindowMessages` awaits
`channelHeadHydration()` and, for a hydration-seeded query (`data !==
undefined && dataUpdatedAt === 0`), the in-flight snapshot fetch before
invalidating. Without this, a subscription that settles before the
SQLite load invalidated a data-less in-flight query; TanStack dedupes
that onto the existing fetch (`query-core` `fetch()` only cancels when
`state.data` exists), which returned the seeded snapshot — 0
authoritative fetches. Regression test reproduces Carl's exact ordering
(fails at `35834cb31` with 0 calls), plus a cold-channel guard that the
fix does not double-fetch.
- `b129231c8` fix(desktop): let concurrent post-hydration refreshes
share one window fetch — found independently by Max and Wren reviewing
`5a5566c0f`: subscribe settlement + reconnect both wake on the same
snapshot promise and both invalidate; the second (default
`cancelRefetch: true`) cancelled and replaced the first authoritative
fetch (3 queryFn calls, not 2, and the cancelled Tauri invoke still hits
the relay). The seeded branch now invalidates with `cancelRefetch:
false` so a second waker joins the in-flight fetch; cold/warm keep the
default (`test_canceled_stale_fetch_cannot_overwrite_catch_up_window`
relies on it). Concurrent regression test fails at `5a5566c0f` with 3.
### Testing
At `b129231c8` (PR head; verified in one shell with `git rev-parse HEAD`
= `b129231c8`): `pnpm check`, `tsc --noEmit`, desktop unit 5,393 / 0,
Playwright `boot-splash` + `channel-head-restart` + `relay-reconnect` +
`relay-reconnect-affordance` + `thread-unread` 34 / 34 on a fresh
`build:e2e`, pre-push hooks green.
At `5a5566c0f`: `pnpm check`, `tsc --noEmit`, desktop unit 5,392 / 0,
Playwright `boot-splash` + `channel-head-restart` + `relay-reconnect` +
`relay-reconnect-affordance` + `thread-unread` 34 / 34 on a fresh
`build:e2e`, pre-push hooks green.
At `35834cb31`: desktop unit 5,390 / 0; `cargo test -p buzz-relay --lib`
910 / 0; fmt + clippy `-D warnings` clean; Playwright 32 / 32 (same
specs minus affordance); GitHub CI green on every job except Smoke (3)
(unrelated project-review row-count + messaging timing flake, per Carl)
and Unit Tests (sherpa cache skeleton, below).
Earlier, all at `8133d70bb` (this PR head is `0c492366d` =
|
||
|
|
f99532585a |
refactor(acp): clarify agent prompt sections (#6501)
## Why The ACP prompt puts a machine-specific Workspace prefix before static base guidance and labels the user-facing agent instruction layer as the generic System section. Because the cwd varies by launch and worktree, leading with it reduces reusable prompt-prefix stability. `[Workspace]` was added in [PR #1194](https://github.com/block/buzz/pull/1194) as a defensive fix after a broken `~/.sprout` → `~/.buzz` migration caused agents to scan `$HOME` and trigger macOS TCC prompts. This change retains that grounding while shrinking it to the current working directory and moving dynamic environment context after the static Base prompt. ## What - Emit the prompt in Base → Workspace → Agent Instructions order - Reduce Workspace to `Current working directory: <absolute path>` - Resolve cwd as an absolute native-platform path and preserve Windows drive/UNC paths instead of checking for a leading `/` - Emit Agent Instructions for persona and standalone agent instructions across modern and legacy ACP paths - Preserve parsing for archived observer frames that used System or the former Workspace-before-Base order, and align the persona catalog label ## Risk Assessment Medium-low — this changes prompt framing for every newly created agent session. Existing archived observer frames remain parseable, and execution still uses the same ACP working directory. Cwd resolution now fails clearly instead of substituting `/` when the process directory cannot be resolved. ## References - https://github.com/block/buzz/pull/1103 - https://github.com/block/buzz/pull/1194 Generated with Codex --------- Signed-off-by: Salman Mohammed <smohammed@squareup.com> |
||
|
|
8c0f42e187 |
Add mobile Huddles voice MVP (#6056)
## Summary - add foreground mobile Huddles on Android and iOS with native Opus capture/playback, mute, speaker routing, participants, lifecycle, and minimized drawer UI - keep mobile Huddle cards and roster state live, including ended rooms, relay-resolved profiles, and agents - broadcast desktop agent TTS through the existing Huddle audio protocol ## Scope Foreground human-to-human voice MVP only. Agent setup/transcripts, background calling, recording, and advanced device controls remain out of scope. ## Validation - `just mobile-check` - `just mobile-test` — 1,500 passed - `just desktop-check` and `just desktop-test` — 4,957 passed - desktop typecheck, strict Clippy, and Tauri tests — 2,445 passed, 15 ignored - mobile worktree identity contract checks - physical Pixel/iPhone behavior reviewed during development --------- 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> Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz> Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz> Co-authored-by: leader <71e9f2c44a6932b6772caaaccda1911d010463c3e2c6c40410b8329956046801@buzz.block.builderlab.xyz> Co-authored-by: Tom Brow <tomb@block.xyz> Co-authored-by: Mongo <9cfd347903944d5b85aa6c93d2ab67381b978a92a31914bca69998968752a1d7@buzz.block.builderlab.xyz> |
||
|
|
47526784d5 |
fix(deletion): allow IRSA S3 credentials (#6495)
## Why `buzz-admin deletions` runs inside bb-public relay pods, where S3 credentials are provided by the pod credential chain rather than static `BUZZ_S3_ACCESS_KEY` / `BUZZ_S3_SECRET_KEY` values. The deletion CLI was requiring those two env vars to be non-empty before constructing the shared media storage client, so it could not reach the existing default AWS credential chain. ## What - Treat missing/blank deletion S3 access key and secret key as an empty credential pair so `MediaStorage::new` can use `Credentials::default()`. - Keep the existing static-credential path unchanged when both values are non-empty. - Keep deletion unit tests deterministic by covering only deletion env normalization for missing/blank pair, trimmed static pair, and partial/whitespace-partial outputs; shared media tests continue to own credential-pair enforcement. ## Risk Assessment Low and scoped to the operator-only community deletion CLI startup path. The shared media storage credential validation still owns static-vs-default credential selection and still rejects mixed partial credentials. ## Testing At committed head `0a86c2914b1f97caf4788a771048aa8d9d9d88ac` with a clean worktree before and after (`git rev-parse HEAD` before/after matched): - `just fmt-check` — passed. - `cargo test -p buzz-deletion` — passed: 12 passed, 9 ignored. - `cargo test -p buzz-media` — passed: 120 passed; `static_creds_round_trip_against_minio` remained ignored because it requires live MinIO. - `cargo test -p buzz-admin` — passed: 1 passed. - `cargo clippy -p buzz-deletion --all-targets -- -D warnings` — passed. - Startup smoke at the same head: built `buzz-admin`, then ran `target/debug/buzz-admin deletions drain` with `BUZZ_S3_ACCESS_KEY=` and `BUZZ_S3_SECRET_KEY=' '` plus `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` fallback credentials; command exited `0`, proving startup transitions past deletion S3 key validation and exercises the shared default credential-chain branch using AWS env fallback credentials. - `git push origin HEAD:seiler/deletion-irsa-credentials` — passed; pre-push hooks passed. Not run: the full `TESTING.md` live-local relay workflow. Docker Desktop currently refuses CLI access on this machine with `Sign in to continue using Docker Desktop. Membership in the [squareup] organization is required.` ## References - Buzz channel: `buzz://message?channel=9e4aabc6-414c-4978-aba7-b9f5228776de&id=177c5b8ad9e78a647f438ec7040d25f0c20a7c2b8678dd0e34768effe053f7f4` Generated with Codex --------- Signed-off-by: coder 0 <d97ebdbb198c7237c94f84ea8bb8a73583ea067407eebd0062abbb3962527fb1@buzz.block.builderlab.xyz> Co-authored-by: coder 0 <d97ebdbb198c7237c94f84ea8bb8a73583ea067407eebd0062abbb3962527fb1@buzz.block.builderlab.xyz> |
||
|
|
025425591e |
fix(benchmarks): wait for scripted event delivery (#6487)
## Why The Buzz-native runtime could stop a solo agent after one turn even when a scripted follow-up event was queued or already running. That could turn a harness timing race into a benchmark failure. Follow-up to #6448. ## What - Treat solo tasks with scripted events separately from ordinary one-turn tasks. - Wait until turn counts and authored message IDs remain idle and unchanged for several polls before stopping the agent. - Remove the message-count shortcut that could stop an active turn. - Add focused regressions for a delayed follow-up turn and an already-running turn with a `DONE:` message. ## Testing - `pytest -q tests/test_container_runtime.py -k "solo_turn_end or scripted_events"` — 3 passed - `ruff check` on the three touched Python files - `ruff format --check` on the three touched Python files - Local `buzz-native-solo-luna.yaml` Harbor run, one attempt per affected task: - `cross-thread-requests` — reward 1.0; all 6 task-specific checks passed - `interleaved-agent-reports` — reward 1.0; all 7 task-specific checks passed - 2 completed trials, 0 exceptions, 56 seconds total ## Risk Low. This only changes completion detection for solo benchmark tasks with scripted events. Ordinary solo tasks retain immediate completion after their first turn ends. --- **Update Aug 21, 13:07 EDT:** Replaced the fixed settle delay with exact delivery receipts after [review feedback](https://github.com/block/buzz/pull/6487#discussion_r3832050452). - ACP now records the event IDs delivered by each completed turn; the runtime also recognizes existing successful-steer receipts. - Scripted trials stop only after every expected event ID is acknowledged and no turn is active. Missing receipts wait for the trial budget instead of producing partial evidence. - TDD regression delayed delivery beyond the old five-poll boundary and failed before the implementation; the receipt parser and Rust receipt format are pinned independently. - Focused verification: 31 container-runtime tests passed, 6 ACP delivery tests passed, plus Ruff, rustfmt, and Clippy. - Receipt-gated Luna run: 2 completed trials and 0 exceptions. `interleaved-agent-reports` scored 1.0. `cross-thread-requests` completed both calculations and thread isolation but Luna omitted the user mention on ALPHA, so that model-output dimension scored 0. - Risk remains low: the ACP production change adds delivery receipt logging and restores a missing turn-end log on a completed-before-control race; it does not change queue dispatch behavior. Generated with Codex --------- Signed-off-by: Salman Mohammed <smohammed@squareup.com> |
||
|
|
24ec6a468e |
Repair stale large channel roster snapshots (#6251)
## Summary - detect relay-authored NIP-29 kind 39002 roster snapshots truncated by the former 1,000-member query cap and repair stale large rosters during relay startup - serialize canonical roster capture and replacement with membership writes, preserving tenant, channel, signer, pubkey, and role boundaries through mixed-version deployments - install migration 0032's fail-closed roster fence on the partitioned events table and verify its catalog shape plus behavior before opening relay listeners ## Rollout Migration 0032 is a hard schema-before-code compatibility boundary. Apply migrations before rolling this relay version. Startup refuses to open listeners when the parent/partition triggers are missing, disabled, mis-shaped, or behaviorally inert. For large installations, prefer `buzz-admin migrate` and monitor lock acquisition as documented in the chart README. ## Validation Exact head: `bcbba271f54bc0046a6683007e5a2b70403a11d5` - rebased onto `569308c23c9c2bf620dd3a9a5e4baecbcfa22e16`; the nine-file feature patch is byte-identical to pre-rebase head `be8ea0084f4d4c78c7c2550baad4399e4df8ce73` - pre-push hook passed at exact head: branch-skew, file-size, full Rust unit suite, Desktop Tauri clippy, and Desktop Tauri tests - `cargo fmt --all -- --check` - `cargo test -p buzz-relay group_members_snapshot_keeps_members_past_one_thousand -- --nocapture` - focused CI-mode Playwright regression: `selected relay agents revoked after the invite prompt cause no side effects` passed at exact head - prior exact-patch validation: `large_roster_reconciliation_candidates_respect_snapshot_count_and_signer`, mixed-writer locking/rollback, migration admission, partition trigger coverage, and desired-schema parity regressions ## Review Independent DB/relay review found no blocking issues in the exact feature patch. The concurrency fence holds the established replacement and membership locks on one transaction/connection through replacement; failures roll back both soft-delete and insert. Reconciliation remains tenant/channel/signer scoped and validates exact normalized pubkey-plus-role membership. The prior red Desktop shard was unrelated to this backend-only diff: its mocked mention test exercises no relay, database, or migration path. It reproduced as a timing flake on the old head, passed on retry/base, and now passes locally after rebasing onto current main. --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz> |
||
|
|
c6e3fe7dfa |
feat(workflows): reply in-thread from send_message action (#6178)
The `send_message` workflow action could only post new top-level channel
messages. This adds a `reply_in_thread` option so a `message_posted`
workflow can reply in the triggering message's thread, plus a
`trigger_is_reply` filter variable so a workflow can fire only on
top-level messages.
Every NIP-10 thread-marker reader in the tree now routes through one
shared parser and one collapse rule in `buzz-core`, deleting four
hand-rolled copies that had drifted on id-validity and marker semantics.
## What changed
- **Schema** (`crates/buzz-workflow/src/schema.rs`): new
`reply_in_thread: bool` (serde default `false`) on
`ActionDef::SendMessage`. `validate()` rejects `reply_in_thread: true`
on `schedule`/`webhook` triggers — they carry no message to reply to.
- **Executor** (`crates/buzz-workflow/src/executor.rs`): threads
`reply_to: Option<&str>` (the trigger `message_id`) through
`ActionSink::send_message` when `reply_in_thread` is set; errors clearly
if the trigger has no `message_id`. `resolve_step_templates` carries the
new field. Adds `trigger_is_reply` (boolean) to the eval context.
- **Relay sink** (`crates/buzz-relay/src/workflow_sink.rs`): when
`reply_to` is set, resolves parent/root/depth from the known trigger
event, persists real thread metadata instead of the hardcoded top-level
`depth: 0`, and pushes the live kind:39005 thread-summary overlay after
insert so subscribed desktops update the root's reply badge without
refetching — matching the human ingest path. Emits the same NIP-10
`e`-tag shape as `buzz_sdk::builders::thread_tags`: a single `["e", id,
"", "reply"]` tag for a direct reply (parent == root), and the `root` +
`reply` pair only when nested — so every writer produces one wire shape
per reply kind.
- **Ingest resolver** (`crates/buzz-relay/src/handlers/ingest.rs`): new
`resolve_relay_reply_thread_meta` + `ReplyAncestry`. Unlike the
client-facing `resolve_nip10_thread_meta` (which validates
client-supplied tags), this computes root/depth from the known parent
and enforces the same same-channel and depth-limit invariants. When the
parent has no `thread_metadata` row, both resolvers share
`derive_ancestry_from_parent_tags`, which applies
`ThreadMarkers::resolve()`: a marked nested parent remains depth 2,
while a root-only, malformed, or unmarked parent is top-level and starts
its reply thread at itself. Both the client resolver and the
parent-ancestor fallback now read markers via the shared parser instead
of hand-scanning `e`-tags.
- **Shared NIP-10 parser** (`crates/buzz-core/src/nip10.rs`):
`parse_thread_markers` reads an event's `root`/`reply` markers, honoring
a marker only when its event id is exactly 64 ASCII-hex characters.
`ThreadMarkers::resolve()` is the single definition of the (root, reply)
→ (root_id, parent_id) collapse: `root`+`reply` as-is, `reply`-only →
`(reply, reply)`, a lone `root` or neither → top-level. A slice-based
`parse_thread_markers_from_parts` entry point serves consumers holding
raw JSON tag arrays. Relay ingest, the workflow `trigger_is_reply`
predicate, ACP anchoring, and the CLI reply resolver all call these, so
they cannot drift on marker, id-validity, or collapse semantics.
- **ACP** (`crates/buzz-acp/src/queue.rs`): `parse_thread_tags`
delegates marker parsing and collapse to `buzz_core::nip10`, keeping
only its local `p`-tag mention collection. This fixes a parity gap with
ingest: a malformed non-64-hex marker id is no longer counted as a
thread link, and a lone `root` marker is now top-level rather than being
treated as root == parent.
- **CLI** (`crates/buzz-cli/src/commands/messages.rs`):
`find_root_from_tags` routes its JSON tags through the shared slice
parser and collapse rule. CLI replies to root-only, malformed, or
unmarked parents now correctly start at that immediate parent;
reply-only and root+reply parent ancestry remain intact.
- **Trigger context** (`crates/buzz-workflow/src/lib.rs`):
`build_trigger_context` derives `is_reply` solely from a valid NIP-10
`reply` marker (no DB hit). A lone `root` marker is top-level to ingest,
so it does not count; neither does a `reply` marker whose event id is
malformed, nor a bare/unmarked `e`-tag (mentions, quotes).
- **Desktop** (`workflowFormTypes.ts`, `WorkflowStepCard.tsx`):
`replyInThread` form field with YAML round-trip, and a "Reply to
triggering message in thread" checkbox shown only for message-based
triggers. Switching to `schedule` or `webhook` clears the option on
every step before serialization, so a value hidden behind an action
change cannot resurrect when the action is switched back. The form
parser also requires `reply_in_thread` to be a boolean and refuses
`reply_in_thread: true` under an ineligible trigger, falling back to
YAML mode rather than silently normalizing a backend-invalid definition.
## Usage
```yaml
trigger:
on: message_posted
filter: trigger_is_reply == false
steps:
- id: auto_reply
action: send_message
text: "pre-written reply text"
reply_in_thread: true
```
---------
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
|
||
|
|
b728a2af31 |
feat(llm): stamp thinking effort on call-completed log line (#6424)
The performance-analysis pipeline measures harness × provider × model × effort from production logs, but thinking effort was the only dimension not stamped per call — it had to be attributed by config-changeover timestamp. Add `thinking_effort = ?cfg.thinking_effort` to the `llm: call completed` `tracing::info!` in `Llm::complete()` so every call self-describes all four dimensions. `?`-formatting preserves the `None`-vs-`Some(...)` distinction the same way the token fields keep None-vs-zero. Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> |
||
|
|
84c095f8be |
feat(cli): accept Buzz message links for thread reads (#6359)
## Summary - let `buzz messages thread` accept a canonical `buzz://message` link directly - treat the selected message ID as authoritative, verify its channel, and derive its containing thread root from the event - reject stale or forged `thread` hints that disagree with the selected message - preserve the existing `--channel` / `--event` form while allowing it to target any message in a thread ## Why Agent skills should be able to hand first-class Buzz links to the public Buzz CLI instead of carrying their own URL parsing wrappers. Link handling belongs with the CLI that owns the protocol; identity and relay selection remain explicit through the existing Buzz CLI environment and do not read Buzz Desktop private state. ## Verification - `cargo test -p buzz-cli` — 360 passed - `cargo clippy -p buzz-cli -- -D warnings` - `cargo fmt --all -- --check` - repository pre-push gates, including 5,070 Desktop tests - final architecture/code review — no must-fix findings A relay-backed manual smoke test was not run because this session did not have `BUZZ_RELAY_URL` and `BUZZ_PRIVATE_KEY` configured; the change has focused parser, CLI argument, channel/root binding, and mismatch tests. --------- Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> |
||
|
|
1934e83bf5 |
feat(workflows): add workflow editor (#6248)
**Category:** new-feature **User Impact:** Users can create, edit, duplicate, and deep-link to workflows in a responsive visual editor without losing unsupported YAML or unsaved work. **Problem:** Workflow editing was split across disconnected surfaces and lacked reliable URL state, lifecycle protection, and parity between Form and YAML modes. **Solution:** This adds a route-addressable editor foundation with stable pane identity, guarded dirty exits, lossless Form/YAML transitions, responsive workflow and channel controls, and matching reaction-filter execution support. <details> <summary>File changes</summary> **crates/buzz-workflow/src/lib.rs** Apply reaction trigger filters during workflow execution and cover target-message gating. **crates/buzz-workflow/src/schema.rs** Extend the reaction trigger schema with the editor-owned filter field. **desktop/src/app/navigation/useAppNavigation.ts** Add navigation helpers for explicit workflow create, edit, and duplicate editor modes. **desktop/src/app/routes/WorkflowsRouteScreen.tsx** Coordinate route state with the shared workflow library and editor dialog. **desktop/src/app/routes/lazyWorkflowsRouteScreen.ts** Share one lazy route component across workflow route entry points to avoid loading flashes. **desktop/src/app/routes/workflows.$workflowId.tsx** Parse workflow editor modes and pane deep links for workflow-specific URLs. **desktop/src/app/routes/workflows.tsx** Parse library-level create state and render the shared workflow route screen. **desktop/src/app/AppWorkflowEditorOverlayProvider.tsx** Host the shared workflow editor at the app-shell level so channel-originated workflow dialogs stay above the active channel instead of replacing it. **desktop/src/shared/context/WorkflowEditorOverlayContext.tsx** Expose route-independent open-existing and create-new workflow actions to channel settings. **desktop/src/features/workflows/ui/WorkflowEditorHost.tsx** Share editor loading, unavailable, and dialog lifecycle wiring between canonical workflow routes and the channel overlay. **desktop/src/features/channels/ui/ChannelManagementSheet.tsx** Add an experiment-gated, Canvas-style Workflows ingress below Canvas, including channel-scoped loading, error, empty, and list states plus open/create actions; disabled users issue no workflow query. **desktop/src/features/channels/ui/ChannelWorkflowsSection.tsx** Render the channel workflow list and New workflow action without pushing the existing settings sheet past its file-size ceiling. **desktop/src/features/workflows/ui/ChannelCombobox.tsx** Adopt the final channel presentation, portalled scrolling, and one-shot create-flow opening behavior. **desktop/src/features/workflows/ui/CreateWorkflowDialog.tsx** Remove the superseded create-only dialog in favor of the unified workflow editor. **desktop/src/features/workflows/ui/WorkflowCard.tsx** Open workflow cards in the detail and run-history modal while preserving explicit edit and duplicate actions. **desktop/src/features/workflows/ui/WorkflowDetailDialog.tsx** Present workflow Trigger/Steps in the shared modal chrome, with top-chrome ingress to a responsive right-side run-history inspector and an explicit edit action. **desktop/src/features/workflows/ui/WorkflowDialog.tsx** Unify create, edit, and duplicate lifecycle handling with URL panes, generated-name synchronization, dirty-exit guards, stale-write preservation, and protected webhook-secret handoff. **desktop/src/features/workflows/ui/WorkflowFormBuilder.tsx** Build the responsive Form/YAML editor shell, stable step selection, insertion and removal behavior, and lossless canonical-YAML synchronization. **desktop/src/features/workflows/ui/WorkflowStepCard.tsx** Align workflow step controls and presentation with the final editor interaction model. **desktop/src/features/workflows/ui/WorkflowUnavailableDialog.tsx** Show a non-disclosing loading or unavailable state for missing and inaccessible workflow links, with retry and close actions. **desktop/src/features/workflows/ui/WorkflowWebhookSecretDialog.tsx** Obscure one-time webhook secrets by default and require explicit confirmation before any close or navigation discards them. **desktop/src/features/workflows/ui/WorkflowsScreen.tsx** Connect library state and workflow actions to the route-addressable editor. **desktop/src/features/workflows/ui/WorkflowsView.tsx** Restore the responsive workflow library, create tile, cards, loading states, and shared action menu. **desktop/src/features/workflows/ui/workflowEditorPane.test.mjs** Cover pane parsing, serialization, and stable step-ID reconciliation. **desktop/src/features/workflows/ui/workflowEditorPane.ts** Define explicit trigger and stable step pane URL state. **desktop/src/features/workflows/ui/workflowYamlDocument.ts** Read and update header fields independently of full form validation so incomplete steps cannot clear or disable the workflow title. **desktop/src/features/workflows/ui/workflowYamlDocument.test.mjs** Cover document-level workflow header reads and writes for incomplete definitions. **desktop/tests/e2e/workflow-title-stability.spec.ts** Verify generated, renamed, saved, and duplicated titles remain stable while moving between trigger and step panes. **desktop/src/features/workflows/ui/workflowFormTypes.test.mjs** Cover lossless Form/YAML round trips and actionable fallback for unsupported fields. **desktop/src/features/workflows/ui/workflowFormTypes.ts** Own canonical workflow YAML conversion while preserving supported trigger and step fields. **desktop/src/shared/ui/PortalledScrollArea.tsx** Provide bounded scrolling for popovers rendered outside their dialog container. **desktop/src/shared/ui/popover.tsx** Allow workflow popovers to use the shared portalled scroll container. **desktop/tests/e2e/channels.spec.ts** Cover disabled and enabled Workflows experiment states, including suppressed queries while disabled, placement beneath Canvas, channel workflow listing and opening, channel-preselected workflow creation, and direct return to the channel Workflows panel after close, discard, or cancel. **desktop/tests/e2e/workflows.spec.ts** Exercise library actions, deep links, create/edit/duplicate lifecycle, dirty exits, responsive editor behavior, YAML safety, stale updates, and one-shot channel selection. </details> ## Reproduction steps 1. Open **Workflows** and confirm the responsive card library, create tile, card action menu, and card-to-detail/run-history modal navigation. 2. Open `?view=create`; confirm the channel chooser opens once, the trigger inspector stays hidden until a channel is selected, and closing the chooser does not make it reopen after unrelated edits. 3. Create a workflow, switch between Form and YAML, add and remove steps, refresh a pane deep link, and confirm the selected trigger or stable step remains addressable. 4. Edit or duplicate a workflow, make an unsaved change, and confirm close, Escape, browser navigation, and route target changes require discard confirmation while pane-only navigation does not. 5. Enter unsupported YAML and confirm Form mode gives an actionable fallback without rewriting the definition; verify reaction triggers preserve and execute their filter. 6. Open a channel’s settings, select **Workflows** below **Canvas**, and open or create a workflow; confirm the shared modal stays over the channel, the channel URL does not change, New workflow preselects that channel, and closing or discarding returns directly to the channel’s **Workflows** panel. 7. In create, edit, and duplicate modes, move between the trigger and incomplete step panes and confirm the generated or edited title remains visible and editable. ## Screenshots Fresh captures from product head `c5c3abc91a71fe511d43e6cc9168b1626d0c217c`; the later review-guidance fix does not alter these pictured states. ### Workflow library and actions  ### Workflow editor — wide  ### Workflow editor — narrow inspector overlay  ### Workflow editor — active channel overlay  ## Review feedback addressed - Corrected Diff Posted condition guidance to use the executor-supported `str_contains(trigger_text, "deploy")` syntax and added a visible Playwright regression assertion. - Scoped direct workflow-detail navigation coverage to the stable `Edit workflow` dialog name, asserted the workflow title separately, and retained the trigger-node assertion. - Routed dirty channel-overlay Duplicate/Edit transitions through the existing discard confirmation, preserving the original YAML draft when the user keeps editing. - Made workflow deletion await relay success, remain non-dismissible while pending, and retain the confirmation/editor/draft with an actionable inline error on rejection. - Gated the channel-settings Workflows ingress and channel workflow query behind the `workflows` experiment, with defensive rendering if the flag changes while that view is active. - Matched the Reaction Added trigger to the prototype reaction picker, preserving native/custom/legacy values in canonical YAML and providing an explicit clear action. - Kept a channel’s Workflows panel mounted beneath channel-origin editors so clean close, dirty discard, and create cancel return directly to that panel without changing the channel URL. ## Verification Verified at exact pushed head `76ebba7b7ace1e13445744200da70ca9da231b7e`: - Push hooks passed: destination-org policy, branch-skew, differential file-size, Desktop checks, TypeScript typecheck, and **5,115/5,115** Desktop unit tests - Focused channel lifecycle E2E passed: dirty edit discard returns directly to the channel Workflows panel; create cancel does the same while preserving the channel URL and preselected channel - E2E production build, standalone TypeScript typecheck, and Biome checks on both touched files passed - Reaction-picker regression spec remains recorded at `dc28ffa98ec34b4e0656757fb2dd93e60a84674d`: **5/5 passed** (picker interaction, canonical YAML persistence/clear, legacy-value preservation, save/reopen round trip, narrow viewport containment) - Blox existing workflow E2E regression set at `dc28ffa98ec34b4e0656757fb2dd93e60a84674d`: **36/36 passed** - Earlier blocker and review regressions remain recorded at `3760c3d657a525f5af98e8d0f98bdd03999d8e61`: **5,114/5,114** Desktop unit tests and the dirty-overlay/deletion focused checks - Working tree clean; local branch, remote branch, and PR head all match the exact SHA above ### Related issue None found. Closest prior work: #231. --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: Codex <noreply@openai.com> |
||
|
|
2ce8df8533 |
fix(models): curate Databricks alias-aware labels for 5 missing endpoints (#6360)
Curates Databricks model labels through prefix-stripping aliasing and materializes exact records for five endpoints missing from the Databricks catalog. **Alias-aware label resolution** (Databricks v2) - Strip workspace-specific prefixes (`goose-`, `team-x-`, etc.) before exact-record lookup so any prefixed alias resolves to its canonical label without enumerating every alias variant. - Return `None` for uncurated ids instead of falling back to a raw string; callers control the fallback display. **New exact records — label-only (Anthropic Messages route, axes from family rules)** - `databricks-claude-fable-5` → "Claude Fable 5" (materialized from `anthropic-adaptive-xhigh-fable-5`) - `databricks-claude-opus-4-8` → "Claude Opus 4.8" (materialized from `anthropic-adaptive-xhigh-opus-4-8`) - `databricks-claude-opus-5` → "Claude Opus 5" (materialized from `anthropic-adaptive-xhigh-opus-5`) - `databricks-claude-sonnet-5` → "Claude Sonnet 5" (materialized from `anthropic-adaptive-xhigh-sonnet-5`) **New capability-bearing record — Kimi K3 (MLflow Chat route)** - `databricks-kimi-k3` → "Kimi K3" with axes from models.dev Moonshot catalog: reasoning toggle + effort `[low, high, max]`, no default; `_reconciliation_doc` records the source and reconciliation policy. **Corpus and tests** - 6 new normative-corpus vectors (canonical + one `goose-*` alias per new id); executable vector count updated to 113 in both Rust and TS gates. - Rust and TS label tests extended to cover all 3 new canonical ids and their aliases via the prefix stripper. - Sentinel variable fix in discovery-provider test to prevent false failure in Databricks dev environments where `BUZZ_AGENT_PROVIDER` is set. --------- 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> |
||
|
|
d274a6e949 |
fix(acp): guard against unrequested public relay skills (#6394)
Adds a shared base-prompt instruction that agents must not read or blindly follow public Buzz relay skills unless a human explicitly requests them. --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> |
||
|
|
2a236e4137 |
refactor(prompt): simplify Buzz agent guidance (#6340)
## Why The Buzz agent prompt carries verbose agent-draft instructions that add prompt weight without improving benchmark correctness. Buzz-native channel trials also showed agents creating permanent channels while claiming they had applied a one-hour TTL. The static CLI inventory remains for efficient command discovery by smaller agents. ## What - Retain the static Buzz CLI command table and direct agents to use `--help` for full usage - Compact the owner-reviewed agent-draft guidance without changing its workflow - Clarify that `buzz channels create` is permanent unless `--ttl` is provided ## Benchmark Results - Terminal-Bench retained 22/22 correctness in the initial no-table candidate while reducing prompt size, input tokens, tool calls, and cost; total active time was effectively flat. - Across two Buzz-native runs, the non-channel cases tied at 26/30; the CLI clarification directly addresses the observed TTL omission. ## Update — 2026-08-20 - Restored the CLI command table following review feedback about multistep discovery with smaller agents. The restored-table candidate has not been re-benchmarked. Signed-off-by: Salman Mohammed <smohammed@squareup.com> |