a25046e574
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
223 lines
11 KiB
Markdown
223 lines
11 KiB
Markdown
# 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; F1–F4 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-research** — `tap_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-registry** — `CampaignRunRecord.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
|
||
|
||
```json
|
||
"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
|
||
|
||
```rust
|
||
/// 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
|
||
|
||
```rust
|
||
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`:
|
||
|
||
```rust
|
||
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)
|
||
|
||
```rust
|
||
// 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.
|