diff --git a/crates/aura-cli/src/research_docs.rs b/crates/aura-cli/src/research_docs.rs index 7c68e64..3504575 100644 --- a/crates/aura-cli/src/research_docs.rs +++ b/crates/aura-cli/src/research_docs.rs @@ -60,6 +60,17 @@ impl DocIntrospectCmd { } } +/// Parse `--parallel-instruments` in domain terms: clap's built-in +/// `NonZeroUsize` parser would leak the Rust type name ("number would be +/// zero for non-zero type") at exactly the moment a user needs guidance. +fn parse_parallel_instruments(s: &str) -> Result { + s.parse::().ok().and_then(std::num::NonZeroUsize::new).ok_or_else(|| { + "must be a whole number of at least 1 — it bounds how many distinct \ + instruments are resident in parallel" + .to_string() + }) +} + /// Exactly one introspect mode (the graph-introspect guard idiom: usage /// error on stderr, exit 2). fn guard_one_mode(cmd: &DocIntrospectCmd, family: &str) { @@ -320,7 +331,11 @@ pub enum CampaignSub { target: String, /// Bound on distinct instruments resident in parallel (the RAM /// lever; 1 reproduces the sequential loop's footprint). - #[arg(long, default_value_t = aura_campaign::DEFAULT_PARALLEL_INSTRUMENTS)] + #[arg( + long, + default_value_t = aura_campaign::DEFAULT_PARALLEL_INSTRUMENTS, + value_parser = parse_parallel_instruments, + )] parallel_instruments: std::num::NonZeroUsize, }, /// List stored campaign realizations, or dump one campaign's records diff --git a/crates/aura-cli/tests/research_docs.rs b/crates/aura-cli/tests/research_docs.rs index 75978e0..afb143a 100644 --- a/crates/aura-cli/tests/research_docs.rs +++ b/crates/aura-cli/tests/research_docs.rs @@ -4083,4 +4083,7 @@ fn campaign_over_a_gapped_archive_records_the_uncovered_cell_and_continues() { fn campaign_run_rejects_a_zero_parallel_instruments_bound() { let (out, code) = run_code(&["campaign", "run", "unused", "--parallel-instruments", "0"]); assert_eq!(code, Some(2), "clap usage error expected, got: {out}"); + // Domain prose, not clap's raw NonZeroUsize wording ("number would be + // zero for non-zero type") — the fieldtest friction finding. + assert!(out.contains("at least 1"), "domain guidance expected, got: {out}"); } diff --git a/docs/authoring-guide.md b/docs/authoring-guide.md index 74c09fb..4216c9e 100644 --- a/docs/authoring-guide.md +++ b/docs/authoring-guide.md @@ -609,4 +609,9 @@ Exit codes: a clean run exits 0; a usage error exits 2; a refusal before any cell runs (invalid document, missing project, unresolvable strategy) exits 1; a run that **completes with one or more failed cells** exits 3 — the run record and every healthy cell persist, and the failed cells are named on stderr -(#272). +(#272). A **gate-emptied cell** — a `std::gate` stage that filters out every +member — is a *successful* cell, not a failed one: the gate legitimately +answered "no survivors", so the run still exits 0, the cell's realization is +recorded as truncated at that stage, and an informational `aura:`-prefixed +note names the cell on stderr. Exit 3 is reserved for cells that could not be +evaluated (faults), never for an empty-but-valid result.