feat(aura-cli)!: retire the research-verb sugar — run/sweep/walkforward/mc/generalize removed

Slice 6 of the #319 retirement, the destructive half. The five flag verbs,
their five Args structs and dispatch arms, the whole argv->document
translator (verb_sugar.rs, 1257 lines) and 33 quintet-only helpers are
gone; retired verbs now refuse at the clap layer as unknown commands.
main.rs shrinks 4445 -> 2764 lines. The gated-intake route list narrows to
the canonical layer + exec's blueprint leg; exec's own refusal prose no
longer names dead verbs; test seeding recipes ride graph register.

cli_run.rs walked the full disposition table: 94 tests retired with their
dead flag surface (each with a named green twin where the property
survives), 75 ported — exec ports keep assertions byte-identical, campaign
ports inherit their golden grades unchanged (walkforward's 90/30/30 real
roller and the archive window clipping the sugar applied silently are now
explicit document fields — the equivalence held exactly, one reconstructed
walkforward pooled expectancy_r within 1e-12, cause documented inline).
Surface deltas recorded by the ports: per-cell refusals are #272 contained
faults (exit 3 + warning) rather than hard exit 1; the synthetic per-seed
mc family and generalize's merged cross-instrument family record were
sugar-only constructs and retire with it (the campaign record's
generalizations[] carries the data).

