{"record":{"id":"c1c50f0b6da4a127","repo":"block/buzz","slug":"sign-roster","errorCode":null,"errorMessage":"sign roster","messagePattern":"sign roster","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/buzz-db/src/store/channel_members.rs","lineNumber":3179,"sourceCode":"        .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\");\n        assert!(matches!(\n            error,\n            DbError::Sqlx(sqlx::Error::Database(ref db_error))\n                if db_error.code().as_deref() == Some(\"23514\")","sourceCodeStart":3161,"sourceCodeEnd":3197,"githubUrl":"https://github.com/block/buzz/blob/dad5a33865fc81a2e55b3b60746632f615ec1e3a/crates/buzz-db/src/store/channel_members.rs#L3161-L3197","documentation":"This panic message comes from `EventBuilder::sign_with_keys(...).expect(\"sign roster\")` inside a test helper that builds a kind:39002 (community roster / membership) event signed with the relay keys. It fires when the builder cannot produce a signed Nostr event — almost always because the key material is invalid or a required tag/header is missing. The library throws it to fail the test immediately rather than return an untestable Result.","triggerScenarios":"Calling `sign_with_keys(&relay_keys)` on an EventBuilder whose keys cannot sign (malformed/empty SecretKey in `relay_keys`), or when event construction with `.tags([...]).custom_created_at(...)` produced an internally inconsistent event that signing rejects.","commonSituations":"Test fixtures where `relay_keys` was generated or parsed incorrectly (e.g. hex-decoded from an invalid string), refactors that changed EventBuilder tag APIs so a tag list is malformed, or deterministic-timestamp helpers constructing Timestamp::from(0) values rejected by validation.","solutions":["Check how `relay_keys` is constructed in this test file and confirm it holds valid KeyPair/Keys (e.g. Keys::generate() or a valid hex secret).","Run the failing test with RUST_BACKTRACE=1 to see whether the panic originates in key parsing or in event signing.","Verify the Tag::parse calls above it succeed — a bad tag list can leave the builder in a bad state; add distinct expect messages to isolate which call panics.","If keys are loaded from env/config, validate them once in test setup and fall back to Keys::generate() for scratch tests."],"exampleFix":"// before\nlet relay_keys = Keys::parse(env_key_hex).expect(\"relay keys\");\n// after\nlet relay_keys = match env_key_hex {\n    Ok(h) => Keys::parse(h).expect(\"valid relay key hex\"),\n    Err(_) => Keys::generate(), // scratch test does not need a fixed key\n};","handlingStrategy":"validation","validationCode":"// validate keys before building/signing the roster event\nlet secret = relay_keys.secret_key().expect(\"relay keys have a secret key\");\nassert_eq!(secret.display_secret().len(), 64, \"secret key must be 32-byte hex\");","typeGuard":"fn has_signable_keys(keys: &Keys) -> bool {\n    keys.secret_key().map(|s| s.display_secret().len() == 64).unwrap_or(false)\n}","tryCatchPattern":null,"preventionTips":["Generate test keys with Keys::generate() instead of parsing hand-written hex.","Give every expect/unwrap in fixtures a distinct message so the failing call is obvious.","Validate key material once in a shared test setup helper.","Keep EventBuilder tag construction in a small typed helper checked by its own unit test."],"tags":["rust","test-panic","nostr","signing"],"backgroundTag":"invalid-signing-key","analyzedSha":"dad5a33865fc81a2e55b3b60746632f615ec1e3a","analyzedAt":"2026-08-30T13:49:18.474Z","contentChangedAt":"2026-08-30T13:49:18.474Z","schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}