diff --git a/Cargo.lock b/Cargo.lock index a75fe8fc..f7bf45d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1556,7 +1556,6 @@ dependencies = [ "hive-agent-sock", "hive-core-agent-sock", "hive-sh4re", - "hive-types", "rmcp", "serde", "serde_json", @@ -1663,7 +1662,6 @@ name = "hive-core-agent-sock" version = "0.1.0" dependencies = [ "hive-sh4re", - "hive-types", "serde", ] @@ -1768,7 +1766,6 @@ name = "hive-sh4re" version = "0.1.0" dependencies = [ "chrono", - "hive-types", "schemars", "serde", "serde_json", diff --git a/hive-agent-mcp/Cargo.toml b/hive-agent-mcp/Cargo.toml index fda57a7c..33576ddf 100644 --- a/hive-agent-mcp/Cargo.toml +++ b/hive-agent-mcp/Cargo.toml @@ -17,7 +17,6 @@ clap.workspace = true hive-agent-sock.workspace = true hive-core-agent-sock.workspace = true hive-sh4re.workspace = true -hive-types.workspace = true rmcp.workspace = true serde.workspace = true serde_json.workspace = true diff --git a/hive-agent-mcp/src/mcp/mod.rs b/hive-agent-mcp/src/mcp/mod.rs index e9ec17cb..73e30954 100644 --- a/hive-agent-mcp/src/mcp/mod.rs +++ b/hive-agent-mcp/src/mcp/mod.rs @@ -181,17 +181,13 @@ impl AgentServer { async fn ask(&self, Parameters(args): Parameters) -> String { let log = format!("{args:?}"); run_tool_envelope("ask", log, async move { - let to = match args.to.map(|t| hive_types::Ident::parse(&t)).transpose() { - Ok(to) => to, - Err(reason) => return format!("invalid `to` agent name: {reason}"), - }; let (resp, retries) = self .dispatch(hive_core_agent_sock::Request::Ask { question: args.question, options: args.options, multi: args.multi, ttl_seconds: args.ttl_seconds, - to, + to: args.to, }) .await; let s = match resp { @@ -392,12 +388,8 @@ impl AgentServer { async fn get_agent_meta(&self, Parameters(args): Parameters) -> String { let log = args.name.clone().unwrap_or_else(|| "".to_owned()); run_tool_envelope("get_agent_meta", log, async move { - let name = match args.name.map(|n| hive_types::Ident::parse(&n)).transpose() { - Ok(name) => name, - Err(reason) => return format!("invalid agent name: {reason}"), - }; let (resp, retries) = self - .dispatch(hive_core_agent_sock::Request::GetAgentMeta { name }) + .dispatch(hive_core_agent_sock::Request::GetAgentMeta { name: args.name }) .await; annotate_retries(format_agent_meta(resp), retries) }) diff --git a/hive-c0re/src/actions.rs b/hive-c0re/src/actions.rs index 13e3992e..e091a686 100644 --- a/hive-c0re/src/actions.rs +++ b/hive-c0re/src/actions.rs @@ -41,9 +41,12 @@ pub async fn approve(coord: Arc, id: i64) -> Result<()> { // Sub-second git seed + forge-remote wire. Routing through // the queue would surface a queue card that's gone before // the operator's eyes refocus. Run inline. - let proposed_dir = Coordinator::agent_proposed_dir(&approval.agent); - let claude_dir = Coordinator::agent_claude_dir(&approval.agent); - let notes_dir = Coordinator::agent_notes_dir(&approval.agent); + let agent = hive_types::Ident::parse(&approval.agent).map_err(|e| { + anyhow::anyhow!("approval {} has invalid agent name: {e}", approval.id) + })?; + let proposed_dir = Coordinator::agent_proposed_dir(&agent); + let claude_dir = Coordinator::agent_claude_dir(&agent); + let notes_dir = Coordinator::agent_notes_dir(&agent); run_approval_init_config(&coord, approval, proposed_dir, claude_dir, notes_dir).await } ApprovalKind::UpdateMetaInputs => { @@ -72,14 +75,11 @@ pub async fn approve(coord: Arc, id: i64) -> Result<()> { ApprovalKind::Spawn => { // The spawn's tail `Reconcile` starts the container, so the // new agent's power intent is `Up` from the outset. - if let Err(e) = coord - .power - .set(approval.agent.as_str(), crate::power::Wanted::Up) - { + if let Err(e) = coord.power.set(&approval.agent, crate::power::Wanted::Up) { tracing::warn!(agent = %approval.agent, error = ?e, "agent_power: seed on spawn failed"); } let submitted = coord.job_queue.submit(crate::job_queue::templates::spawn( - approval.agent.as_str(), + &approval.agent, id, format!("approval #{id} spawn"), )); @@ -110,7 +110,7 @@ pub async fn approve(coord: Arc, id: i64) -> Result<()> { // deploy tail). enqueue_approval_rebuild( &coord, - approval.agent.as_str(), + &approval.agent, id, format!("approval #{id} merge config pr"), ); @@ -158,8 +158,8 @@ pub async fn run_approval_merge_config_pr( approval_id: i64, ) -> Result<()> { let approval = fetch_approval_for_worker(coord, approval_id, ApprovalKind::MergeConfigPr)?; - let agent_dir = crate::paths::agent_runtime_dir(approval.agent.as_str()); - let applied_dir = crate::paths::applied_dir(approval.agent.as_str()); + let agent_dir = crate::paths::agent_runtime_dir(&approval.agent); + let applied_dir = crate::paths::applied_dir(&approval.agent); // Captured up front to scope the failure-comment's build-log lookup to // rows this deploy produced (see `post_merge_failure_to_pr`). let since_ts = hive_sh4re::wire_time::now_unix(); @@ -172,7 +172,7 @@ pub async fn run_approval_merge_config_pr( // ff'd by the merge, so only the tag refspec actually lands; best-effort, // never fails the approval. coord.set_queue_step(queue_entry_id, "forge push"); - if let Err(e) = crate::forge::push_config(approval.agent.as_str()).await { + if let Err(e) = crate::forge::push_config(&approval.agent).await { tracing::warn!(agent = %approval.agent, error = ?e, "forge: push_config after merge failed"); } // On a failed deploy, surface the failing build log back onto the PR so @@ -208,11 +208,11 @@ async fn post_merge_failure_to_pr( let Ok(pr) = approval.commit_ref.parse::() else { return; }; - let repo = crate::forge::config_repo(approval.agent.as_str()); + let repo = crate::forge::config_repo(&approval.agent); let log_section = coord .build_logs - .list_recent_for_agent(approval.agent.as_str(), 10) + .list_recent_for_agent(&approval.agent, 10) .ok() .and_then(|rows| { rows.into_iter() @@ -300,7 +300,7 @@ async fn run_merge_config_pr( ); } }; - let repo = crate::forge::config_repo(approval.agent.as_str()); + let repo = crate::forge::config_repo(&approval.agent); // 1. Drift gate: the live PR head must still equal what was reviewed. coord.set_queue_step(queue_entry_id, "verify PR head"); @@ -328,9 +328,7 @@ async fn run_merge_config_pr( // 3. Eval-verify BEFORE the irreversible push (bad nix fails fast here). coord.set_queue_step(queue_entry_id, "verify proposal (eval)"); - if let Err(e) = - crate::meta::verify_commit(approval.agent.as_str(), applied_dir, &reviewed).await - { + if let Err(e) = crate::meta::verify_commit(&approval.agent, applied_dir, &reviewed).await { return ( Err(anyhow::anyhow!("verify merge head {reviewed}: {e:#}")), None, @@ -366,7 +364,7 @@ async fn run_merge_config_pr( // 5. Deploy tail. target == finalize == the reviewed head. deploy_applied_target( coord, - approval.agent.as_str(), + &approval.agent, agent_dir, applied_dir, &reviewed, @@ -393,7 +391,7 @@ async fn run_approval_schedule_prompt( coord .scheduled_prompts .submit(&crate::scheduled_prompts::NewSchedule { - owner: approval.agent.to_string(), + owner: approval.agent.clone(), targets: payload.targets, body: payload.body, first_fire_at_unix: payload.first_fire_at_unix, @@ -453,7 +451,7 @@ pub(crate) async fn resolve_approval_dag( // access) — warn-only, then the resolution events + a rescan so // the dashboard reflects the post-spawn state either way. if result.is_ok() { - forge_after_first_spawn(coord, approval.agent.as_str()).await; + forge_after_first_spawn(coord, &approval.agent).await; } else { coord.rescan_containers_and_emit().await; crate::dashboard::emit_tombstones_snapshot(coord).await; @@ -530,7 +528,7 @@ async fn run_approval_init_config( // and let `topology::reconcile` assign the default position on // first spawn, so this path never names a specific root agent. if !approval.commit_ref.is_empty() { - crate::topology::add_child(approval.agent.as_str(), &approval.commit_ref) + crate::topology::add_child(&approval.agent, &approval.commit_ref) .map_err(|e| anyhow::anyhow!("topology add_child: {e}"))?; } // Create the agent's state root as a btrfs subvolume FIRST, before @@ -540,15 +538,15 @@ async fn run_approval_init_config( // materialise the state root as a plain directory — after which // the subvolume create is silently skipped and the agent never // lands on a subvolume (no quota, no snapshot). Order matters. - lifecycle::ensure_agent_state_subvolume(approval.agent.as_str()).await?; - lifecycle::setup_proposed(&proposed_dir, approval.agent.as_str()).await?; + lifecycle::ensure_agent_state_subvolume(&approval.agent).await?; + lifecycle::setup_proposed(&proposed_dir, &approval.agent).await?; lifecycle::ensure_claude_dir(&claude_dir)?; lifecycle::ensure_state_dir(¬es_dir)?; Ok(()) } .await; if result.is_ok() - && let Err(e) = crate::forge::ensure_meta_remote(approval.agent.as_str()).await + && let Err(e) = crate::forge::ensure_meta_remote(&approval.agent).await { tracing::warn!(agent = %approval.agent, error = ?e, "forge: ensure_meta_remote after init_config failed"); } @@ -573,7 +571,7 @@ fn finish_approval( approval.id, &HelperEvent::ApprovalResolved { id: approval.id, - agent: approval.agent.to_string(), + agent: approval.agent.clone(), commit_ref: approval.commit_ref.clone(), status, note: note.clone(), @@ -594,7 +592,7 @@ fn finish_approval( let status_str = if ok { "approved" } else { "failed" }; coord.emit_approval_resolved(crate::coordinator::ApprovalResolved { id: approval.id, - agent: approval.agent.as_str(), + agent: &approval.agent, approval_kind, sha_short, status: status_str, @@ -612,7 +610,7 @@ fn finish_approval( coord.notify_submitter( approval.id, &HelperEvent::ConfigReady { - agent: approval.agent.to_string(), + agent: approval.agent.clone(), }, ); } @@ -620,7 +618,7 @@ fn finish_approval( ApprovalKind::Spawn => coord.notify_submitter( approval.id, &HelperEvent::Spawned { - agent: approval.agent.to_string(), + agent: approval.agent.clone(), ok, note, }, @@ -632,7 +630,7 @@ fn finish_approval( coord.notify_submitter( approval.id, &HelperEvent::Rebuilt { - agent: approval.agent.to_string(), + agent: approval.agent.clone(), ok, note, sha: approval.fetched_sha.clone(), @@ -883,7 +881,7 @@ pub fn deny(coord: &Coordinator, id: i64, note: Option<&str>) -> Result<()> { a.id, &HelperEvent::ApprovalResolved { id: a.id, - agent: a.agent.to_string(), + agent: a.agent, commit_ref: a.commit_ref, status: ApprovalStatus::Denied, note: note.map(String::from), @@ -894,7 +892,7 @@ pub fn deny(coord: &Coordinator, id: i64, note: Option<&str>) -> Result<()> { ); coord.emit_approval_resolved(crate::coordinator::ApprovalResolved { id, - agent: agent_owned.as_str(), + agent: &agent_owned, approval_kind, sha_short, status: "denied", diff --git a/hive-c0re/src/coordinator.rs b/hive-c0re/src/coordinator.rs index 6c07db17..e5f8b3b5 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::trusted_sender(hive_sh4re::SYSTEM_SENDER), + from: hive_sh4re::SYSTEM_SENDER.to_owned(), 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::trusted_sender(hive_sh4re::SYSTEM_SENDER), + from: hive_sh4re::SYSTEM_SENDER.to_owned(), 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::trusted_sender(hive_sh4re::SYSTEM_SENDER), + from: hive_sh4re::SYSTEM_SENDER.to_owned(), 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::trusted_sender(hive_sh4re::SYSTEM_SENDER), + from: hive_sh4re::SYSTEM_SENDER.to_owned(), 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::trusted_sender(hive_sh4re::SYSTEM_SENDER), + from: hive_sh4re::SYSTEM_SENDER.to_owned(), 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: hive_sh4re::trusted_sender(from), + from: from.to_owned(), 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: hive_sh4re::trusted_sender(from), + from: from.to_owned(), to: agent_name.clone(), body: broadcast_body.clone(), in_reply_to: None, diff --git a/hive-c0re/src/dashboard/approvals.rs b/hive-c0re/src/dashboard/approvals.rs index 4239eb6f..04c1ee96 100644 --- a/hive-c0re/src/dashboard/approvals.rs +++ b/hive-c0re/src/dashboard/approvals.rs @@ -66,7 +66,12 @@ pub(super) fn gc_orphans(coord: &Coordinator, approvals: Vec) -> Vec) -> Vec ApprovalHistoryView { let kind = a.kind.as_str(); ApprovalHistoryView { id: a.id, - agent: a.agent.to_string(), + agent: a.agent, kind, sha_short, status, @@ -567,7 +567,7 @@ fn build_approval_views(approvals: Vec) -> Vec { out.push(match a.kind { hive_sh4re::ApprovalKind::Spawn => ApprovalView { id: a.id, - agent: a.agent.to_string(), + agent: a.agent, kind: "spawn", sha_short: None, description: a.description, @@ -577,7 +577,7 @@ fn build_approval_views(approvals: Vec) -> Vec { }, hive_sh4re::ApprovalKind::InitConfig => ApprovalView { id: a.id, - agent: a.agent.to_string(), + agent: a.agent, kind: "init_config", sha_short: None, description: a.description, @@ -587,7 +587,7 @@ fn build_approval_views(approvals: Vec) -> Vec { }, hive_sh4re::ApprovalKind::UpdateMetaInputs => ApprovalView { id: a.id, - agent: a.agent.to_string(), + agent: a.agent, kind: "update_meta_inputs", sha_short: None, description: a.description, @@ -597,7 +597,7 @@ fn build_approval_views(approvals: Vec) -> Vec { }, hive_sh4re::ApprovalKind::SchedulePrompt => ApprovalView { id: a.id, - agent: a.agent.to_string(), + agent: a.agent, kind: "schedule_prompt", sha_short: None, description: a.description, @@ -618,7 +618,7 @@ fn build_approval_views(approvals: Vec) -> Vec { let pr_number = a.commit_ref.parse::().ok(); ApprovalView { id: a.id, - agent: a.agent.to_string(), + agent: a.agent, kind: "merge_config_pr", sha_short: sha, description: a.description, diff --git a/hive-c0re/src/forge/config_pr_poll.rs b/hive-c0re/src/forge/config_pr_poll.rs index fe8ef2b2..ba233b44 100644 --- a/hive-c0re/src/forge/config_pr_poll.rs +++ b/hive-c0re/src/forge/config_pr_poll.rs @@ -145,15 +145,13 @@ fn reconcile_stale_config_pr_approvals( } }; for a in pending { - if a.kind != hive_sh4re::ApprovalKind::MergeConfigPr - || !scanned_agents.contains(a.agent.as_str()) - { + if a.kind != hive_sh4re::ApprovalKind::MergeConfigPr || !scanned_agents.contains(&a.agent) { continue; } let Ok(pr_number) = a.commit_ref.parse::() else { continue; }; - if open_prs.contains(&(a.agent.to_string(), pr_number)) { + if open_prs.contains(&(a.agent.clone(), pr_number)) { continue; } match coord @@ -167,7 +165,7 @@ fn reconcile_stale_config_pr_approvals( ); coord.emit_approval_resolved(crate::coordinator::ApprovalResolved { id: a.id, - agent: a.agent.as_str(), + agent: &a.agent, approval_kind: "merge_config_pr", sha_short: a .fetched_sha diff --git a/hive-c0re/src/loose_ends.rs b/hive-c0re/src/loose_ends.rs index d510d222..b09228f8 100644 --- a/hive-c0re/src/loose_ends.rs +++ b/hive-c0re/src/loose_ends.rs @@ -66,7 +66,7 @@ pub fn for_agent(coord: &Coordinator, agent: &str) -> Result> { } out.push(LooseEnd::Approval { id: a.id, - agent: a.agent.to_string(), + agent: a.agent, commit_ref: a.commit_ref, description: a.description, age_seconds: saturating_age(now, a.requested_at.timestamp()), @@ -110,7 +110,7 @@ pub fn hive_wide(coord: &Coordinator) -> Result> { for a in coord.approvals.pending()? { out.push(LooseEnd::Approval { id: a.id, - agent: a.agent.to_string(), + agent: a.agent, commit_ref: a.commit_ref, description: a.description, age_seconds: saturating_age(now, a.requested_at.timestamp()), diff --git a/hive-c0re/src/questions.rs b/hive-c0re/src/questions.rs index d531ee0c..f6e878a3 100644 --- a/hive-c0re/src/questions.rs +++ b/hive-c0re/src/questions.rs @@ -227,7 +227,7 @@ pub fn handle_cancel_loose_end( .map(|s| s[..s.len().min(12)].to_owned()); coord.emit_approval_resolved(crate::coordinator::ApprovalResolved { id: approval.id, - agent: approval.agent.as_str(), + agent: &approval.agent, approval_kind: approval.kind.as_str(), sha_short, status: "cancelled", diff --git a/hive-c0re/src/socket_server/mod.rs b/hive-c0re/src/socket_server/mod.rs index ffe3c81f..8f14dfd5 100644 --- a/hive-c0re/src/socket_server/mod.rs +++ b/hive-c0re/src/socket_server/mod.rs @@ -222,7 +222,7 @@ pub(crate) async fn dispatch_shared( options, *multi, *ttl_seconds, - to.as_ref().map(hive_types::Ident::as_str), + to.as_deref(), ) .map_or_else( |message| hive_core_agent_sock::Response::Err { message }, @@ -241,7 +241,7 @@ pub(crate) async fn dispatch_shared( } => handle_remind(coord, agent, message, timing, file_path.as_deref()), hive_core_agent_sock::Request::SetStatus { text } => handle_set_status(coord, text), hive_core_agent_sock::Request::GetAgentMeta { name } => { - handle_get_agent_meta(coord, agent, name.as_ref()).await + handle_get_agent_meta(coord, agent, name.as_deref()).await } hive_core_agent_sock::Request::CancelLooseEnd { kind, id } => { crate::questions::handle_cancel_loose_end(coord, agent, *kind, *id).map_or_else( @@ -321,7 +321,7 @@ async fn handle_recv( messages: deliveries .into_iter() .map(|d| hive_sh4re::DeliveredMessage { - from: d.message.from.to_string(), + from: d.message.from, 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: hive_sh4re::trusted_sender(from), + from: from.to_owned(), to: agent.to_owned(), body: body.to_owned(), in_reply_to: None, @@ -418,43 +418,42 @@ async fn handle_create_repo(agent: &str, repo: &str) -> hive_core_agent_sock::Re async fn handle_get_agent_meta( coord: &Arc, agent: &str, - name: Option<&hive_types::Ident>, + name: Option<&str>, ) -> hive_core_agent_sock::Response { - // `name` arrives pre-validated by serde (wire field is `Ident`). The - // `None` default (target == caller) still needs a parse since `agent` - // is a plain `&str` here — but it's the caller's own authenticated - // name, already valid in practice. - let target_id = match name { - Some(id) => id.clone(), - None => match hive_types::Ident::parse(agent) { - Ok(id) => id, - Err(reason) => { - return hive_core_agent_sock::Response::Err { - message: format!("get_agent_meta: invalid agent name {agent:?}: {reason}"), - }; - } - }, + let target = name.unwrap_or(agent); + // `name` is agent-supplied and flows into filesystem reads below + // (`read_agent_status_live`, `read_agent_matrix_identities` → + // `agent_notes_dir(target)`), where a `../` component would traverse at + // the OS level. Validate it before any path is built. The `None` default + // (`target == agent`) is the caller's own authenticated name, already + // valid — but validating unconditionally is simplest and harmless. + let target_id = match hive_types::Ident::parse(target) { + Ok(id) => id, + Err(reason) => { + return hive_core_agent_sock::Response::Err { + message: format!("get_agent_meta: invalid agent name {target:?}: {reason}"), + }; + } }; let (status_text, status_set_at, running) = crate::container_view::read_agent_status_live(&target_id).await; let (hive_name, swarm_name) = crate::container_view::hive_swarm_names(); - // Matrix identities are public handles (`name` / `user_id` - // `@user:server` / `homeserver`) — the access token lives separately - // in the agent's `matrix-token` and is never part of this response. - // Peer visibility is intentional: it lets an agent verify/contact - // another on a public matrix instance. The `Ident::parse` gate - // above is what closes the real vector here (path traversal via `../` - // in an agent-supplied name). - let matrix_accounts = read_agent_matrix_identities(&target_id); hive_core_agent_sock::Response::AgentMeta { - name: target_id.into_string(), + name: target.to_owned(), running, hyperhive_rev: crate::auto_update::current_flake_rev(&coord.hyperhive_flake), status_text, status_set_at, hive_name, swarm_name, - matrix_accounts, + // Matrix identities are public handles (`name` / `user_id` + // `@user:server` / `homeserver`) — the access token lives separately + // in the agent's `matrix-token` and is never part of this response. + // Peer visibility is intentional: it lets an agent verify/contact + // another on a public matrix instance. The `Ident::parse` gate + // above is what closes the real vector here (path traversal via `../` + // in an agent-supplied name). + matrix_accounts: read_agent_matrix_identities(&target_id), } } @@ -490,7 +489,7 @@ fn handle_operator_msg( body: &str, ) -> hive_core_agent_sock::Response { match coord.broker.send(&Message { - from: hive_sh4re::trusted_sender(hive_sh4re::OPERATOR_RECIPIENT), + from: hive_sh4re::OPERATOR_RECIPIENT.to_owned(), to: agent.to_owned(), body: body.to_owned(), in_reply_to: None, @@ -955,7 +954,7 @@ pub(crate) fn fan_out_send( continue; } if let Err(e) = coord.broker.send(&Message { - from: hive_sh4re::trusted_sender(from), + from: from.to_owned(), to: target.clone(), body: body.to_owned(), in_reply_to, @@ -1040,7 +1039,7 @@ pub(crate) fn handle_send( } } match coord.broker.send(&Message { - from: hive_sh4re::trusted_sender(agent), + from: agent.to_owned(), to: resolved, body: body.to_owned(), in_reply_to, diff --git a/hive-c0re/src/stores/approvals.rs b/hive-c0re/src/stores/approvals.rs index 313f04e4..3405d75c 100644 --- a/hive-c0re/src/stores/approvals.rs +++ b/hive-c0re/src/stores/approvals.rs @@ -307,7 +307,7 @@ impl Approvals { /// across both callers (one suppressed the lint, the other aliased the /// tuple) — one named projection + mapper now backs both. struct ApprovalLookup { - agent: hive_types::Ident, + agent: String, kind: String, commit_ref: String, requested_at: i64, @@ -323,16 +323,8 @@ impl ApprovalLookup { description FROM approvals WHERE id = ?1"; fn from_row(row: &rusqlite::Row<'_>) -> rusqlite::Result { - let agent: String = row.get(0)?; - let agent = hive_types::Ident::parse(&agent).map_err(|e| { - rusqlite::Error::FromSqlConversionFailure( - 0, - rusqlite::types::Type::Text, - format!("invalid approval agent {agent:?}: {e}").into(), - ) - })?; Ok(Self { - agent, + agent: row.get(0)?, kind: row.get(1)?, commit_ref: row.get(2)?, requested_at: row.get(3)?, @@ -407,17 +399,9 @@ fn row_to_approval(row: &rusqlite::Row<'_>) -> rusqlite::Result { )); } }; - let agent: String = row.get(1)?; - let agent = hive_types::Ident::parse(&agent).map_err(|e| { - rusqlite::Error::FromSqlConversionFailure( - 1, - rusqlite::types::Type::Text, - format!("invalid approval agent {agent:?}: {e}").into(), - ) - })?; Ok(Approval { id: row.get(0)?, - agent, + agent: row.get(1)?, kind, commit_ref: row.get(3)?, requested_at: hive_sh4re::wire_time::from_secs(row.get(4)?), diff --git a/hive-c0re/src/stores/broker.rs b/hive-c0re/src/stores/broker.rs index 1b8cca58..204d1b1f 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.as_str() == hive_sh4re::OPERATOR_RECIPIENT); + let priority: i64 = i64::from(message.from == "operator"); conn.execute( "INSERT INTO messages (sender, recipient, body, sent_at, in_reply_to, priority) \ VALUES (?1, ?2, ?3, ?4, ?5, ?6)", params![ - message.from.as_str(), + message.from, 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.to_string(), + from: message.from.clone(), to: message.to.clone(), body: message.body.clone(), at: now, @@ -664,7 +664,7 @@ impl Broker { id, redelivered, message: Message { - from: hive_sh4re::trusted_sender(&from), + 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.to_string(), + from: d.message.from.clone(), 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: hive_types::Ident::parse(from).expect("test sender must be a valid ident"), + from: from.to_owned(), 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.as_str(), "operator"); + assert_eq!(batch[0].message.from, "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 cde0d960..b4da2909 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: hive_sh4re::trusted_sender("scheduled"), + from: "scheduled".to_owned(), 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: hive_sh4re::trusted_sender("scheduled"), + from: "scheduled".to_owned(), 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: hive_sh4re::trusted_sender("scheduled"), + from: "scheduled".to_owned(), to: target.clone(), body: schedule.body.clone(), in_reply_to: None, diff --git a/hive-core-agent-sock/Cargo.toml b/hive-core-agent-sock/Cargo.toml index c0e6a342..f810785d 100644 --- a/hive-core-agent-sock/Cargo.toml +++ b/hive-core-agent-sock/Cargo.toml @@ -8,5 +8,4 @@ workspace = true [dependencies] hive-sh4re.workspace = true -hive-types.workspace = true serde.workspace = true diff --git a/hive-core-agent-sock/src/lib.rs b/hive-core-agent-sock/src/lib.rs index 90e18c4d..8c4b58ba 100644 --- a/hive-core-agent-sock/src/lib.rs +++ b/hive-core-agent-sock/src/lib.rs @@ -11,7 +11,6 @@ use hive_sh4re::{ CancelLooseEndKind, ContainerInfo, DeliveredMessage, InboxRow, JournalPriority, LooseEnd, MatrixIdentity, ReminderStats, ReminderTiming, SchedulePromptPayload, WireSchedule, }; -use hive_types::Ident; use serde::{Deserialize, Serialize}; /// serde `default` helper for `Response::AgentMeta::running` (absent = true). @@ -74,7 +73,7 @@ pub enum Request { #[serde(default)] ttl_seconds: Option, #[serde(default)] - to: Option, + to: Option, }, /// Answer a question previously routed to this agent via /// `HelperEvent::QuestionAsked`. Authorised callers + threading @@ -140,7 +139,7 @@ pub enum Request { /// `docs/conventions.md::Agent metadata`. GetAgentMeta { #[serde(default, skip_serializing_if = "Option::is_none")] - name: Option, + name: Option, }, /// Cancel an open thread the agent owns. Authorisation + /// per-kind semantics in diff --git a/hive-sh4re/Cargo.toml b/hive-sh4re/Cargo.toml index 3338ed51..613b0fa7 100644 --- a/hive-sh4re/Cargo.toml +++ b/hive-sh4re/Cargo.toml @@ -8,7 +8,6 @@ workspace = true [dependencies] chrono.workspace = true -hive-types.workspace = true schemars.workspace = true serde.workspace = true diff --git a/hive-sh4re/src/lib.rs b/hive-sh4re/src/lib.rs index 56dfef38..c8bf4c15 100644 --- a/hive-sh4re/src/lib.rs +++ b/hive-sh4re/src/lib.rs @@ -1,7 +1,6 @@ //! Wire types shared between `hive-c0re` and the in-container harness. use chrono::{DateTime, Utc}; -use hive_types::Ident; use serde::{Deserialize, Serialize}; pub mod assets; @@ -53,7 +52,7 @@ pub fn pending_hint(remaining: u64) -> String { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Approval { pub id: i64, - pub agent: Ident, + pub agent: String, #[serde(default)] pub kind: ApprovalKind, /// Kind-specific payload (git sha / inputs array / schedule @@ -155,7 +154,7 @@ pub struct ReminderStats { /// A logical message between agents. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Message { - pub from: Ident, + pub from: String, pub to: String, pub body: String, /// Optional broker row-id of the message this is a reply to. @@ -442,25 +441,6 @@ 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