Phase 9 R1: repo-ensure RPC + fleet-health fixes #107

Merged
osobh merged 6 commits from phase-9-r1a-repo-ensure-rpc into main 2026-07-31 20:05:11 +00:00
2 changed files with 93 additions and 0 deletions
Showing only changes of commit dd2b90872a - Show all commits
+84
View File
@@ -241,6 +241,90 @@ mod tests {
assert_eq!(p.parent().unwrap().parent().unwrap(), root.as_path()); assert_eq!(p.parent().unwrap().parent().unwrap(), root.as_path());
} }
/// End-to-end: seed a bare git repo in a tempdir, ensure it into
/// a fresh repo_root, verify cached=true on the second call, then
/// release. Requires `git` on PATH; marked `#[ignore]` so CI
/// without git-installed runners skips it silently.
#[tokio::test]
#[ignore]
async fn ensure_then_cached_then_release() {
let tmp = tempfile::tempdir().unwrap();
let source = tmp.path().join("source.git");
let repo_root = tmp.path().join("repos");
// Init a bare-ish source repo with one commit on branch `main`.
let seed_dir = tmp.path().join("seed");
std::fs::create_dir_all(&seed_dir).unwrap();
let git = |args: &[&str], cwd: &std::path::Path| {
let out = std::process::Command::new("git")
.args(args)
.current_dir(cwd)
.output()
.unwrap();
assert!(
out.status.success(),
"git {:?} failed: {}",
args,
String::from_utf8_lossy(&out.stderr)
);
};
git(&["init", "-b", "main"], &seed_dir);
git(&["config", "user.email", "t@t"], &seed_dir);
git(&["config", "user.name", "t"], &seed_dir);
std::fs::write(seed_dir.join("README"), "hi").unwrap();
git(&["add", "."], &seed_dir);
git(&["commit", "-m", "seed"], &seed_dir);
git(
&["clone", "--bare", seed_dir.to_str().unwrap(), source.to_str().unwrap()],
tmp.path(),
);
let req = RepoEnsureRequest {
url: format!("file://{}", source.display()),
git_ref: "main".into(),
workspace: "workspace:test".into(),
};
// First ensure → fresh clone.
let r1 = ensure_repo(&repo_root, &req).await.unwrap();
assert!(!r1.cached, "first ensure should not be cached");
assert!(!r1.head_sha.is_empty());
assert!(std::path::Path::new(&r1.path).join(".git").exists());
// Second ensure → cache hit.
let r2 = ensure_repo(&repo_root, &req).await.unwrap();
assert!(r2.cached, "second ensure should hit cache");
assert_eq!(r1.head_sha, r2.head_sha);
assert_eq!(r1.path, r2.path);
// Release removes it.
let rel = release_repo(
&repo_root,
&RepoReleaseRequest {
url: req.url.clone(),
git_ref: req.git_ref.clone(),
workspace: req.workspace.clone(),
},
)
.await
.unwrap();
assert!(rel.removed);
assert!(!std::path::Path::new(&r1.path).exists());
// Idempotent release.
let rel2 = release_repo(
&repo_root,
&RepoReleaseRequest {
url: req.url,
git_ref: req.git_ref,
workspace: req.workspace,
},
)
.await
.unwrap();
assert!(!rel2.removed);
}
#[test] #[test]
fn sanitize_component_replaces_traversal() { fn sanitize_component_replaces_traversal() {
assert_eq!(sanitize_component(".."), "_"); assert_eq!(sanitize_component(".."), "_");
+9
View File
@@ -226,6 +226,15 @@ impl ClusterServices {
r = r.with_tag_store(store.clone()); r = r.with_tag_store(store.clone());
} }
r = r.with_outbound_client(outbound_client); r = r.with_outbound_client(outbound_client);
// Phase 9 R1a wiring: enable RepoEnsure/RepoRelease when
// the daemon has a blob_store_root (which is the
// canonical anchor for all fleet on-disk state). Repos
// materialize under <blob_store_root>/repos/<workspace>/…
if let Some(root) = blob_store_root.as_ref() {
let repo_root = root.join("repos");
let _ = std::fs::create_dir_all(&repo_root);
r = r.with_repo_root(repo_root);
}
let router = Arc::new(r); let router = Arc::new(r);
let server = let server =