Watch
0
0
Fork
You've already forked hyperhive
0

hive-priv: overlay the knowledge .git only when the clone has one, so agents start before the first clone

This commit is contained in:
atlas 2026-11-07 15:12:51 +01:00
commit 78b0451ed1
2 changed files with 35 additions and 8 deletions

View file

@ -112,6 +112,12 @@ tmpfs at `/knowledge/.git`, so agents see the documents, not the branches or
history. An older clone also kept a token in `.git/config`; `knowledge::pull()`
rewrites that remote to the plain URL.
hive-priv adds the overlay only when the host clone has a `.git`. Before the
first clone succeeds, the agent starts with an empty `/knowledge` and no
overlay, because nspawn can't create the mount point inside the read-only
bind. A container that started in that window sees the `.git` once the clone
lands, until its next start.
## Contributing
Agents have read access to `internal/knowledge` (it's public) but no

View file

@ -414,9 +414,15 @@ fn validate_cpu_quota(value: &str) -> Result<()> {
///
/// An overlay rather than an exported copy: there is no second tree to keep in
/// sync, so nothing can go stale, and no code path has to remember to refresh
/// it. It is applied unconditionally: the directory is created empty and cloned
/// into later, so a container started before the clone must already hide the
/// `.git` that arrives.
/// it.
///
/// Applied only when the bind's host source has a `.git` entry. nspawn creates
/// the mount point inside the read-only bind, and a read-only mount refuses
/// that (`EROFS`) where nothing exists to mount over, which fails the whole
/// container start while the first clone is missing. A container started
/// before that clone has no overlay, so it sees the `.git` that arrives until
/// its next start rewrites these flags. The entry is looked up without
/// following a symlink, and a symlink named `.git` counts as present.
///
/// ⚠️ Ordering matters: these must be appended **after** the `--bind` flags so
/// nspawn mounts them on top of the already-mounted trees.
@ -424,6 +430,7 @@ fn git_overlay_flags(binds: &[BindMount]) -> Vec<String> {
binds
.iter()
.filter(|b| b.container_path == "/knowledge")
.filter(|b| std::fs::symlink_metadata(Path::new(&b.host_path).join(".git")).is_ok())
.map(|b| format!("--tmpfs={}/.git", b.container_path))
.collect()
}
@ -814,19 +821,33 @@ mod tests {
}
}
/// The knowledge tree gets its `.git` overlaid, whatever its source holds:
/// it is cloned into after the container may have started, so the overlay
/// does not wait for a `.git` to exist.
/// The knowledge tree gets its `.git` overlaid when its source holds one.
/// Without one the overlay is left out, since nspawn cannot create the
/// mount point inside the read-only bind and the container would not start.
#[test]
fn the_knowledge_tree_gets_its_dot_git_hidden() {
fn the_knowledge_dot_git_is_hidden_only_where_there_is_one() {
let flags = git_overlay_flags(&[bind_from(&repo_source(), "/knowledge")]);
assert_eq!(flags, ["--tmpfs=/knowledge/.git"]);
let empty = scratch().to_string_lossy().into_owned();
let flags = git_overlay_flags(&[bind_from(&empty, "/knowledge")]);
assert!(
flags.is_empty(),
"overlaid a source with no .git: {flags:?}"
);
}
/// A symlink named `.git` is not followed: it counts as present, even
/// dangling, so the path it leads to is hidden rather than skipped.
#[test]
fn a_dangling_dot_git_symlink_still_gets_the_overlay() {
let dir = scratch();
std::os::unix::fs::symlink("/nonexistent", dir.join(".git")).unwrap();
let src = dir.to_string_lossy().into_owned();
let flags = git_overlay_flags(&[bind_from(&src, "/knowledge")]);
assert_eq!(flags, ["--tmpfs=/knowledge/.git"]);
}
/// ...and nothing else does. A blanket "overlay .git on every bind"
/// No other mount is overlaid. A blanket "overlay .git on every bind"
/// would mask a real `.git` under `state/`, where an agent legitimately
/// keeps working clones of its own.
#[test]