refs #319
This commit is contained in:
2026-07-25 21:36:58 +02:00
parent 24782caaec
commit db8f947441
10 changed files with 2703 additions and 8117 deletions
+7 -14
View File
@@ -108,17 +108,13 @@ struct CampaignRunLine<'a> {
campaign_run: &'a CampaignRunRecord,
}
/// Stdout shape of a campaign run. `Full` is `aura campaign run` (emit-gated
/// member/selection lines + the always-on final `campaign_run` record line).
/// `MemberLinesOnly` is dissolved-verb sugar: the generated document's
/// `emit: ["family_table"]` already limits emission to member lines; the mode
/// additionally suppresses the final record line so the verb's stdout
/// contract is reproduced byte-for-byte. The record append is identical in
/// both modes — presentation changes, the record does not.
/// Stdout shape of a campaign run: emit-gated member/selection lines + the
/// always-on final `campaign_run` record line. `MemberLinesOnly` (the
/// dissolved-verb sugar path's suppressed-record-line mode) retired with the
/// quintet (#319 Task 8) — `Full` is the only surviving shape.
#[derive(Clone, Copy, PartialEq)]
pub(crate) enum RunPresentation {
Full,
MemberLinesOnly,
}
/// `aura campaign run <target>`: resolve, gate, execute, emit. Every `Err`
@@ -184,12 +180,9 @@ pub(crate) struct CampaignRun {
/// The one campaign executor path from a resolved content id onward: fetch
/// the stored canonical bytes by id (so file addressing and id addressing
/// produce the same realization by construction) and re-run the intrinsic
/// tier on them, execute, and emit per `presentation`. Shared by
/// `run_campaign` (`RunPresentation::Full`) and the dissolved-verb sugar path
/// (`verb_sugar::run_sweep_sugar`, `RunPresentation::MemberLinesOnly`) — no
/// project gate here: the sugar path must work project-less exactly as the
/// inline verb it replaces did (store mechanics run against `env.registry()`
/// in both cases).
/// tier on them, execute, and emit per `presentation`. Called by
/// `run_campaign` (`RunPresentation::Full`, the sole surviving shape since
/// the quintet's dissolved-verb sugar path retired, #319 Task 8).
pub(crate) fn run_campaign_by_id(
campaign_id: &str,
env: &Env,
+5 -5
View File
@@ -32,11 +32,11 @@ fn zero_trade_note_text(n_windows: usize) -> String {
}
}
/// The #313 zero-trade note, shared by both walk-forward paths (the
/// synthetic-blueprint path in `main.rs` and the `--real` sugar path in
/// `verb_sugar.rs`) so the wording AND the condition live in exactly one
/// place. Takes the per-window trade counts; a no-op unless there is at
/// least one window and every one of them traded zero times.
/// The #313 zero-trade note (#319 Task 5: migrated onto the campaign
/// walk-forward leg in `campaign_run.rs`, the surviving executor path) so
/// the wording AND the condition live in exactly one place. Takes the
/// per-window trade counts; a no-op unless there is at least one window and
/// every one of them traded zero times.
pub(crate) fn note_zero_trade_windows(mut window_trades: impl ExactSizeIterator<Item = u64>) {
let n_windows = window_trades.len();
if n_windows > 0 && window_trades.all(|n| n == 0) {
+14 -61
View File
@@ -805,41 +805,6 @@ pub(crate) fn unresolved_namespace_hint(e: &LoadError, env: &aura_runner::projec
}
}
/// Validate a blueprint-slot document — the `sweep`/`walkforward`/`mc`
/// loaded-blueprint branches' dispatch-boundary check (#210 c0110 fieldtest
/// finding: the sweep slot used to print the raw `{e:?}` Debug leak). Single-
/// sourced here so the three dual-grammar subcommands cannot diverge (#184's
/// convention, extended). Two refusals, checked in order:
///
/// 1. The document parses as JSON but is an op-script ARRAY (#157), not a
/// built blueprint envelope — refused with a targeted hint pointing at
/// `aura graph build`, since `blueprint_from_json` would otherwise surface
/// a confusing internal serde shape mismatch.
/// 2. `blueprint_from_json` fails to load the envelope — phrased house-style
/// via [`blueprint_load_prose`] (+ [`unresolved_namespace_hint`] where it
/// applies), never the raw `LoadError` Debug form.
///
/// `Ok(())` means the document loaded cleanly; callers that only need the
/// validation (not the `Composite`) re-parse `doc` themselves afterward
/// (`blueprint_from_json` is cheap and infallible at that point).
pub(crate) fn blueprint_slot_prose(doc: &str, env: &aura_runner::project::Env) -> Result<(), String> {
if matches!(serde_json::from_str::<serde_json::Value>(doc), Ok(serde_json::Value::Array(_))) {
return Err(
"this is an op-script (an op array), not a built blueprint — run \
'aura graph build' first"
.to_string(),
);
}
blueprint_from_json(doc, &|t| env.resolve(t)).map(|_| ()).map_err(|e| {
let mut msg = blueprint_load_prose(&e);
if let Some(hint) = unresolved_namespace_hint(&e, env) {
msg.push_str("");
msg.push_str(&hint);
}
msg
})
}
/// #196: build a `Composite` from a document that is EITHER a #155 blueprint
/// envelope (a JSON object: `format_version` + `blueprint`) OR a construction
/// op-list (a JSON array) — shape-discriminated on the top-level JSON type,
@@ -893,34 +858,22 @@ pub(crate) fn composite_from_any(text: &str, env: &aura_runner::project::Env) ->
/// function — C29's "registered artifacts are never retroactively
/// invalidated" stays intact.
///
/// **Deliberate exceptions — direct `gate_authored_root_name` callers
/// (main.rs).** A handful of fresh-FILE intakes reach the same unsanitized
/// `traces/<name>/` seam (or `put_blueprint` the loaded envelope straight
/// into the registry) without going through this wrapper, because each
/// **Deliberate exception — the one direct `gate_authored_root_name` caller
/// (main.rs).** One fresh-FILE intake reaches the same unsanitized
/// `traces/<name>/` seam without going through this wrapper, because it
/// already parses the document its own way and only needs the shared root-
/// name gate bolted on, not the shape-discrimination `composite_from_any`
/// does:
/// - `aura run <blueprint.json>` (`dispatch_run`): envelope-only grammar (no
/// op-script fallback), feeds `signal.name()` into
/// - `exec <blueprint.json>` (`exec_blueprint_leg`, #319): envelope-only
/// grammar (no op-script fallback), feeds `signal.name()` into
/// `run_signal_r`/`run_measurement` -> `bind_tap_plan` ->
/// `TraceStore::begin_run`.
/// - `validate_and_register_axes` (shared by `generalize`,
/// `sweep --real`, `walkforward --real`, `mc --real`): `put_blueprint`s
/// the loaded envelope by topology hash before any of those four verbs
/// touches an archive.
/// - `run_blueprint_sweep` / `run_blueprint_walkforward` / `run_blueprint_mc`
/// (the synthetic, no-`--real` family builders): same `put_blueprint`
/// reason, on the routes that bypass `validate_and_register_axes`
/// entirely (#331 cycle-close — these used to plant an ungated envelope
/// in the store; see each fn's own comment).
/// - `list_blueprint_axes` (`aura sweep <FILE> --list-axes`, main.rs): no
/// registry write and no trace directory here, but a shape-violating root
/// name otherwise mangles through `wrapped_to_raw_axis` into a printed,
/// non-bindable axis name instead of refusing (#331 fieldtest finding
/// c331_2e) — the class rule is every CLI intake reading an authored
/// envelope from a file gates the root name, not only the writing ones.
///
/// All of these share the identical `name_gate` + `name_gate_fault_prose`
/// (The quintet's other direct callers — `dispatch_run`,
/// `validate_and_register_axes`, `run_blueprint_sweep`/`_walkforward`/`_mc`,
/// `list_blueprint_axes` — retired with the quintet, #319 Task 8.)
///
/// This shares the identical `name_gate` + `name_gate_fault_prose`
/// primitives this wrapper uses, so the refusal wording is byte-identical
/// across every authored-file-intake route even though the shape-
/// discrimination step is not shared by them.
@@ -934,10 +887,10 @@ pub(crate) fn composite_from_authored_text(
}
/// The root-name shape gate itself (#331 delta re-review), factored out of
/// [`composite_from_authored_text`] so `dispatch_run`'s narrower-grammar file
/// intake (see that fn's doc comment) can share the exact `name_gate` call and
/// `name_gate_fault_prose` wording without going through the shape-discriminating
/// wrapper.
/// [`composite_from_authored_text`] so `exec_blueprint_leg`'s narrower-grammar
/// file intake (see that fn's doc comment) can share the exact `name_gate`
/// call and `name_gate_fault_prose` wording without going through the
/// shape-discriminating wrapper.
pub(crate) fn gate_authored_root_name(name: &str) -> Result<(), String> {
name_gate(name).map_err(|fault| name_gate_fault_prose(name, &fault))
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+17 -27
View File
@@ -40,36 +40,26 @@ const SWEEP_ONLY_PROCESS_DOC: &str = r#"{
"pipeline": [ { "block": "std::sweep", "metric": "sqn_normalized", "select": "argmax" } ]
}"#;
/// Seed one blueprint (its params bound, reopened by the sweep axes per
/// #246) into the built demo project's store via a real sweep and return
/// its content id — copied verbatim from `research_docs.rs`'s recipe of the
/// same name.
/// Seed one blueprint into the built demo project's store via `aura graph
/// register` and return its content id — copied verbatim from
/// `research_docs.rs`'s recipe of the same name (#319 — the retired `sweep`
/// side-effect seeding this used to ride played no role in the returned
/// content id, a pure function of the loaded blueprint's canonical bytes;
/// `name` labels the registered id, mirroring the old per-call sweep family
/// name).
fn seed_blueprint(dir: &Path, name: &str) -> String {
let closed_bp = format!("{}/examples/r_sma.json", env!("CARGO_MANIFEST_DIR"));
let (out, code) = run_code_in(
dir,
&[
"sweep",
&closed_bp,
"--axis",
"fast.length=2,4",
"--axis",
"slow.length=8,16",
"--name",
name,
],
);
assert_eq!(code, Some(0), "seed sweep failed: {out}");
std::fs::read_dir(dir.join("runs").join("blueprints"))
.expect("blueprints dir")
let (out, code) = run_code_in(dir, &["graph", "register", &closed_bp, "--name", name]);
assert_eq!(code, Some(0), "seed register failed: {out}");
out.lines()
.find(|l| l.starts_with("registered blueprint "))
.expect("register line")
.trim_start_matches("registered blueprint ")
.split(' ')
.next()
.expect("one stored blueprint")
.expect("dir entry")
.path()
.file_stem()
.expect("stem")
.to_string_lossy()
.into_owned()
.expect("id")
.trim_start_matches("content:")
.to_string()
}
/// Register `doc` as a process document in the project store; returns its
File diff suppressed because it is too large Load Diff
+409 -52
View File
@@ -61,36 +61,26 @@ const CAMPAIGN_DOC: &str = r#"{
"presentation": { "persist_taps": [], "emit": ["family_table"] }
}"#;
/// Seed one blueprint (its params bound, reopened by the sweep axes per
/// #246) into the built demo project's store via a real sweep and return
/// its content id — copied verbatim from `research_docs.rs`'s recipe of the
/// same name.
/// Seed one blueprint into the built demo project's store via `aura graph
/// register` and return its content id — copied verbatim from
/// `research_docs.rs`'s recipe of the same name (#319 — the retired `sweep`
/// side-effect seeding this used to ride played no role in the returned
/// content id, a pure function of the loaded blueprint's canonical bytes;
/// `name` labels the registered id, mirroring the old per-call sweep family
/// name).
fn seed_blueprint(dir: &Path, name: &str) -> String {
let closed_bp = format!("{}/examples/r_sma.json", env!("CARGO_MANIFEST_DIR"));
let (out, code) = run_code_in(
dir,
&[
"sweep",
&closed_bp,
"--axis",
"fast.length=2,4",
"--axis",
"slow.length=8,16",
"--name",
name,
],
);
assert_eq!(code, Some(0), "seed sweep failed: {out}");
std::fs::read_dir(dir.join("runs").join("blueprints"))
.expect("blueprints dir")
let (out, code) = run_code_in(dir, &["graph", "register", &closed_bp, "--name", name]);
assert_eq!(code, Some(0), "seed register failed: {out}");
out.lines()
.find(|l| l.starts_with("registered blueprint "))
.expect("register line")
.trim_start_matches("registered blueprint ")
.split(' ')
.next()
.expect("one stored blueprint")
.expect("dir entry")
.path()
.file_stem()
.expect("stem")
.to_string_lossy()
.into_owned()
.expect("id")
.trim_start_matches("content:")
.to_string()
}
/// Register `doc` as a process document in the project store; returns its
@@ -285,17 +275,6 @@ fn measurement_blueprint_json() -> String {
serde_json::to_string(&v).expect("re-serialize measurement blueprint")
}
/// Blank the one volatile manifest field (`manifest.commit`, the compile-time
/// build sha) before a byte comparison — the same blanking `aura-bench`'s
/// `run_line_fingerprint` applies before hashing (`fixed_cost.rs:21-31`).
fn strip_volatile(line: &str) -> String {
let mut v: serde_json::Value = serde_json::from_str(line).expect("record line parses");
if let Some(commit) = v.get_mut("manifest").and_then(|m| m.get_mut("commit")) {
*commit = serde_json::Value::String(String::new());
}
serde_json::to_string(&v).expect("re-serializing a parsed Value cannot fail")
}
/// Single run of a fully-bound blueprint on the synthetic stream: the record
/// line is byte-shape-identical to `aura run`'s (manifest-first, one line) —
/// mirrors `cli_run.rs::aura_run_loads_and_runs_a_blueprint_file`.
@@ -310,20 +289,6 @@ fn exec_blueprint_file_emits_the_single_run_record_line() {
assert!(v["manifest"].get("commit").is_some());
}
/// Parity: `exec` and `run` emit the identical record line for the same
/// file (both verbs alive until Task 8; the pin survives `run`'s removal by
/// keeping only the exec half and its literal expectations).
#[test]
fn exec_blueprint_record_line_matches_runs_line_bytes() {
let (run_out, run_code_) = run_code(&["run", "examples/r_sma.json"]);
let (exec_out, exec_code) = run_code(&["exec", "examples/r_sma.json"]);
assert_eq!(run_code_, Some(0), "stdout/stderr: {run_out}");
assert_eq!(exec_code, Some(0), "stdout/stderr: {exec_out}");
let run_line = run_out.lines().find(|l| l.starts_with('{')).expect("run record line");
let exec_line = exec_out.lines().find(|l| l.starts_with('{')).expect("exec record line");
assert_eq!(strip_volatile(run_line), strip_volatile(exec_line));
}
/// The migrated root-name gate (retirement inventory): exec's blueprint
/// intake refuses a bad root name with `run`'s exact prose, before any
/// trace write — mirrors
@@ -715,3 +680,395 @@ fn exec_campaign_walkforward_with_trades_emits_no_zero_trade_note() {
assert_eq!(code, Some(0), "stdout/stderr: {out}");
assert!(!out.contains("recorded zero trades"), "a traded run emits no zero-trade note: {out}");
}
// ---------------------------------------------------------------------------
// Task 8: `cli_run.rs` disposition — ports onto `exec` (retire+port half only;
// the port-to-campaign-document half is a separate follow-up dispatch).
// ---------------------------------------------------------------------------
/// Property (#249, ported from `cli_run.rs::run_manifest_stamps_untouched_bound_defaults`):
/// a plain `exec` of a fully bound blueprint records the untouched bound param
/// values directly in the manifest — `params` stays `[]` (nothing varied) and
/// every bound value lands in `defaults`, RAW (#328) and ordered by
/// `bound_param_space()` (fast, slow, bias — node-declaration order).
#[test]
fn exec_manifest_stamps_untouched_bound_defaults() {
let (out, code) = run_code(&["exec", "examples/r_sma.json"]);
assert_eq!(code, Some(0), "stdout/stderr: {out}");
assert!(out.contains("\"params\":[]"), "params stays empty on a plain exec: {out}");
assert!(
out.contains(
r#""defaults":[["fast.length",{"I64":2}],["slow.length",{"I64":4}],["bias.scale",{"F64":0.5}]]"#
),
"manifest must carry the untouched bound defaults: {out}",
);
}
/// Property (ported from `cli_run.rs::run_manifest_commit_carries_real_git_head`):
/// the RunManifest is self-identifying — its `commit` carries the real git
/// HEAD that produced the run, never the `"unknown"` placeholder. Structural
/// (contains, not equals) since the working tree may be dirty.
#[test]
fn exec_manifest_commit_carries_real_git_head() {
let head = std::process::Command::new("git")
.args(["rev-parse", "HEAD"])
.output()
.expect("spawn git rev-parse HEAD");
assert!(head.status.success(), "git rev-parse HEAD failed");
let head_sha = String::from_utf8(head.stdout).expect("utf-8 sha");
let head_sha = head_sha.trim();
assert!(!head_sha.is_empty(), "empty HEAD sha");
let (out, code) = run_code(&["exec", "examples/r_sma.json"]);
assert_eq!(code, Some(0), "stdout/stderr: {out}");
let key = "\"commit\":\"";
let start = out.find(key).expect("manifest has a commit field") + key.len();
let rest = &out[start..];
let end = rest.find('"').expect("commit field is a closed string");
let commit = &rest[..end];
assert_ne!(commit, "unknown", "manifest.commit is still the placeholder: {out}");
assert!(
commit.contains(head_sha),
"manifest.commit {commit:?} should contain the current HEAD sha {head_sha:?}: {out}"
);
}
/// Property (#275, ported from `cli_run.rs::run_prints_json_and_exits_zero` — a
/// disposition-table gap: the plain synthetic single-run report shape was never
/// re-pinned onto `exec` although its property, a synthetic blueprint-leg
/// single run, belongs squarely to this leg): the canonical record-line shape
/// — nested manifest + metrics, stable keys, exactly one stdout line.
#[test]
fn exec_prints_json_and_exits_zero() {
let (out, code) = run_code(&["exec", "examples/r_sma.json"]);
assert_eq!(code, Some(0), "stdout/stderr: {out}");
assert_eq!(out.lines().count(), 1, "stdout was: {out:?}");
let line = out.trim_end();
assert!(line.starts_with("{\"manifest\":{\"commit\":\""), "got: {line}");
assert!(line.contains("\"broker\":\"sim-optimal+risk-executor(pip_size=0.0001)\""), "got: {line}");
assert!(line.contains("\"window\":[1,18]"), "got: {line}");
assert!(line.contains("\"metrics\":{\"total_pips\":"), "got: {line}");
assert!(line.contains("\"r\":{\"expectancy_r\":"), "got: {line}");
}
/// A blueprint referencing a node type outside the closed vocabulary fails
/// clean at load (invariant 9), in house-style prose (mirrors
/// `cli_run.rs::aura_run_rejects_an_unknown_node_blueprint`).
#[test]
fn exec_rejects_an_unknown_node_blueprint() {
let (out, code) = run_code(&["exec", "tests/fixtures/unknown_node.json"]);
assert_ne!(code, Some(0), "an unknown node type must fail the run: {out}");
assert!(out.contains(r#"unknown node type "Nope""#), "house-style prose names the type: {out}");
assert!(!out.contains("UnknownNodeType"), "does not leak the Debug variant name: {out}");
}
/// Property (#231 task 4): `exec`'s blueprint leg has no `--real` at all
/// (invariant 7 — a real-data single run is a one-cell campaign document), so
/// a multi-column blueprint always hits the synthetic-only refusal — mirrors
/// `cli_run.rs::run_synthetic_refuses_a_multi_column_blueprint` over the
/// shipped `examples/r_channel.json`.
#[test]
fn exec_refuses_a_multi_column_blueprint() {
let (out, code) = run_code(&["exec", "examples/r_channel.json"]);
assert_eq!(code, Some(1), "stdout/stderr: {out}");
assert!(out.contains("consumes columns beyond close"), "names the shape: {out}");
assert!(out.contains("--real"), "names the remedy: {out}");
}
/// A CLOSED high/low blueprint (zero free knobs — copied verbatim from
/// `cli_run.rs::HL_RANGE_CLOSED_BLUEPRINT`) drives the exact-prose twin of the
/// refusal above, data-free (the guard fires directly after binding
/// resolution, before any archive access) — mirrors
/// `cli_run.rs::run_synthetic_refuses_a_multi_column_blueprint_before_data_access`.
const HL_RANGE_CLOSED_BLUEPRINT: &str = r#"{
"format_version": 1,
"blueprint": {
"name": "hl_range",
"nodes": [ {"primitive":{"type":"Sub"}} ],
"edges": [],
"input_roles": [
{"name":"high","targets":[{"node":0,"slot":0}],"source":"F64"},
{"name":"low","targets":[{"node":0,"slot":1}],"source":"F64"}
],
"output": [{"node":0,"field":0,"name":"bias"}]
}
}"#;
#[test]
fn exec_refuses_a_multi_column_blueprint_before_data_access() {
let cwd = temp_cwd("exec_multicolumn_refusal");
let bp_path = cwd.join("hl_range.json");
std::fs::write(&bp_path, HL_RANGE_CLOSED_BLUEPRINT).expect("write fixture");
let (out, code) = run_code_in(&cwd, &["exec", bp_path.to_str().expect("utf-8 path")]);
assert_eq!(code, Some(1), "stdout/stderr: {out}");
assert_eq!(
out.trim_end(),
"aura: strategy \"hl_range\" consumes columns beyond close (high, low) — synthetic \
data generates a close series only; run with --real <SYMBOL>"
);
}
/// Property (#210, dissolution — content-addressed idempotency), ported onto
/// `exec`'s campaign FILE leg (the register-then-run sugar,
/// `campaign_run.rs::run_campaign`'s target resolution): re-`exec`ing the
/// IDENTICAL campaign document file does not litter the store with a second
/// registered campaign document — `put_campaign` writes under a content-id
/// filename, so two runs of the same file collapse onto one registered
/// document while each invocation still records its own `campaign_runs.jsonl`
/// line (a run is an event, a document is content) — mirrors
/// `cli_run.rs::generalize_repeated_identical_invocation_does_not_litter_the_store`,
/// whose translator generated the document; here the document is authored and
/// `exec` performs only the file leg's own register-then-run idempotency.
#[test]
fn exec_campaign_file_repeated_identical_invocation_does_not_litter_the_store() {
let (dir, _fixture) = fresh_project_with_data();
let runs_dir = dir.join("runs");
std::fs::remove_dir_all(&runs_dir).ok();
let _cleanup = ScratchGuard(vec![
ScratchPath::Dir(runs_dir.clone()),
ScratchPath::File(dir.join("idem.process.json")),
ScratchPath::File(dir.join("idem.campaign.json")),
]);
let bp_id = seed_blueprint(&dir, "exec-idempotence-seed");
let proc_id = register_process_doc(&dir, "idem.process.json", SWEEP_ONLY_PROCESS_DOC);
let doc = campaign_doc_json_for("SYMA", &bp_id, &proc_id, (1709251200000, 1719791999999));
write_doc(&dir, "idem.campaign.json", &doc);
let (first_out, first_code) = run_code_in(&dir, &["exec", "idem.campaign.json"]);
assert_eq!(first_code, Some(0), "first run: {first_out}");
let (second_out, second_code) = run_code_in(&dir, &["exec", "idem.campaign.json"]);
assert_eq!(second_code, Some(0), "second run: {second_out}");
let campaigns_count = std::fs::read_dir(dir.join("runs").join("campaigns"))
.map(|d| d.count())
.unwrap_or(0);
assert_eq!(campaigns_count, 1, "two identical file-target invocations register one campaign document");
let runs_log = std::fs::read_to_string(dir.join("runs").join("campaign_runs.jsonl"))
.expect("campaign_runs.jsonl exists");
assert_eq!(runs_log.lines().count(), 2, "each invocation still records its own campaign run");
}
/// `campaign_doc_json_for`'s shape with the axis grid parameterized — needed
/// so the distinctness twin below can vary campaign content without touching
/// the window (keeping the known-good data-availability window fixed).
fn campaign_doc_json_for_axis_grid(
instrument: &str,
bp_id: &str,
proc_id: &str,
window: (i64, i64),
fast_values: &str,
slow_values: &str,
) -> String {
format!(
r#"{{
"format_version": 1,
"kind": "campaign",
"name": "run-seam-grid",
"data": {{ "instruments": ["{instrument}"], "windows": [ {{ "from_ms": {from}, "to_ms": {to} }} ] }},
"strategies": [ {{ "ref": {{ "content_id": "{bp_id}" }},
"axes": {{ "fast.length": {{ "kind": "I64", "values": [{fast_values}] }},
"slow.length": {{ "kind": "I64", "values": [{slow_values}] }} }} }} ],
"process": {{ "ref": {{ "content_id": "{proc_id}" }} }},
"seed": 7,
"presentation": {{ "persist_taps": [], "emit": ["family_table"] }}
}}"#,
from = window.0,
to = window.1,
)
}
/// Property (#210, dissolution — content-addressed distinctness), ported onto
/// `exec`'s campaign FILE leg: two DIFFERENT campaign documents (differing
/// axis-grid content, same window) persist as TWO distinct registered
/// campaign documents — the twin of the idempotency test above, ruling out a
/// store that always overwrites one fixed filename regardless of content —
/// mirrors `cli_run.rs::generalize_distinct_invocations_persist_distinct_campaign_documents`.
#[test]
fn exec_campaign_file_distinct_invocations_persist_distinct_campaign_documents() {
let (dir, _fixture) = fresh_project_with_data();
let runs_dir = dir.join("runs");
std::fs::remove_dir_all(&runs_dir).ok();
let _cleanup = ScratchGuard(vec![
ScratchPath::Dir(runs_dir.clone()),
ScratchPath::File(dir.join("distinct.process.json")),
ScratchPath::File(dir.join("distinct_a.campaign.json")),
ScratchPath::File(dir.join("distinct_b.campaign.json")),
]);
let bp_id = seed_blueprint(&dir, "exec-distinct-seed");
let proc_id = register_process_doc(&dir, "distinct.process.json", SWEEP_ONLY_PROCESS_DOC);
let window = (1709251200000, 1719791999999);
let doc_a = campaign_doc_json_for_axis_grid("SYMA", &bp_id, &proc_id, window, "2", "8");
write_doc(&dir, "distinct_a.campaign.json", &doc_a);
let (out_a, code_a) = run_code_in(&dir, &["exec", "distinct_a.campaign.json"]);
assert_eq!(code_a, Some(0), "first document run: {out_a}");
let doc_b = campaign_doc_json_for_axis_grid("SYMA", &bp_id, &proc_id, window, "2, 4", "8, 16");
write_doc(&dir, "distinct_b.campaign.json", &doc_b);
let (out_b, code_b) = run_code_in(&dir, &["exec", "distinct_b.campaign.json"]);
assert_eq!(code_b, Some(0), "second document run: {out_b}");
let campaigns_count = std::fs::read_dir(dir.join("runs").join("campaigns"))
.map(|d| d.count())
.unwrap_or(0);
assert_eq!(campaigns_count, 2, "two distinct documents persist as two distinct campaign documents");
}
/// Property (C22 first-contact ergonomics, #20), ported onto the surviving
/// verb set (mirrors `cli_run.rs::help_flag_prints_usage_to_stdout_and_exits_zero`,
/// whose "the usage names the only subcommand a newcomer can run" assertion
/// named the now-retired `run` — the newcomer's reflex first command is
/// `exec` today): `--help`/`-h` prints to stdout and exits 0; an unknown
/// subcommand keeps the error path (exit 2).
#[test]
fn exec_help_flag_prints_usage_to_stdout_and_exits_zero() {
for flag in ["--help", "-h"] {
let (out, code) = run_code(&[flag]);
assert_eq!(code, Some(0), "`aura {flag}` exit: {out}");
assert!(!out.trim().is_empty(), "`aura {flag}` should print help to stdout: {out}");
assert!(out.contains("exec"), "`aura {flag}` help should mention the `exec` subcommand: {out}");
}
let (out, code) = run_code(&["frobnicate"]);
assert_eq!(code, Some(2), "unknown subcommand must stay exit 2: {out}");
}
/// Property (post-clap migration, #175), ported onto the surviving verb set
/// (mirrors `cli_run.rs::per_subcommand_help_is_scoped_stdout_exit_zero`,
/// which enumerated the now-retired quintet): every surviving subcommand's
/// `--help`/`-h` prints its own scoped Options section to stdout, exit 0,
/// nothing on stderr.
#[test]
fn exec_per_subcommand_help_is_scoped_stdout_exit_zero() {
for sub in ["exec", "chart", "graph", "runs", "reproduce", "new", "nodes", "process", "campaign", "data", "measure"] {
for help in ["--help", "-h"] {
let (out, code) = run_code(&[sub, help]);
assert_eq!(code, Some(0), "{sub} {help} exit: {out}");
assert!(!out.trim().is_empty(), "{sub} {help} stdout empty");
}
}
}
/// Property (mirrors `cli_run.rs::subcommand_help_is_scoped_not_uniform`): a
/// subcommand's `--help` is NOT byte-identical to another's — each documents
/// its own options, never one shared global blob.
#[test]
fn exec_subcommand_help_is_scoped_not_uniform() {
let (exec_help, exec_code) = run_code(&["exec", "--help"]);
let (graph_help, graph_code) = run_code(&["graph", "--help"]);
assert_eq!(exec_code, Some(0), "exec --help exit");
assert_eq!(graph_code, Some(0), "graph --help exit");
assert!(exec_help.contains("--override"), "exec help names its flags: {exec_help:?}");
assert_ne!(exec_help, graph_help, "per-subcommand help must be scoped, not uniform");
}
/// The GNU `--flag=value` equals form is accepted (mirrors
/// `cli_run.rs::gnu_equals_form_is_accepted`; the surviving vehicle flag is
/// `--override`, since `--seed` retired with `run`'s built-in-harness
/// grammar, invariant 7).
#[test]
fn exec_gnu_equals_form_is_accepted() {
let (out, code) = run_code(&["exec", "examples/r_sma.json", "--override=fast.length=8"]);
assert_eq!(code, Some(0), "stdout/stderr: {out}");
}
/// The `--` end-of-options terminator is recognized (mirrors
/// `cli_run.rs::double_dash_terminator_is_recognized`).
#[test]
fn exec_double_dash_terminator_is_recognized() {
let (out, code) = run_code(&["exec", "examples/r_sma.json", "--override", "fast.length=8", "--"]);
assert_eq!(code, Some(0), "stdout/stderr: {out}");
}
/// GNU long-option abbreviation (mirrors
/// `cli_run.rs::long_option_abbreviation_is_accepted`): an unambiguous prefix
/// of a long flag is accepted (`--over` -> `--override`), via clap's root
/// `infer_long_args`.
#[test]
fn exec_long_option_abbreviation_is_accepted() {
let (out, code) = run_code(&["exec", "examples/r_sma.json", "--over", "fast.length=8"]);
assert_eq!(code, Some(0), "stdout/stderr: {out}");
}
/// exec's target classification, ported/merged from two retired dual-grammar
/// guards (`cli_run.rs::dual_grammar_stray_positional_is_a_usage_error_not_swallowed`
/// and the retired `run_with_trailing_token_is_strict_and_exits_two`'s negative
/// half): `target` is exec's one REQUIRED positional, so clap always parses
/// it — there is no more optional-positional-plus-hidden-default to guard
/// against. A bare, non-`.json` stray token is instead classified at RUNTIME
/// by the campaign leg's own target resolution, which refuses naming both
/// readings it tried, never silently defaulting to anything.
#[test]
fn exec_stray_target_that_is_neither_a_file_nor_a_content_id_refuses() {
let (dir, _fixture) = fresh_project();
let (out, code) = run_code_in(&dir, &["exec", "bogus"]);
assert_eq!(code, Some(1), "stdout/stderr: {out}");
assert!(
out.contains("'bogus' is neither a readable .json file nor a 64-hex content id"),
"stdout/stderr: {out}"
);
}
/// exec's discriminator keys on `is_file()`, not the `.json` suffix (mirrors
/// `cli_run.rs::dual_grammar_discriminator_requires_an_existing_file_not_just_json_suffix`):
/// a `.json`-suffixed name that does not exist on disk is NOT read as a
/// blueprint (which would fail with a file-read error) — it falls through to
/// the same neither-file-nor-content-id classification the bare stray token
/// above gets. The positive `.json`-file path stays covered by
/// `exec_blueprint_file_emits_the_single_run_record_line`.
#[test]
fn exec_json_suffixed_nonexistent_target_is_not_read_as_a_blueprint() {
let (dir, _fixture) = fresh_project();
let (out, code) = run_code_in(&dir, &["exec", "definitely-not-a-real-file.json"]);
assert_eq!(code, Some(1), "stdout/stderr: {out}");
assert!(
out.contains("'definitely-not-a-real-file.json' is neither a readable .json file nor a 64-hex content id"),
"stdout/stderr: {out}"
);
}
/// The usage/runtime exit-code partition (#175 iteration 2, deviation #8),
/// ported onto exec's own grammar (mirrors
/// `cli_run.rs::exit_codes_partition_usage_two_from_runtime_one`, whose
/// runtime half rode `run --real NONEXISTENT` — a flag exec's blueprint leg
/// no longer has, invariant 7): an unknown flag is a command-line error
/// (exit 2); `--override` naming no param of the blueprint is exec's own
/// well-formed-but-unresolvable runtime failure (exit 1).
#[test]
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}");
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}"
);
}
/// `exec` on the shipped closed r-sma example reproduces the built-in r-sma
/// grade (mirrors `cli_run.rs::shipped_r_sma_example_reproduces_the_builtin_grade`).
#[test]
fn exec_shipped_r_sma_example_reproduces_the_builtin_grade() {
let (out, code) = run_code(&["exec", "examples/r_sma.json"]);
assert_eq!(code, Some(0), "stdout/stderr: {out}");
assert!(out.contains("\"expectancy_r\":1.2710005136982836"), "{out}");
assert!(out.contains("\"sqn\":3.141496526818299"), "{out}");
assert!(out.contains("\"total_pips\":0.34185000000002036"), "{out}");
assert!(out.contains("\"n_trades\":3"), "{out}");
}
/// `exec` on the shipped closed r-meanrev example runs end to end through the
/// real, separately-linked `aura` binary (mirrors
/// `cli_run.rs::shipped_r_meanrev_example_runs_end_to_end_via_aura_run`). The
/// synthetic stream never crosses the band at this window/k, so the grade is
/// genuinely all-zero (no trade) — still a meaningful pin: a regression that
/// broke the `Scale` band's CLI wiring would surface as a non-zero exit or a
/// load error here, not as a metrics drift.
#[test]
fn exec_shipped_r_meanrev_example_runs_end_to_end() {
let (out, code) = run_code(&["exec", "examples/r_meanrev.json"]);
assert_eq!(code, Some(0), "stdout/stderr: {out}");
assert!(out.contains("\"topology_hash\":\""), "manifest carries the topology hash: {out}");
assert!(out.contains("\"expectancy_r\":0.0"), "{out}");
assert!(out.contains("\"n_trades\":0"), "{out}");
assert!(out.contains("\"total_pips\":0.0"), "{out}");
}
+76 -16
View File
@@ -104,31 +104,91 @@ fn outside_a_project_the_demo_blueprint_is_unknown() {
/// `<project-root>/runs/`, and no stray `runs/` must appear under the
/// subdirectory. This is the property that makes `[paths]` project-relative
/// rather than shell-relative (C17).
///
/// #319 vehicle note: the synthetic in-process family builder this test used
/// to drive via bare `sweep --axis` (no `--real`) retired with the quintet —
/// no surviving CLI surface produces a family record without touching a real
/// archive (`exec`'s campaign leg always resolves against `DataServer`), so
/// the vehicle is now a one-cell campaign over the `fresh_project_with_data`
/// synthetic `SYMA` archive (mirrors `exec.rs`'s `seed_blueprint` +
/// `campaign_doc_json_for` recipe), executed from a project subdirectory.
#[test]
fn project_registry_anchors_at_discovered_root_not_invocation_cwd() {
let dir = built_project();
let sub = dir.join("src");
let (dir, _fixture) = common::fresh_project_with_data();
let sub = dir.join("sub");
std::fs::create_dir_all(&sub).expect("create invocation subdirectory");
let runs_dir = dir.join("runs");
std::fs::remove_dir_all(&runs_dir).ok();
let closed_bp =
format!("{}/examples/r_sma.json", env!("CARGO_MANIFEST_DIR"));
let closed_bp = format!("{}/examples/r_sma.json", env!("CARGO_MANIFEST_DIR"));
let reg_out = Command::new(env!("CARGO_BIN_EXE_aura"))
.args(["graph", "register", &closed_bp, "--name", "proj-anchor-seed"])
.current_dir(&dir)
.output()
.expect("spawn aura graph register");
assert!(reg_out.status.success(), "register failed: {}", String::from_utf8_lossy(&reg_out.stderr));
let reg_text = String::from_utf8_lossy(&reg_out.stdout);
let bp_id = reg_text
.lines()
.find(|l| l.starts_with("registered blueprint "))
.expect("register line")
.trim_start_matches("registered blueprint ")
.split(' ')
.next()
.expect("id")
.to_string();
let process_doc = r#"{
"format_version": 1,
"kind": "process",
"name": "sweep-only",
"pipeline": [ { "block": "std::sweep", "metric": "sqn_normalized", "select": "argmax" } ]
}"#;
std::fs::write(dir.join("anchor.process.json"), process_doc).expect("write process doc");
let proc_out = Command::new(env!("CARGO_BIN_EXE_aura"))
.args(["process", "register", "anchor.process.json"])
.current_dir(&dir)
.output()
.expect("spawn aura process register");
assert!(proc_out.status.success(), "process register failed: {}", String::from_utf8_lossy(&proc_out.stderr));
let proc_text = String::from_utf8_lossy(&proc_out.stdout);
let proc_id = proc_text
.lines()
.find(|l| l.starts_with("registered process "))
.expect("register line")
.trim_start_matches("registered process ")
.split(' ')
.next()
.expect("id")
.trim_start_matches("content:")
.to_string();
// The established `SYMA` window (2024-03..06, fully inside its 2024-01..08
// span) other #319 vehicle campaigns use.
let campaign_doc = format!(
r#"{{
"format_version": 1,
"kind": "campaign",
"name": "run-seam",
"data": {{ "instruments": ["SYMA"], "windows": [ {{ "from_ms": 1709251200000, "to_ms": 1719791999999 }} ] }},
"strategies": [ {{ "ref": {{ "content_id": "{bp_id}" }},
"axes": {{ "fast.length": {{ "kind": "I64", "values": [2, 4] }},
"slow.length": {{ "kind": "I64", "values": [8, 16] }} }} }} ],
"process": {{ "ref": {{ "content_id": "{proc_id}" }} }},
"seed": 7,
"presentation": {{ "persist_taps": [], "emit": ["family_table"] }}
}}"#
);
std::fs::write(dir.join("anchor.campaign.json"), &campaign_doc).expect("write campaign doc");
let out = Command::new(env!("CARGO_BIN_EXE_aura"))
.args([
"sweep",
&closed_bp,
"--axis",
"fast.length=2,4",
"--axis",
"slow.length=8,16",
"--name",
"proj-anchor",
])
.args(["exec", "../anchor.campaign.json"])
.current_dir(&sub)
.output()
.expect("spawn aura sweep");
.expect("spawn aura exec");
assert!(
out.status.success(),
"sweep failed: {}",
"exec failed: {}",
String::from_utf8_lossy(&out.stderr)
);
assert!(
+22 -80
View File
@@ -319,34 +319,9 @@ fn campaign_validate_in_project_reports_referential_tier_end_to_end() {
]);
// Seed one real, content-addressed blueprint into the project's own
// store via a real sweep over its bound params, which the axes reopen
// as overridable defaults (#246) (mirrors `project_load.rs`'s anchor
// test).
let closed_bp = format!("{}/examples/r_sma.json", env!("CARGO_MANIFEST_DIR"));
let (sweep_out, sweep_code) = run_code_in(
&dir,
&[
"sweep",
&closed_bp,
"--axis",
"fast.length=2,4",
"--axis",
"slow.length=8,16",
"--name",
"campaign-ref-seed",
],
);
assert_eq!(sweep_code, Some(0), "seed sweep failed: {sweep_out}");
let bp_id = std::fs::read_dir(runs_dir.join("blueprints"))
.expect("blueprints dir")
.next()
.expect("one stored blueprint")
.expect("dir entry")
.path()
.file_stem()
.expect("stem")
.to_string_lossy()
.into_owned();
// store via `aura graph register` (#319 — the retired `sweep`
// side-effect seeding this used to ride).
let bp_id = seed_blueprint(&dir, "campaign-ref-seed");
// Register a valid AND executable process into the same project store:
// this test's OK campaign must pass all three validate tiers, and
@@ -441,32 +416,8 @@ fn campaign_validate_resolves_identity_ref_via_index_first_lookup_then_index_hit
]);
// Seed one real, content-addressed blueprint (mirrors the content_id
// sibling test above).
let closed_bp = format!("{}/examples/r_sma.json", env!("CARGO_MANIFEST_DIR"));
let (sweep_out, sweep_code) = run_code_in(
&dir,
&[
"sweep",
&closed_bp,
"--axis",
"fast.length=2,4",
"--axis",
"slow.length=8,16",
"--name",
"campaign-identity-seed",
],
);
assert_eq!(sweep_code, Some(0), "seed sweep failed: {sweep_out}");
let bp_id = std::fs::read_dir(runs_dir.join("blueprints"))
.expect("blueprints dir")
.next()
.expect("one stored blueprint")
.expect("dir entry")
.path()
.file_stem()
.expect("stem")
.to_string_lossy()
.into_owned();
// sibling test above; #319 — the retired `sweep` side-effect seeding).
let bp_id = seed_blueprint(&dir, "campaign-identity-seed");
let bp_path = runs_dir.join("blueprints").join(format!("{bp_id}.json"));
// The identity id of the SAME stored bytes — `--content-id FILE
@@ -1278,35 +1229,26 @@ fn campaign_register_refuses_invalid_cost_section_and_writes_nothing() {
);
}
/// Seed one blueprint (its params bound, reopened by the sweep axes per
/// #246) into the built demo project's store via a real sweep and return
/// its content id (the referential test's recipe).
/// Seed one blueprint into the built demo project's store via `aura graph
/// register` and return its content id (the referential test's recipe).
/// `#319` retired the `sweep`-side-effect seeding this used to ride (its
/// `--axis`/reopen mechanics played no role in the returned content id,
/// which is a pure function of the loaded blueprint's canonical bytes);
/// `name` labels the registered id for readability/uniqueness across call
/// sites, mirroring the old per-call sweep family name.
fn seed_blueprint(dir: &Path, name: &str) -> String {
let closed_bp = format!("{}/examples/r_sma.json", env!("CARGO_MANIFEST_DIR"));
let (out, code) = run_code_in(
dir,
&[
"sweep",
&closed_bp,
"--axis",
"fast.length=2,4",
"--axis",
"slow.length=8,16",
"--name",
name,
],
);
assert_eq!(code, Some(0), "seed sweep failed: {out}");
std::fs::read_dir(dir.join("runs").join("blueprints"))
.expect("blueprints dir")
let (out, code) = run_code_in(dir, &["graph", "register", &closed_bp, "--name", name]);
assert_eq!(code, Some(0), "seed register failed: {out}");
out.lines()
.find(|l| l.starts_with("registered blueprint "))
.expect("register line")
.trim_start_matches("registered blueprint ")
.split(' ')
.next()
.expect("one stored blueprint")
.expect("dir entry")
.path()
.file_stem()
.expect("stem")
.to_string_lossy()
.into_owned()
.expect("id")
.trim_start_matches("content:")
.to_string()
}
/// Register `doc` as a process document in the project store; returns its id.