Watch
0
0
Fork
You've already forked hyperhive
0

hives read the forge through their own swarm-minted read account

swarm-controller now gives every hive in its directory a forge account,
`hive-<hive>`, in a new `hives` team of `agent-configs` (read on
`repo.code` + `repo.pulls`, every repo including later ones), and mints
one `read:repository` token named `swarm-hive` into the store at
`swarm/hives/<hive>/forge/token`. A five-minute pass queues a
`MintHiveForgeToken` node for a hive whose token is missing or no longer
matches the forge, whose account is missing, or whose team membership or
team settings drifted. A hive whose store or forge read failed is never
queued, so an outage cannot rotate every hive's token. The token decision
reuses `agent_token`'s classifier and replace step, now parameterised by
name and scopes.

An agent may be called `hive-<x>`. The hive account is created with
`hive-<hive>@hives.hyperhive.local` and the job refuses an account by that
name with any other email before it locks it down, adds it to the team or
mints for it; agent creation already refuses the hive's account by the
same email check.

The controller's bao policy gains the `swarm/hives/+/forge/token` leaf
(create/read/update), pinned by the bao-grants module-eval assertion. The
hive's own policy already reads `swarm/hives/<hive>/*`.

hive-c0re reads that token through `forge::reader_token()`, falling back
to the core token file when the store holds none, and uses it for:
fetching a merged config rev (swarm deploy, `hivectl forge
reconcile-config`), reseeding a purged `applied`, the meta-update
cascade's fast-forward, seeding `proposed` on first deploy, and finding
the PR that merged a refused rev. Those reads no longer require a local
`hive-forge` container. The `/knowledge` clone moves from the forge
host's boot sweep into the knowledge pull, so every hive clones it.

Posting the deploy-failure comment on that PR stays on the core token.

Refs #4851
This commit is contained in:
atlas 2026-10-07 20:10:41 +02:00
commit 21e3f626f4
17 changed files with 1222 additions and 138 deletions

View file

@ -22,7 +22,7 @@
use anyhow::{Context, Result};
use crate::coordinator::Coordinator;
use crate::forge::{core_auth_header, forge_git_url};
use crate::forge::forge_git_url;
pub const ORG: &str = "internal";
pub const REPO: &str = "knowledge";
@ -38,14 +38,12 @@ pub use crate::paths::KNOWLEDGE_DIR as LOCAL_DIR;
pub const CONTAINER_MOUNT: &str = "/knowledge";
/// Clone `internal/knowledge` to [`LOCAL_DIR`] if it is not already a git
/// repository. `core_token` authenticates the HTTPS clone so private repos
/// work. Idempotent — skips if `LOCAL_DIR/.git` exists.
/// repository, authenticated with `auth` (an `http.extraHeader` value) when
/// given. Idempotent — skips if `LOCAL_DIR/.git` exists.
///
/// The swarm controller seeds a README into a freshly created repo. A clone
/// taken before that lands is empty; the next [`pull`] fills it.
///
/// Called once at hive-c0re startup after `forge::ensure_all`.
pub async fn ensure_local_clone(core_token: &str) -> Result<()> {
async fn ensure_local_clone(auth: Option<&str>) -> Result<()> {
let git_dir = std::path::Path::new(LOCAL_DIR).join(".git");
if git_dir.exists() {
tracing::debug!("knowledge: local clone already present at {LOCAL_DIR}");
@ -53,7 +51,11 @@ pub async fn ensure_local_clone(core_token: &str) -> Result<()> {
}
std::fs::create_dir_all(LOCAL_DIR).context("create knowledge local dir")?;
let url = forge_git_url(&format!("{ORG}/{REPO}"));
let out = crate::lifecycle::git_command_authed(&core_auth_header(core_token))
let mut cmd = match auth {
Some(header) => crate::lifecycle::git_command_authed(header),
None => crate::lifecycle::git_command(),
};
let out = cmd
.args(["clone", &url, LOCAL_DIR])
.output()
.await
@ -86,15 +88,21 @@ async fn head_sha() -> Option<String> {
/// `--ff-only` so a force-push to the knowledge repo never wedges the local
/// copy silently.
///
/// When there is no clone yet, takes one instead ([`ensure_local_clone`]),
/// so every hive gets `/knowledge` from its first pull.
///
/// When the pull actually moves `HEAD` (a real change, not a no-op),
/// broadcasts a short `git diff --stat` summary to every live agent's
/// inbox via `coord` — inbox-only, no forced wake.
pub async fn pull(coord: &Coordinator) -> Result<()> {
// Sanity: if the clone is missing (e.g. storage was wiped), refuse
// to pull and let the caller decide whether to re-clone.
// The repo is public (the swarm controller makes it so), so an
// unauthenticated clone or pull is enough — but authenticate when a token
// is around, which keeps this working if the repo is ever made private
// again.
let auth = crate::forge::reader_token().await.map(|t| t.auth_header());
let git_dir = std::path::Path::new(LOCAL_DIR).join(".git");
if !git_dir.exists() {
anyhow::bail!("knowledge: {LOCAL_DIR}/.git not found — clone first");
return ensure_local_clone(auth.as_deref()).await;
}
// Rewrite any credentialed `origin` left by an older clone, which spliced
// `core:<token>@` into the URL and persisted it in `.git/config` — the one
@ -129,10 +137,6 @@ pub async fn pull(coord: &Coordinator) -> Result<()> {
.await;
let before = head_sha().await;
// The repo is public (the swarm controller makes it so), so an
// unauthenticated pull is enough — but authenticate when a token is around,
// which keeps this working if the repo is ever made private again.
let auth = crate::forge::core_token().map(|t| core_auth_header(&t));
let mut cmd = match &auth {
Some(header) => crate::lifecycle::git_command_authed(header),
None => crate::lifecycle::git_command(),