rebuild_queue: per-entry step label + worker phase annotations (#437)

This commit is contained in:
damocles 2026-05-26 22:47:59 +02:00 committed by Mara
commit a286ae777c
4 changed files with 159 additions and 7 deletions

View file

@ -108,6 +108,7 @@ pub async fn approve(coord: Arc<Coordinator>, id: i64) -> Result<()> {
/// + the lifecycle event (`Rebuilt` / `Spawned` for first-spawn).
pub async fn run_approval_apply_commit(
coord: &Arc<Coordinator>,
queue_entry_id: Option<u64>,
approval_id: i64,
) -> Result<()> {
let approval = fetch_approval_for_worker(coord, approval_id, ApprovalKind::ApplyCommit)?;
@ -115,6 +116,7 @@ pub async fn run_approval_apply_commit(
let applied_dir = Coordinator::agent_applied_dir(&approval.agent);
let claude_dir = Coordinator::agent_claude_dir(&approval.agent);
let notes_dir = Coordinator::agent_notes_dir(&approval.agent);
coord.set_queue_step(queue_entry_id, "apply commit");
let (result, terminal_tag, is_first_spawn) = run_apply_commit(
coord,
&approval,
@ -122,12 +124,15 @@ pub async fn run_approval_apply_commit(
&applied_dir,
&claude_dir,
&notes_dir,
queue_entry_id,
)
.await;
coord.set_queue_step(queue_entry_id, "forge push");
if let Err(e) = crate::forge::push_config(&approval.agent).await {
tracing::warn!(agent = %approval.agent, error = ?e, "forge: push_config after apply failed");
}
if is_first_spawn && result.is_ok() {
coord.set_queue_step(queue_entry_id, "first-spawn forge bootstrap");
forge_after_first_spawn(coord, &approval.agent).await;
}
// `finish_approval` returns the original `result` so the queue
@ -175,10 +180,12 @@ async fn run_approval_schedule_prompt(
/// `inputs` — the queue copy is for dashboard display only.
pub async fn run_approval_update_meta_inputs(
coord: &Arc<Coordinator>,
queue_entry_id: Option<u64>,
approval_id: i64,
) -> Result<()> {
let approval = fetch_approval_for_worker(coord, approval_id, ApprovalKind::UpdateMetaInputs)?;
let inputs: Vec<String> = serde_json::from_str(&approval.commit_ref).unwrap_or_default();
coord.set_queue_step(queue_entry_id, "nix flake update");
let result = crate::meta::lock_update(&inputs).await;
finish_approval(coord, &approval, result, None, false)
}
@ -188,7 +195,11 @@ pub async fn run_approval_update_meta_inputs(
/// `lifecycle::spawn` (the deprecated direct-spawn path). Synchronous
/// in the queue worker — the previous `tokio::spawn` wrapper is gone
/// (the queue worker itself is the async task).
pub async fn run_approval_spawn(coord: &Arc<Coordinator>, approval_id: i64) -> Result<()> {
pub async fn run_approval_spawn(
coord: &Arc<Coordinator>,
queue_entry_id: Option<u64>,
approval_id: i64,
) -> Result<()> {
let approval = fetch_approval_for_worker(coord, approval_id, ApprovalKind::Spawn)?;
let agent_dir = coord.ensure_runtime(&approval.agent)?;
let proposed_dir = Coordinator::agent_proposed_dir(&approval.agent);
@ -199,6 +210,7 @@ pub async fn run_approval_spawn(coord: &Arc<Coordinator>, approval_id: i64) -> R
// the worker is doing the actual nixos-container create. Auto-clears
// on the function's scope exit (success or panic).
let _guard = coord.transient_guard(&approval.agent, TransientKind::Spawning);
coord.set_queue_step(queue_entry_id, "lifecycle::spawn");
let result = lifecycle::spawn(
&approval.agent,
&coord.hyperhive_flake,
@ -213,15 +225,19 @@ pub async fn run_approval_spawn(coord: &Arc<Coordinator>, approval_id: i64) -> R
)
.await;
if result.is_ok() {
coord.set_queue_step(queue_entry_id, "forge user");
if let Err(e) = crate::forge::ensure_user_for(&approval.agent).await {
tracing::warn!(agent = %approval.agent, error = ?e, "forge: ensure_user after spawn failed");
}
coord.set_queue_step(queue_entry_id, "forge config repo");
if let Err(e) = crate::forge::ensure_config_repo(&approval.agent).await {
tracing::warn!(agent = %approval.agent, error = ?e, "forge: ensure_config_repo after spawn failed");
}
coord.set_queue_step(queue_entry_id, "forge push");
if let Err(e) = crate::forge::push_config(&approval.agent).await {
tracing::warn!(agent = %approval.agent, error = ?e, "forge: push_config after spawn failed");
}
coord.set_queue_step(queue_entry_id, "forge meta access");
if let Some(core_token) = crate::forge::core_token()
&& let Err(e) = crate::forge::meta_read_access(&approval.agent, &core_token).await
{
@ -418,6 +434,7 @@ async fn run_apply_commit(
applied_dir: &std::path::Path,
claude_dir: &std::path::Path,
notes_dir: &std::path::Path,
queue_entry_id: Option<u64>,
) -> (Result<()>, Option<String>, bool) {
let id = approval.id;
let proposal_ref = format!("refs/tags/proposal/{id}");
@ -453,6 +470,7 @@ async fn run_apply_commit(
}
};
coord.set_queue_step(queue_entry_id, "plant tags");
if let Err(e) = lifecycle::git_tag(applied_dir, &format!("approved/{id}"), &proposal_ref).await
{
return (
@ -470,6 +488,7 @@ async fn run_apply_commit(
);
}
coord.set_queue_step(queue_entry_id, "fast-forward applied/main");
// Fast-forward applied/main to proposal/<id> + sync the working
// tree. Meta input pins `?ref=main`, so this is what makes nix
// re-lock to the proposal commit on the prepare_deploy step
@ -497,6 +516,7 @@ async fn run_apply_commit(
// before prepare_deploy can update its input lock (which won't
// exist yet if this is the agent's first deploy).
if is_first_spawn {
coord.set_queue_step(queue_entry_id, "meta sync_agents (first spawn)");
let agents = match lifecycle::agents_for_meta_listing_with(&approval.agent).await {
Ok(a) => a,
Err(e) => {
@ -531,6 +551,7 @@ async fn run_apply_commit(
}
}
coord.set_queue_step(queue_entry_id, "meta prepare_deploy");
// Phase 1 of the meta two-phase deploy: relock without committing.
if let Err(e) = crate::meta::prepare_deploy(&approval.agent).await {
let _ = lifecycle::git_update_ref(applied_dir, "refs/heads/main", &prev_main_sha).await;
@ -542,6 +563,7 @@ async fn run_apply_commit(
);
}
coord.set_queue_step(queue_entry_id, "nixos-container update");
// Container-level rebuild (or first-time create) against meta#<name>.
let build_result = lifecycle::rebuild_no_meta(
&approval.agent,
@ -554,6 +576,7 @@ async fn run_apply_commit(
match build_result {
Ok(()) => {
coord.set_queue_step(queue_entry_id, "finalize deploy");
let tag = format!("deployed/{id}");
if let Err(e) = lifecycle::git_tag(applied_dir, &tag, &proposal_ref).await {
tracing::warn!(agent = %approval.agent, %id, error = ?e, "plant deployed tag failed");