From 3f142ccce617f38510054de39a4f8a5e66515819 Mon Sep 17 00:00:00 2001 From: flaukowski Date: Sun, 26 Jul 2026 01:23:33 -0500 Subject: [PATCH] feat(relay): accept agent job-lifecycle kinds (43001-43006) in ingest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Buzz defines the NIP-90-style job kinds (KIND_JOB_REQUEST … KIND_JOB_ERROR) but never wired them into required_scope_for_kind, so member-posted job events were rejected with 'restricted: unknown event kind'. Map them to MessagesWrite: they're not command kinds and not global-only, so they get channel-scoped via their h tag and stored + fanned out through the generic message path — letting agents offer services in-channel (request → accepted → result). Adds a scope unit test. Co-Authored-By: Claude Fable 5 Signed-off-by: flaukowski (cherry picked from commit 0398a398511ab9b9eab02dfcf30f31ccfd5e7720) --- crates/buzz-relay/src/handlers/ingest.rs | 48 +++++++++++++++++++----- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/crates/buzz-relay/src/handlers/ingest.rs b/crates/buzz-relay/src/handlers/ingest.rs index a30b0e714d..5ad7f6a7ae 100644 --- a/crates/buzz-relay/src/handlers/ingest.rs +++ b/crates/buzz-relay/src/handlers/ingest.rs @@ -21,15 +21,16 @@ use buzz_core::kind::{ KIND_GIT_STATUS_CLOSED, KIND_GIT_STATUS_DRAFT, KIND_GIT_STATUS_MERGED, KIND_GIT_STATUS_OPEN, KIND_HUDDLE_ENDED, KIND_HUDDLE_GUIDELINES, KIND_HUDDLE_PARTICIPANT_JOINED, KIND_HUDDLE_PARTICIPANT_LEFT, KIND_HUDDLE_STARTED, KIND_IA_ARCHIVE_REQUEST, - KIND_IA_UNARCHIVE_REQUEST, KIND_LONG_FORM, KIND_MANAGED_AGENT, KIND_MEMBER_ADDED_NOTIFICATION, - KIND_MEMBER_REMOVED_NOTIFICATION, KIND_MODERATION_BAN, KIND_MODERATION_RESOLVE_REPORT, - KIND_MODERATION_TIMEOUT, KIND_MODERATION_UNBAN, KIND_MODERATION_UNTIMEOUT, KIND_MUTE_LIST, - KIND_NIP29_CREATE_GROUP, KIND_NIP29_DELETE_EVENT, KIND_NIP29_DELETE_GROUP, - KIND_NIP29_EDIT_METADATA, KIND_NIP29_JOIN_REQUEST, KIND_NIP29_LEAVE_REQUEST, - KIND_NIP29_PUT_USER, KIND_NIP29_REMOVE_USER, KIND_NIP43_LEAVE_REQUEST, - KIND_NIP65_RELAY_LIST_METADATA, KIND_PERSONA, KIND_PIN_LIST, KIND_PRESENCE_UPDATE, - KIND_PRODUCT_FEEDBACK, KIND_PROFILE, KIND_REACTION, KIND_READ_STATE, KIND_REPORT, - KIND_STREAM_MESSAGE, KIND_STREAM_MESSAGE_BOOKMARKED, KIND_STREAM_MESSAGE_DIFF, + KIND_IA_UNARCHIVE_REQUEST, KIND_JOB_ACCEPTED, KIND_JOB_CANCEL, KIND_JOB_ERROR, + KIND_JOB_PROGRESS, KIND_JOB_REQUEST, KIND_JOB_RESULT, KIND_LONG_FORM, KIND_MANAGED_AGENT, + KIND_MEMBER_ADDED_NOTIFICATION, KIND_MEMBER_REMOVED_NOTIFICATION, KIND_MODERATION_BAN, + KIND_MODERATION_RESOLVE_REPORT, KIND_MODERATION_TIMEOUT, KIND_MODERATION_UNBAN, + KIND_MODERATION_UNTIMEOUT, KIND_MUTE_LIST, KIND_NIP29_CREATE_GROUP, KIND_NIP29_DELETE_EVENT, + KIND_NIP29_DELETE_GROUP, KIND_NIP29_EDIT_METADATA, KIND_NIP29_JOIN_REQUEST, + KIND_NIP29_LEAVE_REQUEST, KIND_NIP29_PUT_USER, KIND_NIP29_REMOVE_USER, + KIND_NIP43_LEAVE_REQUEST, KIND_NIP65_RELAY_LIST_METADATA, KIND_PERSONA, KIND_PIN_LIST, + KIND_PRESENCE_UPDATE, KIND_PRODUCT_FEEDBACK, KIND_PROFILE, KIND_REACTION, KIND_READ_STATE, + KIND_REPORT, KIND_STREAM_MESSAGE, KIND_STREAM_MESSAGE_BOOKMARKED, KIND_STREAM_MESSAGE_DIFF, KIND_STREAM_MESSAGE_EDIT, KIND_STREAM_MESSAGE_PINNED, KIND_STREAM_MESSAGE_SCHEDULED, KIND_STREAM_MESSAGE_V2, KIND_STREAM_REMINDER, KIND_TEAM, KIND_TEXT_NOTE, KIND_USER_STATUS, KIND_WORKFLOW_DEF, KIND_WORKFLOW_TRIGGER, RELAY_ADMIN_ADD_MEMBER, RELAY_ADMIN_CHANGE_ROLE, @@ -313,6 +314,12 @@ fn required_scope_for_kind(kind: u32, event: &Event) -> Result Ok(Scope::MessagesWrite), KIND_WORKFLOW_DEF | KIND_WORKFLOW_TRIGGER => Ok(Scope::MessagesWrite), KIND_APPROVAL_GRANT | KIND_APPROVAL_DENY => Ok(Scope::MessagesWrite), + // NIP-90-style agent job lifecycle (43001 request … 43006 error). + // Channel-scoped via the `h` tag; stored and fanned out like messages + // so agents can offer services in-channel. Upstream defines these kinds + // but never wired them into ingest — this makes them writable. + KIND_JOB_REQUEST | KIND_JOB_ACCEPTED | KIND_JOB_PROGRESS | KIND_JOB_RESULT + | KIND_JOB_CANCEL | KIND_JOB_ERROR => Ok(Scope::MessagesWrite), _ => Err("restricted: unknown event kind"), } } @@ -2921,6 +2928,29 @@ mod tests { } } + #[test] + fn job_lifecycle_kinds_require_messages_write() { + let dummy = make_dummy_event(); + for kind in [ + KIND_JOB_REQUEST, + KIND_JOB_ACCEPTED, + KIND_JOB_PROGRESS, + KIND_JOB_RESULT, + KIND_JOB_CANCEL, + KIND_JOB_ERROR, + ] { + assert_eq!( + required_scope_for_kind(kind, &dummy).ok(), + Some(Scope::MessagesWrite), + "job kind {kind} should require MessagesWrite scope" + ); + assert!( + !is_global_only_kind(kind), + "job kind {kind} must stay channel-scoped via its h tag" + ); + } + } + #[test] fn nip51_and_nip65_lists_require_users_write() { let dummy = make_dummy_event();