refactor(#2591): model a DAG as a container node — grouping side-tables become graph walks

This commit is contained in:
atlas 2026-07-20 22:33:43 +02:00 committed by mara
commit a78280feed
4 changed files with 362 additions and 324 deletions

View file

@ -100,76 +100,69 @@ pub(super) async fn run_node(coord: &Arc<Coordinator>, claim: &Claim) -> Result<
NodeKind::WritePermFile => run_write_perm_file(coord, claim, &ctx).await,
NodeKind::ApprovalDeploy => run_approval_deploy(coord, claim).await,
NodeKind::SetWanted { up } => run_set_wanted(coord, claim, *up),
NodeKind::ResolveApproval => run_resolve_approval(coord, claim).await,
NodeKind::EmitRebuilt => Ok(run_emit_rebuilt(coord, claim)),
NodeKind::RevertIntent => run_revert_intent(coord, claim).await,
// Pure grouping container — no work; completing it lets it reach
// `Finishing` so its child template nodes start. The DAG's terminal
// hook fires when the container itself rolls up terminal.
// hook fires (inline, via `run_terminal_hook`) when the container itself
// rolls up terminal — not as a scheduled node.
NodeKind::Dag { .. } => Ok(NodeOutput::default()),
}
}
/// Terminal hook (approval DAGs — spawn / opaque deploy): resolve the DAG's
/// approval row from its rolled-up outcome. Its own graph node, weak-dep on the
/// DAG tails, so it runs once everything has settled (any outcome, incl. a
/// cancel before starting — the fallback that resolves a queued-then-cancelled
/// approval whose node never ran). Always succeeds — a hook failure is logged
/// inside, not surfaced as a node failure.
async fn run_resolve_approval(coord: &Arc<Coordinator>, claim: &Claim) -> Result<NodeOutput> {
if let Some(terminal) = coord.job_queue.terminal_summary(claim.dag_id) {
crate::actions::resolve_approval_dag(coord, &terminal).await;
/// Run a settled DAG's inline terminal hook, dispatched off its rolled-up
/// summary — the container-terminal replacement for the old per-DAG hook node.
/// Always best-effort: a hook failure is logged inside, never surfaced.
pub(super) async fn run_terminal_hook(coord: &Arc<Coordinator>, terminal: &super::TerminalDag) {
match super::terminal_hook(terminal.template, terminal.approval_id) {
Some(super::HookKind::ResolveApproval) => {
crate::actions::resolve_approval_dag(coord, terminal).await;
}
Some(super::HookKind::EmitRebuilt) => emit_rebuilt(coord, terminal),
Some(super::HookKind::RevertIntent) => revert_intent(coord, terminal).await,
None => {}
}
Ok(NodeOutput::default())
}
/// Terminal hook (rebuild / perm-change DAGs): emit one `Rebuilt` manager event
/// per targeted agent — `ok` on `Done`, `!ok` on `Failed`, none on cancel.
fn run_emit_rebuilt(coord: &Arc<Coordinator>, claim: &Claim) -> NodeOutput {
if let Some(terminal) = coord.job_queue.terminal_summary(claim.dag_id) {
for agent in &terminal.agents {
match terminal.state {
State::Done => coord.notify_manager(&hive_sh4re::HelperEvent::Rebuilt {
agent: agent.clone(),
ok: true,
note: None,
sha: None,
tag: None,
}),
State::Failed => coord.notify_manager(&hive_sh4re::HelperEvent::Rebuilt {
agent: agent.clone(),
ok: false,
note: terminal.error.clone(),
sha: None,
tag: None,
}),
_ => {}
}
/// Rebuild / perm-change hook: emit one `Rebuilt` manager event per targeted
/// agent — `ok` on `Done`, `!ok` on `Failed`, none on cancel.
fn emit_rebuilt(coord: &Arc<Coordinator>, terminal: &super::TerminalDag) {
for agent in &terminal.agents {
match terminal.state {
State::Done => coord.notify_manager(&hive_sh4re::HelperEvent::Rebuilt {
agent: agent.clone(),
ok: true,
note: None,
sha: None,
tag: None,
}),
State::Failed => coord.notify_manager(&hive_sh4re::HelperEvent::Rebuilt {
agent: agent.clone(),
ok: false,
note: terminal.error.clone(),
sha: None,
tag: None,
}),
_ => {}
}
}
NodeOutput::default()
}
/// Terminal hook (power-op DAGs): on a *cancelled* DAG, revert each targeted
/// agent's `wanted` intent to its observed state — the operator's cancel means
/// "don't do it", so the intent snaps back instead of the flip executing as a
/// surprise side effect of some later reconcile. Noop on any non-cancelled
/// outcome. Always succeeds — a revert failure is logged, not surfaced.
async fn run_revert_intent(coord: &Arc<Coordinator>, claim: &Claim) -> Result<NodeOutput> {
if let Some(terminal) = coord.job_queue.terminal_summary(claim.dag_id)
&& terminal.state == State::Cancelled
{
for agent in &terminal.agents {
let running = crate::lifecycle::is_running(agent).await;
if let Err(e) = coord
.power
.set(agent, crate::power::Wanted::from_running(running))
{
tracing::warn!(%agent, error = ?e, "agent_power: cancel revert failed");
}
/// Power-op hook: on a *cancelled* DAG, revert each targeted agent's `wanted`
/// intent to its observed state — the operator's cancel means "don't do it", so
/// the intent snaps back instead of the flip executing as a surprise side effect
/// of some later reconcile. Noop on any non-cancelled outcome.
async fn revert_intent(coord: &Arc<Coordinator>, terminal: &super::TerminalDag) {
if terminal.state != State::Cancelled {
return;
}
for agent in &terminal.agents {
let running = crate::lifecycle::is_running(agent).await;
if let Err(e) = coord
.power
.set(agent, crate::power::Wanted::from_running(running))
{
tracing::warn!(%agent, error = ?e, "agent_power: cancel revert failed");
}
}
Ok(NodeOutput::default())
}
/// Write the agent's durable power intent — the DAG-node form of the old