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

13 KiB
Raw Blame History

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_vocabularyunknown 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).