deb: shrink records to upper, journal and log; keep metadata for every build

Records carried a full chroot each, and space turned out to be the hard
constraint: five records per target multiplied by a multi-GB chroot does
not fit. The chroot is now per-run scratch — every build extracts a
fresh one from the sha-verified mmdebstrap tarball cache into its
record's chroot/ dir and removes it at the end (success, failure and
interrupt included); resume_chroot, the .pkh-ready probe and
chroot_is_usable are gone, and crashed-run leftovers are swept when the
next run of that identity starts.

What a record keeps instead: manifest.json (renamed, now with the
tarball sha it used, a kept_upper flag and a tree-only journal — the
environment phases rerun in every fresh chroot), a log/build.log full
tee of the build output (kept for EVERY build; plain runs become
metadata-only records, so keep-iff-incremental now applies to the
staged tree alone), and — for --incremental runs — tree-upper/ with its
host-files.list. The fork gate carries a base's staged tree only while
its recorded tarball sha still matches the cache and the changelog
version is unchanged; a fork candidate is any record with a staged tree.

The keep-last-5 cap now applies to staged trees: a sixth retention
degrades the oldest record to metadata-only instead of deleting it.
New `pkh deb log [-f] [<id>]` prints (or follows) a record's build log,
and `pkh deb list` shows the staged-tree state and plain-run records
too. README rewritten around the shrunk model.
This commit is contained in:
2026-09-29 14:00:34 +02:00
parent 81f0d52f85
commit dd2634abe3
7 changed files with 1287 additions and 946 deletions
+6 -5
View File
@@ -98,7 +98,8 @@ enum Artifact {
Tarball(PathBuf),
/// A build log file under `<cache>/logs`.
LogFile(PathBuf),
/// A build record root (chroot + journal), one per build.
/// A build record root (manifest + log, and the staged tree of
/// `--incremental` runs), one per build.
Session(PathBuf),
/// A `pkh test` run directory (report artifacts).
TestRun(PathBuf),
@@ -184,7 +185,7 @@ fn discover_sessions(sessions_dir: &Path, all: bool) -> Vec<Artifact> {
} else if is_stale_path(&path, all) {
// A record root only ever holds directories; a plain
// file inside a slug directory is a leftover of the old
// flat layout (its session.json / host-tree snapshot).
// flat layout (its manifest / host-tree snapshot).
artifacts.push(Artifact::Session(path));
}
}
@@ -240,7 +241,7 @@ fn is_prunable_session(path: &Path, all: bool) -> bool {
if all {
return true;
}
let last_used = fs::read_to_string(path.join("session.json"))
let last_used = fs::read_to_string(path.join("manifest.json"))
.ok()
.and_then(|content| serde_json::from_str::<serde_json::Value>(&content).ok())
.and_then(|value| {
@@ -1277,7 +1278,7 @@ none /tmp/other proc rw 0 0
fn write_session_manifest(session_dir: &Path, last_used: &str) {
fs::create_dir_all(session_dir).unwrap();
fs::write(
session_dir.join("session.json"),
session_dir.join("manifest.json"),
format!(r#"{{ "last_used": "{last_used}" }}"#),
)
.unwrap();
@@ -1299,7 +1300,7 @@ none /tmp/other proc rw 0 0
&(chrono::Utc::now() - chrono::Duration::hours(1)).to_rfc3339(),
);
fs::create_dir_all(&corrupt).unwrap();
fs::write(corrupt.join("session.json"), "not json").unwrap();
fs::write(corrupt.join("manifest.json"), "not json").unwrap();
let report = prune_in_roots(
temp.path(),