nix/meta: follow hyperhive's nixpkgs instead of duplicating channel pin

mara via triage on #619 (post-merge follow-up):
> "I thought all nixpkgs follow the one hyperhive was deployed
>  with?! if not, thats what we should fix."

This is the fix. Flip the rendered meta flake from:

    nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
    nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
    hyperhive.url = "...";
    hyperhive.inputs.nixpkgs.follows = "nixpkgs";
    hyperhive.inputs.nixpkgs-unstable.follows = "nixpkgs-unstable";

to:

    hyperhive.url = "...";
    nixpkgs.follows = "hyperhive/nixpkgs";
    nixpkgs-unstable.follows = "hyperhive/nixpkgs-unstable";

Net effect: hyperhive's `flake.nix` is the single channel-pin
authority. 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 every
agent transparently.

This is the same shape I shipped in #619 v1 (then reverted per mara
on comment 7354, then re-affirmed via triage). Going with the
re-affirmed direction.

Closes #317 invariant still satisfied — `nixpkgs` is a single
canonical name in the meta tree, just resolving through hyperhive
instead of being its own root input.

Tests:
- rename `render_flake_declares_canonical_nixpkgs` →
  `render_flake_aliases_nixpkgs_to_hyperhive`, asserts the new
  follows-form AND the absence of any literal `nixpkgs.url`
- drop `render_flake_collapses_hyperhive_nixpkgs_via_follows` (no
  separate meta-level nixpkgs to collapse anymore)
This commit is contained in:
atlas 2026-05-30 13:08:34 +02:00 committed by Mara
commit 8377600f0c

View file

@ -333,27 +333,30 @@ where
use std::fmt::Write as _;
let mut out = String::new();
out.push_str("{\n description = \"hyperhive deployed agents\";\n inputs = {\n");
// 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.
// hyperhive's own flake.nix is the single channel-pin authority.
// 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.
//
// 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");
// Per mara via triage on #619: "all nixpkgs follow the one
// hyperhive was deployed with — if not, that's what we should
// fix." This is the fix (flip from the concrete-url shape
// #619 originally shipped with).
//
// 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 invariant still satisfied — `nixpkgs` is still a
// single canonical name in the meta tree, it just resolves
// through hyperhive instead of being its own root input.
let _ = writeln!(out, " hyperhive.url = \"{hyperhive_flake}\";");
// 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");
out.push_str(" nixpkgs.follows = \"hyperhive/nixpkgs\";\n");
out.push_str(" nixpkgs-unstable.follows = \"hyperhive/nixpkgs-unstable\";\n");
for spec in agents {
let _ = writeln!(
out,
@ -569,7 +572,7 @@ mod tests {
}
#[test]
fn render_flake_declares_canonical_nixpkgs() {
fn render_flake_aliases_nixpkgs_to_hyperhive() {
let out = render_flake(
"github:example/hyperhive",
8000,
@ -577,31 +580,32 @@ mod tests {
&std::collections::HashMap::new(),
&[sample_spec("alice", false, 9001)],
);
// 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\""));
// Meta's `nixpkgs` + `nixpkgs-unstable` are aliases for
// hyperhive's sub-inputs. Single channel-pin authority:
// hyperhive's own flake.nix. Per mara via triage on #619:
// "all nixpkgs follow the one hyperhive was deployed with."
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 into meta. If
// this fails, someone reintroduced a hardcoded ref — see
// #619 follow-up rationale for why that drifts.
assert!(
!out.contains("nixpkgs.url ="),
"no literal `nixpkgs.url` should be emitted (hyperhive owns the pin):\n{out}"
);
}
#[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\""));
}
// `render_flake_collapses_hyperhive_nixpkgs_via_follows` dropped:
// with meta's `nixpkgs.follows = "hyperhive/nixpkgs"`, there's no
// separate meta-level nixpkgs to collapse hyperhive's into. The
// redirect goes the other way now (the alias test above covers
// the new invariant).
#[test]
fn render_flake_emits_follows_for_agents_declaring_nixpkgs() {