{"record":{"id":"c54bb5ee853f564c","repo":"block/buzz","slug":"member-p-tag","errorCode":null,"errorMessage":"member p tag","messagePattern":"member p tag","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/buzz-db/src/store/channel_members.rs","lineNumber":3175,"sourceCode":"            \"INSERT INTO channel_members (community_id, channel_id, pubkey, role, invited_by) \\\n             VALUES ($1, $2, $3, 'admin', $4)\",\n        )\n        .bind(community_uuid)\n        .bind(channel)\n        .bind(member.as_slice())\n        .bind(owner.as_slice())\n        .execute(&pool)\n        .await\n        .expect(\"seed canonical admin\");\n\n        let roster = |role: &str, timestamp| {\n            EventBuilder::new(Kind::Custom(39002), \"\")\n                .tags(vec![\n                    Tag::parse([\"d\", channel.to_string().as_str()]).expect(\"d tag\"),\n                    Tag::parse([\"p\", hex::encode(owner).as_str(), \"\", \"owner\"])\n                        .expect(\"owner p tag\"),\n                    Tag::parse([\"p\", hex::encode(member).as_str(), \"\", role])\n                        .expect(\"member p tag\"),\n                ])\n                .custom_created_at(Timestamp::from(timestamp))\n                .sign_with_keys(&relay_keys)\n                .expect(\"sign roster\")\n        };\n        let base = Timestamp::now().as_secs();\n        let fresh = roster(\"admin\", base);\n        assert!(\n            db.replace_addressable_event(community, &fresh, Some(channel))\n                .await\n                .expect(\"publish canonical role\")\n                .1\n        );\n        let stale = roster(\"member\", base + 1);\n        let error = db\n            .replace_addressable_event(community, &stale, Some(channel))\n            .await\n            .expect_err(\"desired-state fence must reject stale role\");","sourceCodeStart":3157,"sourceCodeEnd":3193,"githubUrl":"https://github.com/block/buzz/blob/dad5a33865fc81a2e55b3b60746632f615ec1e3a/crates/buzz-db/src/store/channel_members.rs#L3157-L3193","documentation":"Identical family to the owner p tag: the test parses the member's role p tag with Tag::parse([\"p\", hex::encode(member), \"\", role]). The expect panics when the nostr crate rejects the p tag because the member bytes are not a valid 32-byte pubkey or the tag shape fails that crate version's validation.","triggerScenarios":"Tag::parse errors for [\"p\", member_hex, \"\", role] when member is not 32 bytes, is all zeros/empty, or `role` is empty at call time (the closure receives role strings like \"owner\"/\"member\"; an empty string may be rejected by stricter versions).","commonSituations":"member buffer built from wrong-length data; crate upgrade tightened validation of p-tag third/fourth elements; calling the closure with an empty role string in a test case.","solutions":["Assert member.len() == 32 and role is non-empty before Tag::parse","Log hex::encode(member) and role in the panic message to diagnose","Upgrade or pin the nostr crate to a version whose Tag::parse accepts the tag shape","Use typed constructors (Tag::public_key / Tag::custom) instead of generic parse"],"exampleFix":"// before\nTag::parse([\"p\", hex::encode(member).as_str(), \"\", role]).expect(\"member p tag\"),\n// after\nassert_eq!(member.len(), 32, \"member pubkey must be 32 bytes\");\nassert!(!role.is_empty(), \"role must be non-empty\");\nTag::parse([\"p\", hex::encode(member).as_str(), \"\", role]).expect(\"member p tag\"),","handlingStrategy":"validation","validationCode":"assert_eq!(member.len(), 32);\nassert!(!role.is_empty(), \"role string must be non-empty\");","typeGuard":"fn valid_member_p_tag(pubkey: &[u8], role: &str) -> bool {\n    pubkey.len() == 32 && !role.is_empty() && Tag::parse([\"p\", &hex::encode(pubkey), \"\", role]).is_ok()\n}","tryCatchPattern":"let tag = Tag::parse([\"p\", hex::encode(member).as_str(), \"\", role])\n    .unwrap_or_else(|e| panic!(\"member p tag invalid (member={}, role={role}): {e}\", hex::encode(member)));","preventionTips":["Same as owner p tag: 32-byte keys, non-empty role values","Add a one-time validation loop over all closure role inputs in the test","Keep nostr crate pinned and review its Tag::parse changes on upgrade"],"tags":["rust","nostr","tag-parsing","test-code"],"backgroundTag":"nostr-tag-parse-failed","analyzedSha":"dad5a33865fc81a2e55b3b60746632f615ec1e3a","analyzedAt":"2026-08-30T13:49:18.474Z","contentChangedAt":"2026-08-30T13:49:18.474Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}