# Fieldtest — milestone "Construction layer" — 2026-06-05

**Status:** Draft — awaiting orchestrator triage
**Author:** fieldtester (dispatched by fieldtest skill, milestone-scope variant)
**Scope mode:** MILESTONE (closing functional gate), not per-cycle.

## Scope

The "Construction layer" milestone (cycles 0012 composites + 0013 render)
promises that a downstream researcher can **build a graph fractally from a Rust
builder** — author leaf nodes and reusable *named composites* (a sub-graph
exposing one output), nest them arbitrarily, and `compile()` / `bootstrap()` the
param-generic `Blueprint` down to the flat, deterministic runnable instance
(C9/C19/C23). They can **introspect** the built graph as data and **render** it
as an ASCII DAG (`aura graph` clustered authored view, `aura graph --compiled`
flat inlined view) so the structure — and any mis-wiring — is visible
(C14/C22, #13). The composite boundary dissolves at inline (C23); a
composite-authored graph runs bit-identical to the hand-wired flat equivalent
(C1).

This fieldtest derives four end-to-end scenarios top-down from that promise, one
per carrier axis (a–d), and drives them from the **public interface only**: the
design ledger (`docs/design/INDEX.md`), the crate doc-comments (rendered via
`cargo doc --no-deps`), the public re-export lists, and the `aura` CLI. **No
`crates/*/src` was read.** The construction-layer API was discovered entirely
from the ledger + rustdoc. The artefact under test was built from HEAD
(`cargo build --workspace`; the consumer crate compiled against the current
path-dep'd engine crates; the CLI driven via `cargo run -p aura-cli`, so HEAD is
always what ran).

## Examples

### fieldtests/milestone-construction-layer/mc_1_composite_build_run.rs — author + compile + run a named composite
- A standalone consumer crate (own `[workspace]`, path-deps on the engine
  crates — the shape a C16 project takes) authors an SMA(2)/SMA(4)-cross as a
  reusable named `Composite` (`sma_cross`, one price role fanning into both
  SMAs, Sub's output as the exposed port), wires it into a harness `Blueprint`
  (`source → composite → Exposure(0.5) → SimBroker(1e-4) → Recorder`),
  `bootstrap()`s, and `run()`s it over a synthetic price ramp.
- Fits the milestone: this is the headline authoring story — build a graph
  fractally from a Rust builder via a named composite, then compile/bootstrap/run
  it.
- Outcome: built? **yes, first try.** ran? yes. matched expected? **yes** — 12
  recorded equity rows, non-degenerate (`equity_pips` ramps 0 → 11.5 → −6.5),
  the composite-authored harness produces a populated trace.

### fieldtests/milestone-construction-layer/mc_2_miswire_render.rs — a fast/slow swap is observable in graph-as-data + labels
- Builds the `sma_cross` composite twice — correct (fast→Sub.slot0,
  slow→Sub.slot1) and swapped — and asserts the per-node `label()`s carry the
  disambiguating params (`SMA(2)` / `SMA(4)`) and that the interior edge table
  **differs** between the two. The property under test is the one the render
  relies on: if a swap produced identical graph-as-data, no renderer could
  surface it.
- Fits the milestone: the headline promise is "the render visibly surfaces the
  mistake." This verifies the consumer-side substrate of that promise (labels +
  edge table), complementing the `aura graph` CLI render exercised below.
- Outcome: built? yes. ran? yes. matched expected? **yes** — labels read
  `["SMA(2)", "SMA(4)", "Sub"]`; correct edges `[{0→2.s0},{1→2.s1}]` vs swapped
  `[{1→2.s0},{0→2.s1}]` — observably different.

### fieldtests/milestone-construction-layer/mc_3_nested_composite.rs — composite-inside-composite inlines, runs
- Nests the `sma_cross` composite inside an outer `strategy` composite (which
  maps it through `Exposure(0.5)`), wires `strategy → SimBroker → Recorder`,
  `bootstrap()`s and `run()`s.
- Fits the milestone: "nest composites arbitrarily" + "the compiled/flat view
  handles nesting." This verifies the recursive-inline run-path from the
  consumer side (the known sharp edge #26 is about the *clustered render*, not
  the run path — see findings).
- Outcome: built? yes. ran? yes. matched expected? **yes** — the nested
  composite recursively inlined to a flat runnable instance; 12 recorded rows.

### fieldtests/milestone-construction-layer/mc_4_introspect_graph.rs — walk the graph-as-data via read-only accessors
- Builds a `Blueprint` (composite + Exposure) and walks it purely through the
  public accessors: `Blueprint::nodes()/sources()/edges()` and
  `Composite::name()/nodes()/edges()/input_roles()/output()`, plus `label()` on
  each boxed leaf, recursing into the composite interior.
- Fits the milestone: "introspect the built graph as data" (C9/C14) — can a
  consumer walk it without engine internals?
- Outcome: built? yes (after one import fix — see finding). ran? yes. matched
  expected? **yes** — the full interior (3 items, 1 role fanning to 2 targets,
  Sub's output port, 2 interior edges) is reachable through the public accessors
  alone.

### Render artefacts (CLI, built-in sample) — render_clustered.txt / render_compiled.txt
- `aura graph` and `aura graph --compiled` run live (via `cargo run -p
  aura-cli`), captured as artefacts. Both render the engine's **built-in sample
  blueprint** (the same SMA-cross harness). Byte-identical across two runs (C1
  for the render).

## Findings

### [working] The full composite authoring → compile → bootstrap → run thread is authorable from the public interface
- Examples: mc_1, mc_3.
- What happened: the entire construction-layer authoring story —
  `Composite::new(name, nodes, edges, input_roles, output)`,
  `BlueprintNode::from(node)` / `BlueprintNode::Composite(..)`,
  `Blueprint::new(..).bootstrap()`, `Harness::run` — compiled on the **first
  attempt** from rustdoc signatures + the ledger (C9/C19/C23) alone, for both a
  single composite and a composite-in-composite. Both produced populated,
  non-degenerate recorded traces.
- Why working: the milestone's central promise — build a graph fractally from a
  Rust builder and compile/bootstrap/run it — is delivered, and the public
  surface (ledger + rustdoc) is self-sufficient for a downstream author. The
  recursive inline of a nested composite works end to end on the run path.
- Recommended action: carry-on.

### [working] The graph-as-data accessors let a consumer walk the built graph (incl. composite interiors) with no engine internals
- Example: mc_4.
- What happened: `Blueprint::{nodes,sources,edges}` and
  `Composite::{name,nodes,edges,input_roles,output}` together expose the whole
  graph as read-only data; a recursive walk reaches every leaf label, every
  role fan-out, the output port, and every interior edge. `node.label()`
  dispatches off the boxed leaf without importing the `Node` trait.
- Why working: C9 "the built graph is introspectable runtime data" and the #13
  graph-as-data promise hold from the consumer side.
- Recommended action: carry-on.

### [working] Per-node labels disambiguate identical node types, and a mis-wire changes the graph-as-data
- Examples: mc_2, render_clustered.txt.
- What happened: `label()` overrides carry params (`SMA(2)` vs `SMA(4)`), so the
  two SMAs are distinguishable; a fast/slow swap changes the interior edge
  table. The `aura graph` clustered render shows both SMAs feeding `Sub` with
  the labels present, so a reader can trace which leg goes where. Render is
  byte-identical across runs (C1).
- Why working: the C8-refinement label mechanism does exactly what the milestone
  needs — it makes a swap *readable*. This is the substrate of the headline
  "render surfaces a mis-wiring" promise.
- Recommended action: carry-on.

### [friction] `aura graph` renders ONLY the built-in sample — a consumer cannot render the graph they authored
- Examples: mc_1/mc_2/mc_3 (authored blueprints) vs the CLI render artefacts.
- What happened: `aura graph` and `aura graph --compiled` are hard-wired to one
  built-in sample blueprint. Any extra argument is silently ignored:
  `aura graph --bogus`, `aura graph mc_1_composite_build_run.rs`, and even
  `aura graph --help` all render the **same** built-in sample and exit 0. There
  is no path (flag, file, stdin) to point the renderer at a consumer's own
  `Blueprint`.
- Why friction: the milestone's headline payoff — "render the structure so any
  **mis-wiring** is visible before/around a run" — is, at the CLI level, only
  available for the engine's own sample. A downstream researcher who authored
  `mc_2`'s *swapped* cross (the literal mis-wire the feature exists to catch)
  cannot feed it to `aura graph`; they fall back to introspecting the graph-as-data
  by hand (mc_2/mc_4) or writing their own ascii-dag adapter (which lives only in
  `aura-cli`, not a reusable surface). The render-as-a-library affordance that
  would let a consumer render their own graph is absent. Task completed (the
  render exists and is correct), but the consumer is forced into a hand-rolled
  workaround to apply it to their own work.
- Recommended action: plan — expose a render entry point a consumer can call on
  their own `Blueprint` (a library `render(&Blueprint) -> String` in a face
  crate, or `aura graph` accepting a project/blueprint selector). Pairs naturally
  with the deferred `aura new` / experiment-builder surface.

### [friction] The construction surface (aura-engine) does not re-export the core scalar vocabulary a graph-builder needs
- Example: mc_4 (initial `use aura_engine::{… ScalarKind …}` failed to compile:
  "no `ScalarKind` in the root … consider importing `aura_core::ScalarKind`").
- What happened: building a `Blueprint`/`SourceSpec`/`Recorder` requires
  `ScalarKind` (and, for sources/recorders, `Scalar` / `Firing` / `Timestamp`),
  but `aura-engine` — the crate that owns `Blueprint`, `Composite`, `Edge`,
  `Target`, `SourceSpec` — re-exports none of them. `SourceSpec { kind:
  ScalarKind, .. }` is an `aura-engine` struct whose own field type must be
  imported from a *different* crate (`aura-core`). A downstream author must know
  to reach across crate boundaries for the vocabulary the engine's own public
  types demand.
- Why friction: it is a paper cut, not a blocker — the compiler error names the
  right import — but it makes the "build a graph from a Rust builder" surface
  more than one `use aura_engine::*`. The construction API and the scalar
  vocabulary it consumes live in two crates with no convenience re-export.
- Recommended action: plan (tidy) — re-export the core scalar vocabulary
  (`Scalar`, `ScalarKind`, `Firing`, `Timestamp`) from `aura-engine` (or a
  prelude) so a graph-builder has a single import surface. Low priority.

### [spec_gap] `aura graph --help` / unknown args → render the sample, exit 0 (no help, args silently swallowed)
- Examples: render probes — `aura graph --help` and `aura graph --bogus` both
  print the clustered sample and exit 0.
- What happened: the `graph` subcommand keys only on the presence of the exact
  `--compiled` token; every other argument (including `--help`) falls through to
  the clustered view. No usage/help text, no rejection of unknown flags.
- Why spec_gap: the milestone/CLI contract does not pin how `aura graph` handles
  unknown or `--help` arguments; the binary chose the lenient "ignore and render
  the default view" reading. An equally plausible reading rejects unknown flags
  (exit 2) and prints help on `--help`. This re-confirms, on the new `graph`
  subcommand, the same lenient-arg gap the walking-skeleton fieldtest recorded
  for `aura run <junk>` and `aura --help`.
- Recommended action: ratify-or-tighten — decide a single CLI arg policy
  (reject unknown flags + `--help`→stdout/exit 0) across **both** `run` and
  `graph`, rather than per-subcommand. Low priority; carried/expanded from the
  walking-skeleton finding.

### [spec_gap] The known nested-composite render panic (#26) is unreachable from the public CLI today — its consumer impact is latent, not live
- Example: mc_3 (authored a nested composite; could not feed it to `aura graph`).
- What happened: the carrier flags #26 — the clustered blueprint view has an
  `unimplemented!` on nested composites — as a sharp edge to classify. As a
  downstream consumer I **could not reach it**: `aura graph` renders only the
  built-in single-level sample (see the friction above), so there is no public
  surface that feeds a *nested* blueprint into the clustered renderer. The panic
  is real in the code but **structurally unreachable** from the shipped CLI; the
  run-path nesting (mc_3) works fine.
- Why spec_gap: the milestone promise says the render makes nesting visible, but
  the only render entry point is the fixed sample, so "render a nested composite"
  is not actually exercisable by a consumer — the gap and the latent panic are
  two faces of the missing "render my own graph" affordance. Severity of #26 as a
  *consumer-facing* bug is currently **low/latent** (no public trigger), but it
  becomes **live** the moment the render is made parameterizable (the friction
  above) — at which point a consumer rendering a nested clustered view would
  panic.
- Recommended action: tighten the design ledger / sequence #26 — record that the
  "render my own graph" affordance (the friction above) and the #26 clustered
  nesting fix are **coupled**: shipping the former without the latter exposes the
  panic to consumers. The audit's own note ("resolve #26 before the next
  render-touching cycle") already points this way; this fieldtest confirms the
  coupling empirically. Classify #26 as the blocker *for the parameterizable-render
  cycle*, not for this milestone (which ships only the sample render).

## Recommendation summary

| Finding | Class | Action |
|---|---|---|
| Composite authoring → compile → bootstrap → run thread authorable, populated | working | carry-on |
| Graph-as-data accessors walk the built graph (incl. interiors) | working | carry-on |
| Labels disambiguate; a swap changes graph-as-data; render deterministic | working | carry-on |
| `aura graph` renders only the built-in sample (no render-my-own-graph) | friction | plan |
| aura-engine doesn't re-export the core scalar vocabulary | friction | plan (tidy) |
| `aura graph --help` / unknown args → render sample, exit 0 | spec_gap | ratify-or-tighten (unify CLI arg policy) |
| #26 nested-render panic unreachable from CLI today (latent, coupled to render-my-own-graph) | spec_gap | tighten ledger / sequence #26 with the parameterizable-render cycle |

## Milestone roll-up

**The "Construction layer" milestone DELIVERS its core promise.** A downstream
researcher can author leaf nodes and reusable named composites, nest them
arbitrarily, and `compile()`/`bootstrap()`/`run()` the param-generic `Blueprint`
down to a flat, deterministic, populated-trace instance (C9/C19/C23) — all from
the public interface, first try, including a composite-in-composite. The
graph-as-data accessors let a consumer walk the built graph without engine
internals, and the C8-refinement labels make a mis-wire readable in both the
graph-as-data and the `aura graph` render.

The gap is in the **render's reach**, not its correctness: `aura graph` is
hard-wired to one built-in sample, so the headline "render any mis-wiring before
a run" payoff is, at the CLI level, only demonstrated on the engine's own
example — a consumer cannot point it at the graph they authored, which is exactly
where a mis-wire would live. That single missing affordance ("render my own
graph") subsumes both the friction and the latent reachability of the #26
nested-render panic: making the render parameterizable is the next step, and #26
must land with it. **No bugs found** (the #26 panic is real in code but
structurally unreachable from the public surface today). Three `working`, two
`friction`, two `spec_gap` — none blocking the milestone gate.
