feat(relay): allow agent owners to edit/manage agent-owned content#1403
Merged
Conversation
wesbillman
approved these changes
Jun 30, 2026
Humans who own agents (established via NIP-OA) can now edit, delete, and manage channels/messages authored by those agents using their own key — without impersonation or re-signing. The relay widens four authorization predicates to fall back to is_agent_owner() when the actor is not the author/owner/admin: - validate_edit_ownership (kind:40003): author == actor OR owner of agent - validate_admin_event 9005 (DELETE_EVENT): also accept agent-owner - validate_admin_event 9002 privileged-tag branch: also accept agent-owner - validate_admin_event 9008 (DELETE_GROUP): also accept agent-owner All four sites reuse the existing is_agent_owner() DB predicate and preserve provenance — the editor's own pubkey is on the edit event. Non-members are allowed for 9002/9008/9005 when they are agent owners (intentional divergence from kind:9001, which requires channel membership). Adds authenticate_with_nip_oa() to BuzzTestClient and eleven e2e tests covering: owner-allowed, third-party-rejected, and agent-self-allowed scenarios at each of the four sites. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Fix 1 [blocking]: per-kind validators now run for 40003/9002/9005/9008 regardless of channel membership. The global check_channel_membership gate blocked non-member owning humans on private agent channels before any of the four new predicates could run. Add the four kinds to the skip_membership set (alongside 9021/9007); each validator independently proves authorization and fails closed. Fix 2 [blocking]: replace the inline first-owner-only check in 9002 and 9008 with a new actor_owns_any_owner_agent() helper that checks all active owner-role members. The previous members.find(role == owner) resolved to the oldest owner by join date, silently denying a valid owner-of-agent when their agent was not the first owner (co-ownership, later promotion). The helper also eliminates the duplicated inline logic between the two call sites. Fix 3 [blocking]: remove unused RelayMessage import and the stale suppression comment referencing Duration/Filter that were already absent. These caused a hard clippy -D warnings failure. Add four private-channel e2e tests (one per kind) as regression guards for Fix 1. These tests would have caught the original private-channel blind spot. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…annels Adding 40003/9005 to skip_membership (Pass 1 fix) also widened the self-author fast-path: a removed private-channel member could edit or delete their own historical messages after access was revoked (Thufir Pass 2, Option A). Fix: inside the author==actor branch of validate_edit_ownership (40003) and the 9005 validator, check membership/open-visibility before returning Ok. Active members and open-channel authors remain allowed; non-member self-authors on private channels fall through to the owner/admin/owner-of-agent check and are rejected there. The owner-of-agent non-member exception is untouched: it lives in the author!=actor branch (40003) and the final is_agent_owner fallthrough (9005), both still reachable for private channels. Add two E2E regression tests (ignored, require live relay): - test_removed_author_cannot_edit_own_message_in_private_channel - test_removed_author_cannot_delete_own_message_in_private_channel Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Whitespace-only formatting drift in side_effects.rs and e2e_human_edit_agent_content.rs; no logic change. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
4f3cfd0 to
1ad094d
Compare
Wire the relay's owner-of-agent permission gate into the desktop UI so the controls that were always permitted by the relay now appear in the client. Two sites gated: - Message edit/delete (TimelineMessageList): extend canEdit/canDelete to also allow when ownsAuthorAgent() is true for the message author, using the ownerPubkey on the author's UserProfileSummary (the same NIP-OA record the relay gates on). - Channel manage/delete (ChannelManagementSheet): fetch profiles for owner-role channel members via useUsersBatchQuery; set canManageOwnedAgentChannel when any owner-role member is an agent whose ownerPubkey matches the current user. OR this into canManageChannel and isOwner so the Edit quick-action and Delete button both appear. Both gates use ownerPubkey (NIP-OA, server-authoritative) rather than the local managed-agents list, keeping the client and relay in sync by construction. Adds an ownsAuthorAgent() helper in identity.ts and a Playwright spec (human-edit-agent-content.spec.ts) with four tests: owned-agent message shows Edit/Delete, unowned agent message does not, channel with owned agent as owner shows Edit, channel with no ownership claim does not. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…elete UI Fix 1 (IMPORTANT): Extract canManageMessageForCurrentUser as a shared helper in features/messages/lib/canManageMessage.ts and wire it into MessageThreadPanel (both thread-head and reply sites), replacing the old private canManageMessage that only matched self-author. Eliminates the duplication that caused the thread-panel false-negative. Fix 2 (IMPORTANT): Extend Playwright spec to actually drive the controls — edit an owned-agent message and assert new content renders, delete and assert the row is gone, edit channel name via the owned-agent-owner path and assert the updated title, negative case confirms unowned agent shows no Edit/Delete items in the dropdown. Add delete_message to the mock bridge so the delete mutation round- trips cleanly in tests. Register spec in playwright.config.ts smoke project so CI picks it up. Fix 3 (MINOR): Rename isOwner → isSelfOwner + canDeleteChannel in ChannelManagementSheet so the name reflects the capability not the membership state; sub-component prop name (isOwner) and downstream usage unchanged. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…elete UI Thread-panel regression test: replace the full delete-confirm-assert-gone flow (which cannot cross the React Query cache boundary from the thread panel to the main timeline in the E2E harness) with the alertdialog-only assertion Thufir explicitly accepted. The alertdialog appearing proves canManageMessageForCurrentUser is wired into MessageThreadPanel — the exact regression guard from pass 1. Full delete/cache behavior is already covered by the main-timeline delete test. isOwner rename: eliminate the misleading prop name throughout the channel management stack. ChannelManagementPanelContent and ChannelManagementModerationActions both rename isOwner to canDeleteChannel in their props interfaces, destructures, and usages. The two outer call sites in ChannelManagementSheet updated accordingly. No behavior change. Tombstone comment: remove the stale canManageMessage comment from MessageThreadPanel.tsx that predated the shared-helper extraction. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
tellaho
pushed a commit
that referenced
this pull request
Jul 1, 2026
…-preview * origin/main: fix(relay): enable Redis TLS for rediss:// (ElastiCache) (#1417) chore(release): release Buzz Desktop version 0.3.40 (#1414) fix(desktop): stabilize channel-timeline scrollback with per-row height reserves (#1413) fix(sidebar): trim working badge label and name working agents in tooltip (#1408) Mobile tab bar polish (#1368) feat(desktop): let thread pane expand on ultrawide monitors (#1407) chore(release): release Buzz Desktop version 0.3.39 (#1410) fix: close cross-process keychain race and namespace dev-build nest (#1409) feat(relay): allow agent owners to edit/manage agent-owned content (#1403) fix(media): support IRSA/credential-chain S3 auth and configurable signing region (#1406) fix(desktop): fold baked build env into in-process model discovery (#1376) docs: link VISION_ACTIVITY from the VISION index (#1405)
tellaho
pushed a commit
that referenced
this pull request
Jul 1, 2026
…vity-embed * origin/main: fix(relay): enable Redis TLS for rediss:// (ElastiCache) (#1417) chore(release): release Buzz Desktop version 0.3.40 (#1414) fix(desktop): stabilize channel-timeline scrollback with per-row height reserves (#1413) fix(sidebar): trim working badge label and name working agents in tooltip (#1408) Mobile tab bar polish (#1368) feat(desktop): let thread pane expand on ultrawide monitors (#1407) chore(release): release Buzz Desktop version 0.3.39 (#1410) fix: close cross-process keychain race and namespace dev-build nest (#1409) feat(relay): allow agent owners to edit/manage agent-owned content (#1403) fix(media): support IRSA/credential-chain S3 auth and configurable signing region (#1406) fix(desktop): fold baked build env into in-process model discovery (#1376) docs: link VISION_ACTIVITY from the VISION index (#1405)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Humans who own agents (via NIP-OA) can now edit, delete, and manage channels/messages authored by those agents using their own key — no impersonation, no re-signing.
Previously a human owner was blocked at four authz sites because their key didn't match the agent's key. This PR widens each predicate to also accept
is_agent_owner(agent, actor)when the primary check fails.Four relay sites changed
validate_edit_ownership(ingest.rs)author == actor→ ORis_agent_owner(author, actor)validate_admin_event(side_effects.rs)is_agent_owner(message_author, actor)fallbackvalidate_admin_event(side_effects.rs)is_agent_owner(channel_owner_agent, actor)fallbackvalidate_admin_event(side_effects.rs)is_agent_owner(channel_owner_agent, actor)fallbackAll four sites reuse the existing
is_agent_owner()DB predicate (no schema change). The owning human is not required to be a channel member for 9002/9008/9005 — intentional divergence from kind:9001 (which requires membership). This matches the real use case: a human owns a bot that autonomously created a channel the human was never added to.Provenance
Free: every edit/delete is its own event signed by the editor's own key. The edit event's
pubkeyis the human's, distinct from the original agent author — clients can render "edited by owner" by comparing the two.Reuses existing infrastructure
is_agent_ownerinbuzz-db/src/user.rs(already used for kind:9001 member-remove agent-owner fast-path, NIP-OA auth backfill, observer frame auth)Testing
authenticate_with_nip_oa()toBuzzTestClientfor NIP-OA test setup.buzz-sdkas a dev-dependency ofbuzz-test-client(needed forcompute_auth_tag/parse_auth_tag).e2e_human_edit_agent_content.rs(#[ignore], require live relay):