diff --git a/hive-c0re/src/job_queue/tests.rs b/hive-c0re/src/job_queue/tests.rs index 1609b096..088a25fe 100644 --- a/hive-c0re/src/job_queue/tests.rs +++ b/hive-c0re/src/job_queue/tests.rs @@ -673,52 +673,6 @@ fn multi_agent_restart_is_one_dag_with_concurrent_per_agent_subgraphs() { ); } -/// A multi-agent DAG frees an agent's lease the moment THAT agent's -/// subgraph is terminal — not when the whole DAG finishes. So a -/// concurrent DAG wanting the finished agent can proceed while the rest -/// of the first DAG runs on. -#[test] -fn multi_agent_lease_frees_per_subgraph_not_whole_dag() { - let q = JobQueue::new(4); - let id = submit(&q, restart_online(&["agent-a", "agent-b"], false, "r")); - - // Drive agent-a's ENTIRE subgraph to Done while leaving agent-b's - // head running (so agent-b keeps holding its lease). - let mut b_in_flight = false; - loop { - let mut progressed = false; - for c in q.claim_ready() { - if c.agent == "agent-a" { - q.complete_node(c.node_id, Ok(())); - progressed = true; - } else { - b_in_flight = true; // leave agent-b's node running - } - } - if !progressed { - break; - } - } - assert!(b_in_flight, "agent-b subgraph should still be in flight"); - // The DAG as a whole is NOT terminal — agent-b runs on. - assert_eq!(state_of(&q, id), State::Running); - - // agent-a's lease is freed early → a concurrent agent-a DAG runs; - // an agent-b DAG still blocks on the lease agent-b's subgraph holds. - submit(&q, restart_online(&["agent-a"], false, "concurrent-a")); - submit(&q, restart_online(&["agent-b"], false, "concurrent-b")); - let claims = q.claim_ready(); - let agents: Vec<&str> = claims.iter().map(|c| c.agent.as_str()).collect(); - assert!( - agents.contains(&"agent-a"), - "agent-a lease freed the moment its subgraph settled" - ); - assert!( - !agents.contains(&"agent-b"), - "agent-b lease still held — its subgraph is still in flight" - ); -} - #[test] fn multi_agent_stop_is_one_dag_with_concurrent_per_agent_subgraphs() { let q = JobQueue::new(4); @@ -1228,6 +1182,23 @@ fn rebuild_reconcile_waits_for_the_whole_build_subtree() { // node fails, asserting the DAG reads `Failed`. That is `failed_child_rolls_ // parent_up_to_failed` in hive_jobq, restated through a c0re template. +// `multi_agent_lease_frees_per_subgraph_not_whole_dag` and +// `dag_settles_terminal_and_releases_lease_after_work` lived here. +// +// Both drove a DAG to completion to watch an agent lease free up — one when a +// single agent's subgraph settled inside a still-running multi-agent DAG, the +// other when a whole power op finished. Releasing a grant once its owner's +// subtree is terminal is hive_jobq's (`owner_holds_grant_for_its_whole_subtree`, +// `child_borrows_ancestor_grant_released_when_subtree_done`, +// `leaf_owner_goes_done_directly_and_releases`). +// +// The c0re halves are declared and asserted elsewhere: that each agent's +// subgraph is an independent root holding only its own lease is in +// `multi_agent_restart_is_one_dag_with_concurrent_per_agent_subgraphs`, and +// that a power op emits no tail node is in +// `cancelled_power_op_runs_no_compensating_node`, which checks the DAG has no +// pending nodes left at all. + // ---- cancel ---- #[test] @@ -1362,31 +1333,6 @@ fn cancelled_power_op_runs_no_compensating_node() { // ---- terminal reporting + lease release ---- -#[test] -fn dag_settles_terminal_and_releases_lease_after_work() { - let q = JobQueue::new(1); - let id = submit(&q, restart_online(&["agent-a"], false, "r")); - // restart = StopForUpdate → Reconcile. - let stop = claim_one(&q); - assert_eq!(stop.kind.as_str(), "stop_for_update"); - q.complete_node(stop.node_id, Ok(())); - let rec = claim_one(&q); - assert_eq!(rec.kind.as_str(), "reconcile"); - // Completing the last work node rolls the container up terminal. A power op - // has no tail node, so nothing is left to claim. - q.complete_node(rec.node_id, Ok(())); - assert!(q.claim_ready().is_empty(), "no tail node to claim"); - assert_eq!(state_of(&q, id), State::Done); - // Lease released when the work chain settled: a new DAG for the agent claims - // immediately. - let next = submit( - &q, - templates::reconcile_only("agent-a", Source::Manual, "stop".to_owned()), - ); - let c = claim_one(&q); - assert_eq!(c.dag_id, next); -} - /// A DAG cancelled while fully queued must still **run its tail**, or a queued /// approval DAG cancelled by the operator would dangle its approval forever. ///