Derive the broker-independent position-event table (#114's PositionEvent rows)
from a completed PositionManagement run, as the first difference of the executed
book (deal = target - book - in_flight; in_flight = 0 for instant backtest
fills). A pure post-run reduction `derive_position_events(record, instrument_id)`
in aura-engine, sibling of summarize_r, reading the 14-column PM record
positionally; a reversal emits Close then the opposite open at one event_ts
(close first); a window-end open position emits its open with no synthetic Close.
Supersedes the rolled-back 0064 exposure-integral derive (abandoned per #117):
this derives from the executed book (the PM record), never from exposure deltas.
Stage-2 fixed-fractional / currency / equity-feedback sizing is out of scope
(#116 owns the equity->Sizer z^-1 register per C10).
Boss-signed: specify Step-5 grounding-check PASS (8/8 load-bearing assumptions
ratified by named green tests). Fork decisions recorded on #115.
refs #115
Architect audit clean (no drift, no debt): raw sqn verified bit-identical,
C18 serde back-compat + C4 SoA seam + C1 determinism + Part-B symmetry all hold.
- Ledger C10: add a "Realization (cycle 0067, #130 + #135)" note — SQN100 as
the turnover-robust opt-in rank key (raw sqn / default ranker byte-unchanged,
serde(default) for back-compat), and per-member --trace now symmetric across
all three sweep strategies via the shared persist_traces_r.
- Glossary: R-metrics list gains sqn_normalized; the SQN entry documents the
SQN100 variant (was "deferred #130").
- Remove the ephemeral spec + plan (cycle closed; rationale lives in the ledger
and git history).
refs #130, refs #135
Two settled, additive follow-ups to the cycle-0065/0066 R-sweep surface,
named together by the user under /boss. Signed on the grounding-check PASS
(9 load-bearing current-behaviour assumptions, each ratified by a named
green test).
Part A (refs #130) — n-normalized SQN (SQN100): a new opt-in ranking metric
sqn_normalized = (mean_R/stdev_R)·√(min(n,100)), turnover-robust across sweep
members with different trade counts. Additive RMetrics field (serde(default)
for C18 back-compat) + a new Metric::SqnNormalized rank key mirroring Sqn.
Raw sqn and the default ranker are unchanged; the 0066 single-run golden is
re-baselined (the r: block gains one field — intended, not drift).
Part B (refs #135) — wire --trace for the stage1-r sweep: stage1_r_sweep_family
honours --trace by persisting each member's equity/exposure/r_equity under
runs/traces/<n>/<member_key>/ via the existing persist_traces_r, mirroring
momentum_sweep_family; the interim refusal guard in run_sweep is dropped.
Makes --trace symmetric across all three sweep strategies.
Derived fork decisions logged on #130 and #135 (reconciliation comments).
Cycle 0066 (#133) closed. The close audit (architect) found the family ratified against C12 axis-2 (rank-by-metric over the C18 family store) and the C10 R framing, with one [high] gap (swept-member manifest omitted the fixed R-defining params) fixed forward in 499121e.
- Ledger: a C10 realization note records the durable rationale -- SQN as the single-number sweep objective, the signal-only grid (the stop defines R, so varying it breaks cross-member SQN comparability -- the SQN100 #130 motivation), the R-metric rank vocabulary, and the manifest-completeness fix.
- Glossary: new 'R metrics' and 'SQN' entries.
- The ephemeral spec + plan are removed; the ledger + git history are the durable record.
Follow-ups filed: #135 (per-member --trace for the stage1-r sweep). refs #133
Cycle 0066, reference issue #133. Settled design (deferred from 0065): make SQN
the single-number objective for ranking a Stage-1 sweep family by R signal
quality.
Two halves:
- aura-registry: metric_cmp learns sqn / expectancy_r / net_expectancy_r (all
higher-is-better, reaching into RunReport.metrics.r); a member with r:None sorts
to the worst position. No persistence change (the family store already carries r).
- aura-cli: --strategy stage1-r sweeps the stage1-r harness over a fast×slow signal
grid (stop & sizing fixed — risk_budget is R-invariant, bias.scale is sign-only
for flat-1R, both degenerate axes; the stop defines the R unit so varying it
breaks cross-member SQN comparability — see the #133 decision log). The stage1-r
topology is extracted into a shared helper so the single run keeps its identical
binding (output unchanged by construction) and only the sweep floats fast/slow.
Signed under /boss via the grounding-check PASS (the autonomous signature). The
first grounding pass BLOCKed a false claim that the stage1-r single-run output was
already byte-pinned; the spec was revised so a golden characterization test is
added against current HEAD before the refactor (verifying byte-preservation rather
than assuming it), and re-grounded to PASS.
refs #133#117
The 'R-based signal quality (Stage 1)' milestone is complete and ratified
(iter-1/2/3 + the corrective primitives mini, the tiny-pip fix, the metric rename;
end-to-end fieldtest green). Closing the cycle:
- Milestone container + issues #119/#126/#127/#128/#129 closed on the tracker.
- The ephemeral per-cycle spec (0065) and plans (0065-0069) are git-rm'd per the
cycle-close convention; the durable rationale lives in the design ledger (the C10
cycle-0065 realization note) plus git history.
Follow-ons tracked: #130 (n-normalized SQN), #131 (CLI UX), #132 (harness polish),
#133 (R-sweep + rank-by-sqn), #134 (strategy-output param-namespace rename).
refs #117
Amend the cycle-0065 spec with the "Iteration 3 - the CLI surface" design:
make the Stage-1 R layer operable from the command line (the research loop's
primary verb, ergonomic for Claude to drive), splitting the CLI/recording
surface out of the originally-lumped iter-2.
Design (forks derived under /boss, user-delegated "du entscheidest das Design",
recorded on #117):
- Promote vol_stop + risk_executor from test fixtures to public aura-engine
composite-builders; the embedded stop generalizes to a StopRule{Fixed,Vol}
enum (a C11 structural axis), vol_stop the volatility-defined default.
- aura run --harness <name>: a compile-time selector (sma|macd|stage1-r) over
Rust-authored built-ins - running, not wiring (C9/C17 intact: no runtime
registry, no DSL). Replaces the run literal-slice arms with a parse_run_args
tokenizer so --harness/--real/--trace compose orthogonally; --macd kept as an
alias.
- The stage1-r harness fans one bias into both SimBroker (pip) and the
RiskExecutor (R), so one RunReport carries both yardsticks honestly; the run
path folds summarize_r -> RunMetrics.r (additive, skip_serializing_if keeps
pip-only/legacy JSON byte-unchanged), round_trip_cost=0.0 (Stage-1 is
frictionless; a --cost knob is a follow-on).
- An r_equity tap (LinComb of cum_realized_r+unrealized_r) persists via an
extended persist_traces and renders through the existing aura chart --tap -
no new viewer code.
Scope line: "operable from the CLI" = run+inspect a Rust-authored harness (the
#109 (a) reading the contracts settle), not authoring topology in the CLI; the
broad #109 meta-level stays a separate future milestone. Deferred follow-ons:
R-sweep families + rank-by-sqn, rich R-record charting, the bias_sign_flips
metric rename (never carried out in iter-0; field is still exposure_sign_flips).
Verified before signing: grounding-check PASS (all substrate + post-patch
current-code claims ratified by green tests) - the autonomous /boss signature;
plus an advisory 5-lens spec-skeptic panel (criterion/invariants/scope SOUND;
ambiguity + plan-readiness BLOCKs resolved inline: the argv tokenizer, the
3-tap persist change, the round_trip_cost value, the dual-tap arg provenance),
re-grounded after the patch.
refs #129#117
VolStop was a design error (a fused node justified by a missing primitive). A
node is a primitive only if not DAG-expressible from other primitives; a missing
primitive is added, not fused away. Adds Mul + Sqrt primitives, removes the fused
VolStop, expresses the vol stop as a composition (rolling EWMA stddev = k *
Sqrt(Ema(Mul(d,d), length))). Spec 0065 b gets a correction note; full record on #117.
refs #117#119
Stage-1 "R-based signal quality" cycle spec (milestone #119/#126/#127/#128/#129,
contract C10). Signal quality measured in R on a strategy's unsized bias stream,
feed-forward (no equity feedback): bias -> stop-rule -> position-management ->
per-trade R-outcomes -> summarize_r fold -> E[R]/SQN.
Design adversarially hardened (12-juror refute panel; decisions logged on #117):
- R is stop-defined and LATCHED at entry (frozen R-denominator, never re-read).
- Stop-outs are NOT capped at -1R (honest loss tail; gap-through -> R < -1).
- Fills one-cycle-lagged (SimBroker no-look-ahead discipline; RED test mandatory).
- First stop is volatility-scaled (VolStop = k * EMA(|d price|)), not constant
(a constant makes R just rescaled pips).
- position-management emits a dense per-cycle R-record (trade ledger + R-equity +
window-end handling), tapped by a Recorder, folded post-run by summarize_r
(a post-run fold, NOT an in-graph node).
- Sizer = load-bearing flat-1R seam; R computed size-invariantly. Veto = doc seam,
not a runtime node (DCE deletes an identity).
- exposure -> bias rename lands first (compiler-driven).
Grounding-check PASS (11 assumptions ratified by green tests; auto-signed under /boss).
refs #117
Cycle-close tidy for broker-foundation (InstrumentSpec deploy metadata #113 +
PositionEvent schema #114).
Architect drift review (3638d48..HEAD): code is drift-clean. PositionEvent matches
the C10 column spec field-for-field (no open_ts, unsigned volume, direction-is-action,
bare-i64 action); the engine run-loop is untouched (C14); InstrumentSpec stays Copy
metadata at the ingestion edge (C7/C15); no instrument identity reaches the hot path.
The only drift was stale ledger prose, fixed here:
- C10 realization note: added a cycle-0063 entry recording that the position-event
*schema* landed (PositionAction/PositionEvent), while derivation (#115) and the
realistic broker nodes (#116) remain the deferred decoupled layer.
- C15 realization note: replaced "minimal today (pip only)" with the six-field
deploy-grade floor and the #124 currency-type forward note (quote_currency is
&'static str; the #124 resolver chooses how its runtime tier carries currency).
Regression (verbatim): cargo test --workspace = 458 passed / 0 failed (exit 0);
cargo clippy --workspace --all-targets -- -D warnings = clean (exit 0).
Carry-on (not actioned, justified): the inline reversal test is a shape-pin only —
the behaviour test arrives with #115's derive, and the E2E twin already exercises the
reversal wire shape. The quote_currency &'static str / #124 resolver concern is logged
on #124 as a deliberate deferred design choice.
This is drift-clean, NOT a milestone close — the realistic-broker milestone needs its
end-to-end fieldtest green, which depends on #115/#116/#120-123 still to land.
Removed the ephemeral cycle artifacts (spec + plan) per the lifecycle convention.
The broker milestone's (C10 A-side) foundation: two pure value-type
deliverables, no broker and no derivation yet.
- #113: extend InstrumentSpec with deploy-grade fields (instrument_id,
contract_size, pip_value_per_lot, min_lot, lot_step, quote_currency) +
populate the vetted table for the five instruments; refuse-don't-guess
arm stays. Permanent authored floor of the override hierarchy (#124).
- #114: PositionAction { Buy, Sell, Close } (closed enum, serde-encoded as
i64) + PositionEvent row in aura-engine, faithful to the C10 column spec
(no open_ts, unsigned volume, direction is the action).
Signed under /boss on a grounding-check PASS (all current-state assumptions
ratified by green tests; the two new types are greenfield, expected).
Derived fork decisions (quote_currency &'static str; action enum encoded as
i64 0/1/2; stable instrument_id + overridable vetted values) recorded on #113.
refs #113#114
Cycle-close tidy for visual-world-cut-2 (decimation #108 + run-context header #102).
Architect drift review (53eeba5..HEAD): the only drift was a STALE LEDGER — the C22
section still listed #102 (run-context header) and #108 (decimation) as deferred/open
after they shipped in 476342d. Resolved in this commit:
- the #101 first-cut note no longer claims "not yet a run-context header";
- a new "served-page hardening — cut 2" amendment records #102 (ChartMeta +
buildHeader, family window = span across members) and #108 (the pure decimate
min-max transform) as landed, both CLI-side (C14);
- the "Open architectural threads" list drops the now-landed run-context header.
Substantive gates (self-verified): C14 honoured — all decimation/header/pixel logic
is in aura-cli (render.rs/main.rs/chart-viewer.js); the only engine touch in range is
the one-line #100 cell.rs doc. C1 preserved — decimate is pure, deterministic
bucketing over the sorted-deduped spine, no-op under budget, monotonic spine (pinned
by a unit test). Regression: cargo test --workspace = 446 passed / 0 failed; cargo
clippy --workspace --all-targets -D warnings clean. No separate baseline script.
Residual debt filed as #110 (idea): the decimation budget is a fixed const (no
--width flag), and the min-then-max sub-pixel ordering is a real ts-displacement in
the non-default continuous x-mode. Both are local aura-cli refinements, neither a bug.
Ephemeral spec/plan removed at cycle close (docs/specs/0062, docs/plans/0062).
Cycle 0062 tidy: drift-clean after this ledger amendment.
Served-page hardening for the families-comparison chart, bundling two issues that
share one code locus (the aura-cli render path) and one purpose (C22/C14: make the
#107 view openable + legible on real multi-year families):
- #108 — serve-time min-max decimation as a pure ChartData->ChartData transform in
emit_chart (both single-run and family paths), bounding the served page to a few
thousand points regardless of the underlying M1 point count; full data stays on
disk; the WFO null-fill page collapses for free. Engine/registry untouched.
- #102 — a ChartMeta threaded from RunManifest into window.AURA_TRACES, rendered by
a new pure buildHeader in chart-viewer.js (headless .mjs guarded), showing
name/window/broker/commit/taps (+ member count for a family).
Signed under /boss on the Step-5 grounding-check PASS (10/10 load-bearing
assumptions ratified by green tests).
refs #108
refs #102
Architect drift review over 99fd32b..HEAD (the housekeeping commits #99/#98 plus
the 0061 cycle): code drift-clean. Engine untouched (aura-engine + aura-core diff
empty — C9/C14); C21 realized faithfully (the comparison view consumes a set of
runs, a family is the first producer, no orchestration-axis rebuild); C18/C10
refuse-don't-guess honoured (every new error path exits 2, never panics; all six
tracing entry points guarded before persist, so name resolution is total). The only
finding was a ledger-realization gap, lifted in this commit.
Ledger (docs/design/INDEX.md): added a C22 amendment recording the
families-comparison view as realized (#107) — aura chart <family> overlays one tap
across a family's members on a shared y-scale, union-ts spine giving one mechanism /
three correct readings, the write-guard totalising name resolution; reconciled the
two stale "families-comparison ... remain open" lines (the #104 note and the
open-threads list) and named the deferred follow-ons (#102 run-context header, #47
multi-column tap selection, the orchestration-composability rebuild).
Regression: cargo test --workspace green (no failures), clippy --workspace
--all-targets -D warnings exit 0, cargo build --workspace clean — verified by the
orchestrator before close. No baseline to update (aura's regression gate is the
gated determinism suite, which is part of the workspace tests).
Retire the cycle's ephemeral spec + plan (0061). refs #107
A read-side cycle: `aura chart <family>` overlays one tap (default equity) of all
members of a sweep / MC / walk-forward family in one self-contained uPlot page,
consistent across kinds via the existing union-ts spine. The view consumes a
"set of runs to compare" (a family is its first producer) — the first step toward
the programmable analysis meta-level (C21), without rebuilding the orchestration
axes. aura-registry gains name_kind / read_family / ensure_name_free (a write-guard
making name resolution a total function); aura-cli gains the comparison ChartData
builder + emit_chart branch + --tap. aura-engine untouched (C9/C14).
Brainstorm-ratified, grounding-check PASS (11/11 current-behaviour assumptions
ratified by green tests). Signed under /boss on the grounding PASS.
refs #107
Architect drift review (b92aab7..HEAD): the cycle is contract-clean — C9 (engine
untouched, whole change CLI-side), C6/C1 (real bars streamed from the pre-recorded
archive, real sweep byte-deterministic), C10 (refuse-don't-guess shared by run
--real and the family path), C12 (MC-exclusion fenced: mc --real exits 2). One
medium ledger-gap, now closed; one low item ratified as carry-on.
- Ledger: docs/design/INDEX.md C22 gains a "real-data family source" amendment
(#106) recording the opt-in real-data axis on sweep/walkforward families, the
DataSource provider, the MC exclusion (Fork A), and the oos{ns} key widening.
This was the only drift — a documentation gap, not code drift.
- Ratified carry-on [low]: the real walk-forward member key oos{from.0} is now an
epoch-ns integer; portable and collision-free, covered in practice by the
walkforward_real test (>=2 distinct oos<ns> dirs) and the 9-window E2E probe.
No explicit ns-uniqueness assertion beyond starts_with("oos") — acceptable; a
dedicated pin is not worth an iteration.
- Retire ephemeral spec/plan 0060 (git rm) per the cycle-artifact lifecycle; the
durable record is this ledger amendment + the git history.
No regression scripts configured (architect is the gate). Full workspace test +
clippy -D warnings green at close.
refs #106
Opt-in `--real <SYMBOL> [--from <ms>] [--to <ms>]` for `aura sweep` and
`aura walkforward`, mirroring `aura run --real`: each family member streams real
M1 close bars from the local data-server archive instead of the synthetic stream.
MC is excluded — its seed varies a synthetic price-walk realization, undefined
over real data (one realization -> identical members; a real MC needs a
bootstrap-resampling axis, its own cycle).
Engine, ingest, and registry untouched: a CLI-side `DataSource` provider replaces
the hardcoded `VecSource`. `M1FieldSource` already impls `Source`; `WindowRoller`
already rolls over epoch-unit timestamps.
Boss-signed: Step-5 grounding-check PASS — all 8 load-bearing current-behaviour
assumptions ratified by named, currently-green tests.
refs #106
Cycle-close tidy for the generic portable sweep member-key + bool-param demo
strategy (spec/plan 0059, reference issue #105, implementations 27f850d + 0b73a75).
Architect drift review (range 1d6fafb..HEAD): clean — no drift, no debt.
- C1 (determinism): varying_axes + member_key are pure functions of the grid
point, no schedule state; the engine carries no trace state (consistent with
the #104 design + the C22/#101 ledger note).
- C7/C8/C9: LongOnly is a clean one-in/one-out f64 node, None until warmed (C8
filter), the first bool *param* through the already-ratified Cell::bool()
path; momentum is an ordinary composite of existing nodes; brokers stay
downstream (C10). C18/C21/C22 upheld.
- The engine accessor exposes the binder's OWN axis-cardinality knowledge (it
holds the value-lists), not a presentation concern — formatting stays in the
CLI. The hand-rolled fnv1a64 mirrors the ledger-blessed SplitMix64 precedent
(harness.rs): tiny, dependency-free, bit-stable-on-purpose (std DefaultHasher
is unstable across releases; on-disk-name stability is the per-case C16
reason). LongOnly is a genuine universal aura-std block (long-only is
asset-agnostic). The ledger amendment matches the code.
Regression: the project declares no dedicated regression command; the gate is
the architect review plus the full suite, verified green at 0b73a75 (cargo test
--workspace, clippy --all-targets -D warnings).
Spec docs/specs/0059 and plan docs/plans/0059 retired (git rm) per the ephemeral
active-cycle artefact lifecycle; the durable rationale (the portable member-key
convention) lives in the ledger amendment (docs/design/INDEX.md C22/#101 note)
landed in 0b73a75.
closes#105
Generalises the family-member trace key (#105 foot-gun): derive it from the
axes that actually vary (not two hardcoded axis names), and render it as a
filesystem-conformant directory name on every major OS — charset [A-Za-z0-9._-],
case-less values (no case-insensitive-FS collision), length-bounded with an
FNV-1a fallback for the unbounded-grid edge.
Adds the first bool *param* node (LongOnly) and a momentum demo strategy
(Ema -> Sub -> Exposure -> LongOnly -> SimBroker) swept via a new
`aura sweep --strategy <sma|momentum>` selector — the empirical proof the
generic key works for a strategy whose params (incl. a bool) differ entirely
from the SMA-cross demo.
Boss-signed on the Step-5 grounding-check PASS (an independent fresh-context
agent): all 9 load-bearing current-behaviour assumptions ratified by green
tests; the end-to-end bool-param sweep path is correctly scoped as new
behaviour proven by new tests, not assumed. Workspace type-checks clean.
refs #105
Cycle-close tidy for family-member trace persistence (spec/plan 0058,
reference issue #104, implementation d3cb5f8).
Architect drift review (range 06fdafa..d3cb5f8): drift_found, low-grade
only — no contract violated. C1 (determinism), C7 (SoA), C8 (sinks),
C18/C21 (registry/families) all upheld; the opt-in --trace path is
genuinely byte-neutral. Two items, both resolved here:
- [high] Ledger gap: the C22/#101 disk-trace note recorded only the
single-run runs/traces/<name>/ layout. This cycle establishes a new,
load-bearing on-disk convention (per-member nested standalone run-dirs
runs/traces/<name>/<member_key>/) that the deferred family-comparison
view will build on. FIXED: amended the C22/#101 realization note in
docs/design/INDEX.md to record the layout, the content-derived
deterministic member keys, and the parallel-safe lock-free write.
- [medium] sweep_member_key hard-codes two grid axes; collision-free only
over today's built-in grid, a latent foot-gun if the grid varies an
unlisted axis. Not a current defect (one grid, covered, fail-loud on a
missing listed axis). CARRY-ON: filed #105 (idea) for generalised key
derivation when the grid generalises.
Regression: the project declares no dedicated regression command; the
gate is the architect review plus the full suite, verified green at
d3cb5f8 (cargo test --workspace, clippy --all-targets -D warnings).
Spec docs/specs/0058 and plan docs/plans/0058 retired (git rm) per the
ephemeral active-cycle artefact lifecycle; durable rationale lifted to
the ledger amendment above.
refs #104
Persist each family member's equity/exposure streams to disk (opt-in
--trace on sweep|mc|walkforward), mirroring the single-run trace path, so
the C21 family-comparison view has per-member data and any single member
is chartable today. Write side only; the comparison view, decimation,
and a binary trace container are deferred.
Autonomously signed under /boss on the Step-5 grounding-check PASS.
refs #104
Architect drift review over 5bff7e0..HEAD: clean, no drift, no debt. C14/C7
firewall intact (main.rs byte-unchanged → build_chart_data still returns
ChartData{xs,series}, the injected window.AURA_TRACES payload shape untouched;
the ordinal index spine is derived client-side). C22 holds — the
ordinal/continuous toggle + wheel-zoom/drag-pan/dblclick-reset are view
affordances over a recorded trace (read-only setScale on the x range), not a
scene editor. The #102 manifest-header gap is neither closed nor regressed,
still tracked.
Regression gate: cargo test --workspace green (incl. the four chart guards +
render inline tests), clippy --all-targets -D warnings clean; aura has no
separate regression script, so the suite + architect are the gates. cycle
clean.
Spec/plan 0057 retired per the ephemeral-artifact lifecycle (durable record:
the feature commit 45cc1c4, the #103 decision-log comments, and git history).
refs #103
Tier-1 comfort pass for the served trace chart (#103): an ordinal x-axis
default (collapses non-trading gaps) with a one-click continuous toggle, plus
wheel-zoom / drag-pan / dblclick-reset, a value-readout legend, and panel
cursor-sync. Confined to the read/render side (chart-viewer.js + render.rs
CHART_HEAD); the Rust serve contract is unchanged.
Boss-signed: grounding-check PASS (five current-behaviour assumptions ratified
by green node guards + render.rs inline tests). Derived-fork decision log in
the #103 comments.
refs #103
Cycle-close audit for the trace-charts visual face (#101, shipped across
8bb5256 persist + 15ee6d5 serve). Architect drift review (13941a2..HEAD)
+ the regression gate (cargo build/test/clippy --workspace, all exit 0,
orchestrator-verified).
What holds (carry-on): C14 clean — aura-engine/aura-core carry zero
UI/HTML/uPlot knowledge; ColumnarTrace does struct->encoding only, file
I/O in aura-registry, rendering in aura-cli. C1/C3 clean — persistence
and serve-time alignment are post-run pure reductions (join_on_ts at
serve time only, never in-graph). C7 clean — on-disk f64 columns +
kinds-tagged base types, the documented coercion honest, no 5th scalar.
Resolutions:
- [fix] Added the C22 realization note now that the web-from-disk seam
shipped (the amendment 3b56efb had deferred it to "when the seam
ships").
- [ratify] The served page injects the chart SERIES but drops the run
manifest (no run-context header) — a deliberate, defensible naive-viewer
scope call (the manifest is on disk in index.json; a header matters most
for the deferred families/comparison work, C21). The deviation from spec
0056 §1/§4 landed unflagged in 15ee6d5; it is now flagged here and
tracked as a follow-up. refs #102
- [tidy] Retired this cycle's ephemera (spec 0056, plans 0055/0056) and
the stale debris from the prior Runway close (spec 0055-fieldtest) per
the ephemeral-artifact convention; the durable record is the ledger +
git history.
Cycle drift-clean (modulo the ratified, tracked manifest-header gap).
refs #101
First cut of aura's visual face (issue #101; the web-from-disk pivot
ratified into C14/C22 this session, commit 3b56efb): tap arbitrary
recorder streams from a run, persist them to disk as columnar (SoA)
JSON, and serve them as a self-contained uPlot HTML chart — feeds
overlaid on a shared timestamp axis, or timestamp-aligned in stacked
panels. `aura run [--macd] [--trace <name>]` and `aura run --real <SYM>
... [--trace <name>]` persist; `aura chart <name> [--panels]` serves.
Both modes share one synthetic-union-spine join_on_ts alignment.
Signed via the specify auto-sign gate: the user is away under an
explicit "work fully autonomously, document decisions" mandate, so no
manual signature was available. All objective gates green (precondition
clean, self-review clean, grounding-check PASS), and a unanimous 5-lens
adversarial spec-skeptic panel (criterion / grounding / scope-fork /
ambiguity / plan-readiness all SOUND).
The panel earned its keep — two self-correction rounds to unanimity:
- Round 0 caught (a) panels shipped NOT timestamp-aligned, contradicting
the user's verbatim "aligned nach Timestamps in Panels" — corrected to
conform to issue #101 Fork A; (b) the USAGE advertised `--macd --trace`
with no dispatch arm — corrected to an orthogonal `--trace` composing
with every run form via a shared persist helper.
- Round 1 caught the union-spine -> join_on_ts mapping under-specified
(which tap is the spine?) — corrected with an explicit 3-step
synthetic-empty-spine mechanism (no privileged tap, no points dropped),
plus a to_rows uniformly-f64 clarification.
The two delegated naive-viewer decisions (per-series y-scale, union
spine) are documented with provenance on issue #101.
refs #101
Milestone-close fieldtest for "Runway — real-data ergonomics & honesty
hardening". Four curated end-to-end scenarios derived top-down from the
milestone promise, run as a downstream consumer against real GER40/EURUSD M1
bars (public interface only; no crates/*/src read):
1. per-instrument pip honesty (#22) — GER40 index pip 1.0 AND EURUSD forex
pip 0.0001 both honest end-to-end; AAPL.US/FOOBAR un-vetted refused
exit 2, no stdout leak
2. real GER40 M1 open via open_ohlc from aura-ingest ALONE (#80/#81/#92) —
no data-server dep, 53173 real bars, C4 O/H/L/C order, ts-window inverse
3. multi-tap ts-join (#93) — two taps at different cadences, 27744 rows,
0 by-ts mismatches; warm-up None rows where a positional zip would corrupt
4. run-registry coherence (#73/#82) — list/rank retired exit 2, families/
family live, two-store Registry read-back + rustdoc
Findings: 4 working, 1 friction, 1 spec_gap.
- friction: Cell exposes only from_i64/from_f64/... while the side-by-side
Scalar is a tagged enum with public I64(..)/F64(..) variants. The natural
param-authoring entry point is Scalar (Cell is the hot-path buffer carrier),
so the wrong reach is a self-correcting compile snag — ergonomic only.
Filed as a follow-up idea.
- spec_gap: unknown family id exits 0 empty — ratified into the C18 note in
the preceding commit (treat-as-empty contract).
Gate verdict: GREEN. All seven axes mapped to a covering scenario and exercised
on real bars; both finding classifications and the one material coverage gap
were adversarially re-verified. That gap — #22's FX-vetted pip face, untested by
the original scenario set — was probed (EURUSD -> sim-optimal(pip_size=0.0001),
FX-scaled, not index-mis-scaled) and folded into scenario 1, so the pip promise
is demonstrated on both positive faces. Every scenario re-run independently on
HEAD before commit.
Audit (architect drift review + cargo test --workspace) clean: code matches the
spec, the engine stayed domain-free (no instrument identity in Ctx / the hot
path), InstrumentSpec sits beside the hot path per C7/C15. No regression scripts
configured (no-op gate); the full suite is green.
Ratify: the architect flagged one ledger-realization gap — C10/C15 described
per-instrument pip as standing design intent but carried no realization note for
the now-shipped channel. Added dated realization notes to C10 (the per-instrument
pip channel + the refuse-don't-guess honesty rule) and C15 (instrument specs as
metadata, first realized by InstrumentSpec/instrument_spec).
Retire the ephemeral cycle artefacts (spec 0054, plan 0054) per the active-cycle
lifecycle. Deferred follow-up: #98 (route the runnable GER40 examples through the
lookup).
refs #22
Cycle 0053 (multi-tap trace join on timestamp, #93) closes drift-clean.
Audit (architect drift review over 066638e..HEAD against docs/design + the
CLAUDE.md domain invariants):
- C8/C18 honoured: join_on_ts is a post-run reduction over recorded
(Timestamp, Vec<Scalar>) sink streams, a true sibling of summarize/f64_field
in the report layer — correct architectural home.
- C3 not violated: the join runs post-run over already-recorded output, not
in-graph; no as-of join smuggled into the DAG. C1 ts-uniqueness is a
documented precondition, not a runtime check — consistent with how the
engine leaves wiring-time invariants to contract.
- Option-per-side keeps the engine honest (reports presence; the consumer owns
the 0.0/-1/false defaults). The hand-rolled *_by_ts HashMaps are fully gone;
the two RED unit tests pin the #93 cardinality-misalignment + spine-drop.
- No drift, no debt, no ledger entry needs updating. Recommendation: carry on.
Regression gate: no dedicated regression scripts configured (optional per the
conventions); build/test/clippy --workspace --all-targets is the gate and ran
green — the gated GER40 byte-identity tests executed against the local archive
(not skipped), proving drain_trace's trace is byte-preserved across the
refactor.
Per the artifact lifecycle (committed while the cycle is live, git rm at cycle
close), the ephemeral spec and plan are retired here; their durable rationale
lives in the implementation commits 74324d1 / 35c5adc and the git history.
Ratified design for a reusable, spine-anchored join_on_ts helper in
aura-engine that fuses multi-tap Recorder traces on the recorded timestamp
(Option-per-side: the engine reports presence, the consumer interprets
absence). Refactors drain_trace onto it and documents the cadence rule on
the Recorder API. Resolves the #93 zip-by-index panic.
refs #93
Cycle 0052 (real-data source-open seam, Runway-1) closes drift-clean.
Audit (architect drift review over aedaa5d..HEAD against docs/design + the
CLAUDE.md domain invariants):
- C3 (one ms<->ns crossing) strengthened — the inverse is seam-private and the
only consumer-side divide left; every consumer ns_to_ms / unix_ms_to_epoch_ns
wrap is gone.
- C4 (O/H/L/C merge tie-break order) now in exactly one vetted home (open_ohlc);
the hand-spelled four-field loops are gone.
- C12 (one shared Arc<DataServer>/cache) preserved; C7 surface additive.
- No drift, no debt, no ledger entry needs updating. Recommendation: carry on.
Regression gate: no dedicated regression scripts configured (optional per the
conventions); build/test/clippy --workspace is the gate and ran green — the gated
open_ohlc_seam behaviour-preservation test executed against the local archive
(not skipped), proving byte-identity vs the ms-path.
Per the artifact lifecycle (committed while the cycle is live, git rm at cycle
close), the ephemeral spec and plan are retired here; their durable rationale
lives in the implementation commit 8c9a1b4 and the git history.
Consolidate aura-ingest's OHLC source-open surface onto the engine-native
epoch-ns Timestamp currency: a private seam-owned inverse, a Timestamp-window
opener, a canonical OHLC bundle opener (fixed C4 O/H/L/C order), and a
DataServer/DEFAULT_DATA_PATH re-export + default_data_server convenience — so a
real-data source builds from aura-ingest alone, with the one ms<->ns crossing
owned by the seam. Behaviour-preserving (byte-identical RunReports).
Grounded against the tree via the ground-spec-0052 workflow (25/28 assertions
confirmed; compare.rs compiler-invisible site + AC1 scope fix applied).
refs #80, #81, #92
Per-cycle specs/plans are step-scoped: every file maps to already-shipped
code, their snippets drift hard against current APIs (InputSpec->PortSpec,
Sim->Harness, schema() removed, C16 zero-dep reversed, ascii-dag retired),
and a stale spec read as an API reference misleads agents into non-compiling
code. No durable knowledge lost — rationale lives in the ledger + code
rustdoc, history in git + the tracker.
- gitignore docs/specs/ and docs/plans/
- delete all 117 existing specs/plans (recoverable via git history)
- CLAUDE.md: specs/plans are local-only ephemeral artifacts
The C12 realization and the 0041 spec claimed streaming residency is O(one chunk) at the process level ("a 20-year window streams in the same memory as a one-day one"). That holds only for the aura source ring (M1FieldSource::resident_records(), bounded by one chunk and correctly tested); whole-process RSS grows O(records-touched) because data-server's FileCache retains each window's parsed chunks (~56 B/record) read-only for the pass. Reproduced: 6 MiB @ 1mo -> 173 MiB @ ~10y, linear.
That retention is the replay-many optimization (load-once; close+volume share one parse via the field-agnostic FileKey), not a leak, and no always-on eviction can bound a single forward pass without regressing it. This narrows the docs/comments to the true per-source-ring property and names the FileCache per-window retention as the real, replay-amortized process cost. The streaming_seam assert is unchanged (it was always the ring bound; only its labelling confused ring vs process). The deferred opt-in non-retaining streaming read path is parked as Brummel/data-server#2.
closes#95
Milestone spec: ship the GER40 session-breakout as a Composite blueprint so the
same strategy runs across backtest / sweep / walk_forward / compare without
re-authoring (the #94 fieldtest friction). Records the design decision that the
bar period is a structural axis (a construction arg binding both Resample and
Session, so a sweep cannot desync them — #96) and that the canonical shippable
strategy form is a Composite (FlatGraph is its compiled substrate). Authoring-
side; no engine-core change (the existing bind() + GraphBuilder + param_space()
layer already suffices).
refs #94, #96, #97
Public-API research deep-dive driving the just-shipped GER40 real-data
session-breakout demo (5b5f034) through the escalating sequence a serious
researcher attempts next: scale to multi-year, walk-forward, structural-param
sweep, multi-instrument compare. Standalone consumer crate (path-deps only;
public interface = ledger + glossary + example corpus + cargo doc; no
crates/*/src read). All runs against the real /mnt/tickdata/Pepperstone archive.
Findings: 3 bugs, 1 friction, 3 spec_gap, 1 working. Triaged to the tracker:
- spec_gap: the flagship breakout ships as a raw FlatGraph the World API cannot
consume (sweep / walk_forward / compare all forced a hand re-author to a
Composite, which reproduced the FlatGraph numbers EXACTLY) — refs #94.
- bug: process residency is O(window) (6->173 MiB across 1mo->10y) despite the
documented O(one-chunk) claim; the aura ring stays <=1024 but data-server
retention scales, and streaming_seam.rs only measures the ring — refs #95.
- bug: the bar-period is one knob split across Resample (tunable param) and
Session (baked) and desyncs silently to 0.0 pips; Delay.lag leaks into the
default param_space (the C34 deform-vs-tune discriminator) — refs #96.
- spec_gap: the walk_forward empty-space idiom for param-free strategies is
undefined — refs #97.
- spec_gap (no per-instrument pip registry) and friction (one-directional
ns->ms conversion) re-confirm the known #22 and #80.
- working: C1 determinism over 7y / 148k bars, the lazy streaming source, and
the sweep/WFO cores held under real multi-year load; the Composite re-author
was behaviour-preserving (C23).
Scratch consumer crate + run transcripts committed under fieldtests/ per the
fieldtest convention (matching cycle-0049). The crate path-deps the engine and
reads the real archive; its bins are the repros cited in the issues above.
Milestone 'Strategy node vocabulary I — temporal, logic, resample & session':
the 7 aura-std nodes (EqConst, Gt, And, Delay, Latch, Resample, Session) plus a
synthetic e2e fixture, driven by the GER40 15m session-breakout. Records the
design pass's cross-cutting decisions: close-instant timestamp convention,
Latch emits f64 (Exposure dropped from the DAG), the EqConst i64->bool gate,
the Resample-only Barrier(0) firing regime, and the build order. refs #84-#91.
Milestone-close gate for "The World, part II — orchestration families".
Four end-to-end scenarios derived top-down from the milestone promise,
each built from HEAD and run as a downstream consumer through the public
API + CLI only (no crates/*/src read):
- mw_3 (headline): real EURUSD-M1 walk-forward composing all four axes
(grid sweep + optimize in-sample, OOS stitch) over the M1FieldSource
seam + lineage — 5 windows, 145 200-pt stitched curve, C2 held.
- mw_2: monte_carlo over 128 seeds, reproducible McFamily (C1) + aggregate.
- mw_1: random sweep -> optimize -> 64-member lineage round-trip.
- mw_4: family-lineage CLI transcript.
Findings: 4 working (promise empirically delivered), 1 bug, 3 friction.
- bug F5: `aura runs list`/`rank` cannot read a pre-0047 runs.jsonl — the
cycle-0047 typed-Scalar params migration (86746e3) left the persisted
flat store unmigrated; load() fails at the first bare float (+ exits 0).
- friction F6: no epoch_ns_to_unix_ms inverse for real-data walk-forward.
- friction F7: DataServer/DEFAULT_DATA_PATH not re-exported by aura-ingest.
- friction F8: family store is directory-keyed, undocumented on Registry::open.
Spec: docs/specs/fieldtest-milestone-the-world-orchestration-families.md
Public-API field test of the random param-sweep surface, from a standalone
downstream-consumer crate (path-deps only; the public interface = ledger +
glossary + spec 0049 + cargo doc rustdoc; no crates/*/src read). Four bins,
each built from HEAD and run: continuous tuning (200-point random tune ranked
by total_pips), the typed validation gate (all five reachable SweepError
variants pre-run), reproducibility + seed-sensitivity + the full i64::MIN..=MAX
sampler edge, and Space-trait interchangeability (one tune_and_rank<S: Space>
over both GridSpace and RandomSpace).
Findings: 0 bugs, 4 working, 2 spec_gap, 1 friction. The four working findings
confirm the cycle's acceptance criterion empirically — the headline tune reads
as the code a researcher would write, the gate is precise and fires before any
run, the C1 reproducibility promise is checkable in one line, and the Space
trait delivers one-consumer/both-enumerations.
Triage of the actionable findings:
- friction (no named-axis builder for RandomSpace — positional Vec<ParamRange>
must align with param_space() by hand, and a same-kind transposition passes
validation silently): filed as a feature for a future cycle, refs #79
(a RandomBinder sibling to the grid's SweepBinder).
- spec_gap (SweepError rustdoc summary named only GridSpace): fixed inline in a
follow-up doc commit.
- spec_gap (NonNumericRange / Bool-slot ranges unreachable with the shipped
aura-std node roster — no node declares a Bool/Timestamp knob): RATIFIED as
intentional. The variant is a forward-looking structural guard for the C16
"author your own node" path (a Bool/Timestamp param-slot a custom node may
declare); its current untriggerability with the standard roster is expected,
not drift.
refs #79
RandomSpace + a Space trait + typed ParamRange: the random half of the
C12.1 param-sweep axis (grid shipped in 0028). N seeded uniform draws over
declared continuous ranges, fed to the same enumeration-agnostic sweep core.
Param-only sweep signature and a code-path-disjoint SplitMix64 instance keep
the World-II source-seam firewall (refs #71) intact.
Auto-signed under /boss spec auto-sign: fresh grounding-check PASS and a
unanimous five-lens spec-skeptic panel (criterion, grounding, scope-fork,
ambiguity, plan-readiness all SOUND).
refs #52
Settled-design spec for the walk-forward param-plane cut: WalkForwardResult gains space: Vec<ParamSpec>, WindowRun.chosen_params Vec<Scalar> -> Vec<Cell>, walk_forward takes the space by value (Fn-closure generic untouched), and param_stability schema-checks a per-slot coercer (Bool -> 0/1, Timestamp unreachable per C20) before a type-blind reduction, deleting scalar_as_f64 + its per-value unreachable.
Behaviour-preserving (C1): param_stability yields identical MetricStats over the existing fixtures. Overturns 0047's Fork A (chosen_params stays Vec<Scalar>) — the WF structs carry no serde derives, so the serializability defense is moot; the substantive case is C7 purity + SweepFamily symmetry. grounding-check PASS.
refs #75
Auto-signed under /boss spec auto-sign: all objective gates green
(Step-1.5 precondition, Step-4 self-review, Step-5 grounding-check PASS)
and a unanimous five-lens spec-skeptic panel SOUND, after one editorial
round on the ambiguity lens (the test-migration rule was sharpened to a
three-role classification: eval-output -> Cell, column-write -> Scalar,
channel-send -> Scalar).
Spec for the deferred C7/C8 carrier swap flagged by the C7 realization
note: Cell becomes the hot-path value carrier (Node::eval ->
Option<&[Cell]>, node out-buffers, inter-node forward via a new
AnyColumn::push_cell), and Scalar narrows to the self-describing dynamic
boundaries (the param path, AnyColumn::get, source ingestion).
Behaviour-preserving (C1): only the carrier type narrows.
refs #74
Thread a derived named view of a sweep point so consumers stop re-zipping
param_space() names onto the bare &[Scalar]. A free function zip_params over
(ParamSpec names ⊗ positional point) in aura-core; GridSpace retains the
ParamSpec list it already receives in new(); SweepFamily carries it and
exposes named_params(i). The run-closure signature
`F: Fn(&[Scalar]) -> RunReport + Sync` stays byte-for-byte (honours #52's
#71-firewall constraint, so RandomSpace plugs in with zero reconciliation).
The lossy f64 collapse moves into the manifest constructor; the view stays
typed. Behaviour-preserving; SweepPoint and enumeration untouched.
Design ratified in-context (brainstorm, approach C over A, free function
over a NamedPoint type); a reconciliation comment on #57 records the
resolved forks with provenance. Human sign-off after the auto-sign panel's
grounding lens caught a blast-radius undercount in the first draft —
corrected to all eight sim_optimal_manifest call sites plus the
aura-registry SweepFamily struct-literal.
refs #57
Persist sweep / Monte-Carlo / walk-forward runs as named, linked families
(C18 lineage / C21): a FamilyRunRecord stamps each member with a shared
family_id = "{name}-{counter}" in a sibling families.jsonl store, group_families
re-derives a family as a unit, and the CLI gains aura mc + family-aware aura runs.
Producer-supplied window via Source::bounds()/window_of (the #71 firewall: no Vec
scan, no input artifact). family_id form (name + counter, not a content-hash)
resolved by the user in-session; reconciliation comment on the issue records it.
Auto-signed under the boss spec-auto-sign gate: all objective gates green
(precondition, self-review, grounding-check PASS) and a unanimous five-lens
spec-skeptic panel (criterion, grounding, scope-fork, ambiguity, plan-readiness).
refs #70
Walk-forward orchestration: a WindowRoller rolls (in-sample, out-of-sample)
splits over a time span, walk_forward runs a disjoint harness per split via the
shared run_indexed core (C1), and stitches the OOS pip-equity segments into one
continuous curve. The varying dimension is the data window (axis 3), realized
eager-agnostically (#71): the public surface carries WindowBounds + a per-window
closure, never a materialized stream Vec; C2 no-look-ahead is a pure bounds
invariant (oos.0 > is.1), checkable with zero ticks.
The in-sample optimize (axis 2) is closure-supplied, not called by the engine:
aura-engine cannot depend on aura-registry (C9), and C12 forbids baking search
policy into the primitive. The CLI bridges both crates in run_walkforward.
Param-stability shape (the load-bearing fork) resolved with the user as R2:
on-demand, not stored. WalkForwardResult stores only raw per-window outcomes +
the stitched curve; param_stability(&result) -> Vec<MetricStats> is a public
on-demand reduction over the retained per-window chosen params (sweep-precedent:
summaries computed on demand, not cached, vs McFamily's stored aggregate). A
stored summary would be recomputable from the raw windows (redundant) and would
force one statistic canonical when "stability" admits several. Recorded with
provenance in the #69 reconciliation comment. MetricStats::from_values is
extracted from McAggregate::from_draws (behaviour-preserving; the 7 mc tests
guard) so the MC aggregate and the helper share one reduction. Empty OOS segment
contributes 0.0 to the stitch offset (mirrors summarize's unwrap_or(0.0)).
Gates: Step-1.5 precondition clean (no fork silently picked), self-review clean,
grounding-check PASS (twice — re-run after each edit). Auto-sign panel escalated
on the scope-fork design lens (param-stability shape) to human sign-off; the user
resolved it (R2), so this is a user-signed spec, not boss-signed.
refs #69
C12 axis 4: Monte-Carlo as a sweep over seeds. `monte_carlo(base_point, seeds,
closure) -> McFamily`, the analog of SweepFamily, reusing the disjoint-parallel
executor (extracted into a shared `run_indexed` core) rather than forking a new
run loop. Each realization is a disjoint C1 unit; parallelism is across draws.
The aggregate (V1) covers all three run metrics with mean + p5/p25/p50/p75/p95,
a pure post-run reduction over the retained raw draws.
Eager-agnostic firewall (#71): the API takes seeds + a per-draw closure, never a
materialized stream Vec; seed->Source construction stays a closure-body concern.
Signed under the /boss spec auto-sign gate: objective gates green (precondition,
self-review, grounding-check PASS) and a unanimous five-lens spec-skeptic panel
(criterion, grounding, scope-fork, ambiguity, plan-readiness) returned SOUND.
The aggregate fork was settled to V1 by the user (issue #68 reconciliation
comment, verbatim "ja, wir machen v1", 2026-06-15).
refs #68
A seeded source whose stream is fully determined by a u64 seed, wired so the
seed reaches RunManifest.seed at the manifest-construction site (Fork B: seed at
the data-generation edge, outside the engine graph). Contract is a producer
Fn(u64) -> impl Source, never seed -> Vec, so the Monte-Carlo family can re-seed
N times without materializing N streams. Deterministic dependency-free PRNG
(SplitMix64) for C1 bit-stability. Precondition for #68 (Monte-Carlo) and #52
(random sweep).
Auto-signed under /boss spec auto-sign: objective gates green (precondition,
self-review, grounding-check PASS) and a unanimous five-lens spec-skeptic panel
(criterion, grounding, scope-fork, ambiguity, plan-readiness all SOUND) after one
editorial-repair round.
refs #66
Re-types the engine's ingestion input from a materialized
Vec<Vec<(Timestamp, Scalar)>> into a `Source` producer trait the k-way
merge drives by peek/next, and proves it against a real, lazily-streamed
data-server window in the same cycle — closing the cycle-0011 deliberate
eager-materialization gap (C12 Arc<[T]> cross-sim sharing). A VecSource
adapter keeps every existing run call site byte-identical
(behaviour-preserving); an M1FieldSource streams data-server chunks with
O(one-chunk) residency, independent of window length.
Foundational cycle of the World-II milestone; #66/#68/#69 inherit the
seam. Signed under the boss auto-sign gate: precondition clean, grounding
PASS on the final bytes, unanimous five-lens spec-skeptic panel (two
editorial ambiguity rounds on the residency predicate repaired to a
measured per-pull CHUNK_SIZE ceiling).
refs #71
"Compilat" (German "Kompilat") was a coined noun for the product of the
bootstrap compilation — neither English nor a natural fit. The runtime
artifact already has a code identifier for exactly this thing: the
`FlatGraph` struct (harness.rs). Replace the coinage with that identifier:
- prose mentions -> "flat graph" (mirrors the type, reads plainly)
- definitional anchors -> `FlatGraph` (C11, C23, the running-graph line)
- `render_compilat` -> `render_flat_graph` (historical render symbol;
keeps the `render_blueprint` / `render_flat_graph`
source-vs-product pairing)
- `intra-compilat` -> `intra-graph`
- `from_compilat` (test) -> `from_flat`
"compilation" / "re-compilation" / "bootstrap-as-compilation" (the process,
ordinary English) are deliberately left untouched. Behaviour-preserving:
only comments, design ledger, specs/plans, one test-local variable and its
assert messages change. Full workspace test suite green; clippy clean.
The boss-signed 0040 spec's blast-radius section claimed "exactly three" flipping
tests, named from a hand-trace. A throwaway probe (the check implemented, full
workspace suite run, then discarded) found FOUR: the three named negative tests plus
`param_space_mirrors_compiled_flat_node_param_order_under_nesting`, a compile-success
param-order test that today compiles a deliberately under-wired nested graph (LinComb's
two term inputs never wired) just to read its flat param order. The new check rejects
it where it currently returns Ok.
Correction is editorial, not a design change: the check itself is unchanged. The
fourth test's fix differs from the three negative tests — it needs the contrived graph
fully wired (fan fast_slow's output into both LinComb terms + a covering root role),
not just a covering role — so the §Blast radius and §Acceptance criteria now enumerate
all four with their distinct fixes. Verified empirically (119 passed, 4 failed; every
other crate green), the strongest possible grounding for the count.
refs #65