829a1984e6
The binary is the only always-present teacher (C29); the 2026-07-22 external field test showed the help teaching nothing but the verb inventory. This cycle makes every closed vocabulary the binary ships listable with a one-line meaning, and opens the help with the concepts the field agent had to discover forensically. #315 — help + introspection: - `aura --help` opens with the two-layer concepts paragraph (research verbs as sugar over registered process/campaign documents; the directly authorable data plane; bias-as-target-position + protective-stop execution model in R; how traces come to exist). - Each document-bridged sugar verb's long help names the process shape it desugars to (sweep -> std::sweep; walkforward -> [std::grid, std::walk_forward]; mc -> + std::monte_carlo; generalize -> [std::sweep (selection), std::generalize]). `run` is not verb_sugar-bridged, so its help points at the canonical document-first form instead of claiming a desugaring it does not perform. - `graph introspect --vocabulary` lists each node type with its schema doc (bare names taught nothing); `--node <T>`'s head line carries the meaning. - `graph introspect --folds` (new): the tap-fold vocabulary with bind rule, output kind, and meaning — it existed only as source comments. The rows live in aura-std (`fold_vocabulary`), unit-pinned against `fold_binds_at`/`fold_output_kind` so the prose cannot drift from the executable rules, and doc_gate-checked (C29 entry seam). The discovery surface is graph introspect because folds bind at graph-declared taps (C27); the document layer still refuses a folds slot pending #310. - `process introspect --metrics` lines carry each metric's meaning (the doc column existed since #316 but was never printed). - `campaign introspect --block std::presentation` lists the four persisted taps' meanings under the persist_taps slot. - The scaffolded project CLAUDE.md teaches the execution semantics (bias as held target, protective stop, R) and the document data plane. #323 — C29 authoring surfaces: - `introspect --unwired` enumerates the optional `description` envelope slot for both document kinds (absent-only, like risk/cost); the campaign lib fixture gains a description, and the risk/cost-bound e2e binds description too so "no open slots" keeps meaning complete. - `graph build --help` carries the op-list reference: all nine op kinds with fields and a worked element each (OP_REFERENCE lives beside OpDoc so a new variant is one screen from its help line). - The authoring guide's S0 worked literal is byte-pinned to the scaffold's LIB_RS template (rendered with the guide's my_lab namespace); the scaffold e2es compile that template for real, so the pin is a transitive compile guard. The template gains the guide's C29 comment, the guide gains the template's rename note — one worked example, two surfaces, zero drift. Deliberately out of scope (ratified #319, 2026-07-24): no doc-strings for the quintet's 48 flag fields — the sugar surface retires; only the pointer lines above bridge toward the document layer. Verification: full workspace suite green, clippy -D warnings clean, aura-bench exit 0 with all fingerprints OK (help_ms +1.6% — the longer help text, in tolerance), cargo doc warning count unchanged (7 pre-existing). 9 new e2e pins in tests/help_self_description.rs; guide + CLAUDE.md pins in scaffold unit tests; fold-vocabulary rules + doc_gate unit-pinned in aura-std. closes #315 closes #323
166 lines
7.5 KiB
Rust
166 lines
7.5 KiB
Rust
//! End-to-end pins for the self-description surfaces (#315, #323): the help
|
|
//! opens with the two-layer concept, the sugar verbs name the document shape
|
|
//! they desugar to, every introspection roster carries per-entry meanings,
|
|
//! and `graph build --help` carries the op-list reference. Driven over the
|
|
//! built binary — the zero-setup surface a reader without repo access gets.
|
|
|
|
use std::process::Command;
|
|
|
|
const BIN: &str = env!("CARGO_BIN_EXE_aura");
|
|
|
|
/// Run `aura <args>` (no stdin); return (stdout, stderr, success).
|
|
fn run(args: &[&str]) -> (String, String, bool) {
|
|
let out = Command::new(BIN).args(args).output().expect("spawn aura");
|
|
(
|
|
String::from_utf8_lossy(&out.stdout).into_owned(),
|
|
String::from_utf8_lossy(&out.stderr).into_owned(),
|
|
out.status.success(),
|
|
)
|
|
}
|
|
|
|
/// #315: `aura --help` opens with the concepts paragraph — the two layers,
|
|
/// the bias-as-target-position + protective-stop execution model, and how
|
|
/// traces come to exist. Each pin sits on one authored line of the explicit
|
|
/// `long_about` string, so clap's wrapping cannot split it.
|
|
#[test]
|
|
fn top_level_help_opens_with_the_two_layer_concept() {
|
|
let (out, err, ok) = run(&["--help"]);
|
|
assert!(ok, "aura --help failed: {err}");
|
|
assert!(out.contains("research verbs"), "names the sugar layer: {out}");
|
|
assert!(out.contains("directly authorable"), "names the document data plane: {out}");
|
|
assert!(out.contains("bias in [-1,+1]"), "names the bias output: {out}");
|
|
assert!(out.contains("defines the risk unit"), "names the protective stop / R: {out}");
|
|
assert!(out.contains("introspect --unwired"), "names the bare-{{}} ramp: {out}");
|
|
assert!(out.contains("aura chart"), "names the trace consumers: {out}");
|
|
}
|
|
|
|
/// #315: each document-bridged sugar verb's long help names the process
|
|
/// shape it desugars to and points at the document layer. `run` (not
|
|
/// document-bridged) points at the canonical document-first form instead.
|
|
#[test]
|
|
fn sugar_verbs_name_their_document_shape() {
|
|
for (verb, needle) in [
|
|
("sweep", "std::sweep"),
|
|
("walkforward", "std::walk_forward"),
|
|
("mc", "std::monte_carlo"),
|
|
("generalize", "std::generalize"),
|
|
] {
|
|
let (out, err, ok) = run(&[verb, "--help"]);
|
|
assert!(ok, "aura {verb} --help failed: {err}");
|
|
assert!(out.contains("Sugar"), "{verb} --help names the sugar relation: {out}");
|
|
assert!(out.contains(needle), "{verb} --help names {needle}: {out}");
|
|
assert!(out.contains("aura campaign"), "{verb} --help points at the documents: {out}");
|
|
}
|
|
let (out, err, ok) = run(&["run", "--help"]);
|
|
assert!(ok, "aura run --help failed: {err}");
|
|
assert!(out.contains("document-first"), "run --help names the canonical form: {out}");
|
|
}
|
|
|
|
/// #323: `graph build --help` carries the op-list reference — the op kinds
|
|
/// and fields are learnable from the binary, not only from serde refusals.
|
|
#[test]
|
|
fn graph_build_help_carries_the_op_reference() {
|
|
let (out, err, ok) = run(&["graph", "build", "--help"]);
|
|
assert!(ok, "aura graph build --help failed: {err}");
|
|
for op in [
|
|
r#"{"op":"source""#,
|
|
r#"{"op":"input""#,
|
|
r#"{"op":"add""#,
|
|
r#"{"op":"feed""#,
|
|
r#"{"op":"connect""#,
|
|
r#"{"op":"expose""#,
|
|
r#"{"op":"tap""#,
|
|
r#"{"op":"gang""#,
|
|
r#"{"op":"doc""#,
|
|
] {
|
|
assert!(out.contains(op), "op reference carries {op}: {out}");
|
|
}
|
|
assert!(out.contains("graph introspect --vocabulary"), "points at the node roster: {out}");
|
|
}
|
|
|
|
/// #315: the node vocabulary listing carries each type's one-line meaning —
|
|
/// no more bare names that teach nothing.
|
|
#[test]
|
|
fn graph_introspect_vocabulary_carries_meanings() {
|
|
let (out, err, ok) = run(&["graph", "introspect", "--vocabulary"]);
|
|
assert!(ok, "vocabulary listing failed: {err}");
|
|
let sma = out
|
|
.lines()
|
|
.find(|l| l.split_whitespace().next() == Some("SMA"))
|
|
.unwrap_or_else(|| panic!("no SMA line: {out}"));
|
|
assert!(sma.contains("moving average"), "SMA line carries its meaning: {sma}");
|
|
for l in out.lines().filter(|l| !l.trim().is_empty()) {
|
|
assert!(l.split_whitespace().count() >= 2, "bare name without meaning: {l}");
|
|
}
|
|
}
|
|
|
|
/// #315: `--node <T>`'s head line carries the type's meaning beside its label.
|
|
#[test]
|
|
fn graph_introspect_node_head_line_carries_the_meaning() {
|
|
let (out, err, ok) = run(&["graph", "introspect", "--node", "SMA"]);
|
|
assert!(ok, "node introspect failed: {err}");
|
|
let head = out.lines().next().unwrap_or("");
|
|
assert!(head.contains("moving average"), "head line carries the meaning: {out}");
|
|
}
|
|
|
|
/// #315: the fold vocabulary is introspectable from the binary — name, bind
|
|
/// rule, output kind, and meaning per fold (it lived only in source comments).
|
|
#[test]
|
|
fn graph_introspect_folds_lists_the_fold_vocabulary() {
|
|
let (out, err, ok) = run(&["graph", "introspect", "--folds"]);
|
|
assert!(ok, "folds listing failed: {err}");
|
|
let lines: Vec<&str> = out.lines().filter(|l| !l.trim().is_empty()).collect();
|
|
assert_eq!(lines.len(), 7, "one line per fold kind: {out}");
|
|
let mean = lines.iter().find(|l| l.starts_with("Mean")).expect("Mean row");
|
|
assert!(mean.contains("f64") && mean.contains("mean"), "Mean row rules + meaning: {mean}");
|
|
let count = lines.iter().find(|l| l.starts_with("Count")).expect("Count row");
|
|
assert!(count.contains("i64") && count.contains("any"), "Count row rules: {count}");
|
|
}
|
|
|
|
/// #315: the metric roster carries each metric's one-line meaning beside its
|
|
/// applicability tags.
|
|
#[test]
|
|
fn process_introspect_metrics_carries_meanings() {
|
|
let (out, err, ok) = run(&["process", "introspect", "--metrics"]);
|
|
assert!(ok, "metric roster failed: {err}");
|
|
let er = out
|
|
.lines()
|
|
.find(|l| l.split_whitespace().next() == Some("expectancy_r"))
|
|
.unwrap_or_else(|| panic!("no expectancy_r line: {out}"));
|
|
assert!(er.contains("mean realized R"), "meaning text on the roster line: {er}");
|
|
for l in out.lines().filter(|l| !l.trim().is_empty()) {
|
|
assert!(l.contains(" — "), "roster line without meaning: {l}");
|
|
}
|
|
}
|
|
|
|
/// #315: the persisted-tap vocabulary's meanings are readable where the taps
|
|
/// are advertised — under the `std::presentation` block's slot listing.
|
|
#[test]
|
|
fn campaign_introspect_block_presentation_lists_tap_meanings() {
|
|
let (out, err, ok) = run(&["campaign", "introspect", "--block", "std::presentation"]);
|
|
assert!(ok, "presentation describe failed: {err}");
|
|
assert!(out.contains("cumulative pip equity"), "equity tap meaning: {out}");
|
|
assert!(out.contains("after the cost model"), "net_r_equity tap meaning: {out}");
|
|
}
|
|
|
|
/// #323: `introspect --unwired` enumerates the optional C29 `description`
|
|
/// slot for both document kinds — the authoring side of the gate is
|
|
/// advertised where every other slot is.
|
|
#[test]
|
|
fn introspect_unwired_lists_the_description_slot() {
|
|
let dir = std::env::temp_dir().join(format!("aura-selfdesc-{}", std::process::id()));
|
|
std::fs::create_dir_all(&dir).unwrap();
|
|
let bare = dir.join("bare.json");
|
|
std::fs::write(&bare, "{}").unwrap();
|
|
for family in ["process", "campaign"] {
|
|
let (out, err, ok) = run(&[family, "introspect", "--unwired", bare.to_str().unwrap()]);
|
|
assert!(ok, "{family} --unwired failed: {err}");
|
|
assert!(
|
|
out.contains("open slot: description"),
|
|
"{family} --unwired lists the description slot: {out}"
|
|
);
|
|
assert!(out.contains("C29-gated"), "{family} hint names the gate: {out}");
|
|
}
|
|
let _ = std::fs::remove_dir_all(&dir);
|
|
}
|