Files
Aura/docs/plans/0109-persist-taps.md
T
Brummel ef7579f8fb plan: 0109 persist_taps wiring
4 tasks, strictly sequential: tap vocabulary + UnknownTap intrinsic
validation (deferral eprintln survives until task 3; the 0107 deferral
test splits into its two 0109 pins), trace_name widening + the
claim-sentinel name-composition contract across registry and executor,
persist_campaign_traces (nominee non-reduce re-run, metrics-equality
C1 guard, TraceStore member-dir writes, loud skips, deferral line
dies), gated e2e extension (trace files + chart read-back).

Drafted per-task against the live tree (3 authors); cross-task seams
verified in the notes: campaign_run.rs untouched by tasks 1-2 (task
3's fences stay valid), the task-1(b) test found by fixture not name,
tap->channel mapping verified against persist_traces_r, nominee params
bound from the report manifest via the shipped point_from_params
inverse, reduce/non-reduce metrics equality grounded in the shared
SeriesFold/GatedRecorder ledger construction.

refs #201
2026-07-04 03:07:48 +02:00

65 KiB
Raw Blame History

persist_taps Wiring — Implementation Plan (cycle 0109)

Parent spec: docs/specs/0109-persist-taps.md

For agentic workers: REQUIRED SUB-SKILL: use the implement skill to run this plan. Steps use - [ ] checkboxes for tracking.

Goal: aura campaign run honours presentation.persist_taps: closed tap vocabulary validated intrinsically, nominee-only non-reduce re-run per cell (METRICS-equality C1 guard), traces via the existing TraceStore under {campaign8}-{run}, one sparse trace_name pointer on the record.

Architecture: Task 1 lands the vocabulary + UnknownTap validation (aura-research + CLI twins; the deferral eprintln STAYS until Task 3; the old deferral test splits into its two 0109 pins). Task 2 widens the record and lands the claim-sentinel → composed-name contract across registry and executor. Task 3 ships persist_campaign_traces (the consumer wiring; the deferral line dies; the Task-1(b) seam test tightens). Task 4 extends the gated real-data e2e (trace files + chart read-back).

Tech Stack: Rust workspace — aura-research, aura-registry, aura-campaign, aura-cli.

Task-order constraints: strictly sequential 1→4. Task 3's old-fences in campaign_run.rs are valid because Tasks 1-2 do not touch that file; Task 3 finds the Task-1(b) test by its fixture (campaign_doc_json(.., (1,2), "\"equity\"", "")), not by name.


Task 1: Tap vocabulary + UnknownTap intrinsic validation (aura-research + aura-cli twins)

The tap namespace becomes a closed vocabulary (#201 decision 1, spec docs/specs/0109-persist-taps.md): aura-research gains tap_vocabulary() + DocFault::UnknownTap in validate_campaign, a new SlotKind::TapKinds for the persist_taps slot (mirroring EmitKinds), and the open-slot hint names the vocabulary. aura-cli gains the doc_fault_prose twin arm. The 0107 deferral seam test campaign_run_persist_taps_deferred_loudly splits into two pins.

Sequencing decision — state loudly: this task keeps the loud-deferral eprintln! in crates/aura-cli/src/campaign_run.rs (lines 360368) untouched. It still prints on any run with non-empty valid persist_taps. Therefore the new test (b) below asserts nothing about the deferral line, in either direction — it pins only validate-pass + the member-data-seam refusal + the absence of any trace output. Task 3 of this plan removes the eprintln! and tightens test (b) to pin its absence. Do not remove the eprintln! in this task; do not assert on "not yet honored" in test (b).

Files:

  • crates/aura-research/src/lib.rs

  • crates/aura-cli/src/research_docs.rs

  • crates/aura-cli/tests/research_docs.rs

  • Step 1: RED — aura-research unit tests (vocabulary, fault, hint label, open-slot hint)

    In crates/aura-research/src/lib.rs, inside the #[cfg(test)] mod tests module, insert four new tests immediately after the closing brace of validate_campaign_accepts_the_fixture_and_reports_each_fault. Anchor (byte-exact, the test's last lines):

    old:

          let mut emit = ok.clone();
          emit.presentation.emit.push("pie_chart".into());
          assert!(validate_campaign(&emit).contains(&DocFault::UnknownEmitKind("pie_chart".into())));
      }
    

    new:

          let mut emit = ok.clone();
          emit.presentation.emit.push("pie_chart".into());
          assert!(validate_campaign(&emit).contains(&DocFault::UnknownEmitKind("pie_chart".into())));
      }
    
      /// #201 decision 1 (cycle 0109): the tap namespace is a CLOSED vocabulary —
      /// the wrap convention's four persisted sink names. A genuinely new
      /// observable is a new vocabulary entry (or an authored blueprint sink),
      /// never an open node-path namespace in the document.
      #[test]
      fn tap_vocabulary_is_the_wrap_conventions_four_sink_names() {
          assert_eq!(tap_vocabulary(), ["equity", "exposure", "r_equity", "net_r_equity"]);
      }
    
      /// Each `presentation.persist_taps` entry outside `tap_vocabulary()` is an
      /// intrinsic fault carrying its index (for path-addressed prose downstream)
      /// and the offending name — mirroring the UnknownEmitKind loop. The
      /// fixture's own "net_r_equity" tap is in-vocabulary, so the clean-fixture
      /// assert in `validate_campaign_accepts_the_fixture_and_reports_each_fault`
      /// stays green unchanged.
      #[test]
      fn validate_campaign_refuses_unknown_taps_by_index() {
          let ok = parse_campaign(CAMPAIGN_FIXTURE).unwrap();
          let mut tap = ok.clone();
          tap.presentation.persist_taps.push("bias".into());
          assert_eq!(
              validate_campaign(&tap),
              vec![DocFault::UnknownTap { index: 1, tap: "bias".into() }]
          );
          // every in-vocabulary name passes
          let mut all = ok.clone();
          all.presentation.persist_taps = tap_vocabulary().iter().map(|t| t.to_string()).collect();
          assert_eq!(validate_campaign(&all), Vec::new());
      }
    
      /// The `std::presentation` persist_taps slot advertises the closed tap
      /// vocabulary through the describe path (`describe_block` +
      /// `slot_kind_label`); the label is cross-pinned against `tap_vocabulary()`
      /// so the static label and the validator cannot drift apart.
      #[test]
      fn persist_taps_slot_advertises_the_tap_vocabulary() {
          let block = describe_block("std::presentation").expect("presentation describable");
          let slot = block
              .slots
              .iter()
              .find(|s| s.name == "persist_taps")
              .expect("presentation has a persist_taps slot");
          assert_eq!(slot.kind, SlotKind::TapKinds);
          assert_eq!(
              slot_kind_label(slot.kind),
              format!("list of: {}", tap_vocabulary().join(" | "))
          );
          assert_eq!(
              slot_kind_label(slot.kind),
              "list of: equity | exposure | r_equity | net_r_equity"
          );
      }
    
      /// A draft missing its `presentation` section is pointed at the section
      /// AND the closed tap vocabulary (the guide never advertises an open
      /// namespace the validator would refuse).
      #[test]
      fn presentation_open_slot_hint_names_the_tap_vocabulary() {
          let draft = r#"{ "format_version": 1, "kind": "campaign", "name": "draft",
              "data": { "instruments": ["GER40"], "windows": [ { "from_ms": 1, "to_ms": 2 } ] },
              "strategies": [ { "ref": { "content_id": "9f3a" },
                                "axes": { "slow": { "kind": "I64", "values": [10] } } } ],
              "process": { "ref": { "content_id": "4e2d" } },
              "seed": 1 }"#;
          let slots = open_slots_campaign(draft).unwrap();
          assert_eq!(
              slots,
              vec![open(
                  "presentation",
                  "required section: persist_taps (equity | exposure | r_equity | net_r_equity) + emit",
              )]
          );
      }
    

    Run: cargo test -p aura-research Expected: compilation failure of the test target — errors naming tap_vocabulary (E0425, cannot find function), UnknownTap (E0599, no variant on DocFault), and TapKinds (E0599, no variant on SlotKind). This compile-fail IS the RED evidence: tests of a not-yet-existing API cannot fail at runtime. Do not "fix" this here; Step 3 turns it green.

  • Step 2: RED — split the 0107 deferral seam test into the two 0109 pins (aura-cli)

    In crates/aura-cli/tests/research_docs.rs, replace the entire campaign_run_persist_taps_deferred_loudly test (doc comment included) with the two new tests. The old deferral-prose assert dies here.

    old:

