From 99e93000c58e4a9323c01a94806af253a9de06e1 Mon Sep 17 00:00:00 2001 From: claude Date: Sun, 26 Jul 2026 20:06:07 +0200 Subject: [PATCH] =?UTF-8?q?fix(aura-runner,=20aura-cli):=20milestone-field?= =?UTF-8?q?test=20bugs=20=E2=80=94=20library=20exposes-neither=20guard,=20?= =?UTF-8?q?override=20class,=20doc=20truth?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- crates/aura-cli/src/main.rs | 6 ++- crates/aura-cli/tests/exec.rs | 41 ++++++++++++++++-- crates/aura-runner/src/member.rs | 43 +++++++++++++++++++ crates/aura-runner/src/tap_plan.rs | 5 ++- docs/authoring-guide.md | 8 ++-- .../contracts/c30-stability-discipline.md | 10 +++-- 6 files changed, 99 insertions(+), 14 deletions(-) diff --git a/crates/aura-cli/src/main.rs b/crates/aura-cli/src/main.rs index 4b35c40..774b970 100644 --- a/crates/aura-cli/src/main.rs +++ b/crates/aura-cli/src/main.rs @@ -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 `" ); - 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)> = 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 diff --git a/crates/aura-cli/tests/exec.rs b/crates/aura-cli/tests/exec.rs index 2c6cda0..f053e0c 100644 --- a/crates/aura-cli/tests/exec.rs +++ b/crates/aura-cli/tests/exec.rs @@ -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}" ); } diff --git a/crates/aura-runner/src/member.rs b/crates/aura-runner/src/member.rs index f3bd674..54c3c6f 100644 --- a/crates/aura-runner/src/member.rs +++ b/crates/aura-runner/src/member.rs @@ -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), 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); + } } diff --git a/crates/aura-runner/src/tap_plan.rs b/crates/aura-runner/src/tap_plan.rs index ec21b3e..77ccbb9 100644 --- a/crates/aura-runner/src/tap_plan.rs +++ b/crates/aura-runner/src/tap_plan.rs @@ -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 }, diff --git a/docs/authoring-guide.md b/docs/authoring-guide.md index 893195d..ea316e0 100644 --- a/docs/authoring-guide.md +++ b/docs/authoring-guide.md @@ -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 diff --git a/docs/design/contracts/c30-stability-discipline.md b/docs/design/contracts/c30-stability-discipline.md index 158da32..096c5ef 100644 --- a/docs/design/contracts/c30-stability-discipline.md +++ b/docs/design/contracts/c30-stability-discipline.md @@ -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