Watch
0
0
Fork
You've already forked hyperhive
0

swarm-builder: give the check flake the hive's docs source

The builder's hyperhive input is the docs-stripped flake source, so an
agent config with docs.enable = true failed to evaluate: docs.source
defaults to reference-docs, built from the missing docs/. Pass the host's
c0re.hyperhiveDocs as a hyperhive-docs input and set docs.source from it,
as the hive's meta flake does.

Fixes #5411
This commit is contained in:
atlas 2026-10-17 08:54:06 +02:00 • committed by mara
commit e9c471caeb
6 changed files with 80 additions and 10 deletions

View file

@ -26,6 +26,10 @@ Where it runs and how it's enabled: [`services.md`](services.md#builder).
- `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
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>`.
A build has one hour.
4. The reply is the system closure's store path, or the last lines `nix`
@ -48,7 +52,7 @@ Where it runs and how it's enabled: [`services.md`](services.md#builder).
- It composes the config the way a hive's meta flake does
(`agent-base.extendModules` with the config's `nixosModules.default`), minus
what a hive adds: its CA bundle, docs source, `claude-code` path, ports and
what a hive adds: its CA bundle, `claude-code` path, ports and
OTEL settings, and the `follows` that point the config's own inputs at the
hive's. A green check can therefore still fail on a hive, and the reverse.
- The agent base is the builder's host's, not the hive's. Hives on another

View file

@ -1,6 +1,8 @@
# 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
# adds (its CA, docs source, 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`
# (swarm-builder/src/nix_build.rs), so the URLs below are never fetched and
@ -12,15 +14,25 @@
nixpkgs.url = "flake:nixpkgs";
hyperhive.url = "flake:hyperhive";
hyperhive.inputs.nixpkgs.follows = "nixpkgs";
hyperhive-docs.url = "flake:hyperhive-docs";
hyperhive-docs.flake = false;
agent.url = "flake:agent";
};
outputs =
{ hyperhive, agent, ... }:
{
hyperhive,
hyperhive-docs,
agent,
...
}:
{
packages.x86_64-linux.check =
(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;
};
}

View file

@ -89,10 +89,11 @@ let
# The `nix` the host daemon runs, so client and daemon speak one protocol.
nix = config.nix.package;
# Strings, not paths: both are already store paths, and the flake reference
# the daemon passes must be the same one a hive's meta flake names.
# 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;
nixpkgsFlake = hyperhiveCfg.c0re.nixpkgsFlake;
docsFlake = hyperhiveCfg.c0re.hyperhiveDocs;
privateNetwork = true;
in
@ -253,6 +254,7 @@ in
SWARM_BUILDER_CHECK_FLAKE = "${../builder/check-flake}";
SWARM_BUILDER_HYPERHIVE_FLAKE = hyperhiveFlake;
SWARM_BUILDER_NIXPKGS_FLAKE = nixpkgsFlake;
SWARM_BUILDER_HYPERHIVE_DOCS_FLAKE = docsFlake;
SWARM_BUILDER_FORGE_URL = "https://${forgeCfg.domain}";
SWARM_BUILDER_CONFIG_ORG = "agent-configs";
}

View file

@ -49,6 +49,30 @@ let
svc = inner.systemd.services.swarm-builder;
env = svc.environment;
# 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
# 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 =
agentModule:
let
src = env.SWARM_BUILDER_HYPERHIVE_FLAKE;
hyperhive = (import "${src}/flake.nix").outputs (
self.inputs
// {
self = hyperhive // {
outPath = src;
inherit (self) inputs;
};
}
);
in
((import "${env.SWARM_BUILDER_CHECK_FLAKE}/flake.nix").outputs {
inherit hyperhive;
hyperhive-docs.outPath = env.SWARM_BUILDER_HYPERHIVE_DOCS_FLAKE;
agent.nixosModules.default = agentModule;
}).packages.x86_64-linux.check;
cases = [
{
name = "the swarm builder's container exists only where its host enables it";
@ -113,9 +137,10 @@ let
{
# The check builds on the sources a hive on this host would render into
# its meta flake, so a green check means the same base.
name = "the builder builds on the hyperhive and nixpkgs this host's hives use";
name = "the builder builds on the hyperhive, docs and nixpkgs this host's hives use";
ok =
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
&& lib.hasSuffix "-check-flake" env.SWARM_BUILDER_CHECK_FLAKE
&& env.SWARM_BUILDER_CONFIG_ORG == "agent-configs"
@ -150,6 +175,18 @@ let
&& lib.elem "execution" reviewer
&& !(lib.elem "meta" reviewer);
}
{
# `docs.enable` reads `docs.source`, whose default builds from the
# `docs/` the `hyperhive` source has none of; an eval error there fails
# this whole group.
name = "the builder's check evaluates a config with the reference docs on";
ok =
lib.hasSuffix ".drv"
(builderCheck {
services.hyperhive.agent.user.name = "a1";
services.hyperhive.agent.docs.enable = true;
}).drvPath;
}
{
# The responder denies a client id it does not know, and a NATS denial
# arrives as a timeout.

View file

@ -49,6 +49,7 @@ async fn main() -> Result<()> {
check_flake: var("CHECK_FLAKE")?,
hyperhive: var("HYPERHIVE_FLAKE")?,
nixpkgs: var("NIXPKGS_FLAKE")?,
hyperhive_docs: var("HYPERHIVE_DOCS_FLAKE")?,
forge_url: forge_url()?,
config_org: var("CONFIG_ORG")?,
deadline: BUILD_DEADLINE,

View file

@ -1,5 +1,6 @@
//! One build: `nix build` of the check flake with its three inputs pointed at
//! this host's hyperhive and nixpkgs and at the agent's config commit.
//! One build: `nix build` of the check flake with its four inputs pointed at
//! this host's hyperhive, hyperhive docs and nixpkgs and at the agent's config
//! commit.
//!
//! The config repo is private, so `git` fetches it with the builder's forge
//! token. The token reaches `git` through a credential helper that reads it
@ -44,6 +45,8 @@ pub struct Builder {
pub hyperhive: String,
/// The nixpkgs source this host was deployed from.
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.
pub forge_url: String,
/// The org the config repos are in.
@ -99,6 +102,9 @@ impl Builder {
"nixpkgs".to_owned(),
self.nixpkgs.clone(),
"--override-input".to_owned(),
"hyperhive-docs".to_owned(),
self.hyperhive_docs.clone(),
"--override-input".to_owned(),
"agent".to_owned(),
self.agent_url(job),
])
@ -302,6 +308,7 @@ mod tests {
check_flake: "/nix/store/c-check".to_owned(),
hyperhive: "/nix/store/h-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(),
config_org: "agent-configs".to_owned(),
nix: PathBuf::from("nix"),
@ -326,7 +333,14 @@ mod tests {
.filter(|w| w[0] == "--override-input")
.map(|w| w[1].as_str())
.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 == "--no-write-lock-file"));
}