From 27a099bb5bfa56c51ab192063b18d907236a3dfb Mon Sep 17 00:00:00 2001 From: atlas Date: Thu, 16 Jul 2026 20:16:08 +0200 Subject: [PATCH] feat(#2502): re-lock deploy + rebuild from local applied, not forge The persistent agent- input url in the meta flake is now the forge config repo, but the deploy + manual-rebuild re-lock must not follow it: prepare_deploy and lock_update_for_rebuild override the input back to the local applied mirror (git+file://, main head) via --override-input, which writes the applied rev into flake.lock. This keeps the forge as the declared, reviewable source while: - surviving forge unreachability (rebuilds fire on crash-restart and meta bumps too, not just config PRs -- coupling every rebuild to forge would be a regression), - deploying exactly the reviewed head applied//main was fast-forwarded to (no TOCTOU on a newer forge head merging mid-deploy), - reusing verify_commit's local-override pattern so verify and deploy eval the same source. New applied_override_url helper + unit test. --- hive-c0re/src/meta.rs | 66 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 2 deletions(-) diff --git a/hive-c0re/src/meta.rs b/hive-c0re/src/meta.rs index 2a6ef2ea..d22e0c68 100644 --- a/hive-c0re/src/meta.rs +++ b/hive-c0re/src/meta.rs @@ -238,7 +238,29 @@ pub async fn prepare_deploy(name: &str) -> Result<()> { let _guard = META_LOCK.lock().await; let dir = crate::paths::meta_root(); let input = format!("agent-{name}"); - nix_logged(&dir, &["flake", "update", &input], name, "prepare-deploy").await?; + // Re-lock the agent input against the LOCAL applied mirror, not the + // persistent forge URL declared in the meta flake (see the `## Meta flake` + // note in docs/approvals.md): the deploy must build the exact reviewed + // config that `verify_commit` gated and `applied//main` was + // fast-forwarded to, and it must keep working when the forge is + // unreachable (rebuilds fire on crash-restart / meta bumps too, not just + // config PRs). `--override-input` writes the applied rev into the lock; + // the forge URL stays the declared, reviewable source of truth. + let applied = applied_override_url(&crate::paths::applied_dir(name)); + nix_logged( + &dir, + &[ + "flake", + "update", + &input, + "--override-input", + &input, + &applied, + ], + name, + "prepare-deploy", + ) + .await?; // Stage the new lock — git+file://'s dirty-tree fetcher reads // index entries, so the upcoming nixos-container update sees the // bumped rev without a commit yet. @@ -281,7 +303,22 @@ pub async fn lock_update_for_rebuild(name: &str) -> Result<()> { let _guard = META_LOCK.lock().await; let dir = crate::paths::meta_root(); let input = format!("agent-{name}"); - nix(&dir, &["flake", "update", &input]).await?; + // Re-lock from the local applied mirror, not the persistent forge URL — + // same rationale as `prepare_deploy`: build exactly `applied//main` and + // stay reproducible when the forge is unreachable. + let applied = applied_override_url(&crate::paths::applied_dir(name)); + nix( + &dir, + &[ + "flake", + "update", + &input, + "--override-input", + &input, + &applied, + ], + ) + .await?; if !paths_dirty(&dir, &["flake.lock"]).await? { return Ok(()); } @@ -301,6 +338,17 @@ fn agent_input_override(applied_dir: &Path, sha: &str) -> String { format!("git+file://{}?rev={sha}", applied_dir.display()) } +/// `--override-input` URL re-locking an agent's config input against its +/// LOCAL applied mirror (`git+file://`, current `main` head) +/// instead of the persistent forge URL declared in the meta flake. The +/// deploy + rebuild paths re-lock from here: the applied tree was already +/// fast-forwarded to the reviewed head, so this builds exactly that config +/// and stays reproducible when the forge is unreachable. No `?rev` — `main` +/// head is the reviewed head at deploy time. Pure so it's unit-testable. +fn applied_override_url(applied_dir: &Path) -> String { + format!("git+file://{}", applied_dir.display()) +} + /// Non-mutating "would this commit apply?" verify for the PR-based config /// flow. Evaluates the agent's nixos configuration with its meta /// input overridden to the exact `sha`, WITHOUT moving `applied/main` or @@ -1463,6 +1511,20 @@ mod tests { ); } + #[test] + fn applied_override_url_targets_local_mirror_main_head() { + // Deploy + rebuild re-lock against the local applied mirror's `main` + // head (no `?rev`), never the persistent forge URL — so a rebuild + // survives forge unreachability and builds the fast-forwarded config. + let p = Path::new("/var/lib/hyperhive/agents/iris/applied"); + let url = applied_override_url(p); + assert_eq!(url, "git+file:///var/lib/hyperhive/agents/iris/applied"); + assert!( + !url.contains("?rev="), + "must lock main head, not a pinned rev: {url}" + ); + } + #[test] fn render_flake_uses_explicit_nixpkgs_url_when_provided() { let out = render_flake(