From 4989bcdb5e22d910f8905af592f32e55718ab1a7 Mon Sep 17 00:00:00 2001 From: damocles Date: Wed, 22 Jul 2026 20:12:50 +0200 Subject: [PATCH] type Message.from as Ident --- hive-c0re/src/coordinator.rs | 14 ++++++------- hive-c0re/src/dashboard/misc_api.rs | 2 +- hive-c0re/src/socket_server/mod.rs | 10 ++++----- hive-c0re/src/stores/broker.rs | 14 ++++++------- .../src/workers/scheduled_prompts_worker.rs | 6 +++--- hive-sh4re/src/lib.rs | 21 ++++++++++++++++++- 6 files changed, 43 insertions(+), 24 deletions(-) diff --git a/hive-c0re/src/coordinator.rs b/hive-c0re/src/coordinator.rs index e5f8b3b5..6c07db17 100644 --- a/hive-c0re/src/coordinator.rs +++ b/hive-c0re/src/coordinator.rs @@ -1025,7 +1025,7 @@ impl Coordinator { // doesn't bubble out and unwind the topology write. if let Some(op) = old_parent.as_deref() { let _ = self.broker.send(&hive_sh4re::Message { - from: hive_sh4re::SYSTEM_SENDER.to_owned(), + from: hive_sh4re::trusted_sender(hive_sh4re::SYSTEM_SENDER), to: op.to_owned(), body: format!("{child} moved out of your subtree to {new_label}"), in_reply_to: None, @@ -1033,7 +1033,7 @@ impl Coordinator { } if let Some(np) = new_parent { let _ = self.broker.send(&hive_sh4re::Message { - from: hive_sh4re::SYSTEM_SENDER.to_owned(), + from: hive_sh4re::trusted_sender(hive_sh4re::SYSTEM_SENDER), to: np.to_owned(), body: format!( "{child} just moved into your subtree (was previously under {old_label})" @@ -1087,7 +1087,7 @@ impl Coordinator { let new_label = new_parent.unwrap_or(""); if let Some(op) = old_parent.as_deref() { let _ = self.broker.send(&hive_sh4re::Message { - from: hive_sh4re::SYSTEM_SENDER.to_owned(), + from: hive_sh4re::trusted_sender(hive_sh4re::SYSTEM_SENDER), to: op.to_owned(), body: format!("{child} moved out of your subtree to {new_label}"), in_reply_to: None, @@ -1095,7 +1095,7 @@ impl Coordinator { } if let Some(np) = new_parent { let _ = self.broker.send(&hive_sh4re::Message { - from: hive_sh4re::SYSTEM_SENDER.to_owned(), + from: hive_sh4re::trusted_sender(hive_sh4re::SYSTEM_SENDER), to: np.to_owned(), body: format!( "{child} just moved into your subtree (was previously under {old_label})" @@ -1349,7 +1349,7 @@ impl Coordinator { still in your window." ); if let Err(e) = self.broker.send(&hive_sh4re::Message { - from: hive_sh4re::SYSTEM_SENDER.to_owned(), + from: hive_sh4re::trusted_sender(hive_sh4re::SYSTEM_SENDER), to: name.to_owned(), body, in_reply_to: None, @@ -1402,7 +1402,7 @@ impl Coordinator { } }; if let Err(e) = self.broker.send(&hive_sh4re::Message { - from: from.to_owned(), + from: hive_sh4re::trusted_sender(from), to: agent.to_owned(), body, in_reply_to: None, @@ -1424,7 +1424,7 @@ impl Coordinator { continue; } if let Err(e) = self.broker.send(&hive_sh4re::Message { - from: from.to_owned(), + from: hive_sh4re::trusted_sender(from), to: agent_name.clone(), body: broadcast_body.clone(), in_reply_to: None, diff --git a/hive-c0re/src/dashboard/misc_api.rs b/hive-c0re/src/dashboard/misc_api.rs index 1548d8db..ddce4636 100644 --- a/hive-c0re/src/dashboard/misc_api.rs +++ b/hive-c0re/src/dashboard/misc_api.rs @@ -164,7 +164,7 @@ pub(super) async fn post_op_send( )); } } else if let Err(e) = state.coord.broker.send(&hive_sh4re::Message { - from: hive_sh4re::OPERATOR_RECIPIENT.to_owned(), + from: hive_sh4re::trusted_sender(hive_sh4re::OPERATOR_RECIPIENT), to: to.clone(), body, in_reply_to: None, diff --git a/hive-c0re/src/socket_server/mod.rs b/hive-c0re/src/socket_server/mod.rs index 27e14ae3..ffe3c81f 100644 --- a/hive-c0re/src/socket_server/mod.rs +++ b/hive-c0re/src/socket_server/mod.rs @@ -321,7 +321,7 @@ async fn handle_recv( messages: deliveries .into_iter() .map(|d| hive_sh4re::DeliveredMessage { - from: d.message.from, + from: d.message.from.to_string(), body: d.message.body, id: d.id, redelivered: d.redelivered, @@ -347,7 +347,7 @@ fn handle_wake( body: &str, ) -> hive_core_agent_sock::Response { match coord.broker.send(&Message { - from: from.to_owned(), + from: hive_sh4re::trusted_sender(from), to: agent.to_owned(), body: body.to_owned(), in_reply_to: None, @@ -490,7 +490,7 @@ fn handle_operator_msg( body: &str, ) -> hive_core_agent_sock::Response { match coord.broker.send(&Message { - from: hive_sh4re::OPERATOR_RECIPIENT.to_owned(), + from: hive_sh4re::trusted_sender(hive_sh4re::OPERATOR_RECIPIENT), to: agent.to_owned(), body: body.to_owned(), in_reply_to: None, @@ -955,7 +955,7 @@ pub(crate) fn fan_out_send( continue; } if let Err(e) = coord.broker.send(&Message { - from: from.to_owned(), + from: hive_sh4re::trusted_sender(from), to: target.clone(), body: body.to_owned(), in_reply_to, @@ -1040,7 +1040,7 @@ pub(crate) fn handle_send( } } match coord.broker.send(&Message { - from: agent.to_owned(), + from: hive_sh4re::trusted_sender(agent), to: resolved, body: body.to_owned(), in_reply_to, diff --git a/hive-c0re/src/stores/broker.rs b/hive-c0re/src/stores/broker.rs index 204d1b1f..1b8cca58 100644 --- a/hive-c0re/src/stores/broker.rs +++ b/hive-c0re/src/stores/broker.rs @@ -277,12 +277,12 @@ impl Broker { // Operator messages get elevated priority so they surface before // queued wakes (bash completions, forge events, etc.) when the // harness pops the next turn driver. All other senders stay at 0. - let priority: i64 = i64::from(message.from == "operator"); + let priority: i64 = i64::from(message.from.as_str() == hive_sh4re::OPERATOR_RECIPIENT); conn.execute( "INSERT INTO messages (sender, recipient, body, sent_at, in_reply_to, priority) \ VALUES (?1, ?2, ?3, ?4, ?5, ?6)", params![ - message.from, + message.from.as_str(), message.to, message.body, now, @@ -294,7 +294,7 @@ impl Broker { drop(conn); let _ = self.events.send(MessageEvent::Sent { id: row_id, - from: message.from.clone(), + from: message.from.to_string(), to: message.to.clone(), body: message.body.clone(), at: now, @@ -664,7 +664,7 @@ impl Broker { id, redelivered, message: Message { - from, + from: hive_sh4re::trusted_sender(&from), to, body, in_reply_to, @@ -678,7 +678,7 @@ impl Broker { for d in &deliveries { let _ = self.events.send(MessageEvent::Delivered { id: d.id, - from: d.message.from.clone(), + from: d.message.from.to_string(), to: d.message.to.clone(), body: d.message.body.clone(), at: now, @@ -1187,7 +1187,7 @@ mod tests { fn msg(from: &str, to: &str, body: &str) -> Message { Message { - from: from.to_owned(), + from: hive_types::Ident::parse(from).expect("test sender must be a valid ident"), to: to.to_owned(), body: body.to_owned(), in_reply_to: None, @@ -1475,7 +1475,7 @@ mod tests { assert_eq!(batch.len(), 3); // Operator message surfaces first despite arriving last. assert_eq!(batch[0].message.body, "stop what you're doing"); - assert_eq!(batch[0].message.from, "operator"); + assert_eq!(batch[0].message.from.as_str(), "operator"); // Remaining two in FIFO order. assert_eq!(batch[1].message.body, "task done"); assert_eq!(batch[2].message.body, "new pr"); diff --git a/hive-c0re/src/workers/scheduled_prompts_worker.rs b/hive-c0re/src/workers/scheduled_prompts_worker.rs index b4da2909..cde0d960 100644 --- a/hive-c0re/src/workers/scheduled_prompts_worker.rs +++ b/hive-c0re/src/workers/scheduled_prompts_worker.rs @@ -129,7 +129,7 @@ fn fire_schedule(coord: &Arc, schedule: &Schedule, now: i64) { Ok(false) => {} } let msg = Message { - from: "scheduled".to_owned(), + from: hive_sh4re::trusted_sender("scheduled"), to: target.clone(), body: schedule.body.clone(), in_reply_to: None, @@ -227,7 +227,7 @@ fn notify_operator_missing_target(coord: &Coordinator, schedule: &Schedule, targ body = schedule.body ); let msg = Message { - from: "scheduled".to_owned(), + from: hive_sh4re::trusted_sender("scheduled"), to: hive_sh4re::OPERATOR_RECIPIENT.to_owned(), body, in_reply_to: None, @@ -327,7 +327,7 @@ pub async fn fire_now( continue; } let msg = Message { - from: "scheduled".to_owned(), + from: hive_sh4re::trusted_sender("scheduled"), to: target.clone(), body: schedule.body.clone(), in_reply_to: None, diff --git a/hive-sh4re/src/lib.rs b/hive-sh4re/src/lib.rs index 91babdcf..56dfef38 100644 --- a/hive-sh4re/src/lib.rs +++ b/hive-sh4re/src/lib.rs @@ -155,7 +155,7 @@ pub struct ReminderStats { /// A logical message between agents. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Message { - pub from: String, + pub from: Ident, pub to: String, pub body: String, /// Optional broker row-id of the message this is a reply to. @@ -442,6 +442,25 @@ pub const CHILDREN_RECIPIENT: &str = ""; /// Manager harness recognises this and parses the body as a `HelperEvent`. pub const SYSTEM_SENDER: &str = "system"; +/// Parse `s` as a [`Ident`] for use as `Message.from`, falling back to +/// [`SYSTEM_SENDER`] on the (should-be-unreachable) case that `s` isn't +/// ident-shaped. `Message.from` is always either a fixed sentinel literal +/// (`SYSTEM_SENDER`, `OPERATOR_RECIPIENT`, `"scheduled"`, …) or an +/// already-registered agent's own name reaching this point through +/// hive-c0re's internal dispatch — never arbitrary external input — so +/// this is a defensive fallback for a programming-bug case, not a +/// validation gate. +/// +/// # Panics +/// +/// Never, unless [`SYSTEM_SENDER`] itself stops being ident-shaped (which +/// would also be a programming bug, caught by `hive-types`' own tests). +#[must_use] +pub fn trusted_sender(s: &str) -> Ident { + Ident::parse(s) + .unwrap_or_else(|_| Ident::parse(SYSTEM_SENDER).expect("SYSTEM_SENDER is a valid Ident")) +} + /// Out-of-band events the host-side daemon pushes to the manager's inbox. /// Serialised as JSON in `Message::body` (sender = `SYSTEM_SENDER`). /// Per-variant triggers + the optional `sha`/`tag` semantics live in