Files
Brummel f68258b044 fieldtest: cycle-0010 — 3 examples, 6 findings
Public-interface-only field test of the walking-skeleton closing seam (#8):
the `aura run` CLI and the reusable aura-std::Recorder sink. A standalone
consumer crate (path-deps on the three engine crates, as a C16 project would)
drives the real binary as a subprocess and wires Recorder via the raw
Harness::bootstrap API.

Examples (all built from HEAD and ran green):
- c0010_1: drive `aura run` as a downstream CLI/jq consumer — single-line JSON
  report, byte-identical across two runs (C1), bad-args → exit 2 + exact usage on
  stderr with empty stdout.
- c0010_2: wire Recorder onto Sma(3) via raw bootstrap, drain the mpsc::Receiver
  — rows match the rustdoc warm-up model exactly.
- c0010_3: Recorder over [I64, Bool, Timestamp] — the four-kind claim (never
  exercised by the CLI's f64-only path) holds, verified from rustdoc alone.

Findings: 0 bugs, 4 working, 1 spec_gap, 1 friction.
- [spec_gap] `aura run <trailing-arg>` is accepted (exit 0), not rejected — the
  arg parse keys only on the first token being `run` and ignores the rest. The
  cycle_scope's "any other or missing subcommand -> exit 2" does not unambiguously
  cover `run <junk>`; the binary chose the lenient reading. Tracked for a
  ratify-or-tighten decision.
- [friction] verifying the documented {manifest, metrics} JSON nesting needs a
  hand-rolled string walker — the zero-dep consumer has only to_json() and no
  typed read-path. Low-priority tidy.

Both non-bug findings filed to the forward queue; neither blocks the cycle.
2026-06-04 20:55:28 +02:00

113 lines
5.4 KiB
Rust

//! Fieldtest c0010 #1 — `aura run` as a downstream CLI consumer (axis a).
//!
//! Question under test: invoking the REAL `aura` binary the way the deferred
//! C18 registry or a shell/jq pipeline would — capture stdout, treat it as the
//! single-line canonical JSON report (manifest + metrics) — does the documented
//! C14 machine-readable face hold? Specifically:
//! - `aura run` -> exit 0, one line of JSON on stdout, nothing on stderr;
//! - the JSON parses as an object with {manifest, metrics} nesting (rustdoc:
//! "prints the run's metrics + manifest as canonical JSON to stdout");
//! - two runs are byte-identical (C1 determinism, pinned on the surface);
//! - missing / unknown subcommand -> exit 2 + `aura: usage: aura run` on
//! stderr, NOTHING on stdout (cycle_scope bad-args contract).
//!
//! This consumer does NOT read crates/*/src. It only runs the binary and
//! inspects observable behaviour. The binary path comes from $AURA_BIN (a CI
//! consumer would point this at the built artefact); we default to the
//! sibling workspace target/debug/aura so `cargo run` works out of the box.
use std::process::Command;
fn aura_bin() -> String {
if let Ok(p) = std::env::var("AURA_BIN") {
return p;
}
// Default: the workspace debug artefact, two levels up from this crate.
let here = env!("CARGO_MANIFEST_DIR");
format!("{here}/../../target/debug/aura")
}
/// A bare-bones JSON value walker — enough for a consumer to confirm the
/// documented {manifest, metrics} shape WITHOUT a serde dependency (the
/// workspace is deliberately zero-external-dependency; a downstream consumer
/// would reach for serde_json or jq, but here we only assert structural facts
/// the rustdoc promises). Returns the substring value following `"key":`.
fn raw_value_after<'a>(json: &'a str, key: &str) -> Option<&'a str> {
let needle = format!("\"{key}\":");
let idx = json.find(&needle)? + needle.len();
Some(&json[idx..])
}
fn run_capture(args: &[&str]) -> (String, String, i32) {
let out = Command::new(aura_bin())
.args(args)
.output()
.unwrap_or_else(|e| panic!("failed to spawn {}: {e}", aura_bin()));
(
String::from_utf8_lossy(&out.stdout).to_string(),
String::from_utf8_lossy(&out.stderr).to_string(),
out.status.code().unwrap_or(-1),
)
}
fn main() {
println!("using binary: {}", aura_bin());
// --- `aura run`: exit 0, one JSON line on stdout, clean stderr ---
let (stdout, stderr, code) = run_capture(&["run"]);
println!("`aura run` exit={code}");
println!("`aura run` stdout = {stdout:?}");
assert_eq!(code, 0, "aura run must exit 0");
assert!(stderr.is_empty(), "aura run must not write stderr, got {stderr:?}");
let line = stdout.trim_end_matches('\n');
assert!(!line.contains('\n'), "report must be a single line of JSON");
assert!(line.starts_with('{') && line.ends_with('}'), "report is a JSON object");
// Documented nesting: a `manifest` object and a `metrics` object.
assert!(raw_value_after(line, "manifest").is_some(), "manifest key present");
assert!(raw_value_after(line, "metrics").is_some(), "metrics key present");
// Documented manifest fields (RunManifest rustdoc: commit/params/window/seed/broker).
for k in ["commit", "params", "window", "seed", "broker"] {
assert!(raw_value_after(line, k).is_some(), "manifest.{k} key present");
}
// Documented metrics fields (RunMetrics rustdoc).
for k in ["total_pips", "max_drawdown", "exposure_sign_flips"] {
assert!(raw_value_after(line, k).is_some(), "metrics.{k} key present");
}
// --- determinism: two runs byte-identical (C1) ---
let (stdout2, _, _) = run_capture(&["run"]);
assert_eq!(stdout, stdout2, "two runs must be byte-identical (C1)");
println!("determinism: two runs byte-identical OK");
// --- bad-args contract: missing subcommand ---
let (out, err, code) = run_capture(&[]);
println!("`aura` (no args) exit={code} stderr={err:?}");
assert_eq!(code, 2, "no subcommand must exit 2");
assert!(out.is_empty(), "no subcommand must print nothing on stdout, got {out:?}");
assert_eq!(err.trim_end(), "aura: usage: aura run", "exact usage line on stderr");
// --- bad-args contract: unknown subcommand ---
let (out, err, code) = run_capture(&["play"]);
println!("`aura play` exit={code} stderr={err:?}");
assert_eq!(code, 2, "unknown subcommand must exit 2");
assert!(out.is_empty(), "unknown subcommand must print nothing on stdout");
assert_eq!(err.trim_end(), "aura: usage: aura run", "exact usage line on stderr");
// --- probe: trailing junk after `run`. The cycle_scope says "Any other or
// missing subcommand prints usage and exits 2"; `run extra` is neither a
// clean `run` nor a different subcommand. What does the binary do? ---
let (out, err, code) = run_capture(&["run", "extra-garbage"]);
println!("`aura run extra-garbage` exit={code} stdout_len={} stderr={err:?}", out.len());
// We RECORD the behaviour; the reading we assert is the lenient one the
// binary actually exhibits, and flag the spec ambiguity in the report.
if code == 0 {
println!("NOTE: `aura run <extra>` was ACCEPTED (exit 0) — trailing args ignored.");
} else {
println!("NOTE: `aura run <extra>` was REJECTED (exit {code}).");
}
println!("c0010_1 OK: aura run -> JSON report; determinism + bad-args contract observed");
}