feat(#2453): remove DAG parent_id now that every op is one DAG

With the meta-update cascade (#2476) and startup sweep (#2450) folded
into single DAGs that grow per-agent subgraphs via append_subgraph,
nothing links parent/child DAGs anymore — parent_id is dead.

hive-c0re: drop parent_id from Dag/DagSpec (+ the DagView copy); delete
append_children and cancel_children (no callers); simplify trim_history
(no more terminal-parent-with-live-children guard — a one-big-DAG is
terminal only when its whole graph settles); drop the rebuild() parent_id
param; QueueDag returns just the polled DAG (no fan-out children to
gather). hive-sh4re: drop the DagView.parent_id wire field.

frontend: a multi-step op is one DAG now, so renderRebuildQueue drops the
childrenOf/orphans cross-DAG grouping and renders each entry flat; its
per-agent subgraphs render as nodes within the one row (split by deps).
Removed the dead rqe-child style + isChild plumbing.

Docs + the child-DAG queue tests updated/removed to match.
This commit is contained in:
atlas 2026-07-15 19:50:04 +02:00
commit edf9fd036e
15 changed files with 40 additions and 337 deletions

View file

@ -215,7 +215,7 @@ of dangling it).
`RebuildQueueChanged { seq, queue: [DagView…] }` (event name kept). Each
`DagView` carries the entry-level fields (`id`, `kind` = template string,
roll-up `state`, `source`, `parent_id`, `reason`, timestamps, `inputs`,
roll-up `state`, `source`, `reason`, timestamps, `inputs`,
`approval_id`) plus `nodes: [NodeView…]` — per-node `agent`, `kind`, `deps`,
`state`, `step`, `build_log_id`, timestamps, `error`. There is **no
DAG-level `agent`** (agent is per-node, so a DAG can span agents); consumers

View file

@ -211,11 +211,11 @@ shows a state glyph (`⏸` queued / `▶` running / `✔` done /
`■ stop`), agent name,
source chip (`manual | meta_update | auto_update | startup_sweep | crash_recover | approval`
— green for operator-approved config changes), timing, and an
optional reason / error. Meta-update cascade rebuilds and
deferred-start follow-ups nest under their parent entry (`parent_id`
grouping; `rqe-child` CSS class — e.g. a `Rebuild` entry groups its
`Start` child, which fires after the profile-swap to free the build
lane before the container boot). Dedup: re-enqueueing a still-queued
optional reason / error. A multi-step op is a single DAG: its
per-agent subgraphs and sub-steps (a meta-update cascade's per-agent
rebuilds, a `Reconcile`'s deferred `Start`/`Stop`) render as nodes
within the one entry — split into subgraphs by the backend `deps`
edges — not as nested child entries. Dedup: re-enqueueing a still-queued
op for the same agent collapses into the existing entry. All timing
labels stay live: running entries tick elapsed seconds every second;
queued and terminal ("done N ago" / "failed N ago") labels tick every
@ -1380,9 +1380,9 @@ payload):
full snapshot of the rebuild queue on every mutation (enqueue,
state transition, dedup collapse, terminal-history trim).
Same snapshot-over-diff rationale as `tombstones_changed` /
`meta_inputs_changed`: the list is small and the client's
`parent_id` grouping is most naturally re-derived from the
full list. Cold-loaded from `/api/state.rebuild_queue`.
`meta_inputs_changed`: the list is small and the client renders
each DAG's multi-agent shape from its own `nodes` (no cross-DAG
grouping). Cold-loaded from `/api/state.rebuild_queue`.
- `schedules_changed` (seq, schedules: `Vec<WireSchedule>`) —
full snapshot of all scheduled prompts. Emitted after every
operator mutation via the `/api/schedules` surface (new /

View file

@ -248,7 +248,7 @@ function nodeComponents(nodes) {
return components;
}
function rebuildQueueEntryFingerprint(entry, isChild) {
function rebuildQueueEntryFingerprint(entry) {
return JSON.stringify({
state: entry.state,
kind: entry.kind,
@ -259,7 +259,6 @@ function rebuildQueueEntryFingerprint(entry, isChild) {
finished_at: entry.finished_at,
reason: entry.reason,
nodes: (entry.nodes || []).map((n) => [n.kind, n.state, n.step, n.build_log_id, n.error]),
isChild,
});
}
@ -275,45 +274,23 @@ function renderRebuildQueue(s) {
return;
}
const byId = new Map(queue.map((e) => [e.id, e]));
const tops = queue.filter((e) => e.parent_id == null);
const childrenOf = new Map();
for (const e of queue) {
if (e.parent_id != null) {
if (!childrenOf.has(e.parent_id)) childrenOf.set(e.parent_id, []);
childrenOf.get(e.parent_id).push(e);
}
}
const orphans = queue.filter((e) => e.parent_id != null && !byId.has(e.parent_id));
// Every multi-step op is a single DAG now (its whole graph lives in
// `nodes`, split into subgraphs by the backend `deps` edges) — there are
// no cross-DAG parent/child links to group. Render each queue entry in
// enqueue order.
const orderedLis = [];
function addEntry(entry, isChild) {
const fp = rebuildQueueEntryFingerprint(entry, isChild);
for (const entry of queue) {
const fp = rebuildQueueEntryFingerprint(entry);
const cached = rebuildQueueRowCache.get(entry.id);
let li;
if (cached && cached.fingerprint === fp) {
li = cached.el;
} else {
li = renderQueueEntry(entry, byId, isChild);
li = renderQueueEntry(entry);
rebuildQueueRowCache.set(entry.id, { el: li, fingerprint: fp });
}
orderedLis.push(li);
}
// Render each root followed by its whole subtree, depth-first. Recursion
// (rather than a single level of children) handles nested fan-outs such as
// the boot DAG: a `boot` root anchors the `startup_sweep` child, which itself
// fans out `rebuild` grandchildren — all of which must appear under the one
// boot tree. `visited` guards against re-rendering a node reached twice and
// any malformed parent cycle.
const visited = new Set();
function addTree(entry, isChild) {
if (visited.has(entry.id)) return;
visited.add(entry.id);
addEntry(entry, isChild);
for (const child of childrenOf.get(entry.id) || []) addTree(child, true);
}
for (const top of tops) addTree(top, false);
for (const o of orphans) addTree(o, true);
const liveIds = new Set(queue.map((e) => e.id));
for (const [id, entry] of rebuildQueueRowCache) {
@ -336,12 +313,11 @@ function renderRebuildQueue(s) {
while (ul.children.length > orderedLis.length) ul.lastChild.remove();
}
function renderQueueEntry(entry, _byId, isChild) {
function renderQueueEntry(entry) {
const li = el('li', {
class: 'rebuild-queue-entry rqe-' + entry.state,
'data-id': String(entry.id),
});
if (isChild) li.classList.add('rqe-child');
li.append(
el('span', { class: 'rqe-state', title: entry.state }, QUEUE_STATE_GLYPH[entry.state] || '?'),
' ',

View file

@ -105,7 +105,6 @@
align-items: baseline;
gap: 0.4em;
}
.rebuild-queue-entry.rqe-child { margin-left: 1.6em; border-color: var(--purple-dim); }
.rebuild-queue-entry.rqe-running {
border-color: var(--purple);
background: color-mix(in srgb, var(--purple) 12%, transparent);

View file

@ -349,7 +349,6 @@ mod tests {
kind: Template::Rebuild,
state: State::Running,
source: Source::Manual,
parent_id: None,
reason: "manual".to_owned(),
enqueued_at: 0,
started_at: Some(1),
@ -389,7 +388,6 @@ mod tests {
kind: Template::Rebuild,
state: State::Failed,
source: Source::Manual,
parent_id: None,
reason: "manual".to_owned(),
enqueued_at: 0,
started_at: Some(1),

View file

@ -208,8 +208,8 @@ pub enum DashboardEvent {
/// snapshot-shape rationale as `TombstonesChanged` /
/// `MetaInputsChanged`: the list is small, snapshot semantics avoid
/// the add/remove races a per-row event would have, and the
/// dashboard's grouping (`parent_id`) is most naturally re-derived
/// from the full list.
/// dashboard renders each DAG's multi-agent shape from its `nodes`
/// (grouped by `NodeView::agent`) — no cross-DAG grouping needed.
RebuildQueueChanged { seq: u64, queue: Vec<DagView> },
/// Full snapshot of all scheduled prompts. Emitted after every
/// operator mutation (new / edit / cancel / fire-now) and after the

View file

@ -154,20 +154,6 @@ impl JobQueue {
Ok(id)
}
/// Append fan-out children under a parent DAG (meta-update / sweep
/// cascade); returns the child ids created. No dedup (see
/// [`Self::submit`]).
pub fn append_children(&self, specs: Vec<DagSpec>) -> Vec<u64> {
let mut ids = Vec::with_capacity(specs.len());
for spec in specs {
match self.submit(spec) {
Ok(id) => ids.push(id),
Err(e) => tracing::error!(error = ?e, "job_queue: invalid fan-out child spec"),
}
}
ids
}
/// Append a node into a *live* (non-terminal) DAG at runtime,
/// depending `AfterOk` on `dep_on` (the node that emitted it). Lets a
/// planner node — e.g. [`NodeKind::Reconcile`] — fan a mechanical
@ -297,7 +283,6 @@ impl JobQueue {
template: spec.template,
source: spec.source,
reason: spec.reason,
parent_id: spec.parent_id,
approval_id: spec.approval_id,
inputs: spec.inputs,
perm_payload: spec.perm_payload,
@ -541,29 +526,6 @@ impl JobQueue {
true
}
/// Cancel every still-fully-queued child DAG of `parent`. Running
/// children are left alone. Returns the count of cancelled DAGs.
pub fn cancel_children(&self, parent: u64) -> usize {
let mut inner = self.inner.lock().expect("job_queue mutex poisoned");
let now = now_unix();
let mut count = 0;
for dag in &mut inner.dags {
if dag.parent_id == Some(parent) && dag.rollup() == State::Queued {
for n in &mut dag.nodes {
n.state = State::Cancelled;
n.finished_at = Some(now);
}
count += 1;
}
}
if count > 0 {
Self::settle(&mut inner);
drop(inner);
self.notify.notify_one();
}
count
}
/// Set the step label on a `Running` node. Returns `true` when the
/// label actually changed (callers emit a snapshot only then).
pub fn set_step(&self, dag_id: u64, node_id: NodeId, step: &str) -> bool {
@ -653,25 +615,16 @@ impl JobQueue {
}
/// Keep only the newest `MAX_HISTORY_PER_TEMPLATE` terminal DAGs
/// per template. Never evicted: live DAGs; terminal parents with
/// live children (a fan-out parent is terminal the moment its
/// `MetaLock` completes — evicting it while cascade rebuilds run
/// would orphan their dashboard group); and terminal DAGs that
/// per template. Never evicted: live DAGs; and terminal DAGs that
/// finished after `grace_cutoff` (see [`HISTORY_GRACE_SECS`]).
fn trim_history(inner: &mut Inner, grace_cutoff: i64) {
let live_parents: std::collections::HashSet<u64> = inner
.dags
.iter()
.filter(|d| !d.is_terminal())
.filter_map(|d| d.parent_id)
.collect();
let mut counts: HashMap<Template, usize> = HashMap::new();
let kept: Vec<Dag> = inner
.dags
.iter()
.rev()
.filter(|d| {
if !d.is_terminal() || live_parents.contains(&d.id) {
if !d.is_terminal() {
return true;
}
let finished = d.nodes.iter().filter_map(|n| n.finished_at).max();

View file

@ -115,11 +115,10 @@ pub enum NodeKind {
/// deploy stays inside `actions.rs` in v1 — deliberately not
/// modeled as scheduler nodes (see the design doc §9).
ApprovalDeploy,
/// No-op anchor that completes immediately with no work. It exists so a
/// boot's `StartupSweep` + per-agent `Reconcile` child DAGs can hang off
/// one root (`parent_id`) and render as a single boot tree on the
/// dashboard. Holds no lease and no build slot; the child DAGs it anchors
/// still run concurrently — the grouping is a display link, not a dep edge.
/// No-op anchor that completes immediately with no work. Vestigial since
/// the boot sweep became one in-DAG graph (no `boot_root` anchor / child
/// DAGs) — slated for removal with the other `StartupSweep` residuals.
/// Holds no lease and no build slot.
Noop,
/// Write the agent's durable power intent (`wanted = Up` when `up`, else
/// `Offline`) as a first-class DAG node, at the head of a power-op
@ -242,8 +241,6 @@ pub struct DagSpec {
pub source: Source,
/// Free-form "why".
pub reason: String,
/// Cascade grouping (meta-update / sweep children).
pub parent_id: Option<u64>,
/// Fires the approval-resolution hook on DAG terminal.
pub approval_id: Option<i64>,
/// `MetaUpdate`-only: the inputs to bump (also part of the dedup
@ -266,7 +263,6 @@ pub struct Dag {
pub template: Template,
pub source: Source,
pub reason: String,
pub parent_id: Option<u64>,
pub approval_id: Option<i64>,
pub inputs: Vec<String>,
pub perm_payload: Option<PermPayload>,
@ -355,7 +351,6 @@ impl Dag {
kind: self.template,
state: self.rollup(),
source: self.source,
parent_id: self.parent_id,
reason: self.reason.clone(),
enqueued_at: self.created_at,
started_at,

View file

@ -38,10 +38,10 @@ fn submit_and_emit(coord: &Arc<Coordinator>, spec: super::DagSpec) -> u64 {
}
/// Manual/approval-independent rebuild (always relocks the agent's
/// meta input — cascade children are built by the scheduler's fan-out
/// instead of this surface).
/// meta input — the meta-update cascade grows its own rebuild subgraphs
/// in-DAG instead of going through this surface).
pub fn rebuild(coord: &Arc<Coordinator>, agent: &str, source: Source, reason: String) -> u64 {
submit_and_emit(coord, templates::rebuild(agent, source, reason, None, true))
submit_and_emit(coord, templates::rebuild(agent, source, reason, true))
}
// ---- dynamic power-op DAG assembly ----------------------------------------
@ -148,7 +148,6 @@ fn power_dag(
template,
source,
reason,
parent_id: None,
approval_id: None,
inputs: Vec::new(),
perm_payload: None,

View file

@ -19,7 +19,7 @@
//! rebuild(a): Prebuild(a) → StopForUpdate(a) → Swap(a) →(any) Reconcile(a)
//! spawn(a): Provision(a) → Create(a) → WriteDropin(a) → Reconcile(a) [wanted=Up at approve]
//! perm-change(a): WritePermFile(a) → «rebuild subgraph»
//! meta-update(inp): MetaLock(inp) → «fan-out rebuild(a) per affected a»
//! meta-update(inp): MetaLock(inp) →«in-DAG rebuild subgraph per affected a»
//! ```
//!
//! For the dynamic power-op shapes (`stop` / `start` / `restart`, built from
@ -83,18 +83,11 @@ pub(crate) fn rebuild_nodes(agent: &str, relock: bool, base: u32) -> Vec<NodeSpe
/// when `wanted = Offline` (a rebuild of a deliberately-stopped agent
/// leaves it stopped). `relock = false` only for meta-update cascade
/// children.
pub fn rebuild(
agent: &str,
source: Source,
reason: String,
parent_id: Option<u64>,
relock: bool,
) -> DagSpec {
pub fn rebuild(agent: &str, source: Source, reason: String, relock: bool) -> DagSpec {
DagSpec {
template: Template::Rebuild,
source,
reason,
parent_id,
approval_id: None,
inputs: Vec::new(),
perm_payload: None,
@ -111,7 +104,6 @@ pub fn approval_deploy(agent: &str, approval_id: i64, reason: String) -> DagSpec
template: Template::Rebuild,
source: Source::Approval,
reason,
parent_id: None,
approval_id: Some(approval_id),
inputs: Vec::new(),
perm_payload: None,
@ -135,7 +127,6 @@ pub fn reconcile_only(
template,
source,
reason,
parent_id: None,
approval_id: None,
inputs: Vec::new(),
perm_payload: None,
@ -153,7 +144,6 @@ pub fn spawn(agent: &str, approval_id: i64, reason: String) -> DagSpec {
template: Template::Spawn,
source: Source::Approval,
reason,
parent_id: None,
approval_id: Some(approval_id),
inputs: Vec::new(),
perm_payload: None,
@ -177,7 +167,6 @@ pub fn perm_change(agent: &str, source: Source, reason: String, payload: PermPay
template: Template::PermChange,
source,
reason,
parent_id: None,
approval_id: None,
inputs: Vec::new(),
perm_payload: Some(payload),
@ -190,7 +179,7 @@ pub fn perm_change(agent: &str, source: Source, reason: String, payload: PermPay
/// per affected agent into *this same* DAG on completion (via
/// `append_subgraph`) — appended *after* the bump lands so their prebuilds
/// run against the post-bump lock, and a failed bump appends nothing
/// (replacing the old fan-out-child-DAGs + `cancel_children` dance).
/// (replacing the old fan-out-child-DAGs dance).
/// `transient = Rebuilding` because those appended subgraphs are rebuilds:
/// it's applied per-agent at claim time (the `MetaLock` head needs no lease,
/// so the "hyperhive" pseudo-agent gets no pill), giving each cascade agent
@ -206,7 +195,6 @@ pub fn meta_update(
template: Template::MetaUpdate,
source,
reason,
parent_id: None,
approval_id,
inputs,
perm_payload: None,

View file

@ -1,7 +1,8 @@
//! Queue-core unit tests: submit / no-dedup, cycle rejection, resource
//! serialization (build slots / per-agent leases), lease-exempt
//! overlap, FIFO fairness, cancel semantics, `AfterAny` failure
//! routing, fan-out, and history retention. All synchronous — the
//! routing, in-DAG subgraph growth, and history retention. All
//! synchronous — the
//! scheduler's async loop is a thin claim/complete pump over the same
//! methods exercised here.
@ -13,7 +14,7 @@ fn submit(q: &JobQueue, spec: DagSpec) -> u64 {
}
fn rebuild(agent: &str, reason: &str) -> DagSpec {
templates::rebuild(agent, Source::Manual, reason.to_owned(), None, true)
templates::rebuild(agent, Source::Manual, reason.to_owned(), true)
}
/// Restart DAG spec with every agent treated as **running** — the online
@ -460,7 +461,6 @@ fn append_subgraph_roots_on_emitter_and_rebases_local_deps() {
template: Template::Boot,
source: Source::AutoUpdate,
reason: "sweep".to_owned(),
parent_id: None,
approval_id: None,
inputs: Vec::new(),
perm_payload: None,
@ -646,125 +646,6 @@ fn cancel_refuses_running_dag() {
assert_eq!(state_of(&q, id), State::Running);
}
#[test]
fn cancel_children_marks_queued_children_only() {
let q = JobQueue::new(1);
let meta = submit(
&q,
templates::meta_update(vec![], Source::Manual, "bump".to_owned(), None),
);
// Parent's MetaLock is running while children exist.
let lock = claim_one(&q);
assert_eq!(lock.dag_id, meta);
let child_a = submit(
&q,
templates::rebuild(
"agent-a",
Source::MetaUpdate,
"cascade".to_owned(),
Some(meta),
false,
),
);
let child_b = submit(
&q,
templates::rebuild(
"agent-b",
Source::MetaUpdate,
"cascade".to_owned(),
Some(meta),
false,
),
);
let unrelated = submit(&q, rebuild("agent-c", "operator queued"));
// MetaLock holds the single build slot, so both children (and the
// unrelated rebuild) are still fully queued here.
let cancelled = q.cancel_children(meta);
assert_eq!(cancelled, 2);
assert_eq!(state_of(&q, child_a), State::Cancelled);
assert_eq!(state_of(&q, child_b), State::Cancelled);
assert_eq!(state_of(&q, unrelated), State::Queued);
}
#[test]
fn cancel_children_skips_running_child() {
let q = JobQueue::new(2);
let meta = submit(
&q,
templates::meta_update(vec![], Source::Manual, "bump".to_owned(), None),
);
let lock = claim_one(&q);
let running_child = submit(
&q,
templates::rebuild(
"agent-a",
Source::MetaUpdate,
"cascade".to_owned(),
Some(meta),
false,
),
);
let queued_child = submit(
&q,
templates::rebuild(
"agent-b",
Source::MetaUpdate,
"cascade".to_owned(),
Some(meta),
false,
),
);
// Second slot lets running_child's prebuild start.
let child_claim = claim_one(&q);
assert_eq!(child_claim.dag_id, running_child);
let n = q.cancel_children(meta);
assert_eq!(n, 1);
assert_eq!(state_of(&q, running_child), State::Running);
assert_eq!(state_of(&q, queued_child), State::Cancelled);
q.complete_node(meta, lock.node_id, Ok(()));
}
// ---- fan-out ----
#[test]
fn append_children_sets_parent() {
let q = JobQueue::new(1);
let meta = submit(
&q,
templates::meta_update(vec![], Source::Manual, "bump".to_owned(), None),
);
let specs = vec![
templates::rebuild(
"alice",
Source::MetaUpdate,
"cascade".to_owned(),
Some(meta),
false,
),
templates::rebuild(
"bob",
Source::MetaUpdate,
"cascade".to_owned(),
Some(meta),
false,
),
// No dedup: a second alice child is its own DAG now.
templates::rebuild(
"alice",
Source::MetaUpdate,
"cascade again".to_owned(),
Some(meta),
false,
),
];
let ids = q.append_children(specs);
assert_eq!(ids.len(), 3);
assert_ne!(ids[0], ids[2], "no dedup: duplicate child is distinct");
let snap = q.snapshot();
let children: Vec<_> = snap.iter().filter(|d| d.parent_id == Some(meta)).collect();
assert_eq!(children.len(), 3);
}
// ---- terminal reporting + lease release ----
#[test]
@ -826,88 +707,6 @@ fn cancelled_dag_reports_terminal_once() {
assert!(q.drain_terminal().iter().all(|t| t.dag_id != id));
}
#[test]
fn cancel_children_reports_terminals() {
let q = JobQueue::new(1);
let meta = submit(
&q,
templates::meta_update(vec![], Source::Manual, "bump".to_owned(), None),
);
let _lock = claim_one(&q);
let child = submit(
&q,
templates::rebuild(
"agent-a",
Source::MetaUpdate,
"cascade".to_owned(),
Some(meta),
false,
),
);
assert_eq!(q.cancel_children(meta), 1);
let reports = q.drain_terminal();
assert_eq!(reports.len(), 1);
assert_eq!(reports[0].dag_id, child);
assert_eq!(reports[0].state, State::Cancelled);
}
/// History trim must not evict a terminal fan-out parent while its
/// children are still live — the dashboard groups children under it.
#[test]
fn trim_keeps_terminal_parent_with_live_children() {
let q = JobQueue::new(1);
// Pin agent-x's lease with a running stop DAG so the child below
// stays fully queued while we churn history.
let pin = submit(
&q,
templates::reconcile_only(
Template::Stop,
"agent-x",
Source::Manual,
"lease pin".to_owned(),
None,
),
);
let pin_claim = claim_one(&q);
assert_eq!(pin_claim.dag_id, pin);
// Terminal fan-out parent + a lease-blocked child under it.
let meta = submit(
&q,
templates::meta_update(vec![], Source::Manual, "bump".to_owned(), None),
);
let lock = claim_one(&q);
q.complete_node(meta, lock.node_id, Ok(()));
let mut child_spec = submit::restart_spec(
&[("agent-x".to_owned(), true)],
false,
Source::MetaUpdate,
"cascade".to_owned(),
);
child_spec.parent_id = Some(meta);
let child = submit(&q, child_spec);
// Churn > MAX_HISTORY_PER_TEMPLATE terminal meta_update DAGs.
for i in 0..7 {
let id = submit(
&q,
templates::meta_update(
vec![format!("input-{i}")],
Source::Manual,
"churn".to_owned(),
None,
),
);
let c = claim_one(&q);
assert_eq!(c.dag_id, id, "child is lease-blocked; churn claims freely");
q.complete_node(id, c.node_id, Ok(()));
}
let snap = q.snapshot();
assert!(
snap.iter().any(|d| d.id == meta),
"terminal parent with live child must survive trim"
);
assert!(snap.iter().any(|d| d.id == child));
}
// ---- steps, build logs, history ----
#[test]

View file

@ -145,12 +145,12 @@ async fn dispatch(req: &HostRequest, coord: Arc<Coordinator>) -> HostResponse {
}
HostRequest::Rebuild { name } => submit_single(&coord, name, Verb::Rebuild).await,
HostRequest::QueueDag { id } => {
// The polled DAG first, then its live fan-out children.
// A multi-step op is one DAG now (no fan-out children to gather).
let dags = coord
.job_queue
.snapshot()
.into_iter()
.filter(|d| d.id == *id || d.parent_id == Some(*id))
.filter(|d| d.id == *id)
.collect();
HostResponse::dags(dags)
}

View file

@ -112,7 +112,6 @@ pub async fn ensure_root_agent(coord: &Arc<Coordinator>) -> Result<()> {
MANAGER_NAME,
crate::job_queue::Source::AutoUpdate,
"manager migration: no applied flake".to_owned(),
None,
true,
)) {
tracing::warn!(error = ?e, "manager migration rebuild submit failed");
@ -347,7 +346,6 @@ fn submit_boot_tree(
template: Template::Boot,
source: Source::AutoUpdate,
reason,
parent_id: None,
approval_id: None,
inputs: Vec::new(),
perm_payload: None,

View file

@ -78,9 +78,9 @@ pub enum HostRequest {
/// matrix GUI disabled). Backs `hivectl open` + the federation
/// peer-config block (which reads the bare `domain`).
Urls,
/// Fetch one job-queue DAG (plus its live fan-out children, linked
/// via `parent_id`) by id — the polling surface behind `hivectl`'s
/// wait/progress loop. Result: [`HostResponse::dags`].
/// Fetch one job-queue DAG by id — the polling surface behind
/// `hivectl`'s wait/progress loop. A multi-step op is a single DAG
/// (its whole graph in `nodes`). Result: [`HostResponse::dags`].
QueueDag { id: u64 },
/// List pending approval requests.
Pending,

View file

@ -189,8 +189,6 @@ pub struct DagView {
/// `queued` / `cancelled` / `done`.
pub state: State,
pub source: Source,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parent_id: Option<u64>,
pub reason: String,
pub enqueued_at: i64,
#[serde(default, skip_serializing_if = "Option::is_none")]