feat(aura-runner, aura-cli): a run reports the trace handle it recorded under

A single run persisted its taps and then said nothing about where: the only
way to learn the directory name was `ls runs/traces/`, and the chart intake's
not-found refusal pointed at "the handle a sweep/walk-forward/campaign run
printed" — two verbs retired with #319, and a single run printed no handle at
all. A caller holding a family id from a families listing was stuck: that id
is not a trace handle, and nothing said so.

The handle now rides BESIDE the report, not inside it. Both declared-tap entry
points return `RunOutcome { report, skipped, trace_name }` in place of the
`(report, skipped)` pair, and the CLI composes report + handle into one stdout
object through a `serde(flatten)` wrapper. `RunReport`, `MeasurementReport`,
`RunManifest` and the registry's compat mirror are untouched, so no stored
record shape moves and a tap-free run's line stays byte-identical.

Placement is the load-bearing decision, and it follows a precedent rather than
inventing one: the report is the durable C18 run record — its manifest states
what the run *was* — while a trace directory is where its output went. The
project settled this exact question one cycle earlier for the sibling value,
where the unbound-tap names ride beside the report so the CLI, not the
library, prints the note (C27/#297). An embedding host gets the handle as a
value, never as text to parse back.

The chart refusal gains a second arm. A family id
(`{campaign8}-{strategy_ordinal}-{instrument}-w..-r..-s..-{run}`) is
recognised syntactically — the trailing segment shape plus an 8-hex head, the
form `derive_trace_name` mints — and answered with the campaign's REAL
recorded handles, filtered to those `chart` would accept, suggesting one only
when exactly one matches. It never derives a handle by truncating the id: the
id's second segment counts strategies while the handle's counts runs, and one
campaign run mints family ids at several strategy ordinals whose traces all
live under that single run's handle. `TraceStore::names()` supplies the
enumeration, keeping trace file I/O in the store where C22 puts it.

Verification caught three errors worth recording. The first spec draft claimed
the handle was the id's leading pair — refuted by a green test where a run-0
campaign mints ordinal-1 ids. The second draft's replacement refusal said a
campaign run "prints one handle per family"; it prints one per run. Review
then found the `NotFound` filter untested — deleting it left the suite green —
so the case now has a test that fails without it.

closes #309
This commit is contained in:
2026-07-26 23:10:24 +02:00
parent 9636b00314
commit 9221bcd167
10 changed files with 448 additions and 31 deletions
+3 -2
View File
@@ -826,7 +826,7 @@ $ aura exec 42edebd2159de708009ba21e1ed4aea2cffabc373bf761c5765f79c190b677bd
With two stop regimes every (instrument, window) cell runs twice — the four
cells above are the "4 cell(s)" the validate summary counted. The regime
ordinal is an INFIX segment, `-r{k}`, right after the window segment
(`{id8}-{ordinal}-{instrument}-w{k}-r{k}-s{seed}-{member}`) — the
(`{id8}-{ordinal}-{instrument}-w{k}-r{k}-s{stage}-{run}`) — the
default/first regime carries `-r0` explicitly, never left unmarked — each
cell record names its regime, and generalization is keyed per regime —
regimes are compared, never pooled.
@@ -843,7 +843,8 @@ name — chartable with `aura chart <trace_name>` (members keyed
`<cell>/<member>`); `aura chart` also resolves a bare campaign id/name
against the stored campaign documents when no literal trace directory
matches, refusing rather than guessing when the name is ambiguous across
recorded runs.
recorded runs. A single run reports its handle the same way: the `trace_name` value on
its own stdout line, absent when the run recorded nothing.
Exit codes: a clean run exits 0; a usage error exits 2; a refusal before any
cell runs (invalid document, missing project, unresolvable strategy) exits 1;
@@ -71,6 +71,13 @@ path's job, not a per-run flag (the repeatable `--tap TAP=FOLD` selector,
#310, only chooses which of the blueprint's own declared taps to subscribe,
never names the trace).
A run that persists anything reports the handle it wrote under as
`trace_name` on its stdout line (#309) — appended to the record's own bytes,
absent entirely when the run recorded nothing — so the handle is read, never
guessed. The library hands it to an embedding caller as a value beside the
report, like the unbound-tap names (C27/#297); it is not part of the stored
record.
**Families.** A campaign document's `presentation.persist_taps` (the closed
tap vocabulary, C18/C27) requests traces for each cell's nominee, run through
`aura exec <campaign.json|id>` (#319 — a document's `axes` replace the