diff --git a/docs/approvals.md b/docs/approvals.md index ca84dc77..c1713fb5 100644 --- a/docs/approvals.md +++ b/docs/approvals.md @@ -213,31 +213,13 @@ transient. The hive-c0re-owned repo at `/var/lib/hyperhive/meta/` declares one flake input per agent (`agent-.url = -"git+http:///agent-configs/.git"`) and one +"git+file:///var/lib/hyperhive/applied/"`) and one `nixosConfigurations.` output per agent. Each output wraps `inputs.agent-.nixosModules.default` with the identity + `HIVE_PORT` / `HIVE_LABEL` / `HIVE_DASHBOARD_PORT` injection module that `setup_applied` used to generate inline. Containers run against `--flake /var/lib/hyperhive/meta#`. -The declared input url is the agent's **forge config repo** (the -same `agent-configs/` the config-PR flow lands approved changes -on), so the meta flake references a reviewable, reproducible source -rather than a local checkout. hive-c0re authenticates that -`git+http` fetch via a git credential helper that reads the live -forge-core token — no token in the url or the lock. The deploy and -manual-rebuild paths, however, do **not** re-lock from the forge: -they `--override-input agent- -git+file:///var/lib/hyperhive/applied/`, locking the exact config -that `verify_commit` gated and `applied//main` was -fast-forwarded to. That keeps a deploy/rebuild reproducible and -independent of forge reachability — rebuilds fire on crash-restart -and meta bumps, not just config PRs — while the declared url stays -the forge. `sync_agents` re-renders + re-locks the persistent input; -a plain `nix flake lock` leaves an existing applied override in -place (it only re-locks when the declared url itself changes), so -the forge-declared / applied-deployed split is stable. - Per-deploy lock flow (two-phase, owned by `actions::run_merge_config_pr` → `deploy_applied_target` → `meta::{prepare,finalize,abort}_deploy`): diff --git a/docs/forge.md b/docs/forge.md index 5895d659..b657191b 100644 --- a/docs/forge.md +++ b/docs/forge.md @@ -64,11 +64,6 @@ Two things live in the `agent-configs` Forgejo organization: approved history, the `failed/` tag records the divergence). Repos stay private, so an agent can't read another agent's config. (Agents remain read-only collaborators on `core/meta`.) - hive-c0re also references this repo as the agent's **persistent meta - flake input** (`agent-.url = git+http:///agent-configs/.git`; - see [approvals.md § Meta flake](approvals.md)), fetching it as the `core` - user via a git credential helper that reads the live forge-core token — - so the config lives on the forge, not a hand-synced local checkout. - The dashboard links each container's "config" anchor to this config repo, so operators can click straight from the SW4RM tab into the rendered repo without an extra `git` step. diff --git a/hive-c0re/src/meta.rs b/hive-c0re/src/meta.rs index 7d824d38..66f61023 100644 --- a/hive-c0re/src/meta.rs +++ b/hive-c0re/src/meta.rs @@ -74,7 +74,6 @@ pub async fn sync_agents(hive: &HiveEnv, agents: &[AgentSpec]) -> Result<()> { &hive.operator_pronouns, &hive.context_window_tokens, agents, - crate::forge::forge_http_base(), ); let flake_path = dir.join("flake.nix"); let on_disk = std::fs::read_to_string(&flake_path).unwrap_or_default(); @@ -239,29 +238,7 @@ pub async fn prepare_deploy(name: &str) -> Result<()> { let _guard = META_LOCK.lock().await; let dir = crate::paths::meta_root(); let input = format!("agent-{name}"); - // Re-lock the agent input against the LOCAL applied mirror, not the - // persistent forge URL declared in the meta flake (see the `## Meta flake` - // note in docs/approvals.md): the deploy must build the exact reviewed - // config that `verify_commit` gated and `applied//main` was - // fast-forwarded to, and it must keep working when the forge is - // unreachable (rebuilds fire on crash-restart / meta bumps too, not just - // config PRs). `--override-input` writes the applied rev into the lock; - // the forge URL stays the declared, reviewable source of truth. - let applied = applied_override_url(&crate::paths::applied_dir(name)); - nix_logged( - &dir, - &[ - "flake", - "update", - &input, - "--override-input", - &input, - &applied, - ], - name, - "prepare-deploy", - ) - .await?; + nix_logged(&dir, &["flake", "update", &input], name, "prepare-deploy").await?; // Stage the new lock — git+file://'s dirty-tree fetcher reads // index entries, so the upcoming nixos-container update sees the // bumped rev without a commit yet. @@ -304,22 +281,7 @@ pub async fn lock_update_for_rebuild(name: &str) -> Result<()> { let _guard = META_LOCK.lock().await; let dir = crate::paths::meta_root(); let input = format!("agent-{name}"); - // Re-lock from the local applied mirror, not the persistent forge URL — - // same rationale as `prepare_deploy`: build exactly `applied//main` and - // stay reproducible when the forge is unreachable. - let applied = applied_override_url(&crate::paths::applied_dir(name)); - nix( - &dir, - &[ - "flake", - "update", - &input, - "--override-input", - &input, - &applied, - ], - ) - .await?; + nix(&dir, &["flake", "update", &input]).await?; if !paths_dirty(&dir, &["flake.lock"]).await? { return Ok(()); } @@ -339,17 +301,6 @@ fn agent_input_override(applied_dir: &Path, sha: &str) -> String { format!("git+file://{}?rev={sha}", applied_dir.display()) } -/// `--override-input` URL re-locking an agent's config input against its -/// LOCAL applied mirror (`git+file://`, current `main` head) -/// instead of the persistent forge URL declared in the meta flake. The -/// deploy + rebuild paths re-lock from here: the applied tree was already -/// fast-forwarded to the reviewed head, so this builds exactly that config -/// and stays reproducible when the forge is unreachable. No `?rev` — `main` -/// head is the reviewed head at deploy time. Pure so it's unit-testable. -fn applied_override_url(applied_dir: &Path) -> String { - format!("git+file://{}", applied_dir.display()) -} - /// Non-mutating "would this commit apply?" verify for the PR-based config /// flow. Evaluates the agent's nixos configuration with its meta /// input overridden to the exact `sha`, WITHOUT moving `applied/main` or @@ -635,11 +586,6 @@ pub async fn bulk_commit_topology( Ok(changed) } -#[allow( - clippy::too_many_arguments, - reason = "many genuine flake inputs (source flakes, port, pronouns, tokens, \ - agents, forge base); a params struct would just move the same fields" -)] fn render_flake( hyperhive_flake: &str, docs_flake: &str, @@ -648,7 +594,6 @@ fn render_flake( operator_pronouns: &str, context_window_tokens: &std::collections::HashMap, agents: &[AgentSpec], - forge_base: &str, ) -> String { render_flake_with_lookup( hyperhive_flake, @@ -658,7 +603,6 @@ fn render_flake( operator_pronouns, context_window_tokens, agents, - forge_base, agent_canonical_inputs, ) } @@ -935,7 +879,6 @@ fn render_flake_with_lookup( operator_pronouns: &str, context_window_tokens: &std::collections::HashMap, agents: &[AgentSpec], - forge_base: &str, lookup: F, ) -> String where @@ -980,19 +923,12 @@ where let _ = writeln!(out, " hyperhive-docs.url = \"{docs_flake}\";"); out.push_str(" hyperhive-docs.flake = false;\n"); } - // Each agent's *persistent* config input is its canonical repo on the - // forge (`git+{forge_base}/agent-configs/.git`, `forge_base` supplied - // by the caller from `HIVE_FORGE_URL`), authenticated by hive-core's git - // credential helper (which reads the live `forge-core-token` — no token in - // the URL or lock). The deploy re-lock + `verify_commit` eval keep pinning - // the local `applied/` override (`agent_input_override`), so a deploy - // never does a network fetch — only the persistent input tracks the forge. for spec in agents { let _ = writeln!( out, - " agent-{name}.url = \"git+{forge_base}/{org}/{name}.git\";", - name = spec.name, - org = crate::forge::CONFIG_ORG, + " agent-{}.url = \"git+file://{}\";", + spec.name, + crate::paths::applied_dir(&spec.name).display(), ); // For each canonical input the agent declares in its own // `flake.nix` (detected by reading its applied `flake.lock`), @@ -1514,20 +1450,6 @@ mod tests { ); } - #[test] - fn applied_override_url_targets_local_mirror_main_head() { - // Deploy + rebuild re-lock against the local applied mirror's `main` - // head (no `?rev`), never the persistent forge URL — so a rebuild - // survives forge unreachability and builds the fast-forwarded config. - let p = Path::new("/var/lib/hyperhive/agents/iris/applied"); - let url = applied_override_url(p); - assert_eq!(url, "git+file:///var/lib/hyperhive/agents/iris/applied"); - assert!( - !url.contains("?rev="), - "must lock main head, not a pinned rev: {url}" - ); - } - #[test] fn render_flake_uses_explicit_nixpkgs_url_when_provided() { let out = render_flake( @@ -1538,7 +1460,6 @@ mod tests { "she/her", &std::collections::HashMap::new(), &[sample_spec("alice", false, 9001)], - "http://forge.test", ); // nixpkgs is a top-level input with an explicit URL; hyperhive // follows it. @@ -1582,7 +1503,6 @@ mod tests { "she/her", &std::collections::HashMap::new(), &[sample_spec("alice", false, 9001)], - "http://forge.test", ); assert!( !out.contains("hyperhive-docs"), @@ -1602,7 +1522,6 @@ mod tests { "she/her", &std::collections::HashMap::new(), &[sample_spec("alice", false, 9001)], - "http://forge.test", ); assert!( out.contains("nixpkgs.follows = \"hyperhive/nixpkgs\""), @@ -1636,7 +1555,6 @@ mod tests { sample_spec("bitburner", false, 9002), sample_spec("dmatrix", false, 9003), ], - "http://forge.test", lookup, ); // bitburner declares nixpkgs → follows emitted. @@ -1665,7 +1583,6 @@ mod tests { "she/her", &std::collections::HashMap::new(), &[sample_spec("alice", false, 9001)], - "http://forge.test", |_| Vec::new(), ); // No agent-side follows when the lookup reports nothing @@ -1700,7 +1617,6 @@ mod tests { "she/her", &std::collections::HashMap::new(), &[sample_spec("alice", false, 9001)], - "http://forge.test", ); unsafe { std::env::remove_var("HIVE_FORGE_URL"); @@ -1723,36 +1639,6 @@ mod tests { ); } - #[test] - fn render_flake_agent_input_points_at_forge_config_repo() { - // The persistent agent config input must reference the canonical - // repo on the forge (git+http, org `agent-configs`), NOT the local - // `applied/` checkout — that's what lets the config live on the - // forge instead of a hand-synced local copy. Auth is out-of-band via - // hive-core's git credential helper, so no creds appear in the URL. - // `forge_base` is an explicit param now, so no env mutation is needed. - let out = render_flake( - "github:example/hyperhive", - "path:/nix/store/bbbb-hyperhive-docs-source", - "path:/nix/store/aaaa-nixpkgs-source", - 8000, - "she/her", - &std::collections::HashMap::new(), - &[sample_spec("alice", false, 9001)], - "http://forge.example.test", - ); - assert!( - out.contains( - "agent-alice.url = \"git+http://forge.example.test/agent-configs/alice.git\"" - ), - "expected the agent input to point at the forge config repo:\n{out}" - ); - assert!( - !out.contains("agent-alice.url = \"git+file://"), - "the local applied/ path must no longer be the persistent input:\n{out}" - ); - } - #[test] fn render_flake_embeds_hive_ca_when_signalled() { // When hive-tls.nix signals a self-signed hive CA via @@ -1779,7 +1665,6 @@ mod tests { "she/her", &std::collections::HashMap::new(), &[sample_spec("alice", false, 9001)], - "http://forge.test", ) }; @@ -1862,7 +1747,6 @@ mod tests { "she/her", &std::collections::HashMap::new(), &[sample_spec("alice", false, 9001)], - "http://forge.test", ) }; unsafe { @@ -1947,7 +1831,6 @@ mod tests { "she/her", &std::collections::HashMap::new(), &[sample_spec("alice", false, 9001)], - "http://forge.test", ) }; unsafe { diff --git a/nix/host-modules/hive-c0re/default.nix b/nix/host-modules/hive-c0re/default.nix index ce89b2fe..ae2736be 100644 --- a/nix/host-modules/hive-c0re/default.nix +++ b/nix/host-modules/hive-c0re/default.nix @@ -21,33 +21,9 @@ let # only satisfies the ownership guard. libgit2 honours the literal `*` # (mid-path globs aren't supported, so per-agent repos can't be listed); # in practice these processes only ever touch hyperhive's own repos. - # - # The agent config inputs now live on the forge - # (`git+http://${forge.domain}/agent-configs/.git`, see meta.rs render), - # so hive-core's `nix flake update` of those inputs is an authenticated - # `git+http` fetch. The `[credential]` stanza points git at the `hive-forge` - # helper below (scoped to the forge host) so the fetch authenticates as the - # forge `core` user with no token in any URL or lock. safeDirGitconfig = pkgs.writeText "hyperhive-safe-gitconfig" '' [safe] directory = * - [credential "http://${config.services.hyperhive.forge.domain}"] - helper = hive-forge - username = core - ''; - - # git credential helper for hive-core's authenticated fetches of the - # agent-config repos on the forge. Reads the live forge-core admin token - # (`/var/lib/hyperhive/forge-core-token` — paths.rs `FORGE_CORE_TOKEN`) on - # every invocation, so it never holds a stale copy and survives token - # rotation. Scoped to the forge host by the `[credential]` stanza above; - # implements the git credential-helper protocol (only `get` answers). - forgeCredHelper = pkgs.writeShellScriptBin "git-credential-hive-forge" '' - [ "''${1:-}" = "get" ] || exit 0 - if [ -r /var/lib/hyperhive/forge-core-token ]; then - printf 'username=core\n' - printf 'password=%s\n' "$(cat /var/lib/hyperhive/forge-core-token)" - fi ''; # The `hive-c0re serve` config JSON. Keys are snake_case to match the @@ -140,9 +116,6 @@ in after = [ "hive-c0re.socket" ]; path = [ pkgs.git - # `git-credential-hive-forge` on PATH so git finds it when nix fetches - # the forge-hosted agent-config inputs (helper = hive-forge). - forgeCredHelper "/run/current-system/sw" ]; environment = import ./environment.nix { inherit lib config pkgs; };