hive-c0re/hivectl/hive-agent: pause as a job-queue DAG node (closes #3056)

This commit is contained in:
damocles 2026-08-11 21:59:25 +02:00 committed by mara
commit 20a7a21053
13 changed files with 316 additions and 27 deletions

View file

@ -137,6 +137,46 @@ pub(crate) fn quiesce<'a>(builder: &'a JobBuilder, agent: &str, brace: Handle<'a
.after_ok(signal)
}
/// The pause quiesce pair — `PauseSignal` then `PauseDrain`: write the
/// pause marker, then wait for the harness to acknowledge it. Returns
/// the **group root** — the brace, not the tail. Unlike [`quiesce`],
/// nothing chains onto `PauseDrain` (there's no downstream
/// `Reconcile`-shaped node the way a stop has one), so the handle a
/// caller actually needs is the brace whose roll-up covers the whole
/// pair, same as [`super::power`]'s `stop_chain` returning `wanted`'s
/// guid rather than `quiesce`'s own returned `Drain` handle.
///
/// Unlike [`quiesce`], there's no natural resource-holding head to
/// borrow a `brace` from — pausing isn't a `wanted`-state transition,
/// so there's no `SetWanted`-shaped parent the way `stop_chain` has
/// one. This is exactly the case [`NodeKind::AgentWindow`] exists for
/// (see the module header's _brace_ paragraph): a pure-resource-holder
/// root with no work of its own, so `PauseSignal`/`PauseDrain` can be
/// plain siblings under it, both borrowing its lease via `part_of`.
///
/// ⚠️ `PauseSignal` can *not* hold the lease itself with `PauseDrain`
/// nested under it (`.part_of(signal)`) — that was the first shape
/// tried here, and `hive_jobq` rejects it at insert: `PauseDrain`
/// depending on `PauseSignal` via `after_ok` while also being its
/// *child* reaches outside `PauseDrain`'s own group (its parent, not a
/// sibling) — "an edge must stay within the depender's own group".
/// `AgentWindow` as a separate, actual brace is what makes them
/// siblings instead.
pub(crate) fn pause_quiesce<'a>(builder: &'a JobBuilder, agent: &str) -> Handle<'a> {
let a = || agent.to_owned();
let brace = builder
.node(NodeKind::AgentWindow { agent: a() })
.needs(Resource::Agent(a()));
let signal = builder
.node(NodeKind::PauseSignal { agent: a() })
.part_of(brace);
let _drain = builder
.node(NodeKind::PauseDrain { agent: a() })
.part_of(brace)
.after_ok(signal);
brace
}
/// The group-roots a [`rebuild_nodes`] subgraph exposes to its caller: what a
/// tail node edges onto, and what a follow-up node waits for.
///