fix(desktop): discover authenticated owned relay agents - #7122
Conversation
🔐 Codex Security Review
|
jedwards27
left a comment
There was a problem hiding this comment.
Review verdict: request changes
Reviewed base bc006f67087b049e2f9c4d8a2f26faceff628225 through exact live head f3ad8ad2d202047da1170516ba8d072ecec777e0, integrating both assigned lanes. The signed discovery boundary is generally sound, but two presentation contracts discard the truth it established.
1. Policy-only discovery fabricates an offline status without liveness evidence
The new discovery conversion assigns status: "offline" to an agent for which it has ownership/discovery evidence but no runtime evidence (desktop/src-tauri/src/nostr_convert/agent_directory.rs:135-153). Pulse renders that value as an offline status dot (desktop/src/features/pulse/ui/PulseView.tsx:130-135,282-285; AgentActivityCard.tsx:32-39,82), while Projects treats and sorts it as inactive (ProjectsAgentPromptPage.tsx:187-201,705-713). A remotely running policy-only agent can therefore be falsely shown offline, conflating discovery with availability contrary to this PR's own boundary.
Author action: represent absent liveness evidence as unknown/null end to end, make status-bearing consumers omit or explicitly present unknown state, and regress at least one production consumer.
2. Authenticated owner identity is discarded in the headline picker journeys
Tauri supplies RelayAgent.ownerPubkey, but relay candidates reset it to null in New Message and Channel Members (desktop/src/features/messages/ui/useNewMessageRecipients.ts:170-185; desktop/src/features/channels/ui/MembersSidebar.tsx:329-337). Those downstream rows explicitly use the field to render managed by … and fetch owner profiles (NewMessageResultRow.tsx:84-89,137-141; MembersSidebar.tsx:388-409,795-810). Relay-only owned agents thus lose their authenticated ownership distinction, particularly harmful among duplicate names.
Author action: propagate agent.ownerPubkey in both constructors and add relay-only production-seam regressions proving owner attribution appears in New Message and add-member results. Audit the analogous null assignment in useSearchResults.ts, though no separate affected consumer was established there.
Verification
The full exact-head Tauri suite passed (3,016 passed, 18 ignored, plus workspace targets); focused ownership/conversion tests, fmt/check, and applicable CI passed. No packaged native/UI capture was performed; that is a reviewer/tooling confidence gap, not additional author rework.
— :bot: Jude’s code review agent
jedwards27
left a comment
There was a problem hiding this comment.
Verdict: REQUEST CHANGES
Reviewed: bc006f67087b049e2f9c4d8a2f26faceff628225..f3ad8ad2d202047da1170516ba8d072ecec777e0 (exact head f3ad8ad2d202047da1170516ba8d072ecec777e0)
Risk: high — this changes signed owner/policy/profile interpretation at the native relay boundary and feeds the result into multiple existing renderer surfaces.
Behavior/contracts traced: authenticated owner/profile/policy binding; community-bound relay discovery; membership and local-management separation; native RelayAgent conversion; New Message, member-add, Pulse, and Projects consumers; lifecycle/compatibility and current CI coverage.
Blocking findings
-
Policy-only discovery fabricates offline status without liveness evidence.
relay_agent_from_managed_policycreates every newly discovered policy-only agent withstatus: "offline", although kind 30177 ownership/policy data says nothing about whether its runtime is online. This escapes the native seam as product truth: Pulse maps and renders that value as a gray status dot, while Projects converts it to inactive and sorts it below active agents. A remotely running agent with authenticated ownership but no runtime directory record is therefore falsely presented as offline/inactive. This conflicts with the PR's own boundary that discovery is not availability.Author action: represent absence of runtime/liveness evidence distinctly (for example,
unknown/null) across the native/IPC contract; make status-bearing consumers omit or explicitly represent unknown state instead of treating it as offline; add a regression that sends a policy-only discovered agent through at least one affected production consumer (Pulse or Projects).Verification owner: author for implementation/regression; reviewer for exact-head source and consumer verification.
-
Both headline picker journeys discard authenticated owner provenance. Native correctly returns
owner_pubkeyfor the new policy-only result, but New Message reconstructs the relay candidate withownerPubkey: null, and Channel members does the same. Their existing row components consume that field to rendermanaged by …; relay-only owned results consequently lose the authenticated ownership distinction. A coincident user-search duplicate may mask this through coalescing, but the production relay-only path deterministically drops the value.Author action: propagate
agent.ownerPubkeyin both candidate constructors and add renderer regressions proving a relay-only policy-discovered fixture (without a user-search duplicate) rendersmanaged by youin New Message and member-add results. Please also audit the analogous relay reconstruction indesktop/src/features/search/useSearchResults.ts; no separate blocking consumer defect was established there.Verification owner: author for implementation/regressions; reviewer for exact-head relay-only fixture verification.
These are presentation/integration correctness defects, not authorization escalation: eligibility still uses the authenticated relay collection, and discovery still grants neither membership nor local Start/Edit authority.
Validation
At matching exact head with clean dedicated worktrees:
git diff --check— passed.just desktop-tauri-test— passed: main native target 3,016 passed, 18 ignored, with all additional workspace targets passing.cargo test --manifest-path desktop/src-tauri/Cargo.toml commands::agent_discovery::relay_directory::owned_tests— 1 passed.cargo test --manifest-path desktop/src-tauri/Cargo.toml nostr_convert::oa_profile_tests— 8 passed.- Tauri
cargo fmt --all -- --checkandcargo check— passed. - Current applicable CI at this SHA is green, including Desktop Core, smoke/integration E2E, macOS build, Windows Rust, static security checks, and DCO.
The signed native fixtures support the ownership/authentication boundary, but they do not send a policy-only result through the affected renderer presentation seams; green CI therefore does not contradict either deterministic finding.
Manual/native evidence: none. No GUI was launched on the shared host without explicit opt-in.
Residual risk: no packaged/live-relay native observation of authenticated owned-agent discovery. That is a reviewer/tooling confidence gap, not additional author rework beyond the regressions above. Source/tests otherwise showed fail-closed signature and exact-owner checks, community-bound queries, latest relay-signed membership snapshots, deliberate foreground polling, and preservation of membership/local-management boundaries.
Extract native discovery and signed ownership, policy and current membership verification together from #7114. Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Signed-off-by: Logan Johnson <loganj@squareup.com>
Retain authenticated owner labels in relay-only pickers, distinguish discovery from liveness across native and UI consumers, and retain authorized stored profile history independently of presence. Add production-seam, hook/store, native and picker regressions. Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Signed-off-by: Logan Johnson <loganj@squareup.com>
f3ad8ad to
ae23c1c
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
jedwards27
left a comment
There was a problem hiding this comment.
:bot: Jude’s code review agent
Verdict: REQUEST CHANGES
Reviewed: 1c8321cd08feb597f8bcff5195c21148fb3e98ed..ae23c1c9680a881cee7eed94e259bf15bf8ce3f7 (exact head ae23c1c9680a881cee7eed94e259bf15bf8ce3f7)
Risk: high — authenticated ownership/discovery and liveness state cross native IPC into multiple product surfaces.
Blocking finding: the policy overlay erases trustworthy runtime availability. relay_agents_from_directory_events first creates the verified self-authored kind-10100 runtime record, then removes it when a verified owner policy exists and inserts a newly constructed record whose status is always unknown (desktop/src-tauri/src/nostr_convert/agent_directory.rs:68-80,135-153). Consequently an agent with signed online, away, or offline runtime evidence becomes unknown merely because its authenticated owner also published policy. Projects then loses active ranking/state, and Pulse suppresses the valid status indicator. The documented contract says policy-only agents are unknown; this overlapping agent is not policy-only.
Author action: overlay policy-authoritative fields onto the verified runtime record while retaining known runtime status; use unknown only when no trustworthy runtime status exists. Keep untrusted runtime channel IDs/permissions separate. Add Rust merge regressions for known online/away/offline plus absent-status and policy-only unknown.
Verification owner: author for focused Rust coverage; reviewer for replacement-head integration and affected product-consumer recheck.
Resolved from the previous review: policy-only discovery now remains unknown rather than falsely Offline; authenticated ownerPubkey reaches relay-only New Message/member-add candidates and their managed by presentation.
Validation: at matching clean head, full Desktop JS tests passed 5,910/5,910, typecheck passed, pnpm check exited 0, and cargo test --workspace passed with one ignored performance test. A focused local Tauri attempt in the second lane was blocked by the absent sidecar binary; that reviewer-environment gap is not author action. Applicable completed CI was green; several Desktop/build/integration jobs remained in progress.
Manual/native evidence: no packaged live-relay UI capture.
Residual risk: pending CI and no native UI observation remain confidence gaps, not additional blockers.
jedwards27
left a comment
There was a problem hiding this comment.
Verdict: REQUEST CHANGES
Reviewed: 1c8321cd08feb597f8bcff5195c21148fb3e98ed..ae23c1c9680a881cee7eed94e259bf15bf8ce3f7 (exact head ae23c1c9680a881cee7eed94e259bf15bf8ce3f7; prior review at f3ad8ad2d202047da1170516ba8d072ecec777e0 expired)
Risk: high — signed runtime, owner-policy, membership, and renderer availability contracts converge in this merge path.
Behavior/contracts traced: kind-10100 runtime conversion; verified kind-0 owner binding and kind-30177 policy overlay; relay-signed membership separation; Rust→IPC status/owner propagation; policy-only and overlapping runtime+policy records; New Message/member-add ownership labels; Pulse and Projects availability consumers; new delta and current gates.
Blocking finding
Authenticated policy overlay erases known signed runtime availability. relay_agents_from_directory_events first builds the self-authored kind-10100 runtime record, then removes every pubkey that has a verified owner policy and replaces it with a fresh policy projection. That projection unconditionally sets status: "unknown", even though the kind-10100 converter preserves supplied status.
Consequently, an agent with independently signed online, away, or offline runtime evidence loses that evidence merely because its authenticated owner also publishes policy. Pulse then omits a valid status, while Projects converts it to indeterminate activity and ranks it below online agents. The documented rule is that policy-only agents are unknown; this overlapping record is not policy-only. The existing merge test supplies no runtime status and therefore cannot detect this overwrite.
Author action: overlay authenticated policy/ownership fields onto the verified runtime record while retaining a known runtime status; use unknown only when trustworthy runtime liveness is absent. Continue keeping policy-authoritative fields and relay-authoritative membership separate—do not restore claimed kind-10100 channel IDs. Add Rust merge regressions for known online, away, and offline, plus missing-status/policy-only unknown.
Verification owner: author for implementation and Rust regressions; reviewer for exact replacement-head integration and Pulse/Projects consumer recheck.
Prior blockers
- Resolved: policy-only discovery now carries
unknown; Pulse omits its status dot and Projects does not claim online/offline. - Resolved: authenticated
ownerPubkeynow survives Rust/IPC/raw mapping into relay-only New Message and member-add candidates; coalescing preserves non-null provenance and E2E assertsmanaged by youin both journeys. - Identity/authorization remained fail-closed in the inspected paths: policy is accepted only for the owner cryptographically bound by the agent-signed profile; ownership does not fabricate membership or local management authority.
Validation
At exact clean head ae23c1c9680a881cee7eed94e259bf15bf8ce3f7:
pnpm test— 5,910/5,910 passed.pnpm typecheck— passed.pnpm check— exit 0 (existing unrelated diagnostics only).cargo test --workspace— passed, one explicitly ignored performance test.- A focused broken-control mutation giving the overlapping kind-10100 fixture
status: "online"and requiring that status after merge fails through the deterministic replacement path; mutation reverted and tree returned clean. - Current CI snapshot: release candidate, Rust lint, Windows Desktop build, Desktop relay E2E, DCO, Semgrep, and zizmor passed; remaining Desktop Core/smoke/integration/macOS/Windows Rust jobs were still running when reviewed.
Confidence gaps (not verdict basis): one focused Tauri invocation in the other review worktree could not compile because the required local sidecar desktop/src-tauri/binaries/buzz-acp-aarch64-apple-darwin was absent. No packaged/live-relay native UI observation was performed. Reviewer/tooling owns those gaps; the requested change rests on the direct overwrite and production-consumer paths above.
Preserve explicit verified online, away and offline status when applying authenticated policy. Treat missing or unrecognized runtime status as unknown without reviving stale permissions or claimed membership. Add falsifiable merge regressions and document evidence precedence. Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Signed-off-by: Logan Johnson <loganj@squareup.com>
|
🤖 Addressing the overlapping runtime/policy finding in review 5092113972 and review 5092119120 with
Local CI is complete via a disclosed failed-lane continuation, not an uninterrupted green run. At this exact replacement tip, The frontend is unchanged by this four-file native/test/doc correction. Existing production Pulse regressions retain explicit online/away/offline labels and omit unknown; Projects still maps unknown to null and known status normally. The previously posted nine browser tests/zero retries and cropped picker images remain applicable to those unchanged mock-IPC journeys; they were captured at Both older original findings were explicitly marked resolved in your latest reviews: policy-only discovery stays unknown and both relay-only pickers preserve owner provenance. Prior response/evidence remains available in the audit trail. No inline review threads exist to resolve, and none of the four formal CHANGES_REQUESTED reviews has been dismissed. Renewed GitHub technical review is requested for this replacement head. Current-range security authorization/review and code-owner/latest-push approval remain separate; no approval or live-relay/native-webview observation is claimed. |
jedwards27
left a comment
There was a problem hiding this comment.
:bot: Jude’s code review agent
Review basis: base 1c8321cd08feb597f8bcff5195c21148fb3e98ed, exact head 3a56d17824522580fe04cae463b54f4c7ba66021.
The prior blocker is resolved. The runtime/policy merge now preserves only explicit, verified online/away/offline status from the latest signed kind-10100 record while retaining policy authority over ownership, name, response permissions, and allowlist. Runtime-claimed membership and capabilities remain excluded; relay-signed membership remains a separate authority. Missing, unrecognized, forged-latest, and policy-only runtime evidence remains unknown rather than reviving stale status.
The new production-seam tests cover known statuses, missing/null/non-string/unrecognized status, policy-only discovery, status-less latest replacing older Online regardless input order, forged latest runtime, policy-field precedence, and membership/capability exclusion (desktop/src-tauri/src/nostr_convert/runtime_policy_tests.rs:1-143). Both independent lanes found no remaining author-actionable defect.
Author action: none.
Verification owner / confidence: exact-head git diff --check passed and the author reports a full current-head continuation with 5,910 Desktop tests plus serial Tauri and remaining mobile/web lanes green. Fresh reviewer Tauri execution was blocked only because disposable worktrees lacked the required sidecar. Several exact-head hosted Rust/Desktop smoke/core jobs were still running at final refresh with no observed failed gate; those named CI jobs own completion. Packaged native/live-relay UI observation remains reviewer/release confidence, not author rework.
jedwards27
left a comment
There was a problem hiding this comment.
Verdict: APPROVE
Reviewed: 1c8321cd08feb597f8bcff5195c21148fb3e98ed..3a56d17824522580fe04cae463b54f4c7ba66021 (exact head 3a56d17824522580fe04cae463b54f4c7ba66021; prior review at ae23c1c9680a881cee7eed94e259bf15bf8ce3f7 expired)
Risk: high — signed runtime status, owner policy, relay membership, IPC, and renderer availability semantics meet at this merge boundary.
Behavior/contracts traced: latest/self-authored kind-10100 selection and signature verification; known versus unknown status normalization; verified kind-0 owner binding and kind-30177 policy precedence; malformed-policy fail-closed behavior; relay-signed membership restoration; Rust→IPC compatibility; Pulse/Projects consumers; prior picker provenance repair; regression coverage and current required gates.
Findings
No unresolved author-actionable defect found.
The prior blocker is resolved. relay_agents_from_legacy_events now accepts only explicit online, away, or offline from the latest valid self-authored runtime event and maps absent/unrecognized/non-string status to unknown, avoiding the generic converter's legacy offline default. The policy merge removes the runtime row before policy parsing, preserving fail-closed invalid-policy behavior, then copies only normalized runtime status into the policy-authoritative result. Runtime-supplied owner, name, permissions, capabilities, channels, and channel IDs do not cross the overlay; membership remains sourced separately from relay-signed kind-39002 snapshots.
The production-seam regression suite covers known online/away/offline preservation; missing, null, non-string, unrecognized, and explicit unknown values; policy-only unknown; latest status-less runtime suppressing older online regardless input order; forged latest runtime; policy ownership/permission precedence; and runtime membership/capability exclusion.
Pulse therefore displays a status dot only when liveness is known, while Projects receives truthful active/offline/unknown ordering. The prior authenticated ownerPubkey repair remains intact through relay-only New Message/member-add candidates and their managed by you E2E coverage. No new renderer interaction, focus, keyboard, or accessibility surface was introduced by this four-file repair.
Author action: none.
Verification owner: required CI owns merge gating; reviewer/tooling owns the native local-execution confidence gap below.
Validation
At exact clean head 3a56d17824522580fe04cae463b54f4c7ba66021:
git diff --check ae23c1c9680a881cee7eed94e259bf15bf8ce3f7..3a56d17824522580fe04cae463b54f4c7ba66021— passed.- Source review confirmed latest-event timestamp plus deterministic ID tie-break, signature/kind checks, status allowlisting, policy fail-closed behavior, and membership/authority separation.
- GitHub required checks at submission: Mobile, Desktop Build (macOS), Desktop Release Candidate, Security, and DCO passed; Web was an expected skip. No required check failed or remained pending.
- Additional exact-head CI already green included Rust lint/unit tests, Linux cross-compiles, macOS domain build, Windows Desktop build, security domain, Semgrep, and zizmor. Several non-required Desktop smoke/core/relay-artifact and Windows Rust jobs were still running; none had failed.
Manual/native evidence: no UI capture. No UI code changed, and both independent source reviews traced the production consumer behavior.
Residual risk: both fresh reviewer worktrees lacked desktop/src-tauri/binaries/buzz-acp-aarch64-apple-darwin, so attempted local Tauri package runs stopped in the build script before tests. This is reviewer setup/tooling, not a PR defect; exact-head Rust unit CI passed and required CI is green. Any new head invalidates this approval.
…agent-edit * origin/main: feat(desktop): add persistent Bestie experience (#7223) fix(desktop): harden profile batch and thread-reply fetches against relay slowness (#7188) docs(nip-fi): adopt deny-until-TTL and extend enforcement to HTTP ingress (#7254) fix(composer): align wrapped inline chip fragments (#7242) Add operation-aware database pool acquisition metrics (#7195) fix(desktop): keep explicit agent profiles bound to their exact key (#7131) fix(desktop): discover authenticated owned relay agents (#7122) feat(agents): harness-agnostic effort write path and spawn bridge (#4625) chore(db): drop Phase-A NIP-FI relay-side authority ledger (#7221) fix(acp): replace real user name in base prompt mention example (#7250) ci: split CI into reusable workflows (#7168) fix(desktop): retain automatic mentions only in threads (#7144) feat: add databricks fable 5.1 model capabilities (#7213) docs(nip-fi): rewrite NIP-FI as stateless OSS Buzz spec v2 (#7214) feat(relay): add detailed readiness metrics (#7149) feat(desktop): add Pi agent preset (#7208) Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> # Conflicts: # desktop/src/features/agents/ui/AgentInstanceEditDialog.tsx # desktop/src/features/agents/ui/agentInstanceEditPinning.test.mjs
🤖 ## 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 [block#7124](block#7124); the standalone forum **Invite / Cancel** flow is in [block#7125](block#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: [block#7124](block#7124), then [block#7125](block#7125). Extracted from [block#7114](block#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> (cherry picked from commit 434dafe)
🤖 ## 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](#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](#7125); here those composers only gain visible authorization errors. Same-name selection/binding fixes ([#7133](#7133)) and mention spacing ([#7128](#7128)) are not included. Extracted from [#7114](#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](#7124 (comment)) · [Pending-state capture](#7124 (comment)). 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](#7124 (comment)) 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>
## 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](#7125 (comment)); 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>
🤖 ## 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 [block#7122](block#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 block#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 [block#7125](block#7125); here those composers only gain visible authorization errors. Same-name selection/binding fixes ([block#7133](block#7133)) and mention spacing ([block#7128](block#7128)) are not included. Extracted from [block#7114](block#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](block#7124 (comment)) · [Pending-state capture](block#7124 (comment)). 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](block#7124 (comment)) 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> (cherry picked from commit d595806) Signed-off-by: nambse <sefa.esendemir@gmail.com>
🤖
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.
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; the standalone forum Invite / Cancel flow is in #7125.
Ownership and membership checks
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.
Review corrections
Related issue
Independent base:
main. Child: #7124, then #7125. Extracted from #7114, retained as historical source (98fe33ec).Behavior contract. Originating Buzz discussion · channel
f7a9536a-1738-4bad-a888-b3ea25010ef1.Testing
Current candidate:
3a56d17824522580fe04cae463b54f4c7ba66021, a four-file native/test/doc runtime-status repair atop publishedae23c1c9680a881cee7eed94e259bf15bf8ce3f7. Branch ancestry is main1c8321cd08feb597f8bcff5195c21148fb3e98ed; refreshed main0e878664b08cdf7fb2d89d940bc2aa92cdc485f7adds 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 cipassed 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_probesaw 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 withRUST_TEST_THREADS=1 just desktop-tauri-test: 3,074 main-target tests passed / 19 ignored, all additional workspace targets passed (exit 0). The previously unrunjust web-build mobile-testtail 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.nostr_converttests 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.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.ae23c1c9localjust cipassed without failures, including 3,067 native main-target tests / 19 ignored and 2,019 mobile tests; not substituted for the new source gate above.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 —
ae23c1c9680a881cee7eed94e259bf15bf8ce3f7These cropped rows come from the two real production picker journeys in
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.