End-to-end milestone fieldtest (closing gate) for the "Construction layer" milestone (#12 composites + #13 aura graph render). Four real downstream tasks authored against the PUBLIC interface only (ledger + doc-comments + re-exports + the aura CLI), under fieldtests/milestone-construction-layer/: mc_1 — author a named sma_cross composite, build a Blueprint, compile + bootstrap + run (12 populated equity rows); mc_2 — correct vs fast/slow-swapped cross: labels + graph-as-data differ observably (the headline mis-wire-visible property); mc_3 — composite-nested-in-composite inlines and runs; mc_4 — walk the built graph via the read-only accessors, no engine internals. Roll-up: friction_found, NO bugs. The milestone delivers its core promise — fractal authoring -> compile -> bootstrap -> run, introspection as graph-as-data, and param-carrying labels that make a mis-wire readable. None of the findings block the gate. Findings filed to the forward queue (not fixed here): - #28 (feature) — the headline friction: `aura graph` renders only the built-in sample and the render adapter is CLI-private, so a consumer can't render their OWN graph. Subsumes the reachability of #26 (the nested-render panic is structurally unreachable until the render is parameterizable). Likely next (consumer-project / World) milestone. - #29 (idea) — aura-engine doesn't re-export the scalar vocabulary (ScalarKind etc.) a graph-builder needs; one-crate ergonomics tidy. - #16 (comment) — `aura graph` arg policy (help / unknown-arg) should unify with the still-open `aura run` strictness question. The fieldtester read no implementation source; all artefacts are the consumer crate + the two live render captures (render_clustered.txt / render_compiled.txt, byte-identical across runs).
16 KiB
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 namedComposite(sma_cross, one price role fanning into both SMAs, Sub's output as the exposed port), wires it into a harnessBlueprint(source → composite → Exposure(0.5) → SimBroker(1e-4) → Recorder),bootstrap()s, andrun()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_pipsramps 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_crosscomposite twice — correct (fast→Sub.slot0, slow→Sub.slot1) and swapped — and asserts the per-nodelabel()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 graphCLI 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_crosscomposite inside an outerstrategycomposite (which maps it throughExposure(0.5)), wiresstrategy → SimBroker → Recorder,bootstrap()s andrun()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()andComposite::name()/nodes()/edges()/input_roles()/output(), pluslabel()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 graphandaura graph --compiledrun live (viacargo 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}andComposite::{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 theNodetrait. - 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)vsSMA(4)), so the two SMAs are distinguishable; a fast/slow swap changes the interior edge table. Theaura graphclustered render shows both SMAs feedingSubwith 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 graphandaura graph --compiledare 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 evenaura graph --helpall render the same built-in sample and exit 0. There is no path (flag, file, stdin) to point the renderer at a consumer's ownBlueprint. - 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 toaura 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 inaura-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 libraryrender(&Blueprint) -> Stringin a face crate, oraura graphaccepting a project/blueprint selector). Pairs naturally with the deferredaura 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: "noScalarKindin the root … consider importingaura_core::ScalarKind"). - What happened: building a
Blueprint/SourceSpec/RecorderrequiresScalarKind(and, for sources/recorders,Scalar/Firing/Timestamp), butaura-engine— the crate that ownsBlueprint,Composite,Edge,Target,SourceSpec— re-exports none of them.SourceSpec { kind: ScalarKind, .. }is anaura-enginestruct 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) fromaura-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 --helpandaura graph --bogusboth print the clustered sample and exit 0. - What happened: the
graphsubcommand keys only on the presence of the exact--compiledtoken; 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 graphhandles unknown or--helparguments; 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 newgraphsubcommand, the same lenient-arg gap the walking-skeleton fieldtest recorded foraura run <junk>andaura --help. - Recommended action: ratify-or-tighten — decide a single CLI arg policy
(reject unknown flags +
--help→stdout/exit 0) across bothrunandgraph, 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 graphrenders 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.