Watch
0
0
Fork
You've already forked hyperhive
0

drop the hive's push of the legacy core/meta repo

Nothing reads core/meta: builds come from internal/meta. Remove push_meta, ensure_meta_remote, meta_read_access, ensure_repo, the per-agent forge sync that only served them, and the git commit path in meta.rs; remove_permission_files now just deletes the seed files.
This commit is contained in:
atlas 2026-10-25 16:35:46 +01:00 • committed by mara
commit b6b568e97e

View file

@ -1,149 +1,69 @@
//! The hive's `meta/` directory: plain state beside the hive's other files
//! (`topology.json`, the swarm limits, the permission seed files) that the
//! manager container sees read-only. Agents are built from the swarm's
//! `internal/meta` ([`crate::meta_rev`]), not from anything here; a hive that
//! predates it also has a git repo in this directory, which
//! [`remove_permission_files`] still commits to.
//! (`topology.json`, the swarm limits) that the manager container sees
//! read-only. Agents are built from the swarm's `internal/meta`
//! ([`crate::meta_rev`]), not from anything here.
use std::path::Path;
use anyhow::{Context, Result, bail};
use crate::lifecycle;
const GIT_NAME: &str = "c0re";
const GIT_EMAIL: &str = "c0re@hyperhive.local";
use anyhow::{Context, Result};
/// Where the manager sees this directory inside its container (RO bind).
pub const CONTAINER_MANAGER_META_MOUNT: &str = "/meta";
/// Delete the meta repo's permission files and commit the removal. Their
/// grants live in the swarm's `agent-permissions` bucket once
/// `workers::permissions_import` has sent them.
/// Delete the permission seed files a hive that predates the swarm's
/// permissions left in the meta directory. Their grants live in the swarm's
/// `agent-permissions` bucket once `workers::permissions_import` has sent
/// them. A file already gone is fine.
///
/// # Errors
///
/// Removing a file or a git stage/commit step fails.
pub async fn remove_permission_files() -> Result<()> {
let dir = crate::paths::meta_root();
let mut removed = Vec::new();
for file in crate::workers::permissions_import::META_FILES {
/// Removing a file fails.
pub fn remove_permission_files() -> Result<()> {
remove_in(
&crate::paths::meta_root(),
crate::workers::permissions_import::META_FILES,
)
}
fn remove_in(dir: &std::path::Path, files: &[&str]) -> Result<()> {
for file in files {
match std::fs::remove_file(dir.join(file)) {
Ok(()) => {}
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
Err(e) => return Err(e).with_context(|| format!("removing {file}")),
}
git(&dir, &["rm", "--cached", "--ignore-unmatch", "-q", file]).await?;
// Only a tracked file has a removal to commit; naming an untracked
// one makes `git commit -- <path>` fail.
if paths_dirty(&dir, &[file]).await? {
removed.push(*file);
}
}
if !removed.is_empty() {
git_commit_paths(
&dir,
"permissions moved to the swarm's agent-permissions",
&removed,
)
.await?;
}
Ok(())
}
/// Run `git <args>` in `dir` within [`lifecycle::GIT_LOCAL_DEADLINE`]; the caller
/// reads the exit status.
async fn git_output(dir: &Path, args: &[&str]) -> Result<std::process::Output> {
lifecycle::run_with_deadline(
lifecycle::git_command().current_dir(dir).args(args),
lifecycle::GIT_LOCAL_DEADLINE,
&format!("git {} in {}", args.join(" "), dir.display()),
)
.await
}
async fn git(dir: &Path, args: &[&str]) -> Result<()> {
let out = git_output(dir, args).await?;
if !out.status.success() {
bail!(
"git {} failed ({}): {}",
args.join(" "),
out.status,
String::from_utf8_lossy(&out.stderr).trim()
);
}
Ok(())
}
/// Path-limited commit: commits ONLY the given paths, so unrelated staged
/// content can never be swept into the commit.
async fn git_commit_paths(dir: &Path, message: &str, paths: &[&str]) -> Result<()> {
let name = format!("user.name={GIT_NAME}");
let email = format!("user.email={GIT_EMAIL}");
let mut args = vec!["-c", &name, "-c", &email, "commit", "-m", message, "--"];
args.extend_from_slice(paths);
git(dir, &args).await?;
if let Err(e) = crate::forge::push_meta(dir).await {
tracing::warn!(error = ?e, "forge: meta push after commit failed (non-fatal)");
}
Ok(())
}
/// True when any of `paths` differs between HEAD and the index or working
/// tree.
async fn paths_dirty(dir: &Path, paths: &[&str]) -> Result<bool> {
let mut args = vec!["diff", "--quiet", "HEAD", "--"];
args.extend_from_slice(paths);
let out = git_output(dir, &args).await?;
// Exit 0 = no differences; 1 = differences; anything else (e.g.
// no HEAD yet on a fresh repo) → treat as dirty so the commit
// path runs and surfaces real errors loudly.
Ok(!out.status.success())
}
#[cfg(test)]
mod tests {
use super::*;
use super::remove_in;
/// A path-limited commit must leave an unrelated staged file untouched.
#[tokio::test]
async fn path_limited_commit_leaves_unrelated_staged_file_alone() {
let tmp = tempfile::tempdir().expect("tempdir");
let dir = tmp.path();
git(dir, &["init", "--initial-branch=main"])
.await
.expect("git init");
std::fs::write(dir.join("resource-limits.json"), "{}").expect("write");
std::fs::write(dir.join("other.json"), "v1").expect("write");
git(dir, &["add", "-A"]).await.expect("add");
git_commit_paths(dir, "seed", &["resource-limits.json", "other.json"])
.await
.expect("seed commit");
// Something stages a change (uncommitted)…
std::fs::write(dir.join("other.json"), "v2-staged").expect("write");
git(dir, &["add", "other.json"]).await.expect("stage");
// …and a limits change commits, path-limited.
std::fs::write(dir.join("resource-limits.json"), r#"{"alice":[]}"#).expect("write");
git(dir, &["add", "resource-limits.json"])
.await
.expect("add");
git_commit_paths(
dir,
"set resource limits for alice",
&["resource-limits.json"],
)
.await
.expect("path-limited commit");
// The limits file is committed; the staged one is not.
#[test]
fn the_listed_files_go_and_nothing_else() {
let dir = tempfile::tempdir().unwrap();
for name in ["capabilities.json", "tool-groups.json", "topology.json"] {
std::fs::write(dir.path().join(name), "{}").unwrap();
}
remove_in(dir.path(), &["capabilities.json", "tool-groups.json"]).unwrap();
assert!(!dir.path().join("capabilities.json").exists());
assert!(!dir.path().join("tool-groups.json").exists());
assert!(
!paths_dirty(dir, &["resource-limits.json"])
.await
.expect("check"),
"limits file must be committed"
dir.path().join("topology.json").exists(),
"other state stays"
);
// A file already gone is fine.
remove_in(dir.path(), &["capabilities.json"]).unwrap();
}
#[test]
fn a_file_that_cannot_be_removed_is_an_error_naming_it() {
let dir = tempfile::tempdir().unwrap();
// A directory where the file belongs fails with a non-NotFound error.
std::fs::create_dir(dir.path().join("capabilities.json")).unwrap();
let err = remove_in(dir.path(), &["capabilities.json"]).unwrap_err();
assert!(
paths_dirty(dir, &["other.json"]).await.expect("check"),
"the staged file must survive the limits commit"
format!("{err:#}").contains("removing capabilities.json"),
"{err:#}"
);
}
}