Watch
0
0
Fork
You've already forked hyperhive
0

swarm-controller: split main.rs into jobq, routes and startup modules

no behaviour change: a pure move of items and tests into new modules, plus
visibility edits (pub(crate)) and path updates in sibling modules that named
crate-root items. same routes, same OpenAPI, 561 tests before and after.

why: #5470, new features stop landing in one 6.4k-line file by default.

files outside swarm-controller/src: docs/agent-lifecycle/agent-roster.md,
swarm-ui JobsPage.tsx and AgentsPage.tsx, nix/host-modules/swarm-controller.nix
and swarm-grafana.nix hold comments that named main.rs for items that moved.
This commit is contained in:
atlas 2026-10-18 04:53:59 +02:00
commit 738dd1c95b
37 changed files with 6854 additions and 6680 deletions

View file

@ -9,8 +9,8 @@ use problem_details::ProblemDetails;
use swarm_queue_client::agent_archetype::{self, ArchetypeRef};
use swarm_queue_client::archetypes::SourceRecord;
use crate::routes::{AppState, error_problem};
use crate::schedules::{queue, read_or_default, write_revision};
use crate::{AppState, error_problem};
/// How many times a losing writer re-reads and re-applies, as in
/// `wanted::MAX_ATTEMPTS`.

View file

@ -22,7 +22,7 @@ use serde::Serialize;
use swarm_queue_client::{AgentControl, AgentControlReply};
use utoipa::ToSchema;
use crate::{AppState, error_problem};
use crate::routes::{AppState, error_problem};
/// What became of one command.
#[derive(Debug, PartialEq, Eq)]

View file

@ -31,7 +31,7 @@ use serde::{Deserialize, Serialize};
use swarm_queue_client::AgentInput;
use utoipa::ToSchema;
use crate::{AppState, error_problem};
use crate::routes::{AppState, error_problem};
/// The header the gateway names the session's user in.
const SENDER_HEADER: &str = "remote-user";

View file