/// Non-empty persist_taps defers LOUDLY, and the note's ORDER is pinned: it /// prints before member execution, so it is asserted here on a run /// that refuses at the member-data seam. The [1, 2] epoch-ms window (1970) /// makes that refusal deterministic on every machine: a data-less host /// refuses on missing geometry, a data-ful host on a window no archive file /// overlaps — exit 1 either way, tap note already on stderr. #[test] fn campaign_run_persist_taps_deferred_loudly() { let _fixture = project_lock(); let dir = built_project(); let runs_dir = dir.join("runs"); std::fs::remove_dir_all(&runs_dir).ok(); let _cleanup = ScratchGuard(vec![ ScratchPath::Dir(runs_dir.clone()), ScratchPath::File(dir.join("taps.process.json")), ScratchPath::File(dir.join("taps.campaign.json")), ]); let bp_id = seed_blueprint(dir, "campaign-run-taps-seed"); let proc_id = register_process_doc(dir, "taps.process.json", SWEEP_ONLY_PROCESS_DOC); write_doc( dir, "taps.campaign.json", &campaign_doc_json(&bp_id, &proc_id, (1, 2), ""r_record"", ""), ); let (out, code) = run_code_in(dir, &["campaign", "run", "taps.campaign.json"]); assert_eq!(code, Some(1), "stdout/stderr: {out}"); assert!( out.contains("aura: persist_taps not yet honored (1 tap(s) ignored)"), "the tap note must precede the member-data refusal: {out}" ); assert!( out.contains("no recorded geometry") || out.contains("no data for instrument"), "the refusal names the data condition: {out}" ); }

new:
```rust
/// 0109 split (a) — the tap namespace is CLOSED: the 0107 deferral fixture's
/// made-up "r_record" tap now refuses INTRINSICALLY, exit 1, path-addressed
/// and vocabulary-enumerating. The old test drove `campaign run` on a file
/// target, so this pin drives the same path: the refusal fires at the run's
/// parse-valid gate (`parse_valid_campaign` — the same `validate_campaign` +
/// `doc_fault_prose` composition `campaign validate` shares), before
/// registration, before the referential tier, before any member runs — and
/// therefore before the (Task-3-doomed) loud-deferral note, whose absence is
/// pinned here.
#[test]
fn campaign_run_refuses_unknown_tap_at_validate() {
  let _fixture = project_lock();
  let dir = built_project();
  let runs_dir = dir.join("runs");
  std::fs::remove_dir_all(&runs_dir).ok();
  let _cleanup = ScratchGuard(vec![
      ScratchPath::Dir(runs_dir.clone()),
      ScratchPath::File(dir.join("badtap.process.json")),
      ScratchPath::File(dir.join("badtap.campaign.json")),
  ]);
  let bp_id = seed_blueprint(dir, "campaign-run-badtap-seed");
  let proc_id = register_process_doc(dir, "badtap.process.json", SWEEP_ONLY_PROCESS_DOC);
  write_doc(
      dir,
      "badtap.campaign.json",
      &campaign_doc_json(&bp_id, &proc_id, (1, 2), "\"r_record\"", ""),
  );
  let (out, code) = run_code_in(dir, &["campaign", "run", "badtap.campaign.json"]);
  assert_eq!(code, Some(1), "stdout/stderr: {out}");
  assert!(out.contains("campaign document invalid:"), "stdout/stderr: {out}");
  assert!(
      out.contains(
          "presentation.persist_taps[0]: unknown tap \"r_record\" \
           (taps: equity | exposure | r_equity | net_r_equity)"
      ),
      "the refusal is path-addressed and enumerates the vocabulary: {out}"
  );
  assert!(
      !out.contains("not yet honored"),
      "an invalid document must never reach the deferral note: {out}"
  );
  assert!(!out.contains("UnknownTap"), "Debug leak: {out}");
}

/// 0109 split (b) — the control: an in-vocabulary tap ("equity") sails
/// through the new intrinsic gate (no UnknownTap prose) and the run proceeds
/// to refuse at the member-data seam, exactly as a tap-less run over the
/// [1, 2] 1970 epoch-ms window does (data-less host: missing geometry;
/// data-ful host: no archive file overlaps) — exit 1 either way, and no
/// trace output (tracing never starts on a run that dies before members;
/// there is no "traces persisted" summary line). NOTE: the 0107
/// loud-deferral eprintln still fires in this task's tree and is
/// deliberately NOT asserted either way here — Task 3 deletes it and
/// tightens this test to pin its absence.
#[test]
fn campaign_run_valid_tap_reaches_the_member_data_seam() {
  let _fixture = project_lock();
  let dir = built_project();
  let runs_dir = dir.join("runs");
  std::fs::remove_dir_all(&runs_dir).ok();
  let _cleanup = ScratchGuard(vec![
      ScratchPath::Dir(runs_dir.clone()),
      ScratchPath::File(dir.join("taps.process.json")),
      ScratchPath::File(dir.join("taps.campaign.json")),
  ]);
  let bp_id = seed_blueprint(dir, "campaign-run-taps-seed");
  let proc_id = register_process_doc(dir, "taps.process.json", SWEEP_ONLY_PROCESS_DOC);
  write_doc(
      dir,
      "taps.campaign.json",
      &campaign_doc_json(&bp_id, &proc_id, (1, 2), "\"equity\"", ""),
  );
  let (out, code) = run_code_in(dir, &["campaign", "run", "taps.campaign.json"]);
  assert_eq!(code, Some(1), "stdout/stderr: {out}");
  assert!(
      !out.contains("unknown tap"),
      "\"equity\" is in the closed vocabulary and must pass validate: {out}"
  );
  assert!(
      out.contains("no recorded geometry") || out.contains("no data for instrument"),
      "the refusal names the data condition: {out}"
  );
  assert!(
      !out.contains("traces persisted"),
      "no trace summary on a run that never reached a member: {out}"
  );
}

Run: cargo test -p aura-cli --test research_docs campaign_run_refuses_unknown_tap_at_validate Expected: compiles, 1 test FAILS (RED). Today "r_record" passes validation, so the run refuses at the member-data seam instead — the "campaign document invalid:" and unknown-tap-prose asserts fail, and the deferral line is present (failing the !contains("not yet honored") assert).

Run: cargo test -p aura-cli --test research_docs campaign_run_valid_tap_reaches_the_member_data_seam Expected: 1 test PASSES already. This is deliberate and not a defect: test (b) is the control half of the split — it guards the gate Step 3 introduces against over-rejection ("equity" must keep reaching the data seam once unknown taps refuse). Its RED counterpart is test (a).

  • Step 3: GREEN — aura-research: vocabulary, fault, validation loop, TapKinds, hint

    Seven edits in crates/aura-research/src/lib.rs.

    (1) Add tap_vocabulary directly below emit_vocabulary:

    old:

/// The closed v1 emit vocabulary (data-level presentation outputs). pub fn emit_vocabulary() -> &'static [&'static str] { &["family_table", "selection_report"] }

new:
```rust
/// The closed v1 emit vocabulary (data-level presentation outputs).
pub fn emit_vocabulary() -> &'static [&'static str] {
  &["family_table", "selection_report"]
}

/// The wrap convention's persisted sink names — the closed tap vocabulary
/// (#201 decision 1). A genuinely new observable becomes a new entry or an
/// authored sink in the strategy blueprint (C22: the choice of sinks is part
/// of the experiment) — never an open node-path namespace in the document.
pub fn tap_vocabulary() -> &'static [&'static str] {
  &["equity", "exposure", "r_equity", "net_r_equity"]
}

(2) Add the DocFault variant (campaign side, after UnknownEmitKind):

