nix/meta: keep concrete nixpkgs.url per mara on #619

mara on #619 comment 7354:
> agent flake needs the url actually so the configuring agent
> can eval against it

Reverts the meta.rs `nixpkgs.follows = "hyperhive/nixpkgs"` shape from
this PR's earlier commit. Restores the pre-PR rendered shape with the
concrete `nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05"` baked
into meta + the `hyperhive.inputs.nixpkgs.follows = "nixpkgs"`
redirect, so a configuring agent (or manager pre-apply check) can
evaluate the rendered meta flake without having to resolve through
hyperhive first.

Net behaviour for this PR is now:
- `flake.nix` line 5 bumped 25.11 → 26.05 (kept)
- `meta.rs` literal bumped to match (channel-pin in two places stays
  acknowledged as duplication — drift fixable later if needed)
- `flake_check.rs` test fixtures bumped cosmetically (kept)
- meta.rs tests restored to assert the concrete-URL shape

3 meta tests pass via `nix develop -c cargo test`.
This commit is contained in:
atlas 2026-05-30 10:49:36 +02:00
commit d12da84740

View file

@ -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.<X>.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]