refactor(#2591): move the perm-change payload onto the WritePermFile node

This commit is contained in:
atlas 2026-07-20 23:21:46 +02:00 committed by mara
commit 600bc051e1
9 changed files with 17 additions and 36 deletions

View file

@ -540,26 +540,30 @@ async fn run_write_perm_file(
) -> Result<NodeOutput> { ) -> Result<NodeOutput> {
use super::model::PermPayload; use super::model::PermPayload;
let name = &claim.agent; let name = &claim.agent;
// The perm file payload rides the node itself (the only consumer).
let NodeKind::WritePermFile { payload, .. } = &claim.kind else {
anyhow::bail!("run_write_perm_file on a non-WritePermFile node");
};
ctx.step("writing + committing perm file"); ctx.step("writing + committing perm file");
// Deploy-window gate: a perm commit landing inside another node's // Deploy-window gate: a perm commit landing inside another node's
// staged prepare→finalize window would sweep the staged deploy // staged prepare→finalize window would sweep the staged deploy
// lock into its commit (the commits are also path-limited in // lock into its commit (the commits are also path-limited in
// meta.rs — belt and braces). // meta.rs — belt and braces).
let _window = crate::meta::exclusive().await; let _window = crate::meta::exclusive().await;
match &claim.perm_payload { match payload {
Some(PermPayload::ToolGroups { groups }) => { PermPayload::ToolGroups { groups } => {
crate::meta::commit_tool_groups(name, groups) crate::meta::commit_tool_groups(name, groups)
.await .await
.with_context(|| format!("commit tool-groups for {name}"))?; .with_context(|| format!("commit tool-groups for {name}"))?;
coord.emit_tool_groups_snapshot(); coord.emit_tool_groups_snapshot();
} }
Some(PermPayload::Capabilities { caps }) => { PermPayload::Capabilities { caps } => {
crate::meta::commit_capabilities(name, caps) crate::meta::commit_capabilities(name, caps)
.await .await
.with_context(|| format!("commit capabilities for {name}"))?; .with_context(|| format!("commit capabilities for {name}"))?;
coord.emit_capabilities_snapshot(); coord.emit_capabilities_snapshot();
} }
Some(PermPayload::Combined { groups, caps }) => { PermPayload::Combined { groups, caps } => {
crate::meta::commit_perms(name, groups.as_deref(), caps.as_deref()) crate::meta::commit_perms(name, groups.as_deref(), caps.as_deref())
.await .await
.with_context(|| format!("commit perms for {name}"))?; .with_context(|| format!("commit perms for {name}"))?;
@ -570,10 +574,6 @@ async fn run_write_perm_file(
coord.emit_capabilities_snapshot(); coord.emit_capabilities_snapshot();
} }
} }
None => anyhow::bail!(
"perm_change dag {} for {name} is missing perm_payload",
claim.dag_id
),
} }
Ok(NodeOutput::default()) Ok(NodeOutput::default())
} }

View file

@ -76,7 +76,6 @@ pub struct Claim {
pub template: Template, pub template: Template,
pub approval_id: Option<i64>, pub approval_id: Option<i64>,
pub inputs: Vec<String>, pub inputs: Vec<String>,
pub perm_payload: Option<PermPayload>,
/// Transient pill kind for the lease window (from the spec). Whether the /// Transient pill kind for the lease window (from the spec). Whether the
/// pill is currently shown is derived from live lease ownership /// pill is currently shown is derived from live lease ownership
/// ([`JobQueue::held_transients`]), not a per-claim edge. /// ([`JobQueue::held_transients`]), not a per-claim edge.
@ -118,7 +117,6 @@ struct DagMeta {
transient: Option<TransientKind>, transient: Option<TransientKind>,
approval_id: Option<i64>, approval_id: Option<i64>,
inputs: Vec<String>, inputs: Vec<String>,
perm_payload: Option<PermPayload>,
created_at: i64, created_at: i64,
} }
@ -297,7 +295,6 @@ impl JobQueue {
transient: spec.transient, transient: spec.transient,
approval_id: spec.approval_id, approval_id: spec.approval_id,
inputs: spec.inputs, inputs: spec.inputs,
perm_payload: spec.perm_payload,
created_at: now_unix(), created_at: now_unix(),
}, },
Vec::new(), Vec::new(),
@ -387,7 +384,6 @@ impl JobQueue {
template: meta.template, template: meta.template,
approval_id: meta.approval_id, approval_id: meta.approval_id,
inputs: meta.inputs, inputs: meta.inputs,
perm_payload: meta.perm_payload,
transient: meta.transient, transient: meta.transient,
}); });
if let Some(rt) = inner.node_rt.get_mut(&id) { if let Some(rt) = inner.node_rt.get_mut(&id) {
@ -638,7 +634,6 @@ impl QueueInner {
transient, transient,
approval_id, approval_id,
inputs, inputs,
perm_payload,
created_at, created_at,
} = &self.sched.graph().node(container)?.payload } = &self.sched.graph().node(container)?.payload
else { else {
@ -651,7 +646,6 @@ impl QueueInner {
transient: *transient, transient: *transient,
approval_id: *approval_id, approval_id: *approval_id,
inputs: inputs.clone(), inputs: inputs.clone(),
perm_payload: perm_payload.clone(),
created_at: *created_at, created_at: *created_at,
}) })
} }
@ -801,7 +795,6 @@ impl QueueInner {
}, },
inputs: meta.inputs.clone(), inputs: meta.inputs.clone(),
approval_id: meta.approval_id, approval_id: meta.approval_id,
perm_payload: meta.perm_payload.clone(),
nodes, nodes,
}) })
} }

View file

@ -119,9 +119,10 @@ pub enum NodeKind {
Drain { agent: String }, Drain { agent: String },
/// `set_nspawn_flags` + `set_resource_limits` + daemon-reload. /// `set_nspawn_flags` + `set_resource_limits` + daemon-reload.
WriteDropin { agent: String }, WriteDropin { agent: String },
/// Commit `tool-groups.json` / `capabilities.json` per the DAG's /// Commit `tool-groups.json` / `capabilities.json` per its `payload`
/// `perm_payload` (commit fused under `META_LOCK`). /// (commit fused under `META_LOCK`). The payload rides this node — the only
WritePermFile { agent: String }, /// consumer — rather than the generic DAG container.
WritePermFile { agent: String, payload: PermPayload },
/// Opaque approval deploy pipeline (`MergeConfigPr`): the two-phase /// Opaque approval deploy pipeline (`MergeConfigPr`): the two-phase
/// prepare/finalize/abort meta deploy stays inside `actions.rs` in v1 — /// prepare/finalize/abort meta deploy stays inside `actions.rs` in v1 —
/// deliberately not /// deliberately not
@ -156,7 +157,6 @@ pub enum NodeKind {
transient: Option<TransientKind>, transient: Option<TransientKind>,
approval_id: Option<i64>, approval_id: Option<i64>,
inputs: Vec<String>, inputs: Vec<String>,
perm_payload: Option<PermPayload>,
created_at: i64, created_at: i64,
}, },
} }
@ -203,7 +203,7 @@ impl NodeKind {
| NodeKind::Signal { agent } | NodeKind::Signal { agent }
| NodeKind::Drain { agent } | NodeKind::Drain { agent }
| NodeKind::WriteDropin { agent } | NodeKind::WriteDropin { agent }
| NodeKind::WritePermFile { agent } | NodeKind::WritePermFile { agent, .. }
| NodeKind::ApprovalDeploy { agent } | NodeKind::ApprovalDeploy { agent }
| NodeKind::SetWanted { agent, .. } => agent, | NodeKind::SetWanted { agent, .. } => agent,
NodeKind::MetaLock { .. } | NodeKind::Dag { .. } => "", NodeKind::MetaLock { .. } | NodeKind::Dag { .. } => "",
@ -269,7 +269,9 @@ pub struct NodeSpec {
/// Submit-time spec for a whole DAG. Built by `templates.rs`; validated /// Submit-time spec for a whole DAG. Built by `templates.rs`; validated
/// (cycle rejection) by `JobQueue::submit`. No DAG-level `agent` — every /// (cycle rejection) by `JobQueue::submit`. No DAG-level `agent` — every
/// node carries its own (a DAG can span agents), and the queue derives /// node carries its own (a DAG can span agents), and the queue derives
/// per-agent leasing from [`NodeSpec::agent`]. /// per-agent leasing from [`NodeKind::agent`]. Type-specific payloads
/// (`PermChange`'s file payload) ride the node that consumes them
/// ([`NodeKind::WritePermFile`]), not this generic spec.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct DagSpec { pub struct DagSpec {
pub template: Template, pub template: Template,
@ -281,8 +283,6 @@ pub struct DagSpec {
/// `MetaUpdate`-only: the inputs to bump (also part of the dedup /// `MetaUpdate`-only: the inputs to bump (also part of the dedup
/// key for that template). Display copy lives on the DAG. /// key for that template). Display copy lives on the DAG.
pub inputs: Vec<String>, pub inputs: Vec<String>,
/// `PermChange`-only payload.
pub perm_payload: Option<PermPayload>,
/// Dashboard transient pill (and crash-watch suppression) held for /// Dashboard transient pill (and crash-watch suppression) held for
/// the lease window — from lease acquisition to DAG terminal. /// the lease window — from lease acquisition to DAG terminal.
pub transient: Option<crate::coordinator::TransientKind>, pub transient: Option<crate::coordinator::TransientKind>,

View file

@ -201,7 +201,6 @@ fn power_dag(
reason, reason,
approval_id: None, approval_id: None,
inputs: Vec::new(), inputs: Vec::new(),
perm_payload: None,
transient: Some(transient), transient: Some(transient),
nodes, nodes,
} }

View file

@ -125,7 +125,6 @@ pub fn rebuild(agent: &str, source: Source, reason: String, relock: bool) -> Dag
reason, reason,
approval_id: None, approval_id: None,
inputs: Vec::new(), inputs: Vec::new(),
perm_payload: None,
transient: Some(TransientKind::Rebuilding), transient: Some(TransientKind::Rebuilding),
nodes: rebuild_nodes(agent, relock, 0), nodes: rebuild_nodes(agent, relock, 0),
} }
@ -141,7 +140,6 @@ pub fn approval_deploy(agent: &str, approval_id: i64, reason: String) -> DagSpec
reason, reason,
approval_id: Some(approval_id), approval_id: Some(approval_id),
inputs: Vec::new(), inputs: Vec::new(),
perm_payload: None,
transient: Some(TransientKind::Rebuilding), transient: Some(TransientKind::Rebuilding),
nodes: vec![node( nodes: vec![node(
NodeKind::ApprovalDeploy { NodeKind::ApprovalDeploy {
@ -171,7 +169,6 @@ pub fn reconcile_only(
reason, reason,
approval_id: None, approval_id: None,
inputs: Vec::new(), inputs: Vec::new(),
perm_payload: None,
transient, transient,
nodes: vec![node( nodes: vec![node(
NodeKind::Reconcile { NodeKind::Reconcile {
@ -198,7 +195,6 @@ pub fn spawn(agent: &str, approval_id: i64, reason: String) -> DagSpec {
reason, reason,
approval_id: Some(approval_id), approval_id: Some(approval_id),
inputs: Vec::new(), inputs: Vec::new(),
perm_payload: None,
transient: Some(TransientKind::Spawning), transient: Some(TransientKind::Spawning),
nodes: { nodes: {
let a = || agent.to_owned(); let a = || agent.to_owned();
@ -219,6 +215,7 @@ pub fn perm_change(agent: &str, source: Source, reason: String, payload: PermPay
let mut nodes = vec![node( let mut nodes = vec![node(
NodeKind::WritePermFile { NodeKind::WritePermFile {
agent: agent.to_owned(), agent: agent.to_owned(),
payload,
}, },
Vec::new(), Vec::new(),
)]; )];
@ -229,7 +226,6 @@ pub fn perm_change(agent: &str, source: Source, reason: String, payload: PermPay
reason, reason,
approval_id: None, approval_id: None,
inputs: Vec::new(), inputs: Vec::new(),
perm_payload: Some(payload),
transient: Some(TransientKind::Rebuilding), transient: Some(TransientKind::Rebuilding),
nodes, nodes,
} }
@ -257,7 +253,6 @@ pub fn meta_update(
reason, reason,
approval_id, approval_id,
inputs, inputs,
perm_payload: None,
transient: Some(TransientKind::Rebuilding), transient: Some(TransientKind::Rebuilding),
nodes: vec![node( nodes: vec![node(
NodeKind::MetaLock { NodeKind::MetaLock {

View file

@ -561,7 +561,6 @@ fn append_subgraph_roots_on_emitter_and_rebases_local_deps() {
reason: "sweep".to_owned(), reason: "sweep".to_owned(),
approval_id: None, approval_id: None,
inputs: Vec::new(), inputs: Vec::new(),
perm_payload: None,
transient: None, transient: None,
nodes: vec![NodeSpec { nodes: vec![NodeSpec {
kind: NodeKind::MetaLock { kind: NodeKind::MetaLock {

View file

@ -348,7 +348,6 @@ fn submit_boot_tree(
reason, reason,
approval_id: None, approval_id: None,
inputs: Vec::new(), inputs: Vec::new(),
perm_payload: None,
// Rebuilding when the sweep will grow rebuild subgraphs (per-agent // Rebuilding when the sweep will grow rebuild subgraphs (per-agent
// crash-watch suppression during their Swap, applied at claim time); // crash-watch suppression during their Swap, applied at claim time);
// a reconcile-only boot needs no transient. // a reconcile-only boot needs no transient.

View file

@ -195,7 +195,5 @@ pub struct DagView {
pub inputs: Vec<String>, pub inputs: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub approval_id: Option<i64>, pub approval_id: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub perm_payload: Option<PermPayload>,
pub nodes: Vec<NodeView>, pub nodes: Vec<NodeView>,
} }

View file

@ -353,7 +353,6 @@ mod tests {
finished_at: None, finished_at: None,
inputs: vec![], inputs: vec![],
approval_id: None, approval_id: None,
perm_payload: None,
nodes: vec![ nodes: vec![
node(0, "alice", "prebuild", State::Done, None), node(0, "alice", "prebuild", State::Done, None),
node(1, "alice", "stop_for_update", State::Done, None), node(1, "alice", "stop_for_update", State::Done, None),
@ -392,7 +391,6 @@ mod tests {
finished_at: Some(2), finished_at: Some(2),
inputs: vec![], inputs: vec![],
approval_id: None, approval_id: None,
perm_payload: None,
nodes: vec![failed], nodes: vec![failed],
}; };
let line = render_dag_line(&dag); let line = render_dag_line(&dag);