Files
Aura/docs/specs/0109-persist-taps.md
T
Brummel a25046e574 spec: 0109 persist_taps wiring (boss-signed)
Cycle 0109, anchored on #201. Encodes the five swarm-triaged decisions
(#201 comment 2026-07-04; F1-F4 unanimous, F5 4/5 with dissent
recorded): closed tap vocabulary of the wrap convention's four sink
names (escalation = new vocabulary entry or authored blueprint sink,
never an open node-path namespace); nominee-only non-reduce re-run per
cell (bounded, C1/reproduce precedent, equality-asserted); the
existing TraceStore family-shaped under {campaign8}-{run}; wiring
consumer-side after execute() returns (MemberRunner stays one-method,
aura-campaign stays trace-agnostic bar a one-line name stamp);
CampaignRunRecord.trace_name as the one sparse pointer (the 0108-F10
navigability lesson). Seam refinement recorded: the run counter is
assigned in append_campaign_run, so the store write composes the name
from a claim sentinel — contract pinned, exact seam is the planner's.

Boss-signed via grounding-check PASS (11/11). Planner note carried
from the check: the re-run equality assert pins METRICS equality (the
C1 core) — manifest fields are fresh-context and not reconstructed
cross-mode.

refs #201
2026-07-04 02:39:37 +02:00

11 KiB
Raw Blame History

persist_taps Wiring — Design Spec (cycle 0109)

Date: 2026-07-04 Status: Draft — /boss autonomous run; sign-off gate = grounding-check PASS Authors: orchestrator + Claude Reference issue: #201. All five design forks are decided on #201 (fork-triage comment 2026-07-04; F1F4 unanimous, F5 on a 4/5 majority with the dissent recorded). One seam refinement made here and recorded below: the F5 pointer field is stamped by the library as a pure name derivation, because the run counter it needs is assigned inside append_campaign_run.

Goal

aura campaign run honours presentation.persist_taps: after the pipeline settles, each cell's nominee is re-run once in non-reduce trace mode (bit-identical under C1 — the reproduce precedent, equality-asserted) and the requested taps are persisted through the existing TraceStore under a campaign-derived family name, chartable by the shipped web-from-disk contract (aura chart <name>/<cell-key>). The loud-deferral line dies; loud lines remain only for what genuinely cannot persist (no nominee in a gate-truncated cell; a vocabulary tap the run's configuration cannot produce). The tap namespace becomes a closed vocabulary validated intrinsically.

