{"record":{"id":"94aba513110ed2b0","repo":"block/buzz","slug":"actor-not-authorized-for-name-about-archived-visib","errorCode":null,"errorMessage":"actor not authorized for name/about/archived/visibility/ttl changes","messagePattern":"actor not authorized for name/about/archived/visibility/ttl changes","errorType":"validation","errorClass":"IngestError::Rejected","httpStatus":400,"severity":"error","filePath":"crates/buzz-relay/src/handlers/side_effects.rs","lineNumber":613,"sourceCode":"                let members = state.db.get_members(tenant.community(), channel_id).await?;\n                let actor_member = members.iter().find(|m| m.pubkey == actor_bytes);\n                match actor_member {\n                    Some(m) if m.role == \"owner\" || m.role == \"admin\" => Ok(()),\n                    _ => {\n                        // Allow the owning human of any active owner-role agent in the\n                        // channel, even when the human is not a channel member —\n                        // diverges from kind:9001 intentionally.\n                        if actor_owns_any_owner_agent(\n                            state,\n                            tenant.community(),\n                            &members,\n                            &actor_bytes,\n                        )\n                        .await?\n                        {\n                            return Ok(());\n                        }\n                        Err(anyhow::anyhow!(\n                            \"actor not authorized for name/about/archived/visibility/ttl changes\"\n                        ))\n                    }\n                }\n            } else {\n                // topic/purpose: any member\n                let is_member = state\n                    .is_member_cached(tenant.community(), channel_id, &actor_bytes)\n                    .await?;\n                if is_member {\n                    Ok(())\n                } else {\n                    Err(anyhow::anyhow!(\"not a member\"))\n                }\n            }\n        }\n        9005 => {\n            // DELETE_EVENT: event author OR channel owner/admin.","sourceCodeStart":595,"sourceCodeEnd":631,"githubUrl":"https://github.com/block/buzz/blob/f956e6fe06a76e50cbd8fba1a162482e752e7f1a/crates/buzz-relay/src/handlers/side_effects.rs#L595-L631","documentation":"Thrown by kind:9002 (EDIT_METADATA) when the event includes any privileged tag (name, about, archived, visibility, ttl) but the actor is neither an owner/admin member nor the owning human of an active owner-role agent in the channel. Note the fallback here differs from 9001: a non-member who owns an owner-role agent IS allowed.","triggerScenarios":"A plain member (or non-member) publishes 9002 containing e.g. [\"name\", ...] or [\"visibility\", ...]. They are not owner/admin in the member list and actor_owns_any_owner_agent(...) finds no owner-role agent owned by them.","commonSituations":"Members assuming topic-style permissions extend to renames; automation running with a member keypair; humans who own a regular (non-owner-role) agent trying channel renames through it; agent-owner rows missing after re-registration of the agent under a different key.","solutions":["Restrict your edit to [\"topic\"] / [\"purpose\"] tags — those only require membership.","Get your key promoted to admin/owner, or publish with the owner/admin keypair.","If you own an owner-role agent, verify it is active in the channel and the agent-owner mapping matches the key you are publishing with, then retry.","Split the edit: keep the privileged fields for the owner and send only what your role permits."],"exampleFix":"# before — plain member renames the channel\nbuzz channels update --channel $CH --name \"new-name\"  # → actor not authorized for name/about/... changes\n\n# after — member edits only topic; owner does the rename\nbuzz channels update --channel $CH --topic \"New topic\"            # member: OK\nBUZZ_PRIVATE_KEY=$OWNER_KEY buzz channels update --channel $CH --name \"new-name\"","handlingStrategy":"validation","validationCode":"const PRIVILEGED = [\"name\", \"about\", \"archived\", \"visibility\", \"ttl\"];\n\n// Gate the edit UI/payload on role before publishing kind:9002\nconst members = await buzz.channelsMembersList(channelId);\nconst me = members.find((m) => m.pubkey === myPubkey);\nconst privileged = me?.role === \"owner\" || me?.role === \"admin\";\nconst fields = privileged ? draft : pick(draft, [\"topic\", \"purpose\"]);\nif (Object.keys(fields).some((k) => PRIVILEGED.includes(k)) && !privileged) {\n  throw new Error(\"Only owners/admins can change name/about/archived/visibility/ttl\");\n}","typeGuard":"function isPrivilegedMetadataKey(k: string): boolean {\n  return [\"name\", \"about\", \"archived\", \"visibility\", \"ttl\"].includes(k);\n}","tryCatchPattern":"try {\n  await sdk.publish(editEvent);\n} catch (e) {\n  if (String(e).includes(\"not authorized for name/about/archived/visibility/ttl\")) {\n    // Downgrade the edit to topic/purpose only, or request an owner\n    await sdk.editChannelMetadata(channelId, pick(draft, [\"topic\", \"purpose\"]));\n  } else throw e;\n}","preventionTips":["Disable name/about/archived/visibility/ttl inputs for non-admin roles in the UI.","Split edits: members send topic/purpose events; privileged fields go through an owner-approved path.","If you rely on the agent-owner fallback, verify you own an owner-ROLE agent that is active in the channel."],"tags":["nostr","authorization","insufficient-permissions","kind-9002","relay"],"backgroundTag":"insufficient-permissions","analyzedSha":"f956e6fe06a76e50cbd8fba1a162482e752e7f1a","analyzedAt":"2026-08-16T22:11:40.750Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}