feat: the document-first surface closes — show read-back, typed stops, bare plateau
The #300 keystone cycle: the residual run-quintet flags were already document vocabulary on the --real arm (#210/#220); this closes the loop and ratifies the surface. - `aura process|campaign show <content-id>` prints a registered document's canonical bytes (print!, no framing — #164), so generate -> retrieve -> hand-extend -> re-register needs no direct store filesystem access. Refusals reuse the store-hint prose, exit 1. - walkforward/mc --stop-length/--stop-k become clap-typed i64/f64 (generalize's form); stop_knob_or and its parse_csv_list helper are deleted (multi-value/invalid input is now clap's exit-2 rejection — the two dissolved-verb refusal tests retarget, the type-dead unit test is deleted). - --select accepts bare `plateau` as the documented default plateau:mean (#227 carry); document schema untouched — the two spellings generate content-id-identical campaigns (test-pinned). - family.rs's quadruplicated demo stop literal collapses onto one DEFAULT_STOP const (maintainer-lens drift finding). - Ledger: C25's "which projection next" line resolved (document-first delivered, per-verb identity re-ratified — F8; host/MCP stay demand-driven); C18 records the read-back. Design triage, fork decisions F1-F8, and the auto-signed spec live on the reference issue. Verification: full workspace suite 1482 passed / 0 failed with the real GER40 archive exercised (the byte-identity round-trips ran, not skipped); clippy -D warnings clean. closes #300
This commit is contained in:
+42
-91
@@ -487,12 +487,13 @@ fn resolve_campaign_name(name: &str, env: &aura_runner::project::Env) -> NameRes
|
|||||||
// `aura_runner::family` (#295 Task 8) — imported below by plain name so
|
// `aura_runner::family` (#295 Task 8) — imported below by plain name so
|
||||||
// call sites are unchanged.
|
// call sites are unchanged.
|
||||||
|
|
||||||
/// Parse a `--select` token: `argmax` | `plateau:mean` | `plateau:worst`. Unknown
|
/// Parse a `--select` token: `argmax` | `plateau` (alias for `plateau:mean`) |
|
||||||
/// tokens are a usage error (the caller maps `Err(())` to exit 2).
|
/// `plateau:mean` | `plateau:worst`. Unknown tokens are a usage error (the
|
||||||
|
/// caller maps `Err(())` to exit 2).
|
||||||
fn parse_select(s: &str) -> Result<Selection, ()> {
|
fn parse_select(s: &str) -> Result<Selection, ()> {
|
||||||
match s {
|
match s {
|
||||||
"argmax" => Ok(Selection::Argmax),
|
"argmax" => Ok(Selection::Argmax),
|
||||||
"plateau:mean" => Ok(Selection::Plateau(PlateauMode::Mean)),
|
"plateau" | "plateau:mean" => Ok(Selection::Plateau(PlateauMode::Mean)),
|
||||||
"plateau:worst" => Ok(Selection::Plateau(PlateauMode::Worst)),
|
"plateau:worst" => Ok(Selection::Plateau(PlateauMode::Worst)),
|
||||||
_ => Err(()),
|
_ => Err(()),
|
||||||
}
|
}
|
||||||
@@ -508,22 +509,6 @@ fn select_rule_of(sel: Selection) -> aura_research::SelectRule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a comma-separated list of `T` (each item parsed via `FromStr`), rejecting
|
|
||||||
/// any item that fails to parse — the shared validator for the r-sma grid flags
|
|
||||||
/// (#137). The caller folds the `Err` into the subcommand `usage()` so a malformed
|
|
||||||
/// `--fast 2,x` is the strict usage path, not a downstream panic. `str::split(',')`
|
|
||||||
/// always yields at least one item, and an empty item (`""`, or a trailing comma)
|
|
||||||
/// fails its per-item `parse`, so a non-empty result needs no separate empty-list
|
|
||||||
/// check — the empty/blank inputs are rejected by the per-item parse itself.
|
|
||||||
fn parse_csv_list<T: std::str::FromStr>(s: &str) -> Result<Vec<T>, ()> {
|
|
||||||
let items: Vec<&str> = s.split(',').collect();
|
|
||||||
let mut out = Vec::with_capacity(items.len());
|
|
||||||
for item in items {
|
|
||||||
out.push(item.parse::<T>().map_err(|_| ())?);
|
|
||||||
}
|
|
||||||
Ok(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Render a family-member stdout line: the assigned `family_id` plus the embedded
|
/// Render a family-member stdout line: the assigned `family_id` plus the embedded
|
||||||
/// `RunReport`. The report is emitted in its own declaration key order (manifest
|
/// `RunReport`. The report is emitted in its own declaration key order (manifest
|
||||||
/// leads with `commit`, C18) so the line is byte-identical to the stored
|
/// leads with `commit`, C18) so the line is byte-identical to the stored
|
||||||
@@ -1001,7 +986,7 @@ fn parse_scalar_csv(csv: &str) -> Option<Vec<Scalar>> {
|
|||||||
// `--version`, `--flag=value`, `--`, and long-option abbreviation; the `dispatch_*`
|
// `--version`, `--flag=value`, `--`, and long-option abbreviation; the `dispatch_*`
|
||||||
// handlers below convert each `*Cmd` into the argument shapes the existing execution
|
// handlers below convert each `*Cmd` into the argument shapes the existing execution
|
||||||
// fns accept, reusing the value helpers (`Selection`,
|
// fns accept, reusing the value helpers (`Selection`,
|
||||||
// `DataSource::from_choice`, `parse_scalar_csv`, `parse_csv_list`, `parse_select`,
|
// `DataSource::from_choice`, `parse_scalar_csv`, `parse_select`,
|
||||||
// `parse_param_cells`). The four subcommands with an optional `[blueprint]` positional
|
// `parse_param_cells`). The four subcommands with an optional `[blueprint]` positional
|
||||||
// dispatch on `is_blueprint_file`: a first-positional naming an existing `.json` file
|
// dispatch on `is_blueprint_file`: a first-positional naming an existing `.json` file
|
||||||
// selects the loaded-blueprint branch. There is no second built-in grammar anymore
|
// selects the loaded-blueprint branch. There is no second built-in grammar anymore
|
||||||
@@ -1358,13 +1343,14 @@ struct WalkforwardCmd {
|
|||||||
/// mutually exclusive with --name).
|
/// mutually exclusive with --name).
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
trace: Option<String>,
|
trace: Option<String>,
|
||||||
/// Campaign-path stop length (single value; --real mode).
|
/// Campaign-path stop length (--real mode).
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
stop_length: Option<String>,
|
stop_length: Option<i64>,
|
||||||
/// Campaign-path stop-k multiple (single value; --real mode).
|
/// Campaign-path stop-k multiple (--real mode).
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
stop_k: Option<String>,
|
stop_k: Option<f64>,
|
||||||
/// In-sample winner selection: argmax | plateau:mean | plateau:worst (default argmax).
|
/// In-sample winner selection: argmax | plateau (alias for plateau:mean) |
|
||||||
|
/// plateau:mean | plateau:worst (default argmax).
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
select: Option<String>,
|
select: Option<String>,
|
||||||
/// Blueprint IS-refit axis `<name>=<csv>` (repeatable, >=1 required; .json mode).
|
/// Blueprint IS-refit axis `<name>=<csv>` (repeatable, >=1 required; .json mode).
|
||||||
@@ -1395,12 +1381,12 @@ struct McCmd {
|
|||||||
/// Blueprint IS-refit axis `<name>=<csv>` (repeatable, >=1 required; --real mode).
|
/// Blueprint IS-refit axis `<name>=<csv>` (repeatable, >=1 required; --real mode).
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
axis: Vec<String>,
|
axis: Vec<String>,
|
||||||
/// Campaign-path stop length (single value; --real mode).
|
/// Campaign-path stop length (--real mode).
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
stop_length: Option<String>,
|
stop_length: Option<i64>,
|
||||||
/// Campaign-path stop-k multiple (single value; --real mode).
|
/// Campaign-path stop-k multiple (--real mode).
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
stop_k: Option<String>,
|
stop_k: Option<f64>,
|
||||||
/// Moving-block bootstrap block length (R-bootstrap; default 1).
|
/// Moving-block bootstrap block length (R-bootstrap; default 1).
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
block_len: Option<usize>,
|
block_len: Option<usize>,
|
||||||
@@ -1442,41 +1428,16 @@ fn run_data_from(real: Option<&str>, from: Option<i64>, to: Option<i64>) -> RunD
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve a single optional stop knob (`--stop-length`/`--stop-k`): an absent flag
|
|
||||||
/// defaults to `default`; a present one parses via [`parse_csv_list`] and refuses
|
|
||||||
/// unless it holds exactly one value (the stop is a risk regime, not a swept axis).
|
|
||||||
/// Single-sourced across `walkforward_args_from`/`mc_args_from` (#217 follow-up) —
|
|
||||||
/// `generalize_args_from`'s typed `Option<i64>`/`Option<f64>` fields need no parse
|
|
||||||
/// step, so it keeps its own plain `unwrap_or`.
|
|
||||||
fn stop_knob_or<T: Copy + std::str::FromStr>(
|
|
||||||
raw: Option<&str>,
|
|
||||||
default: T,
|
|
||||||
regime: &impl Fn() -> String,
|
|
||||||
) -> Result<T, String> {
|
|
||||||
match raw {
|
|
||||||
None => Ok(default),
|
|
||||||
Some(s) => {
|
|
||||||
let v: Vec<T> = parse_csv_list(s).map_err(|()| regime())?;
|
|
||||||
if v.len() != 1 {
|
|
||||||
return Err(regime());
|
|
||||||
}
|
|
||||||
Ok(v[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert `WalkforwardCmd` into the resolved argument shape the walkforward sugar
|
/// Convert `WalkforwardCmd` into the resolved argument shape the walkforward sugar
|
||||||
/// consumes (the dissolved `.json --real` branch). Single instrument, single-value
|
/// consumes (the dissolved `.json --real` branch). Single instrument, single-value
|
||||||
/// stop (Fork A: the stop is a risk regime, not a swept axis); `parse_csv_list` is
|
/// stop (Fork A: the stop is a risk regime, not a swept axis); `--stop-length`/
|
||||||
/// generic over `T: FromStr`, so the same helper parses the i64 length and the f64
|
/// `--stop-k` are typed `Option<i64>`/`Option<f64>` (clap rejects a multi-value
|
||||||
/// k. `--stop-length`/`--stop-k` are each OPTIONAL (#217): a missing flag defaults
|
/// spelling itself, "invalid value") and each OPTIONAL (#217): a missing flag
|
||||||
/// independently to the single-sourced [`R_SMA_STOP_LENGTH`]/[`R_SMA_STOP_K`]
|
/// defaults independently to the single-sourced [`R_SMA_STOP_LENGTH`]/
|
||||||
/// regime; a present-but-multi-value stop still refuses (the local `regime`
|
/// [`R_SMA_STOP_K`] regime. `--name`/`--trace` are mutually exclusive, matching
|
||||||
/// closure, captureless → `Copy`, is reused across both arms). `--name`/`--trace`
|
/// the flag's own documented contract and the inline path's `name_persist`
|
||||||
/// are mutually exclusive, matching the flag's own documented contract and the
|
/// refusal; an omitted flag defaults the family name to "walkforward". The
|
||||||
/// inline path's `name_persist` refusal; an omitted flag defaults the family name
|
/// IS-refit `--axis` grid is parsed separately at the dispatch site, not here.
|
||||||
/// to "walkforward". The IS-refit `--axis` grid is parsed separately at the
|
|
||||||
/// dispatch site, not here.
|
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
fn walkforward_args_from(
|
fn walkforward_args_from(
|
||||||
a: &WalkforwardCmd,
|
a: &WalkforwardCmd,
|
||||||
@@ -1485,9 +1446,8 @@ fn walkforward_args_from(
|
|||||||
None | Some("") => return Err("walkforward dissolves only over --real <SYMBOL>".to_string()),
|
None | Some("") => return Err("walkforward dissolves only over --real <SYMBOL>".to_string()),
|
||||||
Some(s) => s.to_string(),
|
Some(s) => s.to_string(),
|
||||||
};
|
};
|
||||||
let regime = || "walkforward: the stop is a single risk regime; --stop-length and --stop-k take one value each".to_string();
|
let stop_length = a.stop_length.unwrap_or(R_SMA_STOP_LENGTH);
|
||||||
let stop_length = stop_knob_or(a.stop_length.as_deref(), R_SMA_STOP_LENGTH, ®ime)?;
|
let stop_k = a.stop_k.unwrap_or(R_SMA_STOP_K);
|
||||||
let stop_k = stop_knob_or(a.stop_k.as_deref(), R_SMA_STOP_K, ®ime)?;
|
|
||||||
let (name, trace) = match (a.name.as_deref(), a.trace.as_deref()) {
|
let (name, trace) = match (a.name.as_deref(), a.trace.as_deref()) {
|
||||||
(Some(_), Some(_)) => {
|
(Some(_), Some(_)) => {
|
||||||
return Err("walkforward: --name and --trace are mutually exclusive".to_string());
|
return Err("walkforward: --name and --trace are mutually exclusive".to_string());
|
||||||
@@ -1501,16 +1461,16 @@ fn walkforward_args_from(
|
|||||||
|
|
||||||
/// Convert `McCmd` into the resolved argument shape the mc sugar consumes (the
|
/// Convert `McCmd` into the resolved argument shape the mc sugar consumes (the
|
||||||
/// `--real` campaign branch). Single instrument, single-value stop (Fork A: the
|
/// `--real` campaign branch). Single instrument, single-value stop (Fork A: the
|
||||||
/// stop is a risk regime, not a swept axis); `--stop-length`/`--stop-k` are each
|
/// stop is a risk regime, not a swept axis); `--stop-length`/`--stop-k` are typed
|
||||||
/// OPTIONAL (#217), independently defaulting to the single-sourced
|
/// `Option<i64>`/`Option<f64>` (clap rejects a multi-value spelling itself,
|
||||||
/// [`R_SMA_STOP_LENGTH`]/[`R_SMA_STOP_K`] regime when omitted — a present-but-
|
/// "invalid value") and each OPTIONAL (#217), independently defaulting to the
|
||||||
/// multi-value stop still refuses. `--block-len`/`--resamples`/`--seed`
|
/// single-sourced [`R_SMA_STOP_LENGTH`]/[`R_SMA_STOP_K`] regime when omitted.
|
||||||
/// default to `1`/`1000`/`1` — the same defaults the retired real-R dispatch
|
/// `--block-len`/`--resamples`/`--seed` default to `1`/`1000`/`1` — the same
|
||||||
/// used, so an omitted flag produces the same document either way. The
|
/// defaults the retired real-R dispatch used, so an omitted flag produces the
|
||||||
/// usize->u32 conversion lands HERE, at the argv boundary where the CLI's
|
/// same document either way. The usize->u32 conversion lands HERE, at the argv
|
||||||
/// `usize` meets the document's `u32` vocabulary (`StageBlock::MonteCarlo`).
|
/// boundary where the CLI's `usize` meets the document's `u32` vocabulary
|
||||||
/// `--name`/`--trace` are rejected: the R-bootstrap records without a family
|
/// (`StageBlock::MonteCarlo`). `--name`/`--trace` are rejected: the R-bootstrap
|
||||||
/// name (the campaign name is a constant "mc").
|
/// records without a family name (the campaign name is a constant "mc").
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
fn mc_args_from(
|
fn mc_args_from(
|
||||||
a: &McCmd,
|
a: &McCmd,
|
||||||
@@ -1522,9 +1482,8 @@ fn mc_args_from(
|
|||||||
if a.name.is_some() || a.trace.is_some() {
|
if a.name.is_some() || a.trace.is_some() {
|
||||||
return Err("mc --real: --name/--trace are not accepted (the R-bootstrap records without a family name)".to_string());
|
return Err("mc --real: --name/--trace are not accepted (the R-bootstrap records without a family name)".to_string());
|
||||||
}
|
}
|
||||||
let regime = || "mc: the stop is a single risk regime; --stop-length and --stop-k take one value each".to_string();
|
let stop_length = a.stop_length.unwrap_or(R_SMA_STOP_LENGTH);
|
||||||
let stop_length = stop_knob_or(a.stop_length.as_deref(), R_SMA_STOP_LENGTH, ®ime)?;
|
let stop_k = a.stop_k.unwrap_or(R_SMA_STOP_K);
|
||||||
let stop_k = stop_knob_or(a.stop_k.as_deref(), R_SMA_STOP_K, ®ime)?;
|
|
||||||
let block_len = a.block_len.map(|v| v as u32).unwrap_or(1);
|
let block_len = a.block_len.map(|v| v as u32).unwrap_or(1);
|
||||||
let resamples = a.resamples.map(|v| v as u32).unwrap_or(1000);
|
let resamples = a.resamples.map(|v| v as u32).unwrap_or(1000);
|
||||||
let seed = a.seed.unwrap_or(1);
|
let seed = a.seed.unwrap_or(1);
|
||||||
@@ -2319,7 +2278,7 @@ fn dispatch_sweep(a: SweepCmd, env: &aura_runner::project::Env) {
|
|||||||
/// #220: arbitrary user blueprint + axes, formerly the welded r-sma branch).
|
/// #220: arbitrary user blueprint + axes, formerly the welded r-sma branch).
|
||||||
fn dispatch_walkforward(a: WalkforwardCmd, env: &aura_runner::project::Env) {
|
fn dispatch_walkforward(a: WalkforwardCmd, env: &aura_runner::project::Env) {
|
||||||
// Single-sourced: both arms below read this one closure (house style, #179).
|
// Single-sourced: both arms below read this one closure (house style, #179).
|
||||||
let usage = || format!("Usage: aura walkforward <blueprint.json> --axis <name>=<csv> [--axis …] [--select <argmax|plateau:mean|plateau:worst>] [--name <n>] | aura walkforward <blueprint.json> --real <SYMBOL> --axis <name>=<csv> [--axis …] [--stop-length <n> (default {R_SMA_STOP_LENGTH})] [--stop-k <x> (default {R_SMA_STOP_K:.1})] [--from <ms>] [--to <ms>] [--name <n> | --trace <n>]");
|
let usage = || format!("Usage: aura walkforward <blueprint.json> --axis <name>=<csv> [--axis …] [--select <argmax|plateau|plateau:mean|plateau:worst>] [--name <n>] | aura walkforward <blueprint.json> --real <SYMBOL> --axis <name>=<csv> [--axis …] [--stop-length <n> (default {R_SMA_STOP_LENGTH})] [--stop-k <x> (default {R_SMA_STOP_K:.1})] [--from <ms>] [--to <ms>] [--name <n> | --trace <n>]");
|
||||||
match is_blueprint_file(&a.blueprint) {
|
match is_blueprint_file(&a.blueprint) {
|
||||||
Some(path) => {
|
Some(path) => {
|
||||||
let doc = std::fs::read_to_string(path).unwrap_or_else(|e| {
|
let doc = std::fs::read_to_string(path).unwrap_or_else(|e| {
|
||||||
@@ -2763,6 +2722,10 @@ mod tests {
|
|||||||
assert!(matches!(parse_select("argmax").unwrap(), Selection::Argmax), "default is argmax");
|
assert!(matches!(parse_select("argmax").unwrap(), Selection::Argmax), "default is argmax");
|
||||||
assert!(matches!(parse_select("plateau:mean").unwrap(), Selection::Plateau(PlateauMode::Mean)));
|
assert!(matches!(parse_select("plateau:mean").unwrap(), Selection::Plateau(PlateauMode::Mean)));
|
||||||
assert!(matches!(parse_select("plateau:worst").unwrap(), Selection::Plateau(PlateauMode::Worst)));
|
assert!(matches!(parse_select("plateau:worst").unwrap(), Selection::Plateau(PlateauMode::Worst)));
|
||||||
|
assert!(matches!(
|
||||||
|
parse_select("plateau").unwrap(),
|
||||||
|
Selection::Plateau(PlateauMode::Mean)
|
||||||
|
));
|
||||||
assert!(parse_select("bogus").is_err(), "unknown --select token is a usage error");
|
assert!(parse_select("bogus").is_err(), "unknown --select token is a usage error");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3820,18 +3783,6 @@ mod tests {
|
|||||||
assert_eq!(stop_k, R_SMA_STOP_K, "omitted --stop-k defaults to the regime");
|
assert_eq!(stop_k, R_SMA_STOP_K, "omitted --stop-k defaults to the regime");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn mc_args_from_refuses_a_multi_value_stop() {
|
|
||||||
let a = McCmd {
|
|
||||||
real: Some("GER40".to_string()),
|
|
||||||
stop_length: Some("14,20".to_string()),
|
|
||||||
stop_k: Some("2.0".to_string()),
|
|
||||||
..bare_mc_cmd()
|
|
||||||
};
|
|
||||||
let err = mc_args_from(&a).unwrap_err();
|
|
||||||
assert!(err.contains("single risk regime"), "stop-regime refusal: {err}");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Property (#217): a missing `--stop-length`/`--stop-k` no longer refuses —
|
/// Property (#217): a missing `--stop-length`/`--stop-k` no longer refuses —
|
||||||
/// each independently defaults to the single-sourced [`R_SMA_STOP_LENGTH`]/
|
/// each independently defaults to the single-sourced [`R_SMA_STOP_LENGTH`]/
|
||||||
/// [`R_SMA_STOP_K`] regime (length 3, k 2.0).
|
/// [`R_SMA_STOP_K`] regime (length 3, k 2.0).
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ pub enum ProcessSub {
|
|||||||
Introspect(DocIntrospectCmd),
|
Introspect(DocIntrospectCmd),
|
||||||
/// Register a valid process document into the store under the runs root.
|
/// Register a valid process document into the store under the runs root.
|
||||||
Register { file: PathBuf },
|
Register { file: PathBuf },
|
||||||
|
/// Print a registered process document's canonical bytes to stdout.
|
||||||
|
Show { id: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(clap::Args)]
|
#[derive(clap::Args)]
|
||||||
@@ -208,6 +210,7 @@ pub fn process_cmd(cmd: ProcessCmd, env: &Env) {
|
|||||||
introspect_process(i)
|
introspect_process(i)
|
||||||
}
|
}
|
||||||
ProcessSub::Register { file } => register_process(file, env),
|
ProcessSub::Register { file } => register_process(file, env),
|
||||||
|
ProcessSub::Show { id } => show_process(id, env),
|
||||||
};
|
};
|
||||||
if let Err(m) = result {
|
if let Err(m) = result {
|
||||||
eprintln!("aura: {m}");
|
eprintln!("aura: {m}");
|
||||||
@@ -259,6 +262,21 @@ fn register_process(file: &PathBuf, env: &Env) -> Result<(), String> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn show_process(id: &str, env: &Env) -> Result<(), String> {
|
||||||
|
let registry = env.registry();
|
||||||
|
match registry.get_process(id).map_err(|e| e.to_string())? {
|
||||||
|
Some(bytes) => {
|
||||||
|
// `print!`, not `println!`: `bytes` is the content-addressed canonical
|
||||||
|
// form the store keyed `id` on — the CLI is a transport and must not
|
||||||
|
// frame the canonical bytes with a trailing newline (#164, mirroring
|
||||||
|
// `graph_construct::build_cmd`).
|
||||||
|
print!("{bytes}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
None => Err(format!("process {id} not found in the project store{}", prefix_hint(id))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn introspect_process(cmd: &DocIntrospectCmd) -> Result<(), String> {
|
fn introspect_process(cmd: &DocIntrospectCmd) -> Result<(), String> {
|
||||||
if cmd.metrics {
|
if cmd.metrics {
|
||||||
print_metric_roster();
|
print_metric_roster();
|
||||||
@@ -341,6 +359,8 @@ pub enum CampaignSub {
|
|||||||
/// List stored campaign realizations, or dump one campaign's records
|
/// List stored campaign realizations, or dump one campaign's records
|
||||||
/// (the bare store lines, not the run-emit wrapper).
|
/// (the bare store lines, not the run-emit wrapper).
|
||||||
Runs { campaign: Option<String>, run: Option<usize> },
|
Runs { campaign: Option<String>, run: Option<usize> },
|
||||||
|
/// Print a registered campaign document's canonical bytes to stdout.
|
||||||
|
Show { id: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The one authoring trap the referential tier can name outright: a ref that
|
/// The one authoring trap the referential tier can name outright: a ref that
|
||||||
@@ -460,6 +480,7 @@ pub fn campaign_cmd(cmd: CampaignCmd, env: &Env) {
|
|||||||
}
|
}
|
||||||
CampaignSub::Register { file } => register_campaign(file, env),
|
CampaignSub::Register { file } => register_campaign(file, env),
|
||||||
CampaignSub::Runs { campaign, run } => campaign_runs(campaign.as_deref(), *run, env),
|
CampaignSub::Runs { campaign, run } => campaign_runs(campaign.as_deref(), *run, env),
|
||||||
|
CampaignSub::Show { id } => show_campaign(id, env),
|
||||||
CampaignSub::Run { .. } => unreachable!("handled above"),
|
CampaignSub::Run { .. } => unreachable!("handled above"),
|
||||||
};
|
};
|
||||||
if let Err(m) = result {
|
if let Err(m) = result {
|
||||||
@@ -562,6 +583,19 @@ fn register_campaign(file: &PathBuf, env: &Env) -> Result<(), String> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn show_campaign(id: &str, env: &Env) -> Result<(), String> {
|
||||||
|
let registry = env.registry();
|
||||||
|
match registry.get_campaign(id).map_err(|e| e.to_string())? {
|
||||||
|
Some(bytes) => {
|
||||||
|
// `print!`, not `println!`: see `show_process` — the CLI must not
|
||||||
|
// frame the canonical bytes with a trailing newline (#164).
|
||||||
|
print!("{bytes}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
None => Err(format!("campaign {id} not found in the project store{}", prefix_hint(id))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn introspect_campaign(cmd: &DocIntrospectCmd) -> Result<(), String> {
|
fn introspect_campaign(cmd: &DocIntrospectCmd) -> Result<(), String> {
|
||||||
if cmd.metrics {
|
if cmd.metrics {
|
||||||
print_metric_roster();
|
print_metric_roster();
|
||||||
|
|||||||
@@ -3220,7 +3220,7 @@ fn walkforward_dissolved_refuses_a_multi_value_stop() {
|
|||||||
.expect("spawn aura");
|
.expect("spawn aura");
|
||||||
assert_eq!(out.status.code(), Some(2), "multi-value stop is a usage refusal");
|
assert_eq!(out.status.code(), Some(2), "multi-value stop is a usage refusal");
|
||||||
let stderr = String::from_utf8_lossy(&out.stderr);
|
let stderr = String::from_utf8_lossy(&out.stderr);
|
||||||
assert!(stderr.contains("single risk regime"), "stop-regime refusal: {stderr}");
|
assert!(stderr.contains("invalid value"), "stop-regime refusal: {stderr}");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// #215: --select plateau is no longer refused on the dissolved path — it now
|
/// #215: --select plateau is no longer refused on the dissolved path — it now
|
||||||
@@ -3647,6 +3647,96 @@ fn walkforward_stopless_defaults_the_regime_byte_identically_to_explicit_3_2() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// #227: bare `--select plateau` is the documented default aggregation
|
||||||
|
/// (`plateau:mean`), not a distinct spelling. Same dedup-pin idiom as
|
||||||
|
/// `walkforward_stopless_defaults_the_regime_byte_identically_to_explicit_3_2`:
|
||||||
|
/// running the same grid-sweep blueprint in the same store first with
|
||||||
|
/// `--select plateau`, then with `--select plateau:mean`, must produce
|
||||||
|
/// byte-identical campaign documents — content-addressed storage collapses
|
||||||
|
/// them onto EXACTLY ONE stored campaign — while each invocation still
|
||||||
|
/// records its own campaign-run line. Gated on the shared GER40 archive;
|
||||||
|
/// skips cleanly on a data refusal.
|
||||||
|
#[test]
|
||||||
|
fn walkforward_bare_plateau_generates_the_same_campaign_as_plateau_mean() {
|
||||||
|
const FROM_MS: &str = "1735689600000";
|
||||||
|
const TO_MS: &str = "1747353600000";
|
||||||
|
let (cwd, _g) = fresh_project();
|
||||||
|
let fixture = format!("{}/examples/r_sma.json", env!("CARGO_MANIFEST_DIR"));
|
||||||
|
|
||||||
|
// Bare `plateau`: today this is an unknown-token usage refusal (exit 2);
|
||||||
|
// the feature makes it an alias for `plateau:mean`.
|
||||||
|
let bare = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||||
|
.args([
|
||||||
|
"walkforward", &fixture, "--real", "GER40",
|
||||||
|
"--axis", "sma_signal.fast.length=3,5", "--axis", "sma_signal.slow.length=12,20",
|
||||||
|
"--stop-length", "14", "--stop-k", "2.0",
|
||||||
|
"--select", "plateau",
|
||||||
|
"--from", FROM_MS, "--to", TO_MS,
|
||||||
|
])
|
||||||
|
.current_dir(&cwd)
|
||||||
|
.output()
|
||||||
|
.expect("spawn aura");
|
||||||
|
if bare.status.code() == Some(1) {
|
||||||
|
let stderr = String::from_utf8_lossy(&bare.stderr);
|
||||||
|
assert!(
|
||||||
|
stderr.contains("no local data") || stderr.contains("no recorded geometry"),
|
||||||
|
"exit 1 must be a data refusal, got: {stderr}"
|
||||||
|
);
|
||||||
|
eprintln!("skip: no local GER40 data");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
bare.status.code(),
|
||||||
|
Some(0),
|
||||||
|
"bare --select plateau must no longer be refused (it aliases plateau:mean): {}",
|
||||||
|
String::from_utf8_lossy(&bare.stderr)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Explicit `plateau:mean`: names the very aggregation the bare spelling
|
||||||
|
// resolves to. Same blueprint, same axes, same window, same stop.
|
||||||
|
let explicit = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||||
|
.args([
|
||||||
|
"walkforward", &fixture, "--real", "GER40",
|
||||||
|
"--axis", "sma_signal.fast.length=3,5", "--axis", "sma_signal.slow.length=12,20",
|
||||||
|
"--stop-length", "14", "--stop-k", "2.0",
|
||||||
|
"--select", "plateau:mean",
|
||||||
|
"--from", FROM_MS, "--to", TO_MS,
|
||||||
|
])
|
||||||
|
.current_dir(&cwd)
|
||||||
|
.output()
|
||||||
|
.expect("spawn aura");
|
||||||
|
assert_eq!(
|
||||||
|
explicit.status.code(),
|
||||||
|
Some(0),
|
||||||
|
"the explicit --select plateau:mean walkforward must succeed: {}",
|
||||||
|
String::from_utf8_lossy(&explicit.stderr)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Byte-identity: bare `plateau` IS `plateau:mean`, so both invocations
|
||||||
|
// generate the same campaign document and content-addressed storage
|
||||||
|
// collapses them onto one content id — exactly one campaign document in
|
||||||
|
// the store, but two independent campaign-run lines.
|
||||||
|
let count = |sub: &str| {
|
||||||
|
std::fs::read_dir(cwd.join("runs").join(sub))
|
||||||
|
.map(|d| d.count())
|
||||||
|
.unwrap_or(0)
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
count("campaigns"),
|
||||||
|
1,
|
||||||
|
"bare plateau binds the SAME aggregation as plateau:mean, so the two campaign \
|
||||||
|
documents are byte-identical and dedup to one content id"
|
||||||
|
);
|
||||||
|
let runs_log = std::fs::read_to_string(cwd.join("runs").join("campaign_runs.jsonl"))
|
||||||
|
.expect("campaign_runs.jsonl exists after two sugar runs");
|
||||||
|
assert_eq!(
|
||||||
|
runs_log.lines().count(),
|
||||||
|
2,
|
||||||
|
"each invocation still records its own campaign run (a run is an event, a document \
|
||||||
|
is content)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Property: `aura generalize` is now thin sugar over the one campaign path — a
|
/// Property: `aura generalize` is now thin sugar over the one campaign path — a
|
||||||
/// successful run durably auto-registers exactly one generated process document,
|
/// successful run durably auto-registers exactly one generated process document,
|
||||||
/// one generated campaign document (carrying the `--name` handle and the stop as
|
/// one generated campaign document (carrying the `--name` handle and the stop as
|
||||||
@@ -5665,7 +5755,7 @@ fn mc_dissolved_refuses_a_multi_value_stop() {
|
|||||||
.expect("spawn aura");
|
.expect("spawn aura");
|
||||||
assert_eq!(out.status.code(), Some(2), "multi-value stop is a usage refusal");
|
assert_eq!(out.status.code(), Some(2), "multi-value stop is a usage refusal");
|
||||||
let stderr = String::from_utf8_lossy(&out.stderr);
|
let stderr = String::from_utf8_lossy(&out.stderr);
|
||||||
assert!(stderr.contains("single risk regime"), "stop-regime refusal: {stderr}");
|
assert!(stderr.contains("invalid value"), "stop-regime refusal: {stderr}");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Property: an `--axis` name that is not among the loaded blueprint's sweepable
|
/// Property: an `--axis` name that is not among the loaded blueprint's sweepable
|
||||||
@@ -6286,3 +6376,334 @@ fn data_list_enumerates_archive_symbols_one_per_line_sorted() {
|
|||||||
// scoping an instrument matrix.
|
// scoping an instrument matrix.
|
||||||
assert_eq!(stdout, "SYMA\nSYMB\n", "sorted symbol enumeration: {stdout:?}");
|
assert_eq!(stdout, "SYMA\nSYMB\n", "sorted symbol enumeration: {stdout:?}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Property (#300): `aura campaign show <CID>` prints the registered
|
||||||
|
/// document's exact canonical bytes to stdout — round-tripping them back
|
||||||
|
/// through `aura campaign register` re-derives the SAME content id (canonical
|
||||||
|
/// bytes are content-id-identical), and the store stays deduplicated at one
|
||||||
|
/// document. Gated on the shared GER40 archive; skips cleanly on a data
|
||||||
|
/// refusal (same guard shape as
|
||||||
|
/// `walkforward_stopless_defaults_the_regime_byte_identically_to_explicit_3_2`).
|
||||||
|
#[test]
|
||||||
|
fn campaign_show_round_trips_the_registered_document() {
|
||||||
|
const FROM_MS: &str = "1735689600000";
|
||||||
|
const TO_MS: &str = "1767225599000";
|
||||||
|
let (cwd, _g) = fresh_project();
|
||||||
|
let fixture = format!("{}/examples/r_sma.json", env!("CARGO_MANIFEST_DIR"));
|
||||||
|
|
||||||
|
let run = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||||
|
.args([
|
||||||
|
"walkforward", &fixture, "--real", "GER40",
|
||||||
|
"--axis", "sma_signal.fast.length=3", "--axis", "sma_signal.slow.length=12",
|
||||||
|
"--select", "plateau:mean",
|
||||||
|
"--from", FROM_MS, "--to", TO_MS,
|
||||||
|
])
|
||||||
|
.current_dir(&cwd)
|
||||||
|
.output()
|
||||||
|
.expect("spawn aura");
|
||||||
|
if run.status.code() == Some(1) {
|
||||||
|
let stderr = String::from_utf8_lossy(&run.stderr);
|
||||||
|
assert!(
|
||||||
|
stderr.contains("no local data") || stderr.contains("no recorded geometry"),
|
||||||
|
"exit 1 must be a data refusal, got: {stderr}"
|
||||||
|
);
|
||||||
|
eprintln!("skip: no local GER40 data");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
run.status.code(),
|
||||||
|
Some(0),
|
||||||
|
"walkforward must succeed: {}",
|
||||||
|
String::from_utf8_lossy(&run.stderr)
|
||||||
|
);
|
||||||
|
|
||||||
|
let campaigns_dir = cwd.join("runs").join("campaigns");
|
||||||
|
let cid = std::fs::read_dir(&campaigns_dir)
|
||||||
|
.expect("campaigns dir exists")
|
||||||
|
.next()
|
||||||
|
.expect("exactly one campaign document")
|
||||||
|
.expect("readable dir entry")
|
||||||
|
.path()
|
||||||
|
.file_stem()
|
||||||
|
.expect("campaign filename has a stem")
|
||||||
|
.to_string_lossy()
|
||||||
|
.into_owned();
|
||||||
|
|
||||||
|
let show = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||||
|
.args(["campaign", "show", &cid])
|
||||||
|
.current_dir(&cwd)
|
||||||
|
.output()
|
||||||
|
.expect("spawn aura campaign show");
|
||||||
|
assert_eq!(
|
||||||
|
show.status.code(),
|
||||||
|
Some(0),
|
||||||
|
"campaign show must succeed: {}",
|
||||||
|
String::from_utf8_lossy(&show.stderr)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Directly pin the #164 no-framing invariant: `show` must print the
|
||||||
|
// stored canonical bytes byte-for-byte, with no added trailing newline
|
||||||
|
// (a `println!` regression in `show_campaign` would still pass the
|
||||||
|
// re-register check below via `campaign_to_json` -> `content_id_of`
|
||||||
|
// stripping it back out before hashing, so this comparison is the only
|
||||||
|
// thing that would catch it).
|
||||||
|
let stored_bytes = std::fs::read(campaigns_dir.join(format!("{cid}.json"))).expect("read stored campaign document");
|
||||||
|
assert_eq!(
|
||||||
|
show.stdout, stored_bytes,
|
||||||
|
"campaign show must print the stored canonical bytes byte-for-byte, no framing"
|
||||||
|
);
|
||||||
|
|
||||||
|
let roundtrip_path = cwd.join("roundtrip.campaign.json");
|
||||||
|
std::fs::write(&roundtrip_path, &show.stdout).expect("write roundtrip campaign doc");
|
||||||
|
|
||||||
|
let register = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||||
|
.args(["campaign", "register", roundtrip_path.to_str().expect("utf-8 path")])
|
||||||
|
.current_dir(&cwd)
|
||||||
|
.output()
|
||||||
|
.expect("spawn aura campaign register");
|
||||||
|
assert_eq!(
|
||||||
|
register.status.code(),
|
||||||
|
Some(0),
|
||||||
|
"re-registering the shown document must succeed: {}",
|
||||||
|
String::from_utf8_lossy(®ister.stderr)
|
||||||
|
);
|
||||||
|
let register_out = String::from_utf8_lossy(®ister.stdout).into_owned();
|
||||||
|
assert!(
|
||||||
|
register_out.contains(&format!("registered campaign {cid}")),
|
||||||
|
"canonical bytes must re-register content-id-identically to {cid}: {register_out}"
|
||||||
|
);
|
||||||
|
|
||||||
|
let count = std::fs::read_dir(&campaigns_dir).expect("campaigns dir exists").count();
|
||||||
|
assert_eq!(count, 1, "the shown bytes dedup onto the same one stored document");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Property (#300): identical to `campaign_show_round_trips_the_registered_document`
|
||||||
|
/// but over the generated process document — `aura process show <PID>` prints
|
||||||
|
/// canonical bytes that re-register content-id-identically through `aura
|
||||||
|
/// process register`, with the store staying deduplicated at one document.
|
||||||
|
#[test]
|
||||||
|
fn process_show_round_trips_the_registered_document() {
|
||||||
|
const FROM_MS: &str = "1735689600000";
|
||||||
|
const TO_MS: &str = "1767225599000";
|
||||||
|
let (cwd, _g) = fresh_project();
|
||||||
|
let fixture = format!("{}/examples/r_sma.json", env!("CARGO_MANIFEST_DIR"));
|
||||||
|
|
||||||
|
let run = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||||
|
.args([
|
||||||
|
"walkforward", &fixture, "--real", "GER40",
|
||||||
|
"--axis", "sma_signal.fast.length=3", "--axis", "sma_signal.slow.length=12",
|
||||||
|
"--select", "plateau:mean",
|
||||||
|
"--from", FROM_MS, "--to", TO_MS,
|
||||||
|
])
|
||||||
|
.current_dir(&cwd)
|
||||||
|
.output()
|
||||||
|
.expect("spawn aura");
|
||||||
|
if run.status.code() == Some(1) {
|
||||||
|
let stderr = String::from_utf8_lossy(&run.stderr);
|
||||||
|
assert!(
|
||||||
|
stderr.contains("no local data") || stderr.contains("no recorded geometry"),
|
||||||
|
"exit 1 must be a data refusal, got: {stderr}"
|
||||||
|
);
|
||||||
|
eprintln!("skip: no local GER40 data");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
run.status.code(),
|
||||||
|
Some(0),
|
||||||
|
"walkforward must succeed: {}",
|
||||||
|
String::from_utf8_lossy(&run.stderr)
|
||||||
|
);
|
||||||
|
|
||||||
|
let processes_dir = cwd.join("runs").join("processes");
|
||||||
|
let pid = std::fs::read_dir(&processes_dir)
|
||||||
|
.expect("processes dir exists")
|
||||||
|
.next()
|
||||||
|
.expect("exactly one process document")
|
||||||
|
.expect("readable dir entry")
|
||||||
|
.path()
|
||||||
|
.file_stem()
|
||||||
|
.expect("process filename has a stem")
|
||||||
|
.to_string_lossy()
|
||||||
|
.into_owned();
|
||||||
|
|
||||||
|
let show = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||||
|
.args(["process", "show", &pid])
|
||||||
|
.current_dir(&cwd)
|
||||||
|
.output()
|
||||||
|
.expect("spawn aura process show");
|
||||||
|
assert_eq!(
|
||||||
|
show.status.code(),
|
||||||
|
Some(0),
|
||||||
|
"process show must succeed: {}",
|
||||||
|
String::from_utf8_lossy(&show.stderr)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Directly pin the #164 no-framing invariant (see the sibling campaign
|
||||||
|
// test's comment): `show` must print the stored canonical bytes
|
||||||
|
// byte-for-byte, with no added trailing newline.
|
||||||
|
let stored_bytes = std::fs::read(processes_dir.join(format!("{pid}.json"))).expect("read stored process document");
|
||||||
|
assert_eq!(
|
||||||
|
show.stdout, stored_bytes,
|
||||||
|
"process show must print the stored canonical bytes byte-for-byte, no framing"
|
||||||
|
);
|
||||||
|
|
||||||
|
let roundtrip_path = cwd.join("roundtrip.process.json");
|
||||||
|
std::fs::write(&roundtrip_path, &show.stdout).expect("write roundtrip process doc");
|
||||||
|
|
||||||
|
let register = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||||
|
.args(["process", "register", roundtrip_path.to_str().expect("utf-8 path")])
|
||||||
|
.current_dir(&cwd)
|
||||||
|
.output()
|
||||||
|
.expect("spawn aura process register");
|
||||||
|
assert_eq!(
|
||||||
|
register.status.code(),
|
||||||
|
Some(0),
|
||||||
|
"re-registering the shown document must succeed: {}",
|
||||||
|
String::from_utf8_lossy(®ister.stderr)
|
||||||
|
);
|
||||||
|
let register_out = String::from_utf8_lossy(®ister.stdout).into_owned();
|
||||||
|
assert!(
|
||||||
|
register_out.contains(&format!("registered process {pid}")),
|
||||||
|
"canonical bytes must re-register content-id-identically to {pid}: {register_out}"
|
||||||
|
);
|
||||||
|
|
||||||
|
let count = std::fs::read_dir(&processes_dir).expect("processes dir exists").count();
|
||||||
|
assert_eq!(count, 1, "the shown bytes dedup onto the same one stored document");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Property (#300): `show` over an unknown id refuses with the same
|
||||||
|
/// project-store-not-found prose the referential tier already uses for a
|
||||||
|
/// missing `process.ref`/`strategy.ref` (`ref_fault_prose`'s
|
||||||
|
/// `ProcessNotFound`/campaign-equivalent hint shape) — no project run needed,
|
||||||
|
/// so this test needs no archive gate.
|
||||||
|
#[test]
|
||||||
|
fn show_refuses_an_unknown_id_with_the_store_hint() {
|
||||||
|
let (cwd, _g) = fresh_project();
|
||||||
|
|
||||||
|
let campaign_show = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||||
|
.args(["campaign", "show", "deadbeef"])
|
||||||
|
.current_dir(&cwd)
|
||||||
|
.output()
|
||||||
|
.expect("spawn aura campaign show");
|
||||||
|
assert_eq!(campaign_show.status.code(), Some(1), "unknown campaign id is a runtime refusal");
|
||||||
|
let campaign_stderr = String::from_utf8_lossy(&campaign_show.stderr);
|
||||||
|
assert!(
|
||||||
|
campaign_stderr.contains("campaign deadbeef not found in the project store"),
|
||||||
|
"campaign show stderr: {campaign_stderr}"
|
||||||
|
);
|
||||||
|
|
||||||
|
let process_show = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||||
|
.args(["process", "show", "deadbeef"])
|
||||||
|
.current_dir(&cwd)
|
||||||
|
.output()
|
||||||
|
.expect("spawn aura process show");
|
||||||
|
assert_eq!(process_show.status.code(), Some(1), "unknown process id is a runtime refusal");
|
||||||
|
let process_stderr = String::from_utf8_lossy(&process_show.stderr);
|
||||||
|
assert!(
|
||||||
|
process_stderr.contains("process deadbeef not found in the project store"),
|
||||||
|
"process show stderr: {process_stderr}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Property (#300): `show`'s not-found prose reuses the existing `prefix_hint`
|
||||||
|
/// authoring-trap detector (#194) — an id pasted WITH the display-only
|
||||||
|
/// `content:` prefix (the form `ref_fault_prose` already prints elsewhere for
|
||||||
|
/// an unresolved `process.ref`/`strategy.ref`) still gets the "drop the
|
||||||
|
/// 'content:' prefix" hint through the new `show` code path, not a bare
|
||||||
|
/// not-found. This is the one authoring mistake the store can name outright,
|
||||||
|
/// and it must survive `show` reusing `ref_fault_prose`'s helper rather than
|
||||||
|
/// re-deriving its own prose.
|
||||||
|
#[test]
|
||||||
|
fn show_hints_the_content_prefix_authoring_mistake() {
|
||||||
|
let (cwd, _g) = fresh_project();
|
||||||
|
|
||||||
|
let campaign_show = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||||
|
.args(["campaign", "show", "content:deadbeef"])
|
||||||
|
.current_dir(&cwd)
|
||||||
|
.output()
|
||||||
|
.expect("spawn aura campaign show");
|
||||||
|
assert_eq!(campaign_show.status.code(), Some(1), "unresolvable id is a runtime refusal");
|
||||||
|
let campaign_stderr = String::from_utf8_lossy(&campaign_show.stderr);
|
||||||
|
assert!(
|
||||||
|
campaign_stderr.contains("drop the 'content:' prefix"),
|
||||||
|
"campaign show must hint the content: authoring mistake: {campaign_stderr}"
|
||||||
|
);
|
||||||
|
|
||||||
|
let process_show = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||||
|
.args(["process", "show", "content:deadbeef"])
|
||||||
|
.current_dir(&cwd)
|
||||||
|
.output()
|
||||||
|
.expect("spawn aura process show");
|
||||||
|
assert_eq!(process_show.status.code(), Some(1), "unresolvable id is a runtime refusal");
|
||||||
|
let process_stderr = String::from_utf8_lossy(&process_show.stderr);
|
||||||
|
assert!(
|
||||||
|
process_stderr.contains("drop the 'content:' prefix"),
|
||||||
|
"process show must hint the content: authoring mistake: {process_stderr}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Property (#300): `show` is scoped to its own document-kind store — a
|
||||||
|
/// content id that IS a real, registered document in one store (here: a
|
||||||
|
/// minimal sweep-only process) does not resolve through the OTHER verb
|
||||||
|
/// (`campaign show`). `show_process`/`show_campaign` are two near-identical
|
||||||
|
/// functions written in the same change (both `registry.get_*(id) -> Option
|
||||||
|
/// -> print! the bytes or format! a not-found`); this pins that they read
|
||||||
|
/// from their own store method and neither accidentally shares the other's
|
||||||
|
/// namespace or falls back to a generic content-blob lookup.
|
||||||
|
#[test]
|
||||||
|
fn show_is_scoped_to_its_own_document_store() {
|
||||||
|
let (cwd, _g) = fresh_project();
|
||||||
|
const SWEEP_ONLY_PROCESS: &str = r#"{"format_version":1,"kind":"process","name":"knob-sweep-only","pipeline":[{"block":"std::sweep","metric":"sqn_normalized","select":"argmax"}]}"#;
|
||||||
|
std::fs::write(cwd.join("knob.process.json"), SWEEP_ONLY_PROCESS).expect("write process doc");
|
||||||
|
|
||||||
|
let register = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||||
|
.args(["process", "register", "knob.process.json"])
|
||||||
|
.current_dir(&cwd)
|
||||||
|
.output()
|
||||||
|
.expect("spawn aura process register");
|
||||||
|
assert_eq!(
|
||||||
|
register.status.code(),
|
||||||
|
Some(0),
|
||||||
|
"process register must succeed: {}",
|
||||||
|
String::from_utf8_lossy(®ister.stderr)
|
||||||
|
);
|
||||||
|
let register_out = String::from_utf8_lossy(®ister.stdout).into_owned();
|
||||||
|
let pid = register_out
|
||||||
|
.lines()
|
||||||
|
.find(|l| l.starts_with("registered process "))
|
||||||
|
.expect("register line")
|
||||||
|
.trim_start_matches("registered process ")
|
||||||
|
.split(' ')
|
||||||
|
.next()
|
||||||
|
.expect("process id")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let process_show = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||||
|
.args(["process", "show", &pid])
|
||||||
|
.current_dir(&cwd)
|
||||||
|
.output()
|
||||||
|
.expect("spawn aura process show");
|
||||||
|
assert_eq!(
|
||||||
|
process_show.status.code(),
|
||||||
|
Some(0),
|
||||||
|
"process show must find the just-registered process: {}",
|
||||||
|
String::from_utf8_lossy(&process_show.stderr)
|
||||||
|
);
|
||||||
|
|
||||||
|
let campaign_show = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
|
||||||
|
.args(["campaign", "show", &pid])
|
||||||
|
.current_dir(&cwd)
|
||||||
|
.output()
|
||||||
|
.expect("spawn aura campaign show");
|
||||||
|
assert_eq!(
|
||||||
|
campaign_show.status.code(),
|
||||||
|
Some(1),
|
||||||
|
"a process id must NOT resolve through campaign show: {}",
|
||||||
|
String::from_utf8_lossy(&campaign_show.stdout)
|
||||||
|
);
|
||||||
|
let campaign_stderr = String::from_utf8_lossy(&campaign_show.stderr);
|
||||||
|
assert!(
|
||||||
|
campaign_stderr.contains(&format!("campaign {pid} not found in the project store")),
|
||||||
|
"campaign show stderr: {campaign_stderr}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -40,6 +40,9 @@ use crate::member::{
|
|||||||
use crate::project::Env;
|
use crate::project::Env;
|
||||||
use crate::translate::{R_SMA_STOP_K, R_SMA_STOP_LENGTH};
|
use crate::translate::{R_SMA_STOP_K, R_SMA_STOP_LENGTH};
|
||||||
|
|
||||||
|
/// The demo default stop regime, shared by every family builder in this module.
|
||||||
|
const DEFAULT_STOP: StopRule = StopRule::Vol { length: R_SMA_STOP_LENGTH, k: R_SMA_STOP_K };
|
||||||
|
|
||||||
/// The in-sample winner-selection objective for walk-forward's per-window IS
|
/// The in-sample winner-selection objective for walk-forward's per-window IS
|
||||||
/// refit (cycle 0077). `Argmax` is the bare-best pick deflated for trials
|
/// refit (cycle 0077). `Argmax` is the bare-best pick deflated for trials
|
||||||
/// (#144, the default); `Plateau` argmaxes the neighbourhood-smoothed surface
|
/// (#144, the default); `Plateau` argmaxes the neighbourhood-smoothed surface
|
||||||
@@ -427,7 +430,7 @@ pub fn blueprint_sweep_family(
|
|||||||
.sweep(|point| {
|
.sweep(|point| {
|
||||||
// fresh per-member graph (Composite is !Clone, reload per member) run through
|
// fresh per-member graph (Composite is !Clone, reload per member) run through
|
||||||
// the shared reduce-mode member path — the same fn reproduction re-runs.
|
// the shared reduce-mode member path — the same fn reproduction re-runs.
|
||||||
run_blueprint_member(reload(doc), point, &space, data.run_sources(env, &binding.columns()), window, 0, pip, &topo, env, StopRule::Vol { length: R_SMA_STOP_LENGTH, k: R_SMA_STOP_K }, &binding, &[], NO_INSTRUMENT_CONTEXT)
|
run_blueprint_member(reload(doc), point, &space, data.run_sources(env, &binding.columns()), window, 0, pip, &topo, env, DEFAULT_STOP, &binding, &[], NO_INSTRUMENT_CONTEXT)
|
||||||
})
|
})
|
||||||
// render the sweep terminal's BindError to prose (#247), the fn's String error
|
// render the sweep terminal's BindError to prose (#247), the fn's String error
|
||||||
// contract — never the raw Debug struct.
|
// contract — never the raw Debug struct.
|
||||||
@@ -476,7 +479,7 @@ pub fn blueprint_sweep_over(
|
|||||||
binder.sweep_with_lattice(|point| {
|
binder.sweep_with_lattice(|point| {
|
||||||
let sources = data.windowed_sources(from, to, env, &binding.columns());
|
let sources = data.windowed_sources(from, to, env, &binding.columns());
|
||||||
let window = window_of(&sources).expect("non-empty in-sample window");
|
let window = window_of(&sources).expect("non-empty in-sample window");
|
||||||
run_blueprint_member(reopen_all(reload(doc), &overrides), point, &space, sources, window, 0, pip, &topo, env, StopRule::Vol { length: R_SMA_STOP_LENGTH, k: R_SMA_STOP_K }, binding, &[], NO_INSTRUMENT_CONTEXT)
|
run_blueprint_member(reopen_all(reload(doc), &overrides), point, &space, sources, window, 0, pip, &topo, env, DEFAULT_STOP, binding, &[], NO_INSTRUMENT_CONTEXT)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -502,7 +505,7 @@ pub fn run_oos_blueprint(
|
|||||||
let pip = data.pip_size();
|
let pip = data.pip_size();
|
||||||
let sources = data.windowed_sources(from, to, env, &binding.columns());
|
let sources = data.windowed_sources(from, to, env, &binding.columns());
|
||||||
let window = window_of(&sources).expect("non-empty out-of-sample window");
|
let window = window_of(&sources).expect("non-empty out-of-sample window");
|
||||||
let report = run_blueprint_member(reload, params, space, sources, window, 0, pip, topo, env, StopRule::Vol { length: R_SMA_STOP_LENGTH, k: R_SMA_STOP_K }, binding, &[], NO_INSTRUMENT_CONTEXT);
|
let report = run_blueprint_member(reload, params, space, sources, window, 0, pip, topo, env, DEFAULT_STOP, binding, &[], NO_INSTRUMENT_CONTEXT);
|
||||||
(Vec::new(), report)
|
(Vec::new(), report)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -642,7 +645,7 @@ pub fn blueprint_mc_family(
|
|||||||
let family = monte_carlo(&base_point, &seeds, |seed, _base| {
|
let family = monte_carlo(&base_point, &seeds, |seed, _base| {
|
||||||
let sources = synthetic_walk_sources(seed);
|
let sources = synthetic_walk_sources(seed);
|
||||||
let window = window_of(&sources).expect("non-empty synthetic walk");
|
let window = window_of(&sources).expect("non-empty synthetic walk");
|
||||||
run_blueprint_member(reload(doc), &[], &space, sources, window, seed, pip, &topo, env, StopRule::Vol { length: R_SMA_STOP_LENGTH, k: R_SMA_STOP_K }, &binding, &[], NO_INSTRUMENT_CONTEXT)
|
run_blueprint_member(reload(doc), &[], &space, sources, window, seed, pip, &topo, env, DEFAULT_STOP, &binding, &[], NO_INSTRUMENT_CONTEXT)
|
||||||
});
|
});
|
||||||
// Silent-vacuous MC guard (refuse-don't-guess, C10): with >= 2 seeds, if every draw's
|
// Silent-vacuous MC guard (refuse-don't-guess, C10): with >= 2 seeds, if every draw's
|
||||||
// metrics are bit-identical to the first, no seed reached a distinguishable realization —
|
// metrics are bit-identical to the first, no seed reached a distinguishable realization —
|
||||||
|
|||||||
+11
-3
@@ -1598,6 +1598,10 @@ by design (the 0106 fieldtest corpus stays as the historical record). Known debt
|
|||||||
metric-roster triplication (still hand-maintained, but drift from the shipped
|
metric-roster triplication (still hand-maintained, but drift from the shipped
|
||||||
`aura-analysis` types is now test-caught by a cross-crate guard, #190;
|
`aura-analysis` types is now test-caught by a cross-crate guard, #190;
|
||||||
single-source removal waits on #147), deflation-constant duplication (#199).
|
single-source removal waits on #147), deflation-constant duplication (#199).
|
||||||
|
#300 (2026-07-21) closed the store's read loop: `aura process|campaign show
|
||||||
|
<content-id>` prints a registered document's canonical bytes — the
|
||||||
|
generate → retrieve → hand-extend → re-register cycle needs no direct store
|
||||||
|
filesystem access.
|
||||||
**Realization (cycle 0108, #200 — the annotator stages execute).** The v2 executable
|
**Realization (cycle 0108, #200 — the annotator stages execute).** The v2 executable
|
||||||
shape is `std::sweep [std::gate]* [std::walk_forward]? [std::monte_carlo]?
|
shape is `std::sweep [std::gate]* [std::walk_forward]? [std::monte_carlo]?
|
||||||
[std::generalize]?` — an ordered optional annotator suffix, each at most once,
|
[std::generalize]?` — an ordered optional annotator suffix, each at most once,
|
||||||
@@ -2526,9 +2530,13 @@ vocabulary (blueprints, process/campaign documents, registry records) is the
|
|||||||
canonical layer, and every control surface — the one-shot CLI's executor
|
canonical layer, and every control surface — the one-shot CLI's executor
|
||||||
verbs, any future long-running host, any MCP face, a World program — is a
|
verbs, any future long-running host, any MCP face, a World program — is a
|
||||||
projection/executor over those artifacts, never a second home for intent.
|
projection/executor over those artifacts, never a second home for intent.
|
||||||
Which projection is built next (document-first flag-vocabulary completion, a
|
The owner resolved the direction 2026-07-21 (minuted on #295): **document-first
|
||||||
typed-protocol host, an MCP face, or none until demand) is deliberately open
|
completion first** — delivered by #300: the executor verb set is settled
|
||||||
— recorded as an open direction fork on #295.
|
(`run`, the four thin per-verb generators, and the document verbs
|
||||||
|
validate/introspect/register/show/run/runs, `show` being #300's read-back
|
||||||
|
addition), the verbs' per-verb identity re-ratified (#300 F8 — reduction, not
|
||||||
|
grammar collapse), and a typed-protocol host or MCP face remains demand-driven,
|
||||||
|
unbuilt.
|
||||||
|
|
||||||
### C26 — Harness input binding: role names bind archive columns
|
### C26 — Harness input binding: role names bind archive columns
|
||||||
**Guarantee.** A strategy blueprint declares WHICH data it consumes as the NAMES
|
**Guarantee.** A strategy blueprint declares WHICH data it consumes as the NAMES
|
||||||
|
|||||||
Reference in New Issue
Block a user