Files
Aura/fieldtests/cycle-0107-campaign-executor/FINDINGS.md
T
Brummel 71405d4d86 fieldtest: cycle-0107 campaign executor + on-ramp corpus (GREEN-core)
Verdict: the 0106 F7 wire is closed — campaign+process documents drive
aura campaign run through sweep -> gate -> walk_forward over the real
GER40 archive, realization recorded as a thin family-linking
CampaignRunRecord, bit-identical on re-run and across file/id
addressing (C1). 0106 F1/F5 on-ramp gaps confirmed repaired; the
honest-refusal surface (v1 boundary, empty gate exit 0 + truncation,
plateau/deflate composition, stale wf vocabulary, bad targets,
persist_taps loud deferral) exercised and clean. 0 bugs.

Findings triaged: F5 -> #202 (op-script vs envelope shape on the
on-ramp verbs), F6 -> #194 comment (content: prefix trap re-confirmed,
now spans graph register), F7 -> #203 (open-param coverage at validate;
raw-vs-wrapped path prose), F8 -> RED-first fix follows this commit
(SpanTooShort Debug leak), F9 -> #197 comment (rankable/per-member
roster split undiscoverable), F10 -> ratified store-before-gate on
#198 (derived: consistent with campaign register's intrinsic-only
gate; content-addressed store is an idempotent intent archive).
Orthogonal: #204 (aura new nested-.git gitlink hazard).

refs #198
2026-07-03 22:29:14 +02:00

216 lines
13 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.
# 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 <bare {}>` 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 <id> in the project store` / `campaign run needs a
project: … no Aura.toml found up from <cwd>`).
- `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:<hash>`, 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 <file>` issued left a `runs/campaigns/<id>.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 (F1F4), friction 4 (F5F8), 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).