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:
@@ -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())
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}");
|
||||
|
||||
Reference in New Issue
Block a user