diff --git a/flake.nix b/flake.nix index d82e9169..6d746ab6 100644 --- a/flake.nix +++ b/flake.nix @@ -236,11 +236,6 @@ hyperhiveFrontend = system: self.packages.${system}.frontend; hyperhiveAssets = system: self.packages.${system}.assets; hyperhiveFlake = "${self}"; - # Store path of the nixpkgs-unstable input this flake was evaluated - # with — the channel that carries claude-code. Passed as the default - # for `services.hyperhive.c0re.nixpkgsUnstableFlake` so operators can - # override it without touching this file. - hyperhiveNixpkgsUnstable = "path:${nixpkgs-unstable}"; # Per-container toplevels — wired into `system.extraDependencies` # when `services.hyperhive.c0re.preBuildAgentTemplates` is on so the # host system closure pre-fetches the heavy build inputs (#97). diff --git a/hive-c0re/src/actions.rs b/hive-c0re/src/actions.rs index 9412f942..1905ac0b 100644 --- a/hive-c0re/src/actions.rs +++ b/hive-c0re/src/actions.rs @@ -224,8 +224,6 @@ pub async fn run_approval_spawn( let result = lifecycle::spawn( &approval.agent, &coord.hyperhive_flake, - &coord.nixpkgs_flake, - &coord.nixpkgs_unstable_flake, &agent_dir, &proposed_dir, &applied_dir, @@ -545,8 +543,6 @@ async fn run_apply_commit( }; if let Err(e) = crate::meta::sync_agents( &coord.hyperhive_flake, - &coord.nixpkgs_flake, - &coord.nixpkgs_unstable_flake, coord.dashboard_port, &coord.operator_pronouns, &coord.context_window_tokens, @@ -717,8 +713,6 @@ async fn sync_meta_after_lifecycle(coord: &Coordinator) -> Result<()> { let agents = lifecycle::agents_for_meta_listing().await?; crate::meta::sync_agents( &coord.hyperhive_flake, - &coord.nixpkgs_flake, - &coord.nixpkgs_unstable_flake, coord.dashboard_port, &coord.operator_pronouns, &coord.context_window_tokens, diff --git a/hive-c0re/src/auto_update.rs b/hive-c0re/src/auto_update.rs index 7c8bfdac..856b1caf 100644 --- a/hive-c0re/src/auto_update.rs +++ b/hive-c0re/src/auto_update.rs @@ -88,8 +88,6 @@ pub async fn rebuild_agent( let result = lifecycle::rebuild( name, &coord.hyperhive_flake, - &coord.nixpkgs_flake, - &coord.nixpkgs_unstable_flake, &agent_dir, &applied_dir, &claude_dir, @@ -192,8 +190,6 @@ pub async fn ensure_manager(coord: &Arc) -> Result<()> { lifecycle::spawn( MANAGER_NAME, &coord.hyperhive_flake, - &coord.nixpkgs_flake, - &coord.nixpkgs_unstable_flake, &runtime, &proposed, &applied, diff --git a/hive-c0re/src/coordinator.rs b/hive-c0re/src/coordinator.rs index f9d3f7d7..7bea60bc 100644 --- a/hive-c0re/src/coordinator.rs +++ b/hive-c0re/src/coordinator.rs @@ -51,19 +51,6 @@ pub struct Coordinator { /// URL of the hyperhive flake (no fragment). Inlined into per-agent /// `flake.nix` files as `inputs.hyperhive.url`. pub hyperhive_flake: String, - /// Store-path URL of the nixpkgs to wire into the meta flake as - /// `inputs.nixpkgs.url`. Populated by `--nixpkgs-flake` (set by the - /// NixOS module to `"path:${pkgs.path}"` so the meta flake always - /// tracks the same nixpkgs the host evaluated with — which is the - /// host's nixpkgs when `inputs.hyperhive.inputs.nixpkgs.follows = - /// "nixpkgs"` is set in the host flake). Empty string = legacy - /// `follows = "hyperhive/nixpkgs"` behaviour. - pub nixpkgs_flake: String, - /// Store-path URL for `nixpkgs-unstable` to wire as a top-level meta - /// flake input. Hyperhive's `inputs.nixpkgs-unstable` then follows it. - /// Set via `--nixpkgs-unstable-flake` from `hive-c0re.nix`. Empty string - /// falls back to the legacy `follows = "hyperhive/nixpkgs-unstable"`. - pub nixpkgs_unstable_flake: String, /// TCP port the host's hive-c0re dashboard listens on. Inlined into /// each per-agent flake so the agent's web UI can build the right /// rebuild-button URL pointing back at the dashboard. @@ -224,8 +211,6 @@ impl Coordinator { pub fn open( db_path: &Path, hyperhive_flake: String, - nixpkgs_flake: String, - nixpkgs_unstable_flake: String, dashboard_port: u16, operator_pronouns: String, context_window_tokens: std::collections::HashMap, @@ -257,8 +242,6 @@ impl Coordinator { scheduled_prompts: Arc::new(scheduled_prompts), build_logs, hyperhive_flake, - nixpkgs_flake, - nixpkgs_unstable_flake, dashboard_port, operator_pronouns, context_window_tokens, diff --git a/hive-c0re/src/lifecycle.rs b/hive-c0re/src/lifecycle.rs index 1dcd4866..516ae0db 100644 --- a/hive-c0re/src/lifecycle.rs +++ b/hive-c0re/src/lifecycle.rs @@ -185,8 +185,6 @@ async fn port_collision(self_name: &str) -> Option { pub async fn spawn( name: &str, hyperhive_flake: &str, - nixpkgs_flake: &str, - nixpkgs_unstable_flake: &str, agent_dir: &Path, proposed_dir: &Path, applied_dir: &Path, @@ -213,8 +211,6 @@ pub async fn spawn( let agents = agents_after_spawn(name).await?; crate::meta::sync_agents( hyperhive_flake, - nixpkgs_flake, - nixpkgs_unstable_flake, dashboard_port, operator_pronouns, context_window_tokens, @@ -353,8 +349,6 @@ pub async fn destroy(name: &str) -> Result<()> { pub async fn rebuild( name: &str, hyperhive_flake: &str, - nixpkgs_flake: &str, - nixpkgs_unstable_flake: &str, agent_dir: &Path, applied_dir: &Path, claude_dir: &Path, @@ -372,8 +366,6 @@ pub async fn rebuild( let agents = agents_for_meta(None).await?; crate::meta::sync_agents( hyperhive_flake, - nixpkgs_flake, - nixpkgs_unstable_flake, dashboard_port, operator_pronouns, context_window_tokens, diff --git a/hive-c0re/src/main.rs b/hive-c0re/src/main.rs index d463ba0a..6d149abf 100644 --- a/hive-c0re/src/main.rs +++ b/hive-c0re/src/main.rs @@ -36,23 +36,6 @@ enum Cmd { /// `flake.nix` as the `hyperhive` input. #[arg(long, default_value = "/etc/hyperhive")] hyperhive_flake: String, - /// Store-path URL of the nixpkgs to wire into the meta flake as - /// `inputs.nixpkgs.url`. Set by the NixOS module to - /// `"path:${pkgs.path}"` so the meta flake tracks exactly the - /// nixpkgs the host was evaluated with (the host's own nixpkgs - /// when `inputs.hyperhive.inputs.nixpkgs.follows = "nixpkgs"` is - /// set, otherwise hyperhive's pin). Empty = legacy - /// `follows = "hyperhive/nixpkgs"` fallback. - #[arg(long, default_value = "")] - nixpkgs_flake: String, - /// Store-path URL of the nixpkgs-unstable to wire into the meta - /// flake as `inputs.nixpkgs-unstable.url`. Hyperhive's - /// `inputs.nixpkgs-unstable` then follows this top-level input. - /// Set by the NixOS module; defaults to the hyperhive flake's own - /// nixpkgs-unstable store path. Empty = legacy - /// `follows = "hyperhive/nixpkgs-unstable"` fallback. - #[arg(long, default_value = "")] - nixpkgs_unstable_flake: String, /// Path to the sqlite message store. #[arg(long, default_value = "/var/lib/hyperhive/broker.sqlite")] db: PathBuf, @@ -135,8 +118,6 @@ async fn main() -> Result<()> { match cli.cmd { Cmd::Serve { hyperhive_flake, - nixpkgs_flake, - nixpkgs_unstable_flake, db, dashboard_port, operator_pronouns, @@ -144,8 +125,6 @@ async fn main() -> Result<()> { } => { cmd_serve( hyperhive_flake, - nixpkgs_flake, - nixpkgs_unstable_flake, db, dashboard_port, operator_pronouns, @@ -193,8 +172,6 @@ async fn main() -> Result<()> { /// dashboard), then serve the admin socket until a signal arrives. async fn cmd_serve( hyperhive_flake: String, - nixpkgs_flake: String, - nixpkgs_unstable_flake: String, db: std::path::PathBuf, dashboard_port: u16, operator_pronouns: String, @@ -206,8 +183,6 @@ async fn cmd_serve( let coord = Arc::new(Coordinator::open( &db, hyperhive_flake, - nixpkgs_flake, - nixpkgs_unstable_flake, dashboard_port, operator_pronouns, cwt, diff --git a/hive-c0re/src/meta.rs b/hive-c0re/src/meta.rs index a155e1b0..8bd21f32 100644 --- a/hive-c0re/src/meta.rs +++ b/hive-c0re/src/meta.rs @@ -52,8 +52,6 @@ pub fn meta_dir() -> PathBuf { #[allow(dead_code, clippy::implicit_hasher)] // first caller lands in a later commit pub async fn sync_agents( hyperhive_flake: &str, - nixpkgs_flake: &str, - nixpkgs_unstable_flake: &str, dashboard_port: u16, operator_pronouns: &str, context_window_tokens: &std::collections::HashMap, @@ -65,8 +63,6 @@ pub async fn sync_agents( let new_flake = render_flake( hyperhive_flake, - nixpkgs_flake, - nixpkgs_unstable_flake, dashboard_port, operator_pronouns, context_window_tokens, @@ -304,8 +300,6 @@ pub async fn lock_update_hyperhive() -> Result<()> { fn render_flake( hyperhive_flake: &str, - nixpkgs_flake: &str, - nixpkgs_unstable_flake: &str, dashboard_port: u16, operator_pronouns: &str, context_window_tokens: &std::collections::HashMap, @@ -313,8 +307,6 @@ fn render_flake( ) -> String { render_flake_with_lookup( hyperhive_flake, - nixpkgs_flake, - nixpkgs_unstable_flake, dashboard_port, operator_pronouns, context_window_tokens, @@ -400,8 +392,6 @@ fn agent_canonical_inputs(name: &str) -> Vec<&'static str> { )] fn render_flake_with_lookup( hyperhive_flake: &str, - nixpkgs_flake: &str, - nixpkgs_unstable_flake: &str, dashboard_port: u16, operator_pronouns: &str, context_window_tokens: &std::collections::HashMap, @@ -414,35 +404,28 @@ where use std::fmt::Write as _; let mut out = String::new(); out.push_str("{\n description = \"hyperhive deployed agents\";\n inputs = {\n"); - // `nixpkgs` + `nixpkgs-unstable` are top-level meta inputs with explicit - // store-path URLs. `hyperhive` then follows them via - // `hyperhive.inputs.*.follows`. This cascades through to every agent - // because `agent-.inputs.nixpkgs.follows = "nixpkgs"` resolves to - // the same top-level node. + // 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..inputs.nixpkgs.follows = "nixpkgs"` directive + // resolves transitively to hyperhive's pin. One channel decision + // in the whole tree, no second source to drift. // - // Why explicit `path:` URLs instead of - // `nixpkgs.follows = "hyperhive/nixpkgs"`: - // meta points to hyperhive's *store path* as its flake input, so nix - // reads hyperhive's own pinned lock when evaluating that input — the - // host-level `follows` the operator set never propagates. Injecting the - // evaluated `pkgs.path` / nixpkgs-unstable path directly at nix-module - // evaluation time is the only reliable way to honour the host's channel - // choice. + // All nixpkgs follow the one hyperhive was deployed with — + // anything else would drift. // - // Fallback (both flake args empty): legacy `follows` wiring — used when - // hive-c0re is not built with this option wired up. - if nixpkgs_flake.is_empty() { - // Legacy path: meta defers to hyperhive's own lock. - 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"); - } else { - let _ = writeln!(out, " nixpkgs.url = \"{nixpkgs_flake}\";"); - let _ = writeln!(out, " nixpkgs-unstable.url = \"{nixpkgs_unstable_flake}\";"); - let _ = writeln!(out, " hyperhive.url = \"{hyperhive_flake}\";"); - out.push_str(" hyperhive.inputs.nixpkgs.follows = \"nixpkgs\";\n"); - out.push_str(" hyperhive.inputs.nixpkgs-unstable.follows = \"nixpkgs-unstable\";\n"); - } + // 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. + // + // `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}\";"); + 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, @@ -704,67 +687,41 @@ mod tests { } #[test] - fn render_flake_uses_explicit_nixpkgs_url_when_provided() { + fn render_flake_aliases_nixpkgs_to_hyperhive() { let out = render_flake( "github:example/hyperhive", - "path:/nix/store/aaaa-nixpkgs-source", - "path:/nix/store/bbbb-nixpkgs-unstable-source", - 8000, - "she/her", - &std::collections::HashMap::new(), - &[sample_spec("alice", false, 9001)], - ); - // Both nixpkgs + nixpkgs-unstable are top-level inputs with - // explicit URLs; hyperhive follows them. - assert!( - out.contains("nixpkgs.url = \"path:/nix/store/aaaa-nixpkgs-source\""), - "expected explicit nixpkgs.url:\n{out}" - ); - assert!( - out.contains("nixpkgs-unstable.url = \"path:/nix/store/bbbb-nixpkgs-unstable-source\""), - "expected explicit nixpkgs-unstable.url:\n{out}" - ); - assert!( - out.contains("hyperhive.inputs.nixpkgs.follows = \"nixpkgs\""), - "expected hyperhive.inputs.nixpkgs.follows:\n{out}" - ); - assert!( - out.contains("hyperhive.inputs.nixpkgs-unstable.follows = \"nixpkgs-unstable\""), - "expected hyperhive.inputs.nixpkgs-unstable.follows:\n{out}" - ); - assert!( - !out.contains("nixpkgs.follows = \"hyperhive"), - "old-style follows must not appear when flake args are set:\n{out}" - ); - } - - #[test] - fn render_flake_falls_back_to_follows_when_nixpkgs_flake_empty() { - // Empty nixpkgs_flake → legacy follows behaviour (backward compat - // for any code path that can't inject pkgs.path). - let out = render_flake( - "github:example/hyperhive", - "", - "", 8000, "she/her", &std::collections::HashMap::new(), &[sample_spec("alice", false, 9001)], ); + // Meta's `nixpkgs` + `nixpkgs-unstable` are aliases for + // hyperhive's sub-inputs. Single channel-pin authority: + // hyperhive's own flake.nix. All nixpkgs follow the one + // hyperhive was deployed with. assert!( out.contains("nixpkgs.follows = \"hyperhive/nixpkgs\""), - "expected fallback follows:\n{out}" + "missing nixpkgs follows alias:\n{out}" ); assert!( out.contains("nixpkgs-unstable.follows = \"hyperhive/nixpkgs-unstable\""), - "expected fallback unstable follows:\n{out}" + "missing nixpkgs-unstable follows alias:\n{out}" ); + // And conversely: no literal channel ref baked into meta. If + // this fails, someone reintroduced a hardcoded ref — would + // drift away from hyperhive's pin. assert!( !out.contains("nixpkgs.url ="), - "no explicit url should be emitted in fallback mode:\n{out}" + "no literal `nixpkgs.url` should be emitted (hyperhive owns the pin):\n{out}" ); } + // `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() { // Stub lookup: pretend `bitburner` declares `nixpkgs` at its @@ -778,8 +735,6 @@ mod tests { }; let out = render_flake_with_lookup( "github:example/hyperhive", - "path:/nix/store/aaaa-nixpkgs-source", - "path:/nix/store/bbbb-nixpkgs-unstable-source", 8000, "she/her", &std::collections::HashMap::new(), @@ -813,8 +768,6 @@ mod tests { fn render_flake_skips_canonical_follows_when_lookup_returns_empty() { let out = render_flake_with_lookup( "github:example/hyperhive", - "path:/nix/store/aaaa-nixpkgs-source", - "path:/nix/store/bbbb-nixpkgs-unstable-source", 8000, "she/her", &std::collections::HashMap::new(), diff --git a/hive-c0re/src/migrate.rs b/hive-c0re/src/migrate.rs index df4fc050..8b37643d 100644 --- a/hive-c0re/src/migrate.rs +++ b/hive-c0re/src/migrate.rs @@ -78,8 +78,6 @@ pub async fn run(coord: &Arc) -> Result<()> { .unwrap_or_default(); if let Err(e) = meta::sync_agents( &coord.hyperhive_flake, - &coord.nixpkgs_flake, - &coord.nixpkgs_unstable_flake, coord.dashboard_port, &coord.operator_pronouns, &coord.context_window_tokens, diff --git a/hive-c0re/src/server.rs b/hive-c0re/src/server.rs index 0987ef48..62d7410f 100644 --- a/hive-c0re/src/server.rs +++ b/hive-c0re/src/server.rs @@ -87,8 +87,6 @@ async fn dispatch(req: &HostRequest, coord: Arc) -> HostResponse { match lifecycle::spawn( name, &coord.hyperhive_flake, - &coord.nixpkgs_flake, - &coord.nixpkgs_unstable_flake, &agent_dir, &proposed_dir, &applied_dir, @@ -153,8 +151,6 @@ async fn dispatch(req: &HostRequest, coord: Arc) -> HostResponse { let result = lifecycle::rebuild( name, &coord.hyperhive_flake, - &coord.nixpkgs_flake, - &coord.nixpkgs_unstable_flake, &agent_dir, &applied_dir, &claude_dir, diff --git a/nix/modules/hive-c0re.nix b/nix/modules/hive-c0re.nix index 8b31026d..38a7f158 100644 --- a/nix/modules/hive-c0re.nix +++ b/nix/modules/hive-c0re.nix @@ -3,7 +3,6 @@ hyperhiveFrontend, hyperhiveAssets, hyperhiveFlake, - hyperhiveNixpkgsUnstable, agentBaseToplevel, managerToplevel, }: @@ -188,43 +187,6 @@ in only override if you want agents tracking a different ref. ''; }; - nixpkgsFlake = lib.mkOption { - type = lib.types.str; - default = "path:${pkgs.path}"; - defaultText = lib.literalMD "`\"path:\${pkgs.path}\"`"; - description = '' - Store-path URL for the `nixpkgs` input in the generated meta - flake. The meta flake declares this as a top-level input and - wires `inputs.hyperhive.inputs.nixpkgs.follows = "nixpkgs"` so - every agent container evaluates with this exact nixpkgs. - - Defaults to `"path:''${pkgs.path}"` — the store path of the - nixpkgs the host NixOS module was evaluated with. When the - operator sets `inputs.hyperhive.inputs.nixpkgs.follows = - "nixpkgs"` in their host flake, `pkgs.path` resolves to the - host's own nixpkgs, so agents transparently track the same - channel as the host. - - Override to pin agents to a specific nixpkgs version regardless - of the host's channel. - ''; - }; - nixpkgsUnstableFlake = lib.mkOption { - type = lib.types.str; - default = hyperhiveNixpkgsUnstable; - defaultText = lib.literalMD "hyperhive's own `nixpkgs-unstable` store path"; - description = '' - Store-path URL for the `nixpkgs-unstable` input in the generated - meta flake. The meta flake declares this as a top-level input and - wires `inputs.hyperhive.inputs.nixpkgs-unstable.follows = - "nixpkgs-unstable"` so agents use this exact unstable nixpkgs. - - Defaults to the store path of the `nixpkgs-unstable` input - hyperhive's own `flake.nix` was evaluated with (the channel that - carries `claude-code`). Override when you want to track a newer - unstable snapshot or a custom `claude-code` package. - ''; - }; dashboardPort = lib.mkOption { type = lib.types.port; default = 7000; @@ -411,7 +373,7 @@ in ); }; serviceConfig = { - ExecStart = "${cfg.package}/bin/hive-c0re --socket /run/hyperhive/host.sock serve --hyperhive-flake ${cfg.hyperhiveFlake} --nixpkgs-flake ${cfg.nixpkgsFlake} --nixpkgs-unstable-flake ${cfg.nixpkgsUnstableFlake} --dashboard-port ${toString cfg.dashboardPort} --operator-pronouns ${lib.escapeShellArg cfg.operatorPronouns} --context-window-tokens ${lib.escapeShellArg (builtins.toJSON cfg.contextWindowTokens)}"; + ExecStart = "${cfg.package}/bin/hive-c0re --socket /run/hyperhive/host.sock serve --hyperhive-flake ${cfg.hyperhiveFlake} --dashboard-port ${toString cfg.dashboardPort} --operator-pronouns ${lib.escapeShellArg cfg.operatorPronouns} --context-window-tokens ${lib.escapeShellArg (builtins.toJSON cfg.contextWindowTokens)}"; Restart = "on-failure"; RestartSec = 2; RuntimeDirectory = "hyperhive";