diff --git a/fieldtests/m1-self-description/README.md b/fieldtests/m1-self-description/README.md new file mode 100644 index 0000000..baf8449 --- /dev/null +++ b/fieldtests/m1-self-description/README.md @@ -0,0 +1,18 @@ +# Milestone 36 fieldtest — "Self-description: every surface explains itself" + +Empirical, binary-only check of the milestone promise: that a deployment-posture +agent with **only** the `aura` binary (no engine sources, no repo docs, no +hand-written bootstrap card) can bootstrap from the surfaces alone. + +Every artefact here was produced touching nothing but the release binary +(`d26f0c8`) and what it emits (help text, `aura new` scaffold, introspection +output, error messages). One child dir per scenario; each README states the +property it protects. Full findings + verdict: `docs/specs/fieldtest-m1-self-description.md`. + +| dir | axis | outcome | +|-----|------|---------| +| s1_bootstrap_author_validate | cold bootstrap → author → validate | green | +| s2_execution_pulse_idiom | execution semantics + pulse idiom | green (1 friction) | +| s3_vocabulary_breadth | vocabulary self-description breadth| pass (1 friction) | +| s4_document_ramp | document-first ramp from `{}` | green (2 gaps) | +| s5_trace_measurement | trace / measurement path | dead-end (1 bug, 1 gap) | diff --git a/fieldtests/m1-self-description/s1_bootstrap_author_validate/README.md b/fieldtests/m1-self-description/s1_bootstrap_author_validate/README.md new file mode 100644 index 0000000..c717461 --- /dev/null +++ b/fieldtests/m1-self-description/s1_bootstrap_author_validate/README.md @@ -0,0 +1,33 @@ +# s1 — cold bootstrap → author → validate + +**Protected property:** starting from `aura --help` alone (no repo, no docs), a +downstream agent can scaffold a project, author a new strategy over the +discovered op-grammar vocabulary, and drive it to a green backtest — using only +what the binary emits. + +## Trail (binary-only) +1. `aura --help` — states what aura is, the two-layer model, and the execution + model (bias in [-1,+1], held as target position, stop = R). +2. `aura new demo-strat` — scaffolds `Aura.toml`, `blueprints/signal.json`, and a + machine-written `CLAUDE.md` bootstrap card. +3. `aura graph build --help` — the op-list authoring grammar (one line of meaning + per op). +4. `aura graph introspect --vocabulary` / `--node ` — node types and port + schemas. +5. Author `ema_cross.oplist.json` (this dir); build it: + `aura graph build < ema_cross.oplist.json > blueprints/ema_cross.json` +6. Validate green: + - synthetic: `aura run blueprints/ema_cross.json` → exit 0 (18-cycle synthetic + stream → 0 trades; all-zero metrics, no note that the stream is tiny). + - real: `aura run blueprints/ema_cross.json --real EURUSD --from 1577836800000 + --to 1583020800000` → 3944 trades, valid metrics JSON, exit 0. + +`ema_cross.blueprint.json` is the built artefact `graph build` emitted. + +## Notes +- The harness auto-wraps the bias with `sim-optimal+risk-executor` and reports R + metrics even though the blueprint emits only a bias — confirming the documented + execution model without any extra authoring. +- Minor friction: `graph build` has no op to set the blueprint name; it defaults + to `graph`, and that name leaks into the sweep axis namespace + (`graph.fast.length`). diff --git a/fieldtests/m1-self-description/s1_bootstrap_author_validate/ema_cross.blueprint.json b/fieldtests/m1-self-description/s1_bootstrap_author_validate/ema_cross.blueprint.json new file mode 100644 index 0000000..c1ee8f0 --- /dev/null +++ b/fieldtests/m1-self-description/s1_bootstrap_author_validate/ema_cross.blueprint.json @@ -0,0 +1 @@ +{"format_version":1,"blueprint":{"name":"graph","doc":"EMA(5)/EMA(20) spread clamped to a directional bias","nodes":[{"primitive":{"type":"EMA","name":"fast","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":5}}]}},{"primitive":{"type":"EMA","name":"slow","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":20}}]}},{"primitive":{"type":"Sub","name":"spread"}},{"primitive":{"type":"Bias","name":"bias","bound":[{"pos":0,"name":"scale","kind":"F64","value":{"F64":0.25}}]}}],"edges":[{"from":0,"to":2,"slot":0,"from_field":0},{"from":1,"to":2,"slot":1,"from_field":0},{"from":2,"to":3,"slot":0,"from_field":0}],"input_roles":[{"name":"price","targets":[{"node":0,"slot":0},{"node":1,"slot":0}],"source":"F64"}],"output":[{"node":3,"field":0,"name":"bias"}]}} \ No newline at end of file diff --git a/fieldtests/m1-self-description/s1_bootstrap_author_validate/ema_cross.oplist.json b/fieldtests/m1-self-description/s1_bootstrap_author_validate/ema_cross.oplist.json new file mode 100644 index 0000000..6bab547 --- /dev/null +++ b/fieldtests/m1-self-description/s1_bootstrap_author_validate/ema_cross.oplist.json @@ -0,0 +1,13 @@ +[ + {"op":"source","role":"price","kind":"F64"}, + {"op":"add","type":"EMA","name":"fast","bind":{"length":{"I64":5}}}, + {"op":"add","type":"EMA","name":"slow","bind":{"length":{"I64":20}}}, + {"op":"feed","role":"price","into":["fast.series","slow.series"]}, + {"op":"add","type":"Sub","name":"spread"}, + {"op":"connect","from":"fast.value","to":"spread.lhs"}, + {"op":"connect","from":"slow.value","to":"spread.rhs"}, + {"op":"add","type":"Bias","name":"bias","bind":{"scale":{"F64":0.25}}}, + {"op":"connect","from":"spread.value","to":"bias.signal"}, + {"op":"expose","from":"bias.bias","as":"bias"}, + {"op":"doc","text":"EMA(5)/EMA(20) spread clamped to a directional bias"} +] diff --git a/fieldtests/m1-self-description/s2_execution_pulse_idiom/README.md b/fieldtests/m1-self-description/s2_execution_pulse_idiom/README.md new file mode 100644 index 0000000..53833a9 --- /dev/null +++ b/fieldtests/m1-self-description/s2_execution_pulse_idiom/README.md @@ -0,0 +1,32 @@ +# s2 — execution semantics + one-shot/pulse idiom + +**Protected property:** from the surfaces alone a consumer can learn (a) what the +strategy's output stream means, (b) how it is executed against a protective stop, +and (c) the signal-side idiom the surfaces recommend when a held one-shot / pulse +behaviour is wanted — and can author that idiom to a green run. + +## What the surfaces say +- `aura --help`: "a strategy emits a bias in [-1,+1] per cycle, held as the + continuously-tracked target position; a protective stop defines the risk unit + R, and signal quality is measured in R." +- Node schemas make the execution layer concrete: `Bias` (clamp to [-1,+1]), + `FixedStop` (`price → stop_distance`, defines R), `PositionManagement` + (`bias + price + stop_distance + size → managed position in R`). The built-in + `run`/`sweep` harness supplies this risk executor automatically. +- The scaffolded `CLAUDE.md`: "Entry signals become held state via the signal-side + latch/edge-pulse idiom (see `aura graph introspect --vocabulary`)." + +## Authored idioms (both build + validate green) +- `latch_hold.oplist.json` — momentary crossover events → held state via `Latch` + (`set = fast>slow`, `reset = slow>fast`), held level → `Bias`. Runs green + (2543 trades over EURUSD 2020-01..03). +- `edge_pulse.oplist.json` — the complementary one-shot pulse, built as the + first-difference of the latched level (`Delay(lag=1)` + `Sub`) fed to `Bias` + (+1 on entry edge, −1 on exit edge, 0 held). Builds + validates green. + +## Friction recorded +The `CLAUDE.md` names the "latch/edge-pulse idiom" and points to +`graph introspect --vocabulary`, but that surface only lists node one-liners — +it lists `Latch` but no `edge`/`pulse` primitive and no composition recipe. The +pulse *is* constructible (Latch→Delay→Sub→Bias), but the consumer must infer the +first-difference recipe; the pointer resolves to a list, not a recipe. diff --git a/fieldtests/m1-self-description/s2_execution_pulse_idiom/edge_pulse.blueprint.json b/fieldtests/m1-self-description/s2_execution_pulse_idiom/edge_pulse.blueprint.json new file mode 100644 index 0000000..142b88e --- /dev/null +++ b/fieldtests/m1-self-description/s2_execution_pulse_idiom/edge_pulse.blueprint.json @@ -0,0 +1 @@ +{"format_version":1,"blueprint":{"name":"graph","doc":"edge-pulse: first-difference of the latched level, +1 on entry edge -1 on exit edge","nodes":[{"primitive":{"type":"SMA","name":"fast","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":5}}]}},{"primitive":{"type":"SMA","name":"slow","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":20}}]}},{"primitive":{"type":"Gt","name":"cross_up"}},{"primitive":{"type":"Gt","name":"cross_dn"}},{"primitive":{"type":"Latch","name":"hold"}},{"primitive":{"type":"Delay","name":"prev","bound":[{"pos":0,"name":"lag","kind":"I64","value":{"I64":1}}]}},{"primitive":{"type":"Sub","name":"edge"}},{"primitive":{"type":"Bias","name":"bias","bound":[{"pos":0,"name":"scale","kind":"F64","value":{"F64":1.0}}]}}],"edges":[{"from":0,"to":2,"slot":0,"from_field":0},{"from":1,"to":2,"slot":1,"from_field":0},{"from":1,"to":3,"slot":0,"from_field":0},{"from":0,"to":3,"slot":1,"from_field":0},{"from":2,"to":4,"slot":0,"from_field":0},{"from":3,"to":4,"slot":1,"from_field":0},{"from":4,"to":5,"slot":0,"from_field":0},{"from":4,"to":6,"slot":0,"from_field":0},{"from":5,"to":6,"slot":1,"from_field":0},{"from":6,"to":7,"slot":0,"from_field":0}],"input_roles":[{"name":"price","targets":[{"node":0,"slot":0},{"node":1,"slot":0}],"source":"F64"}],"output":[{"node":7,"field":0,"name":"bias"}]}} \ No newline at end of file diff --git a/fieldtests/m1-self-description/s2_execution_pulse_idiom/edge_pulse.oplist.json b/fieldtests/m1-self-description/s2_execution_pulse_idiom/edge_pulse.oplist.json new file mode 100644 index 0000000..869af7f --- /dev/null +++ b/fieldtests/m1-self-description/s2_execution_pulse_idiom/edge_pulse.oplist.json @@ -0,0 +1,24 @@ +[ + {"op":"source","role":"price","kind":"F64"}, + {"op":"add","type":"SMA","name":"fast","bind":{"length":{"I64":5}}}, + {"op":"add","type":"SMA","name":"slow","bind":{"length":{"I64":20}}}, + {"op":"feed","role":"price","into":["fast.series","slow.series"]}, + {"op":"add","type":"Gt","name":"cross_up"}, + {"op":"connect","from":"fast.value","to":"cross_up.a"}, + {"op":"connect","from":"slow.value","to":"cross_up.b"}, + {"op":"add","type":"Gt","name":"cross_dn"}, + {"op":"connect","from":"slow.value","to":"cross_dn.a"}, + {"op":"connect","from":"fast.value","to":"cross_dn.b"}, + {"op":"add","type":"Latch","name":"hold"}, + {"op":"connect","from":"cross_up.value","to":"hold.set"}, + {"op":"connect","from":"cross_dn.value","to":"hold.reset"}, + {"op":"add","type":"Delay","name":"prev","bind":{"lag":{"I64":1}}}, + {"op":"connect","from":"hold.value","to":"prev.series"}, + {"op":"add","type":"Sub","name":"edge"}, + {"op":"connect","from":"hold.value","to":"edge.lhs"}, + {"op":"connect","from":"prev.value","to":"edge.rhs"}, + {"op":"add","type":"Bias","name":"bias","bind":{"scale":{"F64":1.0}}}, + {"op":"connect","from":"edge.value","to":"bias.signal"}, + {"op":"expose","from":"bias.bias","as":"bias"}, + {"op":"doc","text":"edge-pulse: first-difference of the latched level, +1 on entry edge -1 on exit edge"} +] diff --git a/fieldtests/m1-self-description/s2_execution_pulse_idiom/latch_hold.blueprint.json b/fieldtests/m1-self-description/s2_execution_pulse_idiom/latch_hold.blueprint.json new file mode 100644 index 0000000..bcc8363 --- /dev/null +++ b/fieldtests/m1-self-description/s2_execution_pulse_idiom/latch_hold.blueprint.json @@ -0,0 +1 @@ +{"format_version":1,"blueprint":{"name":"graph","doc":"latched long: set on fast>slow, reset on slow>fast, held as bias","nodes":[{"primitive":{"type":"SMA","name":"fast","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":5}}]}},{"primitive":{"type":"SMA","name":"slow","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":20}}]}},{"primitive":{"type":"Gt","name":"cross_up"}},{"primitive":{"type":"Gt","name":"cross_dn"}},{"primitive":{"type":"Latch","name":"hold"}},{"primitive":{"type":"Bias","name":"bias","bound":[{"pos":0,"name":"scale","kind":"F64","value":{"F64":1.0}}]}}],"edges":[{"from":0,"to":2,"slot":0,"from_field":0},{"from":1,"to":2,"slot":1,"from_field":0},{"from":1,"to":3,"slot":0,"from_field":0},{"from":0,"to":3,"slot":1,"from_field":0},{"from":2,"to":4,"slot":0,"from_field":0},{"from":3,"to":4,"slot":1,"from_field":0},{"from":4,"to":5,"slot":0,"from_field":0}],"input_roles":[{"name":"price","targets":[{"node":0,"slot":0},{"node":1,"slot":0}],"source":"F64"}],"output":[{"node":5,"field":0,"name":"bias"}]}} \ No newline at end of file diff --git a/fieldtests/m1-self-description/s2_execution_pulse_idiom/latch_hold.oplist.json b/fieldtests/m1-self-description/s2_execution_pulse_idiom/latch_hold.oplist.json new file mode 100644 index 0000000..a45c126 --- /dev/null +++ b/fieldtests/m1-self-description/s2_execution_pulse_idiom/latch_hold.oplist.json @@ -0,0 +1,19 @@ +[ + {"op":"source","role":"price","kind":"F64"}, + {"op":"add","type":"SMA","name":"fast","bind":{"length":{"I64":5}}}, + {"op":"add","type":"SMA","name":"slow","bind":{"length":{"I64":20}}}, + {"op":"feed","role":"price","into":["fast.series","slow.series"]}, + {"op":"add","type":"Gt","name":"cross_up"}, + {"op":"connect","from":"fast.value","to":"cross_up.a"}, + {"op":"connect","from":"slow.value","to":"cross_up.b"}, + {"op":"add","type":"Gt","name":"cross_dn"}, + {"op":"connect","from":"slow.value","to":"cross_dn.a"}, + {"op":"connect","from":"fast.value","to":"cross_dn.b"}, + {"op":"add","type":"Latch","name":"hold"}, + {"op":"connect","from":"cross_up.value","to":"hold.set"}, + {"op":"connect","from":"cross_dn.value","to":"hold.reset"}, + {"op":"add","type":"Bias","name":"bias","bind":{"scale":{"F64":1.0}}}, + {"op":"connect","from":"hold.value","to":"bias.signal"}, + {"op":"expose","from":"bias.bias","as":"bias"}, + {"op":"doc","text":"latched long: set on fast>slow, reset on slow>fast, held as bias"} +] diff --git a/fieldtests/m1-self-description/s3_vocabulary_breadth/README.md b/fieldtests/m1-self-description/s3_vocabulary_breadth/README.md new file mode 100644 index 0000000..748f995 --- /dev/null +++ b/fieldtests/m1-self-description/s3_vocabulary_breadth/README.md @@ -0,0 +1,22 @@ +# s3 — vocabulary self-description breadth + +**Protected property:** every closed-vocabulary entry class the binary exposes is +reachable and carries a non-empty one-line meaning, and no entry's meaning is a +byte-identical (non-discriminating) duplicate of a sibling in the same class. + +`breadth_probe.sh` drives only `aura` introspection output; `breadth_probe.out` +is its captured result. Run with `AURA= bash breadth_probe.sh`. + +## Result (see breadth_probe.out) +- 33/33 node types resolve via `graph introspect --node ` with a + `""` schema. PASS. +- 7/7 folds, 6/6 process blocks, 6/6 campaign blocks, 17/17 metrics reachable and + carry a meaning (metrics also carry applicability tags). PASS. +- **One blemish:** three distinct node types — `CarryCost`, `ConstantCost`, + `VolSlippageCost` — share the byte-identical one-liner + *"cost-model node: charges its cost in R from position geometry, at close or + accrued per held cycle"*. From `--vocabulary` (the primary discovery surface) + a consumer cannot tell them apart; the distinguishing detail (the param + `carry_per_cycle` vs `cost_per_trade` vs `slip_vol_mult`, and the extra + `volatility` input) lives only in `--node` and in the campaign `cost` slot + prose. The one-liner exists (C29 satisfied) but is not entry-specific. diff --git a/fieldtests/m1-self-description/s3_vocabulary_breadth/breadth_probe.out b/fieldtests/m1-self-description/s3_vocabulary_breadth/breadth_probe.out new file mode 100644 index 0000000..62ce110 --- /dev/null +++ b/fieldtests/m1-self-description/s3_vocabulary_breadth/breadth_probe.out @@ -0,0 +1,45 @@ +== node vocabulary: every node resolves via --node with a meaning == + nodes probed: 33 +== duplicate node one-liners (non-discriminating meanings) == + x3 identical: cost-model node: charges its cost in R from position geometry, at close or accrued per held cycle +== folds carry a meaning == + ok: Count + ok: Sum + ok: Mean + ok: Min + ok: Max + ok: First + ok: Last +== process blocks resolve via --block == + ok: std::sweep + ok: std::gate + ok: std::walk_forward + ok: std::monte_carlo + ok: std::generalize + ok: std::grid +== campaign blocks resolve via --block == + ok: std::data + ok: std::risk + ok: std::cost + ok: std::strategy + ok: std::process_ref + ok: std::presentation +== metrics carry an applicability tag + meaning == + ok: expectancy_r + ok: win_rate + ok: avg_win_r + ok: avg_loss_r + ok: profit_factor + ok: max_r_drawdown + ok: sqn + ok: sqn_normalized + ok: net_expectancy_r + ok: n_trades + ok: n_open_at_end + ok: total_pips + ok: max_drawdown + ok: bias_sign_flips + ok: deflated_score + ok: overfit_probability + ok: neighbourhood_score +RESULT: fail=0 diff --git a/fieldtests/m1-self-description/s3_vocabulary_breadth/breadth_probe.sh b/fieldtests/m1-self-description/s3_vocabulary_breadth/breadth_probe.sh new file mode 100755 index 0000000..81e91b6 --- /dev/null +++ b/fieldtests/m1-self-description/s3_vocabulary_breadth/breadth_probe.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# Scenario 3 — vocabulary self-description breadth probe. +# Protected property: every closed-vocabulary entry class the binary exposes is +# REACHABLE and carries a non-empty one-line meaning; no entry's meaning is a +# byte-for-byte duplicate of another entry in the SAME class (a duplicate is a +# non-discriminating meaning). +# Binary-only: uses nothing but `aura` introspection output. +set -u +AURA="${AURA:?set AURA to the release binary path}" + +fail=0 + +echo "== node vocabulary: every node resolves via --node with a meaning ==" +nodes=$("$AURA" graph introspect --vocabulary | awk '{print $1}') +for n in $nodes; do + line=$("$AURA" graph introspect --node "$n" 2>&1 | head -1) + case "$line" in + "$n — "*) : ;; # "" + *) echo " MISSING/BAD meaning for node $n: [$line]"; fail=1 ;; + esac +done +echo " nodes probed: $(echo "$nodes" | wc -w)" + +echo "== duplicate node one-liners (non-discriminating meanings) ==" +"$AURA" graph introspect --vocabulary \ + | sed -E 's/^[A-Za-z0-9_]+ +//' \ + | sort | uniq -c | sort -rn \ + | awk '$1 > 1 { print " x"$1" identical: "substr($0, index($0,$2)) }' + +echo "== folds carry a meaning ==" +"$AURA" graph introspect --folds | awk 'NF==0{next} {print " ok: "$1}' + +echo "== process blocks resolve via --block ==" +for b in $("$AURA" process introspect --vocabulary | awk '{print $1}'); do + "$AURA" process introspect --block "$b" >/dev/null 2>&1 && echo " ok: $b" || { echo " BAD: $b"; fail=1; } +done + +echo "== campaign blocks resolve via --block ==" +for b in $("$AURA" campaign introspect --vocabulary | awk '{print $1}'); do + "$AURA" campaign introspect --block "$b" >/dev/null 2>&1 && echo " ok: $b" || { echo " BAD: $b"; fail=1; } +done + +echo "== metrics carry an applicability tag + meaning ==" +"$AURA" process introspect --metrics | awk '{print " ok: "$1}' + +echo "RESULT: fail=$fail" diff --git a/fieldtests/m1-self-description/s4_document_ramp/README.md b/fieldtests/m1-self-description/s4_document_ramp/README.md new file mode 100644 index 0000000..c75ed63 --- /dev/null +++ b/fieldtests/m1-self-description/s4_document_ramp/README.md @@ -0,0 +1,38 @@ +# s4 — document-first ramp (grow from `{}` to a runnable campaign) + +**Protected property:** using only `--unwired` introspection and validation +diagnostics, a consumer can grow a process document and a campaign document from a +bare `{}` to a registered, runnable state, and run the campaign green. + +## Trail (binary-only) +1. `echo '{}' > x.json; aura process introspect --unwired x.json` — lists the + whole envelope (`format_version`, `kind`, `name`, `pipeline`). +2. Grow `process.json`: `--unwired` on a doc with `pipeline:[]` says "a process + needs at least one stage"; a stage `{}` is told `pipeline[0].block (required, + block id)` → correct shape is `{"block":"std::sweep"}`. + `aura process validate` → valid; `aura process register` → content id. +3. Grow `campaign.json` similarly (`aura campaign introspect --unwired`, + `--block std::*`). `aura campaign validate` passes all three tiers + (intrinsic / referential / executable); `register` → content id; + `campaign run ` → 3 members, green. + +`process.json` and `campaign.json` in this dir are the final registered forms; +`ema_open.oplist.json` is the strategy authored with an **open** `fast.length` +param (see friction 2). + +## Frictions recorded +1. **`process.ref` shape under-documented.** `campaign introspect --block + std::process_ref` and `--unwired` both describe it as *"content id of a process + document"* (reads as a bare string), but the validator rejects the bare string: + `unknown variant '', expected 'content_id' or 'identity_id'` — the tagged + `{"content_id": ""}` form is required and appears nowhere in the + introspection. Recoverable only via the validate error. +2. **Two-layer axis-namespace seam.** `aura sweep --list-axes` presents + `graph.fast.length` as sweepable (overriding the *bound* default, #246), and + the sugar `aura sweep --axis graph.fast.length=…` accepts it. But the canonical + **document** form rejects that same name: `axis "graph.fast.length" is not in + the param space` — a campaign axis must be an **open** param, named **without** + the `graph.` prefix (`fast.length`, per `graph introspect --params`, which + prints *nothing* for a fully-bound blueprint). The name a consumer learns from + the sugar (`graph.fast.length`) is not the name the document layer wants + (`fast.length`), and no surface reconciles the two. diff --git a/fieldtests/m1-self-description/s4_document_ramp/campaign.json b/fieldtests/m1-self-description/s4_document_ramp/campaign.json new file mode 100644 index 0000000..9a5b44b --- /dev/null +++ b/fieldtests/m1-self-description/s4_document_ramp/campaign.json @@ -0,0 +1,10 @@ +{ + "format_version":1, + "kind":"campaign", + "name":"ema_axis_sweep", + "data":{"instruments":["EURUSD"],"windows":[{"from_ms":1577836800000,"to_ms":1583020800000}]}, + "strategies":[{"ref":{"content_id":"2c94edd4e3a3253fa446f2700f5ada1cb4cb9ceafcd99494d8d8e5244e3148a6"},"axes":{"fast.length":{"kind":"I64","values":[3,5,8]}}}], + "process":{"ref":{"content_id":"b3c0f7548731f4078ac6f0ba4444781ff51b067b4dd25567a12c49423b5d63ef"}}, + "seed":0, + "presentation":{"persist_taps":["r_equity"],"emit":["family_table"]} +} diff --git a/fieldtests/m1-self-description/s4_document_ramp/ema_open.oplist.json b/fieldtests/m1-self-description/s4_document_ramp/ema_open.oplist.json new file mode 100644 index 0000000..4b5e015 --- /dev/null +++ b/fieldtests/m1-self-description/s4_document_ramp/ema_open.oplist.json @@ -0,0 +1,13 @@ +[ + {"op":"source","role":"price","kind":"F64"}, + {"op":"add","type":"EMA","name":"fast"}, + {"op":"add","type":"EMA","name":"slow","bind":{"length":{"I64":20}}}, + {"op":"feed","role":"price","into":["fast.series","slow.series"]}, + {"op":"add","type":"Sub","name":"spread"}, + {"op":"connect","from":"fast.value","to":"spread.lhs"}, + {"op":"connect","from":"slow.value","to":"spread.rhs"}, + {"op":"add","type":"Bias","name":"bias","bind":{"scale":{"F64":0.25}}}, + {"op":"connect","from":"spread.value","to":"bias.signal"}, + {"op":"expose","from":"bias.bias","as":"bias"}, + {"op":"doc","text":"EMA(open fast)/EMA(20) spread bias, fast.length swept by campaign"} +] diff --git a/fieldtests/m1-self-description/s4_document_ramp/process.json b/fieldtests/m1-self-description/s4_document_ramp/process.json new file mode 100644 index 0000000..4de9813 --- /dev/null +++ b/fieldtests/m1-self-description/s4_document_ramp/process.json @@ -0,0 +1 @@ +{"format_version":1,"kind":"process","name":"minimal_sweep","pipeline":[{"block":"std::sweep"}]} diff --git a/fieldtests/m1-self-description/s5_trace_measurement/README.md b/fieldtests/m1-self-description/s5_trace_measurement/README.md new file mode 100644 index 0000000..66db3b5 --- /dev/null +++ b/fieldtests/m1-self-description/s5_trace_measurement/README.md @@ -0,0 +1,39 @@ +# s5 — trace / measurement path + +**Protected property:** from the surfaces a consumer can discover which verbs +persist per-cycle traces and reach a signal-quality measurement — the milestone's +own headline being "signal quality is measured in R". + +## What the surfaces say +`aura --help`: "Traces: `sweep --real --trace` / `walkforward --real --trace` +record per-cycle taps under runs/, consumed by `aura chart` and `aura measure`." +`aura measure ic --help`: "Information Coefficient of a **signal** tap against +forward returns of a **price** tap" — requires `--signal ` and +`--price `. + +## What actually happens +- Discovery works: `runs families` lists the traced families; `measure ic`'s + `` is the member path **relative to** `runs/traces/` + (`//`); the missing-tap error is clear and + lists available taps. `measure ic` runs green on the persisted taps. +- **Dead-end for the intended inputs.** The only taps any surface-reachable verb + persists are the closed presentation set `equity` / `exposure` / `r_equity` + (`net_r_equity`) — execution-side R/pip curves, **not** the strategy's bias + signal nor the instrument price. So `measure ic` can only be fed, e.g., + `--signal exposure --price equity`, which is not a signal-vs-price IC at all + (result ≈ noise, 0.0019). +- **Declared taps are silently dropped.** `graph build --help` documents the op + `tap` as *"declare a **recordable** tap on a wire"*. `tapped.oplist.json` (this + dir) declares `tap bias.bias as signal` and `tap fast.value as price`, yet after + `sweep --real --trace` the persisted set is still `["equity","exposure", + "r_equity"]`; both `measure ic --signal signal` and `chart --tap signal` report + the tap does not exist. There is no surface-documented path to persist a genuine + signal or price tap, so `measure ic`'s stated contract is unreachable end-to-end. + +## Evidence (verbatim) +``` +$ aura measure ic --signal signal --price price +aura: run '…' has no tap 'signal' (taps: ["equity", "exposure", "r_equity"]) +$ aura chart 010f8afa-0 --tap signal +aura: no family member has a tap named 'signal' (exit 0) +``` diff --git a/fieldtests/m1-self-description/s5_trace_measurement/tapped.oplist.json b/fieldtests/m1-self-description/s5_trace_measurement/tapped.oplist.json new file mode 100644 index 0000000..13ac131 --- /dev/null +++ b/fieldtests/m1-self-description/s5_trace_measurement/tapped.oplist.json @@ -0,0 +1,15 @@ +[ + {"op":"source","role":"price","kind":"F64"}, + {"op":"add","type":"SMA","name":"fast","bind":{"length":{"I64":5}}}, + {"op":"add","type":"SMA","name":"slow","bind":{"length":{"I64":20}}}, + {"op":"feed","role":"price","into":["fast.series","slow.series"]}, + {"op":"add","type":"Sub","name":"spread"}, + {"op":"connect","from":"fast.value","to":"spread.lhs"}, + {"op":"connect","from":"slow.value","to":"spread.rhs"}, + {"op":"add","type":"Bias","name":"bias","bind":{"scale":{"F64":0.25}}}, + {"op":"connect","from":"spread.value","to":"bias.signal"}, + {"op":"tap","from":"bias.bias","as":"signal"}, + {"op":"tap","from":"fast.value","as":"price"}, + {"op":"expose","from":"bias.bias","as":"bias"}, + {"op":"doc","text":"tapped: bias exposed and tapped as signal, fast tapped as price"} +]