# Fieldtest — cycle 0107 — campaign executor + blueprint on-ramp — 2026-07-03 **Role:** downstream consumer (methodology / campaign designer), public interface only (ledger C16/C18/C20/C24/C25, glossary, CLI `--help` + introspection verbs, the 0106 fieldtest corpus as shape hints). No `crates/` source read. Binary built from HEAD (`cargo build --workspace`, `target/debug/aura`, commit `428f83c`). Real data: scaffolded `demo_project/` (`aura new`), `cargo build` for the dylib, ran the executor over the local GER40 2024-09 Pepperstone archive. ## Fixtures (this directory) - `c0107_1_smacross.json` — SMA-cross op-script, lengths **bound** (content id `573262…`) - `c0107_1_smacross_open.json` / `_open_built.json` — lengths **unbound** (sweepable), op-script + its `graph build` envelope (content id `597d71…`) - `c0107_2_process.json` — sweep(sqn,argmax,deflate) → gate(expectancy_r>0) → walk_forward(rolling, ms lengths) (content id `5a88…`) - `c0107_2_campaign.json` — campaign missing a `bias.scale` axis (validates, then fails run) - `c0107_2_campaign_full.json` — the runnable campaign (all 3 open params covered), GER40, 2024-09 - `c0107_2_realization_campaign_runs.jsonl` — the recorded realization (3 identical runs + 1 gate-all) - `c0107_3_process_{mc,gateall,badmetric}.json` + `c0107_3_campaign_*.json` — refusal probes - `c0107_4_process_{plateau_wf,deflate_plateau,wf_toobig}.json` + `c0107_4_campaign_*.json` + `c0107_4_campaign_prefixref.json` — trip-up probes - `demo_project/` — `aura new` scaffold + all docs (its `runs/` store is project-gitignored; the realization is copied out to the corpus root as evidence) ## Findings ### F1 [working] The full loop closes — 0106 F7 is answered `aura campaign run c0107_2_campaign_full.json` (file = register-then-run) and `aura campaign run 533aed18…` (id) both execute the cell `GER40 × [2024-09-01,10-01]` through `std::sweep → std::gate → std::walk_forward` over real data (exit 0). The gate `expectancy_r > 0` kept survivors `[0,1,2]` (dropped the losing `fast=4/slow=16` member, `expectancy_r=-0.022`); the sweep argmax winner was ordinal 0 (`sqn=1.054`); walk-forward rolled 2 OOS folds re-sweeping **only** the survivor points and stamped `manifest.selection` on the OOS winners. Emit honored: `family_table` + `selection_report` lines, always the `campaign_run` record. The stored `CampaignRunRecord` (`runs/campaign_runs.jsonl`) links `{campaign, process, run, seed, cells[{stages: family_id / survivor_ordinals / selection}]}` over untouched `families.jsonl` records, run-counted per campaign id (3 successful runs → run 0/1/2). Intent → execution → recorded realization is one continuous, reproducible path. → carry-on. ### F2 [working] Determinism is bit-identical across re-run and addressing mode Two file runs and one id-addressed run of the same campaign are **byte-identical** after normalizing only the per-campaign run counter in family ids (`diff` clean on both pairs). Deflation nulls seed from the doc `seed=42` (`overfit_probability`, `deflated_score` reproduce). Realization is a pure function of doc + stores + data, as C1 promises. → carry-on. ### F3 [working] 0106 F1 closed — the document envelope is now introspectable `aura campaign introspect --unwired ` enumerates the whole envelope, including the previously-invisible `seed`: ``` open slot: format_version (required, must be 1) open slot: kind (required, must be "campaign") open slot: name / data / strategies / process.ref / seed / presentation … ``` `process introspect --unwired {}` likewise names `format_version/kind/name/pipeline`. The field names (`data`, `strategies`, `process.ref`, `presentation`) that 0106 F1 could not map are now spelled out. → carry-on. ### F4 [working] The honest-refusal surface is loud, precise, and correctly disposed All probed refusals were clean, actionable prose with the right exit code: - v1 boundary: `process stage 1 (std::monte_carlo) is not executable in v1; executable pipeline shape: std::sweep [std::gate]* [std::walk_forward]` (exit 1). `std::generalize` is symmetric. - gate-everything-out (`expectancy_r > 100`): exit **0**, `family_table` + sweep `selection_report` still emitted, walk-forward **truncated**, loud stderr `cell …: gate at stage 1 left no survivors; cell realization truncated`. - `plateau:mean` inside walk_forward: `walk_forward cannot use a plateau select (a gated survivor subset has no parameter lattice)` (exit 1). - `deflate:true` + non-argmax: `sweep deflate: true composes only with select "argmax"` (exit 1). - old 0106 wf vocab (`folds`): `block std::walk_forward: unknown slot "folds"` (exit 1) — the 0107 machinery-true vocabulary correction is enforced. - bogus file / unknown 64-hex id / uppercase-hex / outside-project: each exit 1 with a precise message (`neither a readable .json file nor a 64-hex content id` / `no campaign in the project store` / `campaign run needs a project: … no Aura.toml found up from `). - `persist_taps` deferred **LOUDLY** on stderr before execution (`persist_taps not yet honored (N tap(s) ignored)`), the 0106 F7 lesson honored. → carry-on. ### F5 [friction] The on-ramp rejects the op-script; the `graph build` bridge is undocumented The corpus's canonical strategy form is an **op-script** (`c0106_2_strategy.json`, `c0107_1_*`). `aura graph introspect --content-id FILE` accepts it (shape-discriminated) — but `aura graph register FILE` and `aura graph introspect --params FILE` **reject** it with a raw serde message: ``` aura: blueprint document is not valid JSON: invalid type: map, expected u32 at line 2 column 2 ``` Only the envelope emitted by `aura graph build < op-script > envelope.json` is accepted. Nothing in `--help`, the glossary `blueprint` entry, or the error names this `graph build` step, and the three on-ramp verbs disagree on which shapes they accept. (Once bridged, the content id is shape-invariant — op-script and envelope both hash to `573262…`, so `register` and a ref computed from the op-script agree; this part is correct.) Repro: `aura graph register c0107_1_smacross_open.json`. → plan: shape-discriminate the op-list in `register`/`--params` (as `--content-id` already does), or make the error name the `graph build` bridge. ### F6 [friction] The `content:` prefix trap persists and the on-ramp emits the wrong-to-paste form `graph|process|campaign register` and `introspect --content-id` all print `content:`, but every `ref` wants the **bare** hash. A campaign whose `process.ref` carries the copied `content:5a88…` form **passes intrinsic validation** and then fails referential: ``` aura: campaign references do not resolve: process content:5a88…6ea not found in the project store ``` The error now echoes the offending prefixed ref (an improvement over 0106 F3) but never says "strip the `content:` prefix", and `register` still prints exactly the form that breaks. This directly taxes the on-ramp the cycle set out to smooth. Repro: `aura campaign validate c0107_4_campaign_prefixref.json`. → plan: one bare display form across verbs, or accept/strip the `content:` prefix in refs (carried from 0106 F3, still open: #194). ### F7 [friction] `campaign validate` passes but `campaign run` aborts when an open param is uncovered Referential validation reports `all references resolve, axes are in the param space` (exit 0), but it only checks that each axis **name** is in `param_space` — not that **every** open param is covered by an axis. The open blueprint has three open params (`fast.length`, `slow.length`, `bias.scale`); a campaign sweeping only the two lengths **validates**, then aborts at member-bind: ``` aura: a member failed to bind: open param "graph.bias.scale" of strategy 597d71… is bound by no campaign axis ``` The executor's "every open knob required, no default" rule (documented for `sweep`/`walkforward` in the glossary) is not mirrored in the validator, so "valid" ≠ "runnable". (Secondary: the bind error uses the wrapped `graph.bias.scale` path while `--params` and the axis use the raw `bias.scale` — two namespaces for one knob.) Repro: `aura campaign validate c0107_2_campaign.json` (passes) then `aura campaign run c0107_2_campaign.json` (aborts). → plan: extend the referential tier to require full open-param coverage, surfacing the missing knob at validate time; reconcile the raw-vs-wrapped param path in the bind message. ### F8 [friction] The walk-forward window-fit refusal leaks a raw Rust `Debug` struct A walk_forward whose `in_sample_ms` exceeds the campaign window refuses correctly, but in internal `{:?}` form, unlike its clean sibling refusals: ``` aura: process stage 1: walk_forward windows do not fit the campaign window: SpanTooShort { span: (Timestamp(1725148800000), Timestamp(1727740800000)), need: 315964799999 } ``` The `SpanTooShort {…}` struct, the `Timestamp()` wrapper, and the unit-unlabeled `need` (ms) break the "Debug-free house seam" the cycle otherwise maintains, and a user cannot easily see that `need` is `in_sample_ms + out_of_sample_ms` in ms against the window span. Repro: `aura campaign run c0107_4_campaign_wf_toobig.json`. → debug (RED-pin the prose form). ### F9 [doc-gap] The rankable / gate metric roster is enumerable by no verb, nor by the refusal Block hints say `metric name (see metric_vocabulary)`, but no verb lists it (`--vocabulary` is the block list; `--block metric_vocabulary` → `unknown block`). `process validate` accepts **any** string (`profit_factor` passes intrinsic), and the run-time refusal names no roster: ``` aura: process stage 0: metric "profit_factor" is not rankable (winner selection needs one of the registry's rankable metrics) ``` `profit_factor` is itself emitted in every member's metrics block, so the exists-but-not-rankable split is invisible. A campaign author must guess the sweep/gate/wf metric names (0106 F6, still open: #197). → tighten docs: expose the rankable + gate metric rosters via `process introspect` (or enumerate them in the refusal). ### F10 [spec_gap] Register-then-run stores the campaign before the gate — unrunnable docs accumulate Every `campaign run ` issued left a `runs/campaigns/.json` (9 stored), including campaigns that then failed the referential gate (`prefixref`) or preflight (`mc`/`badmetric`/`plateauwf`/`deflateplateau`/ `wf_toobig`). The store thus accumulates never-runnable and referentially-broken documents, with no cleanup. The ledger is silent on whether the file sugar should gate before storing, and whether the content-addressed store should hold referentially-broken docs. → ratify (accept intrinsic-keyed store as-is) or tighten (gate before storing). ## Verdict **Yes — the campaign+process pair now delivers the full loop the 0106 verdict said was one wire short.** F7 (0106) is closed end to end: a campaign document + its referenced process document drive `aura campaign run` through `sweep → gate → walk_forward` over the real GER40 archive, and the run's realization is recorded as a thin, family-linking `CampaignRunRecord` that is content-addressed, run-counted, and **bit-identical on re-run** — persisted intent finally has a consumer, and every id, axis, gate, and selection earns its reason to exist. The authoring on-ramp that 0106 flagged is substantially repaired too: `graph register` + `introspect --params` (file and store-id) + `--content-id` file mode give a blueprint ref a resolvable path (F5-0106), and `--unwired {}` makes the whole envelope, `seed` included, derivable (F1-0106). The honest-refusal surface is a genuine strength — v1 boundary, empty gate (exit 0 + truncation), plateau/deflate composition, stale wf vocabulary, and every bad target are refused loudly, precisely, and with the correct disposition. What remains is friction concentrated at the *first-author* edge, not the loop: the on-ramp verbs disagree on op-script vs envelope shape and hide the `graph build` bridge (F5); the `content:` prefix that every `register`/`--content-id` prints is the exact form that silently breaks a ref (F6); `campaign validate` blesses a campaign the executor then rejects for an uncovered open param (F7); one preflight fault leaks a raw `Debug` struct (F8); and the metric roster a process must name is still discoverable nowhere (F9). None block the loop; all tax the newcomer. One spec silence to settle: whether register-then-run should store before or after the gate (F10). Counts: working 4 (F1–F4), friction 4 (F5–F8), doc-gap 1 (F9), spec-gap 1 (F10), bugs 0. ## Orthogonal concern (out of 0107 scope) `aura new` (cycle 0103) scaffolds a **nested `.git` repo** inside the project directory — a gitlink/submodule hazard when the scaffold is embedded in the tracked `fieldtests/` corpus. `demo_project/.git` (and the large `demo_project/target/`) were removed so the corpus commits cleanly. Worth a scaffold-side note (`--no-git` or skip when nested under a repo).