refactor(#2439): build hive-wide stop/start/restart DAGs dynamically

Hive-wide `stop` / `start` / `restart` emit ONE DAG with a per-agent
subgraph each (concurrent on their own leases) instead of N DAGs — and each
subgraph is now built dynamically from the agent's live running state rather
than a fixed template shape:

- online agent: the full stop→reconcile (restart: stop-for-update→reconcile)
  chain; `graceful` prepends signal→drain.
- offline agent: just `SetWanted → Reconcile` (nothing to quiesce/stop; a
  restart of a down agent is really a start).

The head `SetWanted` (intent) and tail `Reconcile` (convergence guarantee)
are always present; only the mechanical `Signal`/`Drain`/`StopForUpdate`
nodes are state-conditional. Keeping `Reconcile` in every shape closes the
TOCTOU window — a race-up between the `is_running` read and node exec is
still converged in-DAG (with `StopForUpdate`-noop as the backstop) — with no
reliance on an external reconcile sweep.

The state-aware assembly needs an async `is_running` read, so it moves out
of the pure/sync `templates.rs` into `submit.rs`, layered as pure
`*_chain(running)` → pure `*_spec(targets)` (the unit-test seam) → async
`*_many` (reads live state + submits). `templates.rs` keeps only the shared
pure primitives (`node`/`after_ok`/`rebuild_nodes`).

Callers await the now-async submit fns (server, dashboard, socket_server).
Tests exercise both the online and offline shapes via the pure `*_spec`
seam. docs/coordinator.md shapes updated.
This commit is contained in:
atlas 2026-07-14 23:34:56 +02:00
commit 860484a193
8 changed files with 574 additions and 335 deletions

View file

@ -65,7 +65,8 @@ pub(super) async fn post_kill(
&logical,
Source::Manual,
"manual via dashboard graceful stop".to_owned(),
);
)
.await;
return (StatusCode::OK, "ok").into_response();
}
// Manager is stoppable from the dashboard like any other
@ -83,7 +84,8 @@ pub(super) async fn post_kill(
&logical,
Source::Manual,
"manual via dashboard stop".to_owned(),
);
)
.await;
(StatusCode::OK, "ok").into_response()
}
@ -100,7 +102,8 @@ pub(super) async fn post_restart(
&logical,
Source::Manual,
"manual via dashboard ↺ R3START button".to_owned(),
);
)
.await;
(StatusCode::OK, "ok").into_response()
}
@ -117,7 +120,8 @@ pub(super) async fn post_start(
&logical,
Source::Manual,
"manual via dashboard start".to_owned(),
);
)
.await;
(StatusCode::OK, "ok").into_response()
}