Architecture

  • aura-researchtap_vocabulary() (the wrap convention's four sink names) + DocFault::UnknownTap in validate_campaign; the persist_taps slot hint names the vocabulary. Validation only — no content id moves; a doc naming an unknown tap refuses at validate from this cycle on.
  • aura-registryCampaignRunRecord.trace_name: Option<String> (serde-default sparse; the C14/C23 widening convention).
  • aura-campaign — ONE line of new semantics: execute stamps trace_name = Some("{campaign8}-{run}") when persist_taps is non-empty (a pure derivation from fields it already holds — the record declares which store key this realization claims; writing bytes there stays the consumer's). Everything else is byte-untouched; the MemberRunner seam stays one-method (#201 decision 4).
  • aura-cli — after execute() returns: if trace_name is Some, ensure_name_free(WriteKind::Family) once, then per cell with a nominee: re-run the nominee non-reduce (the run_signal_r-style channel drain over the shipped wrap convention, windowed to the nominee report's own manifest.window), assert the re-run report equals the recorded nominee report (a C1 drift alarm — hard refusal on mismatch), and persist the requested-AND-producible taps as traces/<name>/<cell-key>/<tap>.json (the persist_traces_r ColumnarTrace shapes). Loud stderr lines per skipped cell (no nominee) and per unproducible requested tap (net_r_equity without a cost run — the campaign runner wires none today).

Determinism (C1): the re-run is the same member the executor already ran (same params, same window, same seed conventions) — the equality assert turns any divergence into a refusal instead of a silently-wrong trace.

Concrete code shapes

The user-facing program

"presentation": { "persist_taps": ["equity", "r_equity"], "emit": ["selection_report"] }
$ aura campaign run campaign.json
…selection/record lines as today…
aura: traces persisted: bb34aa55-0 (2 tap(s) x 1 cell(s))     # stderr, once
$ aura chart bb34aa55-0/<cell-key>                             # the shipped viewer, unchanged
$ aura campaign runs <campaign-id>                             # record carries "trace_name":"bb34aa55-0"

A campaign naming an unknown tap refuses at validate: presentation.persist_taps[0]: unknown tap "bias" (taps: equity | exposure | r_equity | net_r_equity). A gate-truncated cell: aura: cell …: no nominee; no traces persisted. A requested-but-unproducible tap: aura: tap "net_r_equity" is not produced by this run (needs a cost run); skipped.

aura-research

/// 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"]
}

// DocFault gains:
UnknownTap { index: usize, tap: String },
// validate_campaign: each presentation.persist_taps entry must be in
// tap_vocabulary() (mirror the UnknownEmitKind loop).
// open-slot / describe hint for persist_taps names the vocabulary
// ("list of: equity | exposure | r_equity | net_r_equity").

aura-registry

pub struct CampaignRunRecord {
    existing
    /// The TraceStore family name this realization claims when the document
    /// requests persist_taps — stamped by the executor as a pure derivation
    /// ("{campaign8}-{run}"); the consumer persists the bytes (#201 d5).
    /// None when the document requests no taps.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub trace_name: Option<String>,
}

aura-campaign (the one-line semantics + outcome plumbing)

In execute, where the record is finalized after append_campaign_run assigns run:

    record.run = run;
    if !campaign.presentation.persist_taps.is_empty() {
        record.trace_name = Some(format!("{campaign_prefix}-{run}"));
    }

(The stored line is written by append_campaign_run BEFORE run/trace_name are set on the returned copy — so the stamp must move INTO the store write: append_campaign_run already overrides run on the stored line; the cleanest honest shape is that execute computes trace_name's ELIGIBILITY up front — record.trace_name = None at build, and the store-side counter assignment composes the name. Since the registry cannot know the eligibility rule, the record handed to append_campaign_run carries a sentinel: execute sets trace_name = Some(String::new()) (empty = "claim a name") pre-append, and append_campaign_run replaces a Some (any content) with the derived Some("{campaign-prefix8}-{run}") on the stored line — the prefix from the record's own campaign field — leaving None as None. execute then mirrors the same derivation onto the returned copy. The planner pins the exact seam; the CONTRACT is: stored line and returned record carry the same Some("{campaign8}-{run}") iff persist_taps is non-empty, else None.)

aura-cli (the consumer)

// campaign_run.rs, replacing the loud-deferral eprintln after execute():
if let Some(trace_name) = &outcome.record.trace_name {
    persist_campaign_traces(trace_name, &campaign.presentation.persist_taps,
        &outcome, &strategies, env)?;   // Err => exit-1 prose (C1 drift alarm included)
}

// New fn (campaign_run.rs; the non-reduce member drain lives beside the
// runner's machinery in main.rs as a crate-root helper if cleaner):
// - env.trace_store().ensure_name_free(trace_name, WriteKind::Family)
// - per cell with nominee (params, report):
//     cell_key = sanitize_component("{strategy8}-{instrument}-w{window_ordinal}")
//       (content-derived, the member_key discipline — planner pins the exact
//        composition; never a runtime ordinal)
//     re-run: reload blueprint, wrap_r(sig, txs…, false, /*reduce=*/false, None),
//       bind the nominee params (wrapped names — the manifest form),
//       windowed sources over report.manifest.window (the ns bounds the
//       member actually ran), drain eq/ex/r/req channels
//     assert rerun_report == *report else Err("trace re-run diverged from the
//       recorded nominee (C1 violation): …")
//     persist requested ∩ producible taps via the TraceStore
//       (equity/exposure/r_equity ColumnarTraces from the drained channels;
//        net_r_equity only when a cost leg ran — today never: loud skip)
// - one summary stderr line: "aura: traces persisted: {name} ({t} tap(s) x {c} cell(s))"

Components

Change Crate
tap_vocabulary, DocFault::UnknownTap, validate + hints aura-research
CampaignRunRecord.trace_name + the append-side name composition aura-registry
the eligibility stamp + returned-copy mirror aura-campaign
persist_campaign_traces (non-reduce nominee re-run + TraceStore write + prose), deferral line removed, doc_fault_prose arm aura-cli

Data flow

persist_taps non-empty ─▶ execute stamps eligibility ─▶ append_campaign_run
  composes trace_name = "{campaign8}-{run}" onto the stored line + returned copy
CLI: trace_name Some ─▶ ensure_name_free ─▶ per nominee cell:
  non-reduce re-run (window = nominee manifest.window) ─▶ equality assert (C1)
  ─▶ TraceStore traces/<name>/<cell-key>/<tap>.json ─▶ summary stderr line
web face: aura chart <name>/<cell-key> — unchanged reader

Error handling

  • Unknown tap: intrinsic DocFault::UnknownTap, path-addressed prose with the vocabulary enumerated (validate AND the run's parse-valid gate).
  • Name collision (ensure_name_free): the store's cross-kind refusal, exit 1.
  • Re-run divergence: hard exit-1 refusal naming the C1 violation — never a silently-wrong trace.
  • No nominee / unproducible tap: loud stderr per case, run stays exit 0.

Testing strategy

  1. aura-research: vocabulary + UnknownTap fault (RED-first), hint lines.
  2. aura-registry: trace_name widening round-trip; pre-0109 line parses; append-side composition (sentinel Some → derived name on the stored line AND the counter's run; None stays None).
  3. aura-campaign (fake runner): execute returns trace_name Some("{campaign8}-0") iff persist_taps non-empty (and the stored line agrees — read back via load_campaign_runs).
  4. aura-cli seam: unknown-tap validate refusal; the persist_taps stderr surface flips from the deferral line to the new summary/skip lines (the 0107 campaign_run_persist_taps_deferred_loudly test flips — data-less hosts hit the member-data seam BEFORE tracing, so the flipped test pins the validate-tier + prose, and the full trace path is pinned by the gated e2e).
  5. Gated real-data e2e: extend the shipped e2e campaign with persist_taps: ["equity", "r_equity"]; assert exit 0, the record's trace_name, the trace files exist under traces/<name>/<cell-key>/, and aura chart <name>/<cell-key> (or the store's read_family) reads them; skip where data is absent.

Acceptance criteria

  1. The worked program persists nominee traces chartable by the shipped viewer; the record's trace_name navigates to them (no reader-side convention knowledge needed — the F10 lesson).
  2. persist_taps: [] behaves exactly as today (trace_name: None, no lines).
  3. Unknown taps refuse at validate; unproducible taps and gate-truncated cells skip loudly; the deferral line is gone.
  4. The re-run equality assert holds on the gated e2e (C1).
  5. Suite green, clippy clean, doc build clean; pre-0109 stored records parse.