Watch
0
0
Fork
You've already forked hyperhive
0

Compare commits

...
17 changed files with 159 additions and 345 deletions

View file

@ -117,10 +117,7 @@ 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 = { hyperhive.url = "git+https://forge.darkest.space/hyperhive/hyperhive";
url = "git+https://forge.darkest.space/hyperhive/hyperhive";
inputs.nixpkgs.follows = "nixpkgs"; # see "Overriding nixpkgs"
};
}; };
outputs = { nixpkgs, hyperhive, ... }: { outputs = { nixpkgs, hyperhive, ... }: {
@ -160,14 +157,6 @@ 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

View file

@ -28,12 +28,8 @@ 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, the same two a hive on that host renders into its meta deployed with (`c0re.hyperhiveFlake`, `c0re.nixpkgsFlake`). A hive's meta
flake (`c0re.hyperhiveFlake`, `c0re.nixpkgsFlake`). flake pins `hyperhive` by forge URL and rev instead.
- `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`

View file

@ -20,9 +20,8 @@ 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/` source is a narrow flake input (`hyperhive-docs`) tracked The docs come from the pinned `hyperhive` input (the `reference-docs` package
separately from the main `hyperhive` flake so editing docs re-locks only built from its `docs/` tree), so they match the hyperhive rev the host runs.
that input — not every agent's container gets rebuilt on a doc-only change.
## Agent icon ## Agent icon

View file

@ -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 (meta-flake + docs inputs) # nix/sources.nix filtered source views (the builder's hyperhive input)
# 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,9 +129,7 @@
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}";
# Narrow docs/ source, threaded as its own meta-flake input hyperhiveRev = lib.mkDefault (self.rev or null);
# 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

View file

@ -36,22 +36,13 @@ 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>,
/// URL of the hyperhive flake (no fragment). Inlined into per-agent /// Store path of the hyperhive flake source (no fragment). Used to
/// `flake.nix` files as `inputs.hyperhive.url`. /// identify the running revision (`current_flake_rev`); the meta flake
/// pins `hyperhive` by forge URL and [`Self::hyperhive_rev`] instead.
pub hyperhive_flake: String, pub hyperhive_flake: String,
/// URL of the narrow `docs/` source (no fragment). Inlined into the /// Git rev of the hyperhive checkout this host runs. Pins the meta
/// meta `flake.nix` as `inputs.hyperhive-docs.url` and threaded to /// flake's `hyperhive` input; `None` makes the meta render fail.
/// each agent as `services.hyperhive.agent.docs.source`. Its own store path pub hyperhive_rev: Option<String>,
/// 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
@ -198,12 +189,8 @@ pub struct Coordinator {
#[serde(default)] #[serde(default)]
pub struct HiveEnv { pub struct HiveEnv {
pub hyperhive_flake: String, pub hyperhive_flake: String,
/// Store-path URL of the narrow `docs/` source, wired into the meta /// Git rev of the hyperhive checkout this host runs; see [`Coordinator::hyperhive_rev`].
/// flake as `inputs.hyperhive-docs.url` and threaded to each agent as pub hyperhive_rev: Option<String>,
/// `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
@ -227,8 +214,7 @@ 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_docs_flake: String::new(), hyperhive_rev: None,
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(),
@ -404,8 +390,7 @@ impl Coordinator {
) -> Result<Self> { ) -> Result<Self> {
let HiveEnv { let HiveEnv {
hyperhive_flake, hyperhive_flake,
hyperhive_docs_flake, hyperhive_rev,
nixpkgs_flake,
claude_code_path, claude_code_path,
dashboard_port, dashboard_port,
agent_cpu_quota, agent_cpu_quota,
@ -443,8 +428,7 @@ impl Coordinator {
broker: Arc::new(broker), broker: Arc::new(broker),
build_logs, build_logs,
hyperhive_flake, hyperhive_flake,
hyperhive_docs_flake, hyperhive_rev,
nixpkgs_flake,
claude_code_path, claude_code_path,
dashboard_port, dashboard_port,
agent_cpu_quota, agent_cpu_quota,
@ -474,8 +458,7 @@ 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_docs_flake: self.hyperhive_docs_flake.clone(), hyperhive_rev: self.hyperhive_rev.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(),

View file

@ -78,15 +78,10 @@ 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: URL of the hyperhive flake. Inlined into each /// Override: store path of the hyperhive flake source, used to
/// per-agent `flake.nix` as the `hyperhive` input. /// identify the running revision.
#[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>,
@ -137,7 +132,6 @@ 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,
@ -161,9 +155,6 @@ 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;
} }

View file

@ -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,13 +327,37 @@ 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]) -> String { fn render_hive_flake(hive: &HiveEnv, agents: &[AgentSpec]) -> Result<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(
&hive.hyperhive_flake, hyperhive_url,
&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,
@ -341,17 +365,13 @@ fn render_hive_flake(hive: &HiveEnv, agents: &[AgentSpec]) -> String {
} }
fn render_flake( fn render_flake(
hyperhive_flake: &str, hyperhive_url: &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_flake, hyperhive_url,
docs_flake,
nixpkgs_flake,
claude_code_path, claude_code_path,
dashboard_port, dashboard_port,
agents, agents,
@ -732,9 +752,7 @@ 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_flake: &str, hyperhive_url: &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],
@ -746,42 +764,12 @@ 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");
// `nixpkgs` is a top-level meta input with an explicit store-path URL. // `hyperhive` is pinned by forge URL and rev, so the flake resolves on
// `hyperhive` then follows it via `hyperhive.inputs.nixpkgs.follows`. // any machine that can reach the forge. `nixpkgs` follows hyperhive's
// This cascades through to every agent because // own pin, which cascades to every agent because
// `agent-<n>.inputs.nixpkgs.follows = "nixpkgs"` resolves to the same // `agent-<n>.inputs.nixpkgs.follows = "nixpkgs"` resolves to this node.
// top-level node. let _ = writeln!(out, " hyperhive.url = \"{hyperhive_url}\";");
// out.push_str(" nixpkgs.follows = \"hyperhive/nixpkgs\";\n");
// 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");
} 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.
@ -842,14 +830,6 @@ 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
@ -1283,71 +1263,55 @@ mod tests {
} }
#[test] #[test]
fn render_flake_uses_explicit_nixpkgs_url_when_provided() { fn pinned_git_url_carries_the_rev_and_refuses_without_one() {
let out = render_flake( assert_eq!(
"github:example/hyperhive", pinned_git_url(
"path:/nix/store/bbbb-hyperhive-docs-source", "https://forge.example/hyperhive/hyperhive.git",
"path:/nix/store/aaaa-nixpkgs-source", Some("abc123")
None, )
8000, .unwrap(),
&[sample_spec("alice", 9001)], "git+https://forge.example/hyperhive/hyperhive.git?rev=abc123"
);
// 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_omits_docs_input_when_docs_flake_empty() { fn render_flake_pins_hyperhive_by_url_and_follows_its_nixpkgs() {
// 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(
"github:example/hyperhive", "git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"",
"path:/nix/store/aaaa-nixpkgs-source",
None, None,
8000, 8000,
&[sample_spec("alice", 9001)], &[sample_spec("alice", 9001)],
); );
assert!( assert!(
!out.contains("hyperhive-docs"), out.contains(
"no docs input/source when docs_flake is empty:\n{out}" "hyperhive.url = \"git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd\";"
),
"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(
"github:example/hyperhive", "git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"",
"path:/nix/store/aaaa-nixpkgs-source",
None, None,
8000, 8000,
&[sample_spec("alice", 9001)], &[sample_spec("alice", 9001)],
@ -1383,9 +1347,7 @@ 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(
"github:example/hyperhive", "git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"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)],
@ -1409,9 +1371,7 @@ 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(
"github:example/hyperhive", "git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"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)],
@ -1422,28 +1382,6 @@ 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`
@ -1455,9 +1393,7 @@ mod tests {
} }
}; };
let out = render_flake_with_lookup( let out = render_flake_with_lookup(
"github:example/hyperhive", "git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"path:/nix/store/bbbb-hyperhive-docs-source",
"path:/nix/store/aaaa-nixpkgs-source",
None, None,
8000, 8000,
&[ &[
@ -1486,9 +1422,7 @@ 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(
"github:example/hyperhive", "git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"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)],
@ -1513,9 +1447,7 @@ 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(
"github:example/hyperhive", "git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"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)],
@ -1543,9 +1475,7 @@ mod tests {
..sample_spec("droid", 9002) ..sample_spec("droid", 9002)
}; };
let out = render_flake_with_lookup( let out = render_flake_with_lookup(
"github:example/hyperhive", "git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"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],
@ -1596,9 +1526,7 @@ 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(
"github:example/hyperhive", "git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"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)],
@ -1639,9 +1567,7 @@ mod tests {
std::env::set_var(VARS[1], domain); std::env::set_var(VARS[1], domain);
} }
render_flake( render_flake(
"github:example/hyperhive", "git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"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)],
@ -1836,9 +1762,7 @@ 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(
"github:example/hyperhive", "git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"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)],
@ -1870,9 +1794,7 @@ 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(
"github:example/hyperhive", "git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"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)],
@ -1886,8 +1808,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("git+http"), !out.contains("agent-alice.url = \"git+http"),
"no forge git+http URL must remain in the rendered flake:\n{out}" "no forge git+http URL must remain for the agent input:\n{out}"
); );
} }
@ -1912,9 +1834,7 @@ mod tests {
let render = || { let render = || {
render_flake( render_flake(
"github:example/hyperhive", "git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"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)],
@ -2002,9 +1922,7 @@ 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(
"github:example/hyperhive", "git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"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)],
@ -2074,9 +1992,7 @@ 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(
"github:example/hyperhive", "git+https://forge.example/hyperhive/hyperhive.git?rev=0123abcd",
"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)],
@ -2112,8 +2028,16 @@ mod tests {
..HiveEnv::default() ..HiveEnv::default()
}; };
let agents = [sample_spec("alice", 9001)]; let agents = [sample_spec("alice", 9001)];
let small = render_hive_flake(&hive("4G"), &agents); let small = render_hive_flake_at(
let large = render_hive_flake(&hive("16G"), &agents); &hive("4G"),
"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"

View file

@ -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 standalone `hyperhive-docs` derivation) available read-only as the `reference-docs` package) 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,11 +26,7 @@
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) so a standalone container build from a full checkout build), which the meta flake's pinned `hyperhive` input provides.
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.
''; '';
}; };
@ -40,9 +36,7 @@
# `--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` (the narrow `hyperhive-docs` # Source is `services.hyperhive.agent.docs.source` (default: the `reference-docs` package). See hive-agent::turn.
# 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}";
}; };
}; };

View file

@ -1,8 +1,6 @@
# 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). The docs source is set the way the # adds (its CA, claude-code path and ports).
# 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
@ -14,25 +12,19 @@
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 = [ modules = [ agent.nixosModules.default ];
agent.nixosModules.default
{ services.hyperhive.agent.docs.source = hyperhive-docs.outPath; }
];
}).config.system.build.toplevel; }).config.system.build.toplevel;
}; };
} }

View file

@ -96,8 +96,7 @@ let
# c0re binary itself changes. # c0re binary itself changes.
serveConfigJson = builtins.toJSON { serveConfigJson = builtins.toJSON {
hyperhive_flake = cfg.hyperhiveFlake; hyperhive_flake = cfg.hyperhiveFlake;
hyperhive_docs_flake = cfg.hyperhiveDocs; hyperhive_rev = cfg.hyperhiveRev;
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

View file

@ -16,6 +16,10 @@
(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 = {
@ -70,24 +74,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 = ''
URL of the hyperhive flake (no fragment). Inlined into each Store path of the hyperhive flake source (no fragment). hive-c0re
per-agent `flake.nix` at `inputs.hyperhive.url`. The per-agent reads it to tell which revision is running, and the swarm builder
flake then pulls `hyperhive.nixosConfigurations.agent-base` to builds on it. The meta flake does not use it: that pins
build the container. Wired by `nixosModules.default` to this `hyperhive` by forge URL and `hyperhiveRev`. Wired by
flake's own filtered source — only override if you want agents `nixosModules.default` to this flake's own filtered source.
tracking a different ref.
''; '';
}; };
hyperhiveDocs = lib.mkOption { hyperhiveRev = lib.mkOption {
type = lib.types.str; type = lib.types.nullOr lib.types.str;
defaultText = lib.literalMD "the docs/ tree's own store path"; default = null;
defaultText = lib.literalMD "the hyperhive flake's own `self.rev`";
description = '' description = ''
URL of the narrow `docs/` source (no fragment). Inlined into the Git rev of the hyperhive checkout this host runs. hive-c0re pins
generated meta `flake.nix` at `inputs.hyperhive-docs.url` and the generated meta flake's `hyperhive` input to
threaded to each agent as `services.hyperhive.agent.docs.source`, from which the `git+<forge>/hyperhive/hyperhive.git?rev=<rev>`, and `nixpkgs`
harness resolves `$HIVE_DOCS_DIR`. Its own store path — separate follows that input. Wired by `nixosModules.default` from
from `hyperhiveFlake` — so a doc edit only re-locks this input `self.rev`, which a dirty tree does not have; with `null`,
instead of rebuilding every agent container. hive-c0re refuses to render the meta flake.
''; '';
}; };
agentBaseToplevel = lib.mkOption { agentBaseToplevel = lib.mkOption {
@ -105,20 +109,12 @@
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 for the `nixpkgs` input in the generated meta Store-path URL of the nixpkgs the swarm builder's check flake
flake. The meta flake declares this as a top-level input and builds on. The generated meta flake does not use it: its `nixpkgs`
wires `inputs.hyperhive.inputs.nixpkgs.follows = "nixpkgs"` so follows `hyperhive/nixpkgs`.
every agent container evaluates with this exact nixpkgs.
Defaults to `"path:''${pkgs.path}"` — the store path of the Defaults to `"path:''${pkgs.path}"`, the nixpkgs the host NixOS
nixpkgs the host NixOS module was evaluated with. When the module was evaluated with.
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 {
@ -340,7 +336,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. whatever `c0re.nixpkgsFlake` resolves to. nixpkgs — i.e. the nixpkgs pinned by hyperhive's own `flake.lock`.
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

View file

@ -89,11 +89,9 @@ 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: all three are already store paths, and the flake # Strings, not paths: both are already store paths.
# 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
@ -254,7 +252,6 @@ 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";
} }

View file

@ -51,8 +51,7 @@ 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`, so its agent base sees the tree without `docs/` that a # own `flake.nix`.
# real build gets; `self`'s own agent base would find `docs/` here.
builderCheck = builderCheck =
agentModule: agentModule:
let let
@ -69,7 +68,6 @@ 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;
@ -135,12 +133,11 @@ let
&& lib.elem "swarm-bao-pki.service" c.after; && lib.elem "swarm-bao-pki.service" c.after;
} }
{ {
# The check builds on the sources a hive on this host would render into # The check builds on the sources this host was deployed with, so a green check means
# its meta flake, so a green check means the same base. # the same hyperhive and nixpkgs base.
name = "the builder builds on the hyperhive, docs and nixpkgs this host's hives use"; name = "the builder builds on the hyperhive and nixpkgs this host was deployed with";
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"

View file

@ -4,23 +4,10 @@
# (crane's cargo-source filter — see ./rust.nix). # (crane's cargo-source filter — see ./rust.nix).
{ lib }: { lib }:
{ {
# Filtered view of this repo handed to hive-c0re as the `hyperhive` # Filtered view of this repo the swarm builder builds on as its `hyperhive`
# meta-flake input. Drops files no nix or cargo derivation reads — # input. Drops files no nix or cargo derivation reads — shell helper
# shell helper scripts and root-level markdown — so editing them # scripts and root-level markdown. `docs/` is kept: the agent base's
# does NOT change the store path and therefore does NOT force a # `reference-docs` package builds from it.
# 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 = ../.;
@ -31,19 +18,6 @@
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;
};
} }

View file

@ -12,9 +12,8 @@ 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 four - `nix build` of `nix/builder/check-flake`'s `check` output, with its three
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

View file

@ -68,7 +68,6 @@ 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,

View file

@ -1,5 +1,5 @@
//! One build: `nix build` of the check flake with its four inputs pointed at //! One build: `nix build` of the check flake with its three inputs pointed at
//! this host's hyperhive, hyperhive docs and nixpkgs and at the agent's config //! this host's hyperhive 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,8 +45,6 @@ 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.
@ -102,9 +100,6 @@ 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),
]) ])
@ -308,7 +303,6 @@ 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"),
@ -333,14 +327,7 @@ 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!( assert_eq!(overridden, ["hyperhive", "nixpkgs", "agent"]);
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"));
} }