c0re: reject apply_commit on duplicate flake inputs (#317)

This commit is contained in:
damocles 2026-05-25 23:06:31 +02:00 committed by Mara
commit 45833d0793
3 changed files with 307 additions and 2 deletions

View file

@ -633,14 +633,34 @@ async fn submit_apply_commit(
.approvals
.set_fetched_sha(id, &sha)
.map_err(|e| anyhow::anyhow!("persist fetched_sha: {e:#}"))?;
// #317 dedup gate: parse the just-fetched flake.lock and reject if
// the agent declared two inputs that resolve to the same upstream
// (a missing `follows` directive). Runs after `set_fetched_sha` so
// the failed approval row carries the sha that broke — handy when
// the manager needs to inspect the bad commit.
let sha_short = sha[..sha.len().min(12)].to_owned();
if let Err(e) = crate::flake_check::check_no_duplicate_inputs(&applied_dir, &tag).await {
let note = format!("{e:#}");
let _ = coord.approvals.mark_failed(id, &note);
coord.emit_approval_resolved(
id,
agent,
"apply_commit",
Some(sha_short.clone()),
"failed",
Some(note),
description.map(str::to_owned),
);
return Err(anyhow::anyhow!("flake dedup check: {e:#}"));
}
// Mirror the freshly-planted proposal/<id> tag to the forge.
if let Err(e) = crate::forge::push_config(agent).await {
tracing::warn!(%agent, %id, error = ?e, "forge: push_config after submit failed");
}
// Phase 5b: surface the new pending approval on the dashboard
// event channel. Compute the diff once here so live subscribers
// get a fully-formed row without a snapshot refetch.
let sha_short = sha[..sha.len().min(12)].to_owned();
// get a fully-formed row without a snapshot refetch. `sha_short`
// is reused from the dedup gate above.
let diff = crate::dashboard::approval_diff(agent, id).await;
coord.emit_approval_added(
id,