From 9f87e5a58321ec945c98d86b006d7a0ea84173f8 Mon Sep 17 00:00:00 2001 From: claude Date: Sun, 26 Jul 2026 23:22:17 +0200 Subject: [PATCH] =?UTF-8?q?audit:=20trace-handle=20cycle=20close=20?= =?UTF-8?q?=E2=80=94=20refusal=20parity,=20measurement=20pin,=20ledger=20l?= =?UTF-8?q?ockstep?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Architect review over 9636b00..9221bcd. What holds, confirmed against the diff rather than the commit body: no stored record shape moved (neither `aura-engine/src/report.rs` nor the registry compat mirror appears in the change at all, and the byte pins pass unedited); the handle travels the beside-the-report route C27/#297 established for the unbound-tap names, with the library still printing nothing; trace enumeration landed inside `aura-registry` where C22 puts trace file I/O; no C14 exit class moved, both refusal arms falling through to the same exit 1. Two high drift items, both fixed here: - The corrected refusal claimed a campaign run prints its handle as `trace_name` on stdout. It prints `campaign_run.trace_name` — the campaign leg wraps its record, as the glossary states outright — so the chain the refusal advertised returns null there. This is the third instance of one error class in this cycle: a false claim inside the very prose meant to stop misdirecting callers. Grounding caught the first two, review the third. - The measurement leg shipped without the handle pin the spec asked for. All three new pins drove the strategy path, so the measurement leg emitted a handle nothing asserted. It has its own test now, on the shared bind pair the two legs cannot drift across. Ledger and vocabulary brought into lockstep: C27's current state records the `RunOutcome` carrier that replaced the pair; the glossary's tap entry names the single-run handle and warns that a family id is not one; the authoring guide's own single-run tap example now says the name is printed rather than guessed. The suggestion line gained the `aura:` prefix every other stderr line carries. Bench: all five fingerprints OK — the decisive signal, since they hang on the record bytes this cycle was required not to move. Timing deltas are load noise (loadavg 8.7 under concurrent agents), not regressions. refs #309 --- crates/aura-cli/src/main.rs | 9 +++---- crates/aura-cli/tests/run_measurement.rs | 28 ++++++++++++++++++++++ docs/authoring-guide.md | 4 +++- docs/design/contracts/c27-declared-taps.md | 7 +++++- docs/glossary.md | 2 +- 5 files changed, 43 insertions(+), 7 deletions(-) diff --git a/crates/aura-cli/src/main.rs b/crates/aura-cli/src/main.rs index 413b4fa..e8601bc 100644 --- a/crates/aura-cli/src/main.rs +++ b/crates/aura-cli/src/main.rs @@ -455,16 +455,17 @@ fn render_chart_by_kind( handles.join(", ") ); if handles.len() == 1 { - eprintln!("Did you mean: aura chart {}", handles[0]); + eprintln!("aura: Did you mean: aura chart {}", handles[0]); } } } None => { eprintln!( "aura: no recorded run or family '{name}' under runs/traces \ - (both a single run and a campaign run print their handle as `trace_name` \ - on stdout — one per run — so check that value for a typo. A trace is \ - produced by `aura exec --tap =` on a blueprint \ + (a single run prints its handle as `trace_name` on stdout; a campaign \ + run prints it as `campaign_run.trace_name`, one per run — check that \ + value for a typo. A trace is produced by \ + `aura exec --tap =` on a blueprint \ or a campaign's `presentation.persist_taps` section, not by naming a handle here)" ); } diff --git a/crates/aura-cli/tests/run_measurement.rs b/crates/aura-cli/tests/run_measurement.rs index a3e82e0..55fe626 100644 --- a/crates/aura-cli/tests/run_measurement.rs +++ b/crates/aura-cli/tests/run_measurement.rs @@ -138,3 +138,31 @@ fn run_refuses_a_hand_crafted_envelope_with_a_bad_root_name_before_any_trace_wri "no trace directory of any shape is written on a refused run" ); } + +/// #309: the measurement leg reports its trace handle exactly as the strategy +/// leg does — both bind through one shared tap-plan pair, so a handle present +/// on one and absent on the other would mean the seam had drifted. +#[test] +fn measurement_run_reports_its_trace_handle_on_stdout() { + let cwd = temp_cwd("measurement-trace-handle"); + let bp_path = cwd.join("measurement.json"); + std::fs::write(&bp_path, measurement_blueprint_json()).expect("write measurement blueprint"); + + let out = Command::new(BIN) + .args(["exec", bp_path.to_str().expect("utf-8 path")]) + .current_dir(&cwd) + .output() + .expect("spawn exec"); + assert!(out.status.success(), "stderr: {}", String::from_utf8_lossy(&out.stderr)); + + let line = String::from_utf8_lossy(&out.stdout); + let report: serde_json::Value = + serde_json::from_str(line.trim()).expect("stdout is one JSON object"); + let handle = report["trace_name"].as_str().expect("trace_name is present and a string"); + assert!( + cwd.join("runs/traces").join(handle).join("fast_tap.json").exists(), + "the reported handle must name the directory the tapped series landed in; got {handle}" + ); + + let _ = std::fs::remove_dir_all(&cwd); +} diff --git a/docs/authoring-guide.md b/docs/authoring-guide.md index f9680f5..2711fbc 100644 --- a/docs/authoring-guide.md +++ b/docs/authoring-guide.md @@ -334,7 +334,9 @@ The built blueprint carries a `taps` array naming the resolved wire (`{"name":"spread","from":{"node":,"field":0}}` — the name is addressed, the index is resolved *for* you). A single `aura exec ` then constructs a recorder at that point and persists the `spread` series as a -`ColumnarTrace` in the run's trace store, chartable by its name. A tap is inert +`ColumnarTrace` in the run's trace store, chartable by its name — which the +run prints for you as `trace_name` on its stdout line, so it need not be +guessed. A tap is inert in a campaign member run (no per-cell recorder) — it is a single-run observation surface (or, per campaign, what a nominee's `persist_taps` re-run records, §3). diff --git a/docs/design/contracts/c27-declared-taps.md b/docs/design/contracts/c27-declared-taps.md index 93d8e7b..3b1c751 100644 --- a/docs/design/contracts/c27-declared-taps.md +++ b/docs/design/contracts/c27-declared-taps.md @@ -77,7 +77,12 @@ data-reachable: no flag keeps the record-all default, any flag replaces the plan entirely (unlisted taps stay unbound/inert). `BoundTaps` carries the unbound tap names out (`skipped: Vec`), riding beside the returned report rather than inside it, so the CLI — not the library — prints the -unbound-tap note (#297). The boundary is thereby +unbound-tap note (#297). Since #309 both entry points return that pair as a +named `RunOutcome { report, skipped, trace_name }`, the third field being the +trace-store handle the run recorded under (`Some` under exactly the condition +that opens the write path, `None` otherwise) — the same beside-the-report +discipline, widened rather than re-cut: the record keeps its shape and the +shell renders the handle. The boundary is thereby fixed in place: *selecting* a subscription is run-mode authority, exercised by the run-mode owner — on the one-shot path the CLI invocation itself, a projection exercising this contract's authority, not a second home for diff --git a/docs/glossary.md b/docs/glossary.md index 6738d3a..84297c3 100644 --- a/docs/glossary.md +++ b/docs/glossary.md @@ -337,7 +337,7 @@ An orchestration axis varying tuning params (grid or random) within a fixed stru ### tap **Avoid:** probe, monitor, scope (for the observation slot — "probe" is taken by the sweep-terminal `blueprint_axis_probe` sense; the #77 `Recorder`→`Probe` rename was retired, 2026-07-21) -A named recorded stream produced by a recording `sink` — the addressable label (e.g. `equity`, `net_r_equity`) under which one sink's per-cycle output is persisted as a columnar (SoA) `ColumnarTrace` and selected for charting via `--tap`. Distinct from the `sink` node that emits it (a tap is the stream, the sink is the role) and from a whole recorded run (a bundle of taps); taps fire at their own cadences and are fused only by joining on the recorded timestamp, never by positional index. In a `campaign document`, `persist_taps` names taps from the CLOSED vocabulary `equity | exposure | r_equity | net_r_equity` (`tap_vocabulary`, 0109/#201) — a new observable is a new vocabulary entry or an authored blueprint sink, never an open node-path namespace. Persisted taps are charted by the printed handle: a campaign run's deterministic `trace_name` (`"{campaign8}-{run}"`, never a user-chosen name since #319) charts its whole family (`aura chart `; members keyed `/` in the chart) — or, equivalently, a bare campaign id/name that uniquely resolves against the recorded campaign documents (#238; a name reused across runs refuses rather than guessing). +A named recorded stream produced by a recording `sink` — the addressable label (e.g. `equity`, `net_r_equity`) under which one sink's per-cycle output is persisted as a columnar (SoA) `ColumnarTrace` and selected for charting via `--tap`. Distinct from the `sink` node that emits it (a tap is the stream, the sink is the role) and from a whole recorded run (a bundle of taps); taps fire at their own cadences and are fused only by joining on the recorded timestamp, never by positional index. In a `campaign document`, `persist_taps` names taps from the CLOSED vocabulary `equity | exposure | r_equity | net_r_equity` (`tap_vocabulary`, 0109/#201) — a new observable is a new vocabulary entry or an authored blueprint sink, never an open node-path namespace. Persisted taps are charted by the printed handle: a campaign run's deterministic `trace_name` (`"{campaign8}-{run}"`, never a user-chosen name since #319) charts its whole family (`aura chart `; members keyed `/` in the chart) — or, equivalently, a bare campaign id/name that uniquely resolves against the recorded campaign documents (#238; a name reused across runs refuses rather than guessing). A single run reports its own handle the same way, as `trace_name` on its stdout line (#309) — the run's render name, absent when the run recorded nothing. NB a `family id` is not a handle: it names one cell/stage record within a run, and cutting it down does not yield the run's handle. Since C27 (#282) the word also names a second, author-facing sense: a **declared tap** — a `Composite.taps` entry `Tap { name, from: {node, field} }` a hand-authored blueprint declares on an interior output wire, the output-side twin of an `input_role`. It is a pure declaration (no channel endpoint); the harness binds it run-mode-aware (a single `aura exec` constructs a recorder at each and persists the series through the trace store; a campaign member run leaves it unbound and inert). This is an OPEN, per-blueprint author-declared name — distinct from the CLOSED campaign `persist_taps` vocabulary above, which selects among fixed observables of the standard R-harness. Both senses land as `ColumnarTrace`s in the same trace store; the closed vocabulary is what a `campaign document` selects, the declared tap is what a blueprint author names.