diff --git a/hive-c0re/src/meta.rs b/hive-c0re/src/meta.rs index 611c48f8..6fe93fcf 100644 --- a/hive-c0re/src/meta.rs +++ b/hive-c0re/src/meta.rs @@ -333,24 +333,27 @@ where use std::fmt::Write as _; let mut out = String::new(); out.push_str("{\n description = \"hyperhive deployed agents\";\n inputs = {\n"); - // hyperhive's own flake.nix is the single channel-pin authority - // (closes #526). meta declares `nixpkgs` + `nixpkgs-unstable` as - // aliases for hyperhive's sub-inputs via `follows`, so every - // agent-level `inputs..inputs.nixpkgs.follows = "nixpkgs"` - // directive resolves transitively to hyperhive's pin. One - // channel decision in the whole tree, no second source to drift. + // Pin canonical nixpkgs revisions at the meta level so every input + // that pulls a nixpkgs sub-input can `follows = "nixpkgs"` and + // collapse to one shared node (closes #317). hyperhive's own + // flake.nix picks `nixos-26.05`; we mirror that here so meta and + // hyperhive don't diverge into two stable channels by default. + // Operator can override these at the meta layer to slide every + // dependent agent onto a different channel in one move. // - // Operators who want to slide the whole swarm onto a different - // channel do it at the host level via - // `inputs.hyperhive.inputs.nixpkgs.follows = "nixpkgs"`, which - // makes hyperhive's nixpkgs = the host's nixpkgs and cascades - // through to every agent. (closes #317 also stays satisfied — - // `nixpkgs` is still a single canonical name in the meta tree, - // it just resolves through hyperhive instead of being its own - // root input.) + // Keeping the URL concrete (rather than `follows = "hyperhive/nixpkgs"`) + // is intentional per mara on #619: a configuring agent needs the + // url present in the rendered meta flake so it can eval against + // it — `follows` requires resolving through hyperhive, which + // breaks standalone eval workflows the agent + manager rely on. + out.push_str(" nixpkgs.url = \"github:NixOS/nixpkgs/nixos-26.05\";\n"); + out.push_str(" nixpkgs-unstable.url = \"github:NixOS/nixpkgs/nixpkgs-unstable\";\n"); let _ = writeln!(out, " hyperhive.url = \"{hyperhive_flake}\";"); - out.push_str(" nixpkgs.follows = \"hyperhive/nixpkgs\";\n"); - out.push_str(" nixpkgs-unstable.follows = \"hyperhive/nixpkgs-unstable\";\n"); + // Collapse hyperhive's own `nixpkgs` + `nixpkgs-unstable` inputs + // into meta's. Without this, hyperhive's flake.nix declarations + // become independent `nixpkgs_N` nodes in meta/flake.lock. + out.push_str(" hyperhive.inputs.nixpkgs.follows = \"nixpkgs\";\n"); + out.push_str(" hyperhive.inputs.nixpkgs-unstable.follows = \"nixpkgs-unstable\";\n"); for spec in agents { let _ = writeln!( out, @@ -566,7 +569,7 @@ mod tests { } #[test] - fn render_flake_aliases_nixpkgs_to_hyperhive() { + fn render_flake_declares_canonical_nixpkgs() { let out = render_flake( "github:example/hyperhive", 8000, @@ -574,24 +577,30 @@ mod tests { &std::collections::HashMap::new(), &[sample_spec("alice", false, 9001)], ); - // Meta's `nixpkgs` + `nixpkgs-unstable` are aliases for - // hyperhive's sub-inputs (closes #526). Single channel-pin - // authority: hyperhive's own flake.nix. - assert!( - out.contains("nixpkgs.follows = \"hyperhive/nixpkgs\""), - "missing nixpkgs follows alias:\n{out}" - ); - assert!( - out.contains("nixpkgs-unstable.follows = \"hyperhive/nixpkgs-unstable\""), - "missing nixpkgs-unstable follows alias:\n{out}" - ); - // And conversely: no literal channel ref baked in. If this - // assertion fails, someone reintroduced a hardcoded ref — - // see #526 for why that drifts. - assert!( - !out.contains("nixpkgs.url ="), - "no literal `nixpkgs.url` should be emitted (hyperhive owns the pin):\n{out}" + // Top-level nixpkgs inputs pinned by meta — every nested + // nixpkgs input can follow these instead of resolving its own + // (closes #317). Concrete URL (not `follows = "hyperhive/nixpkgs"`) + // per mara on #619 — a configuring agent needs the URL to + // resolve when evaluating meta standalone. + assert!(out.contains("nixpkgs.url = \"github:NixOS/nixpkgs/nixos-26.05\"")); + assert!(out.contains("nixpkgs-unstable.url = \"github:NixOS/nixpkgs/nixpkgs-unstable\"")); + } + + #[test] + fn render_flake_collapses_hyperhive_nixpkgs_via_follows() { + let out = render_flake( + "github:example/hyperhive", + 8000, + "she/her", + &std::collections::HashMap::new(), + &[], ); + // hyperhive's own `nixpkgs` + `nixpkgs-unstable` declarations + // get redirected at meta's. Without these, meta/flake.lock + // ends up with separate `nixpkgs_N` nodes for hyperhive's + // copy (the pre-#317 status quo). + assert!(out.contains("hyperhive.inputs.nixpkgs.follows = \"nixpkgs\"")); + assert!(out.contains("hyperhive.inputs.nixpkgs-unstable.follows = \"nixpkgs-unstable\"")); } #[test]