The single-file ledger had grown to 2968 lines / ~42k tokens, mixing current design law with accreted history: 59 cycle-stamped realization blocks, 18 [HISTORY] passages, 22 supersession markers, and the C10 / C22 / C24 reframe sagas layered several supersessions deep. A code-grounding audit (31 agents, adversarially verified) confirmed 11 defects stated as current truth: stale crate homes from the C28 #288 roster split (cost nodes, PositionManagement, PositionEvent, Session), the renamed InputSpec->PortSpec, the pre-#241 project model in C16 and the open-threads section, a stale HarnessKind retirement deferral in C24, and three C28-internal inconsistencies. New shape, per the ailang precedent: - INDEX.md stays the sole addressable entry point: foundation, external components, a C-id-keyed contract map (one line per contract), and only the genuinely open architectural threads. - contracts/cNN-<slug>.md carries each contract's current truth only: Guarantee / Forbids / Why with ratified refinements integrated, plus a code-anchored Current state. All confirmed defects are fixed here; crate anchors were re-verified against the tree. - contracts/cNN-<slug>.history.md (18 sidecars) and INDEX.history.md preserve every superseded block verbatim, stamps and issue refs intact, under a frozen-record banner. Nothing was deleted: superseded design intent remains an addressable working-tree artifact, off the per-cycle audit walk. - Ledger discipline is now stated in INDEX.md: live files are edited in place at cycle close, superseded text moves verbatim to the sidecar, and a supersession marker in a live file is itself an audit finding. Every contract file was verified against its old text by an independent zero-loss pass (statement-by-statement) plus a code-accuracy spot check; C-ids and contract titles are unchanged, so existing C-id citations in code, tests, and issues resolve as before.
9.8 KiB
C22 — The playground is a trace explorer; sinks are the recording mechanism
Guarantee. The World is a program (C21): nothing is displayable until it runs, and its harnesses are transient machinery (built, run, discarded — C19). The only durable, displayable substance is the recorded trace, captured by sinks — pure consumer nodes (C8) that persist a stream (equity, a bias / exposure level, a node's output) into the run registry (C18). Displayable = exactly what a sink recorded; with no sink, only the input params + summary metrics remain. The playground is therefore an execution viewer / trace explorer, and it plays any harness (the harness player, not a harness, never bound to a default one): before a run it shows the program structure (graph-as-data, C9) + param knobs; during a run, live sink streams; after a run, recorded traces + metrics from the registry — including meta-views (stitched walk-forward, sweep surfaces, multi-strategy / instrument comparison). Observability is explicit and selective — you instrument what you want to see; the choice of sinks is part of the experiment. The engine ships sample blueprints and a scaffolded starter so a newcomer has something to run at once; a fresh project is empty until built, run, and instrumented. The shipped visual face is web-from-disk — disk-persisted traces rendered as self-contained HTML chart pages (not egui).
Forbids. A scene-editor model that assumes a persistent, populated world; constructing or wiring topology in the UI (topology is Rust + hot-reload, C9/C17 — the UI reflects the live graph-as-data and tunes runtime params via sliders, C12); retaining un-sinked data; binding the playground to a single / default harness. The "never a scene editor" forbid is precisely no persistent scene-at-rest (a World is a program) — it does not forbid a future blueprint editor over the World-owned topology data (C24); that data format would be the substrate such an editor round-trips, not a new mechanism.
Why. A program has no scene-at-rest to inspect; what persists is what it records. Making sinks the one recording-and-observability mechanism keeps "what can I see?" answerable by "what did I instrument?", and keeps the engine UI-agnostic (C14). Live param tuning (runtime values, no topology change — C12 / C19) gives the interactive feel without a wiring DSL. The visual face is orthogonal and optional: the blueprint data format (C24) is required by C21 (generation / structural search) and C18 (reproduction) at the CLI level, autonomously, independent of any visual surface — so the playground's form (web or egui, viewer or editor, or not built at all) is downstream, and topology authoring does not live in it. Generalized by the role model (C25): every artifact's canonical, complete form is text, every operation is executable headless, and any visual surface is a stateless projection that reads/writes that canonical form and adds no semantics; read-only projections (this trace explorer) rank far above write editors, because the Blockly-litmus discipline (C25) already keeps each vocabulary palette-generatable without one.
Current state
Sinks at the substrate. A recorded trace is exactly what a recording (tap) node
pushed out of the graph — there is no engine recording registry; the constructing
World holds each recording node's destination. Harness::run returns () and
recording is a node-side concern, so one run records many streams (one per tap)
rather than exactly one row. Recorded streams are sparse and timestamped (a record
per fired cycle, tagged ctx.now()), matching a trace of timestamped events (C18).
Encoding / storage / rendering split (C14). A drained tap is encoded as a
columnar (SoA, C7) ColumnarTrace — struct→JSON only — in aura-engine
(report.rs); file I/O is aura-registry::TraceStore, persisting each run under
runs/traces/<name>/ beside the run registry's runs.jsonl; rendering is
aura-cli (render_chart_html + the vendored chart-viewer.js). The engine stays
headless.
Single run. aura run <blueprint.json> persists every tap the blueprint
declares to the trace store under the run's own name, on both shapes: a
bias-output strategy (aura-runner::member::run_signal_r, R-wrapped) and a bare
measurement blueprint with ≥1 declared tap but no bias
(aura-runner::measure::run_measurement). A tap-free run writes nothing to runs/
and its stdout stays byte-identical. The CLI --trace <NAME> flag is retired on
run (and on mc): it parses but is refused at dispatch (exit 2) — naming a trace
is the family / campaign path's job, not a per-run flag.
Families. aura sweep|walkforward --real <SYM> … --trace <NAME> persists each
member via the campaign path: the dissolved verb is translated into a
content-addressed process + campaign document whose presentation.persist_taps
requests the tap vocabulary, run through the one campaign executor;
aura-runner::runner::persist_campaign_traces writes each member under a depth-2
fan-out runs/traces/<name>/<cell>/<member>/. Every written member is
independently re-run once in non-reduce trace mode over its own recorded window,
and its re-derived metrics are asserted equal to the recorded member's — a C1
drift alarm that refuses (exit 1) rather than persist a silently-wrong trace. The
synthetic sweep / walk-forward path refuses --trace (exit 2);
Monte-Carlo is excluded from trace persistence (mc --real refuses
--name/--trace, exit 2 — the real-data R-bootstrap campaign itself runs, but
records no per-member family traces; the synthetic --seeds family's realization
argument, C12, does not carry over to one real series).
TraceStore::ensure_name_free makes name resolution a total function, refusing
cross-kind reuse of one name by both a run and a family.
Viewer. aura chart <name> [--tap <t>] [--panels] classifies the name on disk
(TraceStore::name_kind: top-level index.json → a single Run; member subdirs → a
Family; else NotFound, which then tries campaign-name resolution), reads the traces
back, and aligns all taps on a synthetic union-timestamp spine via the post-run
join_on_ts (C3 — a post-run join, not an in-graph merge; C1 — pure, no live
external call), emitting a self-contained uPlot page (vendored, like the graph
render). A single run overlays all its taps, each on its own y-scale (or the
one --tap selects); a family overlays one tap (default equity, --tap
to pick) of every member on a single shared y-scale — members measure one
identical quantity, so a shared scale is what makes them comparable. One mechanism
yields three correct readings: sweep / MC members share the data window (a true
overlay), walk-forward members are disjoint OOS windows (null-complementary → the
stitched curve). A run-context header (ChartMeta → window.AURA_TRACES.meta →
the pure buildHeader) renders chips for kind / name / members / tap / broker /
window / seed / commit / bound-params; a family's window is the span
(min from, max to) across members. Serve-time min-max decimation (decimate,
CHART_DECIMATE_BUCKETS = 2000) thins the page to a few-thousand spine slots (full
recorded data stays on disk, view-only), and the reduction is tap-aware
(reduce_for_tap / ReduceKind): an unbounded cumulative curve (equity) keeps
the per-bucket min/max envelope so drawdowns survive, while a bounded level
series (exposure / the C10 bias level, ∈ [-1,+1]) reduces by per-bucket mean
so its net / duty-cycle level shows instead of collapsing to a solid ±1 band.
--panels renders stacked panels instead of the overlay.
Newcomer. aura new <name> scaffolds a data-only project — a paths-only
Aura.toml, a runnable starter blueprint blueprints/signal.json over the std
vocabulary (an SMA-cross → Bias strategy), .gitignore, and a project CLAUDE.md
— no crate, no build step (aura nodes new attaches a native node crate, C16/C17).
The engine also ships example blueprints under
crates/aura-cli/examples/r_{sma,breakout,channel,meanrev}.json. Honest gap against
the Guarantee's "populated trace immediately": these shipped blueprints are
strategies (a bias output, no declared taps), so aura run prints summary
R-metrics to stdout but writes no on-disk trace — a chartable trace today comes
from a blueprint that declares taps (single run) or from a --real … --trace family
campaign.
Deferred. Live sink streams during a run are not built — taps are buffer-then-drain (collected, then written after the run), and there is no local replay server or replay clock; the shipped face is post-run static HTML. Reworking tap draining into live subscribers (stream-to-disk / fold / live consumer) is #283. Embeddable chart / graph fragments for hosting in external pages are unbuilt (#150). The programmable analysis meta-level — families as composable orchestration operators — is C21's open direction, not this contract's.
History: c22-playground-traces.history.md
See also
- C27 — declared taps: the named sinks a run persists
- C8 — sinks are pure consumer nodes
- C21 — the World is the program the playground plays
- C24 — the topology data a future editor would round-trip
- C25 — visual surfaces as stateless projections
- C14 — headless core, two faces (the encode / store / render split)
- C18 — the run registry the trace store sits beside
- C9 — topology grown in Rust; the structure the viewer reflects
- C10 — the bounded bias / exposure level the viewer reduces by mean