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

@ -1,15 +1,18 @@
//! Optional Forgejo wiring — the `core/meta` repo, meta read-access
//! grants, and each agent's read-only grant on `internal/docs` (the
//! swarm-controller creates that repo, with the other swarm-wide orgs,
//! teams and repos).
//! No-op when `hive-forge` isn't running. Full design: `docs/integrations/forge.md`.
//! teams and repos). Those writes, as `core`, run only beside a local
//! `hive-forge` container; reads of the forge go through [`reader_token`] on
//! every hive. Full design: `docs/integrations/forge.md`.
mod pr_comment;
mod reader;
mod reconcile;
mod repos;
mod users;
pub use pr_comment::{config_repo, merged_pr_for_commit, post_pr_comment};
pub use reader::reader_token;
pub use reconcile::{fetch_forge_main, reconcile_config_apply, reconcile_config_status};
pub use repos::{
clone_config_into_proposed, ensure_meta_remote, ensure_repo, fast_forward_applied_main,
@ -71,14 +74,20 @@ fn git_url_with_base(base: &str, repo: &str) -> String {
}
/// The `http.extraHeader` value authenticating as the forge core user.
pub(crate) fn core_auth_header(token: &str) -> String {
basic_auth_header("core", token)
}
/// The `http.extraHeader` value carrying `token`. Forgejo takes the account
/// from the token, so `login` only labels the request.
///
/// Basic auth over a header rather than userinfo in the URL, so the secret
/// reaches git through the environment (see [`forge_git_url`]). Pair with
/// [`crate::lifecycle::git_command_authed`], which is the only thing that
/// should ever hold the result.
pub(crate) fn core_auth_header(token: &str) -> String {
fn basic_auth_header(login: &str, token: &str) -> String {
use base64::Engine as _;
let basic = base64::engine::general_purpose::STANDARD.encode(format!("core:{token}"));
let basic = base64::engine::general_purpose::STANDARD.encode(format!("{login}:{token}"));
format!("Authorization: Basic {basic}")
}
@ -223,8 +232,7 @@ pub async fn sync_agent(name: &str, core_token: Option<&str>) -> bool {
}
/// The `core_token.is_some()` half of [`ensure_all`]: the meta repo, the
/// local knowledge clone, the core avatar, and removing this hive's own
/// config-PR hook —
/// core avatar, and removing this hive's own config-PR hook —
/// every step that needs an authenticated forge client. The swarm-wide
/// objects (orgs, the `operators` team, mirrors, `internal/docs`,
/// `internal/knowledge`, the `agent-configs` avatar) are the
@ -243,16 +251,6 @@ async fn ensure_all_orgs_and_repos(token: &str) {
format!("forge: core/meta repo provisioning failed: {e}"),
);
}
// Clone knowledge repo locally so it can be bind-mounted into agents.
// The swarm-controller creates and seeds the repo itself.
if let Err(e) = crate::knowledge::ensure_local_clone(token).await {
tracing::warn!(error = ?e, "knowledge: ensure_local_clone failed");
crate::warnings::set_boot_warning(
"forge_knowledge_local_clone",
"crit",
format!("knowledge: local clone failed: {e}"),
);
}
if let Err(e) = ensure_core_avatar(token).await {
tracing::warn!(error = ?e, "forge: ensure_core_avatar failed");
crate::warnings::set_boot_warning(