Dissolve the orchestration verbs into the campaign path (sweep, mc, walkforward, generalize) #210

Closed
opened 2026-07-04 15:55:07 +02:00 by Brummel · 27 comments
Owner

Context

Fork A of the role-model design pass (issues/188#issuecomment — "Direction forks resolved", 2026-07-03) settled the fate of the four standalone orchestration verbs: dissolve them into the campaign path — "Das Ziel muss sein, das bestehende im Neuen aufzulösen - aber erst wenn das Neue trägt" (user, 2026-07-03). The reading on record: a verb is re-cut as thin sugar over a generated campaign document only when (not before) the campaign substrate reproduces its behaviour, then retires on the #159 pattern; the "once it carries" gate applies per verb.

The gate's precondition is now satisfied: the "Research artifacts — process & campaign documents" milestone closed 2026-07-04 with a green end-to-end milestone fieldtest — the campaign substrate demonstrably carries its own path. What remains per verb is coverage: whether the campaign path reproduces that verb's behaviour. This issue anchors the dissolution milestone and is the decision log for its design forks.

Coverage map (recon against the current tree, 2026-07-04)

sweep — The verb performs no selection: it appends a FamilyKind::Sweep family and prints one member line per grid point; ranking is deferred to aura runs family <id> rank. The campaign sweep stage's core job IS selection (metric+select+deflate required; a StageSelection is always recorded). Verb-only surface: ad-hoc CSV axes (--fast/--slow/…), --axis on blueprints, --list-axes, per-member --trace dirs, the five built-in demo strategies, the synthetic data path. Campaign-only: trials-deflation and plateau as first-class slots, the multi-instrument x multi-window matrix, selection_report.

mc — Three verb sub-paths. Only the R-bootstrap path (--strategy r-sma: walk-forward, pool OOS trade-R, r_bootstrap) has a campaign analog (the std::monte_carlo stage's PooledOos arm). The synthetic seed-resweep and blueprint --seeds N paths re-run the harness across distinct synthetic price realizations and reduce metric distributions (McAggregate over total_pips/max_drawdown/bias_sign_flips) — no campaign equivalent exists. Genuine semantic difference: the verb (synthetic/blueprint paths) resamples the data-generating seed; the campaign stage resamples the realized R series. The campaign's PerSurvivor arm conversely has no standalone verb.

walkforward — The verb hard-wires the selection metric per strategy (total_pips for sma, sqn_normalized for r-sma/blueprints) and the window geometry (synthetic: bar-index 24/12/12; real: 90/30/30 days), always rolling, and supports --select plateau:* (it sweeps the full grid per window, so a lattice exists). The campaign wf stage takes explicit in/oos/step_ms + mode (incl. anchored, which the verb lacks), a free metric slot, but refuses plateau (it re-sweeps only post-gate survivors — no lattice) and emits no stitched summary (stitched_total_pips, param_stability, pooled oos_r).

generalize — The verb grades one fixed candidate (identical params applied to every instrument, r-sma only, single window): the cross-instrument floor of the same params everywhere. The campaign generalize stage grades each cell's own selected nominee (per-instrument-optimized winners) per (strategy, window). The verb's question is expressible in the campaign vocabulary as a degenerate case: single-point axes make each cell's nominee exactly the fixed candidate.

Presentation — Each verb prints a bespoke summary line (walkforward_summary_json, mc_aggregate_json / mc_r_bootstrap_json, generalize_json) with no campaign stdout equivalent; aura campaign run emits emit-gated family_table/selection_report lines plus one final campaign_run record line. A sugar re-cut needs a presentation adapter reconstructing the verb's pinned lines from record fields. The family_member_line byte shape is already shared between both paths.

Cross-cutting — The campaign member runner executes stored blueprints over real archive windows only. The verbs' built-in demo strategies and synthetic data branches have no campaign target at all; their retirement is #159's subject.

Open design forks (gate the first cycle's spec)

  1. Select-free sweep. Loosen the process vocabulary (selection optional, e.g. when the sweep is terminal) vs. the sugar injecting a default selection the invocation never asked for (the record would claim a selection intent that did not exist).
  2. Scope of dissolution vs. #159. Dissolve only the real-data blueprint branches and leave built-in/synthetic branches untouched until #159 retires them, vs. extend the document vocabulary with a synthetic source form so those branches dissolve too.
  3. Seed-resweep Monte-Carlo. A new stage form (an axis over seeds is P1-conformant — "an MC run IS a loop over seeds", issues/188#issuecomment "the power axis") vs. verb-only semantics that never dissolve vs. retiring the semantics.
  4. Plateau in campaign walk-forward. Currently refused because the wf stage sweeps only survivors; a gate-free document sweeps the full lattice — allow plateau when the surviving set is the full grid?
  5. Summary-line fidelity. The verbs' integration tests pin the summary lines; "reproduces its behaviour" read strictly implies byte-identical stdout via a presentation adapter — vs. re-pinning the tests to the campaign record shape.
  6. Dissolution order. Which verb is re-cut first (distance per the coverage map: generalize < walkforward < sweep < mc).

Acceptance (per verb)

The verb's dissolved invocation forms run as thin sugar over an internally generated campaign document through the one campaign executor; the behaviour pinned by the verb's existing integration tests is reproduced; the campaign run record captures the realized intent that previously evaporated into shell history. Undissolved branches (per fork 2) stay untouched.

Refs #188, refs #159.

## Context Fork A of the role-model design pass (issues/188#issuecomment — "Direction forks resolved", 2026-07-03) settled the fate of the four standalone orchestration verbs: dissolve them into the campaign path — "Das Ziel muss sein, das bestehende im Neuen aufzulösen - aber erst wenn das Neue trägt" (user, 2026-07-03). The reading on record: a verb is re-cut as thin sugar over a generated campaign document only when (not before) the campaign substrate reproduces its behaviour, then retires on the #159 pattern; the "once it carries" gate applies per verb. The gate's precondition is now satisfied: the "Research artifacts — process & campaign documents" milestone closed 2026-07-04 with a green end-to-end milestone fieldtest — the campaign substrate demonstrably carries its own path. What remains per verb is coverage: whether the campaign path reproduces that verb's behaviour. This issue anchors the dissolution milestone and is the decision log for its design forks. ## Coverage map (recon against the current tree, 2026-07-04) **sweep** — The verb performs no selection: it appends a `FamilyKind::Sweep` family and prints one member line per grid point; ranking is deferred to `aura runs family <id> rank`. The campaign sweep stage's core job IS selection (`metric`+`select`+`deflate` required; a `StageSelection` is always recorded). Verb-only surface: ad-hoc CSV axes (`--fast`/`--slow`/…), `--axis` on blueprints, `--list-axes`, per-member `--trace` dirs, the five built-in demo strategies, the synthetic data path. Campaign-only: trials-deflation and plateau as first-class slots, the multi-instrument x multi-window matrix, `selection_report`. **mc** — Three verb sub-paths. Only the R-bootstrap path (`--strategy r-sma`: walk-forward, pool OOS trade-R, `r_bootstrap`) has a campaign analog (the `std::monte_carlo` stage's `PooledOos` arm). The synthetic seed-resweep and blueprint `--seeds N` paths re-run the harness across distinct synthetic price realizations and reduce metric distributions (`McAggregate` over `total_pips`/`max_drawdown`/`bias_sign_flips`) — no campaign equivalent exists. Genuine semantic difference: the verb (synthetic/blueprint paths) resamples the data-generating seed; the campaign stage resamples the realized R series. The campaign's `PerSurvivor` arm conversely has no standalone verb. **walkforward** — The verb hard-wires the selection metric per strategy (`total_pips` for sma, `sqn_normalized` for r-sma/blueprints) and the window geometry (synthetic: bar-index 24/12/12; real: 90/30/30 days), always rolling, and supports `--select plateau:*` (it sweeps the full grid per window, so a lattice exists). The campaign wf stage takes explicit `in/oos/step_ms` + `mode` (incl. anchored, which the verb lacks), a free metric slot, but refuses plateau (it re-sweeps only post-gate survivors — no lattice) and emits no stitched summary (`stitched_total_pips`, `param_stability`, pooled `oos_r`). **generalize** — The verb grades one fixed candidate (identical params applied to every instrument, r-sma only, single window): the cross-instrument floor of the same params everywhere. The campaign generalize stage grades each cell's own selected nominee (per-instrument-optimized winners) per (strategy, window). The verb's question is expressible in the campaign vocabulary as a degenerate case: single-point axes make each cell's nominee exactly the fixed candidate. **Presentation** — Each verb prints a bespoke summary line (`walkforward_summary_json`, `mc_aggregate_json` / `mc_r_bootstrap_json`, `generalize_json`) with no campaign stdout equivalent; `aura campaign run` emits emit-gated `family_table`/`selection_report` lines plus one final `campaign_run` record line. A sugar re-cut needs a presentation adapter reconstructing the verb's pinned lines from record fields. The `family_member_line` byte shape is already shared between both paths. **Cross-cutting** — The campaign member runner executes stored blueprints over real archive windows only. The verbs' built-in demo strategies and synthetic data branches have no campaign target at all; their retirement is #159's subject. ## Open design forks (gate the first cycle's spec) 1. **Select-free sweep.** Loosen the process vocabulary (selection optional, e.g. when the sweep is terminal) vs. the sugar injecting a default selection the invocation never asked for (the record would claim a selection intent that did not exist). 2. **Scope of dissolution vs. #159.** Dissolve only the real-data blueprint branches and leave built-in/synthetic branches untouched until #159 retires them, vs. extend the document vocabulary with a synthetic source form so those branches dissolve too. 3. **Seed-resweep Monte-Carlo.** A new stage form (an axis over seeds is P1-conformant — "an MC run IS a loop over seeds", issues/188#issuecomment "the power axis") vs. verb-only semantics that never dissolve vs. retiring the semantics. 4. **Plateau in campaign walk-forward.** Currently refused because the wf stage sweeps only survivors; a gate-free document sweeps the full lattice — allow plateau when the surviving set is the full grid? 5. **Summary-line fidelity.** The verbs' integration tests pin the summary lines; "reproduces its behaviour" read strictly implies byte-identical stdout via a presentation adapter — vs. re-pinning the tests to the campaign record shape. 6. **Dissolution order.** Which verb is re-cut first (distance per the coverage map: generalize < walkforward < sweep < mc). ## Acceptance (per verb) The verb's dissolved invocation forms run as thin sugar over an internally generated campaign document through the one campaign executor; the behaviour pinned by the verb's existing integration tests is reproduced; the campaign run record captures the realized intent that previously evaporated into shell history. Undissolved branches (per fork 2) stay untouched. Refs #188, refs #159.
Brummel added this to the Verb dissolution — the campaign path as canonical orchestration milestone 2026-07-04 15:55:07 +02:00
Author
Owner

Fork triage: verb dissolution (grounded stance review, 2026-07-04)

The seven open forks in this issue's body were populated by five adversarial design lenses (minimalist / composability / maintainer / skeptic / off-ledger wildcard), each grounding its answers in the design ledger, glossary, issues #188/#159/#210, and the code. Decisions below are made on cited ground; veto by replying here. No fork remained a genuine direction fork: where lenses split, one side's cited ground is dispositive and the adjudication is recorded.

Decided

  1. Fork 1 — selection becomes optional in the std::sweep stage vocabulary, permitted when no downstream stage consumes a nominee (at minimum: when the sweep is terminal). All lenses rejected a sugar-injected default selection: a run record claiming selection intent the invocation never expressed is a permanent audit lie (C18's reproducible-manifest discipline in spirit). The counter-position "leave the selection-free form un-dissolved" grounds on the fork-A timing clause ("until then the verbs stay untouched"), which governs when, not whether to cover a branch — the same user decision's goal sentence ("Das Ziel muss sein, das bestehende im Neuen aufzulösen", 2026-07-03) settles the direction. The optional slot wraps shipped, inventoried behaviour (aura sweep appends a family and defers ranking to aura runs family <id> rank), so the "no invented taxonomy" guard does not apply. The exact structural rule (terminal-only vs. required-iff-a-nominee-consumer-follows) is a vocabulary-spec detail.

  2. Fork 2 — dissolution covers the real-data blueprint branches only; the built-in demo strategies and synthetic data branches stay verb-wired until #159 retires them. Converged 4:0 on cited ground: #159's own body gates that retirement separately ("Droppable / no commitment until those land"), C16 forbids project signals in the engine repo, and extending the closed campaign vocabulary with a synthetic-source form would build vocabulary for constructs already earmarked for deletion. This also matches the honest extent of the "once it carries" gate evidence: the green milestone fieldtest (0106-0109) exercised the real-archive path only — the dissolved scope and the validated scope coincide.

  3. Fork 3 — the seed-resweep Monte-Carlo semantics (synthetic seed-resweep, blueprint --seeds N) stay verb-only, an un-dissolved residue whose fate follows the built-in/synthetic branches (#159). Consequence of decision 2, not a preference: without a synthetic source form in the document vocabulary, a seeds axis has nothing to range over — the campaign member runner executes stored blueprints over real archive windows only. The P1 licence ("an MC run IS a loop over seeds", issues/188 power-axis comment) keeps the door structurally open should synthetic sources ever enter; recorded alongside: if such a form ever lands, it must not share the monte_carlo stage name — resampling the data-generating seed and bootstrapping the realized R series are different objects and a shared name would conflate them. Retiring the semantics now (the C10-precedent argument) was rejected: C10's retirement carried an epistemic argument (historical friction unknowable) that does not transfer here.

  4. Fork 4 — plateau selection becomes permitted in the campaign walk-forward stage when no gate precedes it (structural, preflight-checkable); it stays refused when a gate precedes. The shipped refusal's own recorded reason is "a gated survivor subset has no grid lattice"; in a gate-free document the wf stage re-sweeps the full declared grid, the lattice exists, and the refusal would be a rule outliving its reason. Unconditional allowance (silent degradation on non-lattice survivor sets) was rejected by every lens that addressed it. This is the enabling change for dissolving the walkforward verb's --select plateau:* branch.

  5. Fork 5 — full behaviour parity: byte-identical pinned stdout via a presentation adapter deriving the verbs' summary lines from campaign records, plus registry-family parity (dissolved verbs keep persisting the FamilyKind families the verbs persist today). "Reproduces its behaviour" is the ratified gate wording (fork A, 2026-07-03) and the verbs' integration tests pin stdout shapes and families; re-pinning the tests would redefine the behaviour instead of reproducing it. Registry families are data, not presentation: aura runs family <id> / rank are live consumers that would silently stop finding families otherwise. The C25 stateless-projection analogy offered for re-pinning is an admitted analogical stretch (C25's scope is visual surfaces). Whether family parity is implemented by the campaign annotators persisting families or by the sugar layer is an implementation-spec detail.

  6. Fork 6 — the generated campaign document and its generated process document are auto-registered into the content-addressed stores on every sugar run. Converged 4:0: the #188 fork-C resolution already names auto-registration as the designated tooling pattern; a computed-but-unstored content id would make the run record's reproduction pointer dangle (the exact thing C18 forbids); an opt-in flag reinstates intent-evaporation as the forgotten-flag default. With this, every ad-hoc verb invocation becomes durable, diffable, reproducible intent — the #188 diagnosis's cure applied to the verbs themselves.

  7. Fork 7 — the sweep re-cut ships first; the remaining verbs follow by coverage distance (generalize, walkforward, mc's R-bootstrap path). Structural ground: the v2 process shape makes std::sweep the mandatory first stage of every generated document, so the sweep sugar's translator (axes mapping, window mapping, blueprint registration, presentation adapter, auto-registration) is the shared skeleton all four dissolutions ride on. Beyond that structural constraint, ordering is sequencing judgement, not design.

Surfaced concerns and their dispositions

  • Per-member --trace vs. the closed tap vocabulary (composability lens): resolved by decision 2's scope — the dissolved real-data blueprint branches carry no live per-member trace surface today (run_blueprint_sweep's persist slot is a reserved no-op, tracked as #168; the blueprint walkforward branch rejects --trace; mc's trace belongs to the synthetic branch). Whether the campaign path ever grows per-member trace persistence remains #168's question, out of this milestone.
  • The argv→document translator as a second serialization surface (maintainer lens): bounded by building it once as a single shared module (decision 7) and by the verbs' sunset trajectory (fork A: re-cut, then retire on the #159 pattern) — the lockstep burden ends with the verbs.
  • Old-path deletion (minimalist lens): folded into acceptance — a dissolved branch's inline implementation is removed in the same cycle its sugar ships; "thin sugar" replaces the execution path, it never adds a parallel one.
  • Vocabulary scope-creep (wildcard lens): honoured by decisions 2/3 (no synthetic source form, no seeds axis, no new stage names) — the only vocabulary changes this milestone makes are one optional slot (decision 1) and one preflight relaxation with a structural criterion (decision 4).

Status

All forks of the dissolution design are decided; the design basis is this issue's body (coverage map) plus the decisions above. The first cycle — the sweep re-cut: translator skeleton, optional-selection vocabulary change, auto-registration, presentation adapter, inline-path deletion for the dissolved branch — is settled and ready for spec production.

## Fork triage: verb dissolution (grounded stance review, 2026-07-04) The seven open forks in this issue's body were populated by five adversarial design lenses (minimalist / composability / maintainer / skeptic / off-ledger wildcard), each grounding its answers in the design ledger, glossary, issues #188/#159/#210, and the code. Decisions below are made on cited ground; veto by replying here. No fork remained a genuine direction fork: where lenses split, one side's cited ground is dispositive and the adjudication is recorded. ### Decided 1. **Fork 1 — selection becomes optional in the `std::sweep` stage vocabulary, permitted when no downstream stage consumes a nominee (at minimum: when the sweep is terminal).** All lenses rejected a sugar-injected default selection: a run record claiming selection intent the invocation never expressed is a permanent audit lie (C18's reproducible-manifest discipline in spirit). The counter-position "leave the selection-free form un-dissolved" grounds on the fork-A timing clause ("until then the verbs stay untouched"), which governs *when*, not *whether* to cover a branch — the same user decision's goal sentence ("Das Ziel muss sein, das bestehende im Neuen aufzulösen", 2026-07-03) settles the direction. The optional slot wraps shipped, inventoried behaviour (`aura sweep` appends a family and defers ranking to `aura runs family <id> rank`), so the "no invented taxonomy" guard does not apply. The exact structural rule (terminal-only vs. required-iff-a-nominee-consumer-follows) is a vocabulary-spec detail. 2. **Fork 2 — dissolution covers the real-data blueprint branches only; the built-in demo strategies and synthetic data branches stay verb-wired until #159 retires them.** Converged 4:0 on cited ground: #159's own body gates that retirement separately ("Droppable / no commitment until those land"), C16 forbids project signals in the engine repo, and extending the closed campaign vocabulary with a synthetic-source form would build vocabulary for constructs already earmarked for deletion. This also matches the honest extent of the "once it carries" gate evidence: the green milestone fieldtest (0106-0109) exercised the real-archive path only — the dissolved scope and the validated scope coincide. 3. **Fork 3 — the seed-resweep Monte-Carlo semantics (synthetic seed-resweep, blueprint `--seeds N`) stay verb-only, an un-dissolved residue whose fate follows the built-in/synthetic branches (#159).** Consequence of decision 2, not a preference: without a synthetic source form in the document vocabulary, a seeds axis has nothing to range over — the campaign member runner executes stored blueprints over real archive windows only. The P1 licence ("an MC run IS a loop over seeds", issues/188 power-axis comment) keeps the door structurally open should synthetic sources ever enter; recorded alongside: if such a form ever lands, it must not share the `monte_carlo` stage name — resampling the data-generating seed and bootstrapping the realized R series are different objects and a shared name would conflate them. Retiring the semantics now (the C10-precedent argument) was rejected: C10's retirement carried an epistemic argument (historical friction unknowable) that does not transfer here. 4. **Fork 4 — plateau selection becomes permitted in the campaign walk-forward stage when no gate precedes it (structural, preflight-checkable); it stays refused when a gate precedes.** The shipped refusal's own recorded reason is "a gated survivor subset has no grid lattice"; in a gate-free document the wf stage re-sweeps the full declared grid, the lattice exists, and the refusal would be a rule outliving its reason. Unconditional allowance (silent degradation on non-lattice survivor sets) was rejected by every lens that addressed it. This is the enabling change for dissolving the walkforward verb's `--select plateau:*` branch. 5. **Fork 5 — full behaviour parity: byte-identical pinned stdout via a presentation adapter deriving the verbs' summary lines from campaign records, plus registry-family parity (dissolved verbs keep persisting the `FamilyKind` families the verbs persist today).** "Reproduces its behaviour" is the ratified gate wording (fork A, 2026-07-03) and the verbs' integration tests pin stdout shapes and families; re-pinning the tests would redefine the behaviour instead of reproducing it. Registry families are data, not presentation: `aura runs family <id>` / `rank` are live consumers that would silently stop finding families otherwise. The C25 stateless-projection analogy offered for re-pinning is an admitted analogical stretch (C25's scope is visual surfaces). Whether family parity is implemented by the campaign annotators persisting families or by the sugar layer is an implementation-spec detail. 6. **Fork 6 — the generated campaign document and its generated process document are auto-registered into the content-addressed stores on every sugar run.** Converged 4:0: the #188 fork-C resolution already names auto-registration as the designated tooling pattern; a computed-but-unstored content id would make the run record's reproduction pointer dangle (the exact thing C18 forbids); an opt-in flag reinstates intent-evaporation as the forgotten-flag default. With this, every ad-hoc verb invocation becomes durable, diffable, reproducible intent — the #188 diagnosis's cure applied to the verbs themselves. 7. **Fork 7 — the sweep re-cut ships first; the remaining verbs follow by coverage distance (generalize, walkforward, mc's R-bootstrap path).** Structural ground: the v2 process shape makes `std::sweep` the mandatory first stage of every generated document, so the sweep sugar's translator (axes mapping, window mapping, blueprint registration, presentation adapter, auto-registration) is the shared skeleton all four dissolutions ride on. Beyond that structural constraint, ordering is sequencing judgement, not design. ### Surfaced concerns and their dispositions - **Per-member `--trace` vs. the closed tap vocabulary** (composability lens): resolved by decision 2's scope — the dissolved real-data blueprint branches carry no live per-member trace surface today (`run_blueprint_sweep`'s persist slot is a reserved no-op, tracked as #168; the blueprint walkforward branch rejects `--trace`; mc's trace belongs to the synthetic branch). Whether the campaign path ever grows per-member trace persistence remains #168's question, out of this milestone. - **The argv→document translator as a second serialization surface** (maintainer lens): bounded by building it once as a single shared module (decision 7) and by the verbs' sunset trajectory (fork A: re-cut, then retire on the #159 pattern) — the lockstep burden ends with the verbs. - **Old-path deletion** (minimalist lens): folded into acceptance — a dissolved branch's inline implementation is removed in the same cycle its sugar ships; "thin sugar" replaces the execution path, it never adds a parallel one. - **Vocabulary scope-creep** (wildcard lens): honoured by decisions 2/3 (no synthetic source form, no seeds axis, no new stage names) — the only vocabulary changes this milestone makes are one optional slot (decision 1) and one preflight relaxation with a structural criterion (decision 4). ### Status All forks of the dissolution design are decided; the design basis is this issue's body (coverage map) plus the decisions above. The first cycle — the sweep re-cut: translator skeleton, optional-selection vocabulary change, auto-registration, presentation adapter, inline-path deletion for the dissolved branch — is settled and ready for spec production.
Author
Owner

Spec-level derived decisions (sweep dissolution, cycle 1)

Recorded per the decision-log discipline; each is a derived orchestrator decision with its rationale, refining the fork-triage comment on this issue (issues/210, comment "Fork triage: verb dissolution", 2026-07-04). Veto by replying here.

  • Selection-free sweep rule: terminal-stage-only. Derived: every downstream stage kind consumes either the family's selection state or the nominee, so "no selection ⇒ nothing may follow" is the narrowest preflight-checkable rule that keeps the executor's nominee contract intact; a later refinement (required-iff-a-nominee-consumer-follows) stays open without foreclosing anything.
  • Vocabulary shape: the selection triple (metric/select/deflate) becomes one optional group (Option<SweepSelection>). Derived: a half-populated triple is unrepresentable by construction — the same make-invalid-unrepresentable move the axis-kind decision took (#189); wire form of existing documents is unchanged, no stored content id moves.
  • Generated documents carry seed: 0. Derived: no stage of a selection-free single-sweep pipeline consumes the seed; a fixed value keeps generated bytes deterministic so identical invocations dedupe onto identical content ids.
  • --name maps to the campaign document name; family names follow the campaign convention ({campaign8}-…-w0-s0). Derived: the pinned member stdout lines never carried the family name, so byte parity is unaffected; the user's name handle lands on the durable artifact (campaign document + run record) rather than the derived family row.
  • Sugar-mode presentation: member lines only. Derived: byte parity requires suppressing the campaign path's selection_report and final campaign_run stdout lines for the sugar invocation; the run record is still appended — presentation changes, the record does not.
  • Reproduction evidence: characterization-first. Derived: a gated real-data test pinning the current inline path's observable behaviour is written and green before the re-cut and must survive it unchanged — the mechanical form of the ratified "campaign substrate reproduces its behaviour" gate.

Spec: docs/specs/sweep-dissolution.md (working file, in-tree while the cycle is live).

## Spec-level derived decisions (sweep dissolution, cycle 1) Recorded per the decision-log discipline; each is a derived orchestrator decision with its rationale, refining the fork-triage comment on this issue (issues/210, comment "Fork triage: verb dissolution", 2026-07-04). Veto by replying here. - **Selection-free sweep rule: terminal-stage-only.** Derived: every downstream stage kind consumes either the family's selection state or the nominee, so "no selection ⇒ nothing may follow" is the narrowest preflight-checkable rule that keeps the executor's nominee contract intact; a later refinement (required-iff-a-nominee-consumer-follows) stays open without foreclosing anything. - **Vocabulary shape: the selection triple (metric/select/deflate) becomes one optional group** (`Option<SweepSelection>`). Derived: a half-populated triple is unrepresentable by construction — the same make-invalid-unrepresentable move the axis-kind decision took (#189); wire form of existing documents is unchanged, no stored content id moves. - **Generated documents carry `seed: 0`.** Derived: no stage of a selection-free single-sweep pipeline consumes the seed; a fixed value keeps generated bytes deterministic so identical invocations dedupe onto identical content ids. - **`--name` maps to the campaign document name; family names follow the campaign convention** (`{campaign8}-…-w0-s0`). Derived: the pinned member stdout lines never carried the family name, so byte parity is unaffected; the user's name handle lands on the durable artifact (campaign document + run record) rather than the derived family row. - **Sugar-mode presentation: member lines only.** Derived: byte parity requires suppressing the campaign path's `selection_report` and final `campaign_run` stdout lines for the sugar invocation; the run record is still appended — presentation changes, the record does not. - **Reproduction evidence: characterization-first.** Derived: a gated real-data test pinning the current inline path's observable behaviour is written and green before the re-cut and must survive it unchanged — the mechanical form of the ratified "campaign substrate reproduces its behaviour" gate. Spec: docs/specs/sweep-dissolution.md (working file, in-tree while the cycle is live).
Author
Owner

Derived decision: the instrument manifest stamp survives the sweep re-cut

The spec's grounding check (2026-07-04) surfaced a real divergence the coverage map had missed: the campaign member runner stamps manifest.instrument = Some(<SYM>) into every member report (crates/aura-cli/src/campaign_run.rs:256) while the inline aura sweep --real path leaves the field None (serde omits it) — so the dissolved form's member stdout lines gain an additive "instrument":"<SYM>" key and are not byte-identical to the pre-change verb.

Decision (derived): keep the stamp; correct the spec's byte-identity claim to "identical modulo the additive instrument field". Rationale: the stamp is provenance the substrate deliberately records (the audit direction C18 pulls in), the verbs' existing integration tests nowhere pin the field's absence on the real-data blueprint branch, and stripping it in sugar mode would fork the member runner's behaviour for presentation cosmetics — the same dual-path drift the dissolution exists to remove. The alternative (a sugar-mode flag suppressing the stamp) was rejected on that ground. The characterization test takes an instrument-field-tolerant form, with the stamp's presence asserted separately after the re-cut.

This refines decision 5 of the fork-triage comment on this issue ("full behaviour parity"): parity of the pinned contract, plus one named, recorded, additive provenance field. Veto by replying here.

## Derived decision: the instrument manifest stamp survives the sweep re-cut The spec's grounding check (2026-07-04) surfaced a real divergence the coverage map had missed: the campaign member runner stamps `manifest.instrument = Some(<SYM>)` into every member report (`crates/aura-cli/src/campaign_run.rs:256`) while the inline `aura sweep --real` path leaves the field `None` (serde omits it) — so the dissolved form's member stdout lines gain an additive `"instrument":"<SYM>"` key and are not byte-identical to the pre-change verb. Decision (derived): **keep the stamp; correct the spec's byte-identity claim to "identical modulo the additive instrument field".** Rationale: the stamp is provenance the substrate deliberately records (the audit direction C18 pulls in), the verbs' existing integration tests nowhere pin the field's *absence* on the real-data blueprint branch, and stripping it in sugar mode would fork the member runner's behaviour for presentation cosmetics — the same dual-path drift the dissolution exists to remove. The alternative (a sugar-mode flag suppressing the stamp) was rejected on that ground. The characterization test takes an instrument-field-tolerant form, with the stamp's presence asserted separately after the re-cut. This refines decision 5 of the fork-triage comment on this issue ("full behaviour parity"): parity of the pinned contract, plus one named, recorded, additive provenance field. Veto by replying here.
Author
Owner

Spec auto-signed (grounding-check PASS, no human signature)

The cycle-1 spec (docs/specs/sweep-dissolution.md — the real-data blueprint sweep re-cut as sugar over a generated, auto-registered campaign document; optional selection group in the sweep stage vocabulary; terminal-only preflight rule; inline-path deletion) was signed autonomously. The signature is the grounding-check PASS of 2026-07-04: every load-bearing current-behaviour assumption is ratified by a named, currently-green test. Two earlier grounding dispatches BLOCKed on the instrument-manifest-stamp claim — first as factually wrong (the spec claimed byte-identical member lines; corrected to identity modulo the additive stamp, see the derived-decision comment on this issue), then as code-true-but-unpinned; the second BLOCK was closed by two green characterization pins (commit 8235cee) before any re-cut work, and the third dispatch PASSed with the gated pins confirmed executing against the local real archive. No human signed; the auto-sign notify was sent with veto standing.

## Spec auto-signed (grounding-check PASS, no human signature) The cycle-1 spec (docs/specs/sweep-dissolution.md — the real-data blueprint sweep re-cut as sugar over a generated, auto-registered campaign document; optional selection group in the sweep stage vocabulary; terminal-only preflight rule; inline-path deletion) was signed autonomously. The signature is the grounding-check PASS of 2026-07-04: every load-bearing current-behaviour assumption is ratified by a named, currently-green test. Two earlier grounding dispatches BLOCKed on the instrument-manifest-stamp claim — first as factually wrong (the spec claimed byte-identical member lines; corrected to identity modulo the additive stamp, see the derived-decision comment on this issue), then as code-true-but-unpinned; the second BLOCK was closed by two green characterization pins (commit 8235cee) before any re-cut work, and the third dispatch PASSed with the gated pins confirmed executing against the local real archive. No human signed; the auto-sign notify was sent with veto standing.
Author
Owner

Spec reconciliation note: after the auto-sign, plan recon surfaced three spec/tree mismatches — the preflight fault type (the variant lands on ExecFault; no PreflightFault type exists), the sugar presentation seam (a mode parameter on the campaign-run entry, not a Presentation variant), and the Testing-1 characterization description (the shipped pin test asserts instrument-absence today; the re-cut task flips exactly that one assertion to stamp-equality). The spec was corrected accordingly — no behaviour claim changed — and a fresh grounding check PASSed on the corrected bytes (all ten load-bearing assumptions ratified by named green tests, gated pins confirmed executing against the local archive). The signature stands on that fresh PASS.

Spec reconciliation note: after the auto-sign, plan recon surfaced three spec/tree mismatches — the preflight fault type (the variant lands on ExecFault; no PreflightFault type exists), the sugar presentation seam (a mode parameter on the campaign-run entry, not a Presentation variant), and the Testing-1 characterization description (the shipped pin test asserts instrument-absence today; the re-cut task flips exactly that one assertion to stamp-equality). The spec was corrected accordingly — no behaviour claim changed — and a fresh grounding check PASSed on the corrected bytes (all ten load-bearing assumptions ratified by named green tests, gated pins confirmed executing against the local archive). The signature stands on that fresh PASS.
Author
Owner

Cycle 1 (sweep dissolution) shipped: commits c37abb2 (optional selection group + terminal-only preflight + selection-free executor arm), fd0b21d/verb_sugar translator, b7aaa0b (dispatch re-cut). The real-data blueprint sweep now runs as sugar over a generated, auto-registered campaign document through the one executor; the inline real-data arm is gone; synthetic/built-in branches and --list-axes untouched per decision 2. Three implementation-level findings recorded for later verb cycles: (1) generated documents speak the RAW campaign-axis namespace — the dispatch strips the CLI probe's wrapper segment (#203 convention); (2) the probed archive window is epoch-ns at the seam and converts once through aura-ingest's epoch_ns_to_unix_ms (now pub) into the document's ms currency; (3) family ids carry a per-execution run-suffix by design, so cross-invocation determinism is pinned at the member-report level, not stdout bytes. Characterization pin survived the re-cut with exactly the one sanctioned assertion flip; suite 1055/0; gated real-data paths executed locally. Remaining in the milestone: generalize, walkforward, mc (R-bootstrap path) per decision 7.

Cycle 1 (sweep dissolution) shipped: commits c37abb2 (optional selection group + terminal-only preflight + selection-free executor arm), fd0b21d/verb_sugar translator, b7aaa0b (dispatch re-cut). The real-data blueprint sweep now runs as sugar over a generated, auto-registered campaign document through the one executor; the inline real-data arm is gone; synthetic/built-in branches and --list-axes untouched per decision 2. Three implementation-level findings recorded for later verb cycles: (1) generated documents speak the RAW campaign-axis namespace — the dispatch strips the CLI probe's wrapper segment (#203 convention); (2) the probed archive window is epoch-ns at the seam and converts once through aura-ingest's epoch_ns_to_unix_ms (now pub) into the document's ms currency; (3) family ids carry a per-execution run-suffix by design, so cross-invocation determinism is pinned at the member-report level, not stdout bytes. Characterization pin survived the re-cut with exactly the one sanctioned assertion flip; suite 1055/0; gated real-data paths executed locally. Remaining in the milestone: generalize, walkforward, mc (R-bootstrap path) per decision 7.
Author
Owner

Cycle 0110 closed end-to-end. Audit: drift-clean after two fixes (docs aligned with the optional selection group; the #203 wrapped/raw axis convention single-sourced as a cross-documented inverse pair in campaign_run — ratified semantics tightening: the old loose suffix-match's undocumented sub-segment shorthands now refuse loudly instead of matching). Fieldtest (fieldtests/cycle-0110-sweep-dissolution/, spec in docs/specs/): GREEN-core, 0 bugs, all four cycle axes working. Findings disposition: 5 fixed same-session — axis-name preflight echoing the typed name with a --list-axes pointer; validate-before-register in the sugar (refusals leave no store litter); house-style blueprint-slot prose shared by sweep/walkforward/mc incl. an op-script build-first hint; authoring-guide §1 bind inconsistency + the bind-closes-the-axis rule; the sweep-axis wrapped-namespace documentation. 1 deferred as #211 (in-project intrinsic validate hits the cdylib wall — pre-existing eager-load coupling, resolves in the role-homes design pass, not per-verb). Suite 1060/0. Next per decision 7: the generalize re-cut.

Cycle 0110 closed end-to-end. Audit: drift-clean after two fixes (docs aligned with the optional selection group; the #203 wrapped/raw axis convention single-sourced as a cross-documented inverse pair in campaign_run — ratified semantics tightening: the old loose suffix-match's undocumented sub-segment shorthands now refuse loudly instead of matching). Fieldtest (fieldtests/cycle-0110-sweep-dissolution/, spec in docs/specs/): GREEN-core, 0 bugs, all four cycle axes working. Findings disposition: 5 fixed same-session — axis-name preflight echoing the typed name with a --list-axes pointer; validate-before-register in the sugar (refusals leave no store litter); house-style blueprint-slot prose shared by sweep/walkforward/mc incl. an op-script build-first hint; authoring-guide §1 bind inconsistency + the bind-closes-the-axis rule; the sweep-axis wrapped-namespace documentation. 1 deferred as #211 (in-project intrinsic validate hits the cdylib wall — pre-existing eager-load coupling, resolves in the role-homes design pass, not per-verb). Suite 1060/0. Next per decision 7: the generalize re-cut.
Author
Owner

Fork triage: risk-harness parameters across the campaign seam (grounded stance review, 2026-07-04)

Cycle-2 recon surfaced the milestone's central remaining design question, and a five-lens grounded stance review (minimalist / composability / maintainer / skeptic / off-ledger wildcard) ran on it. One fork is decided on cited ground; the load-bearing one is a genuine direction fork and awaits a decision here.

The finding

The verbs' r-sma machinery treats fast/slow/stop-length/stop-k as ONE search space (r_sma_sweep_family builds stop-open and grids all four). The campaign member runner bakes the stop to fixed defaults (3 / 2.0) inside non-serialized scaffolding; stored strategy blueprints are the bias leg only, and the referential tier requires every open param to be covered by an axis ("there is no default"). Consequences: the campaign path cannot vary stop parameters at all; a non-default-stop candidate is not reproducible through it (generalize's own E2E pin uses --stop-length 14); and — the audit gap — the executed stop regime appears in no content-addressed artifact, so "same candidate, different stop" would leave identical topology hashes (the C18 concern). This blocks the behaviour-preserving dissolution of generalize, walkforward, and mc's R-path equally.

Decided (cited ground)

Cycle sequencing: the stop question is resolved first, as its own design pass; no further verb is dissolved against the unresolved substrate. Ground: the ratified per-verb gate ("a verb is re-cut only when the campaign substrate reproduces its behaviour" — fork A, 2026-07-03) is not satisfied by reproducing only default-stop invocations; a backend split keyed on flag VALUES (default stop → campaign path, other → inline) would produce silently different registry artifacts for look-alike invocations — the dual-path drift this milestone exists to remove — and was rejected by three lenses on that gate wording. "Dissolve a stop-free branch first" is foreclosed by fact: all three remaining verbs share the same four knobs. (The one lens preferring the partial split undermined it in its own surfaced concern: the non-default-stop E2E pin would become a permanent carve-out, not a dissolution.)

Open — the direction fork (user's call)

Where do risk-harness parameters (stop-length, stop-k) live in the artifact model? The lenses split 2:2 with citable ground on both sides, plus two off-menu directions; no source settles it:

  • (a) Ordinary campaign axes over a stop-open param space (maintainer, composability). For: the executed stop regime becomes addressable, auditable data — today's baked default is a live audit-trail gap (C18 spirit: what ran must be reconstructable from recorded artifacts); the verbs' own precedent treats signal+stop as one joint search space; the glossary already homes "per-strategy param axes" in the campaign document. Cost: under the twice-stated "no default" rule, every stored campaign document becomes invalid unless either all are migrated (maintainer's preference: absolute rule, upfront churn) or a default concept enters the vocabulary (composability's preference: the owning artifact declares the default, axes override — noting the baked constants ARE already a silent, unadmitted default).
  • (d) Nowhere yet — keep the baked default, defer to its own later design pass (minimalist, skeptic). For: no shipped campaign document has ever needed stop variation (the only pressure is one verb E2E fixture); this milestone's own triage refused vocabulary-for-the-unproven twice (no synthetic source form, no seeds axis — same discipline); invariant 7's strategy/risk-execution boundary deserves an unhurried design pass, not a decision under dissolution-cycle pressure. Cost: generalize/walkforward/mc stay un-dissolved until that pass happens; the audit gap persists meanwhile.
  • (Off-menu, surfaced by the composability lens): risk regime as a STRUCTURAL axis — a peer of instruments/windows at the campaign document's top level, not a per-strategy tuning axis. Precedent cited: the C20 realization treats bar-period as a structural matrix axis, "never a param_space entry, so a sweep cannot desync"; C10's "the stop defines the risk unit R" reads a stop change as a different risk regime (a different kind of candidate), which is structural-axis-shaped. Un-decided by the stances; named as a genuine fourth direction.
  • (Off-menu, wildcard, deliberately against the ledger): stop policy stays authored Rust (role 2), the campaign tier never carries risk params; and separately: is "stop = two scalars" even the right closed vocabulary long-term (trailing/breakeven/regime-conditional stops would force a re-migration of whichever tier wins)?
  • (b) into the stored blueprint found no taker: it collapses the bias/risk-execution artifact boundary into node structure (signal-vs-stop provenance becomes unreadable from the artifact), and it forecloses cheap strategy × risk-regime recombination (every pair a separate blueprint).
  • (c) into the process document found no taker as posed: the verbs treat the stop as candidate-search space, and P1 axes live in the campaign document — though the structural-axis reading above is its stronger sibling.

Cross-cutting note (maintainer): whichever direction wins, the resolved stop parameters must land in the per-run manifests / addressable record — resolving where they live without stamping what ran would repeat the instrument-stamp near-miss the cycle-1 grounding check caught.

Status

Milestone paused at this fork after cycle 1 (sweep dissolved, shipped, closed end-to-end). The direction decision above gates cycles 2-4 (generalize, walkforward, mc's R-path). Decision requested; the fork's options and grounds are reproduced in full above so the choice can be made from this thread alone.

## Fork triage: risk-harness parameters across the campaign seam (grounded stance review, 2026-07-04) Cycle-2 recon surfaced the milestone's central remaining design question, and a five-lens grounded stance review (minimalist / composability / maintainer / skeptic / off-ledger wildcard) ran on it. One fork is decided on cited ground; the load-bearing one is a genuine direction fork and awaits a decision here. ### The finding The verbs' r-sma machinery treats fast/slow/stop-length/stop-k as ONE search space (`r_sma_sweep_family` builds stop-open and grids all four). The campaign member runner bakes the stop to fixed defaults (3 / 2.0) inside non-serialized scaffolding; stored strategy blueprints are the bias leg only, and the referential tier requires every open param to be covered by an axis ("there is no default"). Consequences: the campaign path cannot vary stop parameters at all; a non-default-stop candidate is not reproducible through it (generalize's own E2E pin uses --stop-length 14); and — the audit gap — the executed stop regime appears in no content-addressed artifact, so "same candidate, different stop" would leave identical topology hashes (the C18 concern). This blocks the behaviour-preserving dissolution of generalize, walkforward, and mc's R-path equally. ### Decided (cited ground) **Cycle sequencing: the stop question is resolved first, as its own design pass; no further verb is dissolved against the unresolved substrate.** Ground: the ratified per-verb gate ("a verb is re-cut only when the campaign substrate reproduces its behaviour" — fork A, 2026-07-03) is not satisfied by reproducing only default-stop invocations; a backend split keyed on flag VALUES (default stop → campaign path, other → inline) would produce silently different registry artifacts for look-alike invocations — the dual-path drift this milestone exists to remove — and was rejected by three lenses on that gate wording. "Dissolve a stop-free branch first" is foreclosed by fact: all three remaining verbs share the same four knobs. (The one lens preferring the partial split undermined it in its own surfaced concern: the non-default-stop E2E pin would become a permanent carve-out, not a dissolution.) ### Open — the direction fork (user's call) **Where do risk-harness parameters (stop-length, stop-k) live in the artifact model?** The lenses split 2:2 with citable ground on both sides, plus two off-menu directions; no source settles it: - **(a) Ordinary campaign axes over a stop-open param space** (maintainer, composability). For: the executed stop regime becomes addressable, auditable data — today's baked default is a live audit-trail gap (C18 spirit: what ran must be reconstructable from recorded artifacts); the verbs' own precedent treats signal+stop as one joint search space; the glossary already homes "per-strategy param axes" in the campaign document. Cost: under the twice-stated "no default" rule, every stored campaign document becomes invalid unless either all are migrated (maintainer's preference: absolute rule, upfront churn) or a default concept enters the vocabulary (composability's preference: the owning artifact declares the default, axes override — noting the baked constants ARE already a silent, unadmitted default). - **(d) Nowhere yet — keep the baked default, defer to its own later design pass** (minimalist, skeptic). For: no shipped campaign document has ever needed stop variation (the only pressure is one verb E2E fixture); this milestone's own triage refused vocabulary-for-the-unproven twice (no synthetic source form, no seeds axis — same discipline); invariant 7's strategy/risk-execution boundary deserves an unhurried design pass, not a decision under dissolution-cycle pressure. Cost: generalize/walkforward/mc stay un-dissolved until that pass happens; the audit gap persists meanwhile. - **(Off-menu, surfaced by the composability lens): risk regime as a STRUCTURAL axis** — a peer of instruments/windows at the campaign document's top level, not a per-strategy tuning axis. Precedent cited: the C20 realization treats bar-period as a structural matrix axis, "never a param_space entry, so a sweep cannot desync"; C10's "the stop defines the risk unit R" reads a stop change as a different risk regime (a different kind of candidate), which is structural-axis-shaped. Un-decided by the stances; named as a genuine fourth direction. - **(Off-menu, wildcard, deliberately against the ledger):** stop policy stays authored Rust (role 2), the campaign tier never carries risk params; and separately: is "stop = two scalars" even the right closed vocabulary long-term (trailing/breakeven/regime-conditional stops would force a re-migration of whichever tier wins)? - **(b) into the stored blueprint** found no taker: it collapses the bias/risk-execution artifact boundary into node structure (signal-vs-stop provenance becomes unreadable from the artifact), and it forecloses cheap strategy × risk-regime recombination (every pair a separate blueprint). - **(c) into the process document** found no taker as posed: the verbs treat the stop as candidate-search space, and P1 axes live in the campaign document — though the structural-axis reading above is its stronger sibling. Cross-cutting note (maintainer): whichever direction wins, the resolved stop parameters must land in the per-run manifests / addressable record — resolving *where they live* without stamping *what ran* would repeat the instrument-stamp near-miss the cycle-1 grounding check caught. ### Status Milestone paused at this fork after cycle 1 (sweep dissolved, shipped, closed end-to-end). The direction decision above gates cycles 2-4 (generalize, walkforward, mc's R-path). Decision requested; the fork's options and grounds are reproduced in full above so the choice can be made from this thread alone.
Author
Owner

Direction decided: risk regime as a structural campaign axis

The fork "where do risk-harness parameters (stop-length, stop-k) live in the artifact model" — options and grounds in the fork-triage comment — is resolved by user decision, 2026-07-06: deferral is ruled out (the question is decided now, not parked), and the structural axis wins over ordinary param axes; the user's stated ground, translated: the structural axis is clearly the more coherent shape.

The decided shape:

  • A risk section enters the campaign document as a peer of data.instruments / data.windows: a finite list of risk regimes in a closed vocabulary, initially { stop_length, stop_k } (the vol-stop's two scalars). The realization matrix gains one structural dimension: strategy x instrument x window x regime.
  • The stop never enters param_space(): strategy axes keep binding only the referenced blueprint's real open knobs, and the campaign runner feeds the regime to the member scaffolding the way it feeds instrument and window.

Grounds, one sentence each:

  • C10 ("the stop defines the risk unit R"): a stop change is a change of yardstick, not of candidate — selection therefore ranks only within one regime, where all compared R-multiples share their unit.
  • C20 precedent: bar-period is a structural matrix axis, "never a param_space entry, so a sweep cannot desync" — the stop takes the same shape.
  • Domain invariant 7: the bias-leg / risk-execution boundary stays visible in the document model — stop parameters never masquerade as tuning axes of a blueprint that does not contain them (stored strategy blueprints are the signal leg only, price -> bias).
  • The no-defaults coverage rule ("every open knob is required — there is no default") never meets the stop because the stop never becomes an open knob — the defaults fork that option (a) would have forced dissolves instead of needing an answer.

Ratified consequences:

  1. Deliberate parity break. aura walkforward / aura mc accept --stop-length <csv> / --stop-k <csv> and rank per fold across the joint grid including stops (usage strings at crates/aura-cli/src/main.rs:4651 and :4730); the campaign path will NOT reproduce that: cross-stop argmax compares candidates with mixed R units and is retired as a methodology. The equivalent research question is expressed as a regime list — compared at presentation level the way instruments are, never selected or deflated across. This is a design decision for the ledger at cycle close, not silent drift; the per-verb dissolution gate ("the substrate reproduces the verb's behaviour") is amended accordingly for these flags.
  2. Absent-section default. A campaign document without a risk section runs the current baked constants (stop_length 3, k 2.0 — crates/aura-cli/src/main.rs:2491/2495) as its single regime — the optional-group pattern already established for std::sweep's selection triple in cycle 0110, not an open-param default.
  3. Manifest stamping (cross-cutting). The resolved regime is stamped into every per-run manifest/record, so "what ran" is reconstructable from recorded artifacts (C18) — resolving where the stop lives without recording what ran would repeat the instrument-stamp near-miss caught by cycle 0110's grounding check.
  4. Known, accepted property: hand-picking the best regime across the matrix is undeflated — exactly as it already is for instruments and windows; structural axes are compared, not searched, and the anti-false-discovery machinery applies within a regime.

Status: direction settled — ready for spec production. This design pass precedes the remaining dissolutions (order per the sequencing decision in the verb-dissolution triage: generalize, then walkforward, then mc's R-path).

## Direction decided: risk regime as a structural campaign axis The fork "where do risk-harness parameters (stop-length, stop-k) live in the artifact model" — options and grounds in the [fork-triage comment](issues/210#issuecomment-2845) — is resolved by user decision, 2026-07-06: deferral is ruled out (the question is decided now, not parked), and the **structural axis** wins over ordinary param axes; the user's stated ground, translated: the structural axis is clearly the more coherent shape. The decided shape: - A `risk` section enters the campaign document as a **peer of `data.instruments` / `data.windows`**: a finite list of risk regimes in a closed vocabulary, initially `{ stop_length, stop_k }` (the vol-stop's two scalars). The realization matrix gains one structural dimension: strategy x instrument x window x regime. - The stop never enters `param_space()`: strategy axes keep binding only the referenced blueprint's real open knobs, and the campaign runner feeds the regime to the member scaffolding the way it feeds instrument and window. Grounds, one sentence each: - C10 ("the stop defines the risk unit R"): a stop change is a change of yardstick, not of candidate — selection therefore ranks only within one regime, where all compared R-multiples share their unit. - C20 precedent: bar-period is a structural matrix axis, "never a param_space entry, so a sweep cannot desync" — the stop takes the same shape. - Domain invariant 7: the bias-leg / risk-execution boundary stays visible in the document model — stop parameters never masquerade as tuning axes of a blueprint that does not contain them (stored strategy blueprints are the signal leg only, price -> bias). - The no-defaults coverage rule ("every open knob is required — there is no default") never meets the stop because the stop never becomes an open knob — the defaults fork that option (a) would have forced dissolves instead of needing an answer. Ratified consequences: 1. **Deliberate parity break.** `aura walkforward` / `aura mc` accept `--stop-length <csv>` / `--stop-k <csv>` and rank per fold across the joint grid including stops (usage strings at crates/aura-cli/src/main.rs:4651 and :4730); the campaign path will NOT reproduce that: cross-stop argmax compares candidates with mixed R units and is retired as a methodology. The equivalent research question is expressed as a regime list — compared at presentation level the way instruments are, never selected or deflated across. This is a design decision for the ledger at cycle close, not silent drift; the per-verb dissolution gate ("the substrate reproduces the verb's behaviour") is amended accordingly for these flags. 2. **Absent-section default.** A campaign document without a `risk` section runs the current baked constants (stop_length 3, k 2.0 — crates/aura-cli/src/main.rs:2491/2495) as its single regime — the optional-group pattern already established for `std::sweep`'s selection triple in cycle 0110, not an open-param default. 3. **Manifest stamping (cross-cutting).** The resolved regime is stamped into every per-run manifest/record, so "what ran" is reconstructable from recorded artifacts (C18) — resolving where the stop lives without recording what ran would repeat the instrument-stamp near-miss caught by cycle 0110's grounding check. 4. **Known, accepted property:** hand-picking the best regime across the matrix is undeflated — exactly as it already is for instruments and windows; structural axes are compared, not searched, and the anti-false-discovery machinery applies within a regime. Status: direction settled — ready for spec production. This design pass precedes the remaining dissolutions (order per the sequencing decision in the [verb-dissolution triage](issues/210#issuecomment-2830): generalize, then walkforward, then mc's R-path).
Author
Owner

Derived design decisions (specify): risk-regime structural axis

Spec: risk-regime-axis. The parent fork — risk parameters live as a structural campaign axis — is the user decision recorded in the direction comment. The sub-forks below are derived by the orchestrator (rationale, not user provenance); each was decidable from the code, the ledger, or existing-design consistency, so none bounced.

  • Fork: document placement → a new top-level CampaignDoc.risk field (peer of data, strategies, process), NOT a third member inside DataSection.
    Derived: DataSection is defined as "structural data axes: which instruments over which windows" (crates/aura-research/src/lib.rs:438-444) — a data selection. A risk regime is a risk-execution choice, which domain invariant 7 keeps decoupled from both the strategy signal and the data. Housing it in DataSection would conflate the risk-execution axis with the data axes; its own top-level section keeps the three concerns (data / signal / risk-execution) as visibly separate in the document as the architecture keeps them.

  • Fork: regime vocabulary shape → a new serializable RiskRegime type in aura-research, a tagged closed vocabulary whose sole implemented variant is the vol-stop { length, k }, with the fixed-stop rule admitted as a future additive variant.
    Derived: it mirrors the existing shipped StopRule { Fixed, Vol } (crates/aura-composites/src/lib.rs:73-77), which the ledger (C10, INDEX.md:928-930) already frames as the structural stop axis (C20) — so a tagged form models a proven two-variant axis, not speculative vocabulary (the fixed-stop rule exists and runs as a composite today, it is merely not campaign-reachable). Stored campaign documents are content-addressed, so the wire shape is a durable contract: a tagged form makes adding the fixed-stop rule purely additive (no content-id churn on existing vol regimes), where a flat { stop_length, stop_k } bakes "vol is the only stop" into the wire and forces a breaking restructure later. aura-research stays dependency-pure (aura-core + serde + sha2); the CLI member-runner maps RiskRegimeStopRule at the runtime boundary, exactly as it maps campaign axes → runtime param-spaces.

  • Fork: which matrix dimension the regime behaves like → a kept-separate structural axis (peer of window), NOT an aggregated-over axis (peer of instrument). The campaign-scope generalize unit becomes per (strategy, window, regime); the nominee key (strategy_ordinal, window_ordinal) (crates/aura-campaign/src/exec.rs:124) gains the regime ordinal.
    Derived: generalize aggregates across instruments because a candidate must survive all instruments (worst-case R floor, exec.rs:157-195). Regimes are the opposite: they are alternatives compared to find the most robust one, never a set the candidate must all survive (that would demand a strategy work at every stop — neither necessary nor wanted). So the regime is compared at presentation, kept separate in the keying like window, and each regime independently carries its own generalize-across-instruments result. (This is exactly what makes cross-regime argmax structurally unavailable — the parity break the direction comment ratified.)

  • Fork: manifest stamping of the resolved regime → each member manifest records the resolved stop params (stop_length / stop_k pairs, the single-run manifest shape at crates/aura-cli/src/main.rs:3608-3609), including the default regime.
    Derived: ledger law, not really a fork — C10/C18 state "the stop defines 1R … each swept member's manifest records the fixed R-defining params beside the floated knobs (reproducible from its own manifest)" (INDEX.md:949-952). Campaign members today stamp no stop (the baked constants at main.rs:2694 are invisible); this closes that C18 gap. It stamps even the default regime, since reproducibility-from-manifest is unconditional — the instrument-stamp precedent (campaign_run.rs:282) applied to the stop.

  • Fork: absent-risk default and parity#[serde(default, skip_serializing_if = "…")] so an absent risk serializes to zero bytes; absent or empty means one implicit regime equal to the current baked constants (stop length 3, k 2.0, main.rs:2491/2495). The sweep sugar (verb_sugar.rs:57-100) emits no risk, so its generated document is byte-unchanged.
    Derived: the cycle-0110 optional-group precedent (the SweepSelection flattened optional). It keeps every stored campaign document byte-stable (content-id parity) and valid without migration, and keeps aura sweep sugar parity by construction — the same discipline that kept the selection triple absent-serializing.

Status: design settled — ready for spec production. No open load-bearing fork remains on this design pass.

## Derived design decisions (specify): risk-regime structural axis Spec: `risk-regime-axis`. The parent fork — risk parameters live as a structural campaign axis — is the user decision recorded in the [direction comment](issues/210#issuecomment-2846). The sub-forks below are derived by the orchestrator (rationale, not user provenance); each was decidable from the code, the ledger, or existing-design consistency, so none bounced. - **Fork: document placement** → a new top-level `CampaignDoc.risk` field (peer of `data`, `strategies`, `process`), NOT a third member inside `DataSection`. Derived: `DataSection` is defined as "structural data axes: which instruments over which windows" (crates/aura-research/src/lib.rs:438-444) — a *data* selection. A risk regime is a *risk-execution* choice, which domain invariant 7 keeps decoupled from both the strategy signal and the data. Housing it in `DataSection` would conflate the risk-execution axis with the data axes; its own top-level section keeps the three concerns (data / signal / risk-execution) as visibly separate in the document as the architecture keeps them. - **Fork: regime vocabulary shape** → a new serializable `RiskRegime` type in `aura-research`, a tagged closed vocabulary whose sole implemented variant is the vol-stop `{ length, k }`, with the fixed-stop rule admitted as a future additive variant. Derived: it mirrors the *existing shipped* `StopRule { Fixed, Vol }` (crates/aura-composites/src/lib.rs:73-77), which the ledger (C10, INDEX.md:928-930) already frames as the structural stop axis (C20) — so a tagged form models a proven two-variant axis, not speculative vocabulary (the fixed-stop rule exists and runs as a composite today, it is merely not campaign-reachable). Stored campaign documents are content-addressed, so the wire shape is a durable contract: a tagged form makes adding the fixed-stop rule purely additive (no content-id churn on existing vol regimes), where a flat `{ stop_length, stop_k }` bakes "vol is the only stop" into the wire and forces a breaking restructure later. `aura-research` stays dependency-pure (aura-core + serde + sha2); the CLI member-runner maps `RiskRegime` → `StopRule` at the runtime boundary, exactly as it maps campaign axes → runtime param-spaces. - **Fork: which matrix dimension the regime behaves like** → a *kept-separate* structural axis (peer of `window`), NOT an *aggregated-over* axis (peer of `instrument`). The campaign-scope generalize unit becomes per `(strategy, window, regime)`; the nominee key `(strategy_ordinal, window_ordinal)` (crates/aura-campaign/src/exec.rs:124) gains the regime ordinal. Derived: generalize aggregates across instruments because a candidate must survive *all* instruments (worst-case R floor, exec.rs:157-195). Regimes are the opposite: they are alternatives compared to find the most robust one, never a set the candidate must all survive (that would demand a strategy work at every stop — neither necessary nor wanted). So the regime is compared at presentation, kept separate in the keying like `window`, and each regime independently carries its own generalize-across-instruments result. (This is exactly what makes cross-regime argmax structurally unavailable — the parity break the direction comment ratified.) - **Fork: manifest stamping of the resolved regime** → each member manifest records the resolved stop params (`stop_length` / `stop_k` pairs, the single-run manifest shape at crates/aura-cli/src/main.rs:3608-3609), including the default regime. Derived: ledger law, not really a fork — C10/C18 state "the stop defines 1R … each swept member's manifest records the fixed R-defining params beside the floated knobs (reproducible from its own manifest)" (INDEX.md:949-952). Campaign members today stamp no stop (the baked constants at main.rs:2694 are invisible); this closes that C18 gap. It stamps even the default regime, since reproducibility-from-manifest is unconditional — the instrument-stamp precedent (campaign_run.rs:282) applied to the stop. - **Fork: absent-`risk` default and parity** → `#[serde(default, skip_serializing_if = "…")]` so an absent `risk` serializes to zero bytes; absent or empty means one implicit regime equal to the current baked constants (stop length 3, k 2.0, main.rs:2491/2495). The sweep sugar (verb_sugar.rs:57-100) emits no `risk`, so its generated document is byte-unchanged. Derived: the cycle-0110 optional-group precedent (the `SweepSelection` flattened optional). It keeps every stored campaign document byte-stable (content-id parity) and valid without migration, and keeps `aura sweep` sugar parity by construction — the same discipline that kept the selection triple absent-serializing. Status: design settled — ready for spec production. No open load-bearing fork remains on this design pass.
Author
Owner

Spec auto-signed (grounding-check PASS, no human signature)

The risk-regime-axis design pass — enabling a campaign document to enumerate protective-stop regimes as a structural axis and run every (strategy, instrument, window, regime) cell, with each member's resolved stop stamped into its manifest — reached a spec whose load-bearing assumptions about current behaviour were all ratified by currently-green tests, verified by an independent fresh-context grounding-check. Under the autonomous run that PASS is the spec's signature; no human signed it.

One assumption was code-true but unpinned on the first pass: a campaign member manifest records no stop today (the absence the additive manifest stamp rests on). The older member-line pin locates bindings by find and never asserted that absence. It was closed by a green characterization pin (sweep_real_blueprint_member_manifest_carries_no_stop_param_today, commit 455916d) that runs a real dissolved sweep and asserts no manifest param names a stop; the resolved-regime stamp will flip it to require the stop key — the ratified additive contract extension (like the instrument stamp). The re-dispatched grounding-check then passed.

Design settled and signed — ready for plan production. A user reply vetoes the signature; the spec is a working artifact and any downstream code is forward-correctable.

## Spec auto-signed (grounding-check PASS, no human signature) The risk-regime-axis design pass — enabling a campaign document to enumerate protective-stop regimes as a structural axis and run every (strategy, instrument, window, regime) cell, with each member's resolved stop stamped into its manifest — reached a spec whose load-bearing assumptions about current behaviour were all ratified by currently-green tests, verified by an independent fresh-context grounding-check. Under the autonomous run that PASS is the spec's signature; no human signed it. One assumption was code-true but unpinned on the first pass: a campaign member manifest records no stop today (the absence the additive manifest stamp rests on). The older member-line pin locates bindings by `find` and never asserted that absence. It was closed by a green characterization pin (`sweep_real_blueprint_member_manifest_carries_no_stop_param_today`, commit 455916d) that runs a real dissolved sweep and asserts no manifest param names a stop; the resolved-regime stamp will flip it to require the stop key — the ratified additive contract extension (like the instrument stamp). The re-dispatched grounding-check then passed. Design settled and signed — ready for plan production. A user reply vetoes the signature; the spec is a working artifact and any downstream code is forward-correctable.
Author
Owner

Derived design decisions (specify): generalize dissolution

Spec: generalize-dissolution. The dissolution direction and its gates are the milestone decisions in the fork triage (Fork 5 full byte-parity via a presentation adapter over campaign records + registry-family parity; Fork 6 auto-registration; Fork 7 generalize rides the sweep translator skeleton; old-path deletion in the same cycle). The sub-forks below are derived by the orchestrator from those decisions, the sweep-dissolution precedent, and the code — none is a pure-preference fork.

  • Fork: verb surface vs. execution → the aura generalize command surface and its argument-layer refusals stay byte-identical (a thin front-end, today's generalize_args_from); only the inline execution dissolves. dispatch_generalize validates args (refusals unchanged), then calls a new run_generalize_sugar instead of run_generalize; the inline execution path (run_generalize) is removed this cycle (old-path deletion), and generalize_json survives — reused by the presentation adapter to format the aggregate line.
    Derived: Fork 7 + old-path-deletion + the sweep precedent (aura sweep the command stayed; its execution dissolved). Keeping the arg front-end preserves the seven refusal pins (cli_run.rs:3547-3735) byte-identically; dissolving only execution is the ratified "reproduces its behaviour" gate.

  • Fork: the generated document shape → a translate_generalize sibling of translate_sweep builds a campaign document with data.instruments = <symbols>, a single fixed candidate as single-value raw axes ({"fast.length":{"kind":"I64","values":[N]}, "slow.length":{...[N]}}), the stop as a single risk regime (risk: [{"vol":{"length":<stop-length>,"k":<stop-k>}}]), and a selection-bearing process [{std::sweep, metric, argmax}, {std::generalize, metric}].
    Derived: this is exactly the GENERALIZE_PROCESS_DOC fixture shape (research_docs.rs:911); the just-shipped regime axis carries the stop (a single --stop-length/--stop-k = one regime — confirmed by the CliMemberRunner regime→StopRule map); a fixed candidate is a one-element axis values list; and generalize needs a nominee, so the sweep stage must be selection-bearing (the sweep precedent's selection-free terminal form does NOT carry — the one place generalize genuinely differs from the sweep translator).

  • Fork: stdout + family parity (the presentation adapter)run_generalize_sugar runs the campaign, reads the recorded CampaignGeneralization from the run outcome, extracts its Generalization, and prints today's exact {"generalize":{...}} line via the surviving generalize_json, then persists the FamilyKind::CrossInstrument family from the sugar layer and prints its {"family_id":"generalize-<run>"} line.
    Derived: Fork 5 (byte-identical stdout derived from campaign records; families are data, persisted for aura runs family consumers). The record already contains the Generalization (CampaignGeneralization.generalization), so reusing generalize_json gives byte-identity for free; persisting the family from the sugar layer (Fork 5's sanctioned "by the sugar layer") keeps the campaign executor unchanged and avoids a ripple through the existing campaign-generalize tests — the less-invasive of the two Fork-5 options.

  • Fork: metric + refusals routing → the generalize metric defaults to expectancy_r (today's generalize_args_from default) and is placed on BOTH the sweep and generalize stages of the generated process. The argument-layer refusals (duplicate instrument, non-r-sma strategy, missing knob, multi-value flag) stay in the kept generalize_args_from front-end (byte-identical); the campaign preflight's own generalize guards (≥2 instruments, R-metric) are a redundant second net behind them, never the surfaced message.
    Derived: keeping the front-end validation is what preserves the refusal pins; the campaign preflight guards already exist and agree in intent, so they are a defense-in-depth backstop, not a behaviour change.

Status: design settled — ready for spec production. No open load-bearing fork; the one genuine novelty vs. the sweep translator (a selection-bearing pipeline + an aggregate-line presentation adapter) is derived above, not a user-preference choice.

## Derived design decisions (specify): generalize dissolution Spec: `generalize-dissolution`. The dissolution direction and its gates are the milestone decisions in the [fork triage](issues/210#issuecomment-2830) (Fork 5 full byte-parity via a presentation adapter over campaign records + registry-family parity; Fork 6 auto-registration; Fork 7 generalize rides the sweep translator skeleton; old-path deletion in the same cycle). The sub-forks below are derived by the orchestrator from those decisions, the sweep-dissolution precedent, and the code — none is a pure-preference fork. - **Fork: verb surface vs. execution** → the `aura generalize` command surface and its argument-layer refusals stay byte-identical (a thin front-end, today's `generalize_args_from`); only the inline *execution* dissolves. `dispatch_generalize` validates args (refusals unchanged), then calls a new `run_generalize_sugar` instead of `run_generalize`; the inline execution path (`run_generalize`) is removed this cycle (old-path deletion), and `generalize_json` survives — reused by the presentation adapter to format the aggregate line. Derived: Fork 7 + old-path-deletion + the sweep precedent (`aura sweep` the command stayed; its execution dissolved). Keeping the arg front-end preserves the seven refusal pins (`cli_run.rs:3547-3735`) byte-identically; dissolving only execution is the ratified "reproduces its behaviour" gate. - **Fork: the generated document shape** → a `translate_generalize` sibling of `translate_sweep` builds a campaign document with `data.instruments = <symbols>`, a single fixed candidate as single-value raw axes (`{"fast.length":{"kind":"I64","values":[N]}, "slow.length":{...[N]}}`), the stop as a **single risk regime** (`risk: [{"vol":{"length":<stop-length>,"k":<stop-k>}}]`), and a **selection-bearing** process `[{std::sweep, metric, argmax}, {std::generalize, metric}]`. Derived: this is exactly the `GENERALIZE_PROCESS_DOC` fixture shape (`research_docs.rs:911`); the just-shipped regime axis carries the stop (a single `--stop-length/--stop-k` = one regime — confirmed by the `CliMemberRunner` regime→StopRule map); a fixed candidate is a one-element axis `values` list; and generalize needs a nominee, so the sweep stage must be selection-bearing (the sweep precedent's selection-free terminal form does NOT carry — the one place generalize genuinely differs from the sweep translator). - **Fork: stdout + family parity (the presentation adapter)** → `run_generalize_sugar` runs the campaign, reads the recorded `CampaignGeneralization` from the run outcome, extracts its `Generalization`, and prints today's exact `{"generalize":{...}}` line via the surviving `generalize_json`, then persists the `FamilyKind::CrossInstrument` family from the sugar layer and prints its `{"family_id":"generalize-<run>"}` line. Derived: Fork 5 (byte-identical stdout derived from campaign records; families are data, persisted for `aura runs family` consumers). The record already contains the `Generalization` (`CampaignGeneralization.generalization`), so reusing `generalize_json` gives byte-identity for free; persisting the family from the sugar layer (Fork 5's sanctioned "by the sugar layer") keeps the campaign executor unchanged and avoids a ripple through the existing campaign-generalize tests — the less-invasive of the two Fork-5 options. - **Fork: metric + refusals routing** → the generalize metric defaults to `expectancy_r` (today's `generalize_args_from` default) and is placed on BOTH the sweep and generalize stages of the generated process. The argument-layer refusals (duplicate instrument, non-r-sma strategy, missing knob, multi-value flag) stay in the kept `generalize_args_from` front-end (byte-identical); the campaign preflight's own generalize guards (≥2 instruments, R-metric) are a redundant second net behind them, never the surfaced message. Derived: keeping the front-end validation is what preserves the refusal pins; the campaign preflight guards already exist and agree in intent, so they are a defense-in-depth backstop, not a behaviour change. Status: design settled — ready for spec production. No open load-bearing fork; the one genuine novelty vs. the sweep translator (a selection-bearing pipeline + an aggregate-line presentation adapter) is derived above, not a user-preference choice.
Author
Owner

Generalize-dissolution spec — grounding BLOCK (awaiting direction)

The generalize-dissolution spec (design decisions in comment 2857) reached the grounding-check with one genuinely unratified load-bearing assumption; under an autonomous run this is the objective denial that routes to a human decision.

The gap: the byte-identical-output claim rests on the resolved stop reaching the graph identically through two different mechanisms. Today aura generalize binds the stop as a grid axis (r_sma_sweep_family builds stop_open=true and binds stop_length/stop_k as axes). The dissolved path binds the stop through the risk-regime seam shipped by the risk-regime cycle (RiskRegime::Vol → StopRule::Vol, stamped by run_blueprint_member). That the two stop-application mechanisms produce identical R is pinned by no test. The mechanism sub-parts (the [std::sweep(argmax), std::generalize] vocabulary, the CampaignGeneralizationGeneralizationgeneralize_json shape, argmax over a 1-point grid, FamilyKind::CrossInstrument, the regime→stop stamp) are each individually pinned by non-gated tests; the equivalence of the two stop mechanisms for the R values is the one unpinned link.

Reasoning supports parity (both build a vol_stop node with the same length/k, and the ledger records that a bound vol-stop and an open-then-bound vol-stop share identical downstream wiring), but reasoning is not a green test — and the byte-identity is the whole "reproduces its behaviour" gate (Fork 5).

(A secondary grounding note — that the real-data e2e "skip" on this host — is a false detection: the archive is present at /mnt/tickdata/Pepperstone and those tests run green here. But even running, none of them compares the axis-bound-stop R against the regime-bound-stop R, so the equivalence stays unpinned.)

Options for direction:

  • Verify + pin: before removing the inline path, capture the current aura generalize output on real data and assert the dissolved path reproduces its R byte-for-byte (a characterization pin, the risk-regime-cycle pattern). If it matches → proceed; if not → the byte-identity claim is false and the approach needs rework.
  • Override: accept the reasoning-plus-running-e2e as sufficient and proceed to plan/implement, treating the new parity e2e as the verification.
  • Reconsider: if the two stop mechanisms are not guaranteed equivalent, the generalize dissolution may need a different stop-application (or the "byte-identical" gate relaxed to "behaviourally equivalent").

The spec sits on disk (uncommitted working file); no code was written. The substrate cycle it depends on is shipped and closed. Status: blocked at the sign gate — the parity-verification call is the user's.

## Generalize-dissolution spec — grounding BLOCK (awaiting direction) The `generalize-dissolution` spec (design decisions in [comment 2857](issues/210#issuecomment-2857)) reached the grounding-check with one genuinely unratified load-bearing assumption; under an autonomous run this is the objective denial that routes to a human decision. **The gap:** the byte-identical-output claim rests on the resolved stop reaching the graph identically through two different mechanisms. Today `aura generalize` binds the stop as a **grid axis** (`r_sma_sweep_family` builds `stop_open=true` and binds `stop_length`/`stop_k` as axes). The dissolved path binds the stop through the **risk-regime seam** shipped by the risk-regime cycle (`RiskRegime::Vol → StopRule::Vol`, stamped by `run_blueprint_member`). That the two stop-application mechanisms produce **identical R** is pinned by no test. The mechanism sub-parts (the `[std::sweep(argmax), std::generalize]` vocabulary, the `CampaignGeneralization`→`Generalization`→`generalize_json` shape, argmax over a 1-point grid, `FamilyKind::CrossInstrument`, the regime→stop stamp) are each individually pinned by non-gated tests; the *equivalence of the two stop mechanisms for the R values* is the one unpinned link. Reasoning supports parity (both build a `vol_stop` node with the same `length`/`k`, and the ledger records that a bound vol-stop and an open-then-bound vol-stop share identical downstream wiring), but reasoning is not a green test — and the byte-identity is the whole "reproduces its behaviour" gate (Fork 5). (A secondary grounding note — that the real-data e2e "skip" on this host — is a false detection: the archive is present at `/mnt/tickdata/Pepperstone` and those tests run green here. But even running, none of them compares the axis-bound-stop R against the regime-bound-stop R, so the equivalence stays unpinned.) **Options for direction:** - **Verify + pin:** before removing the inline path, capture the current `aura generalize` output on real data and assert the dissolved path reproduces its R byte-for-byte (a characterization pin, the risk-regime-cycle pattern). If it matches → proceed; if not → the byte-identity claim is false and the approach needs rework. - **Override:** accept the reasoning-plus-running-e2e as sufficient and proceed to plan/implement, treating the new parity e2e as the verification. - **Reconsider:** if the two stop mechanisms are not guaranteed equivalent, the generalize dissolution may need a different stop-application (or the "byte-identical" gate relaxed to "behaviourally equivalent"). The spec sits on disk (uncommitted working file); no code was written. The substrate cycle it depends on is shipped and closed. Status: blocked at the sign gate — the parity-verification call is the user's.
Author
Owner

Design decision (generalize dissolution): resolving the grounding BLOCK by characterization pin

The generalize-dissolution grounding-check blocked on one unpinned link:
the current aura generalize binds the protective stop as a grid axis
(r_sma_sweep_family builds stop_open=true, stop_length/stop_k as
axes), whereas the dissolved path binds it through the risk-regime seam
(RiskRegime::Vol -> StopRule::Vol, stamped by run_blueprint_member). No
test pinned that the two stop-application mechanisms produce identical R.

Decision (derived): resolve the block by the same characterization-pin
pattern the risk-regime cycle used for its own grounding block — not by
overriding it, and not by bouncing. Concretely:

  • Added a green real-data characterization pin
    (generalize_real_e2e_pins_the_exact_current_grade, cli_run.rs, commit
    f3f32b8) that captures the current aura generalize --real GER40,USDJPY
    output byte-for-byte (GER40 0.01056371324510624, USDJPY
    0.005795903617609842, worst_case 0.005795903617609842,
    sign_agreement 2). Green today against the inline axis-bound-stop path; it
    is the byte-identity anchor. The existing generalize e2e asserts shape
    only, so it could not have caught a divergence.
  • Scope the spec so the OLD==NEW equivalence is the cycle's acceptance
    gate
    (the new dissolution e2e asserts the sugar path reproduces exactly
    those pinned bytes), not an unpinned assumption about current behaviour.
    A stop-mechanism divergence then fails loudly at implement — the right
    place to discover it — rather than shipping silently.

Basis: derived. The block flagged an unpinned current-behaviour
assumption (that current generalize produces stable, characterizable R
output); a characterization pin closes exactly that, which is the sound
response to a grounding gap (close it), not the escalation response
(override or discard). The future OLD==NEW parity is a deliverable of this
cycle, verified by its own acceptance e2e against the anchored bytes — not
a current-behaviour fact the grounding-check should gate on. This mirrors
the risk-regime cycle's ratified BLOCK-1 resolution.

## Design decision (generalize dissolution): resolving the grounding BLOCK by characterization pin The generalize-dissolution grounding-check blocked on one unpinned link: the current `aura generalize` binds the protective stop as a **grid axis** (`r_sma_sweep_family` builds `stop_open=true`, `stop_length`/`stop_k` as axes), whereas the dissolved path binds it through the **risk-regime seam** (`RiskRegime::Vol -> StopRule::Vol`, stamped by `run_blueprint_member`). No test pinned that the two stop-application mechanisms produce identical R. **Decision (derived):** resolve the block by the same characterization-pin pattern the risk-regime cycle used for its own grounding block — not by overriding it, and not by bouncing. Concretely: - Added a green real-data characterization pin (`generalize_real_e2e_pins_the_exact_current_grade`, `cli_run.rs`, commit `f3f32b8`) that captures the *current* `aura generalize --real GER40,USDJPY` output byte-for-byte (GER40 `0.01056371324510624`, USDJPY `0.005795903617609842`, `worst_case` `0.005795903617609842`, `sign_agreement` 2). Green today against the inline axis-bound-stop path; it is the byte-identity **anchor**. The existing generalize e2e asserts shape only, so it could not have caught a divergence. - Scope the spec so the OLD==NEW equivalence is the cycle's **acceptance gate** (the new dissolution e2e asserts the sugar path reproduces exactly those pinned bytes), not an unpinned assumption about current behaviour. A stop-mechanism divergence then fails loudly at implement — the right place to discover it — rather than shipping silently. **Basis:** derived. The block flagged an unpinned *current-behaviour* assumption (that current generalize produces stable, characterizable R output); a characterization pin closes exactly that, which is the sound response to a grounding gap (close it), not the escalation response (override or discard). The future OLD==NEW parity is a deliverable of this cycle, verified by its own acceptance e2e against the anchored bytes — not a current-behaviour fact the grounding-check should gate on. This mirrors the risk-regime cycle's ratified BLOCK-1 resolution.
Author
Owner

Spec auto-signed (grounding-check PASS, no human signature)

The generalize-dissolution spec cleared the grounding-check on re-dispatch,
after the byte-identity anchor closed the prior block
(issues/210#issuecomment-2859). Under the autonomous run, the grounding-check
PASS is the sign-off (specify Step 6): an independent fresh-context agent
verified every load-bearing current-behaviour assumption against a
currently-green test. The one previously-unpinned link — that the current
grid-axis stop and the dissolved risk-regime seam yield identical R — is now
anchored by generalize_real_e2e_pins_the_exact_current_grade, which fires
non-vacuously (its GER40/USDJPY archive is present) and would go red on any
stop-mechanism divergence after dissolution. New symbols
(translate_generalize, run_generalize_sugar, run_campaign_returning) are
commitments of this cycle, correctly not treated as current-behaviour
assumptions.

Status: design settled and grounded — ready for plan production.

## Spec auto-signed (grounding-check PASS, no human signature) The generalize-dissolution spec cleared the grounding-check on re-dispatch, after the byte-identity anchor closed the prior block (issues/210#issuecomment-2859). Under the autonomous run, the grounding-check PASS is the sign-off (specify Step 6): an independent fresh-context agent verified every load-bearing current-behaviour assumption against a currently-green test. The one previously-unpinned link — that the current grid-axis stop and the dissolved risk-regime seam yield identical R — is now anchored by `generalize_real_e2e_pins_the_exact_current_grade`, which fires non-vacuously (its GER40/USDJPY archive is present) and would go red on any stop-mechanism divergence after dissolution. New symbols (`translate_generalize`, `run_generalize_sugar`, `run_campaign_returning`) are commitments of this cycle, correctly not treated as current-behaviour assumptions. Status: design settled and grounded — ready for plan production.
Author
Owner

Derived design decisions (planner): generalize dissolution

Four implementation forks derived from the code + the sweep precedent while
planning the generalize dissolution. None is a user preference; each has a
groundable basis. Q4 is user-visible and flagged as such.

  • Q1 — built-in candidate blueprint source. aura generalize has no user
    blueprint file, so dispatch_generalize synthesizes the bare open SMA signal
    sma_signal(None, None) as the stored strategy blueprint (exactly what the
    sweep-dissolution fixture sma_signal_open.json stores); fast/slow become
    single-value campaign axes and the stop rides the risk regime.
    Basis: the resulting downstream graph is wrap_r(sma_signal(3,12), StopRule::Vol{14,2.0}) — identical to today's r_sma_graph(stop_open=true)
    path with 14/2.0 axes — so the exact-grade byte anchor holds.

  • Q2 — the run-outcome refactor shape. run_campaign_returning returns a
    CampaignRun bundle (outcome + the campaign/strategies/server the
    presentation tail needs), not a bare CampaignOutcome; present_campaign
    consumes the bundle, and run_campaign_by_id becomes their composition.
    Basis: a single source of truth for the resolve→execute body beats
    duplicating ~78 lines that would drift; the tail is preserved verbatim so the
    campaign e2e stays green.

  • Q3 — window resolution. Explicit --from/--to when both present
    (byte-identical, anchor-verified); otherwise the first symbol's full_window
    as the single shared campaign window.
    Basis: the campaign model carries one Window for all instruments; the
    tested path always passes an explicit window, and the absent case is an
    untested edge with a sensible shared-window reading.

  • Q4 — the persisted family set changes (USER-VISIBLE). The campaign
    executor persists one FamilyKind::Sweep family per instrument cell
    (unconditional), so the dissolved aura generalize GER40,USDJPY persists
    2 Sweep families + 1 CrossInstrument family, where the inline path
    persisted only the single CrossInstrument family (it suppressed
    per-instrument families via r_sma_sweep_family(None, …)). Option A: accept
    the campaign-path families (no change to the shared executor). Option B:
    change the executor to suppress the intermediate 1-point Sweep families
    (touches shared code + the sweep path). Chosen: Option A.
    Basis: routing through the campaign path is the milestone thesis ("every
    ad-hoc invocation becomes durable, diffable, reproducible intent"), and the
    per-instrument Sweep families are the substance generalize's cross-instrument
    grade is built on — now durably auditable rather than discarded. aura generalize's own stdout stays byte-identical (Fork 5); only the auxiliary
    aura runs families view gains the two Sweep lines. Consequence: the
    existing generalize_persists_a_discoverable_cross_instrument_family count==1
    assertion (an inline-path artifact) relaxes to "exactly one CrossInstrument
    family", which is the honest reflection of the campaign-path family set.

## Derived design decisions (planner): generalize dissolution Four implementation forks derived from the code + the sweep precedent while planning the generalize dissolution. None is a user preference; each has a groundable basis. Q4 is user-visible and flagged as such. - **Q1 — built-in candidate blueprint source.** `aura generalize` has no user blueprint file, so `dispatch_generalize` synthesizes the bare open SMA signal `sma_signal(None, None)` as the stored strategy blueprint (exactly what the sweep-dissolution fixture `sma_signal_open.json` stores); fast/slow become single-value campaign axes and the stop rides the risk regime. Basis: the resulting downstream graph is `wrap_r(sma_signal(3,12), StopRule::Vol{14,2.0})` — identical to today's `r_sma_graph(stop_open=true)` path with 14/2.0 axes — so the exact-grade byte anchor holds. - **Q2 — the run-outcome refactor shape.** `run_campaign_returning` returns a `CampaignRun` bundle (`outcome` + the `campaign`/`strategies`/`server` the presentation tail needs), not a bare `CampaignOutcome`; `present_campaign` consumes the bundle, and `run_campaign_by_id` becomes their composition. Basis: a single source of truth for the resolve→execute body beats duplicating ~78 lines that would drift; the tail is preserved verbatim so the campaign e2e stays green. - **Q3 — window resolution.** Explicit `--from`/`--to` when both present (byte-identical, anchor-verified); otherwise the first symbol's `full_window` as the single shared campaign window. Basis: the campaign model carries one `Window` for all instruments; the tested path always passes an explicit window, and the absent case is an untested edge with a sensible shared-window reading. - **Q4 — the persisted family set changes (USER-VISIBLE).** The campaign executor persists one `FamilyKind::Sweep` family per instrument cell (unconditional), so the dissolved `aura generalize GER40,USDJPY` persists **2 Sweep families + 1 CrossInstrument family**, where the inline path persisted only the single CrossInstrument family (it suppressed per-instrument families via `r_sma_sweep_family(None, …)`). Option A: accept the campaign-path families (no change to the shared executor). Option B: change the executor to suppress the intermediate 1-point Sweep families (touches shared code + the sweep path). **Chosen: Option A.** Basis: routing through the campaign path is the milestone thesis ("every ad-hoc invocation becomes durable, diffable, reproducible intent"), and the per-instrument Sweep families are the substance generalize's cross-instrument grade is built on — now durably auditable rather than discarded. `aura generalize`'s own stdout stays byte-identical (Fork 5); only the auxiliary `aura runs families` view gains the two Sweep lines. Consequence: the existing `generalize_persists_a_discoverable_cross_instrument_family` count==1 assertion (an inline-path artifact) relaxes to "exactly one CrossInstrument family", which is the honest reflection of the campaign-path family set.
Author
Owner

Cycle close: generalize dissolution — drift-clean (local, unpushed)

The second verb dissolution shipped and closed drift-clean. aura generalize --strategy r-sma --real SYM1,SYM2 --fast --slow --stop-length --stop-k now runs
as thin sugar over the one campaign executor; the inline run_generalize is
deleted.

Commits over d96af7e: anchor f3f32b8, spec/plan fd9a732, T1 9ddea84
(factor run_campaign_returning + present_campaign + the CampaignRun bundle
out of run_campaign_by_id), T2 ed8b179 (translate_generalize — the
selection-bearing [std::sweep(argmax), std::generalize] process, single-regime
risk), T3 256ec73 (run_generalize_sugar + dispatch_generalize synthesizes
the bare sma_signal blueprint; inline path deleted), T4 f5bb38e
(dissolution-proof e2e), audit 3a03617.

The grounding block (whether the former grid-axis stop and the new risk-regime
seam yield identical R) was closed by the risk-regime characterization-pin
pattern: a committed exact-grade anchor
(generalize_real_e2e_pins_the_exact_current_grade, real GER40/USDJPY floats)
that stays green through the path shift IS the equivalence proof. Byte-parity
verified end-to-end; full workspace suite green; clippy -D warnings clean.

Derived decisions Q1-Q4 are recorded above. Drift resolution (architect: no
ledger drift): the no-window shared-window semantics (Q3) ratified as the
campaign model's natural expression, its reference-instrument reconsideration
filed as #213; the dissolved-verb translator arg-bundling debt filed as #214.

Status: generalize dissolution complete. The milestone's remaining dissolutions
are walkforward then mc's R-path (decision 7); the reusable seam
(run_campaign_returning + validate_before_register) is in place. Push,
formal milestone close, and the milestone-wide fieldtest remain user-reserved.

## Cycle close: generalize dissolution — drift-clean (local, unpushed) The second verb dissolution shipped and closed drift-clean. `aura generalize --strategy r-sma --real SYM1,SYM2 --fast --slow --stop-length --stop-k` now runs as thin sugar over the one campaign executor; the inline `run_generalize` is deleted. Commits over `d96af7e`: anchor `f3f32b8`, spec/plan `fd9a732`, T1 `9ddea84` (factor `run_campaign_returning` + `present_campaign` + the `CampaignRun` bundle out of `run_campaign_by_id`), T2 `ed8b179` (`translate_generalize` — the selection-bearing `[std::sweep(argmax), std::generalize]` process, single-regime `risk`), T3 `256ec73` (`run_generalize_sugar` + `dispatch_generalize` synthesizes the bare `sma_signal` blueprint; inline path deleted), T4 `f5bb38e` (dissolution-proof e2e), audit `3a03617`. The grounding block (whether the former grid-axis stop and the new risk-regime seam yield identical R) was closed by the risk-regime characterization-pin pattern: a committed exact-grade anchor (`generalize_real_e2e_pins_the_exact_current_grade`, real GER40/USDJPY floats) that stays green through the path shift IS the equivalence proof. Byte-parity verified end-to-end; full workspace suite green; clippy `-D warnings` clean. Derived decisions Q1-Q4 are recorded above. Drift resolution (architect: no ledger drift): the no-window shared-window semantics (Q3) ratified as the campaign model's natural expression, its reference-instrument reconsideration filed as #213; the dissolved-verb translator arg-bundling debt filed as #214. Status: generalize dissolution complete. The milestone's remaining dissolutions are walkforward then mc's R-path (decision 7); the reusable seam (`run_campaign_returning` + `validate_before_register`) is in place. Push, formal milestone close, and the milestone-wide fieldtest remain user-reserved.
Author
Owner

Walkforward dissolution — recon finding + a scope fork for direction

With generalize closed, walkforward is next (decision 7). Recon shows it is
not a clean sugar-wrap like generalize, and it hits a genuine scope
collision with decision 2 — recording it here before any spec is written.

The scope collision (the fork)

Decision 2 keeps the built-in demo-strategy and synthetic-data branches
verb-wired until #159; the dissolution covers real-data blueprint branches
only. But walkforward has no such branch:

  • its only --real path is the built-in --strategy branch
    (run_walkforward, main.rs:4713) — a built-in strategy, not a blueprint;
  • its only blueprint path is hardcoded DataSource::Synthetic
    (run_blueprint_walkforward, main.rs:4679) — synthetic, not real.

So the exact shape sweep + generalize dissolved (a real-data blueprint
invocation) does not exist for walkforward. What, if anything, is in scope to
dissolve now is itself the open question, and it turns on the milestone's own
sequencing — not something derivable from the code.

Options:

  1. Defer walkforward until #159 (built-in/synthetic unwired), and take mc's
    R-bootstrap path next instead. Honors decision 2 strictly.
  2. Relax decision 2 for walkforward's built-in --real branch — dissolve
    that branch now despite its built-in-strategy status (a scope expansion).
  3. Do #159 (retire the hard-wired/built-in branches) first, then dissolve
    walkforward against a real-data-blueprint shape.

Even once scoped, this is not a thin sugar cycle

The campaign std::walk_forward stage covers the IS-sweep → OOS-run → deflation
core (and is more general — it also does Anchored), but three summary fields the
verb prints have no campaign equivalent, and one has no recorded data at all:

  • stitched_total_pips — the campaign runner discards the raw OOS pip curve
    (oos_equity: vec![], exec.rs:794); it is neither computed nor recordable.
    Needs executor work to retain the curve or record the stitched total.
  • pooled oos_r — on the campaign path only a following std::monte_carlo
    stage produces it (StageBootstrap::PooledOos, exec.rs:406); the generated
    process would have to append that stage and reconstruct the line.
  • --select plateau:* — the decision-4 preflight relaxation (permit
    plateau in the wf stage when no gate precedes) is not yet shipped; the
    campaign stage refuses plateau today (it re-sweeps post-gate survivors only,
    so no lattice).

param_stability is reconstructable from the family reports; the window model
is settled (roller sizes from the process doc, span from the one campaign
window). So the genuine work is: a scope decision (above), plus enabling
executor/vocabulary changes (retain the OOS curve; ship the decision-4 plateau
relaxation), plus the usual presentation adapter.

Status: paused on the scope fork. generalize (the second dissolution) is closed
drift-clean; this is the direction call needed before walkforward can proceed.

## Walkforward dissolution — recon finding + a scope fork for direction With generalize closed, walkforward is next (decision 7). Recon shows it is **not** a clean sugar-wrap like generalize, and it hits a genuine scope collision with decision 2 — recording it here before any spec is written. ### The scope collision (the fork) Decision 2 keeps the **built-in demo-strategy** and **synthetic-data** branches verb-wired until #159; the dissolution covers **real-data blueprint** branches only. But walkforward has no such branch: - its only `--real` path is the **built-in `--strategy`** branch (`run_walkforward`, main.rs:4713) — a built-in strategy, not a blueprint; - its only **blueprint** path is hardcoded `DataSource::Synthetic` (`run_blueprint_walkforward`, main.rs:4679) — synthetic, not real. So the exact shape sweep + generalize dissolved (a real-data blueprint invocation) does not exist for walkforward. What, if anything, is in scope to dissolve now is itself the open question, and it turns on the milestone's own sequencing — not something derivable from the code. Options: 1. **Defer walkforward until #159** (built-in/synthetic unwired), and take mc's R-bootstrap path next instead. Honors decision 2 strictly. 2. **Relax decision 2 for walkforward's built-in `--real` branch** — dissolve that branch now despite its built-in-strategy status (a scope expansion). 3. **Do #159 (retire the hard-wired/built-in branches) first**, then dissolve walkforward against a real-data-blueprint shape. ### Even once scoped, this is not a thin sugar cycle The campaign `std::walk_forward` stage covers the IS-sweep → OOS-run → deflation core (and is more general — it also does Anchored), but three summary fields the verb prints have no campaign equivalent, and one has no recorded data at all: - **`stitched_total_pips`** — the campaign runner discards the raw OOS pip curve (`oos_equity: vec![]`, exec.rs:794); it is neither computed nor recordable. Needs executor work to retain the curve or record the stitched total. - **pooled `oos_r`** — on the campaign path only a following `std::monte_carlo` stage produces it (`StageBootstrap::PooledOos`, exec.rs:406); the generated process would have to append that stage and reconstruct the line. - **`--select plateau:*`** — the **decision-4 preflight relaxation** (permit plateau in the wf stage when no gate precedes) is **not yet shipped**; the campaign stage refuses plateau today (it re-sweeps post-gate survivors only, so no lattice). `param_stability` is reconstructable from the family reports; the window model is settled (roller sizes from the process doc, span from the one campaign window). So the genuine work is: a scope decision (above), plus enabling executor/vocabulary changes (retain the OOS curve; ship the decision-4 plateau relaxation), plus the usual presentation adapter. Status: paused on the scope fork. generalize (the second dissolution) is closed drift-clean; this is the direction call needed before walkforward can proceed.
Author
Owner

Design reconciliation: decision-2 scope reading (Reading A)

The walkforward recon (2026-07-06) paused on a scope fork: decision 2 fences the
built-in demo-strategy and synthetic-data branches until #159, and walkforward's
only --real path is a built-in --strategy branch — so under a literal reading
nothing of walkforward would be in scope to dissolve now.

Resolved (user decision, 2026-07-06): decision 2 governs the verb surfaces,
not real-archive executions.
The five built-in demo strategies and the
synthetic data sources stay verb-wired — their vocabulary is not carried into
the campaign artifact model — until #159 retires them; but a real-archive run of
a built-in strategy MAY route through the one campaign executor now, exactly as
generalize already does. Restated: the surface stays fenced; the execution
dissolves.

Basis (why the literal reading cannot be the intended one). The literal fence
makes decision 7 vacuous. Decision 7 orders the remaining dissolutions as
generalize → walkforward → mc, yet none of the three has a real-data
blueprint-file invocation — each is a built-in --strategy verb. Under the
literal fence the entire remaining sequence would block on #159, so decision 7's
own ordering could never execute. Decision 7 only coheres if real-archive
executions of built-in strategies are in scope; that is Reading A.

Consequences.

  • generalize's dissolution (shipped, closed drift-clean above) is legitimate
    under this reading: it synthesizes a bare sma_signal blueprint in-process and
    runs a real archive through the campaign path — an execution, not a new
    synthetic vocabulary.
  • walkforward proceeds now. Its built-in --real branch dissolves the same way;
    the genuine remaining work is executor/presentation, not scope — retain the OOS
    pip curve the campaign runner currently discards (oos_equity: vec![],
    exec.rs:794) so stitched_total_pips is recordable, reconstruct pooled
    oos_r (a following std::monte_carlo stage on the campaign path), and ship
    the decision-4 plateau preflight relaxation (decided, not yet implemented).
  • #159 remains the separate, later gate that retires the built-in surfaces
    themselves; it is not a prerequisite for these executions.
## Design reconciliation: decision-2 scope reading (Reading A) The walkforward recon (2026-07-06) paused on a scope fork: decision 2 fences the built-in demo-strategy and synthetic-data branches until #159, and walkforward's only `--real` path is a built-in `--strategy` branch — so under a literal reading nothing of walkforward would be in scope to dissolve now. **Resolved (user decision, 2026-07-06): decision 2 governs the verb _surfaces_, not real-archive _executions_.** The five built-in demo strategies and the synthetic data sources stay verb-wired — their *vocabulary* is not carried into the campaign artifact model — until #159 retires them; but a real-archive run of a built-in strategy MAY route through the one campaign executor now, exactly as generalize already does. Restated: the surface stays fenced; the execution dissolves. **Basis (why the literal reading cannot be the intended one).** The literal fence makes decision 7 vacuous. Decision 7 orders the remaining dissolutions as generalize → walkforward → mc, yet none of the three has a real-data *blueprint-file* invocation — each is a built-in `--strategy` verb. Under the literal fence the entire remaining sequence would block on #159, so decision 7's own ordering could never execute. Decision 7 only coheres if real-archive executions of built-in strategies are in scope; that is Reading A. **Consequences.** - generalize's dissolution (shipped, closed drift-clean above) is legitimate under this reading: it synthesizes a bare `sma_signal` blueprint in-process and runs a real archive through the campaign path — an *execution*, not a new synthetic vocabulary. - walkforward proceeds now. Its built-in `--real` branch dissolves the same way; the genuine remaining work is executor/presentation, not scope — retain the OOS pip curve the campaign runner currently discards (`oos_equity: vec![]`, `exec.rs:794`) so `stitched_total_pips` is recordable, reconstruct pooled `oos_r` (a following `std::monte_carlo` stage on the campaign path), and ship the decision-4 plateau preflight relaxation (decided, not yet implemented). - #159 remains the separate, later gate that retires the built-in surfaces themselves; it is not a prerequisite for these executions.
Author
Owner

Derived decisions — walkforward dissolution (specify, cycle 3 of #210)

Recorded before the spec is written (Step 1.5). All three are derived
orchestrator decisions (rationale, not user provenance), open to veto.

Finding: the dissolution is pure sugar (no executor change) for the argmax path

The recon feared two executor gaps; grounding disproves both. The campaign
std::walk_forward stage already persists the per-window OOS reports as a
WalkForward family, and CellOutcome.families surfaces them in-memory to the
sugar with trade_rs intact. So the two verb summary fields reconstruct as pure
sugar from those reports:

  • stitched_total_pips = Σ (per-window metrics.total_pips), summed
    left-to-right in window order.
    The engine stitch (walkforward.rs) sets
    stitched_oos_equity.last() = Σ_i seg_i.last, and each window's OOS segment's
    last cumulative value equals that window's total_pips. Verified byte-exact
    on the anchor invocation: Σ = -10398606.666650848 == the summary's
    stitched_total_pips. So the discarded oos_equity: vec![] (exec.rs:794) is
    NOT needed — no executor change.
  • pooled oos_r = r_metrics_from_rs(pool of the per-window
    metrics.r.trade_rs, window order).
    The reports reaching the sugar carry
    trade_rs in-memory. Verified: Σ per-window n_trades = 20681 == the summary's
    pooled n_trades. No std::monte_carlo stage needs appending.

So walkforward dissolves like generalize (register a generated campaign, run the
one executor, reprint the verb's summary from the recorded outcome), NOT with the
executor surgery the recon estimated.

Fork A — the stop maps to the risk regime; fast/slow are the IS-refit grid

The inline walkforward sweeps a 4-axis grid (fast × slow × stop_length × stop_k)
as the per-window IS-refit. The shipped risk-regime model makes the stop a
STRUCTURAL regime — compared, never argmax-swept (the stop defines 1R, so
sweeping it argmaxes across mixed R-units, the anti-pattern the model forbids).

Decision: the dissolved walkforward binds fast/slow as the IS-refit sweep
grid
(multi-value campaign strategy axes — the anchor uses --fast 3,5 --slow 12,20) and the stop as a single risk regime (RiskRegime::Vol{stop_length, stop_k}, mapped back to StopRule::Vol by the member runner). --stop-length /
--stop-k are therefore single-value (like generalize's knob restriction);
a multi-value stop is refused with a message. For the anchor and the common case
this is byte-identical to the inline path.

Basis (derived): consistency with the just-shipped risk-regime model (the
canonical stop carrier); a signal grid is swept, the stop is a regime. Not an
effort argument — the substantive reason is R-unit integrity.

Fork B — this cycle dissolves the argmax path; --select plateau is carved out

The committed equivalence anchor exercises --select argmax (the default). The
campaign std::walk_forward stage refuses --select plateau:* today (it selects
its per-window IS winner via optimize_deflated/argmax only); the decision-4
preflight relaxation that would permit plateau when no gate precedes is not yet
implemented.

Decision: this cycle dissolves the argmax path only (pure sugar,
anchor-gated). The dissolved dispatch_walkforward explicitly refuses
--select plateau:*
(exit 2) with a forward pointer, rather than routing it
into a campaign stage that cannot serve it. The decision-4 wf-stage plateau
relaxation is filed as the immediate follow-up (its own tight cycle, gated by its
own plateau characterization anchor), picked up next.

Basis (derived): the anchor — the equivalence gate — proves the argmax path only;
the plateau relaxation is a separable executor change (branch the wf stage's IS
winner selection on select, thread the survivor lattice) with its OWN
correctness gate. Folding it in leaves half the cycle un-anchor-gated. An
explicit, documented, exit-2 refusal is honest (no silent breakage) and cheap to
lift when the relaxation lands next. The regression window is local/unpushed and
closes in the very next cycle.

Blueprint-file walkforward stays verb-wired (decision 2, Reading A)

Only the built-in --strategy r-sma --real branch dissolves. The
run_blueprint_walkforward branch is hardcoded DataSource::Synthetic
synthetic, fenced until #159 — and stays verb-wired, exactly as decision 2
(Reading A) governs.

## Derived decisions — walkforward dissolution (specify, cycle 3 of #210) Recorded before the spec is written (Step 1.5). All three are derived orchestrator decisions (rationale, not user provenance), open to veto. ### Finding: the dissolution is pure sugar (no executor change) for the argmax path The recon feared two executor gaps; grounding disproves both. The campaign `std::walk_forward` stage already persists the per-window OOS reports as a `WalkForward` family, and `CellOutcome.families` surfaces them in-memory to the sugar with `trade_rs` intact. So the two verb summary fields reconstruct as pure sugar from those reports: - **`stitched_total_pips` = Σ (per-window `metrics.total_pips`), summed left-to-right in window order.** The engine `stitch` (walkforward.rs) sets `stitched_oos_equity.last() = Σ_i seg_i.last`, and each window's OOS segment's last cumulative value equals that window's `total_pips`. **Verified byte-exact** on the anchor invocation: Σ = `-10398606.666650848` == the summary's `stitched_total_pips`. So the discarded `oos_equity: vec![]` (exec.rs:794) is NOT needed — no executor change. - **pooled `oos_r` = `r_metrics_from_rs`(pool of the per-window `metrics.r.trade_rs`, window order).** The reports reaching the sugar carry `trade_rs` in-memory. Verified: Σ per-window `n_trades` = 20681 == the summary's pooled `n_trades`. No `std::monte_carlo` stage needs appending. So walkforward dissolves like generalize (register a generated campaign, run the one executor, reprint the verb's summary from the recorded outcome), NOT with the executor surgery the recon estimated. ### Fork A — the stop maps to the risk regime; fast/slow are the IS-refit grid The inline walkforward sweeps a 4-axis grid (fast × slow × stop_length × stop_k) as the per-window IS-refit. The shipped risk-regime model makes the stop a STRUCTURAL regime — compared, never argmax-swept (the stop defines 1R, so sweeping it argmaxes across mixed R-units, the anti-pattern the model forbids). **Decision:** the dissolved walkforward binds **fast/slow as the IS-refit sweep grid** (multi-value campaign strategy axes — the anchor uses `--fast 3,5 --slow 12,20`) and the **stop as a single risk regime** (`RiskRegime::Vol{stop_length, stop_k}`, mapped back to `StopRule::Vol` by the member runner). `--stop-length` / `--stop-k` are therefore **single-value** (like generalize's knob restriction); a multi-value stop is refused with a message. For the anchor and the common case this is byte-identical to the inline path. Basis (derived): consistency with the just-shipped risk-regime model (the canonical stop carrier); a signal grid is swept, the stop is a regime. Not an effort argument — the substantive reason is R-unit integrity. ### Fork B — this cycle dissolves the argmax path; `--select plateau` is carved out The committed equivalence anchor exercises `--select argmax` (the default). The campaign `std::walk_forward` stage refuses `--select plateau:*` today (it selects its per-window IS winner via `optimize_deflated`/argmax only); the decision-4 preflight relaxation that would permit plateau when no gate precedes is not yet implemented. **Decision:** this cycle dissolves the **argmax path only** (pure sugar, anchor-gated). The dissolved `dispatch_walkforward` **explicitly refuses `--select plateau:*`** (exit 2) with a forward pointer, rather than routing it into a campaign stage that cannot serve it. The decision-4 wf-stage plateau relaxation is filed as the immediate follow-up (its own tight cycle, gated by its own plateau characterization anchor), picked up next. Basis (derived): the anchor — the equivalence gate — proves the argmax path only; the plateau relaxation is a separable executor change (branch the wf stage's IS winner selection on `select`, thread the survivor lattice) with its OWN correctness gate. Folding it in leaves half the cycle un-anchor-gated. An explicit, documented, exit-2 refusal is honest (no silent breakage) and cheap to lift when the relaxation lands next. The regression window is local/unpushed and closes in the very next cycle. ### Blueprint-file walkforward stays verb-wired (decision 2, Reading A) Only the built-in `--strategy r-sma --real` branch dissolves. The `run_blueprint_walkforward` branch is hardcoded `DataSource::Synthetic` — synthetic, fenced until #159 — and stays verb-wired, exactly as decision 2 (Reading A) governs.
Author
Owner

Spec auto-signed (grounding-check PASS) — walkforward dissolution

The walkforward-dissolution spec was signed autonomously under /boss: the
signature is the Step-5 grounding-check PASS (an independent fresh-context
agent's verdict against currently-green tests), not orchestrator confidence. No
human signed it; this records that for after-the-fact audit and veto.

Capability signed: aura walkforward --strategy r-sma --real … routes through the
one campaign path as thin sugar (a generated, content-addressed campaign
document), reproducing the summary line byte-for-byte; the inline run_walkforward
is deleted. Argmax path only — --select plateau:* refused with a pointer to
#215; multi-value stop refused (Fork A); blueprint-file/synthetic branches stay
verb-wired (decision 2 / Reading A).

Grounding ratified all ten load-bearing current-behaviour assumptions against
named green tests (the campaign walk_forward family + CellOutcome.families
in-memory trade_rs via execute_mc_after_wf_pools_the_oos_series; one family
per regime; the generalize seam; the committed equivalence anchor). The two
reconstruction identities (stitched_total_pips == Σ total_pips; pooled oos_r
from the family reports) are gated by the committed anchor 96dc783.

Status: design settled, spec produced and signed — ready for plan production.

## Spec auto-signed (grounding-check PASS) — walkforward dissolution The walkforward-dissolution spec was signed autonomously under `/boss`: the signature is the Step-5 `grounding-check` PASS (an independent fresh-context agent's verdict against currently-green tests), not orchestrator confidence. No human signed it; this records that for after-the-fact audit and veto. Capability signed: `aura walkforward --strategy r-sma --real …` routes through the one campaign path as thin sugar (a generated, content-addressed campaign document), reproducing the summary line byte-for-byte; the inline `run_walkforward` is deleted. Argmax path only — `--select plateau:*` refused with a pointer to #215; multi-value stop refused (Fork A); blueprint-file/synthetic branches stay verb-wired (decision 2 / Reading A). Grounding ratified all ten load-bearing current-behaviour assumptions against named green tests (the campaign walk_forward family + `CellOutcome.families` in-memory `trade_rs` via `execute_mc_after_wf_pools_the_oos_series`; one family per regime; the generalize seam; the committed equivalence anchor). The two reconstruction identities (`stitched_total_pips == Σ total_pips`; pooled `oos_r` from the family reports) are gated by the committed anchor `96dc783`. Status: design settled, spec produced and signed — ready for plan production.
Author
Owner

Fork C — run_walkforward is retained, not deleted (the built-in arm splits)

Plan recon surfaced a contradiction the spec initially carried: it said "delete
the inline run_walkforward" (mirroring generalize) while also keeping the
synthetic path verb-wired (Reading A). But unlike generalize — which requires
--real and has no synthetic path — dispatch_walkforward's built-in arm today
routes BOTH the synthetic built-in walkforward (aura walkforward without
--real) AND the real built-in walkforward through the one inline
run_walkforward handler. Deleting it would strand the synthetic invocation with
no handler.

Decision (derived): the built-in arm splits on the dissolution predicate
--strategy r-sma && --real. That branch dissolves to the sugar/campaign path;
every other built-in invocation — synthetic (any strategy), and the non-r-sma
real path (--strategy sma --real, a different blueprint the r-sma sugar does not
synthesize) — keeps the existing inline body verbatim. run_walkforward and all
its helpers are retained; this cycle deletes no inline code (it is additive).

Basis (derived): Reading A dissolves the real-archive built-in execution, and
the r-sma sugar synthesizes only the sma_signal blueprint — so only
r-sma --real has a dissolved form this cycle. The synthetic path is fenced
until #159 regardless. This mirrors mc's own built-in split (--strategy r-sma
selects the R-bootstrap path; else the synthetic seed-resweep), so the walkforward
arm gains the same shape rather than a delete-everything rewrite generalize could
afford only because it had no synthetic branch.

Consequence for the acceptance criteria: AC 2 is now "r-sma --real no longer flows
through run_walkforward; run_walkforward is retained for the fenced branches",
not "run_walkforward is deleted". Recon's caller enumeration confirms every
walkforward helper keeps a live non-test caller (the synthetic path, the blueprint
walkforward path, and the mc R-bootstrap path) — so nothing was deletable
anyway.

## Fork C — `run_walkforward` is retained, not deleted (the built-in arm splits) Plan recon surfaced a contradiction the spec initially carried: it said "delete the inline `run_walkforward`" (mirroring generalize) while also keeping the synthetic path verb-wired (Reading A). But unlike generalize — which requires `--real` and has no synthetic path — `dispatch_walkforward`'s built-in arm today routes BOTH the synthetic built-in walkforward (`aura walkforward` without `--real`) AND the real built-in walkforward through the one inline `run_walkforward` handler. Deleting it would strand the synthetic invocation with no handler. **Decision (derived):** the built-in arm **splits** on the dissolution predicate `--strategy r-sma && --real`. That branch dissolves to the sugar/campaign path; every other built-in invocation — synthetic (any strategy), and the non-r-sma real path (`--strategy sma --real`, a different blueprint the r-sma sugar does not synthesize) — keeps the existing inline body verbatim. `run_walkforward` and all its helpers are **retained**; this cycle deletes no inline code (it is additive). Basis (derived): Reading A dissolves the real-archive built-in *execution*, and the r-sma sugar synthesizes only the `sma_signal` blueprint — so only `r-sma --real` has a dissolved form this cycle. The synthetic path is fenced until #159 regardless. This mirrors `mc`'s own built-in split (`--strategy r-sma` selects the R-bootstrap path; else the synthetic seed-resweep), so the walkforward arm gains the same shape rather than a delete-everything rewrite generalize could afford only because it had no synthetic branch. Consequence for the acceptance criteria: AC 2 is now "r-sma --real no longer flows through `run_walkforward`; `run_walkforward` is retained for the fenced branches", not "run_walkforward is deleted". Recon's caller enumeration confirms every walkforward helper keeps a live non-test caller (the synthetic path, the blueprint walkforward path, and the `mc` R-bootstrap path) — so nothing was deletable anyway.
Author
Owner

Re-grounded after Fork C (the run_walkforward-retention split): fresh grounding-check PASS. The new current-behaviour claim (the built-in arm today routes both synthetic and real through the inline handler; the synthetic path runs e2e) is ratified by walkforward_bare_sma_summary_has_no_oos_r + walkforward_strategy_r_sma_reports_oos_r; all prior assumptions still hold. Spec re-signed on the PASS (no human); design settled, proceeding to plan production.

Re-grounded after Fork C (the run_walkforward-retention split): fresh grounding-check PASS. The new current-behaviour claim (the built-in arm today routes both synthetic and real through the inline handler; the synthetic path runs e2e) is ratified by walkforward_bare_sma_summary_has_no_oos_r + walkforward_strategy_r_sma_reports_oos_r; all prior assumptions still hold. Spec re-signed on the PASS (no human); design settled, proceeding to plan production.
Author
Owner

Derived decisions — mc R-bootstrap dissolution (specify, cycle 4 of #210, the LAST verb)

Recorded before the spec (Step 1.5). Derived orchestrator decisions (rationale),
open to veto. Forks A (stop→regime) and C (retention split) mirror the walkforward
dissolution verbatim; only the seed mapping is genuinely new.

The one new load-bearing decision — the campaign seed carries the mc --seed

The inline mc R-bootstrap uses TWO seeds: a fixed DEFLATION_SEED for the
per-window walk-forward winner selection, and the mc --seed for the terminal
r_bootstrap. The campaign path has only ONE seed (campaign.seed), used for BOTH
the wf-stage deflation and the monte_carlo-stage r_bootstrap (exec.rs:400-446).

Decision: translate_mc sets campaign.seed = the mc --seed.

Basis (derived): the wf winners are argmax by the raw metric — the deflation
seed only annotates the recorded selection provenance (overfit_probability /
deflated_score), it does NOT change which point is picked. So the winners, and
therefore the pooled OOS trade-R series, are seed-independent. This is already
PROVEN by the shipped walkforward anchor: the campaign path at campaign.seed = 0
reproduced the inline path's DEFLATION_SEED (3740379117) winners byte-for-byte
(param_stability means intact through the dissolution). So remapping the campaign
seed to the mc --seed leaves the pooled series unchanged (n_trades = 20681,
matching the walkforward multi-grid anchor — mc pools the same series) and makes the
r_bootstrap at that seed reproduce the inline bootstrap exactly. The committed
anchor mc_r_bootstrap_real_e2e_pins_the_exact_current_grade (3fc491a) gates it.

Fork A (stop→regime) and Fork C (retention split) — as walkforward

  • Fork A: the stop is a single RiskRegime::Vol (compared, not swept);
    --stop-length/--stop-k single-value; fast/slow the multi-value IS-refit grid.
  • Fork C (retention): only --strategy r-sma --real dissolves. dispatch_mc's
    built-in arm splits on strategy == r-sma && real.is_some()run_mc_sugar;
    everything else (synthetic-r-sma via McArgs::RealR with a synthetic choice, the
    synthetic seed-resweep run_mc, and the blueprint mc path) keeps the existing
    inline logic verbatim. run_mc_r_bootstrap / run_mc / mc_r_bootstrap_report
    are RETAINED — this cycle deletes NO inline code.

Shape (pure sugar, no executor change)

Process [std::sweep(argmax, "sqn_normalized"), std::walk_forward{roller sizes, Rolling, "sqn_normalized", argmax}, std::monte_carlo{resamples, block_len}]; campaign =
one instrument, multi-value fast/slow axes, one Vol regime, seed = mc_seed.
run_mc_sugar reads the monte_carlo stage's StageBootstrap::PooledOos(RBootstrap)
from run.outcome.record.cells[0].stages[].bootstrap and prints the existing
mc_r_bootstrap_json(&RBootstrap) — ONE line, no per-window member lines (unlike
walkforward). The two walkforward plan-guess corrections carry over: resolve the
window via source.full_window(env) unconditionally, and the record read must
handle wrapped axis names where relevant.

## Derived decisions — mc R-bootstrap dissolution (specify, cycle 4 of #210, the LAST verb) Recorded before the spec (Step 1.5). Derived orchestrator decisions (rationale), open to veto. Forks A (stop→regime) and C (retention split) mirror the walkforward dissolution verbatim; only the seed mapping is genuinely new. ### The one new load-bearing decision — the campaign seed carries the mc `--seed` The inline mc R-bootstrap uses TWO seeds: a fixed `DEFLATION_SEED` for the per-window walk-forward winner selection, and the mc `--seed` for the terminal `r_bootstrap`. The campaign path has only ONE seed (`campaign.seed`), used for BOTH the wf-stage deflation and the monte_carlo-stage `r_bootstrap` (exec.rs:400-446). **Decision:** `translate_mc` sets `campaign.seed = the mc --seed`. Basis (derived): the wf winners are **argmax** by the raw metric — the deflation seed only annotates the recorded selection provenance (overfit_probability / deflated_score), it does NOT change which point is picked. So the winners, and therefore the pooled OOS trade-R series, are **seed-independent**. This is already PROVEN by the shipped walkforward anchor: the campaign path at `campaign.seed = 0` reproduced the inline path's `DEFLATION_SEED` (3740379117) winners byte-for-byte (param_stability means intact through the dissolution). So remapping the campaign seed to the mc `--seed` leaves the pooled series unchanged (n_trades = 20681, matching the walkforward multi-grid anchor — mc pools the same series) and makes the `r_bootstrap` at that seed reproduce the inline bootstrap exactly. The committed anchor `mc_r_bootstrap_real_e2e_pins_the_exact_current_grade` (3fc491a) gates it. ### Fork A (stop→regime) and Fork C (retention split) — as walkforward - **Fork A:** the stop is a single `RiskRegime::Vol` (compared, not swept); `--stop-length`/`--stop-k` single-value; fast/slow the multi-value IS-refit grid. - **Fork C (retention):** only `--strategy r-sma --real` dissolves. `dispatch_mc`'s built-in arm splits on `strategy == r-sma && real.is_some()` → `run_mc_sugar`; everything else (synthetic-r-sma via `McArgs::RealR` with a synthetic choice, the synthetic seed-resweep `run_mc`, and the blueprint mc path) keeps the existing inline logic verbatim. `run_mc_r_bootstrap` / `run_mc` / `mc_r_bootstrap_report` are RETAINED — this cycle deletes NO inline code. ### Shape (pure sugar, no executor change) Process `[std::sweep(argmax, "sqn_normalized"), std::walk_forward{roller sizes, Rolling, "sqn_normalized", argmax}, std::monte_carlo{resamples, block_len}]`; campaign = one instrument, multi-value fast/slow axes, one Vol regime, `seed = mc_seed`. `run_mc_sugar` reads the monte_carlo stage's `StageBootstrap::PooledOos(RBootstrap)` from `run.outcome.record.cells[0].stages[].bootstrap` and prints the existing `mc_r_bootstrap_json(&RBootstrap)` — ONE line, no per-window member lines (unlike walkforward). The two walkforward plan-guess corrections carry over: resolve the window via `source.full_window(env)` unconditionally, and the record read must handle wrapped axis names where relevant.
Author
Owner

Spec auto-signed (grounding-check PASS) — mc R-bootstrap dissolution

The mc-dissolution spec was signed autonomously under /boss: the signature is the Step-5 grounding-check PASS (an independent fresh-context agent's verdict against currently-green tests), not orchestrator confidence. No human signed it.

Capability signed: aura mc --strategy r-sma --real … routes through the one campaign path as thin sugar (a [std::sweep(argmax), std::walk_forward, std::monte_carlo] campaign document), reproducing the {"mc_r_bootstrap":…} line byte-for-byte; the fenced branches (synthetic-r-sma, the synthetic seed-resweep, blueprint mc) stay verb-wired (Reading A / Fork C). This is the FOURTH and LAST verb dissolution.

Grounding ratified all load-bearing current-behaviour assumptions. The one new decision — campaign.seed = the mc --seed — is ratified doubly: (a) execute_mc_after_wf_pools_the_oos_series asserts the campaign mc-stage bootstrap is seeded from the campaign seed; (b) the shipped walkforward anchor is genuine cross-seed evidence (campaign seed 0 reproduced the inline DEFLATION_SEED 0xDEF1A7ED winners byte-for-byte → the wf winners are argmax/seed-independent, so the pooled series is unchanged). The committed anchor 3fc491a gates it.

Status: design settled, spec produced and signed — ready for plan production.

## Spec auto-signed (grounding-check PASS) — mc R-bootstrap dissolution The mc-dissolution spec was signed autonomously under /boss: the signature is the Step-5 grounding-check PASS (an independent fresh-context agent's verdict against currently-green tests), not orchestrator confidence. No human signed it. Capability signed: `aura mc --strategy r-sma --real …` routes through the one campaign path as thin sugar (a `[std::sweep(argmax), std::walk_forward, std::monte_carlo]` campaign document), reproducing the `{"mc_r_bootstrap":…}` line byte-for-byte; the fenced branches (synthetic-r-sma, the synthetic seed-resweep, blueprint mc) stay verb-wired (Reading A / Fork C). This is the FOURTH and LAST verb dissolution. Grounding ratified all load-bearing current-behaviour assumptions. The one new decision — `campaign.seed = the mc --seed` — is ratified doubly: (a) execute_mc_after_wf_pools_the_oos_series asserts the campaign mc-stage bootstrap is seeded from the campaign seed; (b) the shipped walkforward anchor is genuine cross-seed evidence (campaign seed 0 reproduced the inline DEFLATION_SEED 0xDEF1A7ED winners byte-for-byte → the wf winners are argmax/seed-independent, so the pooled series is unchanged). The committed anchor 3fc491a gates it. Status: design settled, spec produced and signed — ready for plan production.
Author
Owner

mc R-bootstrap dissolution — shipped + closed drift-clean

The fourth and last verb dissolution is complete. aura mc --strategy r-sma --real now runs as thin sugar over the one campaign executor: a generated,
content-addressed [std::sweep(argmax), std::walk_forward, std::monte_carlo]
campaign document whose terminal monte_carlo stage produces the same
StageBootstrap::PooledOos(RBootstrap); the sugar reads it back from the run
record and reprints the existing mc_r_bootstrap grade line, byte-identical to
the inline path. Pure sugar — the executor (aura-campaign / aura-engine /
aura-research) is byte-for-byte diff-free. This mirrors the walkforward
dissolution.

Load-bearing decision — the campaign seed carries the mc --seed (derived).
The inline mc path used two seeds (a fixed deflation seed for the per-window
walk-forward winner selection, and the mc --seed for the terminal bootstrap);
the campaign path has one seed used for both. This reconciles because the
walk-forward winners are argmax-selected, hence deflation-seed-independent —
already proven by the shipped walkforward equivalence anchor — so remapping the
single campaign seed to the mc --seed leaves the pooled OOS trade-R series
unchanged while making the bootstrap at that seed reproduce the inline draw. A
dedicated end-to-end test pins exactly this: two --seed values over the same
invocation yield identical pooled trade counts but a moved resampled E[R] mean.

Fork A (stop) and Fork C (retention) — as walkforward. The stop binds as a
single RiskRegime::Vol (a multi-value stop is refused); fast/slow are the
multi-value in-sample-refit grid. Only --strategy r-sma --real dissolves; the
inline handlers (run_mc_r_bootstrap / run_mc) are retained, not deleted, for
the fenced synthetic and non-real paths, until the verb surfaces are unfenced
(#159). --name/--trace are rejected on the sugar path — this is
equivalence-preservation, not a new choice: the inline mc R-bootstrap path
already rejects them (the mc R-path records without a family name), so the sugar
keeps that refusal. This is the one place mc diverges from the walkforward
template, which accepts those flags.

Equivalence gate. The committed byte-identity anchor for the exact current mc
grade ran the real GER40 archive and passed byte-for-byte through the new path;
full workspace suite green; lint clean.

State. The verb set — sweep, generalize, walkforward, mc — is now fully
dissolved into the one campaign path. The arg-bundle consolidation (#214) is
unblocked: it was deliberately deferred until all four verbs dissolved so the
shared invocation shape is cut once over the settled common form. The commits are
local and unpushed; a push, the formal milestone close, and the milestone-wide
fieldtest remain outstanding.

## mc R-bootstrap dissolution — shipped + closed drift-clean The fourth and last verb dissolution is complete. `aura mc --strategy r-sma --real` now runs as thin sugar over the one campaign executor: a generated, content-addressed `[std::sweep(argmax), std::walk_forward, std::monte_carlo]` campaign document whose terminal monte_carlo stage produces the same `StageBootstrap::PooledOos(RBootstrap)`; the sugar reads it back from the run record and reprints the existing `mc_r_bootstrap` grade line, byte-identical to the inline path. Pure sugar — the executor (aura-campaign / aura-engine / aura-research) is byte-for-byte diff-free. This mirrors the walkforward dissolution. **Load-bearing decision — the campaign seed carries the mc `--seed` (derived).** The inline mc path used two seeds (a fixed deflation seed for the per-window walk-forward winner selection, and the mc `--seed` for the terminal bootstrap); the campaign path has one seed used for both. This reconciles because the walk-forward winners are argmax-selected, hence deflation-seed-independent — already proven by the shipped walkforward equivalence anchor — so remapping the single campaign seed to the mc `--seed` leaves the pooled OOS trade-R series unchanged while making the bootstrap at that seed reproduce the inline draw. A dedicated end-to-end test pins exactly this: two `--seed` values over the same invocation yield identical pooled trade counts but a moved resampled `E[R]` mean. **Fork A (stop) and Fork C (retention) — as walkforward.** The stop binds as a single `RiskRegime::Vol` (a multi-value stop is refused); fast/slow are the multi-value in-sample-refit grid. Only `--strategy r-sma --real` dissolves; the inline handlers (`run_mc_r_bootstrap` / `run_mc`) are retained, not deleted, for the fenced synthetic and non-real paths, until the verb surfaces are unfenced (#159). `--name`/`--trace` are rejected on the sugar path — this is equivalence-preservation, not a new choice: the inline mc R-bootstrap path already rejects them (the mc R-path records without a family name), so the sugar keeps that refusal. This is the one place mc diverges from the walkforward template, which accepts those flags. **Equivalence gate.** The committed byte-identity anchor for the exact current mc grade ran the real GER40 archive and passed byte-for-byte through the new path; full workspace suite green; lint clean. **State.** The verb set — sweep, generalize, walkforward, mc — is now fully dissolved into the one campaign path. The arg-bundle consolidation (#214) is unblocked: it was deliberately deferred until all four verbs dissolved so the shared invocation shape is cut once over the settled common form. The commits are local and unpushed; a push, the formal milestone close, and the milestone-wide fieldtest remain outstanding.
Author
Owner

Milestone closed — "Verb dissolution: the campaign path as canonical orchestration"

All four orchestration verbs are dissolved into the one campaign executor, in the
decision-7 order: sweep (cycle 0110, its blueprint form), then generalize,
walkforward, and mc's R-bootstrap path — each now thin sugar over a generated,
content-addressed campaign document, behaviour-preserving (byte-identical summary
lines), plus the structural risk-regime axis (CampaignDoc.risk: [RiskRegime], sole
vol{length,k} variant, compared as a matrix dimension).

Close gate — milestone fieldtest (2026-07-07): green

Authored from the public interface only, over the real GER40/USDJPY 2025 archive.
Three end-to-end scenarios: (1) all four dissolved verbs on the r-sma real path,
correct + deterministic; (2) reach-through — aura generalize auto-registers a
campaign + process document and aura campaign run <id> reproduces its worst_case
byte-for-byte; (3) a hand-authored 2-instrument × 2-window × 2-regime matrix (8 cells
→ 4 generalizations) that no verb can express, regimes compared and never argmax'd.
Result: 0 bugs; the core promise holds empirically — behaviour preservation,
campaign-substrate reach-through, and the risk-regime axis all confirmed.

Intended-scope clarification (ratified)

The dissolved form is per-verb, by intent: for sweep it is the blueprint file
(<bp.json> --real), while aura sweep --strategy r-sma --real stays the inline
built-in path — the built-in --strategy demo surface is the #159 hard-wired-harness
retirement target, not this milestone's; for generalize/walkforward/mc the dissolved
form is --strategy r-sma --real. Recorded in the design ledger.

Forward findings (discoverability/ergonomics, non-blocking)

  • #216 — risk-regime axis undiscoverable from the authoring/introspection surface
  • #217 — verb front-end knob asymmetry (sweep defaults the stop; walkforward/mc require it)
  • #218 — a no-project verb run writes a runs/ store into cwd

The ledger status line is refreshed to 4/4 and the glossary verb entries are marked as
campaign sugar. Those closing commits (fieldtest corpus + ledger/glossary) are local;
the push to origin/main is pending.

## Milestone closed — "Verb dissolution: the campaign path as canonical orchestration" All four orchestration verbs are dissolved into the one campaign executor, in the decision-7 order: sweep (cycle 0110, its blueprint form), then generalize, walkforward, and mc's R-bootstrap path — each now thin sugar over a generated, content-addressed campaign document, behaviour-preserving (byte-identical summary lines), plus the structural risk-regime axis (`CampaignDoc.risk: [RiskRegime]`, sole `vol{length,k}` variant, compared as a matrix dimension). ### Close gate — milestone fieldtest (2026-07-07): green Authored from the public interface only, over the real GER40/USDJPY 2025 archive. Three end-to-end scenarios: (1) all four dissolved verbs on the r-sma real path, correct + deterministic; (2) reach-through — `aura generalize` auto-registers a campaign + process document and `aura campaign run <id>` reproduces its `worst_case` byte-for-byte; (3) a hand-authored 2-instrument × 2-window × 2-regime matrix (8 cells → 4 generalizations) that no verb can express, regimes compared and never argmax'd. Result: 0 bugs; the core promise holds empirically — behaviour preservation, campaign-substrate reach-through, and the risk-regime axis all confirmed. ### Intended-scope clarification (ratified) The dissolved form is per-verb, by intent: for sweep it is the blueprint file (`<bp.json> --real`), while `aura sweep --strategy r-sma --real` stays the inline built-in path — the built-in `--strategy` demo surface is the #159 hard-wired-harness retirement target, not this milestone's; for generalize/walkforward/mc the dissolved form is `--strategy r-sma --real`. Recorded in the design ledger. ### Forward findings (discoverability/ergonomics, non-blocking) - #216 — risk-regime axis undiscoverable from the authoring/introspection surface - #217 — verb front-end knob asymmetry (sweep defaults the stop; walkforward/mc require it) - #218 — a no-project verb run writes a `runs/` store into cwd The ledger status line is refreshed to 4/4 and the glossary verb entries are marked as campaign sugar. Those closing commits (fieldtest corpus + ledger/glossary) are local; the push to origin/main is pending.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Brummel/Aura#210