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:
parent
c8472966e9
commit
21e3f626f4
17 changed files with 1222 additions and 138 deletions
|
|
@ -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(),
|
||||
|
|
|
|||
Loading…
Reference in a new issue