job_queue: delete the cancelled-power-op intent revert

The revert hook is dead by construction, so it can only ever be wrong.

DAG state `Cancelled` has exactly one producer: `JobQueue::cancel`, which
refuses unless every work node is still `Pending`. A cancel *cascade*
(some node failed, downstream cancelled) rolls up `Failed` instead —
`dag_rollup` short-circuits on any failed subtree node. So on a DAG that
reaches `Cancelled`, no node ever executed: the `SetWanted` head provably
never ran and `wanted` still reads whatever the operator last set it to.

There is therefore nothing to revert, and `revert_intent` did not revert
anything — it wrote `Wanted::from_running(observed)`, i.e. the agent's
*observed* state, over an intent the DAG never touched. Harmless when
observed already matched, silent corruption otherwise: cancel a queued
start for an agent that is down but `wanted = Up` (crashed, or caught
mid-bounce) and the intent flips to `Offline`, leaving it
deliberately-stopped as far as reconcile and crash-watch are concerned.

The hook made sense when `set_wanted` was a pre-submit side effect
written before the DAG ran; moving it into the DAG as a node left the
hook vestigial.

Drop `HookKind::RevertIntent`, `revert_intent`, and the power-op arm of
`terminal_hook` — start / stop / graceful-stop now settle with no
terminal hook, same as restart always did. The test asserts the general
statement across restart/stop/start x graceful x running: stop and start
carry a `SetWanted` head, and cancelling them still fires no hook.
This commit is contained in:
atlas 2026-07-26 16:19:26 +02:00 committed by mara
commit 5c4a637941
4 changed files with 64 additions and 79 deletions

View file

@ -169,22 +169,18 @@ pub enum HookKind {
ResolveApproval,
/// Rebuild / perm-change: emit one `Rebuilt` manager event per agent.
EmitRebuilt,
/// Intent-writing power-op: on a *cancelled* DAG, revert each agent's
/// `wanted` intent. Only for templates that actually carry a `SetWanted`
/// head — reverting an intent a DAG never wrote invents one.
RevertIntent,
}
/// The terminal hook a DAG needs, from its template + approval id — or `None`
/// for a DAG with no terminal side effect (meta-update, boot, bare reconcile).
/// for a DAG with no terminal side effect (power-op, meta-update, boot, bare
/// reconcile).
///
/// Restart is deliberately **not** a `RevertIntent` template. `restart_chain`
/// writes no `SetWanted` (it bounces the container and lets the tail
/// `Reconcile` converge to the agent's existing intent), so there is nothing
/// for a cancel to revert — and `revert_intent` writes the *observed* state,
/// which for a down-but-`wanted = Up` agent (crashed, or mid-bounce) would
/// flip it to `Offline` and keep it down. Cancelling a restart must leave the
/// intent exactly as it was found.
/// A cancelled DAG deliberately gets **no** compensating hook. [`JobQueue::cancel`]
/// refuses unless every work node is still `Pending`, and a cancel *cascade*
/// rolls up `Failed` (see `dag_rollup`), never `Cancelled` — so on a
/// `Cancelled` DAG no node ever executed and there is nothing to undo. A power
/// op's `SetWanted` head provably never ran, so its intent is still whatever
/// the operator last set it to.
#[must_use]
pub fn terminal_hook(template: Template, approval_id: Option<i64>) -> Option<HookKind> {
if approval_id.is_some() {
@ -192,7 +188,6 @@ pub fn terminal_hook(template: Template, approval_id: Option<i64>) -> Option<Hoo
}
match template {
Template::Rebuild | Template::PermChange => Some(HookKind::EmitRebuilt),
Template::Start | Template::Stop | Template::GracefulStop => Some(HookKind::RevertIntent),
_ => None,
}
}