old:

  EmptyAxes { strategy: usize },
  EmptyAxis { strategy: usize, axis: String },
  UnknownEmitKind(String),
  ProcessRefMustBeContentId,
}

new:

  EmptyAxes { strategy: usize },
  EmptyAxis { strategy: usize, axis: String },
  UnknownEmitKind(String),
  UnknownTap { index: usize, tap: String },
  ProcessRefMustBeContentId,
}

(3) Add the taps loop in validate_campaign, after the emit loop:

old:

  for e in &doc.presentation.emit {
      if !emit_vocabulary().contains(&e.as_str()) {
          faults.push(DocFault::UnknownEmitKind(e.clone()));
      }
  }
  faults
}

new:

  for e in &doc.presentation.emit {
      if !emit_vocabulary().contains(&e.as_str()) {
          faults.push(DocFault::UnknownEmitKind(e.clone()));
      }
  }
  for (i, t) in doc.presentation.persist_taps.iter().enumerate() {
      if !tap_vocabulary().contains(&t.as_str()) {
          faults.push(DocFault::UnknownTap { index: i, tap: t.clone() });
      }
  }
  faults
}

(4) Add the SlotKind variant (mirror EmitKinds):

old:

  Axes,
  EmitKinds,
}

new:

  Axes,
  EmitKinds,
  /// The closed tap vocabulary (`tap_vocabulary()` entries).
  TapKinds,
}

(5) Retype the persist_taps slot in CAMPAIGN_SECTIONS (std::presentation):

old:

          SlotInfo { name: "persist_taps", kind: SlotKind::Strings, required: true },

new:

          SlotInfo { name: "persist_taps", kind: SlotKind::TapKinds, required: true },

(6) Add the slot_kind_label arm:

old:

      SlotKind::EmitKinds => "list of: family_table | selection_report",
  }
}

new:

      SlotKind::EmitKinds => "list of: family_table | selection_report",
      SlotKind::TapKinds => "list of: equity | exposure | r_equity | net_r_equity",
  }
}

(7) Name the vocabulary in the open_slots_campaign presentation hint:

old:

  if v.get("presentation").is_none() {
      slots.push(open("presentation", "required section: persist_taps + emit"));
  }

new:

  if v.get("presentation").is_none() {
      slots.push(open(
          "presentation",
          "required section: persist_taps (equity | exposure | r_equity | net_r_equity) + emit",
      ));
  }

