fieldtest: composites-use — 4 examples, 1 bug / 1 friction / 3 spec-gap / 3 working

Source-blind per-cycle fieldtest of the #317 surface (register->label->
discover->use loop; ref forms + refusal UX; open patterns; sweep axes through
a spliced instance). Corpus under fieldtests/composites-use/ with TRANSCRIPT.md.

Working: the full authoring loop (W1), ref-form variety incl. ambiguous/
unknown-prefix and unknown-label refusals (W2), sweep --list-axes and a real
sweep through graph.<instance>.<node>.<param> plus splice-time bind (W3).

Findings, routed at close:
- B1 (reclassified: docs untruth, behaviour ratified): an open pattern whose
  input role names an archive column RUNS standalone — the harness binds
  roles by name, which is C26's design; the "running standalone refuses"
  claim was the wrong sentence, fixed in the follow-up docs commit and
  minuted on #317.
- S1: the run path's actual refusal trio (bias/tap gate, free knobs, role-
  column) vs the docs' bootstrap-gate story -> same docs commit + #339 note.
- F1: gang-on-instance-member refusal does not name the unsupported-rule ->
  evidence added to #339 item 1.
- S2: README still enumerated nine ops -> fixed in the follow-up commit.
- S3: the use-seam C29 gate is unreachable through today's CLI writes
  (register gates both input forms) -> ratified as a backstop, one line in
  C24.
