Add upgrade all chat command with drain check and convergence verify

- `upgrade all` sweeps every registered sled in sequence, streaming
  per-sled phase markers and reporting a summary.
- Binary source is now the gateway's own artifact store via
  host.docker.internal (was: unresolvable `gateway` hostname serving
  the gateway's macOS binary to Linux sleds — would have bricked them).
- Sleds with active claude processes are skipped, never killed.
- After reconnect, /api/version git_hash is compared against the
  published artifact's .hash sidecar; divergence is reported loudly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019fHdm92yjvguPi2LiXfLB9
This commit is contained in:
Timmy
2026-07-15 16:22:11 +01:00
co-authored by Claude Fable 5
parent 07b9e1605d
commit 18ba57a7b0
4 changed files with 250 additions and 70 deletions
@@ -537,7 +537,7 @@ pub(in crate::chat::transport::matrix::bot) async fn on_room_message(
let bot_sent = Arc::clone(&ctx.bot_sent_event_ids);
let room = room_id_str.clone();
let response = super::super::super::sled_upgrade::handle_sled_upgrade(
let outcome = super::super::super::sled_upgrade::handle_sled_upgrade(
&project,
store,
ctx.gateway_port,
@@ -557,6 +557,53 @@ pub(in crate::chat::transport::matrix::bot) async fn on_room_message(
},
)
.await;
let response = format!("{project}: {outcome}");
let html = markdown_to_html(&response);
if let Ok(msg_id) = ctx
.transport
.send_message(&room_id_str, &response, &html)
.await
&& let Ok(event_id) = msg_id.parse()
{
ctx.bot_sent_event_ids.lock().await.insert(event_id);
}
} else {
let msg = "Gateway projects store unavailable — cannot upgrade sled.";
let html = markdown_to_html(msg);
if let Ok(msg_id) = ctx.transport.send_message(&room_id_str, msg, &html).await
&& let Ok(event_id) = msg_id.parse()
{
ctx.bot_sent_event_ids.lock().await.insert(event_id);
}
}
}
super::super::super::sled_upgrade::UpgradeCommand::UpgradeAll => {
slog!("[matrix-bot] Handling 'upgrade all' from {sender}");
if let Some(ref store) = ctx.gateway_projects_store {
let transport = Arc::clone(&ctx.transport);
let bot_sent = Arc::clone(&ctx.bot_sent_event_ids);
let room = room_id_str.clone();
let response = super::super::super::sled_upgrade::handle_upgrade_all(
store,
ctx.gateway_port,
|phase_msg| {
let transport = Arc::clone(&transport);
let bot_sent = Arc::clone(&bot_sent);
let room = room.clone();
async move {
let html = markdown_to_html(&phase_msg);
if let Ok(msg_id) =
transport.send_message(&room, &phase_msg, &html).await
&& let Ok(event_id) = msg_id.parse()
{
bot_sent.lock().await.insert(event_id);
}
}
},
)
.await;
let html = markdown_to_html(&response);
if let Ok(msg_id) = ctx
@@ -568,7 +615,7 @@ pub(in crate::chat::transport::matrix::bot) async fn on_room_message(
ctx.bot_sent_event_ids.lock().await.insert(event_id);
}
} else {
let msg = "Gateway projects store unavailable — cannot upgrade sled.";
let msg = "Gateway projects store unavailable — cannot upgrade sleds.";
let html = markdown_to_html(msg);
if let Ok(msg_id) = ctx.transport.send_message(&room_id_str, msg, &html).await
&& let Ok(event_id) = msg_id.parse()