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

223 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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-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.