@ -19,8 +19,8 @@ use serde::{Deserialize, Serialize};
use swarm_queue_client::agent_limits::{self, AgentCaps};
use utoipa::ToSchema;
use crate::routes::{AppState, error_problem, wanted_error_status};
use crate::schedules::{live, queue, read_or_default, refused, write_revision};
use crate::{AppState, error_problem, wanted_error_status};
/// How many times a losing writer re-reads and re-applies, as in
/// `wanted::MAX_ATTEMPTS`.
@ -478,9 +478,9 @@ pub(crate) mod tests {
}
/// One hive, `pr1ma`, on `client`.
pub(crate) fn state(client: &async_nats::Client) -> crate::AppState {
crate::AppState {
hives: Arc::new(vec![crate::HiveEntry {
pub(crate) fn state(client: &async_nats::Client) -> crate::routes::AppState {
crate::routes::AppState {
hives: Arc::new(vec![crate::routes::hives::HiveEntry {
name: "pr1ma".to_owned(),
domain: "pr1ma.example".to_owned(),
dashboard: "pr1ma.hives.example".to_owned(),
@ -516,7 +516,7 @@ pub(crate) mod tests {
}
/// A state on a real server whose `pr1ma` declares `atlas`.
pub(crate) async fn declared(client: &async_nats::Client) -> crate::AppState {
pub(crate) async fn declared(client: &async_nats::Client) -> crate::routes::AppState {
WantedWriter::new(client.clone())
.set("pr1ma", "atlas", AgentState::Up)
.await
@ -532,7 +532,7 @@ pub(crate) mod tests {
}
async fn put(
state: &crate::AppState,
state: &crate::routes::AppState,
agent: &str,
value: Caps,
) -> Result<axum::Json<Caps>, problem_details::ProblemDetails> {
@ -544,7 +544,7 @@ pub(crate) mod tests {
.await
}
async fn get(state: &crate::AppState, agent: &str) -> Caps {
async fn get(state: &crate::routes::AppState, agent: &str) -> Caps {
get_agent_limits(State(state.clone()), Path(agent.to_owned()))
.await
.expect("caps are read")
@ -662,7 +662,7 @@ pub(crate) mod tests {
let _ = put(&state, "atlas", caps(Some("400%"), Some("8G")))
.await
.expect("cap atlas");
crate::build_app(state)
crate::startup::build_app(state)
}
async fn put_raw(app: &axum::Router, body: &'static str) -> StatusCode {

View file

@ -23,9 +23,9 @@ use serde::{Deserialize, Serialize};
use swarm_queue_client::agent_permissions::{self, AgentPermissions, Import, ImportReply};
use utoipa::ToSchema;
use crate::routes::{AppState, error_problem, wanted_error_status};
use crate::schedules::{live, queue, read_or_default, refused, write, write_revision};
use crate::wanted::WantedWriter;
use crate::{AppState, error_problem, wanted_error_status};
/// How many times a losing writer re-reads and re-applies, as in
/// `wanted::MAX_ATTEMPTS`.
@ -549,7 +549,7 @@ mod tests {
}
async fn put(
state: &crate::AppState,
state: &crate::routes::AppState,
agent: &str,
value: Permissions,
) -> Result<axum::Json<Permissions>, problem_details::ProblemDetails> {
@ -561,7 +561,7 @@ mod tests {
.await
}
async fn get(state: &crate::AppState, agent: &str) -> Permissions {
async fn get(state: &crate::routes::AppState, agent: &str) -> Permissions {
get_agent_permissions(State(state.clone()), Path(agent.to_owned()))
.await
.expect("read")
@ -641,7 +641,7 @@ mod tests {
let client = nats.client().await;
let state = crate::agent_limits::tests::declared(&client).await;
let _ = put(&state, "atlas", granted()).await.expect("set");
let app = crate::build_app(state);
let app = crate::startup::build_app(state);
for (body, status) in [
(
@ -803,9 +803,9 @@ mod tests {
let client = nats.client().await;
let state = crate::agent_limits::tests::declared(&client).await;
let create = |name: &str, permissions| {
crate::create_agent(
crate::routes::agents::create_agent(
State(state.clone()),
axum::Json(crate::CreateAgentRequest {
axum::Json(crate::routes::agents::CreateAgentRequest {
name: name.to_owned(),
hive: "pr1ma".to_owned(),
limits: None,
@ -855,18 +855,18 @@ mod tests {
let state = crate::agent_limits::tests::declared(&client).await;
let _ = put(&state, "atlas", granted()).await.expect("set");
let _ = crate::set_agent_state(
let _ = crate::routes::agents::set_agent_state(
State(state.clone()),
Path(("pr1ma".to_owned(), "atlas".to_owned())),
axum::Json(crate::SetAgentStateRequest {
axum::Json(crate::routes::agents::SetAgentStateRequest {
state: swarm_queue_client::wanted::AgentState::Destroyed,
}),
)
.await
.expect("destroyed");
let _ = crate::create_agent(
let _ = crate::routes::agents::create_agent(
State(state.clone()),
axum::Json(crate::CreateAgentRequest {
axum::Json(crate::routes::agents::CreateAgentRequest {
name: "atlas".to_owned(),
hive: "pr1ma".to_owned(),
limits: None,

View file

@ -29,7 +29,7 @@ use axum::extract::{Path, State};
use axum::response::sse::{Event, KeepAlive, Sse};
use futures_util::{Stream, StreamExt as _};
use crate::AppState;
use crate::routes::AppState;
#[utoipa::path(
get,
@ -60,19 +60,21 @@ pub(crate) async fn stream_agent_state(
Path(agent): Path<String>,
) -> Result<Sse<impl Stream<Item = Result<Event, Infallible>>>, problem_details::ProblemDetails> {
let Some(status) = state.status.as_ref() else {
return Err(crate::error_problem(
return Err(crate::routes::error_problem(
axum::http::StatusCode::SERVICE_UNAVAILABLE,
"no swarm queue is configured on this host",
));
};
let Some(agent_status) = state.agent_status.as_ref() else {
return Err(crate::error_problem(
return Err(crate::routes::error_problem(
axum::http::StatusCode::SERVICE_UNAVAILABLE,
"no agent-status reader is configured on this host",
));
};
let agent = hive_types::Ident::parse(&agent)
.map_err(|reason| crate::error_problem(axum::http::StatusCode::BAD_REQUEST, reason))?
.map_err(|reason| {
crate::routes::error_problem(axum::http::StatusCode::BAD_REQUEST, reason)
})?
.into_string();
// One-agent "roster": reuses `AgentStatusReader::view`'s already-tested
@ -84,12 +86,12 @@ pub(crate) async fn stream_agent_state(
.map_err(|e| {
let detail = format!("{e:#}");
tracing::warn!(%agent, error = %detail, "state stream: reading agent status failed");
crate::error_problem(axum::http::StatusCode::INTERNAL_SERVER_ERROR, &detail)
crate::routes::error_problem(axum::http::StatusCode::INTERNAL_SERVER_ERROR, &detail)
})?
.into_iter()
.find_map(|row| row.hive)
.ok_or_else(|| {
crate::error_problem(
crate::routes::error_problem(
axum::http::StatusCode::NOT_FOUND,
&format!("{agent:?} has never reported to the swarm"),
)
@ -101,7 +103,7 @@ pub(crate) async fn stream_agent_state(
// against a queue that is not there — the honest answer is 503, the
// same shape every other queue-backed route here already uses.
swarm_queue_client::ensure_connected(&client).map_err(|e| {
crate::error_problem(
crate::routes::error_problem(
axum::http::StatusCode::SERVICE_UNAVAILABLE,
&swarm_queue_client::chain(&e),
)

View file

@ -20,7 +20,7 @@ use swarm_queue_client::AgentControl;
use swarm_queue_client::agents_md::{Apply, ApplyReply, Mirror};
use crate::agent_control::{ControlSink, Outcome};
use crate::{AppState, error_problem, wanted_error_status};
use crate::routes::{AppState, error_problem, wanted_error_status};
/// Reads the `agents-md` bucket, the way [`crate::agent_icon::AgentIconReader`]
/// reads the icon bucket, and creates it: the agents writing it cannot.

View file

@ -23,7 +23,7 @@ use problem_details::ProblemDetails;
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
use crate::{AppState, error_problem};
use crate::routes::{AppState, error_problem};
/// The env var `swarm-controller.nix` sets.
const URL_ENV: &str = "SWARM_CONTROLLER_GRAFANA_URL";
@ -282,12 +282,12 @@ mod tests {
}
async fn get_alerts_with(grafana: Option<Grafana>) -> (StatusCode, serde_json::Value) {
let (mut state, _) = crate::tests::state_with_roster();
let (mut state, _) = crate::routes::tests::state_with_roster();
state.alerts = grafana.map(Arc::new);
let req = Request::get("/api/alerts")
.body(Body::empty())
.expect("a request");
let res = crate::build_app(state)
let res = crate::startup::build_app(state)
.oneshot(req)
.await
.expect("infallible");

View file

@ -21,8 +21,9 @@ use swarm_wire::builder::{
};
use utoipa::ToSchema;
use crate::routes::{AppState, error_problem};
use crate::schedules::{queue, read_or_default, refused, write};
use crate::{AppState, agent_limits, agent_permissions, error_problem, inference};
use crate::{agent_limits, agent_permissions, inference};
/// How often the builder is asked. Its source only changes when its host is
/// redeployed, so this bounds how long a redeploy takes to reach the create

View file

@ -18,8 +18,8 @@ use problem_details::ProblemDetails;
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
use crate::routes::{AppState, error_problem};
use crate::schedules::{read_with, write};
use crate::{AppState, error_problem};
/// The bucket holding the builds. Agents hold no grant on it.
pub const BUCKET: &str = "builder-runs";
@ -449,9 +449,9 @@ pub(crate) mod tests {
push(&*doc, &record("b")).await.expect("stored");
let get_at = |builds: Builds, path: &'static str| async move {
let (mut state, _) = crate::tests::state_with_roster();
let (mut state, _) = crate::routes::tests::state_with_roster();
state.builds = builds;
let res = crate::build_app(state)
let res = crate::startup::build_app(state)
.oneshot(Request::get(path).body(Body::empty()).expect("a request"))
.await
.expect("infallible");

View file

@ -24,7 +24,7 @@ use serde::{Deserialize, Serialize};
use swarm_wire::claude_login::{REPLY_DEADLINE, Reply, Request, SUBJECT};
use utoipa::ToSchema;
use crate::{AppState, error_problem};
use crate::routes::{AppState, error_problem};
/// The header the gateway names the session's user in.
const OPERATOR_HEADER: &str = "remote-user";

View file

@ -13,7 +13,8 @@ use swarm_secret_client::forge;
use utoipa::ToSchema;
use super::linked_accounts::link;
use super::{AppState, error_problem, swarm_hive};
use crate::routes::agents::swarm_hive;
use crate::routes::{AppState, error_problem};
/// The account to store for one agent's external forge.
///
@ -141,9 +142,9 @@ mod tests {
}
/// Bare-minimum `AppState`, as `matrix_account`'s tests build it.
fn state(store: &Arc<FakeStore>) -> super::super::AppState {
super::super::AppState {
hives: std::sync::Arc::new(vec![super::super::HiveEntry {
fn state(store: &Arc<FakeStore>) -> crate::routes::AppState {
crate::routes::AppState {
hives: std::sync::Arc::new(vec![crate::routes::hives::HiveEntry {
name: "pr1ma".to_owned(),
domain: "pr1ma.example".to_owned(),
dashboard: "pr1ma.hives.example".to_owned(),

View file

@ -13,7 +13,8 @@ use swarm_secret_client::github;
use utoipa::ToSchema;
use super::linked_accounts::link;
use super::{AppState, error_problem, swarm_hive};
use crate::routes::agents::swarm_hive;
use crate::routes::{AppState, error_problem};
/// The token to store for one agent.
///
@ -111,9 +112,9 @@ mod tests {
}
/// Bare-minimum `AppState`, as `forge_account`'s tests build it.
fn state(store: &Arc<FakeStore>) -> super::super::AppState {
super::super::AppState {
hives: std::sync::Arc::new(vec![super::super::HiveEntry {
fn state(store: &Arc<FakeStore>) -> crate::routes::AppState {
crate::routes::AppState {
hives: std::sync::Arc::new(vec![crate::routes::hives::HiveEntry {
name: "pr1ma".to_owned(),
domain: "pr1ma.example".to_owned(),
dashboard: "pr1ma.hives.example".to_owned(),

View file

@ -26,8 +26,8 @@ use serde::{Deserialize, Serialize};
use swarm_queue_client::agent_inference::{self, AgentInference, Effective, Effort, Source};
use utoipa::ToSchema;
use crate::routes::{AppState, error_problem, wanted_error_status};
use crate::schedules::{live, queue, read_or_default, refused, write_revision};
use crate::{AppState, error_problem, wanted_error_status};
/// How many times a losing writer re-reads and re-applies, as in
/// `wanted::MAX_ATTEMPTS`.

View file

@ -12,8 +12,8 @@ use axum::{
extract::{Path, State},
};
use super::{AppState, StatusUnavailable, error_problem};
use crate::forge;
use crate::routes::{AppState, StatusUnavailable, error_problem};
/// The 503 detail when the forge did not answer within
/// [`forge::FORGE_DEADLINE`].
@ -21,7 +21,7 @@ const DEADLINE_DETAIL: &str = "the forge did not answer within the deadline";
/// Repos with at least one open issue, as `owner/name` full names — the
/// data source for swarm-ui's repo-filter dropdown. Not scoped to
/// `forge::CONFIG_ORG` the way `main.rs`'s `get_config_prs` is: this
/// `forge::CONFIG_ORG` the way `routes/config_prs.rs`'s `get_config_prs` is: this
/// report deliberately covers the whole forge instance, not one org, since
/// the report itself is a general-purpose browsing tool rather than
/// something agent-config-specific.
@ -156,7 +156,7 @@ pub async fn get_issue_report(
///
/// `Client::issue_report` propagates through `anyhow`, so the concrete
/// [`forgejo_api::ForgejoError`] survives underneath but is not the type in
/// hand — downcasting back to it, same pattern `main.rs`'s
/// hand — downcasting back to it, same pattern `routes`'s
/// `wanted_error_status` uses for `swarm_queue_client::Error`. A 404 there
/// (`forge::is_repo_not_found`) means the repo itself doesn't exist;
/// anything else — including a 404 on some *other* call this function
@ -257,7 +257,7 @@ mod tests {
"/api/issue-report",
"/api/repos/o/r/issue-report",
] {
let (state, _) = crate::tests::state_with_roster();
let (state, _) = crate::routes::tests::state_with_roster();
assert!(
state
.forge
@ -268,7 +268,7 @@ mod tests {
);
let start = tokio::time::Instant::now();
let req = Request::get(uri).body(Body::empty()).expect("a request");
let res = crate::build_app(state)
let res = crate::startup::build_app(state)
.oneshot(req)
.await
.expect("infallible");

2398
swarm-controller/src/jobq.rs Normal file

File diff suppressed because it is too large Load diff

View file

@ -29,7 +29,8 @@ use serde::{Deserialize, Serialize};
use swarm_secret_client::{Error, SecretStore, forge, github, matrix};
use utoipa::{IntoParams, ToSchema};
use super::{AppState, error_problem, swarm_hive};
use crate::routes::agents::swarm_hive;
use crate::routes::{AppState, error_problem};
/// The host shown for a GitHub token. The store keeps none: a token is only
/// ever used against github.com.
@ -950,9 +951,9 @@ pub(crate) mod tests {
}
/// Bare-minimum `AppState`, as `matrix_account`'s tests build it.
fn state(store: &Arc<FakeStore>) -> super::super::AppState {
super::super::AppState {
hives: std::sync::Arc::new(vec![super::super::HiveEntry {
fn state(store: &Arc<FakeStore>) -> crate::routes::AppState {
crate::routes::AppState {
hives: std::sync::Arc::new(vec![crate::routes::hives::HiveEntry {
name: "pr1ma".to_owned(),
domain: "pr1ma.example".to_owned(),
dashboard: "pr1ma.hives.example".to_owned(),

File diff suppressed because it is too large Load diff

View file

@ -34,7 +34,8 @@ use swarm_secret_client::matrix;
use utoipa::ToSchema;
use super::linked_accounts::{AccountStore, Linking, link, refuse_linked};
use super::{AppState, error_problem, swarm_hive};
use crate::routes::agents::swarm_hive;
use crate::routes::{AppState, error_problem};
pub mod agent_token;
pub mod hive_sender;
@ -445,9 +446,9 @@ mod tests {
/// Bare-minimum `AppState` for a handler test: one hive, nothing
/// queue-backed, an empty in-memory job graph the endpoint under test
/// never touches, and `store` as the account store.
fn state(store: &Arc<FakeStore>) -> super::super::AppState {
super::super::AppState {
hives: std::sync::Arc::new(vec![super::super::HiveEntry {
fn state(store: &Arc<FakeStore>) -> crate::routes::AppState {
crate::routes::AppState {
hives: std::sync::Arc::new(vec![crate::routes::hives::HiveEntry {
name: "pr1ma".to_owned(),
domain: "pr1ma.example".to_owned(),
dashboard: "pr1ma.hives.example".to_owned(),

View file

@ -15,7 +15,7 @@ use utoipa::IntoParams;
use crate::forge::{self, OpenPull};
use crate::issue_report::issue_report_error_status;
use crate::review_classifier::split_repo;
use crate::{AppState, error_problem};
use crate::routes::{AppState, error_problem};
/// Whose open pull requests to list.
#[derive(Debug, Deserialize, IntoParams)]
@ -117,9 +117,9 @@ mod tests {
use tower::ServiceExt as _;
async fn get(uri: &str) -> (StatusCode, serde_json::Value) {
let (state, _) = crate::tests::state_with_roster();
let (state, _) = crate::routes::tests::state_with_roster();
let req = Request::get(uri).body(Body::empty()).expect("a request");
let res = crate::build_app(state)
let res = crate::startup::build_app(state)
.oneshot(req)
.await
.expect("infallible");

Some files were not shown because too many files have changed in this diff Show more