Files
Aura/docs/design/contracts/c22-playground-traces.md
T
claude 9eb6d6b4f6 docs(readme, guides, glossary, ledger), bench: the surviving surface everywhere
Slice 8, closing the #319 retirement's prose and bench debt. Bench argv
rides exec (fixed_cost single-run probe, campaign surfaces, and the seed
helper that still called the retired sweep at runtime — caught by the
bench run itself); all five fingerprints unchanged against the committed
baselines, confirming record-line parity through the retirement. Live
docs (README, authoring guide, project layout, glossary) describe only
exec + campaign documents + graph introspect --params; three glossary
entries explicitly mark their verb retired. Ledger amendments per the C29
discipline — C25's executor-verb-set re-settled (exec + the --override
residue), C14's dual grammar reduced to the one dispatch, C24's
document-built runs/families + gated-intake route list (lockstep with the
code comment), C12's override clause and identity anchor repointed to the
runner-layer hash computation, C18/C22/C27/C01 mention rewrites —
superseded sentences moved verbatim to the history sidecars (c25's
created). Stale dual-grammar doc comment on is_blueprint_file rewritten.

refs #319
2026-07-25 22:45:24 +02:00

9.7 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 exec <blueprint.json> (#319) 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. exec carries no free-standing --trace <NAME> flag — naming a trace is the family / campaign path's job, not a per-run flag (the repeatable --tap TAP=FOLD selector, #310, only chooses which of the blueprint's own declared taps to subscribe, never names the trace).

Families. A campaign document's presentation.persist_taps (the closed tap vocabulary, C18/C27) requests traces for each cell's nominee, run through aura exec <campaign.json|id> (#319 — a document's axes replace the retired sweep|walkforward --real <SYM> --trace <NAME> grammar entirely; there is no per-invocation --trace/--name flag left on any surface): aura-runner::runner::persist_campaign_traces writes each member under a depth-2 fan-out runs/traces/{campaign8}-{run}/<cell>/<member>/, the name derived deterministically (derive_trace_name), never user-chosen. 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. 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 (ChartMetawindow.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 exec 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 campaign document requesting presentation.persist_taps.

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