Compare commits
17 changed files with 345 additions and 159 deletions
13
README.md
13
README.md
|
|
@ -117,7 +117,10 @@ covers spreading a swarm across hosts.
|
||||||
{
|
{
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
|
||||||
hyperhive.url = "git+https://forge.darkest.space/hyperhive/hyperhive";
|
hyperhive = {
|
||||||
|
url = "git+https://forge.darkest.space/hyperhive/hyperhive";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs"; # see "Overriding nixpkgs"
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = { nixpkgs, hyperhive, ... }: {
|
outputs = { nixpkgs, hyperhive, ... }: {
|
||||||
|
|
@ -157,6 +160,14 @@ identity, creating your SSO account. Walk through
|
||||||
**[`docs/getting-started/setup.md`](docs/getting-started/setup.md)**
|
**[`docs/getting-started/setup.md`](docs/getting-started/setup.md)**
|
||||||
once, top to bottom.
|
once, top to bottom.
|
||||||
|
|
||||||
|
### Overriding nixpkgs
|
||||||
|
|
||||||
|
hyperhive pins its own `nixpkgs` so it builds standalone in CI. The
|
||||||
|
`follows` line above builds it against your host's `nixpkgs` instead: one
|
||||||
|
evaluation, no version drift. It works as long as your channel is close to
|
||||||
|
the `nixos-26.05` hyperhive develops against — drop it if a much older or
|
||||||
|
newer channel breaks in ways hyperhive's CI doesn't catch.
|
||||||
|
|
||||||
## Operating it
|
## Operating it
|
||||||
|
|
||||||
- **swarm UI** — the day-to-day surface, on the swarm apex behind SSO
|
- **swarm UI** — the day-to-day surface, on the swarm apex behind SSO
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,12 @@ Where it runs and how it's enabled: [`services.md`](services.md#builder).
|
||||||
3. The builder runs `nix build` of `nix/builder/check-flake`'s `check` output
|
3. The builder runs `nix build` of `nix/builder/check-flake`'s `check` output
|
||||||
with every input overridden:
|
with every input overridden:
|
||||||
- `hyperhive` and `nixpkgs`: the sources the builder's own host was
|
- `hyperhive` and `nixpkgs`: the sources the builder's own host was
|
||||||
deployed with (`c0re.hyperhiveFlake`, `c0re.nixpkgsFlake`). A hive's meta
|
deployed with, the same two a hive on that host renders into its meta
|
||||||
flake pins `hyperhive` by forge URL and rev instead.
|
flake (`c0re.hyperhiveFlake`, `c0re.nixpkgsFlake`).
|
||||||
|
- `hyperhive-docs`: the `docs/` source a hive on that host renders into its
|
||||||
|
meta flake (`c0re.hyperhiveDocs`). The check sets
|
||||||
|
`services.hyperhive.agent.docs.source` from it, as the meta flake does,
|
||||||
|
since the `hyperhive` source has no `docs/`.
|
||||||
- `agent`: `git+https://<forge>/agent-configs/<agent>?ref=refs/pull/<pr>/head&rev=<head>`.
|
- `agent`: `git+https://<forge>/agent-configs/<agent>?ref=refs/pull/<pr>/head&rev=<head>`.
|
||||||
A build has one hour.
|
A build has one hour.
|
||||||
4. The reply is the system closure's store path, or the last lines `nix`
|
4. The reply is the system closure's store path, or the last lines `nix`
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,9 @@ markdown is readable during every turn.
|
||||||
Off by default for every agent, ruth included. An agent opts in by adding the
|
Off by default for every agent, ruth included. An agent opts in by adding the
|
||||||
line above to its `agent.nix`.
|
line above to its `agent.nix`.
|
||||||
|
|
||||||
The docs come from the pinned `hyperhive` input (the `reference-docs` package
|
The `docs/` source is a narrow flake input (`hyperhive-docs`) tracked
|
||||||
built from its `docs/` tree), so they match the hyperhive rev the host runs.
|
separately from the main `hyperhive` flake so editing docs re-locks only
|
||||||
|
that input — not every agent's container gets rebuilt on a doc-only change.
|
||||||
|
|
||||||
## Agent icon
|
## Agent icon
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
# Thin entry point — the real logic lives under nix/:
|
# Thin entry point — the real logic lives under nix/:
|
||||||
# nix/sources.nix filtered source views (the builder's hyperhive input)
|
# nix/sources.nix filtered source views (meta-flake + docs inputs)
|
||||||
# nix/rust.nix shared crane wiring (cleanSrc, cargoArtifacts)
|
# nix/rust.nix shared crane wiring (cleanSrc, cargoArtifacts)
|
||||||
# nix/packages/ every package output
|
# nix/packages/ every package output
|
||||||
# nix/checks.nix flake checks
|
# nix/checks.nix flake checks
|
||||||
|
|
@ -129,7 +129,9 @@
|
||||||
assets = lib.mkDefault self.packages.${pkgs.stdenv.hostPlatform.system}.assets;
|
assets = lib.mkDefault self.packages.${pkgs.stdenv.hostPlatform.system}.assets;
|
||||||
xdgIcons = lib.mkDefault self.packages.${pkgs.stdenv.hostPlatform.system}.xdg-icons;
|
xdgIcons = lib.mkDefault self.packages.${pkgs.stdenv.hostPlatform.system}.xdg-icons;
|
||||||
hyperhiveFlake = lib.mkDefault "${sources.hyperhiveFlakeSource}";
|
hyperhiveFlake = lib.mkDefault "${sources.hyperhiveFlakeSource}";
|
||||||
hyperhiveRev = lib.mkDefault (self.rev or null);
|
# Narrow docs/ source, threaded as its own meta-flake input
|
||||||
|
# so doc edits don't re-hash the whole flake source.
|
||||||
|
hyperhiveDocs = lib.mkDefault "${sources.hyperhiveDocsSource}";
|
||||||
# The agent-base toplevel every container builds from — wired into
|
# The agent-base toplevel every container builds from — wired into
|
||||||
# `system.extraDependencies` when
|
# `system.extraDependencies` when
|
||||||
# `services.hyperhive.c0re.preBuildAgentTemplates` is on so
|
# `services.hyperhive.c0re.preBuildAgentTemplates` is on so
|
||||||
|
|
|
||||||
|
|
@ -36,13 +36,22 @@ pub struct Coordinator {
|
||||||
/// `get_full` for the per-card chip + side-panel viewer. See
|
/// `get_full` for the per-card chip + side-panel viewer. See
|
||||||
/// `build_logs.rs` for retention.
|
/// `build_logs.rs` for retention.
|
||||||
pub build_logs: Arc<crate::build_logs::BuildLogs>,
|
pub build_logs: Arc<crate::build_logs::BuildLogs>,
|
||||||
/// Store path of the hyperhive flake source (no fragment). Used to
|
/// URL of the hyperhive flake (no fragment). Inlined into per-agent
|
||||||
/// identify the running revision (`current_flake_rev`); the meta flake
|
/// `flake.nix` files as `inputs.hyperhive.url`.
|
||||||
/// pins `hyperhive` by forge URL and [`Self::hyperhive_rev`] instead.
|
|
||||||
pub hyperhive_flake: String,
|
pub hyperhive_flake: String,
|
||||||
/// Git rev of the hyperhive checkout this host runs. Pins the meta
|
/// URL of the narrow `docs/` source (no fragment). Inlined into the
|
||||||
/// flake's `hyperhive` input; `None` makes the meta render fail.
|
/// meta `flake.nix` as `inputs.hyperhive-docs.url` and threaded to
|
||||||
pub hyperhive_rev: Option<String>,
|
/// each agent as `services.hyperhive.agent.docs.source`. Its own store path
|
||||||
|
/// so doc edits don't re-hash `hyperhive_flake`.
|
||||||
|
pub hyperhive_docs_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 of the `claude-code` build every agent runs, written
|
/// Store path of the `claude-code` build every agent runs, written
|
||||||
/// into each per-agent flake as `services.hyperhive.agent.claudeCodePath`. Set by
|
/// into each per-agent flake as `services.hyperhive.agent.claudeCodePath`. Set by
|
||||||
/// the NixOS module option
|
/// the NixOS module option
|
||||||
|
|
@ -189,8 +198,12 @@ pub struct Coordinator {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct HiveEnv {
|
pub struct HiveEnv {
|
||||||
pub hyperhive_flake: String,
|
pub hyperhive_flake: String,
|
||||||
/// Git rev of the hyperhive checkout this host runs; see [`Coordinator::hyperhive_rev`].
|
/// Store-path URL of the narrow `docs/` source, wired into the meta
|
||||||
pub hyperhive_rev: Option<String>,
|
/// flake as `inputs.hyperhive-docs.url` and threaded to each agent as
|
||||||
|
/// `services.hyperhive.agent.docs.source`. Separate from `hyperhive_flake`
|
||||||
|
/// so a doc edit only re-locks this input, not the whole source.
|
||||||
|
pub hyperhive_docs_flake: String,
|
||||||
|
pub nixpkgs_flake: String,
|
||||||
/// Store path of the `claude-code` agents run, or `None` for "each
|
/// Store path of the `claude-code` agents run, or `None` for "each
|
||||||
/// agent keeps the one out of its own nixpkgs". Travels into the
|
/// agent keeps the one out of its own nixpkgs". Travels into the
|
||||||
/// container as `services.hyperhive.agent.claudeCodePath` — a plain string, kept
|
/// container as `services.hyperhive.agent.claudeCodePath` — a plain string, kept
|
||||||
|
|
@ -214,7 +227,8 @@ impl Default for HiveEnv {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
hyperhive_flake: "/etc/hyperhive".to_string(),
|
hyperhive_flake: "/etc/hyperhive".to_string(),
|
||||||
hyperhive_rev: None,
|
hyperhive_docs_flake: String::new(),
|
||||||
|
nixpkgs_flake: String::new(),
|
||||||
claude_code_path: None,
|
claude_code_path: None,
|
||||||
dashboard_port: 7000,
|
dashboard_port: 7000,
|
||||||
agent_cpu_quota: "200%".to_string(),
|
agent_cpu_quota: "200%".to_string(),
|
||||||
|
|
@ -390,7 +404,8 @@ impl Coordinator {
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let HiveEnv {
|
let HiveEnv {
|
||||||
hyperhive_flake,
|
hyperhive_flake,
|
||||||
hyperhive_rev,
|
hyperhive_docs_flake,
|
||||||
|
nixpkgs_flake,
|
||||||
claude_code_path,
|
claude_code_path,
|
||||||
dashboard_port,
|
dashboard_port,
|
||||||
agent_cpu_quota,
|
agent_cpu_quota,
|
||||||
|
|
@ -428,7 +443,8 @@ impl Coordinator {
|
||||||
broker: Arc::new(broker),
|
broker: Arc::new(broker),
|
||||||
build_logs,
|
build_logs,
|
||||||
hyperhive_flake,
|
hyperhive_flake,
|
||||||
hyperhive_rev,
|
hyperhive_docs_flake,
|
||||||
|
nixpkgs_flake,
|
||||||
claude_code_path,
|
claude_code_path,
|
||||||
dashboard_port,
|
dashboard_port,
|
||||||
agent_cpu_quota,
|
agent_cpu_quota,
|
||||||
|
|
@ -458,7 +474,8 @@ impl Coordinator {
|
||||||
pub fn hive_env(&self) -> HiveEnv {
|
pub fn hive_env(&self) -> HiveEnv {
|
||||||
HiveEnv {
|
HiveEnv {
|
||||||
hyperhive_flake: self.hyperhive_flake.clone(),
|
hyperhive_flake: self.hyperhive_flake.clone(),
|
||||||
hyperhive_rev: self.hyperhive_rev.clone(),
|
hyperhive_docs_flake: self.hyperhive_docs_flake.clone(),
|
||||||
|
nixpkgs_flake: self.nixpkgs_flake.clone(),
|
||||||
claude_code_path: self.claude_code_path.clone(),
|
claude_code_path: self.claude_code_path.clone(),
|
||||||
dashboard_port: self.dashboard_port,
|
dashboard_port: self.dashboard_port,
|
||||||
agent_cpu_quota: self.agent_cpu_quota.clone(),
|
agent_cpu_quota: self.agent_cpu_quota.clone(),
|
||||||
|
|
|
||||||
|
|
@ -78,10 +78,15 @@ enum Cmd {
|
||||||
/// Path to the sqlite message store.
|
/// Path to the sqlite message store.
|
||||||
#[arg(long, default_value = crate::paths::BROKER_DB)]
|
#[arg(long, default_value = crate::paths::BROKER_DB)]
|
||||||
db: PathBuf,
|
db: PathBuf,
|
||||||
/// Override: store path of the hyperhive flake source, used to
|
/// Override: URL of the hyperhive flake. Inlined into each
|
||||||
/// identify the running revision.
|
/// per-agent `flake.nix` as the `hyperhive` input.
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
hyperhive_flake: Option<String>,
|
hyperhive_flake: Option<String>,
|
||||||
|
/// Override: store-path URL of the nixpkgs to wire into the meta
|
||||||
|
/// flake as `inputs.nixpkgs.url`. Empty = legacy
|
||||||
|
/// `follows = "hyperhive/nixpkgs"` fallback.
|
||||||
|
#[arg(long)]
|
||||||
|
nixpkgs_flake: Option<String>,
|
||||||
/// Override: dashboard HTTP port.
|
/// Override: dashboard HTTP port.
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
dashboard_port: Option<u16>,
|
dashboard_port: Option<u16>,
|
||||||
|
|
@ -132,6 +137,7 @@ async fn main() -> Result<()> {
|
||||||
config,
|
config,
|
||||||
db,
|
db,
|
||||||
hyperhive_flake,
|
hyperhive_flake,
|
||||||
|
nixpkgs_flake,
|
||||||
dashboard_port,
|
dashboard_port,
|
||||||
agent_cpu_quota,
|
agent_cpu_quota,
|
||||||
agent_memory_max,
|
agent_memory_max,
|
||||||
|
|
@ -155,6 +161,9 @@ async fn main() -> Result<()> {
|
||||||
if let Some(v) = hyperhive_flake {
|
if let Some(v) = hyperhive_flake {
|
||||||
sc.env.hyperhive_flake = v;
|
sc.env.hyperhive_flake = v;
|
||||||
}
|
}
|
||||||
|
if let Some(v) = nixpkgs_flake {
|
||||||
|
sc.env.nixpkgs_flake = v;
|
||||||
|
}
|
||||||
if let Some(v) = dashboard_port {
|
if let Some(v) = dashboard_port {
|
||||||
sc.env.dashboard_port = v;
|
sc.env.dashboard_port = v;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ pub async fn sync_agents(hive: &HiveEnv, agents: &[AgentSpec]) -> Result<()> {
|
||||||
let dir = crate::paths::meta_root();
|
let dir = crate::paths::meta_root();
|
||||||
std::fs::create_dir_all(&dir).with_context(|| format!("create {}", dir.display()))?;
|
std::fs::create_dir_all(&dir).with_context(|| format!("create {}", dir.display()))?;
|
||||||
|
|
||||||
let new_flake = render_hive_flake(hive, agents)?;
|
let new_flake = render_hive_flake(hive, agents);
|
||||||
let flake_path = dir.join("flake.nix");
|
let flake_path = dir.join("flake.nix");
|
||||||
let on_disk = std::fs::read_to_string(&flake_path).unwrap_or_default();
|
let on_disk = std::fs::read_to_string(&flake_path).unwrap_or_default();
|
||||||
let initial = !dir.join(".git").exists();
|
let initial = !dir.join(".git").exists();
|
||||||
|
|
@ -327,37 +327,13 @@ pub async fn remove_permission_files() -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pinned forge URL of the hyperhive flake: `git+<forge>/hyperhive/hyperhive.git?rev=<rev>`.
|
|
||||||
/// Errors when the host has no rev, rather than rendering an unpinned URL.
|
|
||||||
fn hyperhive_input_url(hive: &HiveEnv) -> Result<String> {
|
|
||||||
pinned_git_url(
|
|
||||||
&crate::forge::forge_git_url("hyperhive/hyperhive"),
|
|
||||||
hive.hyperhive_rev.as_deref(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The pure half of [`hyperhive_input_url`], testable without `HIVE_FORGE_URL`.
|
|
||||||
fn pinned_git_url(forge_git_url: &str, rev: Option<&str>) -> Result<String> {
|
|
||||||
let rev = rev.filter(|r| !r.is_empty()).context(
|
|
||||||
"hyperhive rev unknown: the host was built from a dirty tree \
|
|
||||||
(`self.rev` unset) and `services.hyperhive.c0re.hyperhiveRev` is not set",
|
|
||||||
)?;
|
|
||||||
Ok(format!("git+{forge_git_url}?rev={rev}"))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// [`render_flake`] with its inputs taken from `hive`, as [`sync_agents`]
|
/// [`render_flake`] with its inputs taken from `hive`, as [`sync_agents`]
|
||||||
/// renders it.
|
/// renders it.
|
||||||
fn render_hive_flake(hive: &HiveEnv, agents: &[AgentSpec]) -> Result<String> {
|
fn render_hive_flake(hive: &HiveEnv, agents: &[AgentSpec]) -> String {
|
||||||
Ok(render_hive_flake_at(
|
|
||||||
hive,
|
|
||||||
&hyperhive_input_url(hive)?,
|
|
||||||
agents,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_hive_flake_at(hive: &HiveEnv, hyperhive_url: &str, agents: &[AgentSpec]) -> String {
|
|
||||||
render_flake(
|
render_flake(
|
||||||
hyperhive_url,
|
&hive.hyperhive_flake,
|
||||||
|
&hive.hyperhive_docs_flake,
|
||||||
|
&hive.nixpkgs_flake,
|
||||||
hive.claude_code_path.as_deref(),
|
hive.claude_code_path.as_deref(),
|
||||||
hive.dashboard_port,
|
hive.dashboard_port,
|
||||||
agents,
|
agents,
|
||||||
|
|
@ -365,13 +341,17 @@ fn render_hive_flake_at(hive: &HiveEnv, hyperhive_url: &str, agents: &[AgentSpec
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_flake(
|
fn render_flake(
|
||||||
hyperhive_url: &str,
|
hyperhive_flake: &str,
|
||||||
|
docs_flake: &str,
|
||||||
|
nixpkgs_flake: &str,
|
||||||
claude_code_path: Option<&str>,
|
claude_code_path: Option<&str>,
|
||||||
dashboard_port: u16,
|
dashboard_port: u16,
|
||||||
agents: &[AgentSpec],
|
agents: &[AgentSpec],
|
||||||
) -> String {
|
) -> String {
|
||||||
render_flake_with_lookup(
|
render_flake_with_lookup(
|
||||||
hyperhive_url,
|
hyperhive_flake,
|
||||||
|
docs_flake,
|
||||||
|
nixpkgs_flake,
|
||||||
claude_code_path,
|
claude_code_path,
|
||||||
dashboard_port,
|
dashboard_port,
|
||||||
agents,
|
agents,
|
||||||
|
|
@ -752,7 +732,9 @@ fn agent_canonical_inputs(name: &str) -> Vec<&'static str> {
|
||||||
contiguous fmt block, splitting it would just hide the shape"
|
contiguous fmt block, splitting it would just hide the shape"
|
||||||
)]
|
)]
|
||||||
fn render_flake_with_lookup<F>(
|
fn render_flake_with_lookup<F>(
|
||||||
hyperhive_url: &str,
|
hyperhive_flake: &str,
|
||||||
|
docs_flake: &str,
|
||||||
|
nixpkgs_flake: &str,
|
||||||
claude_code_path: Option<&str>,
|
claude_code_path: Option<&str>,
|
||||||
dashboard_port: u16,
|
dashboard_port: u16,
|
||||||
agents: &[AgentSpec],
|
agents: &[AgentSpec],
|
||||||
|
|
@ -764,12 +746,42 @@ where
|
||||||
use std::fmt::Write as _;
|
use std::fmt::Write as _;
|
||||||
let mut out = String::new();
|
let mut out = String::new();
|
||||||
out.push_str("{\n description = \"hyperhive deployed agents\";\n inputs = {\n");
|
out.push_str("{\n description = \"hyperhive deployed agents\";\n inputs = {\n");
|
||||||
// `hyperhive` is pinned by forge URL and rev, so the flake resolves on
|
// `nixpkgs` is a top-level meta input with an explicit store-path URL.
|
||||||
// any machine that can reach the forge. `nixpkgs` follows hyperhive's
|
// `hyperhive` then follows it via `hyperhive.inputs.nixpkgs.follows`.
|
||||||
// own pin, which cascades to every agent because
|
// This cascades through to every agent because
|
||||||
// `agent-<n>.inputs.nixpkgs.follows = "nixpkgs"` resolves to this node.
|
// `agent-<n>.inputs.nixpkgs.follows = "nixpkgs"` resolves to the same
|
||||||
let _ = writeln!(out, " hyperhive.url = \"{hyperhive_url}\";");
|
// top-level node.
|
||||||
|
//
|
||||||
|
// Why an explicit `path:` URL 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` directly at nix-module evaluation time is the
|
||||||
|
// only reliable way to honour the host's channel choice.
|
||||||
|
//
|
||||||
|
// Fallback (flake arg 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.follows = \"hyperhive/nixpkgs\";\n");
|
||||||
|
} else {
|
||||||
|
let _ = writeln!(out, " nixpkgs.url = \"{nixpkgs_flake}\";");
|
||||||
|
let _ = writeln!(out, " hyperhive.url = \"{hyperhive_flake}\";");
|
||||||
|
out.push_str(" hyperhive.inputs.nixpkgs.follows = \"nixpkgs\";\n");
|
||||||
|
}
|
||||||
|
// Narrow `docs/` source as its own input so a doc edit only
|
||||||
|
// re-locks THIS input instead of re-hashing the whole `hyperhive`
|
||||||
|
// source. Threaded to each agent below as `services.hyperhive.agent.docs.source`.
|
||||||
|
// Empty = hive-c0re not built with the option wired up (legacy);
|
||||||
|
// agents then keep the harness default (`services.hyperhive.agent.packages.reference-docs`).
|
||||||
|
if !docs_flake.is_empty() {
|
||||||
|
// `flake = false`: the docs/ tree is a plain source (no flake.nix),
|
||||||
|
// so nix must treat it as raw source, not evaluate it as a flake.
|
||||||
|
let _ = writeln!(out, " hyperhive-docs.url = \"{docs_flake}\";");
|
||||||
|
out.push_str(" hyperhive-docs.flake = false;\n");
|
||||||
|
}
|
||||||
// Each agent's config input is its LOCAL applied mirror
|
// Each agent's config input is its LOCAL applied mirror
|
||||||
// (`git+file://<applied>`), so the meta flake resolves entirely from
|
// (`git+file://<applied>`), so the meta flake resolves entirely from
|
||||||
// on-disk state and boot never depends on the forge being reachable.
|
// on-disk state and boot never depends on the forge being reachable.
|
||||||
|
|
@ -830,6 +842,14 @@ where
|
||||||
{
|
{
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
|
// Point the in-container docs dir (`$HIVE_DOCS_DIR`) at the narrow
|
||||||
|
// `hyperhive-docs` input instead of the harness default
|
||||||
|
// (`services.hyperhive.agent.packages.reference-docs`, built from the now-docs-stripped source).
|
||||||
|
// `inputs."hyperhive-docs"` is reachable via the outputs `@inputs`
|
||||||
|
// capture. Emitted only when the input exists (docs_flake non-empty).
|
||||||
|
if !docs_flake.is_empty() {
|
||||||
|
out.push_str(" services.hyperhive.agent.docs.source = inputs.\"hyperhive-docs\".outPath;\n");
|
||||||
|
}
|
||||||
// The `claude-code` agents run, as a bare store path rather than a
|
// The `claude-code` agents run, as a bare store path rather than a
|
||||||
// flake input: containers share the host's `/nix/store`, so the
|
// flake input: containers share the host's `/nix/store`, so the
|
||||||
// binary is already there with its closure and has nothing to
|
// binary is already there with its closure and has nothing to
|
||||||
|
|
@ -1263,55 +1283,71 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pinned_git_url_carries_the_rev_and_refuses_without_one() {
|
fn render_flake_uses_explicit_nixpkgs_url_when_provided() {
|
||||||
assert_eq!(
|
let out = render_flake(
|
||||||
pinned_git_url(
|
"github:example/hyperhive",
|
||||||
"https://forge.example/hyperhive/hyperhive.git",
|
"path:/nix/store/bbbb-hyperhive-docs-source",
|
||||||
Some("abc123")
|
"path:/nix/store/aaaa-nixpkgs-source",
|
||||||
)
|
None,
|
||||||
.unwrap(),
|
8000,
|
||||||
"git+https://forge.example/hyperhive/hyperhive.git?rev=abc123"
|
&[sample_spec("alice", 9001)],
|
||||||
|
);
|
||||||
|
// nixpkgs is a top-level input with an explicit URL; hyperhive
|
||||||
|
// follows it.
|
||||||
|
assert!(
|
||||||
|
out.contains("nixpkgs.url = \"path:/nix/store/aaaa-nixpkgs-source\""),
|
||||||
|
"expected explicit nixpkgs.url:\n{out}"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
out.contains("hyperhive.inputs.nixpkgs.follows = \"nixpkgs\""),
|
||||||
|
"expected hyperhive.inputs.nixpkgs.follows:\n{out}"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!out.contains("nixpkgs.follows = \"hyperhive"),
|
||||||
|
"old-style follows must not appear when flake args are set:\n{out}"
|
||||||
|
);
|
||||||
|
// the narrow docs source is its own non-flake input, and each
|
||||||
|
// agent's docs dir resolves from it rather than services.hyperhive.agent.packages.reference-docs.
|
||||||
|
assert!(
|
||||||
|
out.contains("hyperhive-docs.url = \"path:/nix/store/bbbb-hyperhive-docs-source\""),
|
||||||
|
"expected hyperhive-docs input url:\n{out}"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
out.contains("hyperhive-docs.flake = false;"),
|
||||||
|
"docs source is not a flake, must be flake = false:\n{out}"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
out.contains(
|
||||||
|
"services.hyperhive.agent.docs.source = inputs.\"hyperhive-docs\".outPath;"
|
||||||
|
),
|
||||||
|
"expected per-agent docs source wired to the input:\n{out}"
|
||||||
);
|
);
|
||||||
for missing in [None, Some("")] {
|
|
||||||
let err = pinned_git_url("https://forge.example/x.git", missing).unwrap_err();
|
|
||||||
assert!(err.to_string().contains("rev unknown"), "{err}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn render_flake_pins_hyperhive_by_url_and_follows_its_nixpkgs() {
|
fn render_flake_omits_docs_input_when_docs_flake_empty() {
|
||||||
|
// Legacy / not-wired-up: empty docs_flake emits no docs input and
|
||||||
|
// leaves each agent on the harness default (services.hyperhive.agent.packages.reference-docs).
|
||||||
let out = render_flake(
|
let out = render_flake(
|
||||||
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
|
"github:example/hyperhive",
|
||||||
|
"",
|
||||||
|
"path:/nix/store/aaaa-nixpkgs-source",
|
||||||
None,
|
None,
|
||||||
8000,
|
8000,
|
||||||
&[sample_spec("alice", 9001)],
|
&[sample_spec("alice", 9001)],
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
out.contains(
|
!out.contains("hyperhive-docs"),
|
||||||
"hyperhive.url = \"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd\";"
|
"no docs input/source when docs_flake is empty:\n{out}"
|
||||||
),
|
|
||||||
"expected hyperhive pinned by forge URL:\n{out}"
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
out.contains("nixpkgs.follows = \"hyperhive/nixpkgs\";"),
|
|
||||||
"expected nixpkgs to follow hyperhive:\n{out}"
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
!out.contains("nixpkgs.url") && !out.contains("hyperhive.inputs.nixpkgs"),
|
|
||||||
"no separate nixpkgs input:\n{out}"
|
|
||||||
);
|
|
||||||
// docs come from the pinned hyperhive input: no separate docs input,
|
|
||||||
// no docs.source override.
|
|
||||||
assert!(
|
|
||||||
!out.contains("hyperhive-docs") && !out.contains("docs.source"),
|
|
||||||
"no docs input or override:\n{out}"
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn render_flake_names_each_agent_container_after_the_agent() {
|
fn render_flake_names_each_agent_container_after_the_agent() {
|
||||||
let out = render_flake(
|
let out = render_flake(
|
||||||
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
|
"github:example/hyperhive",
|
||||||
|
"",
|
||||||
|
"path:/nix/store/aaaa-nixpkgs-source",
|
||||||
None,
|
None,
|
||||||
8000,
|
8000,
|
||||||
&[sample_spec("alice", 9001)],
|
&[sample_spec("alice", 9001)],
|
||||||
|
|
@ -1347,7 +1383,9 @@ mod tests {
|
||||||
// input would re-copy it as a reference-less `-source` and strip
|
// input would re-copy it as a reference-less `-source` and strip
|
||||||
// the closure the binary actually needs.
|
// the closure the binary actually needs.
|
||||||
let out = render_flake(
|
let out = render_flake(
|
||||||
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
|
"github:example/hyperhive",
|
||||||
|
"path:/nix/store/bbbb-hyperhive-docs-source",
|
||||||
|
"path:/nix/store/aaaa-nixpkgs-source",
|
||||||
Some("/nix/store/cccc-claude-code-2.1.220"),
|
Some("/nix/store/cccc-claude-code-2.1.220"),
|
||||||
8000,
|
8000,
|
||||||
&[sample_spec("alice", 9001)],
|
&[sample_spec("alice", 9001)],
|
||||||
|
|
@ -1371,7 +1409,9 @@ mod tests {
|
||||||
// in `environment.systemPackages`, which is what makes the
|
// in `environment.systemPackages`, which is what makes the
|
||||||
// unpinned case self-contained).
|
// unpinned case self-contained).
|
||||||
let out = render_flake(
|
let out = render_flake(
|
||||||
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
|
"github:example/hyperhive",
|
||||||
|
"path:/nix/store/bbbb-hyperhive-docs-source",
|
||||||
|
"path:/nix/store/aaaa-nixpkgs-source",
|
||||||
None,
|
None,
|
||||||
8000,
|
8000,
|
||||||
&[sample_spec("alice", 9001)],
|
&[sample_spec("alice", 9001)],
|
||||||
|
|
@ -1382,6 +1422,28 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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",
|
||||||
|
"path:/nix/store/bbbb-hyperhive-docs-source",
|
||||||
|
"",
|
||||||
|
None,
|
||||||
|
8000,
|
||||||
|
&[sample_spec("alice", 9001)],
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
out.contains("nixpkgs.follows = \"hyperhive/nixpkgs\""),
|
||||||
|
"expected fallback follows:\n{out}"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!out.contains("nixpkgs.url ="),
|
||||||
|
"no explicit url should be emitted in fallback mode:\n{out}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn render_flake_emits_follows_for_agents_declaring_nixpkgs() {
|
fn render_flake_emits_follows_for_agents_declaring_nixpkgs() {
|
||||||
// Stub lookup: pretend `bitburner` + `dmatrix` declare `nixpkgs`
|
// Stub lookup: pretend `bitburner` + `dmatrix` declare `nixpkgs`
|
||||||
|
|
@ -1393,7 +1455,9 @@ mod tests {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let out = render_flake_with_lookup(
|
let out = render_flake_with_lookup(
|
||||||
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
|
"github:example/hyperhive",
|
||||||
|
"path:/nix/store/bbbb-hyperhive-docs-source",
|
||||||
|
"path:/nix/store/aaaa-nixpkgs-source",
|
||||||
None,
|
None,
|
||||||
8000,
|
8000,
|
||||||
&[
|
&[
|
||||||
|
|
@ -1422,7 +1486,9 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn render_flake_builds_every_agent_from_agent_base() {
|
fn render_flake_builds_every_agent_from_agent_base() {
|
||||||
let out = render_flake(
|
let out = render_flake(
|
||||||
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
|
"github:example/hyperhive",
|
||||||
|
"path:/nix/store/bbbb-hyperhive-docs-source",
|
||||||
|
"path:/nix/store/aaaa-nixpkgs-source",
|
||||||
None,
|
None,
|
||||||
8000,
|
8000,
|
||||||
&[sample_spec("alice", 9001), sample_spec("ruth", 9002)],
|
&[sample_spec("alice", 9001), sample_spec("ruth", 9002)],
|
||||||
|
|
@ -1447,7 +1513,9 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn render_flake_skips_canonical_follows_when_lookup_returns_empty() {
|
fn render_flake_skips_canonical_follows_when_lookup_returns_empty() {
|
||||||
let out = render_flake_with_lookup(
|
let out = render_flake_with_lookup(
|
||||||
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
|
"github:example/hyperhive",
|
||||||
|
"path:/nix/store/bbbb-hyperhive-docs-source",
|
||||||
|
"path:/nix/store/aaaa-nixpkgs-source",
|
||||||
None,
|
None,
|
||||||
8000,
|
8000,
|
||||||
&[sample_spec("alice", 9001)],
|
&[sample_spec("alice", 9001)],
|
||||||
|
|
@ -1475,7 +1543,9 @@ mod tests {
|
||||||
..sample_spec("droid", 9002)
|
..sample_spec("droid", 9002)
|
||||||
};
|
};
|
||||||
let out = render_flake_with_lookup(
|
let out = render_flake_with_lookup(
|
||||||
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
|
"github:example/hyperhive",
|
||||||
|
"path:/nix/store/bbbb-hyperhive-docs-source",
|
||||||
|
"path:/nix/store/aaaa-nixpkgs-source",
|
||||||
None,
|
None,
|
||||||
8000,
|
8000,
|
||||||
&[sample_spec("alice", 9001), android],
|
&[sample_spec("alice", 9001), android],
|
||||||
|
|
@ -1526,7 +1596,9 @@ mod tests {
|
||||||
std::env::set_var("HIVE_FORGE_URL", "http://forge.example.test");
|
std::env::set_var("HIVE_FORGE_URL", "http://forge.example.test");
|
||||||
}
|
}
|
||||||
let out = render_flake(
|
let out = render_flake(
|
||||||
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
|
"github:example/hyperhive",
|
||||||
|
"path:/nix/store/bbbb-hyperhive-docs-source",
|
||||||
|
"path:/nix/store/aaaa-nixpkgs-source",
|
||||||
None,
|
None,
|
||||||
8000,
|
8000,
|
||||||
&[sample_spec("alice", 9001)],
|
&[sample_spec("alice", 9001)],
|
||||||
|
|
@ -1567,7 +1639,9 @@ mod tests {
|
||||||
std::env::set_var(VARS[1], domain);
|
std::env::set_var(VARS[1], domain);
|
||||||
}
|
}
|
||||||
render_flake(
|
render_flake(
|
||||||
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
|
"github:example/hyperhive",
|
||||||
|
"path:/nix/store/bbbb-hyperhive-docs-source",
|
||||||
|
"path:/nix/store/aaaa-nixpkgs-source",
|
||||||
None,
|
None,
|
||||||
8000,
|
8000,
|
||||||
&[sample_spec("alice", 9001)],
|
&[sample_spec("alice", 9001)],
|
||||||
|
|
@ -1762,7 +1836,9 @@ mod tests {
|
||||||
std::env::set_var("HIVE_MATRIX_URL", "http://matrix.example.test");
|
std::env::set_var("HIVE_MATRIX_URL", "http://matrix.example.test");
|
||||||
}
|
}
|
||||||
let out = render_flake(
|
let out = render_flake(
|
||||||
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
|
"github:example/hyperhive",
|
||||||
|
"path:/nix/store/bbbb-hyperhive-docs-source",
|
||||||
|
"path:/nix/store/aaaa-nixpkgs-source",
|
||||||
None,
|
None,
|
||||||
8000,
|
8000,
|
||||||
&[sample_spec("alice", 9001)],
|
&[sample_spec("alice", 9001)],
|
||||||
|
|
@ -1794,7 +1870,9 @@ mod tests {
|
||||||
// forge being reachable. The forge `agent-configs/<n>` repos stay the
|
// forge being reachable. The forge `agent-configs/<n>` repos stay the
|
||||||
// review surface, but they are not the flake's build input.
|
// review surface, but they are not the flake's build input.
|
||||||
let out = render_flake(
|
let out = render_flake(
|
||||||
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
|
"github:example/hyperhive",
|
||||||
|
"path:/nix/store/bbbb-hyperhive-docs-source",
|
||||||
|
"path:/nix/store/aaaa-nixpkgs-source",
|
||||||
None,
|
None,
|
||||||
8000,
|
8000,
|
||||||
&[sample_spec("alice", 9001)],
|
&[sample_spec("alice", 9001)],
|
||||||
|
|
@ -1808,8 +1886,8 @@ mod tests {
|
||||||
"expected the agent input to point at the local applied mirror ({want}):\n{out}"
|
"expected the agent input to point at the local applied mirror ({want}):\n{out}"
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
!out.contains("agent-alice.url = \"git+http"),
|
!out.contains("git+http"),
|
||||||
"no forge git+http URL must remain for the agent input:\n{out}"
|
"no forge git+http URL must remain in the rendered flake:\n{out}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1834,7 +1912,9 @@ mod tests {
|
||||||
|
|
||||||
let render = || {
|
let render = || {
|
||||||
render_flake(
|
render_flake(
|
||||||
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
|
"github:example/hyperhive",
|
||||||
|
"path:/nix/store/bbbb-hyperhive-docs-source",
|
||||||
|
"path:/nix/store/aaaa-nixpkgs-source",
|
||||||
None,
|
None,
|
||||||
8000,
|
8000,
|
||||||
&[sample_spec("alice", 9001)],
|
&[sample_spec("alice", 9001)],
|
||||||
|
|
@ -1922,7 +2002,9 @@ mod tests {
|
||||||
// SAFETY: serialised by the guard above; restored before returning.
|
// SAFETY: serialised by the guard above; restored before returning.
|
||||||
let render = || {
|
let render = || {
|
||||||
render_flake(
|
render_flake(
|
||||||
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
|
"github:example/hyperhive",
|
||||||
|
"path:/nix/store/bbbb-hyperhive-docs-source",
|
||||||
|
"path:/nix/store/aaaa-nixpkgs-source",
|
||||||
None,
|
None,
|
||||||
8000,
|
8000,
|
||||||
&[sample_spec("alice", 9001)],
|
&[sample_spec("alice", 9001)],
|
||||||
|
|
@ -1992,7 +2074,9 @@ mod tests {
|
||||||
// SAFETY: serialised by the guard above; restored before returning.
|
// SAFETY: serialised by the guard above; restored before returning.
|
||||||
let render = || {
|
let render = || {
|
||||||
render_flake(
|
render_flake(
|
||||||
"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
|
"github:example/hyperhive",
|
||||||
|
"path:/nix/store/bbbb-hyperhive-docs-source",
|
||||||
|
"path:/nix/store/aaaa-nixpkgs-source",
|
||||||
None,
|
None,
|
||||||
8000,
|
8000,
|
||||||
&[sample_spec("alice", 9001)],
|
&[sample_spec("alice", 9001)],
|
||||||
|
|
@ -2028,16 +2112,8 @@ mod tests {
|
||||||
..HiveEnv::default()
|
..HiveEnv::default()
|
||||||
};
|
};
|
||||||
let agents = [sample_spec("alice", 9001)];
|
let agents = [sample_spec("alice", 9001)];
|
||||||
let small = render_hive_flake_at(
|
let small = render_hive_flake(&hive("4G"), &agents);
|
||||||
&hive("4G"),
|
let large = render_hive_flake(&hive("16G"), &agents);
|
||||||
"git+https://forge.example/h.git?rev=abc",
|
|
||||||
&agents,
|
|
||||||
);
|
|
||||||
let large = render_hive_flake_at(
|
|
||||||
&hive("16G"),
|
|
||||||
"git+https://forge.example/h.git?rev=abc",
|
|
||||||
&agents,
|
|
||||||
);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
small, large,
|
small, large,
|
||||||
"the rendered flake must not depend on the memory cap"
|
"the rendered flake must not depend on the memory cap"
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
{
|
{
|
||||||
options.services.hyperhive.agent.docs.enable = lib.mkEnableOption ''
|
options.services.hyperhive.agent.docs.enable = lib.mkEnableOption ''
|
||||||
make the hyperhive reference docs (the repo `docs/` tree, shipped
|
make the hyperhive reference docs (the repo `docs/` tree, shipped
|
||||||
read-only as the `reference-docs` package) available
|
read-only as the standalone `hyperhive-docs` derivation) available
|
||||||
in-container. When enabled the harness exposes the docs dir to claude
|
in-container. When enabled the harness exposes the docs dir to claude
|
||||||
via `claude --add-dir`, so the markdown is readable at
|
via `claude --add-dir`, so the markdown is readable at
|
||||||
`$HIVE_DOCS_DIR/`, and appends a single pointer sentence to the agent's
|
`$HIVE_DOCS_DIR/`, and appends a single pointer sentence to the agent's
|
||||||
|
|
@ -26,7 +26,11 @@
|
||||||
Store path of the reference-docs tree exposed at `$HIVE_DOCS_DIR`
|
Store path of the reference-docs tree exposed at `$HIVE_DOCS_DIR`
|
||||||
when `services.hyperhive.agent.docs.enable` is set. Defaults to the flake's
|
when `services.hyperhive.agent.docs.enable` is set. Defaults to the flake's
|
||||||
`reference-docs` package (the `nix/packages/reference-docs.nix`
|
`reference-docs` package (the `nix/packages/reference-docs.nix`
|
||||||
build), which the meta flake's pinned `hyperhive` input provides.
|
build) so a standalone container build from a full checkout
|
||||||
|
works unchanged. The generated meta flake overrides this with the
|
||||||
|
narrow `hyperhive-docs` flake input so a doc edit only
|
||||||
|
re-locks that input instead of rebuilding the container from a
|
||||||
|
re-hashed `hyperhive` source.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -36,7 +40,9 @@
|
||||||
# `--add-dir` so the docs are readable, and appends a single
|
# `--add-dir` so the docs are readable, and appends a single
|
||||||
# pointer sentence to the system prompt
|
# pointer sentence to the system prompt
|
||||||
# (hive-agent::prompt::render) telling the agent the docs exist.
|
# (hive-agent::prompt::render) telling the agent the docs exist.
|
||||||
# Source is `services.hyperhive.agent.docs.source` (default: the `reference-docs` package). See hive-agent::turn.
|
# Source is `services.hyperhive.agent.docs.source` (the narrow `hyperhive-docs`
|
||||||
|
# meta-flake input, or `pkgs.hyperhive-docs` for standalone
|
||||||
|
# builds). See hive-agent::turn.
|
||||||
HIVE_DOCS_DIR = "${config.services.hyperhive.agent.docs.source}";
|
HIVE_DOCS_DIR = "${config.services.hyperhive.agent.docs.source}";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
# The swarm builder's build check: one agent config on hyperhive's agent base,
|
# The swarm builder's build check: one agent config on hyperhive's agent base,
|
||||||
# composed the way a hive's meta flake composes it, minus the values a hive
|
# composed the way a hive's meta flake composes it, minus the values a hive
|
||||||
# adds (its CA, claude-code path and ports).
|
# adds (its CA, claude-code path and ports). The docs source is set the way the
|
||||||
|
# meta flake sets it: `hyperhive`'s source has no `docs/`, so the default
|
||||||
|
# `docs.source` fails to evaluate.
|
||||||
#
|
#
|
||||||
# Every input is replaced at build time with `--override-input`
|
# Every input is replaced at build time with `--override-input`
|
||||||
# (swarm-builder/src/nix_build.rs), so the URLs below are never fetched and
|
# (swarm-builder/src/nix_build.rs), so the URLs below are never fetched and
|
||||||
|
|
@ -12,19 +14,25 @@
|
||||||
nixpkgs.url = "flake:nixpkgs";
|
nixpkgs.url = "flake:nixpkgs";
|
||||||
hyperhive.url = "flake:hyperhive";
|
hyperhive.url = "flake:hyperhive";
|
||||||
hyperhive.inputs.nixpkgs.follows = "nixpkgs";
|
hyperhive.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
hyperhive-docs.url = "flake:hyperhive-docs";
|
||||||
|
hyperhive-docs.flake = false;
|
||||||
agent.url = "flake:agent";
|
agent.url = "flake:agent";
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs =
|
outputs =
|
||||||
{
|
{
|
||||||
hyperhive,
|
hyperhive,
|
||||||
|
hyperhive-docs,
|
||||||
agent,
|
agent,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
{
|
{
|
||||||
packages.x86_64-linux.check =
|
packages.x86_64-linux.check =
|
||||||
(hyperhive.nixosConfigurations.agent-base.extendModules {
|
(hyperhive.nixosConfigurations.agent-base.extendModules {
|
||||||
modules = [ agent.nixosModules.default ];
|
modules = [
|
||||||
|
agent.nixosModules.default
|
||||||
|
{ services.hyperhive.agent.docs.source = hyperhive-docs.outPath; }
|
||||||
|
];
|
||||||
}).config.system.build.toplevel;
|
}).config.system.build.toplevel;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,8 @@ let
|
||||||
# c0re binary itself changes.
|
# c0re binary itself changes.
|
||||||
serveConfigJson = builtins.toJSON {
|
serveConfigJson = builtins.toJSON {
|
||||||
hyperhive_flake = cfg.hyperhiveFlake;
|
hyperhive_flake = cfg.hyperhiveFlake;
|
||||||
hyperhive_rev = cfg.hyperhiveRev;
|
hyperhive_docs_flake = cfg.hyperhiveDocs;
|
||||||
|
nixpkgs_flake = cfg.nixpkgsFlake;
|
||||||
# The `claude-code` every agent runs, or null for "each agent keeps
|
# The `claude-code` every agent runs, or null for "each agent keeps
|
||||||
# the one out of its own nixpkgs". `builtins.toJSON` serialises a
|
# the one out of its own nixpkgs". `builtins.toJSON` serialises a
|
||||||
# derivation as its out path (and null as null), so the package goes
|
# derivation as its out path (and null as null), so the package goes
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,6 @@
|
||||||
(lib.mkRemovedOptionModule [ "services" "hyperhive" "c0re" "managerToplevel" ] ''
|
(lib.mkRemovedOptionModule [ "services" "hyperhive" "c0re" "managerToplevel" ] ''
|
||||||
Every agent, ruth included, builds from `agentBaseToplevel`. Remove this definition.
|
Every agent, ruth included, builds from `agentBaseToplevel`. Remove this definition.
|
||||||
'')
|
'')
|
||||||
(lib.mkRemovedOptionModule [ "services" "hyperhive" "c0re" "hyperhiveDocs" ] ''
|
|
||||||
Agents take their reference docs from the pinned `hyperhive` input, which
|
|
||||||
carries `docs/`. Remove this definition.
|
|
||||||
'')
|
|
||||||
];
|
];
|
||||||
|
|
||||||
options.services.hyperhive.c0re = {
|
options.services.hyperhive.c0re = {
|
||||||
|
|
@ -74,24 +70,24 @@
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
defaultText = lib.literalMD "the hyperhive flake's own filtered source store path";
|
defaultText = lib.literalMD "the hyperhive flake's own filtered source store path";
|
||||||
description = ''
|
description = ''
|
||||||
Store path of the hyperhive flake source (no fragment). hive-c0re
|
URL of the hyperhive flake (no fragment). Inlined into each
|
||||||
reads it to tell which revision is running, and the swarm builder
|
per-agent `flake.nix` at `inputs.hyperhive.url`. The per-agent
|
||||||
builds on it. The meta flake does not use it: that pins
|
flake then pulls `hyperhive.nixosConfigurations.agent-base` to
|
||||||
`hyperhive` by forge URL and `hyperhiveRev`. Wired by
|
build the container. Wired by `nixosModules.default` to this
|
||||||
`nixosModules.default` to this flake's own filtered source.
|
flake's own filtered source — only override if you want agents
|
||||||
|
tracking a different ref.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
hyperhiveRev = lib.mkOption {
|
hyperhiveDocs = lib.mkOption {
|
||||||
type = lib.types.nullOr lib.types.str;
|
type = lib.types.str;
|
||||||
default = null;
|
defaultText = lib.literalMD "the docs/ tree's own store path";
|
||||||
defaultText = lib.literalMD "the hyperhive flake's own `self.rev`";
|
|
||||||
description = ''
|
description = ''
|
||||||
Git rev of the hyperhive checkout this host runs. hive-c0re pins
|
URL of the narrow `docs/` source (no fragment). Inlined into the
|
||||||
the generated meta flake's `hyperhive` input to
|
generated meta `flake.nix` at `inputs.hyperhive-docs.url` and
|
||||||
`git+<forge>/hyperhive/hyperhive.git?rev=<rev>`, and `nixpkgs`
|
threaded to each agent as `services.hyperhive.agent.docs.source`, from which the
|
||||||
follows that input. Wired by `nixosModules.default` from
|
harness resolves `$HIVE_DOCS_DIR`. Its own store path — separate
|
||||||
`self.rev`, which a dirty tree does not have; with `null`,
|
from `hyperhiveFlake` — so a doc edit only re-locks this input
|
||||||
hive-c0re refuses to render the meta flake.
|
instead of rebuilding every agent container.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
agentBaseToplevel = lib.mkOption {
|
agentBaseToplevel = lib.mkOption {
|
||||||
|
|
@ -109,12 +105,20 @@
|
||||||
default = "path:${pkgs.path}";
|
default = "path:${pkgs.path}";
|
||||||
defaultText = lib.literalMD "`\"path:\${pkgs.path}\"`";
|
defaultText = lib.literalMD "`\"path:\${pkgs.path}\"`";
|
||||||
description = ''
|
description = ''
|
||||||
Store-path URL of the nixpkgs the swarm builder's check flake
|
Store-path URL for the `nixpkgs` input in the generated meta
|
||||||
builds on. The generated meta flake does not use it: its `nixpkgs`
|
flake. The meta flake declares this as a top-level input and
|
||||||
follows `hyperhive/nixpkgs`.
|
wires `inputs.hyperhive.inputs.nixpkgs.follows = "nixpkgs"` so
|
||||||
|
every agent container evaluates with this exact nixpkgs.
|
||||||
|
|
||||||
Defaults to `"path:''${pkgs.path}"`, the nixpkgs the host NixOS
|
Defaults to `"path:''${pkgs.path}"` — the store path of the
|
||||||
module was evaluated with.
|
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.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
preBuildAgentTemplates = lib.mkOption {
|
preBuildAgentTemplates = lib.mkOption {
|
||||||
|
|
@ -336,7 +340,7 @@
|
||||||
description = ''
|
description = ''
|
||||||
The `claude-code` build every agent on this hive runs, or `null` (the
|
The `claude-code` build every agent on this hive runs, or `null` (the
|
||||||
default) to leave each agent on the `claude-code` from its own
|
default) to leave each agent on the `claude-code` from its own
|
||||||
nixpkgs — i.e. the nixpkgs pinned by hyperhive's own `flake.lock`.
|
nixpkgs — i.e. whatever `c0re.nixpkgsFlake` resolves to.
|
||||||
|
|
||||||
This is the one binary the whole hive is built around, and it
|
This is the one binary the whole hive is built around, and it
|
||||||
moves fast enough that a release channel routinely trails
|
moves fast enough that a release channel routinely trails
|
||||||
|
|
|
||||||
|
|
@ -89,9 +89,11 @@ let
|
||||||
# The `nix` the host daemon runs, so client and daemon speak one protocol.
|
# The `nix` the host daemon runs, so client and daemon speak one protocol.
|
||||||
nix = config.nix.package;
|
nix = config.nix.package;
|
||||||
|
|
||||||
# Strings, not paths: both are already store paths.
|
# Strings, not paths: all three are already store paths, and the flake
|
||||||
|
# references the daemon passes must be the ones a hive's meta flake names.
|
||||||
hyperhiveFlake = hyperhiveCfg.c0re.hyperhiveFlake;
|
hyperhiveFlake = hyperhiveCfg.c0re.hyperhiveFlake;
|
||||||
nixpkgsFlake = hyperhiveCfg.c0re.nixpkgsFlake;
|
nixpkgsFlake = hyperhiveCfg.c0re.nixpkgsFlake;
|
||||||
|
docsFlake = hyperhiveCfg.c0re.hyperhiveDocs;
|
||||||
|
|
||||||
privateNetwork = true;
|
privateNetwork = true;
|
||||||
in
|
in
|
||||||
|
|
@ -252,6 +254,7 @@ in
|
||||||
SWARM_BUILDER_CHECK_FLAKE = "${../builder/check-flake}";
|
SWARM_BUILDER_CHECK_FLAKE = "${../builder/check-flake}";
|
||||||
SWARM_BUILDER_HYPERHIVE_FLAKE = hyperhiveFlake;
|
SWARM_BUILDER_HYPERHIVE_FLAKE = hyperhiveFlake;
|
||||||
SWARM_BUILDER_NIXPKGS_FLAKE = nixpkgsFlake;
|
SWARM_BUILDER_NIXPKGS_FLAKE = nixpkgsFlake;
|
||||||
|
SWARM_BUILDER_HYPERHIVE_DOCS_FLAKE = docsFlake;
|
||||||
SWARM_BUILDER_FORGE_URL = "https://${forgeCfg.domain}";
|
SWARM_BUILDER_FORGE_URL = "https://${forgeCfg.domain}";
|
||||||
SWARM_BUILDER_CONFIG_ORG = "agent-configs";
|
SWARM_BUILDER_CONFIG_ORG = "agent-configs";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,8 @@ let
|
||||||
|
|
||||||
# The check flake on the inputs the builder's unit names, called as
|
# The check flake on the inputs the builder's unit names, called as
|
||||||
# `nix build` calls it. `hyperhive` is entered through the filtered source's
|
# `nix build` calls it. `hyperhive` is entered through the filtered source's
|
||||||
# own `flake.nix`.
|
# own `flake.nix`, so its agent base sees the tree without `docs/` that a
|
||||||
|
# real build gets; `self`'s own agent base would find `docs/` here.
|
||||||
builderCheck =
|
builderCheck =
|
||||||
agentModule:
|
agentModule:
|
||||||
let
|
let
|
||||||
|
|
@ -68,6 +69,7 @@ let
|
||||||
in
|
in
|
||||||
((import "${env.SWARM_BUILDER_CHECK_FLAKE}/flake.nix").outputs {
|
((import "${env.SWARM_BUILDER_CHECK_FLAKE}/flake.nix").outputs {
|
||||||
inherit hyperhive;
|
inherit hyperhive;
|
||||||
|
hyperhive-docs.outPath = env.SWARM_BUILDER_HYPERHIVE_DOCS_FLAKE;
|
||||||
agent.nixosModules.default = agentModule;
|
agent.nixosModules.default = agentModule;
|
||||||
}).packages.x86_64-linux.check;
|
}).packages.x86_64-linux.check;
|
||||||
|
|
||||||
|
|
@ -133,11 +135,12 @@ let
|
||||||
&& lib.elem "swarm-bao-pki.service" c.after;
|
&& lib.elem "swarm-bao-pki.service" c.after;
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
# The check builds on the sources this host was deployed with, so a green check means
|
# The check builds on the sources a hive on this host would render into
|
||||||
# the same hyperhive and nixpkgs base.
|
# its meta flake, so a green check means the same base.
|
||||||
name = "the builder builds on the hyperhive and nixpkgs this host was deployed with";
|
name = "the builder builds on the hyperhive, docs and nixpkgs this host's hives use";
|
||||||
ok =
|
ok =
|
||||||
env.SWARM_BUILDER_HYPERHIVE_FLAKE == withBuilder.services.hyperhive.c0re.hyperhiveFlake
|
env.SWARM_BUILDER_HYPERHIVE_FLAKE == withBuilder.services.hyperhive.c0re.hyperhiveFlake
|
||||||
|
&& env.SWARM_BUILDER_HYPERHIVE_DOCS_FLAKE == withBuilder.services.hyperhive.c0re.hyperhiveDocs
|
||||||
&& env.SWARM_BUILDER_NIXPKGS_FLAKE == withBuilder.services.hyperhive.c0re.nixpkgsFlake
|
&& env.SWARM_BUILDER_NIXPKGS_FLAKE == withBuilder.services.hyperhive.c0re.nixpkgsFlake
|
||||||
&& lib.hasSuffix "-check-flake" env.SWARM_BUILDER_CHECK_FLAKE
|
&& lib.hasSuffix "-check-flake" env.SWARM_BUILDER_CHECK_FLAKE
|
||||||
&& env.SWARM_BUILDER_CONFIG_ORG == "agent-configs"
|
&& env.SWARM_BUILDER_CONFIG_ORG == "agent-configs"
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,23 @@
|
||||||
# (crane's cargo-source filter — see ./rust.nix).
|
# (crane's cargo-source filter — see ./rust.nix).
|
||||||
{ lib }:
|
{ lib }:
|
||||||
{
|
{
|
||||||
# Filtered view of this repo the swarm builder builds on as its `hyperhive`
|
# Filtered view of this repo handed to hive-c0re as the `hyperhive`
|
||||||
# input. Drops files no nix or cargo derivation reads — shell helper
|
# meta-flake input. Drops files no nix or cargo derivation reads —
|
||||||
# scripts and root-level markdown. `docs/` is kept: the agent base's
|
# shell helper scripts and root-level markdown — so editing them
|
||||||
# `reference-docs` package builds from it.
|
# does NOT change the store path and therefore does NOT force a
|
||||||
|
# rebuild of every agent container.
|
||||||
|
#
|
||||||
|
# Dropped: scripts/, docs/, root-level *.md (README/AGENTS/TODO/…).
|
||||||
|
# Kept (build needs them): .nix, .rs, Cargo.*, branding/,
|
||||||
|
# frontend/, prompts/, flake.lock. docs/ is deliberately dropped:
|
||||||
|
# it is shipped to agent containers as its OWN narrow meta-flake
|
||||||
|
# input (`hyperhiveDocsSource`, below), threaded through hive-c0re
|
||||||
|
# → the meta flake → `services.hyperhive.agent.docs.source`. Keeping docs/ out of
|
||||||
|
# THIS source means a doc edit only re-hashes the docs input (a
|
||||||
|
# cheap re-link), not the whole flake source (which would rebuild
|
||||||
|
# every agent container). The `reference-docs` build from `../docs`
|
||||||
|
# stays only for standalone `nix build .#reference-docs` from a
|
||||||
|
# full checkout — the meta path never evaluates it.
|
||||||
hyperhiveFlakeSource = lib.cleanSourceWith {
|
hyperhiveFlakeSource = lib.cleanSourceWith {
|
||||||
name = "hyperhive-flake-source";
|
name = "hyperhive-flake-source";
|
||||||
src = ../.;
|
src = ../.;
|
||||||
|
|
@ -18,6 +31,19 @@
|
||||||
rel = lib.removePrefix (toString ../. + "/") (toString path);
|
rel = lib.removePrefix (toString ../. + "/") (toString path);
|
||||||
in
|
in
|
||||||
!(lib.hasPrefix "scripts/" rel || rel == "scripts")
|
!(lib.hasPrefix "scripts/" rel || rel == "scripts")
|
||||||
|
&& !(lib.hasPrefix "docs/" rel || rel == "docs")
|
||||||
&& !(type == "regular" && !lib.hasInfix "/" rel && lib.hasSuffix ".md" rel);
|
&& !(type == "regular" && !lib.hasInfix "/" rel && lib.hasSuffix ".md" rel);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# The repo `docs/` tree as a standalone narrow source. Its store
|
||||||
|
# path moves ONLY on doc edits, decoupled from `hyperhiveFlakeSource`.
|
||||||
|
# hive-c0re threads this to the meta flake as the `hyperhive-docs`
|
||||||
|
# input (same pattern as the `hyperhive` input); the harness resolves
|
||||||
|
# `$HIVE_DOCS_DIR` from it via `services.hyperhive.agent.docs.source`. Evaluated
|
||||||
|
# here on the host where docs/ exists — it cannot be derived from
|
||||||
|
# inside the docs-stripped `hyperhiveFlakeSource`.
|
||||||
|
hyperhiveDocsSource = lib.cleanSourceWith {
|
||||||
|
name = "hyperhive-docs-source";
|
||||||
|
src = ../docs;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,9 @@ the answer into a `swarm-builder` commit status on the PR's head.
|
||||||
|
|
||||||
## What a build is
|
## What a build is
|
||||||
|
|
||||||
- `nix build` of `nix/builder/check-flake`'s `check` output, with its three
|
- `nix build` of `nix/builder/check-flake`'s `check` output, with its four
|
||||||
inputs overridden: `hyperhive` and `nixpkgs` to this host's own sources,
|
inputs overridden: `hyperhive` and `nixpkgs` to this host's own sources,
|
||||||
|
`hyperhive-docs` to the `docs/` source its hives use (`c0re.hyperhiveDocs`),
|
||||||
and `agent` to `git+https://<forge>/agent-configs/<agent>?ref=refs/pull/<pr>/head&rev=<rev>`.
|
and `agent` to `git+https://<forge>/agent-configs/<agent>?ref=refs/pull/<pr>/head&rev=<rev>`.
|
||||||
- The evaluation runs here, pure; the builds run in the host daemon's sandbox
|
- The evaluation runs here, pure; the builds run in the host daemon's sandbox
|
||||||
as an untrusted client, so a config can neither set daemon options nor read
|
as an untrusted client, so a config can neither set daemon options nor read
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@ async fn main() -> Result<()> {
|
||||||
check_flake: var("CHECK_FLAKE")?,
|
check_flake: var("CHECK_FLAKE")?,
|
||||||
hyperhive: var("HYPERHIVE_FLAKE")?,
|
hyperhive: var("HYPERHIVE_FLAKE")?,
|
||||||
nixpkgs: var("NIXPKGS_FLAKE")?,
|
nixpkgs: var("NIXPKGS_FLAKE")?,
|
||||||
|
hyperhive_docs: var("HYPERHIVE_DOCS_FLAKE")?,
|
||||||
forge_url: forge_url()?,
|
forge_url: forge_url()?,
|
||||||
config_org: var("CONFIG_ORG")?,
|
config_org: var("CONFIG_ORG")?,
|
||||||
deadline: BUILD_DEADLINE,
|
deadline: BUILD_DEADLINE,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
//! One build: `nix build` of the check flake with its three inputs pointed at
|
//! One build: `nix build` of the check flake with its four inputs pointed at
|
||||||
//! this host's hyperhive and nixpkgs and at the agent's config
|
//! this host's hyperhive, hyperhive docs and nixpkgs and at the agent's config
|
||||||
//! commit.
|
//! commit.
|
||||||
//!
|
//!
|
||||||
//! The config repo is private, so `git` fetches it with the builder's forge
|
//! The config repo is private, so `git` fetches it with the builder's forge
|
||||||
|
|
@ -45,6 +45,8 @@ pub struct Builder {
|
||||||
pub hyperhive: String,
|
pub hyperhive: String,
|
||||||
/// The nixpkgs source this host was deployed from.
|
/// The nixpkgs source this host was deployed from.
|
||||||
pub nixpkgs: String,
|
pub nixpkgs: String,
|
||||||
|
/// The hyperhive `docs/` source this host was deployed from.
|
||||||
|
pub hyperhive_docs: String,
|
||||||
/// `https://<forge>`, with no trailing slash and no path.
|
/// `https://<forge>`, with no trailing slash and no path.
|
||||||
pub forge_url: String,
|
pub forge_url: String,
|
||||||
/// The org the config repos are in.
|
/// The org the config repos are in.
|
||||||
|
|
@ -100,6 +102,9 @@ impl Builder {
|
||||||
"nixpkgs".to_owned(),
|
"nixpkgs".to_owned(),
|
||||||
self.nixpkgs.clone(),
|
self.nixpkgs.clone(),
|
||||||
"--override-input".to_owned(),
|
"--override-input".to_owned(),
|
||||||
|
"hyperhive-docs".to_owned(),
|
||||||
|
self.hyperhive_docs.clone(),
|
||||||
|
"--override-input".to_owned(),
|
||||||
"agent".to_owned(),
|
"agent".to_owned(),
|
||||||
self.agent_url(job),
|
self.agent_url(job),
|
||||||
])
|
])
|
||||||
|
|
@ -303,6 +308,7 @@ mod tests {
|
||||||
check_flake: "/nix/store/c-check".to_owned(),
|
check_flake: "/nix/store/c-check".to_owned(),
|
||||||
hyperhive: "/nix/store/h-source".to_owned(),
|
hyperhive: "/nix/store/h-source".to_owned(),
|
||||||
nixpkgs: "/nix/store/n-source".to_owned(),
|
nixpkgs: "/nix/store/n-source".to_owned(),
|
||||||
|
hyperhive_docs: "/nix/store/d-docs-source".to_owned(),
|
||||||
forge_url: "https://forge.example".to_owned(),
|
forge_url: "https://forge.example".to_owned(),
|
||||||
config_org: "agent-configs".to_owned(),
|
config_org: "agent-configs".to_owned(),
|
||||||
nix: PathBuf::from("nix"),
|
nix: PathBuf::from("nix"),
|
||||||
|
|
@ -327,7 +333,14 @@ mod tests {
|
||||||
.filter(|w| w[0] == "--override-input")
|
.filter(|w| w[0] == "--override-input")
|
||||||
.map(|w| w[1].as_str())
|
.map(|w| w[1].as_str())
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(overridden, ["hyperhive", "nixpkgs", "agent"]);
|
assert_eq!(
|
||||||
|
overridden,
|
||||||
|
["hyperhive", "nixpkgs", "hyperhive-docs", "agent"]
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
args.windows(2)
|
||||||
|
.any(|w| w[0] == "hyperhive-docs" && w[1] == "/nix/store/d-docs-source")
|
||||||
|
);
|
||||||
assert!(args.iter().any(|a| a == "/nix/store/c-check#check"));
|
assert!(args.iter().any(|a| a == "/nix/store/c-check#check"));
|
||||||
assert!(args.iter().any(|a| a == "--no-write-lock-file"));
|
assert!(args.iter().any(|a| a == "--no-write-lock-file"));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue