fix(aura-runner, aura-cli): milestone-fieldtest bugs — library exposes-neither guard, override class, doc truth

RED-first fixes for the two code bugs the safe-to-embed milestone
fieldtest caught: (1) run_signal_r panicked (exit 101) on a blueprint
exposing neither bias nor a declared tap — the CLI pre-validated but the
library seam, the very surface the milestone promises is kill-free, did
not; the guard now refuses class 2 with the CLI's prose family before
wrap_r (run_measurement has no wrap seam and no hole). (2) The override
unknown-param refusal exited 1 through the one inline site the #297
adjudication missed; it and its wrapped-retired sibling are class 2 now,
three existing pins re-pinned with the adjudication comment.

Doc truth from the same fieldtest: the guide's campaign no-data class
corrected (contained cell faults exit 3, not 1), TapPlanError's rustdoc
no longer advertises the retired exit-1 register, and C30 records that
rev + the embedding's committed lockfile are the reproducible-build
contract (branch-referenced engine deps resolve at lock time).

refs #296
This commit is contained in:
2026-07-26 20:06:07 +02:00
parent 32f6be8552
commit 99e93000c5
6 changed files with 99 additions and 14 deletions
+4 -2
View File
@@ -1277,13 +1277,15 @@ fn exec_blueprint_leg(path: &str, overrides: &[String], tap: &[String], env: &au
raw node.param namespace — did you mean \"{raw}\"? see \
`aura graph introspect --params <bp>`"
);
std::process::exit(1);
// C14 class 2: fault in argv-named content (#297 adjudication, fieldtest follow-up)
std::process::exit(2);
}
}
let axes: Vec<(String, Vec<Scalar>)> = ov.iter().map(|(p, v)| (p.clone(), vec![*v])).collect();
let paths = override_paths(&axes, &raw_space, &signal).unwrap_or_else(|e| {
eprintln!("aura: {e}");
std::process::exit(1);
// C14 class 2: fault in argv-named content (#297 adjudication, fieldtest follow-up)
std::process::exit(2);
});
// Refuse an open (free-knob) blueprint at the dispatch boundary — this
// leg's own closed-blueprint guard now (#176; inherited from the
+37 -4
View File
@@ -690,10 +690,11 @@ fn exec_override_non_literal_value_names_the_value_not_a_grammar_fault() {
/// with `override_paths`' own prose (`member.rs:768-771`), pointing at the
/// live axis-discovery verb (`aura graph introspect --params`) — the retired
/// `aura sweep --list-axes` no longer exists.
// C14 class 2: fault in argv-named content (#297 adjudication, fieldtest follow-up)
#[test]
fn exec_override_unknown_path_refuses_with_the_override_prose() {
let (out, code) = run_code(&["exec", "examples/r_sma.json", "--override", "nope.knob=1"]);
assert_eq!(code, Some(1), "stdout/stderr: {out}");
assert_eq!(code, Some(2), "stdout/stderr: {out}");
assert!(
out.contains("names no param of this blueprint (open or bound)"),
"stdout/stderr: {out}"
@@ -704,6 +705,34 @@ fn exec_override_unknown_path_refuses_with_the_override_prose() {
);
}
/// C14's FORM-vs-VALUE boundary, the unknown-param corner (fieldtest
/// milestone-safe-to-embed, finding B2): "a form fault the surface detects
/// before the node ever runs (kind mismatch, unknown param, compile error)
/// stays class 2 argv-content" (`c14-headless-two-faces.md`, and the guide
/// verbatim). An `--override` path naming no param of the blueprint (open or
/// bound) is exactly that form fault, so it must exit 2 like its
/// malformed-token, non-literal-value and kind-mismatch siblings — never 1,
/// which a CI wrapper branching on the class reads as "the environment is
/// missing something". The refusal prose itself is pinned as the bytes that
/// exist today (`override_paths`, member.rs).
#[test]
fn exec_override_unknown_param_refuses_as_argv_content_class_2() {
let (out, code) = run_code(&["exec", "examples/r_sma.json", "--override", "nope.length=3"]);
assert_eq!(
code,
Some(2),
"C14: unknown param is a form fault → argv-content class 2; stdout/stderr: {out}"
);
assert!(
out.contains("names no param of this blueprint (open or bound)"),
"the existing refusal prose must survive the re-class: {out}"
);
assert!(
out.contains("aura graph introspect --params"),
"the discovery pointer must survive the re-class: {out}"
);
}
/// A repeated `--override` path (the same NODE.PARAM named twice) refuses as
/// a usage error naming the duplicated path — it must NOT fall through to
/// `reopen_all`'s `NoSuchBoundParam` panic (the second reopen of an
@@ -813,12 +842,13 @@ fn exec_override_kind_mismatch_refuses_with_prose_not_the_debug_struct() {
/// missing it. Refuse before the zip, mirroring the campaign leg's own
/// did-you-mean refusal for the same retired form
/// (`RefFault::AxisNotInParamSpace`).
// C14 class 2: fault in argv-named content (#297 adjudication, fieldtest follow-up)
#[test]
fn exec_override_wrapped_form_path_refuses_not_panics() {
let (out, code) =
run_code(&["exec", "examples/r_sma.json", "--override", "sma_signal.fast.length=1"]);
assert_ne!(code, Some(101), "must not panic: {out}");
assert_eq!(code, Some(1), "stdout/stderr: {out}");
assert_eq!(code, Some(2), "stdout/stderr: {out}");
assert!(!out.contains("panicked at"), "no raw panic report reaches the consumer: {out}");
assert!(
out.contains("did you mean") && out.contains("fast.length"),
@@ -1688,11 +1718,14 @@ fn exec_well_formed_but_unregistered_content_id_refuses_at_runtime() {
fn exec_exit_codes_partition_usage_two_from_runtime_one() {
let (usage_out, usage_code) = run_code(&["exec", "examples/r_sma.json", "--bogus"]);
assert_eq!(usage_code, Some(2), "unknown flag is a usage error -> 2: {usage_out}");
// C14 class 2: fault in argv-named content (#297 adjudication, fieldtest
// follow-up) — an unresolvable `--override` path is a form fault the
// surface detects before the node ever runs, not a runtime failure.
let (runtime_out, runtime_code) =
run_code(&["exec", "examples/r_sma.json", "--override", "nope.knob=1"]);
assert_eq!(
runtime_code, Some(1),
"a well-formed command with an unresolvable override is a runtime failure -> 1: {runtime_out}"
runtime_code, Some(2),
"a well-formed command with an unresolvable override is argv-content class 2: {runtime_out}"
);
}
+43
View File
@@ -554,6 +554,23 @@ pub fn run_signal_r(
signal: Composite, params: &[Scalar], data: RunData, seed: u64, env: &Env,
plan: TapPlan, topo: Option<&str>,
) -> Result<(RunReport, Vec<String>), RunnerError> {
// #297 fieldtest finding B1 (milestone-safe-to-embed): mirror the CLI's
// own `exec_blueprint_leg` exposes-neither guard (`aura-cli/src/main.rs`,
// `has_bias`/`has_tap`) IN the library, before `wrap_r` below wires a
// "bias" output that does not exist — an embedding host driving
// `run_signal_r` directly (never through the CLI shell) must see this
// same typed refusal instead of `wrap_r`'s wiring `.expect` panicking
// with `UnknownOutPort { name: "bias" }` and killing the host. Same
// detection (`output()`/`taps()`), same prose family as the CLI's,
// minus its own "aura: " print prefix — C14 class 2 (argv-named content).
if !signal.output().iter().any(|o| o.name == "bias") && signal.taps().is_empty() {
return Err(RunnerError {
exit_code: 2,
message: "exec needs either a `bias` output (a strategy) or ≥1 declared tap \
(a measurement); this blueprint exposes neither"
.to_string(),
});
}
// topology_hash's own two-line body, inlined: `content_id_of` over the
// canonical (#164) blueprint JSON — the CLI shell's `topology_hash`
// helper is the same primitive, kept single-sourced at `aura_research`.
@@ -1974,4 +1991,30 @@ mod tests {
assert_eq!(err.exit_code, 2);
assert!(err.message.contains("unknown fold"), "{}", err.message);
}
/// The #297 promise at the exposes-neither seam (fieldtest
/// milestone-safe-to-embed, finding B1): a blueprint exposing NEITHER a
/// `bias` output NOR a declared tap is not runnable on this leg, and the
/// library must say so as a returned `RunnerError` — C14 class 2
/// (argv-named content, the CLI guard's own prose family) — never via
/// `wrap_r`'s wiring `.expect` (`UnknownOutPort { name: "bias" }`,
/// member.rs:443) panicking and killing the embedding host. The CLI's
/// shell-side pre-validation (`exec_blueprint_leg`) must not be the only
/// guard on the seam the milestone promises is kill-free.
#[test]
fn run_signal_r_refuses_an_exposes_neither_blueprint_in_process() {
let (env, _root) = temp_project_env("exposes-neither");
// Minimal exposes-neither signal: one role, one node, no exposed
// output, no declared tap.
let mut g = GraphBuilder::new("blind_signal");
let s = g.add(Sub::builder());
let price = g.source_role("price", ScalarKind::F64);
g.feed(price, [s.input("lhs"), s.input("rhs")]);
let blind = g.build().expect("an exposes-neither composite still builds");
let err = run_signal_r(blind, &[], RunData::Synthetic, 0, &env, TapPlan::empty(), None)
.unwrap_err();
assert_eq!(err.exit_code, 2, "argv-named content (C14): {}", err.message);
assert!(err.message.contains("bias"), "{}", err.message);
assert!(err.message.contains("exposes neither"), "{}", err.message);
}
}
+3 -2
View File
@@ -217,8 +217,9 @@ impl FoldRegistry {
}
/// A typed tap-plan fault — the pre-bootstrap refusals plus the terminal
/// store fault. Entry points map every variant to the established
/// `aura: ` + exit-1 refusal register via `Display`.
/// store fault. Entry points wrap every variant into a returned
/// `RunnerError` (#297) — prose via `Display`, class via `exit_class`
/// (content faults 2, store I/O 1, the C14 partition).
pub enum TapPlanError {
/// The plan names a tap the blueprint does not declare.
UnknownTap { name: String, declared: Vec<String> },
+5 -3
View File
@@ -855,9 +855,11 @@ mismatch, a compile error, a tap-bind content fault (unknown fold/tap,
duplicate) — are the content of the argv-named file and exit 2 like a usage
error; only its environment refusals (a tap-trace store write failure)
exit 1. The no-data family (no local data, no data in the requested window,
no recorded geometry) belongs to the real-data paths `reproduce` and the
campaign legs — where it likewise exits 1 (#297; the full partition lives
in [C14](design/contracts/c14-headless-two-faces.md)). `aura
no recorded geometry) belongs to the real-data paths: on `reproduce` it
exits 1, while inside a campaign the same fault is contained per cell
(#272) — the run completes and exits 3, naming the failed cells (#297;
the full partition lives in
[C14](design/contracts/c14-headless-two-faces.md)). `aura
reproduce`'s identity/pip guard (a stored member's recorded instrument or
broker/pip label contradicting the source it re-derives against) is
likewise class 1 on the plain verb — its source is derived from the
@@ -25,9 +25,13 @@ hand-maintained version.
refused at load (point 3), never trusted.
- An **external embedding** (a World program in its own repo)
consumes the workspace crates as a cargo git dependency pinned to a
rev. The pin is the whole contract: updating it is a deliberate act
that accepts whatever moved, with the design ledger and commit
bodies as the changelog.
rev. The pin names the engine; the embedding's own committed
`Cargo.lock` freezes the rest of the graph — dependencies the
engine itself takes as git-branch references (e.g. the data-server)
resolve at lock time, so rev + lockfile together are the
reproducible-build contract (fieldtest finding, 2026-07-26).
Updating either is a deliberate act that accepts whatever moved,
with the design ledger and commit bodies as the changelog.
3. **The load handshake refuses non-identical builds.** The cdylib
handshake already refuses a rustc mismatch (`RUSTC_VERSION`, emitted