feat(#2476): grow the meta-update cascade in-DAG instead of child DAGs

MetaLock's non-sweep completion now grows one rebuild subgraph per
affected agent into the same DAG (append_subgraph), replacing the
fan-out-child-DAGs + cancel_children dance. Drops NodeOutput.fanout and
scheduler's fanout_specs. meta_update DAG carries Rebuilding transient so
each cascade agent gets crash-watch suppression at Swap (the property the
old child Rebuild DAGs held via their own transient); MetaLock head needs
no lease so the pseudo-agent gets no pill.

append_children/parent_id and child-DAG tests are intentionally left for
the #2453 capstone.
This commit is contained in:
atlas 2026-07-15 18:56:40 +02:00 committed by mara
commit 2b3130f63c
5 changed files with 102 additions and 66 deletions

View file

@ -27,26 +27,24 @@ pub const GRACEFUL_STOP_TIMEOUT: std::time::Duration = std::time::Duration::from
/// success.
#[derive(Debug, Default)]
pub struct NodeOutput {
/// Agents to fan child `Rebuild` DAGs out for (`MetaLock` only).
pub fanout: Vec<String>,
/// Mechanical sub-step nodes to append into *this same* DAG at
/// runtime, each depending `AfterOk` on the emitting node — e.g. a
/// `Reconcile` planner emitting a `Start` / `Stop`. A separate
/// channel from `fanout` (which appends whole *child* DAGs) so the
/// sub-step stays a first-class node in the same DAG and the
/// lease-window transient is held across it. The scheduler applies
/// these *before* the emitting node's completion so the DAG never
/// rolls terminal with the appended work still pending.
/// `Reconcile` planner emitting a `Start` / `Stop`. Keeps the sub-step a
/// first-class node in the same DAG so the lease-window transient is held
/// across it. The scheduler applies these *before* the emitting node's
/// completion so the DAG never rolls terminal with the appended work
/// still pending.
pub append_nodes: Vec<NodeKind>,
/// Whole per-agent *subgraphs* to append into *this same* DAG at
/// runtime — the multi-node generalisation of `append_nodes`. Each
/// inner `Vec<NodeSpec>` is one independent subgraph whose `deps` are
/// local (0-based within that subgraph); the scheduler appends each via
/// [`JobQueue::append_subgraph`], which rebases the deps onto the DAG's
/// node-id space and roots the subgraph on the emitting node. The
/// startup sweep's `MetaLock` uses this to grow one stale-agent rebuild
/// subgraph per agent into the same boot DAG instead of fanning out
/// child DAGs. Same before-completion ordering as `append_nodes`.
/// node-id space and roots the subgraph on the emitting node. Both
/// `MetaLock` flavours use this to grow one rebuild subgraph per agent
/// into their own DAG (the startup sweep's stale agents; the meta-update
/// cascade's affected agents) instead of fanning out child DAGs. Same
/// before-completion ordering as `append_nodes`.
pub append_subgraph: Vec<Vec<NodeSpec>>,
}
@ -312,8 +310,18 @@ async fn run_meta_lock(
Some(list) => list,
None => meta_update_cascade_agents(&claim.inputs).await,
};
// Grow one rebuild subgraph per affected agent into *this* meta-update
// DAG (rooted on this `MetaLock`, so they build against the post-bump
// lock), rather than fanning out child DAGs. `relock = false` — the
// cascade children must NOT re-lock, which would revert the bump this
// node just committed (the property the old `fanout_specs` meta-update
// branch encoded).
let append_subgraph = cascade
.iter()
.map(|agent| super::templates::rebuild_nodes(agent, false, 0))
.collect();
Ok(NodeOutput {
fanout: cascade,
append_subgraph,
..Default::default()
})
}