Run: cargo test -p aura-research Expected: compiles, all tests pass — including the four Step-1 tests (tap_vocabulary_is_the_wrap_conventions_four_sink_names, validate_campaign_refuses_unknown_taps_by_index, persist_taps_slot_advertises_the_tap_vocabulary, presentation_open_slot_hint_names_the_tap_vocabulary) and the untouched validate_campaign_accepts_the_fixture_and_reports_each_fault (the fixture's "net_r_equity" tap is in-vocabulary). NOTE: at this point cargo build --workspace FAILS with E0004 (non-exhaustive match): doc_fault_prose in crates/aura-cli/src/research_docs.rs does not yet cover UnknownTap. That is expected mid-task; Step 4 restores the workspace build. (doc_fault_prose is the only exhaustive DocFault consumer in the workspace — aura-campaign mentions DocFault only in a comment.)

  • Step 4: GREEN — aura-cli prose twin (doc_fault_prose arm + unit test)

    Three edits in crates/aura-cli/src/research_docs.rs.

    (1) Import tap_vocabulary:

    old:

use aura_research::{ campaign_to_json, campaign_vocabulary, describe_block, open_slots_campaign, open_slots_process, parse_campaign, parse_process, process_to_json, process_vocabulary, slot_kind_label, validate_campaign, validate_process, CampaignDoc, DocError, DocFault, DocKind, ProcessDoc, StageBlock, };

new:
```rust
use aura_research::{
  campaign_to_json, campaign_vocabulary, describe_block, open_slots_campaign,
  open_slots_process, parse_campaign, parse_process, process_to_json, process_vocabulary,
  slot_kind_label, tap_vocabulary, validate_campaign, validate_process, CampaignDoc, DocError,
  DocFault, DocKind, ProcessDoc, StageBlock,
};

(2) Add the prose arm (vocabulary composed from tap_vocabulary() — one source, no third copy of the four names):

old:

      DocFault::UnknownEmitKind(kind) => format!("presentation.emit: unknown kind \"{kind}\""),
      DocFault::ProcessRefMustBeContentId => {
          "process.ref: a process is referenced by content id in this version".into()
      }

new:

      DocFault::UnknownEmitKind(kind) => format!("presentation.emit: unknown kind \"{kind}\""),
      DocFault::UnknownTap { index, tap } => format!(
          "presentation.persist_taps[{index}]: unknown tap \"{tap}\" (taps: {})",
          tap_vocabulary().join(" | ")
      ),
      DocFault::ProcessRefMustBeContentId => {
          "process.ref: a process is referenced by content id in this version".into()
      }

(3) Pin the prose in the file's mod tests, after the last existing test:

old:

  #[test]
  fn zero_walk_forward_length_prose_is_path_addressed_and_debug_free() {
      let prose = doc_fault_prose(&DocFault::ZeroWalkForwardLength { stage: 2, field: "step_ms" });
      assert_eq!(prose, "pipeline[2]: walk_forward step_ms must be > 0");
      assert!(!prose.contains("ZeroWalkForwardLength"), "Debug leak: {prose}");
  }
}

new:

  #[test]
  fn zero_walk_forward_length_prose_is_path_addressed_and_debug_free() {
      let prose = doc_fault_prose(&DocFault::ZeroWalkForwardLength { stage: 2, field: "step_ms" });
      assert_eq!(prose, "pipeline[2]: walk_forward step_ms must be > 0");
      assert!(!prose.contains("ZeroWalkForwardLength"), "Debug leak: {prose}");
  }

  #[test]
  /// #201 decision 1 (spec 0109): the unknown-tap refusal is path-addressed
  /// (`presentation.persist_taps[i]`) and enumerates the closed vocabulary
  /// inline — four names are small enough to print, so no introspection
  /// pointer is needed (contrast the 17-name metric roster, which points at
  /// `aura process introspect --metrics` instead).
  fn unknown_tap_prose_is_path_addressed_and_enumerates_the_vocabulary() {
      let prose = doc_fault_prose(&DocFault::UnknownTap { index: 0, tap: "bias".into() });
      assert_eq!(
          prose,
          "presentation.persist_taps[0]: unknown tap \"bias\" \
           (taps: equity | exposure | r_equity | net_r_equity)"
      );
      assert!(!prose.contains("UnknownTap"), "Debug leak: {prose}");
  }
}

Run: cargo test -p aura-cli unknown_tap_prose Expected: compiles, unknown_tap_prose_is_path_addressed_and_enumerates_the_vocabulary passes (1 passed; the filter matches only this test).

  • Step 5: Gates — both crates green, workspace builds

    Run: cargo test -p aura-research Expected: green, 0 failures.

    Run: cargo test -p aura-cli --test research_docs campaign_run Expected: green, 0 failures — campaign_run_refuses_unknown_tap_at_validate now passes (the run refuses at the parse-valid gate with the enumerating prose, no deferral note, no Debug leak), campaign_run_valid_tap_reaches_the_member_data_seam still passes (the control), and every sibling campaign_run_* test is unchanged (campaign_run_real_e2e_sweep_gate_walkforward may self-skip on a data-less machine — that skip is green, not a failure).

    Run: cargo build --workspace Expected: 0 errors (the Step-3 E0004 window is closed by the Step-4 arm).

Task 2: trace_name widening + name composition + eligibility stamp (aura-registry + aura-campaign)

Context (cycle 0109, spec docs/specs/0109-persist-taps.md, #201 decision 5 + the spec's seam note): CampaignRunRecord gains a sparse trace_name: Option<String> — the TraceStore family name a campaign realization claims when its document requests persist_taps. The contract: the STORED campaign_runs.jsonl line AND the record execute RETURNS both carry the same Some("{campaign8}-{run}") iff campaign.presentation.persist_taps is non-empty, else None. Because the per-campaign run counter is assigned inside Registry::append_campaign_run, the seam is split: aura-campaign::execute stamps an eligibility claim sentinel (Some(String::new())) on the record it hands to the store; append_campaign_run replaces any Some with the derived name on the stored line (None stays None); execute then mirrors the same derivation onto the returned copy after record.run = run. The field is serde-default + skip-when-None (the C14/C23 widening convention), so pre-0109 stored lines keep parsing and re-serialized dumps of None-records stay byte-identical.

Files:

  • crates/aura-registry/src/lib.rs (tests: RED composition pin, pre-widening extension, literal threading)

  • crates/aura-registry/src/lineage.rs (the field + the append-side composition)

  • crates/aura-registry/tests/campaign_run_store_e2e.rs (literal threading)

  • crates/aura-campaign/tests/execute.rs (RED stamp pin, None extension)

  • crates/aura-campaign/src/exec.rs (the claim sentinel + the returned-copy mirror)

  • Step 1: RED — registry pins for the name composition and the pre-0109 parse.

    In crates/aura-registry/src/lib.rs, at the very end of the test module, extend the existing pre-widening test and append the new composition test. Replace this exact existing code (the file's final lines — the pre-widening test plus the module-closing brace):

        /// A pre-0108 stored line — no `bootstrap`, no `generalizations` — still
        /// parses (the C14/C23 serde-default widening convention): existing
        /// `campaign_runs.jsonl` stores survive the record widening unchanged.
        #[test]
        fn campaign_run_line_without_new_fields_still_parses() {
            let path = temp_family_dir("campaign_run_pre_widening");
            let store = path.with_file_name("campaign_runs.jsonl");
            let line = r#"{"campaign":"cafe","process":"beef","run":0,"seed":7,"cells":[{"strategy":"3f9c","instrument":"EURUSD","window_ms":[0,1000],"stages":[{"block":"std::gate","survivor_ordinals":[0,2]}]}]}"#;
            fs::write(&store, format!("{line}\n")).expect("write pre-widening line");
            let reg = Registry::open(&path);
            let loaded = reg.load_campaign_runs().expect("pre-widening line parses");
            assert_eq!(loaded.len(), 1);
            assert_eq!(loaded[0].cells[0].stages[0].bootstrap, None);
            assert!(loaded[0].generalizations.is_empty());
        }
    }
    

    with:

        /// A pre-0109 stored line — no `bootstrap`, no `generalizations`, no
        /// `trace_name` — still parses (the C14/C23 serde-default widening
        /// convention): existing `campaign_runs.jsonl` stores survive the record
        /// widening unchanged.
        #[test]
        fn campaign_run_line_without_new_fields_still_parses() {
            let path = temp_family_dir("campaign_run_pre_widening");
            let store = path.with_file_name("campaign_runs.jsonl");
            let line = r#"{"campaign":"cafe","process":"beef","run":0,"seed":7,"cells":[{"strategy":"3f9c","instrument":"EURUSD","window_ms":[0,1000],"stages":[{"block":"std::gate","survivor_ordinals":[0,2]}]}]}"#;
            fs::write(&store, format!("{line}\n")).expect("write pre-widening line");
            let reg = Registry::open(&path);
            let loaded = reg.load_campaign_runs().expect("pre-widening line parses");
            assert_eq!(loaded.len(), 1);
            assert_eq!(loaded[0].cells[0].stages[0].bootstrap, None);
            assert!(loaded[0].generalizations.is_empty());
            assert_eq!(
                loaded[0].trace_name, None,
                "pre-0109 line: an absent trace_name parses to None"
            );
        }
    
        /// The 0109 name composition (#201 d5 / the spec's seam note): a `Some`
        /// trace_name on the input record — the executor's claim sentinel,
        /// content ignored — is replaced on the STORED line with the derived
        /// `"{campaign8}-{run}"` (prefix from the record's own `campaign`, run
        /// from the store's per-campaign counter); `None` stays `None`. The
        /// executor guarantees a 64-hex campaign id, but the registry seam must
        /// not panic on a shorter one — the safe-prefix case is pinned here.
        #[test]
        fn append_campaign_run_composes_trace_name_from_a_claim() {
            let path = temp_family_dir("campaign_run_trace_name");
            let reg = Registry::open(&path);
            let long = "aaaabbbbccccddddeeeeffff0000111122223333444455556666777788889999";
    
            // claim sentinel (empty string) -> derived name, per-campaign counter
            let mut claimed = campaign_run_record(long);
            claimed.trace_name = Some(String::new());
            assert_eq!(reg.append_campaign_run(&claimed).expect("append claim 0"), 0);
            assert_eq!(reg.append_campaign_run(&claimed).expect("append claim 1"), 1);
    
            // no claim -> None stays None
            assert_eq!(reg.append_campaign_run(&campaign_run_record(long)).expect("append plain"), 2);
    
            // shorter-than-8-chars campaign id: composes from the whole id, no panic
            let mut short = campaign_run_record("abc");
            short.trace_name = Some("sentinel content is ignored".to_string());
            assert_eq!(reg.append_campaign_run(&short).expect("append short-id claim"), 0);
    
            let stored = reg.load_campaign_runs().expect("load");
            assert_eq!(
                stored.iter().map(|r| r.trace_name.as_deref()).collect::<Vec<_>>(),
                vec![Some("aaaabbbb-0"), Some("aaaabbbb-1"), None, Some("abc-0")],
                "a claim composes {{campaign8}}-{{run}} on the stored line; None stays None",
            );
        }
    }
    

    (campaign_run_record is the existing helper at the top of the campaign-run test block; temp_family_dir and fs are already in scope in this module.)

    Run: cargo test -p aura-registry append_campaign_run_composes_trace_name Expected outcome: compilation fails with error[E0609]: no field \trace_name` on type ... CampaignRunRecord` (several occurrences); no tests run. This is the RED evidence — the widening does not exist yet.

  • Step 2: GREEN — the field, the append-side composition, and literal threading.

    Four files. All edits are old->new byte-exact replacements.

    (a) crates/aura-registry/src/lineage.rs — the field. The new field goes after generalizations (declaration order = serde order for the Some case). Replace:

        /// Campaign-scope generalize annotations (cycle 0108), one per
        /// (strategy, window) across instruments. Absent pre-0108 — the
        /// serde-default widening keeps existing stored lines parseable.
        #[serde(default, skip_serializing_if = "Vec::is_empty")]
        pub generalizations: Vec<CampaignGeneralization>,
    }
    

    with:

        /// Campaign-scope generalize annotations (cycle 0108), one per
        /// (strategy, window) across instruments. Absent pre-0108 — the
        /// serde-default widening keeps existing stored lines parseable.
        #[serde(default, skip_serializing_if = "Vec::is_empty")]
        pub generalizations: Vec<CampaignGeneralization>,
        /// The TraceStore family name this realization claims when the document
        /// requests persist_taps — a pure derivation ("{campaign8}-{run}"): the
        /// executor hands a claim sentinel (`Some`, any content) and
        /// [`Registry::append_campaign_run`] composes the derived name onto the
        /// stored line; the consumer persists the bytes (#201 d5). `None` when
        /// the document requests no taps. Absent pre-0109 — the serde-default
        /// widening keeps existing stored lines parseable.
        #[serde(default, skip_serializing_if = "Option::is_none")]
        pub trace_name: Option<String>,
    }
    

    (b) crates/aura-registry/src/lineage.rs — the composition in append_campaign_run. Replace:

            let mut file = fs::OpenOptions::new().create(true).append(true).open(&path)?;
            let stored = CampaignRunRecord { run, ..record.clone() };
            let line = serde_json::to_string(&stored).expect("a finite CampaignRunRecord serializes");
    

    with:

            let mut file = fs::OpenOptions::new().create(true).append(true).open(&path)?;
            // A `Some` trace_name (any content — the executor's claim sentinel,
            // #201 d5) is replaced with the derived name "{campaign8}-{run}";
            // `None` stays `None`. The executor guarantees a 64-hex campaign id,
            // but the registry seam must not panic on a shorter one (in-crate
            // tests use short ids), so the prefix take is the safe `get(..8)`.
            let trace_name = record.trace_name.as_ref().map(|_| {
                let prefix = record.campaign.get(..8).unwrap_or(record.campaign.as_str());
                format!("{prefix}-{run}")
            });
            let stored = CampaignRunRecord { run, trace_name, ..record.clone() };
            let line = serde_json::to_string(&stored).expect("a finite CampaignRunRecord serializes");
    

    And extend the method's doc comment. Replace:

        /// run. The input record's own `run` field is ignored. Reads the store once
        /// to pick the run index (a read-before-write; single-process CLI
        /// invocations do not race).
    

    with:

        /// run. The input record's own `run` field is ignored; a `Some`
        /// `trace_name` (the executor's claim sentinel, cycle 0109) is replaced
        /// with the derived `"{campaign8}-{run}"` on the stored line, `None`
        /// stays `None`. Reads the store once to pick the run index (a
        /// read-before-write; single-process CLI invocations do not race).
    

    (c) crates/aura-registry/src/lib.rs — thread trace_name: None through the three test literals.

    Site 1 (the campaign_run_record helper). Replace:

                run: 99,
                seed: 7,
                cells: vec![],
                generalizations: vec![],
            }
    

    with:

                run: 99,
                seed: 7,
                cells: vec![],
                generalizations: vec![],
                trace_name: None,
            }
    

    Site 2 (campaign_run_record_roundtrips). Replace:

                    ],
                }],
                generalizations: vec![],
            };
    

    with:

                    ],
                }],
                generalizations: vec![],
                trace_name: None,
            };
    

    Site 3 (campaign_run_record_roundtrips_with_annotator_fields, where generalizations is a non-empty vec). Replace:

                    missing: vec!["US500".to_string()],
                }],
            };
    

    with:

                    missing: vec!["US500".to_string()],
                }],
                trace_name: None,
            };
    

    (d) crates/aura-registry/tests/campaign_run_store_e2e.rs — the campaign_run helper literal. Replace:

            }],
            generalizations: vec![],
        }
    }
    

    with:

            }],
            generalizations: vec![],
            trace_name: None,
        }
    }
    

    Run: cargo test -p aura-registry Expected outcome: green — every target compiles and all tests pass (0 failed), including the new append_campaign_run_composes_trace_name_from_a_claim and the extended campaign_run_line_without_new_fields_still_parses. The existing whole-record PartialEq round-trip asserts stay green: their literals now carry trace_name: None, no claim is made, and the stored line keeps None.

  • Step 3: RED — campaign pin for the eligibility stamp + returned/stored agreement.

    In crates/aura-campaign/tests/execute.rs, two edits.

    (a) Pin the None side on the existing sweep-only test (this assert passes already — the campaign() helper builds persist_taps: vec![]; it pins that empty taps NEVER grow a name). In execute_sweep_only_records_family_and_selection, replace:

        assert_eq!(runs[0].run, 0);
        assert_eq!(runs[0].seed, 7);
        assert_eq!(runs[0].cells.len(), 1);
    

    with:

        assert_eq!(runs[0].run, 0);
        assert_eq!(runs[0].seed, 7);
        assert_eq!(
            runs[0].trace_name, None,
            "empty persist_taps claims no trace name (0109 contract)"
        );
        assert_eq!(runs[0].cells.len(), 1);
    

    (b) Append the new test at the end of the file. Replace the file's final lines:

        // the record round-trips through the store with BOTH annotations intact
        let runs = reg.load_campaign_runs().expect("load campaign runs");
        assert_eq!(runs[0], out.record);
    }
    

    with:

        // the record round-trips through the store with BOTH annotations intact
        let runs = reg.load_campaign_runs().expect("load campaign runs");
        assert_eq!(runs[0], out.record);
    }
    
    /// The 0109 name contract (spec seam note / #201 d5): `execute` claims a
    /// TraceStore family name iff the document requests persist_taps — the
    /// RETURNED record and the STORED line both carry the same derived
    /// `Some("{campaign8}-{run}")` (`append_campaign_run` composes it onto the
    /// stored line from the claim sentinel; `execute` mirrors the same
    /// derivation onto the returned copy after the counter is assigned). The
    /// empty-taps `None` side is pinned on
    /// `execute_sweep_only_records_family_and_selection`.
    #[test]
    fn execute_persist_taps_stamps_trace_name() {
        let reg = temp_registry("persist_taps_trace_name");
        let mut doc = campaign(&["EURUSD"]);
        doc.presentation.persist_taps = vec!["equity".to_string()];
        let proc_doc = process(vec![sweep_stage(false)]);
        let out = execute(CAMPAIGN_ID, &doc, &proc_doc, &strategies(), &FakeRunner::clean(), &reg)
            .expect("persist_taps campaign executes");
    
        // returned record: the derived name over the first run counter
        let expected = format!("{}-0", &CAMPAIGN_ID[..8]);
        assert_eq!(
            out.record.trace_name.as_deref(),
            Some(expected.as_str()),
            "the returned record carries the derived trace name",
        );
    
        // the stored line agrees — in the field and as a whole record
        let runs = reg.load_campaign_runs().expect("load campaign runs");
        assert_eq!(runs.len(), 1);
        assert_eq!(runs[0].trace_name.as_deref(), Some(expected.as_str()));
        assert_eq!(runs[0], out.record, "stored line and returned record are the same record");
    
        // a second run of the same campaign derives the next counter's name
        let second = execute(CAMPAIGN_ID, &doc, &proc_doc, &strategies(), &FakeRunner::clean(), &reg)
            .expect("second persist_taps run");
        let expected_1 = format!("{}-1", &CAMPAIGN_ID[..8]);
        assert_eq!(second.record.trace_name.as_deref(), Some(expected_1.as_str()));
    }
    

    (CAMPAIGN_ID is the file's existing 64-hex constant; its first 8 chars are aaaabbbb. Presentation's fields are pub, so the doc.presentation.persist_taps mutation compiles.)

    Run: cargo test -p aura-campaign execute_persist_taps_stamps_trace_name Expected outcome: compiles, and the one matched test FAILS (0 passed; 1 failed) on the first assert — left None, right Some("aaaabbbb-0") — because execute does not yet set the claim sentinel. This is the RED evidence.

  • Step 4: GREEN — the claim sentinel + the returned-copy mirror in execute.

    In crates/aura-campaign/src/exec.rs, replace the record finalization at the end of execute (note: campaign_prefix is already in scope — let campaign_prefix = &campaign_id[..8]; earlier in the same function):

        let mut record = CampaignRunRecord {
            campaign: campaign_id.to_string(),
            process: process_id,
            run: 0,
            seed: campaign.seed,
            cells: cells_rec,
            generalizations,
        };
        let run = registry.append_campaign_run(&record).map_err(ExecFault::Registry)?;
        record.run = run;
        Ok(CampaignOutcome { record, run, cells: cells_out })
    

    with:

        let mut record = CampaignRunRecord {
            campaign: campaign_id.to_string(),
            process: process_id,
            run: 0,
            seed: campaign.seed,
            cells: cells_rec,
            generalizations,
            // The claim sentinel (#201 d5): a `Some` (any content) tells
            // `append_campaign_run` to compose the derived trace name onto the
            // stored line; `None` means the document requests no taps.
            trace_name: (!campaign.presentation.persist_taps.is_empty()).then(String::new),
        };
        let run = registry.append_campaign_run(&record).map_err(ExecFault::Registry)?;
        record.run = run;
        // Mirror the store-side derivation onto the returned copy (the 0109
        // contract: stored line and returned record carry the same
        // `Some("{campaign8}-{run}")` iff persist_taps is non-empty, else None).
        record.trace_name = (!campaign.presentation.persist_taps.is_empty())
            .then(|| format!("{campaign_prefix}-{run}"));
        Ok(CampaignOutcome { record, run, cells: cells_out })
    

    Run: cargo test -p aura-campaign Expected outcome: green, 0 failed across all targets — execute_persist_taps_stamps_trace_name now passes, and every existing whole-record PartialEq assert (execute_sweep_only_records_family_and_selection, execute_is_deterministic_twice, execute_mc_after_gate_bootstraps_each_survivor, execute_generalize_across_two_instruments, execute_mc_and_generalize_compose_in_one_pipeline) stays green: those campaigns have empty persist_taps, so both the stored and returned records carry trace_name: None.

  • Step 5: Workspace gates — build + the aura-cli byte-identity pin.

    No code changes. Run, in order:

    1. cargo build --workspace Expected outcome: builds cleanly (no other crate constructs a CampaignRunRecord literal — the only sites are the ones threaded in Steps 2 and 4).

    2. cargo test -p aura-cli --test research_docs campaign_runs_lists Expected outcome: 1 passed; 0 failed — the matched test is the hand-seeded byte-identity pin campaign_runs_lists_and_dumps_the_bare_stored_record. It stays green without modification: its seeded store line omits trace_name, which parses to None via the serde default, and the dump path RE-serializes the parsed record (serde_json::to_string in aura-cli/src/research_docs.rs), where skip_serializing_if = "Option::is_none" omits the field again — so the dump remains byte-identical to the seeded line.

Task 3: persist_campaign_traces — the consumer wiring (aura-cli)

Files: crates/aura-cli/src/campaign_run.rs, crates/aura-cli/tests/research_docs.rs

  • Step 1: RED — pin the deferral line's retirement on the Task-1(b) seam test. In crates/aura-cli/tests/research_docs.rs, locate the seam test Task 1 created as split (b) of the old campaign_run_persist_taps_deferred_loudly: the #[test] fn whose fixture writes a campaign via campaign_doc_json(&bp_id, &proc_id, (1, 2), "\"equity\"", "") (a VALID tap over the deterministic-refusal 1970 window) and asserts exit 1 with the member-data refusal (no recorded geometry / no data for instrument).

    1. If any assertion in that test mentions persist_taps not yet honored (tolerating or requiring the 0107 deferral line), delete that whole assert! statement.
    2. Append, as the LAST two statements of the function body (immediately before its closing }):
        assert!(
            !out.contains("persist_taps not yet honored"),
            "the 0107 deferral line is retired; persist_taps is honored from 0109 on: {out}"
        );
        assert!(
            !out.contains("traces persisted:"),
            "a member-data refusal aborts inside execute, before any tracing: {out}"
        );
    

    Run: cargo test -p aura-cli --test research_docs campaign_run Expected: exactly ONE failure — the tightened seam test fails on the new !out.contains("persist_taps not yet honored") assertion (the deferral eprintln! still fires in campaign_run.rs); every other campaign_run* test passes or prints its data-less skip note. This is the RED pin for this task.

  • Step 2: GREEN — wire trace persistence into crates/aura-cli/src/campaign_run.rs. Four edits, all in this file.

    (a) Imports. Replace this exact block (top of file):

    use std::path::{Path, PathBuf};
    use std::sync::Arc;
    
    use aura_campaign::{CellSpec, ExecFault, MemberFault, MemberRunner};
    use aura_core::{Cell, ParamSpec, Scalar};
    use aura_engine::{blueprint_from_json, FamilySelection, RunReport};
    use aura_ingest::{instrument_geometry, unix_ms_to_epoch_ns, M1Field, M1FieldSource};
    use aura_registry::CampaignRunRecord;
    use aura_research::{
        campaign_to_json, content_id_of, parse_campaign, parse_process, validate_campaign,
        validate_process, DocRef,
    };
    

    with:

    use std::path::{Path, PathBuf};
    use std::sync::{mpsc, Arc};
    
    use aura_campaign::{CampaignOutcome, CellSpec, ExecFault, MemberFault, MemberRunner};
    use aura_core::{Cell, ParamSpec, Scalar, ScalarKind, Timestamp};
    use aura_engine::{
        blueprint_from_json, f64_field, summarize, summarize_r, ColumnarTrace, FamilySelection,
        RunReport,
    };
    use aura_ingest::{instrument_geometry, unix_ms_to_epoch_ns, M1Field, M1FieldSource};
    use aura_registry::{CampaignRunRecord, WriteKind};
    use aura_research::{
        campaign_to_json, content_id_of, parse_campaign, parse_process, validate_campaign,
        validate_process, CampaignDoc, DocRef,
    };
    

    (The test module's own use aura_core::ScalarKind; stays as is — an explicit import shadows the use super::* glob without conflict.)

    (b) Remove the deferral block. Replace this exact block (inside run_campaign, just before the runner construction):

        // Loud deferral (#198 decision 6), once per run — pinned ORDER: this
        // prints after the document gates and BEFORE any member executes, so a
        // data refusal inside execute cannot swallow it.
        if !campaign.presentation.persist_taps.is_empty() {
            eprintln!(
                "aura: persist_taps not yet honored ({} tap(s) ignored)",
                campaign.presentation.persist_taps.len()
            );
        }
    
        let runner = CliMemberRunner {
    

    with:

        let runner = CliMemberRunner {
    

    (c) Call site + the new items. The persist call lands AFTER the always-on final campaign_run stdout line — decided: stdout stays data-pure (the record line is the wire contract), the trace surface is a stderr concern, and the realization record is already stored by execute, so a trace failure exits 1 without un-recording it. Replace this exact block (the tail of run_campaign, including its closing brace):

        println!(
            "{}",
            serde_json::to_string(&CampaignRunLine { campaign_run: &outcome.record })
                .expect("campaign run record serializes")
        );
        Ok(())
    }
    

    with (the #[cfg(test)] module that follows stays untouched below the new items):

        println!(
            "{}",
            serde_json::to_string(&CampaignRunLine { campaign_run: &outcome.record })
                .expect("campaign run record serializes")
        );
    
        // Trace persistence runs LAST, after the always-on record line (0109):
        // stdout stays data-pure (the record line is the wire contract) and the
        // trace surface is a stderr concern; the realization record is already
        // stored by `execute`, so a trace failure exits 1 without un-recording it.
        if let Some(trace_name) = &outcome.record.trace_name {
            persist_campaign_traces(
                trace_name,
                &campaign.presentation.persist_taps,
                &outcome,
                &campaign,
                &strategies,
                &runner.server,
                env,
            )?;
        }
        Ok(())
    }
    
    /// Which drained wrap-convention channel a requested tap persists from on a
    /// campaign trace re-run. The routing mirrors `persist_traces_r` (main.rs):
    /// equity <- the SimBroker equity recorder (tx_eq), exposure <- the Bias
    /// recorder (tx_ex), r_equity <- the cum_realized_r + unrealized_r recorder
    /// (tx_req). `net_r_equity` has no channel here: the campaign runner wires
    /// no cost leg (`wrap_r(.., cost: None)`), so a net curve is not produced
    /// by this run's configuration.
    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
    enum TapChannel {
        Equity,
        Exposure,
        REquity,
    }
    
    /// Route one requested tap of the closed vocabulary to its drained channel;
    /// `None` marks a tap this run cannot produce (the caller skips it loudly).
    /// An unknown name cannot reach here (`validate_campaign` refuses it) and
    /// maps to `None` all the same rather than guessing.
    fn tap_channel(tap: &str) -> Option<TapChannel> {
        match tap {
            "equity" => Some(TapChannel::Equity),
            "exposure" => Some(TapChannel::Exposure),
            "r_equity" => Some(TapChannel::REquity),
            _ => None,
        }
    }
    
    /// The content-derived on-disk key of one campaign cell under
    /// `traces/<trace_name>/` — the `member_key` discipline (content, never a
    /// runtime ordinal): strategy content-id prefix + instrument +
    /// doc-positional window ordinal, sanitized to one portable path component.
    fn campaign_cell_key(strategy: &str, instrument: &str, window_ordinal: usize) -> String {
        let strategy8 = strategy.get(..8).unwrap_or(strategy);
        crate::sanitize_component(&format!("{strategy8}-{instrument}-w{window_ordinal}"))
    }
    
    /// Persist the requested `persist_taps` for every nominee cell under
    /// `traces/<trace_name>/<cell_key>/` (0109, #201 d5): re-run each nominee
    /// once in non-reduce trace mode over its own recorded `manifest.window`,
    /// assert the re-run METRICS equal the recorded nominee metrics (the C1
    /// drift alarm — manifest fields are fresh-context and not compared), and
    /// write the requested-AND-producible taps through the sweep verbs'
    /// member-dir mechanism: a slash-joined `"{name}/{key}"` handed to
    /// `TraceStore::write` (the `persist_traces_r(&format!("{name}/{key}"), ..)`
    /// layout — the fn itself is not reused: it writes a fixed tap set and
    /// process-exits on error). The written index manifest is the RECORDED
    /// nominee's — the trace documents that run's provenance, not a re-derived
    /// fresh-context one. Loud stderr per no-nominee cell and ONCE per
    /// unproducible requested tap (producibility is run-configuration-level,
    /// not per-cell); one summary line at the end. Every `Err` is a refusal
    /// `campaign_cmd` renders as `aura: {msg}` + exit 1.
    fn persist_campaign_traces(
        trace_name: &str,
        taps: &[String],
        outcome: &CampaignOutcome,
        campaign: &CampaignDoc,
        strategies: &[(String, String)],
        server: &Arc<data_server::DataServer>,
        env: &Env,
    ) -> Result<(), String> {
        let store = env.trace_store();
        store
            .ensure_name_free(trace_name, WriteKind::Family)
            .map_err(|e| e.to_string())?;
    
        // Requested ∩ producible, in request order. When nothing producible was
        // requested (net_r_equity only), no member dir is written and the
        // summary honestly reports 0 tap(s).
        let mut routed: Vec<(&str, TapChannel)> = Vec::new();
        for tap in taps {
            match tap_channel(tap) {
                Some(ch) => routed.push((tap.as_str(), ch)),
                None => eprintln!(
                    "aura: tap \"{tap}\" is not produced by this run (needs a cost run); skipped"
                ),
            }
        }
    
        // `outcome.cells` is index-aligned with `outcome.record.cells` by
        // construction: `execute` pushes both in the same cell-loop iteration.
        let mut persisted_cells = 0usize;
        for (cell_rec, cell_out) in outcome.record.cells.iter().zip(&outcome.cells) {
            let Some((_, nominee_report)) = &cell_out.nominee else {
                eprintln!(
                    "aura: cell {}/{}/[{}, {}]: no nominee; no traces persisted",
                    cell_rec.strategy, cell_rec.instrument, cell_rec.window_ms.0, cell_rec.window_ms.1
                );
                continue;
            };
            if routed.is_empty() {
                continue;
            }
    
            // The cell key is doc-derived: the window ordinal is the POSITION
            // of the cell's window in campaign.data.windows (mirroring the
            // family-name convention), never a loop counter re-derived here.
            let window_ordinal = campaign
                .data
                .windows
                .iter()
                .position(|w| (w.from_ms, w.to_ms) == cell_rec.window_ms)
                .ok_or_else(|| {
                    format!(
                        "cell window [{}, {}] is not one of campaign.data.windows",
                        cell_rec.window_ms.0, cell_rec.window_ms.1
                    )
                })?;
            let cell_key =
                campaign_cell_key(&cell_rec.strategy, &cell_rec.instrument, window_ordinal);
    
            // The cell's blueprint: the run's own index-aligned resolution, by
            // content id (exactly the bytes the executor's members ran).
            let (_, blueprint_json) = strategies
                .iter()
                .find(|(id, _)| *id == cell_rec.strategy)
                .ok_or_else(|| {
                    format!(
                        "cell strategy {} is not among the run's resolved strategies",
                        cell_rec.strategy
                    )
                })?;
    
            // Re-run the nominee, non-reduce: the SAME member the executor ran
            // (same wrapped space, same params, same window, seed-free real
            // data), mirroring `CliMemberRunner::run_member` with the reduce
            // fold off so the per-cycle tap streams exist. The window is the
            // nominee report's own `manifest.window` — already epoch-ns
            // (`run_blueprint_member` stamped the post-seam bounds), so no
            // second ms->ns crossing here.
            let space = crate::blueprint_axis_probe(blueprint_json, env).param_space();
            let point = crate::point_from_params(&space, &nominee_report.manifest.params);
            if instrument_geometry(server, &cell_rec.instrument).is_none() {
                return Err(format!(
                    "no recorded geometry for symbol '{}' at {} — refusing to run a \
                     real instrument with a guessed pip",
                    cell_rec.instrument,
                    env.data_path()
                ));
            }
            let (from, to) = nominee_report.manifest.window;
            let no_data = || {
                format!(
                    "no data for instrument {} in the nominee window [{}, {}] (epoch-ns)",
                    cell_rec.instrument, from.0, to.0
                )
            };
            let source = M1FieldSource::open_window(
                server,
                &cell_rec.instrument,
                Some(from),
                Some(to),
                M1Field::Close,
            )
            .ok_or_else(no_data)?;
            if aura_engine::Source::peek(&source).is_none() {
                return Err(no_data());
            }
            let signal = blueprint_from_json(blueprint_json, &|t| env.resolve(t))
                .expect("stored blueprint passed the referential gate; reload is infallible");
            let (tx_eq, rx_eq) = mpsc::channel();
            let (tx_ex, rx_ex) = mpsc::channel();
            let (tx_r, rx_r) = mpsc::channel();
            let (tx_req, rx_req) = mpsc::channel();
            let mut h = crate::wrap_r(signal, tx_eq, tx_ex, tx_r, tx_req, false, false, None)
                .bootstrap_with_cells(&point)
                .expect("the nominee's point re-bootstraps (it already ran this realization)");
            let sources: Vec<Box<dyn aura_engine::Source>> = vec![Box::new(source)];
            h.run(sources);
            // Drain ALL FOUR channels (`run_signal_r` leaves req undrained; the
            // trace path must not): eq/ex/req feed the taps, r feeds the metrics.
            let eq_rows: Vec<(Timestamp, Vec<Scalar>)> = rx_eq.try_iter().collect();
            let ex_rows: Vec<(Timestamp, Vec<Scalar>)> = rx_ex.try_iter().collect();
            let r_rows: Vec<(Timestamp, Vec<Scalar>)> = rx_r.try_iter().collect();
            let req_rows: Vec<(Timestamp, Vec<Scalar>)> = rx_req.try_iter().collect();
    
            // The C1 drift alarm: metrics equality against the recorded
            // nominee. The reduce-mode fold shares its arithmetic with this
            // non-reduce reduction (SeriesFold via `summarize`; GatedRecorder
            // emits exactly the rows `summarize_r`'s ledger reads), so equality
            // is bit-exact.
            let mut rerun_metrics = summarize(&f64_field(&eq_rows, 0), &f64_field(&ex_rows, 0));
            rerun_metrics.r = Some(summarize_r(&r_rows, &[]));
            if rerun_metrics != nominee_report.metrics {
                return Err(format!(
                    "trace re-run diverged from the recorded nominee (C1 violation): cell \
                     {cell_key} of trace {trace_name} does not reproduce its recorded \
                     metrics; refusing to persist a silently-wrong trace"
                ));
            }
    
            let traces: Vec<ColumnarTrace> = routed
                .iter()
                .map(|&(tap, ch)| {
                    let rows: &[(Timestamp, Vec<Scalar>)] = match ch {
                        TapChannel::Equity => &eq_rows,
                        TapChannel::Exposure => &ex_rows,
                        TapChannel::REquity => &req_rows,
                    };
                    ColumnarTrace::from_rows(tap, &[ScalarKind::F64], rows)
                })
                .collect();
            store
                .write(&format!("{trace_name}/{cell_key}"), &nominee_report.manifest, &traces)
                .map_err(|e| e.to_string())?;
            persisted_cells += 1;
        }
    
        eprintln!(
            "aura: traces persisted: {trace_name} ({} tap(s) x {persisted_cells} cell(s))",
            routed.len()
        );
        Ok(())
    }
    

    (d) Hermetic unit tests. In the same file's #[cfg(test)] mod tests, replace this exact block (the last test fn plus the module's closing brace):

            assert!(
                m.contains("open param \"threshold\"") && m.contains("(wrapped path: sma.threshold)"),
                "raw-namespace prose expected: {m}"
            );
        }
    }
    

    with:

            assert!(
                m.contains("open param \"threshold\"") && m.contains("(wrapped path: sma.threshold)"),
                "raw-namespace prose expected: {m}"
            );
        }
    
        #[test]
        /// 0109: the campaign cell key is content-derived (the `member_key`
        /// discipline — never a runtime ordinal): strategy content-id prefix +
        /// instrument + doc-positional window ordinal, one portable component.
        fn campaign_cell_key_is_content_derived() {
            let strategy = "bb34aa55".repeat(8); // a 64-hex content id
            assert_eq!(campaign_cell_key(&strategy, "GER40", 0), "bb34aa55-GER40-w0");
        }
    
        #[test]
        /// A non-portable instrument byte maps to `_` (the shared
        /// `sanitize_component` charset) — one path component, never a nested
        /// path or an invalid directory name.
        fn campaign_cell_key_sanitizes_non_portable_bytes() {
            let strategy = "0123abcd".repeat(8);
            assert_eq!(campaign_cell_key(&strategy, "GER/40", 3), "0123abcd-GER_40-w3");
        }
    
        #[test]
        /// The tap->channel routing mirrors `persist_traces_r` (equity<-eq,
        /// exposure<-ex, r_equity<-req); `net_r_equity` is unproducible on the
        /// campaign runner's cost-free wrap and routes to no channel.
        fn tap_channel_routes_the_producible_vocabulary_only() {
            assert_eq!(tap_channel("equity"), Some(TapChannel::Equity));
            assert_eq!(tap_channel("exposure"), Some(TapChannel::Exposure));
            assert_eq!(tap_channel("r_equity"), Some(TapChannel::REquity));
            assert_eq!(tap_channel("net_r_equity"), None);
        }
    }
    

    Run: cargo build --workspace Expected: clean build, no warnings from the touched crate.

  • Step 3: gates. Run: cargo test -p aura-cli --bin aura campaign_cell_key Expected: 2 passed (campaign_cell_key_is_content_derived, campaign_cell_key_sanitizes_non_portable_bytes), 0 failed. Run: cargo test -p aura-cli --bin aura tap_channel Expected: 1 passed (tap_channel_routes_the_producible_vocabulary_only), 0 failed. Run: cargo test -p aura-cli --test research_docs campaign_run Expected: ALL campaign_run* tests pass (the Step-1 seam test is now GREEN: the deferral line is gone and the member-data refusal aborts inside execute before any tracing; on a data-less host the two real-data e2e tests print their skip note and pass).

Task 4: gated e2e extension

Files: crates/aura-cli/tests/research_docs.rs

  • Step 1: extend campaign_run_real_e2e_sweep_gate_walkforward to pin the full trace path. Three edits, all in crates/aura-cli/tests/research_docs.rs.

    (a) Doc comment. Replace this exact block:

    /// Gated real-data e2e (the `cli_run.rs` skip idiom): a full
    /// sweep -> gate -> walk_forward -> monte_carlo campaign over GER40
    /// Sept-2024 where the local archive is present — exit 0, emit-gated
    /// family/selection lines, a final line parseable as JSON with top-level
    /// `campaign_run` linking a sweep family id, a walk-forward family id, and a
    /// pooled-OOS bootstrap on the terminal mc stage, and the
    /// `campaign_runs.jsonl` sibling store written. Skips with a note elsewhere
    /// so `cargo test --workspace` stays green on a data-less machine.
    

    with:

    /// Gated real-data e2e (the `cli_run.rs` skip idiom): a full
    /// sweep -> gate -> walk_forward -> monte_carlo campaign over GER40
    /// Sept-2024 where the local archive is present — exit 0, emit-gated
    /// family/selection lines, a final line parseable as JSON with top-level
    /// `campaign_run` linking a sweep family id, a walk-forward family id, and a
    /// pooled-OOS bootstrap on the terminal mc stage, and the
    /// `campaign_runs.jsonl` sibling store written. Since 0109 the campaign
    /// also requests `persist_taps: ["equity", "r_equity"]`: the record carries
    /// `trace_name`, the nominee's taps land under `traces/<name>/<cell_key>/`
    /// (the persist re-run equality-asserted against the recorded nominee —
    /// C1), and `aura chart <name>/<cell_key>` reads them back through the
    /// shipped viewer. Skips with a note elsewhere so `cargo test --workspace`
    /// stays green on a data-less machine.
    

    (b) The persist_taps splice. Replace this exact block (inside the test):

        write_doc(
            dir,
            "wf.campaign.json",
            &campaign_doc_json(
                &bp_id,
                &proc_id,
                (1725148800000, 1727740799999),
                "",
                "\"family_table\", \"selection_report\"",
            ),
        );
    

    with:

        write_doc(
            dir,
            "wf.campaign.json",
            &campaign_doc_json(
                &bp_id,
                &proc_id,
                (1725148800000, 1727740799999),
                "\"equity\", \"r_equity\"",
                "\"family_table\", \"selection_report\"",
            ),
        );
    

    (c) The trace assertions. Replace this exact block (the tail of the test, including its closing brace):

        // The registry's new sibling store carries the realization record.
        assert!(
            runs_dir.join("campaign_runs.jsonl").is_file(),
            "campaign_runs.jsonl written beside runs.jsonl"
        );
    }
    

    with:

        // The registry's new sibling store carries the realization record.
        assert!(
            runs_dir.join("campaign_runs.jsonl").is_file(),
            "campaign_runs.jsonl written beside runs.jsonl"
        );
        // 0109: the record claims the trace family it persisted under —
        // "{campaign8}-{run}" (fresh store => run 0).
        let trace_name = v["campaign_run"]["trace_name"]
            .as_str()
            .expect("persist_taps non-empty => the record carries trace_name");
        assert!(
            trace_name.len() == 10 && trace_name.ends_with("-0"),
            "trace_name is {{campaign8}}-{{run}}: {trace_name}"
        );
        assert!(
            trace_name[..8].chars().all(|c| c.is_ascii_hexdigit()),
            "trace_name prefix is the campaign id's first 8 hex chars: {trace_name}"
        );
        // The summary stderr line names the tap x cell arity (2 requested taps,
        // one (strategy, instrument, window) cell).
        assert!(
            out.contains(&format!("aura: traces persisted: {trace_name} (2 tap(s) x 1 cell(s))")),
            "the persist summary line: {out}"
        );
        // The taps land under traces/<name>/<cell_key>/ — the cell key is
        // content-derived: strategy8-instrument-w<window_ordinal>.
        let cell_key = format!("{}-GER40-w0", &bp_id[..8]);
        let cell_dir = runs_dir.join("traces").join(trace_name).join(&cell_key);
        assert!(
            cell_dir.join("equity.json").is_file(),
            "equity tap persisted at {}",
            cell_dir.display()
        );
        assert!(
            cell_dir.join("r_equity.json").is_file(),
            "r_equity tap persisted at {}",
            cell_dir.display()
        );
        assert!(
            !cell_dir.join("exposure.json").exists(),
            "unrequested taps are not persisted"
        );
        // The shipped viewer reads the member back (the cli_run.rs chart
        // idiom): exit 0 and an HTML page carrying both persisted tap series.
        let (chart_out, chart_code) =
            run_code_in(dir, &["chart", &format!("{trace_name}/{cell_key}")]);
        assert_eq!(chart_code, Some(0), "aura chart reads the persisted member: {chart_out}");
        assert!(
            chart_out.contains("\"equity\"") && chart_out.contains("\"r_equity\""),
            "the chart page carries the two persisted taps: {chart_out}"
        );
    }
    

    Run: cargo test -p aura-cli --test research_docs campaign_run_real_e2e Expected: 2 passed, 0 failed. On a host with the local GER40 archive, campaign_run_real_e2e_sweep_gate_walkforward exercises the full trace path (record trace_name, persisted equity.json + r_equity.json, the C1 equality assert holding, chart read-back); on a data-less host both tests print skip: no local GER40 data for the campaign e2e and pass.

  • Step 2: workspace gates. Run: cargo test --workspace Expected: green — 0 failed across all crates (data-gated tests pass or skip per host). Run: cargo clippy --workspace --all-targets -- -D warnings Expected: clean, zero warnings.