| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
feat(desktop): relay admin console for the /api/admin/v1 operator surface (#4768) Adds a relay admin console under Settings — the single surface for relay-level operator work — replacing the previously unreachable moderation section. Operators authenticate with their own Nostr key over NIP-98 — no browser extension, no bearer token — to view deployment-wide moderation reports and product feedback, resolve/dismiss/escalate/reopen reports, update feedback status, and manage the operator/moderator staffing roster. ## Nav reachability The old `moderation` section id was defined in `settingsSections` but never wired into any group in `settingsNavGroups`, so the sidebar never rendered it — unreachable since #1617. This PR replaces it with a `relay-admin` section wired into the Communities nav group, pointing at the relay admin console, and drops the separate `admin-console` section id so a single surface owns relay-level trust & safety. The Relay admin nav entry is always visible. Auth gates the panel itself; the nav entry is a door, not a credential — hiding it behind discovery state would strand an operator whose relay transiently fails NIP-11 discovery with no path back to the connection settings. ## Discovery and connection On mount the console reads the connected relay's NIP-11 document for the optional `admin_api` field. When a valid origin is discovered **and its host matches the connected relay's host** (case-insensitive, host identity only), it is automatically saved and probed — the panel renders immediately with no Save step required. A cross-host advertisement is pre-fill only: it populates the manual origin field under Advanced and is never saved or probed without an explicit Save, so a relay cannot direct an unconsented NIP-98 signature at a third-party origin; residual exposure on the same-host path is bounded by NIP-98's URL/method/payload binding. Saving is only required for manual origin changes. The advertised origin is untrusted input: revalidated through `AdminOrigin::parse`, falling back to manual entry when absent or invalid. A saved manual origin always takes precedence over discovery. ## Auth and transport (Rust, `src-tauri/src/commands/admin/`) - `AdminOrigin` value object: validates scheme + host + optional port, rejects credentials/path/query/fragment; `http://` only for loopback. The relay side normalizes its configured admin host at config load and compares inbound `Host`/`Origin` hosts case-insensitively. - `AdminRoute` closed enum: no IPC surface accepts arbitrary URLs, so the NIP-98-signed URL is byte-identical to the fetched URL. - Dedicated no-redirect `reqwest` client as an SSRF guard: a relay `3xx` is an error and the NIP-98 header never crosses origins. - `admin_probe` signs `GET /probe` and returns a typed state (`nip98Authorized` / `nip98Denied` / `disabled` / `notAdminApi` / `networkOrIntercepted`). The response body is validated against the full relay contract — `nip98Authorized` only when the complete NIP-98 invariant holds, `disabled` only on a coherent disabled-mode body; any incoherent or unrelated JSON classifies as `notAdminApi`, so the console never fails open at the network boundary. Unknown fields are tolerated for forward compatibility. - NIP-98 signing via `AppState::signing_keys()`, one retry on `401` with a fresh signed event; response size bounded by a `Content-Length` preflight and a streaming byte counter; per-pubkey origin storage with atomic `0o600` writes. ## Relay admin console The console is gated behind the relay `OPERATOR`/`MODERATOR` roles from #3777; every `/api/admin/v1/*` route returns `403` to anyone else. Community self-administration (`community-members` / Invites) and in-channel enforcement (the 9040–9043 signed commands) are a separate axis and are untouched. The previously-duplicate community report queue (`ModerationQueueCard`, `moderationQueue.ts`, and three dead hooks) was unreachable dead code and is deleted in its own commit; `useBanMemberMutation` and the rest of `features/moderation/hooks.ts` remain in use by the members sidebar and message menu. ## Console UI (TypeScript, `src/features/admin-console/`) - `AdminConsoleSettingsCard`: discovery + probe flow, honest copy for every probe state, manual origin under an `Advanced` disclosure at the bottom. The role (operator/moderator) is rendered in the connection status line as plain text ("Connected as operator") — no badge-shaped elements above the tab row. The origin provenance (relay config / database) appears inside the Advanced card as small muted text. Section title changed to `Admin`. - `AdminConsolePanel` with Reports / Feedback / Staffing tabs (Staffing gated on `role === "operator"`); reports and feedback grouped by community for cross-community triage. - **Reports**: always requests `scope=all` so the existing resolve/cancel/reopen controls can reach every status (`open`, `processing`, `resolved`, `dismissed`, `escalated`). The relay's omitted-scope default is `escalated`-only (the platform-safety backstop); `scope=all` is explicit and scoped to this console. Full action matrix per `target_kind` (event → delete/kick/ban/timeout/dismiss/escalate; pubkey → ban/timeout/dismiss/escalate; blob → dismiss/escalate); kick is suppressed when the report carries no `channelId`. A `processing` row stays navigable — enforcement state lives in the detail view. A `failed` action (always pre-mutation) offers a single **Cancel & reopen** via `POST /reports/{id}/cancel` with `{actionId}` fencing; there is no client-side retry. `pending`/`enforcing` actions belong to the relay recovery worker and offer no button; a `409` reloads detail. Lists refetch on back-navigation after a mutation. - **Report failure UX**: `reportErrorMessage()` strips 4xx/5xx status prefixes and surfaces the relay's own error reason in the toast (e.g. relay returns `"400: you cannot report this content"` → toast shows `"you cannot report this content"`). Self-report blocking UI removed — the relay has no self-report gate, and the failure was purely client-side error surfacing. - **Reason audience disclosure**: the Reason (optional) field shows exact copy under the input based on selected action — Delete discloses that reason is sent verbatim to the affected user and posted publicly in the room; Kick/Ban/Timeout disclose affected-user only; Dismiss/Escalate disclose reporter only. These mirror the actual relay notice paths. - **Enforcement history**: the report detail carries the governing action even after the report leaves `processing` — a report enforced then reopened is `open` yet still shows its `succeeded` action as executed history alongside the resolve form; a reopen does not un-happen a ban. The resolve form gates on `open` status alone. - **Reopen**: terminal reports (`resolved`/`dismissed`/`escalated`) can be returned to `open` for re-triage via `POST /reports/{id}/reopen`. Reopen never reverses applied enforcement, and the copy says so. - **Feedback**: list, detail, and status control (`new`/`reviewed`/`archived`) with a generation-fenced attachment viewer. A purged source community severs provenance to `null` rather than deleting the row; severed rows bucket under a "source community removed" heading. - **Staffing**: Add is create-only with display names resolved via `useUsersBatchQuery` and npub cross-fade on hover (`HoverStaffingIdentity`). An entered key that already appears in the authoritative roster (config, owner-fallback, or DB) is rejected locally with an inline duplicate-key message and no request is issued; a `409 Conflict` from add, role change, or remove surfaces the relay's parsed error body via `adminErrorMessage(e)` — distinguishing config-backed key conflicts from last-operator conflicts without HTTP-status inference. Roster with `config`/`owner_fallback`/`db` source labels; config-backed entries disable remove client-side. In-place role change via `<select>` calling `putAdminOperator`; the relay responds with the effective `OperatorEntry` (`pubkey`, `effectiveRole`, `sources`) in the same shape as the roster list. Read-only badge for config-managed entries. - **Timeout expiry**: `useTimeoutState` clears the store when derived state is `INACTIVE` but the store is still active; the 1s tick is scoped to entries with a known `expiresAtMs` and owned by `ComposerTimeoutBanner` (mounted only while a timeout is active), while `ChannelPane` subscribes to the boolean `useTimeoutActive()` — the pane re-renders when a timeout starts or ends, not every second. `useMembersSidebarModeration` ticks `nowMs` only while the sidebar is open, the viewer can moderate, and some member has a future `mutedUntil`; the tick after the last expiry stops the interval. - Staffing refreshes the principal after a self-demotion or self-removal: `AdminConsolePanel` passes `onSelfMutation` to `StaffingTab`, which re-probes the saved origin so the role badge and tab visibility update without a manual re-probe. - Staffing mutation failures, including both 409 variants (config-backed key vs. last operator), surface the relay's parsed error via `adminErrorMessage`, so the last-operator recovery guidance is shown. - Mutations confirm via `sonner` toasts; failures surface the relay's parsed error `message`, not raw JSON. The `disabled` auth mode renders read-only. ## Mutation idempotency Resolve and reopen carry a per-attempt `requestId`. On first submit the whole command (`{requestId, action, reason, expirationSecs}`) is frozen; after an ambiguous failure (409, 5xx, transport error, truncated response) the snapshot is retained, action/reason/duration controls are locked, and the retry sends the exact same payload byte-for-byte. A definitive pre-commit rejection (non-409 4xx with `bodyComplete: true`) discards the snapshot, unlocks controls, and the corrected submit uses a fresh payload. On success the toast derives from the authoritative `AdminReportResolution` (`activeAction.action` when present; otherwise `status` for decision-only outcomes) — never from the mutable form selection. The native mutation commands — including operator delete — reject with a typed `AdminMutationError` carrying the relay's HTTP status (`relayStatus`, `null` when no verdict exists) and a `bodyComplete` flag that is `true` only when the full response body was read. ## Restriction routes contract change `GET /members/restrictions` and `DELETE /members/{pubkey}/ban|timeout` now take `communityHost` instead of `communityId`; the relay resolves the tenant itself and returns `400 unknown_community_host` for an unmapped host. This is a breaking contract change with no compatibility shim: the relay must run this build before a desktop that uses it, scripts sending `communityId` must switch, and rolling the relay back breaks the desktop Restrictions UI. `docs/admin/README.md` documents the three routes in the route inventory and carries the migration note. Restriction calls also carry the relay the list was loaded from, and the native side rejects a mismatch before building any URL. ## Hardening - Saved admin origins are stored per pubkey and per relay host. The host slug parses the relay URL, keeps the port-drop for DNS names, and encodes IPv6 hosts canonically, so two IPv6 relays never share a file and no IPv6 slug can match a DNS slug. - NIP-11 admin discovery has its own 10s request deadline and reads `/info` success and error bodies through the bounded reader with a 64 KiB cap (`Content-Length` is only a pre-check). - `PUT /operators` builds its `OperatorEntry` response from the validated body (`effectiveRole = role`, `sources = ["db"]`) instead of re-reading the roster, so a concurrent change can't turn a committed write into a `403`. Related: #3777 (relay `OPERATOR`/`MODERATOR` role model + NIP-98 auth + `admin_api` NIP-11 advertisement — provides the runtime and the discovery field this console consumes) Related: [builderbot-platform-core-infrastructure#140](https://github.com/squareup/builderbot-platform-core-infrastructure/pull/140) (prod admin ingress allowlist and NIP-98 rollout) ## Screenshots Captured headless at `197f0ba0c` against the mock bridge (1280×720). ### Setup **Origin setup — Advanced disclosure open, idle state**  **Authorized console — all three tabs**  ### Reports **Reports queue — open, processing, resolved across two relay groups**  **Open report detail — reporter, target, message snapshot, resolve form**  **Processing report — PROCESSING/HARASSMENT, ban enforcing state**  **Resolve form — Delete selected, audience disclosure copy**  **Resolved report — enforcement history block**  ### Feedback **Feedback list — community grouping with status badges**  **Feedback detail — mutable status control (operator mode)**  **Feedback detail — passive status badge (read-only/disabled mode)**  ### Staffing **Staffing roster — config/owner\_fallback/db source labels**  **Add form — new pubkey filled, ready to submit**  **Duplicate rejection — inline error for existing principal**  --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <wpfleger@block.xyz> Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Signed-off-by: Alia <d32955ad69077062930cc46cfe2df30ca9aaf6f8e76422681265e9e9af704d78@buzz.block.builderlab.xyz> Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Co-authored-by: Alia <d32955ad69077062930cc46cfe2df30ca9aaf6f8e76422681265e9e9af704d78@buzz.block.builderlab.xyz> | 12 天前 | |
Refresh README screenshots (#2236) | 2 个月前 | |
docs(nips): add single-coordinate manual-unread override layer and verification model to NIP-RS (#2864) ## Summary Amends `docs/nips/NIP-RS.md` with the manual mark-as-unread override layer and includes `docs/formal/nip-rs-unread/`, the bounded exhaustive verification model that preceded and informed the spec. All `ov_*` override state lives in exactly one coordinate per installation. That single constraint is what makes the rest of the amendment small: override state never moves between coordinates, so there is no slot lifecycle to make crash-safe, and the only durability obligation is carry-forward on `client_id` rotation. ## Spec changes (`docs/nips/NIP-RS.md`) - **Non-Goals:** drop the stale line stating mark-as-unread is out of scope; state the `ov_*` durability exception to the best-effort/time-horizon model. - **Reserved Namespace:** `ov_` stem and `esc:` escape marker reserved. Escape on publish (prepend `esc:` to raw IDs beginning with `ov_` or `esc:`), unescape on receive (strip exactly one `esc:`). Bijection, with the pre-amendment backward-compat residual documented as a stated limitation. - **Content Validation:** override entries are collected and validated as a complete logical group *before* any decoding, zero-filling, merging, or canonicalizing. Only two wire shapes are accepted — a complete live three-key group, or an `ov_c:`-only tombstone floor. Any other shape rejects the whole group while retaining the frontier entry; applying the generic per-entry discard rule first is prohibited. - **`d` Tag:** `<slot-id>` is exactly 32 lowercase hexadecimal characters, replacing "a random opaque string" of 1–64 ASCII characters. The fixed shape lets a relay recognize a read-state coordinate structurally from the `d` tag alone, without decrypting anything, and apply per-coordinate protections to it — under the old wording a conforming client could pick a shape that silently forfeits them. Recognizable coordinates are also what let a relay replace superseded versions outright rather than accumulating one retained row per publish, which keeps the coordinate count a full-state load must enumerate near one per installation. Every client designates one **primary** coordinate with a stable `<slot-id>` for the installation's lifetime. All `ov_*` entries, and the frontier entries of the contexts they belong to, MUST live in the primary. Additional coordinates remain legal for frontier volume but MUST NOT carry `ov_*`, which keeps them freely rewritable and freely deletable. - **`t` Tag:** described as a discoverability marker rather than a guarantee of relay-side selectivity. A relay MAY apply tag constraints after its result cap, and `kind:30078` is shared with unrelated application data, so clients MUST apply the tag as a correctness filter locally, MUST NOT infer completeness from a short result, and MUST omit the tag entirely when performing a full-state load. - **Fetching / Full-State Load:** clients implementing the override layer MUST NOT apply a finite `since` filter — an encrypted payload means a relay filter cannot select for override-bearing events, so any event-level window can exclude the only coordinate holding a tombstone floor. Removing `since` is not sufficient: relays MAY cap historical results, MAY cap below the requested `limit`, and emit end-of-stored-events after the capped query, so neither EOSE nor a short page proves completeness. No test against the client's requested `limit` can detect truncation either: the effective cap belongs to the relay, a relay MAY cap below what was requested, and an advertised maximum limit is not necessarily the limit enforced. A full-state load is therefore enumerated on `{"kinds": [30078], "authors": [<pubkey>], "limit": <n>}` with **no tag constraint**. A relay MAY apply tag constraints only after its result cap and withhold the events that fail them, so under a tag-constrained filter the delivered count is not the count the cap selected — a delivered page can be empty while older coordinates still exist below it, and `kind:30078` is arbitrary application data whose `d` tag namespace is open to every application that has written under the user's key. Omitting the tag makes delivery observable; read-state selection moves client-side, where the validation rules already place it. Completeness is then established by enumeration on a strictly decreasing cursor: collect a page, descend on the lowest `created_at` across all delivered events, exhaust that second with a window pinned to it, continue below it, and treat only an empty delivery as complete. Every query carries the same explicit `limit` `n` with `n >= L`. Per-second exhaustion is discharged by comparing the pinned window's delivery against the largest delivery the relay has already demonstrated in the same load, floored at `L = 2` so that the ordinary single-coordinate installation can reach *complete* at all. The comparison fails safe: an inconclusive window reports *cannot prove complete* rather than *complete*, and that verdict is terminal for the load. Because these are addressable events, a coordinate republished mid-load moves *above* the descending cursor while its previous version stops existing, so neither is reachable by any later query. A full-state load is therefore fenced by a live subscription on the same tag-free filter, established — defined as receipt of end-of-stored-events — before the first enumeration query and held unbroken on the same connection for the load's duration. Fence deliveries are collected like enumerated events but do not contribute to the cursor or to the demonstrated-delivery bound. Collection deduplicates coordinates on the full NIP-01 addressable ordering — greatest `created_at`, lowest event id on ties — because an equal-timestamp replacement is legal and is the version the relay retains. A lapsed or reconnected fence makes the load potentially incomplete, and a client MUST NOT publish to its own coordinates during its own load. Five relay behaviours the *complete* verdict rests on are stated as normative conformance preconditions rather than assumptions, because none is verifiable from the responses a client receives: newest-first prefix delivery with lowest-id tie-breaking (what NIP-01 already specifies for `limit`), a non-decreasing effective cap within a load, the floor `L`, push delivery on an open subscription, and a delivery barrier ordering accepted matching events ahead of a query's end-of-stored-events on the same connection. Conditioning *complete* on positive proof of these instead would withdraw the override layer from every client rather than from the non-conforming relays. A client MUST NOT load against a relay it has evidence violates them, and MUST treat any such load as potentially incomplete. A load that is potentially incomplete, or that failed on any relay the client publishes to, MUST NOT authorize canonical compaction, publishing a canonicalized override blob, deleting or abandoning a coordinate, or reporting a mark-read as successful; the client falls back to local state. - **Client-ID Rotation / Orphaned Blob Deletion:** rotation is the only event that changes an override-bearing coordinate. Before deleting or abandoning its previous primary, a client MUST republish the componentwise `max()` of every register that primary holds — every tombstone ceiling included — under its new primary, and MUST confirm acceptance on **every relay** from which the old primary will be deleted or allowed to lapse. Acceptance on one relay does not authorize deletion on another. Frontier-only orphans are deletable unconditionally; an unknown same-`client_id` coordinate is treated as a live carrier until merged. - **Live Subscription and Convergence:** the re-publish trigger and its suppression are evaluated on canonicalized state, so a retained live peer blob the client has already tombstoned cannot trigger an identical write on every replay. - **Manual-Unread Override Layer** (new section): - **Wire encoding:** `ov_s:<ctx>`, `ov_c:<ctx>`, `ov_b:<ctx>` as uint32 siblings in the existing `contexts` map. - **Merge rule:** componentwise `max()` per counter — no new wire merge logic. - **Liveness predicate:** `S > 0 AND F <= B AND S > C`, transcribed from `model.py::override_set_b`. - **Actions:** mark-unread bumps S and captures the effective frontier as B; mark-read bumps C; a natural frontier advance past B deactivates a stale set with no counter update. Every action requires a complete full-state load. At the uint32 ceiling, wrapping and resetting are prohibited: mark-unread is refused, and mark-read completes only if the resulting state has `override_active == false` — otherwise it fails visibly rather than reporting success over a still-live override. - **Tombstone floor:** a dead ever-active register compacts to `RegB(0, max(S,C), 0)` — a single `ov_c:` key. A virgin register is omitted entirely. This blocks counter reuse and the resulting resurrection. - **Mandatory canonical publication:** a protocol requirement, not an optimization. Publishing raw dead registers lets two independently-dead registers from different devices produce a live join. - **Override group co-location rule:** a context's frontier entry and all its `ov_*` siblings MUST travel in the same event, and that event MUST be the primary coordinate. An override-bearing context therefore has exactly one legal destination for its whole group; only frontier-only groups may be distributed across additional coordinates. Grouping is per logical context, never per key. - **Unescape-before-group rule:** the frontier wire key MUST be unescaped to its raw logical context ID before use as group identity. Equal normative weight to atomic grouping. - **Tie policy:** clear-wins is MUST. The tie verdict is not encoded on the wire, so a selectable policy makes two conforming clients diverge permanently on both the unread verdict and the canonical wire form. - **Override State Durability:** `ov_*` entries are exempt from age pruning and budget eviction permanently, and durability is defined over retrievable logical state — the containing event must stay reachable and the load must establish completeness, not merely retain keys. There is no safe finite GC horizon. - **Bounds and budget:** byte/key analysis at both small-counter and uint32-maximum values. Confining `ov_*` to one blob makes its plaintext budget a hard lifetime ceiling on ever-overridden contexts — roughly 600 tombstones at the worst-case ~54 bytes against 32 KiB, ~730 at the common ~45 bytes, ~199 simultaneously live overrides at ~164 bytes. At the ceiling a client MUST refuse mark-unread and MUST NOT split override state, drop floors, or publish a truncated override set. Same policy shape as counter exhaustion: visible failure, never silent degradation. - **Verification artifact:** `docs/formal/nip-rs-unread/`. The model is a broader predecessor of this NIP: its `split_blob_into_slots` permits override groups in any slot, so verified atomicity covers every arrangement this NIP allows, but the converse does not follow. The model does not verify the single-primary rule, the completeness procedure, the relay conformance requirements or the mutation fence, or carry-forward; malformed-group wire validation is likewise normative but outside verified scope. - **Abstract / Non-Goals / Backwards Compatibility:** the absolute "no relay-side logic" and "no relay behavior changes" claims are narrowed to what remains true — no new event kind, no new wire message, no relay-stored read-state logic — with the override layer's relay conformance contract named as the exception. Frontier sync and clients that skip the override layer are unaffected on any relay. ## Verification model (`docs/formal/nip-rs-unread/`) Four Python files constituting a bounded exhaustive verification model for the override layer's register algebra. **What it does:** constructs a toy universe — 2–3 devices, 2 channels, every action that can happen (mark-unread, mark-read, late/duplicate syncs, app reinstall, storage compaction) — and brute-forces every reachable ordering (14,258 BFS states; 672-point deep-history parameter cube; 9-mutant harness over ~45,000 merge pairs). After each world-state it asks: did all devices converge? Did any unread flag get resurrected after being cleared, or vanish while live? **What it found and fixed:** 1. **Killed candidate A.** The model produced a concrete kill sequence: an old client that doesn't know about the new field rewrites its read-state blob and silently erases unread flags. That witness is why the spec uses candidate B (two counters that only count up, plus a snapshot) instead. 2. **Candidate B passes everything.** All delivery orders converge; the frontier high-water mark never regresses; duplicated/replayed syncs are harmless; old clients can't destroy it; compaction never resurrects a dead unread or drops a live one, including cleanup-followed-by-weeks-late-stale-sync and tombstone-landing-on-unrelated-live-state corner cases. 3. **Caught a second real bug late.** Two devices each publishing "this unread is cleared" could, on merge, reactivate it. The fix (canonicalize before publishing) is a mandatory rule in the spec; the model re-checks it across ~45,000 merge pairs. **Scope and caveats:** bounded to 2–3 devices and 2 channels. Can't prove the infinite case. `NOTE.md` documents the exact verification scope and the gap between the model's `split_blob_into_slots` generality and the single-primary rule the spec adds on top. **Why it's in the repo:** the spec asserts "verified by bounded exhaustive model checking." Keeping the artifact in-repo means anyone who later amends the merge/compaction rules can `python3 exhaustive.py && python3 mutation.py` (deterministic, exit 0) and confirm the guarantees hold. Without it the spec claims a proof nobody can check. ## Diff scope `docs/nips/NIP-RS.md` — spec amendment, zero product code. `docs/formal/nip-rs-unread/{NOTE.md,model.py,exhaustive.py,mutation.py}` — bounded exhaustive verification model, zero product code. `.gitignore` — `__pycache__/` and `*.pyc` entries for the model directory. --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> | 2 个月前 | |
docs(nips): fix stray angle brackets in created_at clauses (#4486) ## Summary The `<` in a `created_at<` condition is the comparison operator, but in three places it had been paired with a closing `>` as if it opened a `<placeholder>` bracket: - **NIP-OA** clause list: `created_at<unix-timestamp>` → `created_at<unix-timestamp` - **NIP-OA** satisfaction rule: `created_at<t>` → `created_at<t` - **NIP-GS** "Conditions in Git Context": `created_at<t>` → `created_at<t` (line 437 of the same file already used the correct form) This matches the `created_at>...` forms used alongside them, which never carried a closing bracket. Also normalizes the NIP-OA headings to ATX style and pretty-prints the signed-event JSON example for readability. ## Verification Docs-only change. The NIP-OA test vectors are untouched and were re-verified by recomputing them: the preimage SHA-256, `tag-bytes-hex`, and the signed-event `id` all still match the documented values. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Signed-off-by: zmeyer44 <zmmeyer44@gmail.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> | 8 天前 | |
perf(relay): compact Git packs before manifest limits (#2172) | 2 个月前 | |
feat(agent): optional reply guard reminds a silent turn to publish (#3763) ## Why A Buzz agent's assistant text and reasoning are never shown to anyone — only what it posts through the CLI. A turn that runs fifteen tool calls and never publishes is a silent failure: the requester waits on a result that was produced and thrown away. This adds an optional reminder at the end-of-turn gate, off by default. Tyler asked for it in buzz-mesh; plan iterated to **9.5/10 with @Wren** (Minimalness 9.7, Elegance 9.5, Correctness 9.3). ## What `BUZZ_AGENT_REQUIRE_REPLY=1` (default off, per-agent opt-in). A turn about to end with no recognized attempt to post gets a reminder and is rerolled. **At most two, then the turn ends regardless** — the guard catches accidental omission, it does not compel speech. The reminder text explicitly licenses silence so it cannot fight the base prompt's "silence is usually correct." **This is not a new MCP hook.** `RunCtx::run` *is* the turn, so the two per-turn locals need no plumbing, and every tool call already passes through it with arguments visible. The objection is appended at the existing `_Stop` gate and rides `push_hook_outputs_as_tool_results`, so the model receives it as a lower-trust tool result with `{hook, server, text}` attribution. No new trust path, no new lifecycle event, no dev-mcp or CLI protocol change. Earlier revisions of this plan needed four crates (a `_UserPromptSubmit` hook, a marker file, a `buzz-cli` change, dev-mcp state). Tyler pointed out the agent already knows both facts; that deleted all of it. Net runtime change is ~35 lines in `agent.rs` + ~4 in `config.rs`. ### Recognition contract A registered non-hook tool whose qualified name ends in `__shell`, whose `command` argument contains `messages send` or `reactions add`. - **The `__` separator is exact, not approximate.** Given `has()` + `!is_hook()`, `ends_with("__shell")` is *provably equivalent* to a bare name of `shell`: registration forbids `__` in server and bare names (`mcp.rs:227,268`) and qnames are `{server}__{bare}`, so a trailing `__shell` could only straddle the separator if the bare name began with `_` — which `is_hook` excludes. Without the separator, `powershell` and `noshell` would match. - **Reads the structured `command` field**, not serialized arguments, so a `description` that quotes a send cannot disarm the guard, and a non-string `command` is rejected rather than coerced. - **Detects an attempt, not a successful publish.** A failed send already returns non-zero exit and error JSON — louder than this reminder. The variable is named `buzz_reply_call_seen` so the code can't pretend otherwise. - **Checked after the per-turn tool-call cap**, since a discarded call never ran. - `messages send` also covers `messages send-diff`. Reactions count because the base prompt directs agents to react rather than post a bare acknowledgement. **Known limits, both deliberate and documented:** a command assembled at runtime (`$CMD`) or hidden in a wrapper script is missed; text that merely quotes a send (`echo "buzz messages send"`) matches. Missing a real post is the expensive direction and substring matching is the forgiving one there. Neither edge is pinned by a test, so the matcher stays free to improve. ### Budget Reminders share `BUZZ_AGENT_STOP_MAX_REJECTIONS`, the existing outer cap on every end-turn objection. Default 3 fits both; at 1 only one fits; at 0 the guard is off with the hooks. A round carrying both a hook objection and a reminder costs one rejection and delivers both texts. An independent budget would either violate that bound or need a second arbitration rule. ## Prior art - **#3467** (closed) built the same detector one layer up in `buzz-acp` for a different remedy. None of its symbols are on main — this borrows its permission to be coarse, but reads structured data that ACP didn't have. - **#3648** (open) detects turns with *no output at all*; a turn with fifteen tool calls and no post counts as output there, so it does not cover this case. - **#3741** (merged) is mesh-only. ## Testing **14 new tests.** 4 unit tests on the matcher; 10 integration tests through the ACP wire harness: off by default, `=0` still off, opted-in silent → exactly 2 reminders then `end_turn`, registered `fake__shell` send → 0 reminders, hallucinated `fake__shell` → still reminded, publish call truncated past the 64-call cap → still reminded, budget 1 → 1 reminder, budget 0 → off, combined `_Stop` hook objection + reminder → one round both texts and after 2 reminders the hook objection continues alone, unparseable `=true` → startup error naming the key. **10 mutation checks, each breaking a specific named test** — neutralize the nag cap, stop sharing the budget, neutralize `buzz_reply_call_seen`, drop `has`/`is_hook`, ignore the flag, drop the `__`, drop `reactions add`, read serialized args, move detection before truncation. `tests/bin/fake_mcp.rs` gains `FAKE_MCP_SHELL_TOOL=1`: it previously exposed no tool with a bare name of `shell`, so the satisfied-guard path was untestable. Full `cargo test -p buzz-agent` green at 9e0ae1f04; clippy `-D warnings` and `cargo fmt --check` clean. **Unrelated flake found:** `cancelled_turn_with_usage_emits_notification_before_response` (`tests/fake_llm.rs`) is timing-sensitive. Under 10 loaded cores it fails **2/20 on this branch and 1/20 at unmodified `origin/main@02be413b8`** — pre-existing, not caused by this change (which is inert without the env var). Flagging so it isn't misattributed to the next PR that's open when CI hits it. ## Docs `crates/buzz-agent/README.md` is the primary home (env var, recognition contract, limits, budget interaction). `docs/MCP_DRIVEN_HOOKS.md` gets a short cross-reference explaining this is *not* a hook — otherwise readers hunt for a `_ReplyGuard` tool that doesn't exist. --------- Signed-off-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com> Signed-off-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz> Co-authored-by: Dawn (sprout agent) <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@sprout-oss.stage.blox.sqprod.co> Co-authored-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz> | 2 个月前 | |
fix(desktop): derive agent availability from relay presence (#7127) 🤖 ## Requested rebase published — ef40744b Rebased onto fetched main **47d068e2109d077414cbf2f4f1c927f6d051037a**, published **ef40744b3aeb4baaf8c81416e1a644fb5b315f91** with the exact expected-old `df7fad6a` force-with-lease. No merge. Manual conflicts were additive: preserve main's exact-key identity documentation alongside the availability contract, and retain both Bestie props and the shared availability reader in `UnifiedAgentsSection`. Range-diff confirms unchanged lifecycle policy: exact-key action-time authority, Unknown versus Offline, rejected shutdown retains record/memberships, and separate local/provider/owner gates. Main's exact-key profile routing survives. Both test-only CI synchronization repairs (natural toast expiry and bounded stderr wait) are byte-identical to the prior head. All ten original authors/messages/DCO/material coauthor trailers are preserved; configured signing policy was not changed. Fresh checks on the rebased candidate: - TypeScript, Biome on 26 changed TypeScript files, differential file-size gate, and diff whitespace: pass. - Focused production-hook/card/profile units: **58/58**. - Fresh E2E build, availability/deletion browser: **11/11**, no retries. - Main exact-key profile cases plus failed-DM send/startup retries: **6/6**, no retries. Previously reviewed full Desktop/buzz-agent package and mutation evidence is reused for unchanged behavior; no ceremonial full suite, new native/provider test, or `just ci` pass is claimed. Local configs, dependency links, and historical artifacts are preserved. Hosted observation: **MERGEABLE**, **BLOCKED / REVIEW_REQUIRED**, no new-head formal review. [CI 33699735990](https://github.com/block/buzz/actions/runs/33699735990) is running (including Rust and Desktop lanes), not a completed success. DCO and required Security aggregate passed at the observation; the separate Codex advisory review was skipped. No completed failing check or new inline feedback observed. Historical approvals are not new-head approvals. No reviewer/security authorization or merge action was performed. --- ## Feature summary and retained pre-rebase evidence ## Summary In Buzz Desktop, an agent could look online just because it had been started or deployed, even when there was no current sign it was connected. Cards and profiles now show availability from the agent's relay presence rather than a saved launch record, so you can distinguish an online agent from one that was merely deployed. Agents cards and profiles use presence reported through the shared server (the relay). A successful presence read with no online agent shows Offline; failed/disconnected evidence shows unknown, rather than retaining a misleading cached Online state. Lifecycle actions remain separate. An offline agent may still have a Shutdown action because the deployment record exists. Shutdown reports a **request**, not proof the process stopped. Offline does not imply that starting a duplicate agent is safe, and Online does not promise a response. ### Related issue Independent base: `main`; no stack parent or child among the replacements. Extracted from [#7114](https://github.com/block/buzz/pull/7114), retained as historical source (`98fe33ec`). [Behavior contract](https://github.com/block/buzz/blob/f4bb2ed44e5a989d93c5f51e93c0bbd2dca941be/docs/agent-availability.md). Originating [Buzz discussion](buzz://message?channel=f7a9536a-1738-4bad-a888-b3ea25010ef1&id=7aa1f0ab23dce514bd8a0221441cf005bf428914621171472b79747c50820848) · channel `f7a9536a-1738-4bad-a888-b3ea25010ef1`. ### Testing The same saved provider-backed agent, with only authored presence changing. These are mock-browser states, not a before/after deployment or live relay transport test; production UI is unchanged by the later fixture repairs. **No online presence:** gray dot, existing Shutdown control retained.  **Online presence:** green dot, same lifecycle control.  [Capture details](https://github.com/block/buzz/pull/7127#issuecomment-5482264824). To check manually, compare runtime-only transitions with presence updates, then disconnect/fail the presence read and verify it does not stay Online. A Shutdown request should not immediately claim confirmed termination. #### Historical pre-rebase evidence and limitations (df7fad6a) Lifecycle production source remains **`f4bb2ed44e5a989d93c5f51e93c0bbd2dca941be`**. Current published head is **`df7fad6ae65dda78508317186a95522d1bb22ed9`**: the prior browser synchronization at `b78d093e` plus an additive two-file Rust test-harness synchronization described below. No production bytes, dependency/configuration files, or prior commits were changed; no rebase. Current live main `0dbd036f5bff33e7ade75e7639f3218d424a6e73` has identical failing-test/toaster/send-flow source; the causal browser comparison used latest successfully tested main `04babf02655440b4dfd37f2e2df605ead0a030d8`. **Lifecycle/deletion correction:** both Agents and actual profile deletion now pass the shared exact-key availability reader, not raw cached data. It reads the canonical query state and connection at action time, including after awaited channel discovery. Failed/disconnected/pending evidence and unqueried persona siblings are unknown; successful missing means Offline only for a requested key. Successful background refetch cache remains usable; settled failure revokes it. No second cache or per-row polling was added. Provider record + channel + Online/Away/**unknown** awaits shutdown submission before local removal; rejection preserves record/membership for retry. Established Offline preserves intentional no-request removal. No route preserves warned local removal. Local agents retain native stop-before-remove, independent of presence. Profile consent now describes a shutdown **request**, not remote deletion or guaranteed termination. Existing ownership and force gates are unchanged. **Verified, reused exact-candidate validation:** the independently approved eleven-file patch (SHA-256 `2f69fe12ef0420e62dea1fd8db28cfa22cde5eecaf8080e656310a3e60d0cf86`) was committed without byte changes. Desktop **5,921 passed, 0 failed/skipped**, including **26 new mounted production hook/IPC regressions**; rebuilt availability browser suite **11/11 passed, no retries**, including four actual profile Delete journeys. Desktop check (existing 4 warnings/5 infos), typecheck, production/protected-feature artifact matrix, differential file-size/policy and diff checks passed. No blanket rerun or new full-repository `just ci` is claimed for this frontend correction. Production regressions cover cached Online **and Offline** failure/disconnection, genuine missing/Offline, pending, successful inflight refetch versus settled error, retained reader, error during awaited channel discovery, unqueried persona sibling, shutdown rejection/order/cancel, no route and local authority. Browser fixtures use safe mock IPC and a retained provider receipt, not a real deployment. Three restored mutation controls fail: unknown → skip shutdown (**15** regressions), Agents raw-cache reader (**6**), actual profile raw-cache caller (**1 browser journey**, false removal on failed cached Offline). Independent review approved the exact frozen bytes and added **4/4 cached-empty failure/disconnection probes** across both callers. This is local independent approval, not formal GitHub/A Team clearance. The prior native propagation/poll-count defects remain closed ([earlier response](https://github.com/block/buzz/pull/7127#issuecomment-5512278755)). The prior hover-popover correction at `b55423f6` remains covered by the full 11-journey browser run: pending/failed/disconnected means no badge or accessible status, genuine missing/Offline retains an Offline badge. Its earlier fallback-restoration mutation failed as expected (badge count 1 rather than 0); that historical witness is reused, not rerun. **Reused unchanged native/system boundary:** local `just ci` at `c59067d8` passed workspace/Tauri fmt/clippy, static/policy checks, Rust unit recipe, native workspace **3,159 passed / 20 ignored**, Web build and **2,019 mobile tests**. No native implementation changed in this lifecycle correction. These are historical boundary results, not new-head native/live certification. [Parent CI](https://github.com/block/buzz/actions/runs/33650549130) passed with **14 retry-recovered browser flakes**, not retry-free. Old-head CI/reviews are not current-head clearance. **Hosted gates:** [CI33662151103](https://github.com/block/buzz/actions/runs/33662151103) on `f4bb2ed4` **FAILED**: smoke shard1 had 322 pass, one failure, one retry-recovered flaky, two skipped. The failed first-DM retry test timed out on all three attempts because the error toast intercepted Send. That failure is preserved, not waived; the scoped test repair below is published as `b78d093e`. [CI33668171165](https://github.com/block/buzz/actions/runs/33668171165) on `b78d093e` subsequently **FAILED** the Rust unit budget regression described below. Both original failures remain visible; neither was retried to green. Exact `f4bb2ed4` and `b78d093e` APPROVED reviews cover unchanged reviewed bytes, not formal approval of the new head. Fresh exact-head CI and the established automated technical rereview are the next gates for `df7fad6a`. Historical deletion responses remain ([5092381800](https://github.com/block/buzz/pull/7127#issuecomment-5513759316), [5092391193](https://github.com/block/buzz/pull/7127#issuecomment-5513763497)). No formal review dismissed. The [security notice](https://github.com/block/buzz/pull/7127#issuecomment-5482231278) and latest-push maintainer/codeowner policy remain separate actionable gates: eligible Block organization members own current-range authorization. No merge/security authority exercised. **CI causal repair (`b78d093e`, test only):** the error `Message failed to send: Mock first DM send failed.` is deliberately injected by the existing fixture. CI screenshot and retry trace show the bottom-right Sonner notification over the actual enabled Send button. `fill()` leaves the pointer parked there; Sonner pauses its 4-second lifetime while hovered. A fast run can click before animation settles (unchanged local test passed in 2.7s; two actual tested-main CI cases passed first attempt in 3.3s), which does not disprove the failure. Independent controlled browser runs on `f4bb2ed4` and tested main `04babf` both reproduced the same toast hit-test at Send `(1203,627,32,32)`, persistent hover beyond 4s, and intercepted ordinary click with no second send. Moving the real pointer to the editor allows natural expiry and successful ordinary retry, preserving all original DM-channel/recipient assertions. This same synchronization already exists in the neighboring agent-startup-failure test. The one-file correction keeps the visible error assertion, scopes its toast locator, moves the pointer back to the editor and observes normal toast removal (bounded 10s) before retry. No forced click, direct toast dismissal, mocked clock, CSS override, skipped test, production behavior change, or new backend mock. Six focused browser executions pass (first-send/startup-failure, three repeats each, no retries); the held-toast control fails on original bytes at the Send click while the exact repaired test passes. Biome and diff checks pass. Reuse unchanged 5,921 Desktop / 11 availability browser / four independent probes above; no semantic production change warrants repeating those suites. Original failed CI attempt/retries, local fast pass, deliberate failing control and all traces remain in `WORK_LOGS/AVAILABILITY_CI_B9210A40`. Browser evidence is mock-IPC Chromium, not native/live-relay certification. The UI still temporarily overlays Send while a notification is hovered; the test exercises its real move-away/expiry recovery, not immediate click-through. **Rust CI causal repair (`df7fad6a`, test only):** [original Rust / Unit Tests failure, job100375291370](https://github.com/block/buzz/actions/runs/33668171165/job/100375291370) tested GitHub merge `223dee91a396d8cb4ebf18b9b8559e5a54951235`. `context_recovery_budget_exhaustion_surfaces_the_error` failed at `regressions.rs:2756` in **0.091s** because its immediate stderr snapshot lacked `context recovery budget spent`. ACP context-error assertions had already passed. The captured prefix shows all three budgets **32768 → 16384 → 8192 bytes**, above the 4096-byte floor, and ends during the third attempt. This is **not evidence of floor exhaustion**. The collector is an independent Tokio task; a stdout response is not a stderr barrier. Recovery, harness and test blobs were identical across the compared base/head/merge parents; no production regression was implicated. The shared test Harness now provides a bounded event/condition wait, registering for collector notifications before reading the buffer to avoid lost wakeups. The budget and adjacent terminal floor assertions wait for their own diagnostic and retain the matching snapshot. The budget test still requires the provider's ACP context error and exactly three recovery rungs, now corroborated by **exactly four provider calls** and no floor diagnostic. Timeout remains a real failure with captured stderr. No fixed sleep, weaker assertion, skip, provider-limit/logging change, dependency/config edit, or production change. **Deterministic causal control:** the same real agent/HTTP-provider/ACP scenario holds only stderr collection behind a one-shot gate until after stdout responds. The old immediate snapshot fails the original budget assertion (intentional exit101); the repaired wait explicitly remains Pending while held, then passes after release. No scheduler-speed assumption or fixed sleep. This reproduces the observation race under controlled delay, **not the exact historical CI schedule**. A missing-diagnostic test proves the wait actually times out. Original failure and deliberate failing-control logs/patch remain in `WORK_LOGS/RUST_TRIAGE_06E32D2D` and `WORK_LOGS/RUST_SYNC_BC5758B9`. **Final candidate validation:** focused recovery **9/9**, floor **1/1**, absent-diagnostic timeout **1/1** pass. One full touched-package run, `cargo test --locked -p buzz-agent`: **695 passed, 0 failed, 1 existing ignored**, including all **54 regressions**. Local nextest was unavailable, so this uses the repository-supported cargo-test fallback, not a claim of nextest reproduction. `cargo fmt --check`, package-scoped Clippy all-targets with `-D warnings`, differential file-size/policy and diff checks pass. Previously reviewed availability production and the Desktop/browser evidence above are unchanged and reused; no all-native blanket rerun. The test-only delta was self-reviewed against collector ordering, timeout and falsification evidence. Existing production approval remains valid for those bytes; exact-new-head technical/CI clearance is not assumed. The required **Security aggregate** is distinct from optional Codex advisory feedback; no security authorization, human review contact, or merge was requested. **Remaining policy limits:** shutdown submission is not harness acceptance or process termination; confirmed Offline/no-route local removal may leave a remote process; route discovery is best effort, membership cleanup uses `Promise.allSettled`, and multi-instance deletion is sequential/non-atomic. No distributed singleton, provider-health, tenant-switch cancellation, live relay TTL or packaged WebView/VoiceOver certification is claimed. The pre-existing DM-header raw-presence fallback (`ChannelScreenHeader`/`useActiveChannelHeader`) remains outside this repair and uncertified. Screenshots above remain historical mock-browser illustrations, not new deletion or native transport evidence. --------- Signed-off-by: Logan Johnson <loganj@squareup.com> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> | 1 个月前 | |
fix(desktop): unify owned-agent cloud provenance markers (#7129) Unify presentation-only cloud provenance across agent identity surfaces. Keep successful local-inventory and verified-ownership gates; preserve main availability and mention spacing behavior. Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Signed-off-by: Logan Johnson <loganj@squareup.com> | 1 个月前 | |
fix(desktop): keep explicit agent profiles bound to their exact key (#7131) 🤖 ## Summary In Buzz Desktop, clicking a message from stopped agent A could open running agent B—and B's controls—because both shared a persona (an agent definition). This now opens the author you clicked and only that agent's own controls, so you can inspect an old message without being redirected to a different running agent. An explicit public key—the identifier for one agent—now stays exact across message authors, members, DMs, deep links and Instances rows, including stopped, archived and relay-only agents. Local controls come only from a matching local record for that key. A relay-only A cannot borrow B's Start/Stop/Edit controls or configuration. Deliberately opening a **persona** is different: it can still select a representative that respects archived instances or offer Start when none remains. The change removes competing historical-persona redirects rather than adding another identity exception. ### Related issue Independent base: `main`; no stack parent or child among the replacements. Extracted from [#7114](https://github.com/block/buzz/pull/7114), retained as historical source (`98fe33ec`). [Behavior contract](https://github.com/block/buzz/blob/9c4b6523ceaef0f3d92906fcdb5d9a3b9ede7e17/docs/agent-profile-identity.md). Originating [Buzz discussion](buzz://message?channel=f7a9536a-1738-4bad-a888-b3ea25010ef1&id=7aa1f0ab23dce514bd8a0221441cf005bf428914621171472b79747c50820848) · channel `f7a9536a-1738-4bad-a888-b3ea25010ef1`. ### Testing Synthetic Playwright mock-bridge state. After screenshots exercise this independent profile extraction (`df6612b1`); no availability or cloud-marker implementation is included. #### Before: historical A redirects to running B Unchanged main product code (`bc006f67`) with the same updated historical-message fixture fails: clicking Earlier Parity Agent opens Current Parity Agent and its Stop control.  #### After: historical A opens A The clicked author remains Earlier Parity Agent, with A's public key and its own Start control. The current sibling is not substituted.  #### Exact relay-only A while local sibling B exists A's public key and owner-scoped profile are visible; no local Start/Stop/Edit/Add control or sibling definition is borrowed.  #### Explicit persona navigation may select local B Deliberately opening the persona selects its local representative, with B's key and legitimate Stop/Restart/Edit controls.  #### Explicit persona without an instance may offer Start This is a deliberately opened persona, not a relay-only key turned into a persona surface.  [Original screenshot publication](https://github.com/block/buzz/pull/7131#issuecomment-5482310890); all five immutable image URLs and captions retained here. The final documentation-only commit does not change this UI. These are synthetic browser fixtures, not live runtime health evidence. To check manually, open an old message from stopped A while same-persona B is running; compare the displayed key and controls. Then open the persona itself and verify that representative selection still works. #### Evidence and limitations **5,793 desktop tests**, **56 profile/archive browser cases**, type/static/size checks and repository-wide `just ci` passed. The historical-message regression fails on unchanged main by opening B instead of A. [Published-head CI passed](https://github.com/block/buzz/actions/runs/33422207592). The [advisory security check](https://github.com/block/buzz/actions/runs/33422240973) timed out without a result; it is not a passing check. No availability, cloud-marker, discovery or mention-routing change is included. These screenshots do not establish remote delivery, agent execution or termination. #### Security authorization history (audit, not clearance) The [security gate](https://github.com/block/buzz/pull/7131#issuecomment-5482300054) remains visible and unresolved. Existing authorization-request comments were posted by `loganj`: [old-head request](https://github.com/block/buzz/pull/7131#issuecomment-5482311163) for `df6612b1db5a6f8d128cef955fd66a80b6828cb8` at 2026-08-31 17:55:11 UTC, then [current-head request](https://github.com/block/buzz/pull/7131#issuecomment-5482320424) for `9c4b6523ceaef0f3d92906fcdb5d9a3b9ede7e17` at 17:55:57 UTC. The existing [issue-comment workflow run](https://github.com/block/buzz/actions/runs/33422240973) ended cancelled after the previously reported timeout; it did not produce a completed security review. Latest exact-head Run/Post Codex jobs are skipped, not security approval. Historical comments remain available at their original links; consolidating their audit here does not withdraw authorization or clear the gate. An authorized security workflow owner must arrange the missing exact-range result. --------- Signed-off-by: Logan Johnson <loganj@squareup.com> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> | 1 个月前 | |
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` = 8133d70bb + a comments-only commit correcting two `profile/hooks.ts` freshness comments from 60s to 10 min; pre-push desktop check/typecheck/test 5,387/0 re-ran at 0c492366d) in one shell; `origin/main` = `040b203f7` at PR open, since moved to `4baccd539` (#6558, mobile only — zero file overlap, `git merge-tree` clean): - `just desktop-test` — 5,387 passed / 0 failed (includes new hook-level call-count test: cold = 1, stale-prefetched = 1, hydrated = 0 on mount then 1 on invalidate with wholesale replacement) - Playwright smoke `relay-reconnect.spec.ts` + `thread-unread.spec.ts` + `channel-head-restart.spec.ts` — 30/30 (thread-unread was 8/13 at `7acbf951b`; relay-reconnect was 15/16 at `bcfe04e2f`). The restart spec persists a head, reloads into a fresh mock relay with the head fetch held 5s, asserts the persisted row paints within 2s, exactly one `get_channel_window` after open, and the stale row is removed when the authoritative page lands. - `pnpm typecheck`, `pnpm check` — clean At `7acbf951b` (everything except the two-line `useThreadReplies.ts` staleTime revert and the test-only `relay-reconnect.spec.ts` change), also green in one shell: - `just desktop-tauri-test` — 2,859 passed / 0 failed across the workspace (channel_head_cache: wire shape, LRU+caps, schema reset, corrupt-row skip) - `just test-unit` — 632 passed (buzz-core/auth); `cargo test -p buzz-relay --lib` — 908 passed / 0 failed - `just check` components: fmt-check, clippy, desktop-check, desktop-typecheck, desktop-tauri-fmt-check, desktop-tauri-clippy, web-check, mobile-check, file-size-check — all green - `just desktop-build`, `web-build`, `desktop-tauri-check`, `mobile-test` (1,661 passed) — all green CI note: the "Unit Tests" job goes red on this PR and on `main` whenever it hits a poisoned `rust-cache` entry (an empty-directory skeleton of `target/sherpa-onnx-prebuilt` that `sherpa-onnx-sys` build.rs trusts), surfacing as `could not find native static library sherpa-onnx-c-api` in `buzz-voice` — a crate this PR doesn't touch. Deleting the cache entry and rerunning turned the job green at `0c492366d` (28/28); it re-poisons on the next `main` push until the workflow clears that directory after cache restore. Reviewed in-channel by Wren (9 / 9 / 9.5) and Eva (9 / 9 / 9), and line-by-line by me before opening; the staleTime fix re-verified by Wren and me independently; the relay-reconnect test fix bisected and verified by me. --------- Signed-off-by: Perci <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@buzz.block.builderlab.xyz> Signed-off-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz> Signed-off-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz> Signed-off-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz> Co-authored-by: Perci <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@buzz.block.builderlab.xyz> Co-authored-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz> Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz> Co-authored-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz> | 1 个月前 | |
Projects v3: unify sharing, discussions, and issue ownership (#5792) ## Summary Projects v3 makes repository work shareable, discussion-aware, and easier to scan in one coherent workspace. People can copy canonical links, reopen the exact workspace tab, understand issue and pull-request context at a glance, find related channel conversations, and assign or unassign issues across Desktop and CLI. - **Unified workspace** — top-level sections sit above repository controls in one rounded workspace, with navigation positioned close to the page heading. README and Files retain branch selection; every section has a labeled icon header, and Issues and Pull Requests expose creation from a consistent right-aligned action. - **Repository management** — the repository selector is always available, including single-repository projects. Its integrated add flow lets project owners create a repository manually or select an existing repository without a separate toolbar button. - **Readable work-item lists** — issue and pull-request rows use plain-language context instead of opaque metadata. Files, commits, issues, pull requests, channels, and contributors share consistent row density and right-aligned timestamps, while deterministic fallback-avatar colors keep participants distinct on light backgrounds. Inbox pull-request metadata wraps between complete phrases and truncates long channel names instead of compressing copy into narrow columns. - **Reliable entity links** — projects, repositories, issues, pull requests, and commits have canonical `buzz://` links, preview cards, OS deep-link routing, and tab-aware navigation. Reopening the same link re-applies its destination instead of leaving the user on a locally selected tab. - **Related conversations** — repository and work-item views surface channels discussing the current entity, including participants, channel navigation, message context, and an explicit notice when discovery reaches its 500-result cap. - **Reversible issue ownership** — trusted assignment and unassignment events work across Desktop, Tauri, `buzz-sdk`, and `buzz issues`. Assignees appear in project views and the assigned inbox, while authorized users can remove assignments directly from the assignee row. Assignment state is derived chronologically from labeled Nostr notes. Issue authors and repository owners may change any assignee; other users may only assign or unassign themselves. Shared golden fixtures keep entity-link grammar and validation aligned across TypeScript and Rust. The branch also updates `webbrowser` to the patched release for RUSTSEC-2026-0257. ### Related issue N/A. ### Testing - [x] `just ci` — formatting, lint, typechecking, unit tests, and builds passed - [x] Full pre-push suite — organization, branch-skew, Desktop checks, typechecking, and tests passed on the latest push - [x] `cargo test -p buzz-cli` and focused `buzz-sdk` assignment tests passed - [x] Focused Tauri recipient-note and 500-result search-limit tests passed - [x] Desktop entity-link and issue-assignment unit tests passed - [x] Playwright smoke coverage passed for assignment, repeated entity-link navigation, repository create/select flows, section headers and actions, timestamp alignment, timeline icons, sentence-style issue/PR metadata, header spacing, avatar contrast, and Inbox metadata at stacked and side-rail breakpoints - [ ] Manual staging pass: link round-trips, Channels tab, assignment flows, and inbox routing ### Screenshots Pull requests explain who opened the request, where it lives, and which branch it comes from; fallback avatars remain visually distinct.  Issues use the same sentence-style hierarchy while keeping status and recency easy to scan.  The wide Inbox detail keeps author, timestamp, and origin context readable beside its metadata rail.  [View the complete six-state Projects v3 screenshot set](https://github.com/block/buzz/pull/5624#issuecomment-5268039672) and [the compact/wide Inbox comparison](https://github.com/block/buzz/pull/5624#issuecomment-5268614585). --- > Supersedes #5624, whose head commit accumulated permanently-queued required check suites (block-dco-check et al.) that GitHub never dispatched. History flattened into a single signed-off commit on latest main; tree verified byte-identical (`git merge-tree`) to merging the original branch into main. --------- Signed-off-by: Thomas Petersen <thomasp@squareup.com> Co-authored-by: Wintermute <3f1797424fd9ad6653a83665c660517777cd7f8c228c0d5907f49e01537f3ca5@buzz.block.builderlab.xyz> | 1 个月前 | |
mesh: upgrade runtime, enforce membership, add shared compute provider (#1656) | 2 个月前 | |
docs: specify durable data backfills (#7326) ## Why Data backfills need a durable contract outside schema migrations. The contract must survive crashes and support automatic or manual operation without adding version-selection machinery. ## What - Define one PostgreSQL-backed lifecycle for finite data repair - Treat the stable backfill ID as the only durable execution and readiness identity - Use eventually consistent, idempotent registration; compatible workers compete for one exclusive claim - Require bounded batches, atomic mutation and checkpoint commits, generation fencing, and validation before completion - Define all four migration and automatic-backfill configurations - Define the authorized admin API and honest client projection ## How A backfill captures one immutable upper bound, processes finite batches, and commits each target mutation with its checkpoint. PostgreSQL owns lifecycle state, claims, progress, and completion. Registration order and build metadata do not choose a definition. Compatible processes are interchangeable. A material behavior change requires a new stable ID. The orchestrator does not resolve different behavior registered under one active ID. Diagnostic validation on completed rows records only its bounded outcome and audit; it does not create an execution claim. The spec now starts with a six-step mental model and follows the operator flow. It removes repeated requirements and cuts the document from 614 lines and 4,835 words to 474 lines and 3,656 words. ## Risk Low. This PR changes documentation only. It commits Buzz to a design contract, but it does not add runtime, schema, configuration, or protocol behavior. ## Testing No manual runtime testing. A fresh full-spec review checked the lifecycle, safety rules, four configuration cells, admin contract, and production-seam conformance requirements. `git diff --check` and the repository file-size policy pass at `13681664674d4667b181a6211f012090fa40a2cf`. ## Bigger picture The design keeps the relay as orchestrator, a focused backfill crate as domain owner, and `buzz-db` as narrow transaction infrastructure. It removes deployment generations and definition precedence from the implementation plan. Generated with Codex --------- Signed-off-by: tornquist <tornquist@squareup.com> Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> | 12 天前 | |
Make relay readiness process-local (#7341) ## Summary Make Kubernetes readiness depend only on the relay process lifecycle, so a shared Postgres or Redis slowdown cannot withdraw every pod at once. Move dependency checks to one fixed-cadence loop per pod. The loop permits only one evaluation at a time, emits metrics after each sample, and caches a timestamped report. `/_status` reads that cache without calling Postgres, Redis, or the deletion catalog. It reports `not_yet_sampled`, `fresh`, or `stale`. Each completed sample also writes a Unix timestamp gauge, so Datadog can compute sample age at query time. Fail closed when the community lifecycle lookup errors. An inconclusive tenant-state lookup no longer admits a socket to AUTH or REQ handling. ### Related issue None found. This addresses the elevated relay HTTP 500 and reconnect incident investigated on 2026-09-03. ### Tradeoff The readiness gauge now reports the latest private-probe observation, not the shutdown transition itself. After shutdown starts, a scrape can still see `1` until the next readiness probe refreshes the gauge to `0`. Kubernetes routing is unaffected because it uses the readiness response from the authoritative process state. A terminal `0` was never guaranteed: even a transition-owned gauge could update in process and then exit before Prometheus scraped it. If no scrape occurs before exit, both designs miss the terminal sample. The new design adds at most the interval until the next readiness probe when a scrape occurs during shutdown. Dependency status is now sampled every 30 seconds rather than on demand. A slow evaluation delays the next sample instead of stacking more work. The status payload exposes the cached report age. The completion timestamp stays unchanged when sampling stops, so `time() - timestamp` crosses the stale threshold without a server aging loop. Outcome counters and duration histograms remain cumulative and cannot provide no-data detection. ### Testing Gimli exercised exact SHA `edff1ec4a5362e132e367bf7449d2b0a68f1e9a9` on an isolated Blox stack. Repeated `/_status` bursts caused no dependency calls, metrics advanced without status traffic, stale status remained visible, and lifecycle lookup failures admitted no AUTH or REQ handling. Follow-up verification at `fdbb394ed598c94cde85ed01cb11e9de0ee8f00d` confirmed sampler-owned outcome and duration emission without status traffic and bound the real startup seam. The final timestamp-gauge change is covered by targeted red/green tests and the same boot regression at `c0f36e22b527c2fd6a63a548d036d006ffa17171`. ### Complexity Readiness remains one process-local sample per probe. Dependency I/O now has one owner and one execution path; request volume cannot increase dependency-check load. One completion timestamp replaces the prior server-side age update path; Datadog computes age without another loop. Community admission also returns to one fail-closed rule for inactive and unknown tenant state. Generated with [Claude Code](https://claude.ai/code) --------- Signed-off-by: tornquist <tornquist@squareup.com> Signed-off-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz> Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Amp <amp@ampcode.com> | 8 天前 | |
docs(identity): tighten enterprise adapter contract (#8069) Tightens `docs/enterprise-identity-adapter.md` to define the enterprise adapter contract implemented by kgoose in [cash-server#132770](https://github.com/squareup/cash-server/pull/132770) and the Buzz Desktop client in [buzz-app#562](https://github.com/block/buzz-app/pull/562). - Requires canonical `wss://host[:port]` relay URLs with a lowercase scheme, the lowercase ASCII (A-label) host form with no trailing dot, the default port omitted, and no trailing slash, path, query, or fragment. Adapters MUST configure relays in canonical form, compare `relay_url` against that exact form, and reject unknown relays with 403 `authorization_denied`. - Requires NIP-98 proof freshness of at most 60 seconds old and at most 5 seconds in the future, with the proof bound to the endpoint, method, and exact request body. - Rejects content-encoded request bodies with 400 `invalid_request`. - Caps assertions at `exp - iat <= 300` seconds and the adapter session expiry, requires `typ` `nip-fi+jwt`, and validates the echoed `nostr_pubkey`. - Defines refusal and retry handling: Clients MUST retry 429 and 503 with bounded backoff whatever the `error` code. Clients MUST treat any other status, a 400/401/403/413 response with a contract-undefined code, or a rejected `200` response including one it cannot parse, as a refusal: keep the session, show it as refused, and not retry automatically. Network failures retry with bounded backoff. This tightens the enterprise adapter contract restored in [#8064](https://github.com/block/buzz/pull/8064). --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Alia <d32955ad69077062930cc46cfe2df30ca9aaf6f8e76422681265e9e9af704d78@buzz.block.builderlab.xyz> | 1 天前 | |
feat(desktop): invite owned agents from standalone forums (#7125) ## Summary In Desktop's standalone Forums, selecting an owned agent from another device could leave a post or reply unsendable if the agent had not joined the forum. This adds **Invite / Cancel** to the send flow so you can resolve membership without leaving your draft. - **Invite** checks response policy and your permission to add members, adds the agent to the forum, waits for refreshed membership, then rechecks authorization before posting to the original destination. Membership is forum-wide, not limited to one post. - **Cancel / Escape** keeps the text, attachments and selected recipients for retry. Unlike chat's **Do nothing / Send anyway**, this dialog has no reference-only send choice. Invite is disabled while pending; Cancel remains available. - Leaving the source post/reply cancels its pending invitation, even if you return. Errors remain visible, focus returns to the initiating editor when appropriate, and late completion cannot resume a cancelled post or interfere with a newer attempt. - A rejected send restores text, uploaded media and exact selected recipients to the source draft only if no newer edit, deletion, upload intent or send supersedes it. Clipboard verification settles before recipient capture, with stale edits/navigation fenced out. ### Related issue Targets `main` after #7124 merged. This PR reuses its publication checks and draft protection; the five forum commits have been replayed unchanged onto the merged parent. Owned-agent discovery (#7122) is already merged. Split from #7114. Forum creation/templates, channel-less Notes and local-agent management are unchanged. Duplicate-name binding from #7133 is already merged and retained by this stack. Inviting does not start a remote agent or promise that it is online or will reply. ### Testing Forum composer lifecycle tests, including clipboard-settlement cases, and TypeScript/changed-file formatting checks passed after the restack. Earlier invitation, focus and transport-recovery browser coverage is retained, not claimed as a fresh full browser run on this head. See [live CI](https://github.com/block/buzz/pull/7125/checks) for current-head results. Browser evidence uses mock IPC; no full local `just ci` pass or native/live-relay validation is claimed. To try it: open a forum post or reply, select an owned nonmember agent and send. Cancel, then retry without reselecting; Invite should add that agent before posting. Deny the add to check the visible error and retained draft. Navigate away/back during a pending invitation or rejected send; no stale publication or overwrite of a newer draft should occur.  *Earlier mock-browser capture, not current-head runtime proof. [Error and successful-post captures](https://github.com/block/buzz/pull/7125#issuecomment-5513883462); no before-state/native capture available.* **Limits:** cancellation cannot undo accepted membership changes or dispatched posts; authorization and publication are not atomic. Recovery is same-window, subject to browser storage limits, and is not a durable in-flight send journal: reload/crash can lose a pending snapshot. Cross-window coordination and in-flight upload custody are unchanged. The parent's legacy member-agent compatibility does not establish ownership for nonmember invitations. --------- Signed-off-by: Logan Johnson <loganj@squareup.com> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> | 1 个月前 | |
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> | 1 个月前 | |
feat(git): add default-branch management to relay and CLI (#7562) Authored by Brain and opened on behalf of Wes (`wesbillman`). ## Summary Add `buzz repos default-branch get/set` and the relay operation it needs. Git push changes branch tips but cannot select the server's symbolic HEAD. This selects an existing branch without deleting branches, moving refs, or changing packs. - Reuse immutable Git manifests and captured-ETag CAS; concurrent pushes/default changes conflict, including stale no-ops. - Require request-specific NIP-98, body binding, fail-closed replay protection, host tenancy, current channel membership and repository-management authority. Push/project roles alone are insufficient. - Send the CLI mutation once, without redirects; ambiguous delivery retains the observed digest and is non-retryable. - Document the narrow HTTP exception to Nostr-first, delegation scope and admission-time ACL revocation. Kind:30618 remains derived; the manifest CAS is authoritative. - Register the Postgres/Redis/MinIO route and fresh-clone tests in the existing Backend Integration lane. ## Review fixes — `7457bda50afa6b52e36315572fa96928b2e21db6` - Reject signed valueless `payload` tags when verifying a body; preserve the shared verifier's intentional absent-payload compatibility. The real settings route now proves 401, unchanged manifest pointer, and unchanged kind:30618 event IDs for malformed and other invalid credentials. - Require a nonempty returned branch and `head == refs/heads/{branch}`; SET additionally requires the returned branch to equal the attempted branch. Malformed/mismatched mutation responses remain non-retryable `DeliveryUnknown`, retaining the attempted branch and original digest. Invalid reads fail before any POST. - Strengthen the later-push/fresh-clone regression to select `release/v1` while `main` exists. ## Verification At `7457bda50afa6b52e36315572fa96928b2e21db6`: - Full pre-push passed (all 14 Rust test groups, serialized Tauri tests/clippy, file-size and branch-skew gates; 522 seconds). No hooks disabled. - All four isolated Postgres/Redis/MinIO settings tests pass, including real Smart HTTP `ls-remote --symref` and fresh clone on `release/v1`. - All 15 focused NIP-98 verifier tests and three default-branch CLI tests pass; malformed credentials and missing-branch responses were reproduced failing before the fixes. - Five targeted mutants are killed: restoring the valueless-payload bypass, weakening canonical HEAD validation, accepting an empty branch, dropping requested-branch confirmation, and omitting hydrated HEAD for the selected non-main branch. Mutations ran in a separate worktree and target directory; original patch bytes were verified restored. - `cargo clippy -p buzz-auth -p buzz-cli -p buzz-relay --all-targets -- -D warnings`, formatting, diff and file-size checks pass on the fixed tree. Earlier validation at `bb195b140`: seven Git authorization database regressions and 28 existing transport tests passed (three infrastructure cases in that module not selected); four mutations killed management/replay/CAS/redirect regressions. Full pre-push passed with `RUST_TEST_THREADS=1` (all 14 Rust groups, Tauri clippy and 3,173 tests, size/branch-skew gates). Serial execution avoids an unrelated process-global Desktop discovery counter flake; no hooks disabled or Desktop changes. Carl and Jude's review findings are addressed in the new head; **fresh reviewer and exact-head security confirmation remain required**. The earlier source review is not approval of this revision. No claim of green new-head CI. ## Rollout Merge and **deploy the relay support first**, then build/use the updated CLI and explicitly set/verify the desired default. No schema migration. This PR does not deploy anything or change a live repository. Originating conversation (channel `8e2fda80-9142-4df8-8e14-da288bea6471`): buzz://message?channel=8e2fda80-9142-4df8-8e14-da288bea6471&id=8a7a9cf12737abc1e16605f241300be940a1691ef0992b35197afe26c563108d --------- Signed-off-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz> Co-authored-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz> | 26 天前 | |
fix(desktop): use WEBKIT_DMABUF_RENDERER_FORCE_SHM for NVIDIA/AppImage (#3654) (#4505) ## Summary - Heuristic and `--safe-rendering` now set `WEBKIT_DMABUF_RENDERER_FORCE_SHM=1` instead of `WEBKIT_DISABLE_DMABUF_RENDERER=1` - Legacy `DISABLE_DMABUF` stays owned so operators can still set `=0`/`=1` and take over the decision - Linux troubleshooting docs updated to match (#3654) ## Test plan - [ ] unit tests in `webkit_rendering::tests` - [ ] On NVIDIA + WebKitGTK 2.52: workspace switch no longer SIGSEGVs where the distro NVIDIA guard does not fire (Debian/Ubuntu proprietary-NVIDIA may still crash — #3654 stays open for that path) - [ ] `WEBKIT_DISABLE_DMABUF_RENDERER=0` still stands the heuristic down --------- Signed-off-by: Taksh <takshkothari09@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Alia <d32955ad69077062930cc46cfe2df30ca9aaf6f8e76422681265e9e9af704d78@buzz.block.builderlab.xyz> Co-authored-by: Will Pfleger <pfleger.will@gmail.com> | 1 个月前 | |
fix(desktop): restore mention chip identity icons (#7338) ## Summary Restore the missing **@ glyph for people and robot icon for agents** after #7133, and incorporate the requested compact public-key display. - Wrapping mention chips render their existing bounded icon-bearing leading fragment. - Readonly chips show bound keys using the same `8 leading…4 trailing` formatter as the channel member list: `Scout (150b20bd…15dc)`. - Full literal labels and exact keys remain authoritative in metadata, profile targets, title/accessible-name attributes, editor text, saved bodies and recipient tags. Display abbreviations are never used for recipient lookup. - Copy/paste restores the full literal label for a complete compact chip; partial selections remain plain text. Two keys sharing the same abbreviation still round-trip to their separate exact recipients. - No recipient-resolution, authorization, wire-format, composer, or CSS changes. Existing labels, icons, cloud markers and ordinary mentions remain intact. ## Verification Published candidate `9365ab9bc9d9c5d10802580cd576f8e378ff492f`, based on main `e09f715c9d0ee2cb7bf8a39061e601f3a502f588`: - **6,444 desktop unit tests passed** on this candidate's final source tree. - **44 mock-Chromium tests passed, zero retries in the final run** across mention recipients, clipboard and cloud provenance: exact recipient selection, ambiguity rejection, send/edit/reopen, forwarding, full/partial copy, mismatched-key rejection, matching-abbreviation collisions and 100%/150% narrow-window geometry. - TypeScript, desktop Biome/check guards, protected-feature production build and E2E build passed. - The compact-renderer regression fails with the formatting call removed. Original missing-icon and hidden-text accessibility regressions have red/green evidence. - Fresh self-review traced rendering, full-key metadata, copy classifier, paste normalization and identity trust. The same display formatter owns the accepted compact form on both clipboard sides. Iteration exposed an existing team-insertion separator flake (passed final full run) and two new fixture assumptions: non-member sends require invitation, and Chromium rich paste may retain an NBSP separator. Tests now exercise invitation and normalize only that separator when comparing the captured full body and exact tags; no product change was needed for either. Earlier local repository-wide `just ci` completed in two invocations because its initial call hit the ten-minute tool limit during Tauri compilation. Unchanged native/mobile/backend evidence is reused; the desktop delta received the full checks above and new remote CI. Native VoiceOver, real Tauri selection, dark theme and non-Chromium observation were not performed. Browser artifacts exercise real frontend with mocked Tauri/relay boundaries, not an installed release. ## Review and visual evidence Current-head CI and automated review must complete after this update; the old `2997bfb5` green results do not establish this new head. Required human review remains separate from agent approvals. No merge/install/restart authorization. Before/after icon evidence: https://github.com/block/buzz/pull/7338#issuecomment-5544575866 Updated compact-key screenshots are posted below. The editor intentionally retains the full literal address; only readonly chip display is abbreviated. Origin: buzz://message?channel=3355d33a-b72a-423a-b064-a58275f9a8af&id=38b3a27e689f5a9604e273d45f4e3122fceba76081e7bcd3bbdbf439524a5a18 --------- Signed-off-by: Logan Johnson <loganj@squareup.com> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> | 1 个月前 | |
docs: specify desktop-driven mobile push suppression (#7809) ## Summary Define when desktop activity suppresses otherwise eligible mobile message notifications: a ten-minute inactivity window, short-lived relay-owned suppression, and per-installation push preferences. Include a shared glossary and renewal timing diagram. The gateway remains unaware of suppression. This is a design draft ready for review, not an implementation-ready protocol. Wire format, synchronization, and several failure-handling details remain explicitly open. Please also review the tension between the agreed relay-confirmed preference changes and VISION_MOBILE.md's requirement that stopping notifications must not depend on a relay response. ### Related issue Related: #3234 covers per-channel notification settings and push filtering; this spec focuses on desktop-activity suppression. No duplicate spec PR found. ### Testing Documentation-only change. Local Markdown links and anchors resolve; whitespace validation and commit hooks passed. The full local CI gate could not complete: dependency compilation exhausted local disk space (OS error 28). GitHub checks currently report no failures. --------- Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: Diem Nguyen <ngthuydiem@users.noreply.github.com> | 8 天前 | |
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> | 1 个月前 | |
mesh: upgrade runtime, enforce membership, add shared compute provider (#1656) | 2 个月前 | |
fix(deletion): require sole owner at admission (#7966) Follow-up to #7830: reject owner-origin deletion admission for a legacy co-owned community before creating a request. Under the existing community-row lock, admission locks all current owner memberships in pubkey order and requires exactly one matching asserted owner. Existing-request idempotent replay remains unchanged; completion retains its independent post-lock revalidation. **New optional operator field: `community_id`** (`ab0cd713`, `766eb07f`). Owner-origin `POST /operator/communities/delete` now accepts an optional `community_id` that binds the request to the host's community: - **Fresh submission:** checked after sole-owner authority is proven, so a non-owner still gets `404 community_not_found`. A mismatch returns `409 community_id_mismatch` and writes no request row. - **Replay:** checked against the stored request, and only when the stored request is for the same host. A mismatch returns `409 community_id_mismatch`, and the stored request is unchanged. A known UUID sent with a different host goes through the existing convergence check and returns `409 deletion_request_conflict`. - **Omitted:** behaves as before. `admit_owner_request` takes `expected_community_id: Option<Uuid>` rather than a second public entry point, and existing callers pass `None`. - `docs/operator-community-deletion.md` documents the ordering. Also folds in the non-blocking review nits from #7969 (wpfleger96, at `e21151f4`): - **`limit_reached` has a stable `code`.** Create (`provision_community`) and transfer-in now return `409` with `code: "limit_reached"`, matching the other coded lifecycle errors. The `error` message keeps its `limit_reached:` prefix, so clients that match on the message still work. The provision limit test now asserts the code, and a new PostgreSQL test covers a transfer to an owner at the limit (coded 409, no membership change). - **Docs: an ack-version mismatch is a 400, not a 409.** `docs/operator-community-deletion.md` and the `delete_community` handler doc now say a changed host or owner under a known UUID is `409 deletion_request_conflict`, and an unsupported acknowledgement version is rejected first with `400 unsupported_acknowledgement_version`. - **Docs: active-cap overrides above the lifetime cap can't be reached.** The `max_communities_per_owner` doc and the quota section now say `MAX_LIFETIME_COMMUNITIES_PER_OWNER` (20) counts live ownership, so `BUZZ_MAX_COMMUNITIES_PER_OWNER` above 20 can't be reached. - **Legolas P2s:** - The UUID-conflict docs now list every 409 case, including a stored ack version that doesn't match and a UUID held by an operator-origin request. - The runbook now says a legacy co-owned community is rejected as `404 community_not_found` and should be converged with a transfer first. - The preparation drift test now pins `DeletionSafety` plus "sole-owner authority drifted". - **Guard wording:** the `delete_community` handler doc now says "sole current owner", matching the admission check. What got simpler: admission and automatic preparation now enforce the same sole-owner authority, so no accepted-but-unexecutable co-owned request needs a new recovery path. `deletion_api_error` is renamed to `coded_api_error`, since it now carries non-deletion codes too. There's one helper for coded operator errors instead of a second one. Rebased onto main after #7969 merged (`d7a35afa`). The sole-owner commit applied cleanly. Earlier focused Blox verification (at the pre-rebase head `161e9857`): the new admission regression was red before the change and green after it. Five temporary, restored mutants each failed its matching production-seam regression. At the current head `766eb07f`, the relay `api::operator::` suite passes 23/0 on Blox. Each new `community_id` guard, removed one at a time, fails its own regression test. CI owns the full suites. Remaining debt: the separate P2-A purge/completion membership/community lock-order cycle is unchanged. Generated with Codex --------- Signed-off-by: Codex <noreply@openai.com> Signed-off-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz> Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz> | 6 天前 | |
fix(desktop): discover authenticated owned relay agents (#7122) 🤖 ## Summary An agent you own could be missing from **New message → To:** and **Channel members → Add people and agents** on a machine that has never managed it. This PR lets those existing lists find your agent without requiring a shared channel first. Desktop now checks records proving you own it, rather than looking only at agents in channels you've already joined. **No new screen or control is added.** For example, an agent with verified ownership and **Who can send instructions → Only me (default)** can now appear even with no shared channels. Each screen still applies its existing access rules; this does not make every discovered agent selectable everywhere. | Screen / control | Before | After this PR alone | | --- | --- | --- | | **New message → To:** recipient picker | An owned agent absent from this machine and shared-channel bot lists could be missing. | Its named **agent** row can appear; selecting it adds a recipient chip. This is recipient selection, not a guarantee that a later message will reach or wake the agent. | | **Channel members → Add people and agents** | The same agent could be missing from **Not in this channel** search results. | Its row can appear with the existing **Add** button. If you can add members, that button submits the existing channel-membership request; finding the row alone changes no membership. | | **Stream / forum composer → @ suggestions** | An owned agent already in the channel under an ordinary member role could be missing from agent suggestions. | Its actual membership is recognized without requiring the bot role. Agents not managed on this device still need membership in that channel. | | **Pulse → Agents** | An agent absent from both local management and the server's agent list was omitted from the count and author lookup. | The count and feed's author lookup can include it; notes appear only if it has published them. | Being listed does **not** mean the agent is online, add it to a channel, or grant local Start/Edit controls. For agents not managed on this device, global **Search** still excludes those configured for “Only me”, and DM @ selection is not added here. DM @ selection and message-driven nonmember invitation are addressed in [#7124](https://github.com/block/buzz/pull/7124); the standalone forum **Invite / Cancel** flow is in [#7125](https://github.com/block/buzz/pull/7125). <details> <summary>Ownership and membership checks</summary> A discovery lead is not proof: the latest agent profile must have a valid signature and exactly one valid ownership attestation—the owner's signed link to that agent. Its response policy must be signed by that verified owner; an invalid latest policy cannot restore an older permission. Membership comes separately from the latest server-signed roster, including removals. Existing profile cards, owner labels and agent-avatar shapes also use this stricter verification: malformed or forged evidence must not supply ownership/agent classification on its own. Valid ownership was already recognized; no profile-picture or badge design changes. Attestation time conditions apply to the signed event's timestamp, not a live expiry timer. Existing legacy compatibility and builds requiring verified owner policy retain their respective rules. Discovery and sending remain separate operations, not an atomic permission check. </details> ### Review corrections - When runtime and owner policy overlap, **explicit online/away/offline from the verified latest runtime is retained**. Policy still supplies ownership/permissions; claimed runtime membership is not restored. Missing/unrecognized status stays unknown, and invalid latest policy cannot revive runtime permissions. - Discovery without runtime evidence is now **unknown**, not offline: native conversion, both IPC adapters, Pulse, Projects and profile/session consumers preserve that distinction. Unknown has no status dot and is not promoted to a deployed/running agent. - Both relay-only picker paths retain the authenticated owner, including the existing **managed by you** label. The analogous global Search projection is fixed without changing its existing “anyone” filter. - Authorized stored profile activity remains visible when liveness becomes unknown/absent or the active turn ends. History reads do not start a live subscription, grant access, or imply current availability. ### Related issue Independent base: `main`. Child: [#7124](https://github.com/block/buzz/pull/7124), then [#7125](https://github.com/block/buzz/pull/7125). Extracted from [#7114](https://github.com/block/buzz/pull/7114), retained as historical source (`98fe33ec`). [Behavior contract](https://github.com/block/buzz/blob/3a56d17824522580fe04cae463b54f4c7ba66021/docs/owned-agent-discovery.md). Originating [Buzz discussion](buzz://message?channel=f7a9536a-1738-4bad-a888-b3ea25010ef1&id=7aa1f0ab23dce514bd8a0221441cf005bf428914621171472b79747c50820848) · channel `f7a9536a-1738-4bad-a888-b3ea25010ef1`. ### Testing Current candidate: `3a56d17824522580fe04cae463b54f4c7ba66021`, a four-file native/test/doc runtime-status repair atop published `ae23c1c9680a881cee7eed94e259bf15bf8ce3f7`. Branch ancestry is main `1c8321cd08feb597f8bcff5195c21148fb3e98ed`; refreshed main `0e878664b08cdf7fb2d89d940bc2aa92cdc485f7` adds only the independent CI-workflow split. Read-only mergeability succeeds; this is not a tested merged-tree claim. **Local CI attempt and continuation (not an uninterrupted green run):** the new exact-head `just ci` passed formatting/static checks, workspace and Tauri clippy, workspace Rust tests, **5,910 desktop tests**, desktop production build and Tauri check. Its native main target finished **3,073 passed / 1 failed / 19 ignored** (exit 101): `cheap_discovery_reports_absent_before_any_forced_probe` saw a process-global login-shell counter of 2 instead of 0. The counter includes unrelated version/adapter probes whose tests do not hold the failed test's PATH mutex; no managed-agent discovery implementation changed in the runtime repair. The unchanged failing test then passed **three isolated invocations**. Only the failed native workspace lane was retried with `RUST_TEST_THREADS=1 just desktop-tauri-test`: **3,074 main-target tests passed / 19 ignored**, all additional workspace targets passed (exit 0). The previously unrun `just web-build mobile-test` tail then passed (exit 0; **2,019 mobile tests**). Earlier successful lanes were reused; no source/guard changes or blanket CI rerun. The original failure and all diagnostic/retry logs are retained. - **71 native `nostr_convert` tests pass**, including seven new production merge regressions: online/away/offline, missing/invalid status, policy-only, status-less latest replacement and forged latest replacement. Before production repair, those seven yielded **4 failures / 3 passing controls**. - Reused frontend evidence from `ae23c1c9` (frontend is unchanged): Desktop TypeScript and isolated E2E build pass; **9 browser tests / 0 retries**, covering both relay-only picker journeys and seven adjacent stop-control regressions. Real UI with mock Tauri IPC, not live relay/native webview. - Earlier `ae23c1c9` local `just ci` passed without failures, including 3,067 native main-target tests / 19 ignored and 2,019 mobile tests; not substituted for the new source gate above. - Reused unchanged repair evidence: **17 real-store/hook history regressions**, **161 focused tests**, and independent **9 mounted owner/bot/identity revocation/regrant transitions** with zero hook-phase native calls. The regression was falsified before repair (14 failures, 3 controls). - Signed local-server fixtures cover discovery with no local/shared record, ordinary-role membership, forged ownership, invalid signatures, duplicate authentication, wrong-owner/latest-invalid policy, revoked membership and wrong destinations. These establish native data checks, not a live agent response. GitHub checks and renewed technical/security review must apply to the current published head; earlier-head green checks are not replacement-head proof. Local source review is not formal code-owner/latest-push approval or exact-range security authorization. A green security workflow with substantive review skipped is not security clearance. ### Screenshots #### Relay-only picker evidence — `ae23c1c9680a881cee7eed94e259bf15bf8ce3f7` These cropped rows come from the two real production picker journeys in [`owned-agent-discovery.spec.ts`](https://github.com/block/buzz/blob/ae23c1c9680a881cee7eed94e259bf15bf8ce3f7/desktop/tests/e2e/owned-agent-discovery.spec.ts), using mock Tauri IPC with **no local agents and no user-search duplicate**. The fixture supplies verified-owner data and unknown availability; the browser test checks its presentation, not native signature verification. Both exact-tip journeys pass without retries. No live relay, native webview, invitation, delivery or wakeup is claimed. Before the repair, both relay-only candidate constructors discarded the owner, so the existing “managed by you” label was absent. These are after-repair captures; no before image was captured. #### New Message → To The relay-only agent retains its authenticated owner label.  #### Channel members → Add people and agents The matching result retains “managed by you” beside the existing Add action; the test does not click Add or claim membership changed.  --------- Signed-off-by: Logan Johnson <loganj@squareup.com> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> | 1 个月前 | |
Add generic information-flow control core (#7293) Adds a dependency-free `ifc-core` crate implementing reader-set confidentiality labels and monotonic per-computation flow state. - Defines flow ordering, join, and meet over caller-supplied principal universes. - Fails closed for unknown or cross-universe input and checks reader widening at egress. - Includes property tests for lattice laws and monotonic taint, plus the design paper that motivates the broker integration. The crate deliberately contains no Buzz, Nostr, channel, membership, or grant policy; those remain in the Buzz adapter. --------- Signed-off-by: Jordan Mecom <jm@squareup.com> | 1 个月前 | |
feat(push): support configurable HTTP(S) delivery URLs (#7877) The relay can use a configurable HTTP(S) gateway delivery URL and signs the same URL it sends. The gateway verifies NIP-98 against the received request URL, including authority, port, path and query. App Attest, scoped grants, replay checks and delivery quotas are unchanged. Forwarding proxies must preserve Host and supply the original request scheme through X-Forwarded-Proto. The NIP relay-delivery wording now defines the configured HTTP(S) URL as the signed URL and labels the public URL as an example. <!-- buzz-review-completed --> --------- Signed-off-by: Tom Brow <tomb@block.xyz> | 11 天前 | |
refactor: move agent Git bootstrap into ACP harness (#7819) discovered in https://github.com/block/buzz/issues/7742#issuecomment-5783512985 ## Summary Goose uses its native shell, so it bypassed the Git identity and signing setup inside `buzz-dev-mcp`. Agent commits could use the host Git identity, while MCP shells had a separate config. This moves Git setup into `buzz-acp`, where both paths receive the same agent identity, Nostr signer, and relay-scoped credential helper. Before: ```text Buzz Desktop └─ buzz-acp ← Desktop supplied relay Git authentication ├─ Goose → native shell ← no agent Git author or signing setup └─ Buzz Agent └─ buzz-dev-mcp └─ shell ← MCP created the keyfile and Git signing config ``` After: ```text Buzz Desktop or Sprig └─ buzz-acp ← owns the keyfile, identity, signing, and scoped credentials ├─ Goose → native shell ← inherits the complete Git environment └─ Buzz Agent └─ buzz-dev-mcp └─ shell ← receives the same Git environment via mcpServers[].env ``` The harness keeps the signing key in a private temporary file and removes it on normal exit, startup failure, and graceful termination. It composes inherited `GIT_CONFIG_*` entries, forwards the complete block to MCP servers, and bundles the helpers through the ACP multicall entrypoint. For standard `buzz-acp` launches, Desktop and Sprig no longer write Git config. A custom Desktop ACP command bypasses the harness, so Desktop preserves its prior relay-scoped credential helper for that supported override. This changes Git settings for every ACP runtime. The startup signal path also changes so a termination during adapter initialization removes the keyfile. The temporary file can remain after SIGKILL or a machine crash. ### Related issue Related: #6177 covers stricter commit identity enforcement and touches the same Git paths. That PR and this one need to be reconciled before either lands. No issue was found for this runtime bootstrap gap. ### Testing - Ran the ignored `git_runtime_tests` with real Goose 1.50.0 and Buzz Agent ACP sessions. `real_goose_native_git_shell` passed again on this PR branch with `BUZZ_TEST_BIN_DIR` pointing to built binaries. Goose made a signed commit and tag through its native shell; Buzz Agent did the same through its MCP shell. Both verified signatures, identity, credential scoping, and keyfile cleanup. - Built the Linux Sprig image and ran `scripts/test-sprig-image.sh buzz-sprig:acp-git-bootstrap`. The container made and verified a signed commit and tag, then confirmed keyfile removal after SIGTERM. - `just ci` passed on the original branch. After rebasing onto `main` and addressing review feedback, the ACP Git bootstrap integration tests, focused desktop smoke test, custom-command regression test, formatting, and ACP/Desktop Clippy passed. The pre-push hook hit two timing-sensitive ACP deadline tests under parallel load; both passed alone and the complete ACP package passed serially (964 library, 2 Git bootstrap, 9 pool lifecycle tests). The push used `--no-verify` after those checks; GitHub CI is rerunning on the new head. - Under injected `BUZZ_ACP_ALLOWED_RESPOND_TO`, `BUZZ_ACP_LAZY_POOL`, and `BUZZ_ACP_IDLE_POOL_SLEEP`, the clean-env serial package command in `crates/buzz-acp/TESTING.md` passed: 964 active library tests, 2 Git bootstrap tests, and 9 pool lifecycle tests. Both named Databricks OAuth tests passed locally; another reviewer reproduced their environment-specific failures on `main`. - No authenticated relay clone or push was run. The local probes used disposable repositories and did not contact a relay. Generated with Codex --------- Signed-off-by: Salman Mohammed <smohammed@squareup.com> | 13 天前 | |
fix(desktop): authorize remote mentions at publication (#7124) 🤖 ## Summary In Buzz Desktop, you could own an agent running on another device but be unable to mention it in a channel where it had not yet joined: it was filtered out before you could invite it. A selected agent could also disappear from the message's recipients when permissions changed. This lets you select an eligible agent in the existing **@ menu**, invite it from the message composer, and send to that agent—or see an error and keep your draft rather than silently sending without it. #### Where the experience changes | Screen / control | Before → after | | --- | --- | | A channel's **Message #…** composer, or a message's **Reply in thread to …** composer | Type `@` (or use the existing @ button), choose your agent, write the message and press **Send message**. An owned agent not yet in the channel can now reach the existing **“Mention people outside this channel?”** dialog when its response settings allow you to address it. | | That dialog's **Invite** button | Previously the membership requirement could block the agent before the invitation. Now Invite checks permission to add it, adds it as an agent member of the **channel** (not just the thread), then rechecks membership and response permission before sending the waiting message. An agent already in the channel needs no invitation. | | Existing direct message, or the new-message screen with the **To:** field | A mention is checked against the conversation the message will actually enter, including a newly created direct message, rather than the old or not-yet-created destination. This does not add an Invite control to direct messages. | | Editing a message / sending attachments | The selected agent remains part of the send or edit attempt through attachment upload and the final permission check. Lost permission produces a visible error instead of dropping that recipient. | **Invite is not the only chat choice.** The existing **Do nothing** button sends the message *without inviting or notifying the nonmembers*; their names remain references in the text. Where you cannot invite, that choice is labelled **Send anyway**. To abandon the send instead, dismiss the dialog with Escape. Invitation actions are disabled while preparation is pending, preventing duplicate clicks. **Leaving and returning must not resurrect a cancelled send.** Switching threads or leaving the composer cancels its pending invitation, even if you return to the same thread. Cancellation before dispatch sends no message; an accepted membership change cannot be automatically undone. An ordinary send without a pending invitation remains bound to its original destination rather than following you into another conversation. **Failed sends must not overwrite your next draft.** If you leave a thread, return and replace or deliberately clear its draft while an older send is pending, the older failure cannot restore deleted text, recipients or files; success cannot erase the newer draft—even if its text is identical. An untouched draft cleared automatically for sending remains recoverable on failure. This protection also covers reopening the composer and starting a newer send. The channel timeline also keeps its existing **new-messages / Jump to latest** button available when newer messages are waiting to be displayed. For example, after sharing a reply to the channel and closing the thread panel, you can click the catch-up button to reveal buffered messages. Closing the thread does **not** guarantee the shared row appears automatically or force you away from reading history. ### Related issue Built on [#7122](https://github.com/block/buzz/pull/7122), base branch `split/owned-agent-discovery`, which lets Desktop find and verify owned agents independently of this device. Current integration head: `1144465d00273cf74b7c22544ae5a3299bd98560`, built on exact published root `3a56d17824522580fe04cae463b54f4c7ba66021`. Root #7122 has its own CI and security gates; this PR must not land ahead of that dependency. Finding an agent is not channel membership, online status or a promise of a reply. This PR changes what the existing message controls can do with those agents; it adds no profile, presence, cloud marker or remote start/stop UI. Standalone forum post/reply **Invite / Cancel** is added separately in [#7125](https://github.com/block/buzz/pull/7125); here those composers only gain visible authorization errors. Same-name selection/binding fixes ([#7133](https://github.com/block/buzz/pull/7133)) and mention spacing ([#7128](https://github.com/block/buzz/pull/7128)) are not included. Extracted from [#7114](https://github.com/block/buzz/pull/7114) (historical source `98fe33ec`). [Behavior and draft-recovery contract](https://github.com/block/buzz/blob/1144465d00273cf74b7c22544ae5a3299bd98560/docs/remote-mention-routing.md) · [Originating discussion](buzz://message?channel=f7a9536a-1738-4bad-a888-b3ea25010ef1&id=7aa1f0ab23dce514bd8a0221441cf005bf428914621171472b79747c50820848). ### Testing  *Earlier candidate, mock desktop browser: the existing channel dialog now reachable for an eligible owned agent on another device. The two buttons have different send outcomes; Do nothing is not Cancel.* [Success and denial captures](https://github.com/block/buzz/pull/7124#issuecomment-5482292703) · [Pending-state capture](https://github.com/block/buzz/pull/7124#issuecomment-5482604329). These show the relevant UI, not live agent availability, native authorization or the later draft-storage/catch-up repairs. No before-state screenshot is available. Existing coverage exercises exact recipients, invitation rejection/cancellation, new direct-message destinations, uploads, edits, thread re-entry and stored-draft deletion. The timeline regression checks the shared reply becomes visible using the available catch-up action. **Integration validation (2026-09-02):** independently reviewed the routing delta onto root `3a56d178`: seven original patches unchanged; two reconciliations retain generic publication-error toasts alongside authorization errors and retain non-authored editability updates. Added two production-hook regression tests (normal and queued-media publication) requiring visible generic error, recovered draft and released pending state. - Writer validation: **5,995 Desktop tests**, **42 focused tests**, **22 mock-IPC browser journeys** (18 routing, 2 root provenance, 2 destination binding), and **1 voice-note failure journey** passed; lint, types, size guards and E2E build also passed. - The broad suite ran before the final formatting-only test amendment, not as an exact-final-head rerun. Independent AST comparison confirmed that amendment is semantics-preserving; **4 fresh assertions at final `1144465d`** passed. Publication rechecked final-head TypeScript, amended-test formatting and `git diff --check` successfully. No new full repository `just ci` run is claimed. - Browser tests use an isolated E2E build and **mock IPC**, not live relay/native authorization. Historical screenshots above are explicitly earlier UI evidence, not exact-head runtime certification. Packaged Tauri/live-relay behavior was not independently witnessed. - **Published-head gates:** [current CI run](https://github.com/block/buzz/actions/runs/33657948560) and [renewed exact-head formal review request](https://github.com/block/buzz/pull/7124#issuecomment-5513218340) must clear before landing. [Earlier CI run](https://github.com/block/buzz/actions/runs/33438436438) and the two earlier approvals cover `7ffead0f`, not this new head. Root CI/security clearance remains separate; the independent scoped integration approval is not merge authorization. To try it: in a channel or thread, select an owned nonmember agent, Send, then Invite or Escape and retry. Deny the add or revoke its response permission before sending: expect a visible error and recoverable draft, not a message missing the agent. During a pending send, return to the source thread, edit or clear the draft, then leave again: late completion must not overwrite that choice. **Limits:** permission checks and sending are separate operations; cancellation cannot retract a dispatched message. Draft protection is same-window, not new cross-window deletion synchronization. Standalone forum transport failure can still restore text/media without the exact selected recipients. Native compatibility is inherited: open-source builds may still recognize a valid legacy, self-declared agent already in the channel when verified ownership is absent or rejected; that does not establish ownership or unlock this owned-nonmember invitation path. Invalid policy from a verified owner is still rejected. No agent response is guaranteed. --------- Signed-off-by: Logan Johnson <loganj@squareup.com> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> | 1 个月前 | |
Add staging dev relay image workflow (#6709) ## Summary Adds a manual, collaborator-triggered workflow for publishing pre-merge Buzz relay runtime images for bb-block staging. - Resolves a canonical `block/buzz` branch or tag to an immutable commit SHA before checkout. - Builds the relay runtime image for `linux/amd64` and `linux/arm64` and publishes a single OCI index. - Tags each publication uniquely as `dev-sha-<full SHA>-run-<run_id>-<run_attempt>` so no tag is ever reused against the immutable-tag utility ECR pull-through cache (rebuilds of the same SHA aren't byte-identical, given mutable base tags and `apt-get update`). - Uses the staging-only `ghcr.io/block/buzz-staging-dev` namespace, which maps to a distinct utility ECR pull-through path. - Restricts dispatch to `block/buzz` on `refs/heads/main`; job permissions are narrowly scoped to `contents: read` plus `packages: write`. - Emits a deployment summary with the exact BPCI `repository` and unique `tag` values, plus the merged manifest digest. - Keeps the existing production/release image workflow unchanged. The first real workflow run must confirm GHCR package creation/access and utility ECR pull-through import for the new package. --------- Signed-off-by: Brad Seiler <seiler@squareup.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: tornquist <tornquist@squareup.com> Co-authored-by: coder 0 <d97ebdbb198c7237c94f84ea8bb8a73583ea067407eebd0062abbb3962527fb1@buzz.block.builderlab.xyz> Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: tornquist <tornquist@squareup.com> | 1 个月前 | |
Use worker snapshots for relay storage metrics (#7845) ## Summary Make the relay read completed storage-accounting snapshots from PostgreSQL by default. An environment with no snapshot emits no storage metrics. When a worker publishes its first result, the relay picks it up on the next successful leader usage tick, without a restart or configuration change. This changes how storage usage is measured and reported. **S3 remains the object store. PostgreSQL stores the completed counts, byte totals, community breakdown, and calculation metadata—not the objects themselves.** ### Why move relay storage accounting entirely to database reads? Today the relay supports two sources: its own S3 scan (`inline`, the default) and the worker's saved snapshot (`external`). That requires operators to deploy a worker and then coordinate a separate relay-mode change in each environment. The worker already owns the S3 listing and calculation. It runs in a separate process with its own object cap, memory limit, and deadline. Keeping a second scan inside the serving relay retains the resource pressure and failure modes that this isolation is meant to remove. PostgreSQL provides a durable handoff. The worker replaces one completed snapshot atomically after a successful calculation. The relay reads that row and exposes its values through the existing metrics endpoint. A relay restart or leadership change can load the last completed result without scanning the bucket again. There is deliberately no S3 fallback when a snapshot is absent or a read fails. Such a fallback would put the expensive scan back in the relay precisely when a worker is absent or unhealthy. It would also leave two calculation paths with different resource limits and freshness behavior. ### What happens if no worker exists? **A worker that has never published is a normal, successful inactive state.** The snapshot table must exist through the normal database schema, but it may contain zero rows. The reader returns success, emits no storage totals or failed-load gauge, and checks again on the next usage tick. It does not exit the relay, start a worker, or list S3. The relay checks for a completed database row; it does not inspect Kubernetes Jobs or CronJobs. Worker presence and snapshot presence are therefore different: | Situation | Storage-reader behavior | |---|---| | No worker and no snapshot row | Return success, emit no storage metrics, and query again next tick. | | Worker starts later and publishes its first row | Read and emit it on the next successful leader usage tick; no restart or mode change. | | Worker is removed or fails, but its last valid row remains | Continue reporting the last completed totals with their increasing age. | | Query succeeds but a previously present row has been removed | Clear the cache and stop refreshing storage series; existing exporter series expire through the idle timeout. | | Database read fails, times out, or returns an invalid snapshot | Log a warning, set `buzz_storage_snapshot_load_ok=0`, retain any last-good cached totals, and retry next tick. | | Snapshot table is missing | Treat this as a schema error, not normal worker absence; follow the failed-read behavior above. | | `BUZZ_STORAGE_METRICS=off` | Skip the snapshot query and all storage metric emission. | No row means “not measured,” not zero bytes. A valid completed snapshot of an empty bucket can report zero. If a read fails before any good snapshot has been cached, only failed-load health is emitted. These reads happen in the existing background metrics task, not the relay startup path. A storage-read failure does not request process exit or change readiness directly. A wider database outage can still affect the relay's existing database-dependent operations and readiness. ### Read, parse, and emit path ```text Worker: S3 listing -> BucketSnapshot -> JSON -> PostgreSQL Relay: PostgreSQL -> JSON -> BucketSnapshot -> existing metric gauges Collector: relay /metrics endpoint -> Datadog ``` 1. `main.rs::run_storage_sweep_tick` calls the reader from the existing leader-only usage task. The default interval remains 300 seconds. 2. `storage_sweep.rs::run_storage_metrics_tick` coordinates the read, cache update, failure health, and metric emission. 3. `refresh_persisted_snapshot` calls the existing `Db::load_storage_accounting_snapshot()` method. A five-second timeout covers pool acquisition and the query. It uses the relay's existing pool. 4. The existing query in `buzz-db/src/store/storage_accounting.rs` reads `snapshot`, `completed_at`, `duration_ms`, `max_objects`, and `code_sha` from the singleton row. 5. `serde_json::from_value(stored.snapshot)` decodes the JSON into the existing `buzz_media::BucketSnapshot` type. Rust infers that type from `cache_persisted_snapshot`'s argument. The generated `Deserialize` implementation handles all fields, including the UUID-keyed `per_community` map. 6. `emit_cached_storage_metrics` sets the existing physical/logical totals and community byte/object gauges. The existing Prometheus exporter exposes them for Datadog collection; this code does not send snapshot JSON to Datadog. The worker's `BucketSnapshot`/`CommunityStorage` structures, JSON field names, SQL publication method, and database schema are unchanged. The old external-mode JSON conversion moves out of `main.rs` into the reader helper. No new format or hand-written field parser is needed. Duration and object-cap metadata are validated before replacing the cache. The five-second relay read timeout is separate from the worker's 30-second startup acquisition budget introduced in #7770. This PR does not change the worker's pool or startup logic. ### Configuration and code changes | Configuration | Before | After | |---|---|---| | Unset, `inline`, or `on` | Relay-local S3 scan | Read completed database snapshots. `inline` logs one migration warning. | | `external` or `snapshot` | Read completed database snapshots | Continue reading snapshots. | | `off` | Disabled | Disabled. | | Unknown or empty value | Disabled with a configuration error | Same. | Existing `inline` settings intentionally become reader aliases. Merely changing the default would leave older deployments on their explicit `inline` setting and preserve the need for coordinated configuration PRs. Remove relay-local scan scheduling, in-flight scan state, scan configuration, and obsolete scan-attempt tests and health metrics. Preserve the physical/logical total and per-community metric names, leader-only emission, community scope filtering, and cleanup of old community labels. The shared relay-state field remains in place; its comment now describes a cached worker result. The Helm chart defaults to `relayMode: external` while leaving `storageAccounting.enabled: false`. Its rendering test verifies that the reader is configured even when no worker is created. The reader regression tests cover missing rows, later publication, updates, invalid data, timeout, removal, reactivation, and the explicit off switch. `docs/storage-accounting.md` describes this operating model. ### Freshness and rollout impact `buzz_storage_snapshot_age_seconds` uses the worker's original completion timestamp. Re-reading an old row never makes it fresh. `buzz_storage_snapshot_load_ok=1` means the row was read and decoded; it does not prove that the worker's latest attempt succeeded. Monitor snapshot age against the worker's schedule and allowed runtime, and monitor Job failures separately. The old relay scan-attempt health gauges are retired because the relay no longer runs those scans. Deploy this relay version through the usual release process. Once it reaches an environment, workers can be enabled independently. Already-released charts that still set `inline` work with the new binary; future chart releases default explicitly to `external`. An intentional `off` override still requires an operator to enable the reader. **Environments with no completed snapshot lose relay-calculated storage totals after this upgrade.** That is the intended tradeoff for removing scans from the serving process. Environments with a prior snapshot continue showing its last completed totals and age. This PR does not install workers in additional environments or deploy the new relay image. ### Related issue Related to #4601. Follows the worker startup fix in #7770. The separate fleet-usage collector work in #7176 overlaps this code and may need reconciliation when it merges. ### Testing Built and ran the relay locally against isolated PostgreSQL and Redis with the legacy `inline` setting, five-second usage ticks, and a 15-second metric idle timeout. The same process was exercised through these steps: 1. Start with an empty snapshot table: relay becomes ready and exposes no storage series. 2. Publish a snapshot containing 123 bytes: the community gauge becomes 123. 3. Replace it with 456 bytes: the gauge becomes 456. 4. Remove the row: storage series expire without fabricating a zero-byte measurement. 5. Publish 789 bytes: metrics reactivate without restarting the relay; readiness remains healthy. The local S3 endpoint recorded zero requests. The unrelated Git conformance startup probe was disabled for this isolated reader check; the test covers storage accounting, not every other use of S3 in the relay. No new relay image was deployed to staging or production during this test. The broad local integration run also hit an existing `p0_pool_acquisitions_use_typed_operation_pairs_without_other` failure in `buzz-db/tests/observability_source.rs`. The same source-only test fails on unmodified `main` at `0ef7a2222`; this PR changes neither the test nor its failing source. The full local `just ci` run stopped at mobile native-asset setup: the `objective_c` build hook received no SDK path from `xcrun`. Mobile tests did not run. Generated with Codex Signed-off-by: Ravneet Arora <rarora@squareup.com> | 13 天前 | |
feat(relay): add opt-in newest-first thread windows (#7823) ## Summary Add an opt-in, newest-first thread window to the authenticated HTTP query path. A filter with `thread_window: true` returns: - reply rows ordered by `(created_at DESC, id ASC)` with an exact composite cursor; - optional bounded reactions, edits, deletions, and delete-of-aux closure; - one relay-signed `kind:39007` bounds event bound to the normalized host, authenticated reader, channel, root, request cursor, kinds, depth, limit, and aux choice. Filters without the flag keep the existing oldest-first thread behavior. Channel windows and `kind:39006` are unchanged. No client opts into this mode in this PR. ### Why extend NIP-CW NIP-CW already defines relay-computed, cursor-paged conversation views over extended NIP-01 filters. Thread windows use the same protocol ideas: ordinary signed rows, composite keyset pagination, optional aux closure, and relay-signed bounds. Adding a thread mode keeps those related wire contracts together instead of creating a second NIP with duplicated terminology and compatibility rules. Extending a draft, optional NIP with an additive mode is normal protocol evolution. Existing fields and meanings are not reinterpreted: - `top_level: true` remains channel mode; - `kind:39006` remains channel-window bounds; - `thread_window: true` selects the new mode; - `kind:39007` has a distinct identity and root/request binding; - extension-unaware clients never send the selector; - legacy thread queries remain unchanged. NIP-CW documents only the wire contract and relay semantics. Database plans, replica internals, and rollout procedures remain implementation concerns. ### Why thread windows use independent modules The implementation deliberately keeps `thread_window.rs` separate in `buzz-core`, `buzz-db`, and `buzz-relay` rather than adding branches throughout the existing channel-window path. The modes share pagination concepts, but their invariants differ: - channel mode selects top-level rows; thread mode requires one root and depths `1..=depth_limit`; - thread mode accepts only conversation row kinds and rejects unknown/conflicting fields; - thread bounds bind the root and authenticated reader because cross-channel aux visibility can differ by reader; - thread aux includes the root and requires complete two-hop closure; - thread mode refreshes writer authorization before selection and before signing; - historical cursor pages may use a proved replica transaction, but fallback must restart both aux hops on the writer; - legacy thread traversal is oldest-first and must not inherit descending cursor semantics. Forcing these rules into channel-window code would create a mode flag across parsing, SQL predicates, bounds construction, authorization, replica fallback, and tests. That abstraction would not remove complexity. It would distribute it. Separate modules keep each contract local and leave the established channel and legacy paths untouched. ### Technical design - Strict request parsing normalizes UUIDs, event IDs, kinds, defaults, and cursors before computing the response binding. - SQL applies community, root, channel, depth, kind, deletion, and cursor predicates before `limit + 1`. - Ordered metadata scanning plus exact event-key lookup avoids the stale-statistics plan that previously exceeded the four-second SQL budget on a 10k-reply thread. - Aux work is bounded by target batches, query count, raw candidate count, response bytes, SQL timeouts, and one shared HTTP deadline. - Fresh authorization-set comparison catches both grants and revocations during closure. The relay returns retryable `503` rather than signing an incomplete page. - Replica failure permanently degrades the request to the writer and restarts closure from its original targets without resetting budgets. - Migration `0049_thread_window_index.sql` adds the mixed-direction index used by newest-first scans. It validates a prebuilt index and bounds startup lock/build time. ### Tradeoffs - The stale-statistics-safe plan is intentionally less clever. Measured warm 100k deep-cursor execution increased from roughly 0.30 ms to 4.80 ms, plus about 8 ms planning, in exchange for avoiding a reproduced multi-second timeout after rapid 10k-thread insertion. - Bounds are authoritative only for the served page. There is no cross-page snapshot guarantee, and replica insertion coverage does not imply latest edit/deletion/aux visibility. - Strict parsing rejects unsupported constraints instead of silently serving a different row set. - Required aux closure fails closed. The relay does not omit aux and sign plausible-but-incomplete bounds. - This PR ships relay capability only. Client rendering, retry UX, live-history overlap, and compatibility fallback remain separate work. ### Related issue No existing issue found. This work follows the thread-loading design and review discussion. ### Testing - `cargo test -p buzz-core`: 258 unit tests and 2 doctests passed. - `cargo check --workspace --all-targets`: passed at the rebased implementation head. - `cargo test -p buzz-db`: 131 non-PostgreSQL tests passed; 275 infrastructure tests were ignored as declared. - Focused PostgreSQL migration/runtime regressions: 3/3 passed against an isolated PostgreSQL container, including both corrected migration-ledger tests. - `cargo fmt --all --check`: passed. - Pre-rebase feature validation passed 398/398 PostgreSQL tests, signed-wire multi-tenant/auth/pagination/aux/revocation probes, clean Compose boot/reboot, and full core tests. - Independent HA validation exercised three relay pods and a physical PostgreSQL standby on the same runtime logic before the final documentation-only cleanup. - Independent final-delta review confirmed exactly two changed files, byte-identical executable migration SQL after comments/diagnostic normalization, matching prebuild/catalog/recovery instructions, and `git diff --check`; no runtime receipts were relabeled to the new SHA. - The pre-push hook ran repository checks but its Rust unit lane exited 100 without exposing an individual failed test in the captured multiplexed output. The independently run affected-package checks above passed; current-head repository CI remains required. Known unrelated baseline behavior remains: the mesh-demo echo test can return 504, and same-second archive → unarchive can deduplicate its membership notification. Neither path is changed here. --------- Signed-off-by: Kalvin Chau <kalvin@block.xyz> Signed-off-by: cid <d9f92a72922bf45c17379a47d64dae84b6020397c2d5a52b5317d512068cd9d3@buzz.block.builderlab.xyz> Signed-off-by: am <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> Co-authored-by: cid <d9f92a72922bf45c17379a47d64dae84b6020397c2d5a52b5317d512068cd9d3@buzz.block.builderlab.xyz> Co-authored-by: am <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> | 13 天前 | |
Rename Bumble agent to Pollen (#5864) ## Summary - Rename the built-in Bumble agent to Pollen across desktop, onboarding, docs, and test fixtures. - Migrate existing stock definitions and instances in place while preserving customized fields and the stable persona coordinate. - Reserve the Pollen name by removing it from Fizz's generated-name pool. ## Validation - Pre-push desktop checks, typecheck, 4,791 frontend tests, Tauri clippy, and 2,432 native tests - Desktop E2E build --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Kenny Lopez <klopez4212@gmail.com> Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz> Co-authored-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> | 1 个月前 |
| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 12 天前 | ||
| 2 个月前 | ||
| 2 个月前 | ||
| 8 天前 | ||
| 2 个月前 | ||
| 2 个月前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 2 个月前 | ||
| 12 天前 | ||
| 8 天前 | ||
| 1 天前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 26 天前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 8 天前 | ||
| 1 个月前 | ||
| 2 个月前 | ||
| 6 天前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 11 天前 | ||
| 13 天前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 13 天前 | ||
| 13 天前 | ||
| 1 个月前 |