swarmctl decoded POST /api/agents and POST /api/agents/{name}/forge-token
into hand-written copies of the controller's private response structs.
Both now live in swarm-controller-api, so a field renamed on one side is a
compile error on the other. Wire format and OpenAPI schema are unchanged.
The request side stays mirrored: CreateAgentRequest carries inference
settings and an ArchetypeRef (swarm-queue-client types) and Permission
(hive-sh4re), and the permissions body is a list of Permission. Where
those types live is #5471's decision.
Files:
- swarm-controller-api/src/agents.rs, lib.rs: the two responses, with
round-trip tests whose literals come from the tests that move here.
- swarm-controller/src/main.rs: imports them; the struct definitions and
create_agent_warning_tests (now in the api crate) go.
- swarmctl/src/agent.rs: imports them; the mirrors and their decode tests
go, and the module doc says only the request is mirrored now.
- swarmctl/src/permissions.rs: its doc pointed at agent.rs for why it
mirrors; that reason no longer covers Permissions, so it gives its own.
- swarmctl/README.md, swarm-controller-api/README.md, CLAUDE.md: which
bodies are shared and which are mirrored.
Part of #5472
78 lines
2.8 KiB
Rust
78 lines
2.8 KiB
Rust
//! `POST /api/agents` and `POST /api/agents/{name}/forge-token`.
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
/// Where the queued job landed — a caller polls `/api/jobq/graph` (or
|
|
/// `?states=`) with this id to watch it settle, same as every other job
|
|
/// kind this daemon will ever queue.
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[cfg_attr(feature = "schema", derive(utoipa::ToSchema))]
|
|
pub struct CreateAgentResponse {
|
|
pub node_id: u64,
|
|
/// Name collisions that were **allowed through**, phrased for the
|
|
/// operator who just chose the name.
|
|
///
|
|
/// The creation still happened — this is the "loud warning now, refuse
|
|
/// after a grace period" step, so a caller that ignores this field gets
|
|
/// exactly today's behaviour. It rides on the response rather than
|
|
/// living only in the daemon's log because the person who can still fix
|
|
/// the name in one keystroke is the one holding this response, and they
|
|
/// are not reading the journal.
|
|
///
|
|
/// Empty (and omitted from the JSON) in the normal case, so a client
|
|
/// that never looks at it is not handed an empty array to reason about.
|
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
|
pub warnings: Vec<String>,
|
|
}
|
|
|
|
/// Success body of `POST /api/agents/{name}/forge-token`.
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[cfg_attr(feature = "schema", derive(utoipa::ToSchema))]
|
|
pub struct MintAgentForgeTokenResponse {
|
|
/// The queued node, so a caller can follow it in the job view.
|
|
pub node_id: u64,
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::{CreateAgentResponse, MintAgentForgeTokenResponse};
|
|
|
|
/// No warnings means no `warnings` key, and a body without one still
|
|
/// decodes: that is the normal response.
|
|
#[test]
|
|
fn a_create_response_round_trips() {
|
|
for (resp, wire) in [
|
|
(
|
|
CreateAgentResponse {
|
|
node_id: 7,
|
|
warnings: vec![],
|
|
},
|
|
r#"{"node_id":7}"#,
|
|
),
|
|
(
|
|
CreateAgentResponse {
|
|
node_id: 7,
|
|
warnings: vec!["name is reserved".to_owned()],
|
|
},
|
|
r#"{"node_id":7,"warnings":["name is reserved"]}"#,
|
|
),
|
|
] {
|
|
assert_eq!(serde_json::to_string(&resp).unwrap(), wire);
|
|
assert_eq!(
|
|
serde_json::from_str::<CreateAgentResponse>(wire).unwrap(),
|
|
resp
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn a_forge_token_response_round_trips() {
|
|
let resp = MintAgentForgeTokenResponse { node_id: 7 };
|
|
let wire = r#"{"node_id":7}"#;
|
|
assert_eq!(serde_json::to_string(&resp).unwrap(), wire);
|
|
assert_eq!(
|
|
serde_json::from_str::<MintAgentForgeTokenResponse>(wire).unwrap(),
|
|
resp
|
|
);
|
|
}
|
|
}
|