feat(aura-cli): self-identifying RunManifest.commit via build.rs git capture

RunManifest.commit read option_env!("AURA_COMMIT").unwrap_or("unknown"),
so a normal build's manifest identity field was a bare placeholder --
C8's audit trail (this run = this commit) had nothing to point back at
once runs are archived (C18 registry).

Add crates/aura-cli/build.rs: at compile time it shells out to the `git`
already in PATH -- `rev-parse HEAD`, plus a `-dirty` suffix when
`status --porcelain` is non-empty -- and emits
`cargo:rustc-env=AURA_COMMIT=<sha>`. The existing option_env! read is
untouched and now picks it up. Chose shelling to git over a libgit crate
to keep the zero-external-dependency commitment (C14/C18); the firewall
crate is aura-ingest, and a build-time process call adds no runtime dep.
No-git case (packaged source tree, no .git): build.rs returns early and
swallows git errors, leaving AURA_COMMIT unset so "unknown" still holds.

Staleness guard: build.rs emits rerun-if-changed on .git/HEAD and
.git/logs/HEAD (logs/HEAD grows on every HEAD move incl. branch switch),
so the captured sha is rebuilt when HEAD moves rather than going stale.
Determinism (C1) is intact: AURA_COMMIT is fixed at compile time, so two
runs of one build still produce byte-identical manifests.

Two tests that pinned the old "unknown" placeholder are relaxed to the
new structural contract (not scope creep -- they were the old contract):
the cli_run.rs JSON-shape prefix no longer pins the commit value, and
the main.rs unit test asserts commit is non-empty and stable across runs
instead of equal to "unknown". The headline contract (manifest.commit
contains the real HEAD sha) is pinned by run_manifest_commit_carries_
real_git_head (4552d0c). Verified: cargo test -p aura-cli green (5),
clippy --all-targets -D warnings clean.

closes #15
This commit is contained in:
2026-06-05 00:10:42 +02:00
parent 4552d0c9c2
commit 30e5abb6aa
3 changed files with 63 additions and 2 deletions
+53
View File
@@ -0,0 +1,53 @@
//! Capture the git HEAD sha at compile time and expose it as `AURA_COMMIT`, so
//! the binary's RunManifest is self-identifying (C8/C18 audit trail: this run =
//! this commit). When there is no git (e.g. a packaged source tree), `AURA_COMMIT`
//! is left unset and `main.rs`'s `option_env!(...).unwrap_or("unknown")` holds.
//!
//! Zero runtime/build dependency by commitment (C14/C18): we shell out to the
//! `git` already in PATH rather than pulling in a libgit crate.
use std::path::Path;
use std::process::Command;
fn main() {
// build.rs runs with CWD = the crate dir (crates/aura-cli); the repo root,
// which owns `.git`, is two levels up.
let repo_root = Path::new("../..");
let git_dir = repo_root.join(".git");
if !git_dir.exists() {
// No git: emit nothing; the `"unknown"` fallback in main.rs holds.
return;
}
// Re-run when HEAD moves or its working tree changes so AURA_COMMIT never
// goes stale across commits. `.git/logs/HEAD` grows on every HEAD move
// (commit, checkout, reset), covering branch switches without resolving the
// current branch's ref file by hand.
println!("cargo:rerun-if-changed=../../.git/HEAD");
println!("cargo:rerun-if-changed=../../.git/logs/HEAD");
let head = run_git(repo_root, &["rev-parse", "HEAD"]);
let Some(head) = head else { return };
// Append `-dirty` when the working tree has uncommitted changes, so a run
// built off an unclean tree is not silently attributed to the clean HEAD.
let dirty = run_git(repo_root, &["status", "--porcelain"])
.map(|s| !s.is_empty())
.unwrap_or(false);
let commit = if dirty { format!("{head}-dirty") } else { head };
println!("cargo:rustc-env=AURA_COMMIT={commit}");
}
/// Run `git <args>` in `dir`, returning trimmed stdout on success, `None` on any
/// failure (git missing, non-zero exit, non-utf8). Never panics: a build must
/// not fail because git is unavailable.
fn run_git(dir: &Path, args: &[&str]) -> Option<String> {
let out = Command::new("git").current_dir(dir).args(args).output().ok()?;
if !out.status.success() {
return None;
}
let s = String::from_utf8(out.stdout).ok()?;
Some(s.trim().to_string())
}
+6 -1
View File
@@ -156,6 +156,11 @@ mod tests {
// manifest carries the sample's known configuration.
let (from, to) = r1.manifest.window;
assert_eq!((from.0, to.0), (1, 7));
assert_eq!(r1.manifest.commit, "unknown");
// commit is the build's git identity (or the no-git "unknown" fallback);
// either way it is non-empty and fixed at compile time, so it is stable
// across runs of the same build (C1 determinism, already asserted above
// via `to_json()`).
assert!(!r1.manifest.commit.is_empty());
assert_eq!(r1.manifest.commit, r2.manifest.commit);
}
}
+4 -1
View File
@@ -18,7 +18,10 @@ fn run_prints_json_and_exits_zero() {
let line = stdout.trim_end();
// canonical cycle-0009 JSON shape: nested manifest + metrics, stable keys.
assert!(line.starts_with("{\"manifest\":{\"commit\":\"unknown\","), "got: {line}");
// The manifest leads with a `commit` field; its value is the build's git
// identity (asserted by `run_manifest_commit_carries_real_git_head`), so
// this shape check pins only the surrounding structure, not the value.
assert!(line.starts_with("{\"manifest\":{\"commit\":\""), "got: {line}");
assert!(line.contains("\"broker\":\"sim-optimal(pip_size=0.0001)\""), "got: {line}");
assert!(line.contains("\"window\":[1,7]"), "got: {line}");
assert!(line.contains("\"metrics\":{\"total_pips\":"), "got: {line}");