feat(#2502): re-lock deploy + rebuild from local applied, not forge
The persistent agent-<n> 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://<applied_dir>, 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/<n>/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.
This commit is contained in:
parent
b806aa253e
commit
27a099bb5b
1 changed files with 64 additions and 2 deletions
|
|
@ -238,7 +238,29 @@ pub async fn prepare_deploy(name: &str) -> Result<()> {
|
||||||
let _guard = META_LOCK.lock().await;
|
let _guard = META_LOCK.lock().await;
|
||||||
let dir = crate::paths::meta_root();
|
let dir = crate::paths::meta_root();
|
||||||
let input = format!("agent-{name}");
|
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/<n>/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
|
// Stage the new lock — git+file://'s dirty-tree fetcher reads
|
||||||
// index entries, so the upcoming nixos-container update sees the
|
// index entries, so the upcoming nixos-container update sees the
|
||||||
// bumped rev without a commit yet.
|
// 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 _guard = META_LOCK.lock().await;
|
||||||
let dir = crate::paths::meta_root();
|
let dir = crate::paths::meta_root();
|
||||||
let input = format!("agent-{name}");
|
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/<n>/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? {
|
if !paths_dirty(&dir, &["flake.lock"]).await? {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
@ -301,6 +338,17 @@ fn agent_input_override(applied_dir: &Path, sha: &str) -> String {
|
||||||
format!("git+file://{}?rev={sha}", applied_dir.display())
|
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://<applied_dir>`, 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
|
/// Non-mutating "would this commit apply?" verify for the PR-based config
|
||||||
/// flow. Evaluates the agent's nixos configuration with its meta
|
/// flow. Evaluates the agent's nixos configuration with its meta
|
||||||
/// input overridden to the exact `sha`, WITHOUT moving `applied/main` or
|
/// 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]
|
#[test]
|
||||||
fn render_flake_uses_explicit_nixpkgs_url_when_provided() {
|
fn render_flake_uses_explicit_nixpkgs_url_when_provided() {
|
||||||
let out = render_flake(
|
let out = render_flake(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue