Files
Aura/docs/design/contracts/c10-bias-r-cost.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

28 KiB
Raw Blame History

C10 — Strategy output is a bias stream; signal quality is measured in R; cost is a composable downstream graph (gross R → net R); money is decoupled to the live deploy edge: 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: c10-bias-r-cost.md.

Realization (cycle 0007). [HISTORY — pre-reframe; SimBroker is now legacy per the 2026-06-28 reframe.] The signal-quality half was realized at the substrate as two aura-std nodes on the unchanged engine (the engine stays domain-free — it routes only f64 records). The exposure stream was realized as Exposure { scale }: clamp(signal / scale, -1, +1), one f64 per fired cycle. The sim-optimal broker was realized as SimBroker { pip_size }: a two-input node (exposure, price) accumulating prev_exposure · (price prev_price) / pip_size and emitting cumulative pip equity — the exposure held into a cycle (decided at t-1) earns that cycle's return (causal, C2); pip_size is held reference metadata (C7/C15). An end-to-end harness (SMA-cross → ExposureSimBroker → recording sink) produced a recorded pip-equity curve, bit-identical across runs (C1).

Realization (per-instrument pip channel, 2026-06, #22). [HISTORY — pertains to the legacy SimBroker pip channel.] SimBroker's pip_size is sourced per instrument from the recorded geometry sidecar (instrument_geometry, over data-server's symbol_meta), at the ingestion / source edge (never Aura.toml, never Ctx); the engine stays domain-free. (The original cycle-0022 form was a Rust-authored vetted floor InstrumentSpec { pip_size } + instrument_spec(symbol); cycle 0074 removed that floor — see the C15 note — once the sidecar geometry made it redundant for the real path. Refuse-don't-guess on absent geometry.) The honesty rule is refuse, don't guess: a real-data run for a symbol with no vetted spec is a usage error (exit 2). Threaded through the CLI aura run --real path; the manifest broker label records the looked-up pip.

Realization (position-event schema, cycle 0063, #114). [Survives as the deploy / reconciliation schema per the 2026-06-28 reframe — no longer a broker-input research artifact.] A closed PositionAction { Buy, Sell, Close } enum + the PositionEvent row (event_ts, action, position_id, instrument_id, unsigned volume; no open_ts; direction is the action) live beside RunMetrics as a post-run value type (not a per-eval node — C8) — both in the aura-analysis crate since cycle 0079 (#136); originally aura-engine, see the C16 cycle-0079 note. action serde-encodes as a bare i64 (Buy=0, Sell=1, Close=2), the C7 scalar column form, with an out-of-range code rejected on read. The table stays broker-independent.

Reframe (2026-06-23, #117 — exposure → bias, R as the signal-quality unit). [HISTORY — its R spine survives into the 2026-06-28 contract; its Stage-2 currency / realistic-broker / register / flat-1R-vs-compounding portions are SUPERSEDED by that reframe.] The contract was reframed from exposure (a signed fractional position) to an unsized bias plus R as the signal-quality unit. The realization notes below describe the pre-reframe code (Exposure, SimBroker, pip-equity), retained as history and as the ancestor of the current chain: Exposure { scale } is the ancestor of the unsized bias node, and SimBroker's pip integral is the ancestor of the R-evaluator (which additionally requires a stop, since R is stop-defined). The exposure → bias rename, the RiskExecutor / Sizer / Veto nodes, and the R-evaluator landed in cycle 0065; the position-event schema (0063, #114) survives as the audit layer. Industry grounding for this reframe: LEAN / nautilus_trader / backtrader / QSTrader / vectorbt / zipline (see #117 decision log).

Realization (cycle 0065 — Stage-1 R signal quality, #119/#126/#127/#128/#129). [The R spine here is the live model; the Stage-2 deferral clause at its end is SUPERSEDED by the 2026-06-28 reframe — there is no Stage-2 currency / compounding layer; cost is now the cost-model graph and money lives only at the live deploy edge. "Stage-1" reads as a historical identifier, not a gate half.] Exposure → Bias renamed the unsized strategy output (node + output field); the persisted exposure_sign_flips metric key (serde alias), the SimBroker exposure input slot, and the on-disk exposure tap label retain the old name. The strategy-output param namespace (the Bias instance, its bias.scale knob, the bias_scale manifest param) was completed in #134. A stop-rule defines 1R: FixedStop (a triggered-constant primitive) and a vol_stop(length, k) composition k·√EMA(Δ²) (a composition of Mul / Sqrt primitives). PositionManagement (aura-std) is the stateful heart: it latches the entry-cycle stop distance as the immutable R-denominator, marks against the one-cycle-lagged fill (no look-ahead, C2), and emits a dense 14-column per-cycle R-record (one row per eval, C8; the trade ledger is the closed_this_cycle subset, the R-equity is cum_realized_r + unrealized_r). The Sizer (size = risk_budget / stop_distance, flat-1R) was the feed-forward sizing seam, and R is size-invariant — scaling risk_budget leaves every realized_r unchanged (pinned by a RED test); per the 2026-06-28 reframe the Sizer and size / volume are removed from research (size is a deploy concept). summarize_r is a post-run fold (sibling of summarize, not an in-graph node) → RMetrics (E[R], SQN, win-rate, profit-factor, max-R-drawdown, conviction terciles, net-of-cost); per the 2026-06-28 reframe it folds the cost-model's net-R rather than recomputing a scalar cost. The RiskExecutor ships as a public aura-composites composite-builder (risk_executor(StopRule, risk_budget)) with a StopRule{Fixed,Vol} structural axis (C20); per the 2026-06-28 reframe its Sizer interior and risk_budget arg are dropped / vestigial, the Veto stays a documented seam, not a runtime node. The layer is operable from the CLI: aura run --harness <sma|macd|r-sma> — a compile-time selector over Rust-authored harnesses (C9/C17) — folds summarize_r into RunMetrics.r, the r-sma harness fanning one bias into both SimBroker (legacy pip) and the RiskExecutor (R); an r_equity tap charts the by-trade R-equity. Composites live in the dedicated aura-composites crate, so aura-engine's runtime dependency stays aura-core-only and aura-std is an aura-engine [dev-dependencies] (the graph stays acyclic). [HISTORY — the built-in --harness selector was retired with the demos → blueprint-data (#159, cuts 1b-4); run is now blueprint-driven — aura run <blueprint.json> over examples/r_*.json.]

Realization (cycle 0066). SQN is the operational single-number objective for ranking an r-sma sweep family by signal quality — C12 axis-2 (argmax-metric) over the C18 family store. metric_cmp (aura-registry) learns the higher-is-better R metrics sqn, expectancy_r, net_expectancy_r; a member with no r block sorts last (NEG_INFINITY). aura sweep --strategy r-sma produces the rankable R family, each member folding summarize_r into RunMetrics.r. The default grid varies only the signal (fast / slow SMA lengths), holding the stop and sizing fixed: risk_budget is R-invariant and bias.scale is sign-only under flat-1R, and — load-bearing — the stop defines 1R, so varying it would change what R means per member and break cross-member SQN comparability. Each swept member's manifest records the fixed R-defining params beside the floated knobs (reproducible from its own manifest, C18). [HISTORY — the built-in --strategy sweep surface was retired with the demos → blueprint-data (#159, cuts 1b-4); the rankable R sweep now runs as aura sweep <blueprint.json> --axis … over r_sma_open.json (an example then; relocated to crates/aura-cli/tests/fixtures/ by #248).]

Realization (cycle 0067, #130 + #135). #130 (SQN100): RMetrics gains sqn_normalized = (mean_R/stdev_R)·√(min(n, 100)) — the n-normalized "SQN score" (SQN_CAP = 100), turnover-robust where raw sqn rewards trade count. It is an opt-in rank key (Metric::SqnNormalized); raw sqn and the default ranker stay byte-unchanged, and the field carries #[serde(default)] (C18). Below the cap (n ≤ 100) it equals raw sqn exactly. #135 (r-sma --trace): r_sma_sweep_family persists each member's equity / exposure / r_equity under runs/traces/<n>/<member_key>/ via the same persist_traces_r the single run uses; per-member --trace is symmetric across all three sweep strategies. [HISTORY — the built-in --strategy sweep triple (sma / momentum / r-sma) was retired with the demos → blueprint-data (#159, cuts 1b-4). The per-member --trace symmetry was NOT carried to the aura sweep <blueprint.json> path — it was silently dropped at #159/#220 (run_blueprint_sweep never wired persist; let _ = persist), and #168 makes the surface honest: sweep/walkforward (like the pre-existing run/mc) now refuse --trace outright. See the CLI---trace retirement amendment below.]

Realization (cycle 0068, #115 — position-event derive). [The derivation survives as the deploy / reconciliation layer per the 2026-06-28 reframe — its "realistic brokers consuming it" goal is retired; money is the live-edge concern.] derive_position_events(record, instrument_id) -> Vec<PositionEvent> (in aura-analysis since cycle 0079, #136; originally aura-engine, beside summarize_r) is the first difference of the executed book: a pure post-run reduction over the PositionManagement dense record (read positionally as type-erased Scalars, C7 SoA — no in-graph node, so the hot path stays domain-free, C14), emitting a Buy / Sell at each open and a Close at each exit, a reversal (or stop-then-same-cycle reopen) emitting Close then the opposite open at one event_ts (close first — the C8 ">1 event per instant" case that forces a derived table, not a per-eval output). The close sizes the actual book (the closed position's stored volume), never an exposure delta. instrument_id is a caller-supplied scalar (aura-analysis depends only on aura-core). A position open at window end emits its open with no synthetic Close (the table records actual executed events; summarize_r's force-close is for the R metric only). The r_col ⟷ PM-record lockstep is guard-pinned for direction too.

Reframe (2026-06-28, #116 — realistic broker retired; cost-model graph in R; money to the live edge). This is the live contract above. Ratified in an in-context design discussion (reference issue #116). It preserves the durable spine — unsized bias stream (sign = direction, magnitude = conviction), signal quality in R, the stop defining R, and the decoupling of direction from sizing from fill — and the shipped Stage-1 realizations (the Bias node, FixedStop / vol_stop, PositionManagement, summarize_r / RMetrics / sqn_normalized, SQN as ranking objective). The RiskExecutor composite survives in shape (bias + price → stop-rule → position-management) but with its Sizer interior removed and its risk_budget argument dropped / vestigial (it sized the now-removed Sizer); the Veto remains an optional documented seam. The contract supersedes the following, which were design intent, largely unbuilt (#116 — the realistic broker and the whole Stage-2 currency layer were never implemented; the Stage-1 R chain was) and are retired:

  • the "realistic broker" concept — an authored-friction historical broker — rejected as "horseshoe-throwing" (real friction is not historically knowable), replaced by the cost model: a composable C9 graph of cost nodes (in aura-std / aura-composites), approximating not claiming realism, generalizing / subsuming round_trip_cost into net_expectancy_r;
  • the "Currency P&L is Stage 2" paragraph in full, the Stage-1-vs-Stage-2 hard sequencing gate, and currency / fixed-fractional / compounding in research — compounding is now a post-hoc money-management transform of the net-R sequence at the deploy / account layer;
  • the z⁻¹ register on the fill edge and the flat-1R-vs-compounding structural axis as research mechanism — there is no equity → size edge in research, so no register and no such axis in the loop;
  • the Sizer in research and currency size / volume — size is a deploy concept; the research executor is stop + position-management in R; PositionManagement's size port is dropped or constant-driven (no dangling port, C8), its size field and the event table's volume are vestigial in research;
  • the "a broker is an ordinary in-graph node / no special external broker subsystem" forbid, for the live edge only: in-graph brokers are retired outright, and the live broker is now an explicit I/O adapter at the C11 recording / C13 deploy edge — not an in-graph node and not part of the research graph (the no-in-graph-broker-subsystem prohibition still holds inside the graph);
  • the position-event table as the realistic-broker input and its first-difference-of-the-book (deal = target book in_flight) execution framing — the table (schema 0063 #114, derive 0068 #115) survives as the deploy / reconciliation artifact (real volume), not a research artifact and not "fed to a broker" in research. Money, a real broker, and cTrader Open API are a separate, later live / deploy-edge concern (C11 record-then-replay; C13 frozen-deploy invariant) — the only belastbare ground truth, measured, never modelled. The honesty principle is explicit: the net-R curve is a research / ranking hypothesis; the forward / live run is ground truth. SimBroker is downgraded to a legacy / optional pip yardstick, not to be expanded. (Terminology note: with Stage 2 gone, the "Stage-1" label below is no longer one half of a two-stage gate — it survives only as a historical cycle name, like the exposurebias on-disk alias, denoting the shipped feed-forward R chain; the identifier family that carried it was renamed to the r-family — r-sma / r-breakout / r-meanrev — in cycle 0100, #174.)

Realization (cycle 0081 — cost-model graph, cycle 1, #148). The first concrete cost node + the net-R seam shipped. ConstantCost (aura-std) is an ordinary downstream node (C9) emitting a 3-field cost-in-R record {cost_in_r, cum_cost_in_r, open_cost_in_r} isomorphic to PositionManagement's R-triple; R-pure (cost_per_trade / |entry stop|, notional cancels, no equity held). The scalar round_trip_cost argument of summarize_r is retired: summarize_r folds a co-temporal cost stream (positional 1:1 join — cost[i] is record[i]'s cycle) into net_expectancy_r, one home for cost / no double-count, byte-identical to the old cost = 0 baseline on an empty stream. The headline sink is net_r_equity = LinComb(4)[cum_realized_r, unrealized_r, cum_cost_in_r, open_cost_in_r] → Recorder (C8/C18), a sibling of r_equity, emitted only when a cost is authored. Wired on the run path via --cost-per-trade (r_sma_graph); sweep / walk-forward / mc pass None (cost on the reduce-mode sweep path and cost in OOS pooling deferred — the positional join holds only while one cost node fires in lockstep with PM). [Superseded 2026-07-11 (#234): the --cost-* run-path flags are gone (#221 removed that surface); cost is authored as the campaign document's cost block and reaches every family/campaign member — see the cycle-net-r realization below.] Deferred to later milestone cycles: the general CostNode trait + multi-node cost-graph composite-builder, data-grounded factors (realized-vol → slippage, recorded-rate → swap), per-cycle-held accrual (carry / funding), and the conviction-weighting R-aggregation axis. Decision log: #148.

Realization (cycle 0082 — cost-graph composition, cycle 2, #148). The cost graph composes: a second, state-dependent cost node plus an aggregator prove that two cost nodes sum into one net-R curve with summarize_r and the net_r_equity tap structurally unchanged. VolSlippageCost (aura-std) charges slip_vol_mult · vol / |entry stop| in R, reading an independent short-horizon realized-range vol (RollingMax RollingMin, window distinct from the stop's own vol — scaling slippage by the stop's vol would collapse cost-in-R to a constant, indistinguishable from ConstantCost). CostSum (aura-std) is the cost-graph output node: it sums N cost nodes' 3-field cost-in-R records per-field into one aggregate (n = 1 is the identity), so the seam consumes a single cost stream regardless of node count — one home for cost, the positional join unchanged. Co-temporality contract (load-bearing, generalizes to all future factors): since summarize_r positionally joins cost[i] ↔ record[i], a cost node is gated only by the PM trade-geometry; any not-yet-warm state input (the vol proxy warms later than PM) contributes 0 cost that cycle rather than withholding — the node still emits its row, so the cost stream stays co-temporal 1:1 with the PM record. This makes co-temporality structural and warm-up-independent, preserves the C18 golden, and is honest (no slippage estimate yet → no charge). ConstantCost satisfies it trivially; only state-dependent nodes need the missing-factor → 0 rule. Wired on the run path via --slip-vol-mult, composable with --cost-per-trade (their costs sum); sweep / walk-forward / mc pass None. The 3-field cost triple is now restated by-convention across the two producers

  • CostSum (a compiler-unlinked lockstep) — to be unified by the still-deferred general CostNode trait (now justified by two concrete nodes). Decision log: #148. [Superseded 2026-07-11 (#234): the --slip-vol-mult/--cost-per-trade flags are gone; authoring moved to the campaign cost block — see the cycle-net-r realization below.]

Realization (cycle 0083 — CostNode trait + shared cost-record contract, cycle 3, #148). The deferred unifier ships. A new aura-std/src/cost.rs owns the cost-model node abstraction: the 3-field cost triple is now one source of truth (COST_FIELD_NAMES / COST_WIDTH, mirroring position_management::{FIELD_NAMES, WIDTH}), read by both producers + CostSum + the CLI wiring — the cycle-0082 by-convention lockstep is structurally gone (four restatements collapsed to one). The CostNode factor trait carries a cost node's only per-node difference — the price-unit cost numerator (cost_numerator(&mut self, &Ctx) -> f64), plus extra_inputs (default none) and label; everything else is the generic CostRunner<F: CostNode> adapter, which holds the shared cum/out state and implements Node, writing the co-temporality skeleton (geometry-only gating, numerator / latched R-normalization, the closed/open charge, the running cum, the 3-field emit) once. ConstantCost and VolSlippageCost are now thin factors whose new() returns CostRunner<Self>. Honours C9 (a CostRunner<F> is a plain downstream Node, no runtime sub-object) and C23 (name() dropped as dead surface — label() remains the non-load-bearing symbol). Behaviour-preserving: the builders emit unchanged schemas, so the wiring / net_r_equity seam / summarize_r are untouched and the numerator / latched token form is byte-identical; the existing suite passes verbatim and two new CLI characterization goldens pin the exact flat/composed net_expectancy_r (the prior tests only asserted net < gross). Decision log: #148.

Realization (cycle 0084 — cost-graph composite-builder, cycle 4, #148). Decision E ships. A new cost_graph(Vec<PrimitiveBuilder>) -> Composite in aura-composites (the C16 layer that couples the engine builder + aura-std nodes) is the cost-model graph's authoring primitive: it fans the 4 PM-geometry inputs to N cost nodes, surfaces each node's extra inputs (discovered via schema().inputs[GEOMETRY_WIDTH..], GEOMETRY_WIDTH now re-exported from aura-std) as cost[k].<port> composite roles, sums them through CostSum, and exposes the 3-field aggregate. The CLI's manual slot-indexed cost-wiring + the hardcoded MAX_RUN_COST_NODES = 2 cap are deleted — the composite handles arbitrary arity. Behaviour-preserving (C11): the composite inlines at bootstrap to the same flat fan-in, so the cycle-0083 net_expectancy_r goldens are byte-identical (four aura-composites unit tests pin the exposed role-set + output triple, incl. arbitrary-arity per-node namespacing). Honours C9 (ordinary downstream nodes), C16 (wiring stays out of aura-engine), C23 (role/port names are non-load-bearing). Carried debt (#152, for the deferred sweep-cost cycle): the cost[k].<port> index-namespacing is restated across CostSum / cost_graph / the CLI (a build-validated lockstep, not the silent positional kind 0083 collapsed), and cost_graph .leak()s runtime port names per build — fine for one-shot run-path construction, but to be interned (the COL_PORTS production pattern, not the test-only .leak()) before cost reaches the per-member sweep path. Decision log: #148. [Discharged 2026-07-11 (#152/#234): cost_port/intern_port in aura-std/src/cost.rs are the process-global interned single source; both cost_graph .leak()s are gone.]

Realization (cycle 0085 — per-cycle-held accrual, cycle 5, #148). C10's "per-trade factors deduct at close; per-cycle-held factors accrue over the hold" clause is first realized. A cost factor now declares when it charges via ChargeMode { AtClose, PerHeldCycle } (a defaulted CostNode::charge_mode(), default AtClose), read by the one shared CostRunner — not a second runner type (a commission is intrinsically at-close, a carry intrinsically per-held-cycle; the timing belongs to the factor, preserving the cycle-0083 "skeleton written once" win). The PerHeldCycle arm accrues per into a per-position acc every held cycle, dumps the accrued total into cum at close (resetting acc), and marks the open position via a growing open_cost_in_r. The first accrual node, CarryCost (aura-std, a ConstantCost twin differing only in charge_mode()), is a labelled stress parameter — the flat base of the accrual family; a run-path --carry-per-cycle flag pushes it into the existing cost_graph/CostSum aggregation (no new wiring; a per-trade and a per-held-cycle node compose, the costs summing per-field). Approach B (honest bleed), achieved without a summarize_r fold change: the headline net_r_equity curve bleeds continuously over the hold because the bleed lives in open_cost_in_r, which the net_r_equity tap already subtracts — so summarize_r and the CLI net_eq wiring are untouched, and the cycle-0083/0084 net_expectancy_r goldens stay byte-identical (the AtClose arm is the pre-cycle-5 eval tokens verbatim). The 3-field cost record's semantics generalize cleanly: cost_in_r = cost realized this cycle (into cum); open_cost_in_r = the open position's cost marked-to-market as-of-now (would-be-close for AtClose, accrued-so-far for PerHeldCycle) — singly counted, no cum/open double-count (the two are mutually exclusive per cycle). Honours C9 (a CostRunner<CarryCost> is an ordinary downstream Node), C11 (byte-identity), C16 (factor in aura-std, aura-engine domain-free), C23 (label() carries the rate). The B-vs-A discriminator (a growing intra-hold open_cost_in_r, invisible to the scalar net_expectancy_r) is pinned by unit B-proofs + a net_r_equity-bleeds-over-the-hold integration test. Deferred (each its own #148 cycle): a notional-based carry (price × rate, reads the price tap), the calendar-aware overnight swap proper (rollover-boundary timing, 3× Wednesday, long/short asymmetry — deploy-edge realism), and sweep-path cost. Decision log: #148.

Realization (cost-flag harness scoping, #153). Cost flags are defined only against an R-evaluator harness (the gross-R → net-R chain). On a non-R harness (--harness sma/macd, which produce no R) a cost flag is a usage error (exit 2), not a silent no-op — refuse-don't-guess. Negative cost rates are likewise rejected (exit 2) with a named diagnostic that identifies the offending flag. CLI ergonomics only; the cost-model graph and the R math are untouched. [HISTORY — the built-in --harness selector (its sma/macd non-R examples) was retired with the demos → blueprint-data (#159, cuts 1b-4); the cost-flag scoping rule survives on the aura <verb> <blueprint.json> r-sma run path over examples/r_*.json.]

Realization (2026-07-06 — the risk regime as a structural campaign axis, #210). The StopRule{Fixed,Vol} structural axis is realized at the campaign-document level as CampaignDoc.risk: [RiskRegime] (a serializable, content-addressable mirror of the runtime enum — aura-research, variants Vol{length,k} and VolTf{period_minutes,length,k} (#262), the fixed-stop rule additive when needed). It is a kept-separate matrix axis, a peer of instruments and windows: the executor keys the nominee map by (strategy, window, regime), so generalize aggregates across instruments within a regime, never across regimes. Regimes are therefore compared at presentation, never argmax-selected across — a cross-regime E[R] argmax would compare R-multiples in different R units (the stop defines 1R), so the legacy verbs' --stop-length/--stop-k joint stop grid is retired as a campaign methodology (finding the best regime = the same walk-forward / worst-case-R validation, run once per regime, and picking the most robust — a comparison, not a search). Each member manifest stamps its resolved stop (default included), closing the C18 gap; absent/empty risk = one implicit default regime, absent-serializing for content-id parity. Two default representations coexist by design (#217): a dissolved sweep binds no regime at all (risk: []), late-resolved per member by stop_rule_for_regime at run time, while walkforward/mc/generalize — whose --stop-length/--stop-k became optional — bind the default regime eagerly into the campaign document (risk: [Vol{length:3,k:2.0}]). Same R behaviour either way, but deliberately different document content ids: a stop-less verb invocation's document equals its explicit --stop-length 3 --stop-k 2.0 spelling, not a stop-less sweep's document. Deferred (documented): regime-aware trace persistence — the trace re-run and cell-key dir naming still assume the default stop, since CellRealization carries no regime (#212); the core run/stamp/generalize path is unaffected.

Realization (cycle net-r — cost reaches the family/campaign path, #234/#152, 2026-07-11). The deferred sweep-path cost ships, delivered where C24 put experiment intent: the campaign document gains an additive cost: Vec<CostSpec> block — a closed, externally tagged vocabulary over the three shipped nodes (constant / vol_slippage / carry, field names = the builders' own ParamSpec names), mirroring the risk block (serde default + skip-if-empty: cost-less docs hash byte-identically, C18). Net is the default: an absent block is the explicit zero-cost model and summarize_r's net family equals gross under it — every result is net, no second "gross-labelled" result kind. cost_nodes_for (beside stop_rule_for_regime) is the one doc→builder binding, every component fully bound (the wrapped param space stays cost-invariant); wrap_r carries an optional cost leg (the #221-deleted wiring rebuilt: cost_graph off the executor's four geometry outputs, the vol proxy back in production, a gated cost recorder in reduce mode as the summarize_r join input, the LinComb(4) net_r_equity curve in trace mode). Member manifests stamp the components; both re-run sides re-derive them (reproduce via cost_specs_from_params — the #233 stop pattern — and the persist re-run binds the same model, so the C1 drift alarm compares like with like; costed families reproduce bit-identically, pinned incl. Carry). TapChannel::Net routes net_r_equity to persisted curves; a cost-less doc requesting it keeps a remedy-naming skip notice. The --cost-* run-path flags do not return; the verb sugar passes no cost (docs carry it) — the same lean-flag call as the C26 bindings block. #152's interning ships with it (cost_port/intern_port, both cost_graph .leak()s gone). Decision log: #234.