feat(0099): CLI exit-code split (iteration 2) — usage=2, runtime=1

Apply the clean exit-code partition (#175 deviation #8) on top of the clap
migration: runtime failures now exit 1, usage errors stay exit 2, with no
same-class inconsistency.

Partition (attribution principle): exit 2 = a command-line fault (a bad flag
value, an inapplicable combination, or the content of an argv-named file);
exit 1 = the command was well-formed but the environment / recorded state it
needs is missing, or piped stdin data is bad. The 6 boundary cases the spec's
"parse-time vs run-time" rule under-specified are resolved on #175
(malformed/open argv blueprint -> usage 2; stdin op-script content -> runtime 1;
applicability refusals -> usage 2; unknown --axis / --metric -> usage 2).

Flipped 2->1 (runtime): no local data, no recorded geometry, no recorded
run/family, "run has no tap named", trace-name collision, family/trace
persist-write failures (normalizing the lone generalize inconsistency -- all
persist -> 1 together), missing/corrupt content-addressed store state (the
reproduce path), chart-read failures, and graph stdin op-script content +
stdin-read I/O. reproduce-diverged stays exit 1. Usage sites (clap parse,
argv-applicability guards, the dual-grammar blueprint-file read/parse) stay
exit 2.

Tests: ~8 domain-refusal pins flipped Some(2)->Some(1) (message substrings
unchanged), the four runtime tests renamed _exit_2 -> _exit_1, and a partition
property test added. Orchestrator fix after the loop: 3 sibling no-data
skip-guards (run_real_with_trace + the two generalize cross-instrument tests)
that the plan's Task-1 list missed -- left at Some(2), they would fall through
to assert Some(0) and FAIL on a data-less machine; flipped to Some(1) for
consistency with the flipped code.

Deferred (cosmetic, filed forward): full "Usage:" / "usage:" / bare
error-message casing normalization -- no functional impact, high pin-churn.

Verified green (orchestrator, this session): cargo build --workspace, cargo
clippy --workspace --all-targets -- -D warnings, and cargo test --workspace all
clean (0 failed across every test binary).

This completes the #175 clap-adoption cycle (iteration 1 = clap migration at
366170a; iteration 2 = this exit-code split).

refs #175
This commit is contained in:
2026-07-01 20:05:42 +02:00
parent ffb2624dd7
commit fa42bf3878
4 changed files with 148 additions and 85 deletions
+6 -6
View File
@@ -129,7 +129,7 @@ pub fn build_cmd() {
let mut doc = String::new();
if let Err(e) = std::io::stdin().read_to_string(&mut doc) {
eprintln!("aura: reading stdin: {e}");
std::process::exit(2);
std::process::exit(1);
}
match build_from_str(&doc) {
// `print!`, not `println!`: the canonical artifact is exactly the library
@@ -139,7 +139,7 @@ pub fn build_cmd() {
Ok(json) => print!("{json}"),
Err(msg) => {
eprintln!("aura: {msg}");
std::process::exit(2);
std::process::exit(1);
}
}
}
@@ -209,13 +209,13 @@ pub fn introspect_cmd(cmd: crate::GraphIntrospectCmd) {
let mut doc = String::new();
if let Err(e) = std::io::stdin().read_to_string(&mut doc) {
eprintln!("aura: reading stdin: {e}");
std::process::exit(2);
std::process::exit(1);
}
match introspect_unwired(&doc) {
Ok(s) => print!("{s}"),
Err(m) => {
eprintln!("aura: {m}");
std::process::exit(2);
std::process::exit(1);
}
}
} else {
@@ -227,13 +227,13 @@ pub fn introspect_cmd(cmd: crate::GraphIntrospectCmd) {
let mut doc = String::new();
if let Err(e) = std::io::stdin().read_to_string(&mut doc) {
eprintln!("aura: reading stdin: {e}");
std::process::exit(2);
std::process::exit(1);
}
match build_from_str(&doc) {
Ok(json) => println!("{}", crate::content_id(&json)),
Err(m) => {
eprintln!("aura: {m}");
std::process::exit(2);
std::process::exit(1);
}
}
}