Files
Aura/docs/design/contracts/c20-strategy-harness.md
T
claude dc5f1742f4 fix(aura-cli, aura-runner): review findings — duplicate --override refusal, live discovery prose, neutral JSON refusal
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
2026-07-25 23:21:07 +02:00

6.8 KiB
Raw Blame History

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 FlatGraph compiled 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