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

@ -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);