From a43a95cc905ab68f439cdbfe2b79a9c065a9cb1d Mon Sep 17 00:00:00 2001 From: atlas Date: Sat, 30 May 2026 05:01:37 +0200 Subject: [PATCH 1/3] nix: hyperhive owns nixpkgs pin via follows; bump to nixos-26.05 (#526) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drops the hardcoded `nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"` from the meta flake renderer. meta now declares: nixpkgs.follows = "hyperhive/nixpkgs"; nixpkgs-unstable.follows = "hyperhive/nixpkgs-unstable"; so every agent-level `inputs..inputs.nixpkgs.follows = "nixpkgs"` resolves transitively to hyperhive's own pin. One channel decision in the whole tree (hyperhive/flake.nix line 5), no second source to drift. `closes #317` invariant still holds: `nixpkgs` is a single canonical name in the meta tree, it just resolves through hyperhive instead of being its own root input. Also: - bump hyperhive/flake.nix line 5: `nixos-25.11` → `nixos-26.05`, flake.lock relocked - flake_check.rs fixtures bumped cosmetically (synthetic data, not shape-affecting) - rename + rewrite `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 the now-redundant `render_flake_collapses_hyperhive_nixpkgs_via_follows` test (the old indirection it covered no longer exists) Closes #526. --- flake.lock | 8 ++-- flake.nix | 2 +- hive-c0re/src/flake_check.rs | 8 ++-- hive-c0re/src/meta.rs | 71 ++++++++++++++++++------------------ 4 files changed, 44 insertions(+), 45 deletions(-) diff --git a/flake.lock b/flake.lock index ccbe77f4..1ce362be 100644 --- a/flake.lock +++ b/flake.lock @@ -17,16 +17,16 @@ }, "nixpkgs": { "locked": { - "lastModified": 1778737229, - "narHash": "sha256-6xWoytx8jFW4PF1GjRm/i/53trbpKGfz6zjzQGBr4cI=", + "lastModified": 1779971959, + "narHash": "sha256-R5nauXyqyfRUFiZycFFZdkF7wl6eaUpPLst35+2nJQY=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "d7a713c0b7e47c908258e71cba7a2d77cc8d71d5", + "rev": "ec942ba042dad5ef097e2ef3a3effc034241f011", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-25.11", + "ref": "nixos-26.05", "repo": "nixpkgs", "type": "github" } diff --git a/flake.nix b/flake.nix index 28656c70..a4375372 100644 --- a/flake.nix +++ b/flake.nix @@ -2,7 +2,7 @@ description = "hyperhive — multi-Claude-Code-agent orchestration on nixos-containers"; inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05"; nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; # Crane (replaces naersk #538). Stateless — no nixpkgs input to # follow; `crane.mkLib pkgs` returns the lib at whatever pkgs we diff --git a/hive-c0re/src/flake_check.rs b/hive-c0re/src/flake_check.rs index 71550270..75e22413 100644 --- a/hive-c0re/src/flake_check.rs +++ b/hive-c0re/src/flake_check.rs @@ -319,7 +319,7 @@ mod tests { "nodes": { "nixpkgs": { "locked": {"rev": "aaa"}, - "original": {"owner": "NixOS", "repo": "nixpkgs", "ref": "nixos-25.11", "type": "github"} + "original": {"owner": "NixOS", "repo": "nixpkgs", "ref": "nixos-26.05", "type": "github"} }, "nixpkgs-unstable": { "locked": {"rev": "bbb"}, @@ -335,15 +335,15 @@ mod tests { "nodes": { "nixpkgs": { "locked": {"rev": "aaa"}, - "original": {"owner": "NixOS", "repo": "nixpkgs", "ref": "nixos-25.11", "type": "github"} + "original": {"owner": "NixOS", "repo": "nixpkgs", "ref": "nixos-26.05", "type": "github"} }, "nixpkgs_2": { "locked": {"rev": "ccc"}, - "original": {"owner": "NixOS", "repo": "nixpkgs", "ref": "nixos-25.11", "type": "github"} + "original": {"owner": "NixOS", "repo": "nixpkgs", "ref": "nixos-26.05", "type": "github"} }, "nixpkgs_3": { "locked": {"rev": "ddd"}, - "original": {"owner": "NixOS", "repo": "nixpkgs", "ref": "nixos-25.11", "type": "github"} + "original": {"owner": "NixOS", "repo": "nixpkgs", "ref": "nixos-26.05", "type": "github"} }, "treefmt-nix": { "locked": {"rev": "eee"}, diff --git a/hive-c0re/src/meta.rs b/hive-c0re/src/meta.rs index fe496f91..611c48f8 100644 --- a/hive-c0re/src/meta.rs +++ b/hive-c0re/src/meta.rs @@ -333,21 +333,24 @@ 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-25.11`; 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. - out.push_str(" nixpkgs.url = \"github:NixOS/nixpkgs/nixos-25.11\";\n"); - out.push_str(" nixpkgs-unstable.url = \"github:NixOS/nixpkgs/nixpkgs-unstable\";\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. + // + // 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.) 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, @@ -563,7 +566,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, @@ -571,28 +574,24 @@ 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). - assert!(out.contains("nixpkgs.url = \"github:NixOS/nixpkgs/nixos-25.11\"")); - 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(), - &[], + // 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}" ); - // 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] From d12da847400264842f1d2c2e96ba9d5f4a776e98 Mon Sep 17 00:00:00 2001 From: atlas Date: Sat, 30 May 2026 10:49:36 +0200 Subject: [PATCH 2/3] nix/meta: keep concrete nixpkgs.url per mara on #619 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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`. --- hive-c0re/src/meta.rs | 77 ++++++++++++++++++++++++------------------- 1 file changed, 43 insertions(+), 34 deletions(-) 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] From 7ab3b125ecc513325e40dee3bb626fd43e6da2db Mon Sep 17 00:00:00 2001 From: atlas Date: Sat, 30 May 2026 11:36:59 +0200 Subject: [PATCH 3/3] nix/docs: update stub-eval + index text to services.hyperhive.* namespace two stale spots in `nix/docs.nix` that #622 didn't catch: - the stub NixOS eval was force-disabling `hyperhive.{forge,matrix}.enable` on the old paths, which fail eval post-#615 (`The option `hyperhive' does not exist`) - the rendered index page text still listed the old namespace shape both updated to use `services.hyperhive.*` consistently. necessary on this branch for `nix flake check` to pass; the same fix lives on PR a no-op. --- nix/docs.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nix/docs.nix b/nix/docs.nix index bfaf5d06..b5baefc5 100644 --- a/nix/docs.nix +++ b/nix/docs.nix @@ -288,7 +288,7 @@ let echo '

hyperhive — nix options reference

' echo '

Auto-generated from the hyperhive flake. Two reading paths:

' echo '

host options

' - echo '

Options exposed by hyperhive.nixosModules.default to operator host configurations: services.hive-c0re.*, hyperhive.domain, hyperhive.forge.*, hyperhive.matrix.*.

' + echo '

Options exposed by hyperhive.nixosModules.default to operator host configurations: services.hyperhive.{enable,domain,c0re,forge,matrix,gateway}.*.

' echo '

agent options

' echo '

Per-agent options declared in nix/templates/harness-base.nix and visible from every agent.nix: hyperhive.model, hyperhive.allowedRecipients, hyperhive.extraMcpServers, hyperhive.frontend.*, hyperhive.forge.*, hyperhive.matrix.*, hyperhive.gui.*.

' echo '

Regenerate

'