Files
Aura/docs/design/contracts/c22-playground-traces.md
T
claude 8688a60ded docs(ledger): split the design ledger into an INDEX map, per-contract live files, and history sidecars
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.
2026-07-21 16:40:36 +02:00

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 (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 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