feat(aura-cli, aura-engine): graph introspect --taps — positive declared-tap discovery
Harvest sweep, batch 3 of 6 (first half). Composite::declared_taps() walks the blueprint depth-first collecting (tap name, source wire, column kind), names bare at every depth — mirroring inline_composite's hoist, where a tap's name IS the flat graph's load-bearing identifier — and bounds-total over invalid wires (compile's validate_wiring stays the real gate). The CLI view loads like --params (FILE gates the authored root name, a content id does not), prints one 'name node.field Kind' row per tap, and renders the tap-less case as a stderr note with empty stdout, exit 0. Closes the recovery-only discovery loop: since #333 the only way to learn a blueprint's declared tap names was provoking the undeclared-tap refusal roster. closes #337
This commit is contained in:
@@ -832,6 +832,54 @@ fn graph_params_tolerates_content_prefix_on_target() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Property (#337): `--taps <FILE>` is the positive declared-tap discovery
|
||||
/// view — before this, the only way to learn a blueprint's declared tap names
|
||||
/// was provoking #333's undeclared-tap refusal roster. One row per declared
|
||||
/// tap: name, source wire (`node.field`), column kind, in declaration order.
|
||||
#[test]
|
||||
fn graph_introspect_taps_lists_declared_taps_wire_and_kind() {
|
||||
let dir = temp_cwd("taps-listed");
|
||||
let ops = r#"[
|
||||
{"op":"source","role":"price","kind":"F64"},
|
||||
{"op":"add","type":"SMA","name":"fast","bind":{"length":{"I64":2}}},
|
||||
{"op":"add","type":"SMA","name":"slow","bind":{"length":{"I64":4}}},
|
||||
{"op":"add","type":"Sub"},
|
||||
{"op":"feed","role":"price","into":["fast.series","slow.series"]},
|
||||
{"op":"connect","from":"fast.value","to":"sub.lhs"},
|
||||
{"op":"connect","from":"slow.value","to":"sub.rhs"},
|
||||
{"op":"tap","from":"fast.value","as":"fast_ma"},
|
||||
{"op":"tap","from":"sub.value","as":"spread"},
|
||||
{"op":"expose","from":"sub.value","as":"bias"}
|
||||
]"#;
|
||||
let (build_out, build_err, build_code) = run_in_stdin(&dir, &["graph", "build"], ops);
|
||||
assert_eq!(build_code, Some(0), "graph build: {build_out} {build_err}");
|
||||
let bp = dir.join("tapped.json");
|
||||
std::fs::write(&bp, &build_out).expect("write built blueprint");
|
||||
let (stdout, stderr, code) =
|
||||
run_in(&dir, &["graph", "introspect", "--taps", bp.to_str().unwrap()]);
|
||||
assert_eq!(code, Some(0), "stdout: {stdout} stderr: {stderr}");
|
||||
assert_eq!(
|
||||
stdout, "fast_ma fast.value F64\nspread sub.value F64\n",
|
||||
"one row per declared tap: name, source wire, column kind"
|
||||
);
|
||||
}
|
||||
|
||||
/// Property (#337): a blueprint with no declared taps prints nothing to
|
||||
/// stdout (nothing was declared to list) plus a one-line stderr note — a
|
||||
/// listing, not a fault, exit 0.
|
||||
#[test]
|
||||
fn graph_introspect_taps_reports_no_taps_on_stderr_when_none_declared() {
|
||||
let dir = temp_cwd("taps-empty");
|
||||
let bp = fixture("r_sma_open.json");
|
||||
let (stdout, stderr, code) = run_in(&dir, &["graph", "introspect", "--taps", &bp]);
|
||||
assert_eq!(code, Some(0), "a tap-less blueprint is a listing, not a fault: {stderr}");
|
||||
assert_eq!(stdout, "", "no declared taps to list: {stdout}");
|
||||
assert!(
|
||||
stderr.contains("declares no taps"),
|
||||
"stderr carries the one-line note: {stderr}"
|
||||
);
|
||||
}
|
||||
|
||||
/// Property (#196, negative): `--params <ID>` with a well-shaped 64-hex id
|
||||
/// that was never registered refuses with a store-miss message, not a panic
|
||||
/// or a silent empty namespace — the registry lookup branch of
|
||||
|
||||
Reference in New Issue
Block a user