{"record":{"id":"3440ad729e24269c","repo":"block/buzz","slug":"project-announcement-is-missing-its-address","errorCode":null,"errorMessage":"project announcement is missing its address","messagePattern":"project announcement is missing its address","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/buzz-acp/src/lib.rs","lineNumber":1722,"sourceCode":"    });\n}\n\nfn build_project_owner_announcement_events(\n    announcements: Vec<ProjectOwnerAnnouncementTemplate>,\n    keys: &nostr::Keys,\n) -> Result<Vec<nostr::Event>> {\n    let now = nostr::Timestamp::now().as_secs();\n    announcements\n        .into_iter()\n        .map(|template| {\n            if !matches!(template.kind, 30_617 | 30_621) {\n                anyhow::bail!(\"unsupported project announcement kind\");\n            }\n            if !template.tags.iter().any(|tag| {\n                tag.first().is_some_and(|value| value == \"d\")\n                    && tag.get(1).is_some_and(|value| !value.trim().is_empty())\n            }) {\n                anyhow::bail!(\"project announcement is missing its address\");\n            }\n            let tags = template\n                .tags\n                .into_iter()\n                .map(|tag| {\n                    nostr::Tag::parse(tag)\n                        .map_err(|error| anyhow::anyhow!(\"invalid project tag: {error}\"))\n                })\n                .collect::<Result<Vec<_>>>()?;\n            let created_at = template.created_at.unwrap_or(now);\n            if created_at > now.saturating_add(300) {\n                anyhow::bail!(\"project announcement timestamp is too far in the future\");\n            }\n            nostr::EventBuilder::new(nostr::Kind::Custom(template.kind), template.content)\n                .tags(tags)\n                .custom_created_at(nostr::Timestamp::from(created_at))\n                .sign_with_keys(keys)\n                .map_err(|error| anyhow::anyhow!(\"sign project announcement: {error}\"))","sourceCodeStart":1704,"sourceCodeEnd":1740,"githubUrl":"https://github.com/block/buzz/blob/dad5a33865fc81a2e55b3b60746632f615ec1e3a/crates/buzz-acp/src/lib.rs#L1704-L1740","documentation":"Every project announcement template must carry a d tag with a non-empty value: the d tag is the NIP-33 address coordinate (kind:pubkey:d) that makes the announcement replaceable and resolvable. A template with no d tag, or one whose value is empty or whitespace-only, is rejected before signing.","triggerScenarios":"A project_owner_announcements template whose tags array lacks a [\"d\", \"...\"] entry entirely, or contains [\"d\", \"\"] / [\"d\", \"   \"]; the check at crates/buzz-acp/src/lib.rs:1220-1224 fails during event construction at startup.","commonSituations":"Hand-written config where the author assumed the id field was enough; a template generator that emits tags only for members and forgets the address tag.","solutions":["Add a d tag with a stable, non-empty identifier: [[\"d\", \"my-project\"]]","Keep the d value stable across restarts so the announcement replaces (NIP-33 LWW) instead of duplicating","Validate the template JSON with a preflight check before deploying the harness"],"exampleFix":"# agent config\n# before\nproject_owner_announcements:\n  - kind: 30621\n    tags: [[\"name\", \"my-project\"]]\n# after\nproject_owner_announcements:\n  - kind: 30621\n    tags: [[\"d\", \"my-project\"], [\"name\", \"my-project\"]]","handlingStrategy":"validation","validationCode":"// Preflight: every template must carry a non-empty d tag\nlet ok = templates.iter().all(|t| {\n    t.tags.iter().any(|tag| {\n        tag.first().is_some_and(|k| k == \"d\")\n            && tag.get(1).is_some_and(|v| !v.trim().is_empty())\n    })\n});\nif !ok { anyhow::bail!(\"a project announcement template is missing its d tag\"); }","typeGuard":"fn has_address_tag(tags: &[Vec<String>]) -> bool {\n    tags.iter().any(|tag| {\n        tag.first().is_some_and(|k| k == \"d\")\n            && tag.get(1).is_some_and(|v| !v.trim().is_empty())\n    })\n}","tryCatchPattern":null,"preventionTips":["Remember NIP-33 addressable events live at kind:pubkey:d — a missing d tag makes them unreplaceable","Keep the d value stable across config regenerations to avoid duplicate announcements"],"tags":["config","nostr","validation","nip-33","buzz-acp"],"backgroundTag":"missing-required-tag","analyzedSha":"dad5a33865fc81a2e55b3b60746632f615ec1e3a","analyzedAt":"2026-08-20T04:38:24.874Z","contentChangedAt":"2026-08-20T04:38:24.874Z","schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}