diff --git a/swarm-controller/src/main.rs b/swarm-controller/src/main.rs index 20a7c509..3d8bd633 100644 --- a/swarm-controller/src/main.rs +++ b/swarm-controller/src/main.rs @@ -65,24 +65,12 @@ enum SwarmNodeKind { /// Seed `repo` with `agent.nix` + `flake.nix`. See /// `forge::Client::seed_agent_config`. /// - /// `hive` is the **address** this agent's creation was aimed at — the - /// hive a deploy message gets sent to over the queue — and not a - /// property of the agent. Seeding does not use it: it rides on the - /// graph because that is the only thing carrying the operator's choice - /// forward from the API boundary, where it was checked against the - /// roster once. The node that routes on it is the deploy node in the - /// deploy-coordination work; until that lands the field is carried and - /// rendered, not acted on. - /// - /// Carrying the validated value rather than re-reading the roster at - /// node time is deliberate — the roster is loaded at startup, and a - /// node that looked it up again would be answering a question the - /// operator already answered. - InitAgentConfigRepo { - repo: String, - agent: String, - hive: String, - }, + /// Deliberately carries no hive: seeding a config repo is the same + /// work whichever hive the agent is bound for, and an agent's config + /// states nothing about where it runs. The hive is an address the + /// swarm routes a deploy message to — it belongs on the node that + /// sends that message, not on this one. + InitAgentConfigRepo { repo: String, agent: String }, } impl hive_jobq_wire::WireNode for SwarmNodeKind { @@ -103,16 +91,13 @@ impl hive_jobq_wire::WireNode for SwarmNodeKind { SwarmNodeKind::CreateRepo { repo } => { serde_json::json!({ "repo": repo }) } - SwarmNodeKind::AddRepoMember { repo, agent } => { + // Same rendering, and that is not a coincidence to be split + // apart later: both nodes act on one repo for one agent, and + // `label()` is what tells a viewer which of the two it is. + SwarmNodeKind::AddRepoMember { repo, agent } + | SwarmNodeKind::InitAgentConfigRepo { repo, agent } => { serde_json::json!({ "repo": repo, "agent": agent }) } - // Rendered with `hive` because it is the one place the graph - // states where this creation was aimed — nothing persists it, - // so a viewer watching the job is the only reader there is - // until the deploy node routes on it. - SwarmNodeKind::InitAgentConfigRepo { repo, agent, hive } => { - serde_json::json!({ "repo": repo, "agent": agent, "hive": hive }) - } } } } @@ -191,10 +176,7 @@ async fn run_swarm_node( Err(e) => Outcome::Failed(format!("{e:#}")), }, }, - // `hive` is deliberately not destructured: seeding writes no hive - // into the agent's config, so this node carries the address without - // consuming it. See the variant's doc. - SwarmNodeKind::InitAgentConfigRepo { repo, agent, .. } => match deps.forge { + SwarmNodeKind::InitAgentConfigRepo { repo, agent } => match deps.forge { None => Outcome::Failed( "no forge configured on this host (SWARM_CONTROLLER_FORGE_URL / \ SWARM_CONTROLLER_FORGE_TOKEN_FILE unset)" @@ -591,6 +573,13 @@ async fn get_hives_status( /// would have been the friendlier migration and would have left every /// creation in the state this endpoint exists to avoid — one nothing can /// address. +/// +/// Validated here and carried no further: none of the nodes this endpoint +/// queues talks to a hive, so none of them needs the address. It reaches +/// its consumer when the node that *sends* a deploy message exists, and +/// that node takes it from this field. Settling the request shape now is +/// the point — it is the breaking half, and doing it once is cheaper for +/// every caller than doing it again later. #[derive(Clone, Debug, Deserialize, ToSchema)] struct CreateAgentRequest { name: String, @@ -701,7 +690,7 @@ async fn create_agent( }) .after_ok(create_repo); let _init_config = b - .node(SwarmNodeKind::InitAgentConfigRepo { repo, agent, hive }) + .node(SwarmNodeKind::InitAgentConfigRepo { repo, agent }) .after_ok(create_repo); vec![create_identity.guid()] })