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

@ -17,7 +17,7 @@ use crate::coordinator::Coordinator;
use super::{
CONFIG_ORG, SHARED_DOCS_REPO, SHARED_ORG, api, core_auth_header, core_token, forge_git_url,
forge_http_base, is_present,
forge_http_base, is_present, reader_token,
};
/// Creation options for an empty repo defaulting to `main`.
@ -223,17 +223,14 @@ pub async fn ensure_meta_remote(name: &str) -> Result<()> {
/// snapshot. A config deployed through hive-c0re without a forge merge never
/// reaches it.
///
/// Best-effort: returns `false` (not an error) rather than bailing when the
/// forge is absent, the core token isn't minted yet, `applied` already has a
/// `.git` (nothing to do), or any git step fails — callers fall back to a
/// less-current source or the original hard error. Only `true` once `applied` is
/// Best-effort: returns `false` (not an error) rather than bailing when there
/// is no [`reader_token`], `applied` already has a `.git` (nothing to do), or
/// any git step fails — callers fall back to a less-current source or the
/// original hard error. Only `true` once `applied` is
/// actually seeded and tagged `deployed/0`, mirroring first-spawn's own
/// `setup_applied` seed shape exactly.
pub async fn fetch_config_main_into_applied(name: &str) -> bool {
if !is_present().await {
return false;
}
let Some(token) = core_token() else {
let Some(reader) = reader_token().await else {
return false;
};
let applied = crate::paths::applied_dir(name);
@ -249,7 +246,7 @@ pub async fn fetch_config_main_into_applied(name: &str) -> bool {
return false;
}
let url = forge_git_url(&format!("{CONFIG_ORG}/{name}"));
let auth = core_auth_header(&token);
let auth = reader.auth_header();
let out = crate::lifecycle::git_command_authed(&auth)
.current_dir(&applied)
.args([
@ -298,14 +295,11 @@ pub async fn fetch_config_main_into_applied(name: &str) -> bool {
/// ancestry-gated advance for a reviewed PR merge, which this is not.
///
/// Best-effort, same shape as [`fetch_config_main_into_applied`]: returns
/// `false` (not an error) when the forge is absent, the core token isn't
/// minted, `applied` has no `.git` yet (nothing to fast-forward — reseed
/// instead), or any git step fails.
/// `false` (not an error) when there is no [`reader_token`], `applied` has
/// no `.git` yet (nothing to fast-forward — reseed instead), or any git step
/// fails.
pub async fn fast_forward_applied_main(name: &str) -> bool {
if !is_present().await {
return false;
}
let Some(token) = core_token() else {
let Some(reader) = reader_token().await else {
return false;
};
let applied = crate::paths::applied_dir(name);
@ -313,7 +307,7 @@ pub async fn fast_forward_applied_main(name: &str) -> bool {
return false;
}
let url = forge_git_url(&format!("{CONFIG_ORG}/{name}"));
let auth = core_auth_header(&token);
let auth = reader.auth_header();
let out = crate::lifecycle::git_command_authed(&auth)
.current_dir(&applied)
.args(["fetch", "--no-tags", &url, "refs/heads/main"])
@ -356,8 +350,8 @@ pub async fn fast_forward_applied_main(name: &str) -> bool {
/// is the fallback, not the default.
///
/// Best-effort like [`fetch_config_main_into_applied`], and `false` is an
/// ordinary answer rather than an error: the forge is absent, the core token
/// is not minted yet, or `agent-configs/<name>` has no `main` — the last of
/// ordinary answer rather than an error: there is no [`reader_token`], the
/// forge is unreachable, or `agent-configs/<name>` has no `main` — the last of
/// which is the *normal* case right after `swarm-controller` creates the
/// agent, before it has pushed anything into the repo it just made.
///
@ -366,17 +360,14 @@ pub async fn fast_forward_applied_main(name: &str) -> bool {
/// `.git` behind either — so the caller's "does this repo already exist"
/// check reads exactly the same before and after a failed attempt.
pub async fn clone_config_into_proposed(dir: &Path, name: &str) -> bool {
if !is_present().await {
return false;
}
let Some(token) = core_token() else {
let Some(reader) = reader_token().await else {
return false;
};
let url = forge_git_url(&format!("{CONFIG_ORG}/{name}"));
// `--branch main` fails outright against a repo with no commits, so an
// empty repo reads as "nothing to take" instead of cloning to an unborn
// HEAD that would then look like a seeded checkout.
let out = crate::lifecycle::git_command_authed(&core_auth_header(&token))
let out = crate::lifecycle::git_command_authed(&reader.auth_header())
.args([
"clone",
"--branch",