In-cycle fixes for the independent review of the #319 package, RED-first for both Importants. A repeated --override path now refuses at the shared lexer (exit 2, path named) instead of panicking exit 101 through reopen_all — which also un-misattributes the campaign leg's collision prose for duplicates. override_paths' refusal points at aura graph introspect --params (the retired --list-axes reference swept from every live comment, one explicitly-historical mention remains). exec classifies file targets through a Result-bearing peek: unparseable JSON refuses neutrally before any leg is chosen. RunPresentation (single-variant) removed; run_campaign_returning's contract note names its one deliberate usage-class exit; C14's routing sentence and C20's family-builder paragraph amended to current truth (superseded text to the history sidecars); bench error strings and deletion blank-line runs tidied. refs #319
6.8 KiB
C20 — Strategy ↔ harness; the harness is the root sim graph
Guarantee. A strategy is a reusable composite-node blueprint (C9): broker-,
data-, and viz-independent, with inputs declared as named roles (symbol-agnostic
where possible) and the bias stream (C10) as output. A harness (the
experimental setup) is the root sim graph — sources bound to the strategy's
input roles + the strategy + attached broker node(s) + sinks — and is itself
produced by the bootstrap (C19). A harness instance is C1's disjoint unit (the
root scope). The harness has two kinds of parameterization: structural axes
(which strategy, which instrument(s), which broker(s), which window, which risk
regime) whose variation selects different instances — the experiment matrix
(the risk regime is the fourth axis, #210, carried as CampaignDoc.risk;
kept-separate, compared-not-selected, see C10) — and tuning params (the
strategy's numeric params) swept within a fixed structure (Fork A). The same
strategy blueprint is reused across backtest, sweep, visual workspaces, and the
frozen live bot — each a different harness. Both strategy and harness/experiment
are authored in Rust via builder APIs (C17); the experiment matrix is ordinary
Rust control flow (loops/conditionals), not a config schema. Ontologically, a node
(incl. a strategy composite) is an open fragment — free input roles + ≤1 output
(C8/C9) — that does not run alone; a harness is the closed root graph: a
strategy with its input roles bound to sources and its output terminated in
broker/sink nodes, under a clock. A harness is therefore not a node (no free
inputs, no output; it does not fit eval) — it is the closure that runs, C1's
disjoint unit / the root scope. Harnesses do not nest as nodes; the World (C21)
orchestrates them as objects.
Forbids. Embedding data sources / brokers / sinks inside a strategy; a declarative experiment mini-DSL (logic is Rust — C17); modelling the harness as a node (it is the closed root scope, not an open composable node). The mini-DSL prohibition is scoped by the #188 role-model pass: it forbids an open, logic-bearing experiment language (the RustAst trap), not the closed-vocabulary campaign document (C25/C18) that carries persisted experiment intent — data windows × strategy ids × param axes × process ref, under the total, declarative P1 construct tier (bounded axes, gates, ladders; no variables, no general recursion, no unbounded iteration — invariant 5's no-free-feedback move applied one level up; #188/#189). A generator that enumerates structural variants may still be plain Rust; what it yields, and what a campaign declares, is data. Genuinely new logic (metrics, analysis blocks) escalates to a new Rust block, never to a freetext hole in the artifact.
Why. Reusability needs the strategy to be a context-free blueprint that many harnesses embed. Modelling the harness as a root graph keeps it within the one Node/graph abstraction (C9) and makes "10 strategies in one environment" and "one strategy × N instruments" plain nested loops over the structural axes. Rust authoring (not config) preserves full programmatic power — conditional/adaptive matrices, generated axes, custom wiring — and avoids re-introducing the DSL trap C17 rejects.
Current state
The shippable form is the Composite blueprint. A hand-wired FlatGraph is
not a shippable strategy — it carries no param_space(), so the World families
(sweep / walk_forward / compare, C21) cannot consume it without a hand re-author.
The canonical shippable form is the Composite blueprint (the authoring source
level, C9/C19); the FlatGraph is only its compiled substrate (C23). This is
concrete in the GER40 session-breakout example (ger40_breakout_blueprint,
crates/aura-ingest/examples/shared/breakout_real.rs), whose param_space() is
exactly its tuning knobs and whose bar period is a construction argument binding
Resample + Session together — a structural matrix axis (C12: a different period is a
different strategy, not a sweep point), never a param_space entry, so a sweep
cannot desync the two clocks. Structural constants (e.g. a delay.lag, C8) are
bound out of the space.
Experiment-matrix members are topology-data. The "ordinary Rust control flow" clause holds for the generator — the loop / conditional that enumerates structural variants may stay Rust (C17). But each member it yields is a topology-data value (C24), not a hand-coded blueprint nor engine-baked source: a structural axis selects among data blueprints the World constructs, mutates, and serializes, which is what makes structural variation first-class and searchable rather than a hand-enumerated menu. Persisted experiment intent is the campaign document (C25/C18).
Pre-C24 scaffolding retired. The pre-C24 scaffolding — HarnessKind and the
per-strategy *_sweep_family functions — is gone. The generic blueprint-driven
family builders that first replaced it (blueprint_sweep_family,
blueprint_walkforward_family, blueprint_mc_family, blueprint_sweep_over) are,
in turn, retired by the #319 sugar retirement (2026-07-25): the campaign
executor (aura_campaign::exec::execute) now builds every family, driven by
the process document's stage pipeline (std::sweep/std::grid,
std::walk_forward, std::monte_carlo). crates/aura-runner/src/family.rs
retains only the synthetic-stream helpers serving aura_runner::reproduce
(showcase_prices, walkforward_prices, walkforward_window_source,
synthetic_walk_sources, DataSource/DataChoice) — reproduction's own
re-derivation path, not a family-minting path. The R-evaluator scaffold
wrap_r (defined in crates/aura-runner/src/member.rs; imported and called
from runner.rs) survives; its last hard data weld — the hard-wired
price←close role and the M1Field::Close-only open sites — is retired
(a strategy's input roles now bind archive columns by name, C26), and wrap_r's
remaining R-scaffolding retirement is tracked at #159.
See also
- C9 — the strategy as a composite blueprint.
- C10 — the bias output and the risk-regime axis.
- C17 — Rust authoring and the DSL prohibition.
- C19 — the bootstrap that produces the harness.
- C21 — the World orchestrates harnesses as objects.
- C23 — the
FlatGraphcompiled substrate. - C24 — experiment-matrix members as topology data.
- C25, C18 — the campaign document / run registry.
- C26 — roles bind archive columns by name.
History: c20-strategy-harness.history.md