Files
Aura/docs/design/contracts/c22-playground-traces.history.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

11 KiB
Raw Blame History

C22 — The playground is a trace explorer; sinks are the recording mechanism: history

FROZEN HISTORICAL RECORD. Each block below was true as of its cycle/date stamp and may be superseded; this file is NOT current truth and NOT a grounding surface. Current contract: c22-playground-traces.md.

Realization (cycle 0006). Sinks-as-recording-mechanism is realized at the substrate level: a recorded trace is exactly what a recording node pushed out of the graph (no engine recording registry; the constructing World holds each recording node's destination). The engine's single observe: usize affordance is removed — Harness::run returns () and recording is a node-side concern, so one run records many streams (one per recording node) instead of exactly one row. Recorded streams are sparse and timestamped (a record per fired cycle, tagged ctx.now()), matching a trace of timestamped events (C18). No new contract; the Harness API change (observe removed, run -> ()) is recorded here.

Realization (the web-from-disk visual face, #101). The C14/C22 web seam shipped (amendment 3b56efb): a recording run persists each drained tap as a columnar (SoA, C7) ColumnarTrace to runs/traces/<name>/ (aura-registry::TraceStore, beside the run registry's runs.jsonl), reachable via aura run [--real <SYM> …] --trace <name>; aura chart <name> [--panels] reads them back, aligns all taps on a synthetic-union timestamp spine via the post-run join_on_ts (C3 — not in-graph; C1 — pure, no live external call), and emits a static self-contained uPlot page (vendored like render_html's Graphviz-WASM). The engine stays headless (C14): encoding lives in aura-engine (ColumnarTrace, struct→JSON only), file I/O in aura-registry, rendering in aura-cli (render_chart_html + chart-viewer.js). First cut only — overlay (per-series y-scale) / timestamp-aligned panels; the served page injected the chart series but no run-context header (the run manifest is persisted on disk in index.json, deliberately not surfaced in that first cut — a ratified scope call). The header (#102) and serve-time decimation (#108) landed in cut 2 (see the served-page-hardening amendment below); the families-comparison view landed too (#107). A local server and replay-clock controls remain open (see "Open architectural threads").

Amendment (family-member traces, #104, d3cb5f8). The disk-trace layout extends to family runs: aura sweep|mc|walkforward --trace <name> persists each member as a nested standalone run-dir runs/traces/<name>/<member_key>/, reusing persist_traces/TraceStore verbatim (engine untouched — persistence is a side-effect inside each per-member closure). The member_key is content-derived and deterministic — sweep: the varying axes rendered as a filesystem-portable directory component (<axis-name>-<value> tokens joined by _, charset [A-Za-z0-9._-], case-less values, length-capped with an FNV fallback), MC seed{N}, walk-forward oos{ns} — never a runtime ordinal, because members run in parallel under the engine's Fn + Sync HOFs, where a counter would be schedule-dependent (C1); concurrent TraceStore::write targets disjoint member dirs, so it is lock-free. Opt-in: without --trace, stdout/registry are byte-unchanged. Because TraceStore resolves <name>/<member_key> as a subpath, aura chart <name>/<member_key> charts any single member with no view-side change — the write-side precondition for the family-comparison view (overlay / small-multiples across members) — now realized (#107; see the families-comparison amendment below).

Amendment (CLI --trace retired, #168 / #224). The CLI-verb --trace story above (single-run run --trace, amendment 3b56efb; per-member sweep|mc|walkforward --trace, #104) describes capabilities that no longer reach the code. run and mc refuse --trace (structurally parseable, refused at dispatch); the per-member family --trace was never wired to the blueprint sweep (run_blueprint_sweep's let _ = persist) and was silently dropped when the welded --strategy triple retired (#159/#220). #168 makes the surface honest — sweep/walkforward --trace now refuse up front (exit 2, forward-pointer to #224). The single live TraceStore::write site is the campaign presentation.persist_tapsruns/traces/<name>/ (persist_campaign_traces); aura chart <name> reads that back. Restoring CLI-side per-member trace-writing is deferred to #224. [Delivered 2026-07-11 (#224 + fieldtest B1 fix): sweep/walkforward --trace now WRITE per-member traces on the real-data campaign path (the depth-2 fan-out runs/traces/<name>/<cell>/<member>/), refusing only on the synthetic path; aura chart <family handle> resolves both the depth-1 campaign layout and the depth-2 fan-out (members keyed <cell>/<member>, C1-sorted). The refusal story in this amendment is historical.]

Amendment (real-data family source, #106, 8e5d14b). The family runs gain an opt-in real-data source axis: aura sweep|walkforward --real <SYMBOL> [--from <ms>] [--to <ms>] streams real M1 close bars from the data-server archive (the #71 M1FieldSource seam — C6 record-then-replay: a pre-recorded archive, never a live call mid-sim) instead of the built-in synthetic stream, per family member. A CLI-side DataSource provider (synthetic | real) supplies each member's source, pip (resolved from the recorded geometry sidecar (instrument_geometry) — C10 refuse-don't-guess; a symbol with no recorded geometry exits 2 before any data access), window, and — for walk-forward — its WindowRoller sizes (synthetic: bar-index 24/12/12; real: fixed calendar-time 90d/30d/30d in ns; CLI flags for these are deferred). [Amended 2026-07-11 (#239): on the dissolved walkforward --real/mc --real sugar path the fixed 90/30/30 sizes are a ceiling, not a constant — fit_wf_ms_sizes (aura-cli) passes them through byte-identically whenever they fit the resolved campaign window, and scales them down preserving the 3:1 IS:OOS ratio (step = OOS, one roll at the minimum) when the window is shorter; authored process documents still validate their declared sizes as-is.] The engine, ingest, and registry are untouched (C9 — the whole change is in aura-cli); the synthetic path is byte-unchanged. MC is excluded (#106 Fork A): its seed varies a synthetic price-walk realization (C12 axis 4), which is undefined over real data's single realization — aura mc --real refuses with exit 2; a real MC needs a bootstrap-resampling axis (its own cycle). The real walk-forward member key oos{from.0} widens from a small synthetic bar index to an epoch-ns integer (still portable, collision-free). The built-in demo strategy's length grid is likewise data-kind-dependent (DataSource::strategy_lengths): synthetic keeps the short lengths that fit the 18/60-bar demo streams (trend SMA {2,3}×{4,5}, MACD 2/4/3), real uses realistic M1 lengths (trend {50,100}×{200,400}, MACD 12/26/9) so the cross is a genuine trend signal over tens of thousands of bars, not noise. This — like the roller sizes — is a demo-strategy calibration patch; the real answer is project-authored strategies (C9: a project crate owns its own grid), deferred to the project-env work.

Amendment (families-comparison view, #107, 4c64feb). The family-comparison view the #104 amendment left open is now realized. aura chart <name> classifies the name on disk (TraceStore::name_kind: a top-level index.json is a single run; else member subdirs with index.json are a family; else not-found) and, for a family, overlays one tap (default equity, --tap to pick) of every member in one self-contained uPlot page: build_comparison_chart_data emits one labelled series per member, all on a single shared y-scale — members measure one identical quantity, so a shared scale is what makes them comparable, unlike the single-run overlay of different taps (per-series scales). The series align on the same union-ts spine via join_on_ts, so 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). The view consumes a set of runs (&[FamilyMember] from TraceStore::read_family, sorted by key for determinism, C1); a family is its first producer — the deliberate first step toward the programmable analysis meta-level (C21) without rebuilding the orchestration axes (that is its own later cycle). Name resolution is made a total function by the write-guard TraceStore::ensure_name_free, called once per tracing command (run/run <bp.json>/run --real → Run; sweep/mc/walkforward → Family) to refuse cross-kind name reuse — so the one ambiguous on-disk state (a name used by both a run and a family) is unreachable. Every error path exits 2, never panics (C18/C10 refuse-don't-guess). Engine untouched (C9/C14): the whole change is aura-registry (the resolver + classifier + guard) + aura-cli (the builder + emit_chart name-kind branch + --tap); the single-run page is byte-unchanged when no --tap is given. Still deferred: multi-column tap selection (build_comparison_chart_data projects column 0 — the comparison taps in scope are single-column f64; #47); and the orchestration-composability rebuild (sweep/MC/WFO as composable meta-level tools).

Amendment (served-page hardening — cut 2, #102 + #108, 476342d). The two deferred follow-ons the #107 amendment named are realized, both confined to aura-cli (engine

  • registry untouched, C14). Run-context header (#102): a ChartMeta (kind/name/commit/window/broker/seed/taps, + member count for a family, + bound params for a single run) is built from the RunManifest in build_chart_data/build_comparison_chart_data, injected into window.AURA_TRACES.meta, and rendered by a pure buildHeader in chart-viewer.js (headless .mjs-guarded, like buildCharts). The family window is the span across members (min from, max to) — the only reading that labels a disjoint walk-forward family's true OOS coverage (it collapses to the shared window for sweep/MC). Decimation (#108): a pure decimate(ChartData, buckets) -> ChartData min-max transform thins the served page to ≤ ~2·CHART_DECIMATE_BUCKETS spine slots (per-bucket min+max, nulls preserved), applied in emit_chart on both paths — a multi-year M1 family now renders a few-thousand-point page instead of 100s of MB; full recorded data stays on disk (view-only), and the walk-forward null-fill page collapses for free. Deterministic (C1): pure bucketing over the sorted-deduped spine, no-op under budget. Refinement (#111, 2957561): the per-bucket reduction is tap-aware — an envelope series (equity) keeps min/max, but a bounded level series (the C10 bias level — pre-reframe: exposure — ∈[-1,+1]) reduces by per-bucket mean (a ReduceKind on each Series, set by reduce_for_tap). Without it min/max made every multi-year exposure a solid -1..+1 band (each bucket straddles many sign flips), reading as per-point oscillation; the mean shows the net/duty-cycle level. Deferred refinements: rendering the min/max envelope honestly as range bars / OHLC rather than a polyline (#112); a --width budget flag and true intra-bucket min/max ordering for the non-default continuous x-mode (#110).