- Orthogonal pre-existing observation (NOT #317): aura run --params did not
  close free knobs in the fieldtester's hands -> #340.

refs #317
This commit is contained in:
2026-07-24 20:09:11 +02:00
parent 4a30222fdc
commit cb3330ceb5
20 changed files with 285 additions and 0 deletions
+122
View File
@@ -0,0 +1,122 @@
# Fieldtest transcript — cycle #317 (composites, the `use` op)
Verbatim command/output capture for the `fieldtests/composites-use/` fixtures.
Binary: `target/release/aura` built from HEAD `4a30222` (release profile).
Project: `./lab` (scaffolded with `aura new lab`; `/runs` gitignored).
Commands using the store were run from inside `lab/`.
## Example 1 — register → --name → discover → use by name → splice
```
$ aura graph build < cu_1_pattern_spread.ops.json
{... "input_roles":[{"name":"price","targets":[{"node":0,"slot":0},{"node":1,"slot":0}]}] ...}
# open pattern: input role "price" carries no "source":<kind> (contrast a source role)
$ aura graph introspect --params cu_1_pattern_spread.ops.json
fast.length:I64
slow.length:I64
$ aura graph introspect --unwired < cu_1_pattern_spread.ops.json
# (no output — the price input role feeds fast.series/slow.series, so no open interior slot)
# --- inside lab/ ---
$ aura graph register .../cu_1_pattern_spread.ops.json --name spread_xover
registered blueprint f61659aaa8dd1933c9e42f437f06be3ae346dd61569b5e013f07285e6fda03b1 (.../runs/blueprints/f61659....json)
label "spread_xover" -> f61659aaa8dd1933c9e42f437f06be3ae346dd61569b5e013f07285e6fda03b1
$ aura graph introspect --registered
spread_xover f61659aaa8dd open SMA-crossover spread pattern: fast minus slow over an injected price input role
$ aura graph build < cu_1_consumer.ops.json > payload 2> echo
# stderr (echo): aura: note: use "xover": spread_xover -> f61659aaa8dd1933c9e42f437f06be3ae346dd61569b5e013f07285e6fda03b1
# stdout (payload): {... "nodes":[{"composite":{"name":"xover", ... spliced inline ...}}, {"primitive":{"type":"Bias"...}}] ...}
$ aura graph introspect --params cu_1_consumer.ops.json
xover.fast.length:I64
xover.slow.length:I64
bias.scale:F64
```
## Example 2 — ref-form variety + refusal UX
Store contains labels `ema_smoother` (1c8edf24a1eb) and `spread_xover` (f61659aaa8dd),
plus two unlabelled spread variants `e6982f7d...` and `e8f04011...` (both start with `e`).
```
$ aura graph register .../cu_2_docless.ops.json --name docless
aura: blueprint: composite `graph` carries no doc — a registered composite describes itself (C29); add a doc line (...) before register
# exit 1 (register gates doc-less at register; a doc-less entry never enters the store)
# by full content id -> splices, echoes id
$ aura graph build < cu_2_ref_full_id.ops.json
aura: note: use "sp": e6982f7d...d55d82 -> e6982f7d...d55d82 (exit 0)
# by unique prefix "e69" -> splices, echoes resolved id
$ aura graph build < cu_2_ref_prefix.ops.json
aura: note: use "sp": e69 -> e6982f7d...d55d82 (exit 0)
# by ambiguous prefix "e" -> refuses, enumerates candidates
$ aura graph build < cu_2_ref_ambiguous.ops.json
aura: op 2 (use "sp"): ambiguous id "e": candidates e6982f7d...d55d82, e8f04011...07fcf8 (exit 1)
# by unknown prefix "deadbeef" -> refuses
$ aura graph build < cu_2_ref_unknown_prefix.ops.json
aura: op 2 (use "sp"): no registered blueprint matches content id "deadbeef" (exit 1)
# by unknown label "nope" -> refuses, enumerates labels
$ aura graph build < cu_2_ref_unknown_label.ops.json
aura: op 2 (use "sp"): no registered blueprint labeled "nope" — registered labels: ema_smoother, spread_xover (exit 1)
```
## Example 3 — running an open pattern standalone
Docs (authoring guide `input` row; glossary `use (op)`) say: "only running it
standalone refuses (bootstrap's unbound-root-role gate)."
```
# (a) spread-exposing open pattern -> refuses on the run-SHAPE gate (not the role gate)
$ aura run spread_open.bp.json
aura: `aura run` needs either a `bias` output (a strategy) or ≥1 declared tap (a measurement); this blueprint exposes neither (exit 1)
# (b) bias-exposing open pattern, params OPEN -> refuses on the OPEN-PARAMS gate
$ aura run open_bias.bp.json
aura: run requires a closed blueprint (no free parameters); 3 free knob(s) — bind them or use `aura sweep --axis` (exit 2)
$ aura run open_bias.bp.json --params '[{"I64":2},{"I64":4},{"F64":0.5}]'
aura: run requires a closed blueprint (no free parameters); 3 free knob(s) — ... (exit 2) # --params did not close the knobs
# (c) bias-exposing open pattern, params BOUND, input role "price" OPEN -> RUNS CLEAN (docs say refuse)
$ aura run open_bias_bound.bp.json # from cu_3_open_bias_bound.ops.json
{"manifest":{...,"params":[],"defaults":[["graph.fast.length",{"I64":2}],...]},"metrics":{"total_pips":0.3418...}} (exit 0)
# (d) same, but input role named "quote" (not a column name) -> refuses on the ROLE-COLUMN gate
$ aura run altrole.bp.json
aura: input role "quote" of strategy "graph" binds to no archive column — bindable columns: open, high, low, close (alias: price), spread, volume; or bind it explicitly in the campaign data.bindings block (exit 1)
```
## Example 4 — sweep integration + gang refusal + happy-path run
```
$ aura sweep cu_1_consumer.bp.json --list-axes
graph.xover.fast.length:I64
graph.xover.slow.length:I64
graph.bias.scale:F64
$ aura sweep cu_1_consumer.bp.json --axis graph.xover.fast.length=2,3 \
--axis graph.xover.slow.length=6 --axis graph.bias.scale=0.5 --name cu4_sweep
{"family_id":"cu4_sweep-0","report":{"manifest":{...,"params":[["graph.xover.fast.length",{"I64":2}],["graph.xover.slow.length",{"I64":6}],...]}}}
{"family_id":"cu4_sweep-0","report":{"manifest":{...,"params":[["graph.xover.fast.length",{"I64":3}],...]}}}
# exit 0, 2 members — spliced-instance params sweep through the nesting
# gang a spliced instance's member path -> refuses (documented), but message frames it as a missing param
$ aura graph build < cu_4_gang_instance_member.ops.json
aura: note: use "xover": spread_xover -> f61659...
aura: op 6 (gang): node xover has no param "fast.length" (exit 1)
# happy path: use + splice-time bind -> closed blueprint -> run
$ aura graph build < cu_1_consumer_bound.ops.json > closed.bp.json
$ aura graph introspect --params cu_1_consumer_bound.ops.json # (empty = closed)
$ aura run closed.bp.json
{"manifest":{...,"params":[],"defaults":[["graph.xover.fast.length",{"I64":2}],...]},"metrics":{...}} (exit 0)
$ aura run closed.bp.json --real GER40
{"manifest":{...}} (exit 0)
```
@@ -0,0 +1,9 @@
[
{"op": "doc", "text": "consumer: splice the spread pattern by name, feed price, clamp the spread into a bias"},
{"op": "source", "role": "price", "kind": "F64"},
{"op": "use", "ref": {"name": "spread_xover"}, "name": "xover"},
{"op": "feed", "role": "price", "into": ["xover.price"]},
{"op": "add", "type": "Bias", "name": "bias"},
{"op": "connect", "from": "xover.spread", "to": "bias.signal"},
{"op": "expose", "from": "bias.bias", "as": "bias"}
]
@@ -0,0 +1,9 @@
[
{"op": "doc", "text": "consumer that splices the spread pattern, binds its params at the use seam, and clamps to a bias — a closed, runnable blueprint"},
{"op": "source", "role": "price", "kind": "F64"},
{"op": "use", "ref": {"name": "spread_xover"}, "name": "xover", "bind": {"fast.length": {"I64": 2}, "slow.length": {"I64": 6}}},
{"op": "feed", "role": "price", "into": ["xover.price"]},
{"op": "add", "type": "Bias", "name": "bias", "bind": {"scale": {"F64": 0.5}}},
{"op": "connect", "from": "xover.spread", "to": "bias.signal"},
{"op": "expose", "from": "bias.bias", "as": "bias"}
]
@@ -0,0 +1,11 @@
[
{"op": "doc", "text": "open SMA-crossover spread pattern: fast minus slow over an injected price input role"},
{"op": "input", "role": "price"},
{"op": "add", "type": "SMA", "name": "fast"},
{"op": "add", "type": "SMA", "name": "slow"},
{"op": "feed", "role": "price", "into": ["fast.series", "slow.series"]},
{"op": "add", "type": "Sub", "name": "sub"},
{"op": "connect", "from": "fast.value", "to": "sub.lhs"},
{"op": "connect", "from": "slow.value", "to": "sub.rhs"},
{"op": "expose", "from": "sub.value", "as": "spread"}
]
@@ -0,0 +1,6 @@
[
{"op": "input", "role": "price"},
{"op": "add", "type": "EMA", "name": "e"},
{"op": "feed", "role": "price", "into": ["e.series"]},
{"op": "expose", "from": "e.value", "as": "smoothed"}
]
@@ -0,0 +1,7 @@
[
{"op": "doc", "text": "open EMA smoother pattern: exponential moving average over an injected price input role"},
{"op": "input", "role": "price"},
{"op": "add", "type": "EMA", "name": "e"},
{"op": "feed", "role": "price", "into": ["e.series"]},
{"op": "expose", "from": "e.value", "as": "smoothed"}
]
@@ -0,0 +1,7 @@
[
{"op": "doc", "text": "ref by an ambiguous one-char prefix (two ids start with e)"},
{"op": "source", "role": "price", "kind": "F64"},
{"op": "use", "ref": {"content_id": "e"}, "name": "sp"},
{"op": "feed", "role": "price", "into": ["sp.price"]},
{"op": "expose", "from": "sp.spread", "as": "spread"}
]
@@ -0,0 +1,7 @@
[
{"op": "doc", "text": "ref a registered spread by full content id"},
{"op": "source", "role": "price", "kind": "F64"},
{"op": "use", "ref": {"content_id": "e6982f7d4ad35dd59f1d9835e10436c193735d94501b7d10518eae0d9cd55d82"}, "name": "sp"},
{"op": "feed", "role": "price", "into": ["sp.price"]},
{"op": "expose", "from": "sp.spread", "as": "spread"}
]
@@ -0,0 +1,7 @@
[
{"op": "doc", "text": "ref a registered spread by unique content-id prefix"},
{"op": "source", "role": "price", "kind": "F64"},
{"op": "use", "ref": {"content_id": "e69"}, "name": "sp"},
{"op": "feed", "role": "price", "into": ["sp.price"]},
{"op": "expose", "from": "sp.spread", "as": "spread"}
]
@@ -0,0 +1,7 @@
[
{"op": "doc", "text": "ref by a label that was never registered"},
{"op": "source", "role": "price", "kind": "F64"},
{"op": "use", "ref": {"name": "nope"}, "name": "sp"},
{"op": "feed", "role": "price", "into": ["sp.price"]},
{"op": "expose", "from": "sp.spread", "as": "spread"}
]
@@ -0,0 +1,7 @@
[
{"op": "doc", "text": "ref by a prefix that matches nothing in the store"},
{"op": "source", "role": "price", "kind": "F64"},
{"op": "use", "ref": {"content_id": "deadbeef"}, "name": "sp"},
{"op": "feed", "role": "price", "into": ["sp.price"]},
{"op": "expose", "from": "sp.spread", "as": "spread"}
]
@@ -0,0 +1,11 @@
[
{"op": "doc", "text": "spread pattern variant 2 for prefix ambiguity probe"},
{"op": "input", "role": "price"},
{"op": "add", "type": "SMA", "name": "fast"},
{"op": "add", "type": "SMA", "name": "slow"},
{"op": "feed", "role": "price", "into": ["fast.series", "slow.series"]},
{"op": "add", "type": "Sub", "name": "sub"},
{"op": "connect", "from": "fast.value", "to": "sub.lhs"},
{"op": "connect", "from": "slow.value", "to": "sub.rhs"},
{"op": "expose", "from": "sub.value", "as": "spread"}
]
@@ -0,0 +1,11 @@
[
{"op": "doc", "text": "spread pattern variant 5 for prefix ambiguity probe"},
{"op": "input", "role": "price"},
{"op": "add", "type": "SMA", "name": "fast"},
{"op": "add", "type": "SMA", "name": "slow"},
{"op": "feed", "role": "price", "into": ["fast.series", "slow.series"]},
{"op": "add", "type": "Sub", "name": "sub"},
{"op": "connect", "from": "fast.value", "to": "sub.lhs"},
{"op": "connect", "from": "slow.value", "to": "sub.rhs"},
{"op": "expose", "from": "sub.value", "as": "spread"}
]
@@ -0,0 +1,13 @@
[
{"op": "doc", "text": "open bias pattern: SMA-crossover spread clamped to a bias, over an injected price input role"},
{"op": "input", "role": "price"},
{"op": "add", "type": "SMA", "name": "fast"},
{"op": "add", "type": "SMA", "name": "slow"},
{"op": "feed", "role": "price", "into": ["fast.series", "slow.series"]},
{"op": "add", "type": "Sub", "name": "sub"},
{"op": "connect", "from": "fast.value", "to": "sub.lhs"},
{"op": "connect", "from": "slow.value", "to": "sub.rhs"},
{"op": "add", "type": "Bias", "name": "bias"},
{"op": "connect", "from": "sub.value", "to": "bias.signal"},
{"op": "expose", "from": "bias.bias", "as": "bias"}
]
@@ -0,0 +1,13 @@
[
{"op": "doc", "text": "open bias pattern with params bound but the price input role still open — isolates the standalone-run input-role refusal"},
{"op": "input", "role": "price"},
{"op": "add", "type": "SMA", "name": "fast", "bind": {"length": {"I64": 2}}},
{"op": "add", "type": "SMA", "name": "slow", "bind": {"length": {"I64": 4}}},
{"op": "feed", "role": "price", "into": ["fast.series", "slow.series"]},
{"op": "add", "type": "Sub", "name": "sub"},
{"op": "connect", "from": "fast.value", "to": "sub.lhs"},
{"op": "connect", "from": "slow.value", "to": "sub.rhs"},
{"op": "add", "type": "Bias", "name": "bias", "bind": {"scale": {"F64": 0.5}}},
{"op": "connect", "from": "sub.value", "to": "bias.signal"},
{"op": "expose", "from": "bias.bias", "as": "bias"}
]
@@ -0,0 +1,10 @@
[
{"op": "doc", "text": "consumer that tries to gang two params of a spliced instance (documented as unsupported)"},
{"op": "source", "role": "price", "kind": "F64"},
{"op": "use", "ref": {"name": "spread_xover"}, "name": "xover"},
{"op": "feed", "role": "price", "into": ["xover.price"]},
{"op": "add", "type": "Bias", "name": "bias"},
{"op": "connect", "from": "xover.spread", "to": "bias.signal"},
{"op": "gang", "as": "xover_length", "into": ["xover.fast.length", "xover.slow.length"]},
{"op": "expose", "from": "bias.bias", "as": "bias"}
]
+1
View File
@@ -0,0 +1 @@
/runs
+4
View File
@@ -0,0 +1,4 @@
# Static project context only (C17); paths only.
[paths]
runs = "runs"
# data = "/path/to/archive" # the recorded-data root; defaults to the built-in path
+22
View File
@@ -0,0 +1,22 @@
# lab — an aura research project (data-only)
This directory is an aura project: blueprints + research documents over the
std vocabulary, anchored by `Aura.toml`. There is no crate and no build step.
- Run: `aura run blueprints/signal.json` (the starter is closed — all
params bound; bound values are defaults — any `--axis` may override them
(#246))
- Sweep: `aura sweep blueprints/signal.json --axis lab_signal.fast.length=2,4,8`
(`aura sweep <bp> --list-axes` lists the open + bound-overridable axes)
- Native nodes: when the project needs its first project-specific node,
`aura nodes new <name>` scaffolds a node crate beside this project and
attaches it via `[nodes]` in `Aura.toml` (build it with `cargo build`).
- Topology is data (`blueprints/*.json`); results land in `runs/`.
- Execution model: 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 quality metrics are R-based. Entry signals become held state
via the signal-side latch/edge-pulse idiom (see
`aura graph introspect --vocabulary`).
- Data plane: the research verbs are sugar over registered process/campaign
documents — author them directly with `aura process` / `aura campaign`,
growing one from a bare `{}` via `aura campaign introspect --unwired`.
@@ -0,0 +1 @@
{"format_version":1,"blueprint":{"name":"lab_signal","doc":"fast/slow SMA difference clamped into a directional bias","nodes":[{"primitive":{"type":"SMA","name":"fast","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":2}}]}},{"primitive":{"type":"SMA","name":"slow","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":4}}]}},{"primitive":{"type":"Sub"}},{"primitive":{"type":"Bias","name":"bias","bound":[{"pos":0,"name":"scale","kind":"F64","value":{"F64":0.5}}]}}],"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"}]}}