Iteration 3 task 4: register_refuses_undescribed_composites_end_to_end
pins the store gate's verb surface -- a doc-less root refuses naming
`sma_signal` and the rule, a doc-less named nested composite refuses
naming `inner`, the described shipped example registers (exit codes and
stderr through the C14 contract). The pre-existing twin pin
graph_doc_field_moves_content_id_but_not_identity_id stays green.
Checkpoint note: the loop escalated this task spec-ambiguous because
its plan gate demanded a fully green graph_construct suite while the
op-script register test is known-open until the doc-op task lands (a
plan-sequencing defect, orchestrator's authoring error); the task's own
work was verified green and is committed here, the doc-op task follows.
refs #316
Iteration 3 of the self-description plan, tasks 1-3 (checkpoint commit
mid-iteration; the register e2e, the op-script doc slot, and the full
gates follow):
- aura-research: DocFault::BadDescription { subject, fault }; a present
document description passes doc_gate in validate_process /
validate_campaign (absent is never a fault and stays byte-identical);
prose arm in doc_fault_prose; tests incl. content-id participation.
- aura-registry: the store boundary gates admission -- the public
put_blueprint parses the canonical bytes and walks root + named
nested composite docs (None refuses like ""), refusing via
UndescribedComposite { name, fault } / MalformedBlueprint; the raw
write survives as private put_blueprint_unchecked (store-mechanics
tests + the never-retroactive witness: doc-less pre-C29 entries stay
readable). Registry-internal seeders triaged junk-vs-real.
- authored root docs: the 4 example blueprints + their 4 open-fixture
twins, the scaffold starter template, the scaled_signal and hl_signal
inline fixtures, and the three programmatic member.rs builders
(breakout, meanrev, hl_channel) via GraphBuilder::doc -- mechanical
propagation of the authored lines.
Recovery note: the implement run's task 3 discarded tasks 1+2's edits
in three files via a file-level checkout (the issue-#23 guard shape);
the loop's boundary snapshot caught it and the content was restored
from recovery snapshot 11a6ae2, then re-verified (research 81 green,
registry 51 green, cli 58/59).
Known-open at this checkpoint: the op-script register test is red --
op-built composites have no doc surface (#125 scope cut); the op
vocabulary grows the anticipated doc slot in the next task of this
iteration (decision logged on the issue).
refs #316
Iteration 2 of the self-description plan: field presence is compile-
enforced since iteration 1, but an extension crate can still ship the
empty-string alibi (or a name-restating doc) the compiler cannot see.
load_crate now walks every charter-checked type id, resolves its
builder, and runs aura_core::doc_gate over schema.doc; a fault refuses
the load via ProjectError::UndescribedVocabularyEntry { type_id, fault }
(exit 1 through the CLI's Display-driven surface, C14).
The variant carries the DocGateFault beyond the spec's minimal sketch
because the spec's error-handling section requires the refusal to name
entry + field + rule -- with only the type id the prose could not
distinguish the two faults (decision logged on #316).
Two gate-failing fixture crates drive the e2e, modelled on
badcharter-project: undescribed-project (und::Opaque, doc: "") for the
Empty arm, restated-project (restated::Echo, doc: "Restated Echo") for
the RestatesName arm -- the second contributed by the E2E phase to
close the only-unit-pinned gap on that arm. A runner unit test pins
both rendered Display arms directly. The described twins (demo-project,
nested-nodes-project) stay green and are now load-bearing for the gate.
Gates: cargo test -p aura-cli --test project_load (14 green, incl. both
refusal e2es); cargo test --workspace green; clippy -D warnings clean.
refs #316
C29 compile/unit seam, tasks 6-10 -- iteration 1 of the self-description
plan complete: every engine-shipped vocabulary entry now carries a
gate-clean one-line meaning.
- aura-runner: the internal tap recording sink threads its doc (the
last unthreaded NodeSchema site) -- first full workspace build since
the field became required.
- aura-cli scaffold: the sample node's template carries a doc line plus
a maintenance comment, so every scaffolded extension crate starts
gate-clean; both extension-vocabulary fixture exemplars thread real
meaning lines (the load seam walks these in iteration 2). New E2E:
the scaffold's own doc must pass its own gate -- an alibi doc in the
template would teach the wrong pattern to every new project, and the
compiler checks only the field's presence, never its shape.
- aura-research: metric_vocabulary()/tap_vocabulary() promote from bare
name arrays to MetricSchema/TapSchema carriers (id + doc, 17 + 4
authored lines); callers adapt to .id with byte-identical output
(introspection goldens unchanged). The #190 guard keeps its triple --
the doc column adds no fourth roster site.
- Coverage: tests/self_description.rs walks all five vocabularies
(blocks, metrics, taps, folds, std nodes) through doc_gate -- the
guard that keeps any future edit from blanking a doc.
Adjudicated review residue: the 17 metric doc strings are surfaced by
no CLI path yet -- deliberate; task 8 pins callers byte-identical, the
surfacing belongs to the CLI self-description work (#315). Plan-cited
coordinates lib.rs:1003/:1283 resolved to the real caller sites
(:1019/:1299); the plan's literal test code was reconciled to the real
accessors (FoldRegistry::core().roster(), Env::std()).
Gates: cargo test --workspace green (0 failed, incl. the new coverage
walk + scaffold E2E); cargo clippy --workspace --all-targets --
-D warnings clean.
refs #316
Completes the #283 registry cycle. run_measurement drops its
duplicated Recorder-bind + post-run try_iter drain for the shared
bind_tap_plan/BoundTaps pair — the mirror of run_signal_r is now the
same code, so the two declared-tap entry points cannot drift; the
measure CLI arm passes TapPlan::record_all() (semantics unchanged,
constant memory). Measure-side library tests pin a fold plan's
one-row trace (streamed mean bit-equal to the slice mean over the
synthetic prices) and the tap-free write-nothing guarantee through
the shared pair.
Run-side integration tests pin the #283 acceptance surface: fold and
live plans agree exactly with the recorded series of the same
blueprint (C1, three runs, three drain policies); a live-only plan
persists nothing; plan refusals (unknown tap, roster-enumerating
unknown label) fire typed before any store I/O; two identical
record-all runs produce byte-identical tap files. The previously
untested `aura: writing tap traces failed` register gains end-to-end
coverage on both arms: store root occupied by a file (pre-run
refusal) and a read-only run dir (deferred initialize-open failure —
the run completes, the failure surfaces terminally, no index.json is
written, the store's treat-as-absent crash shape).
Two forced deviations from the plan's scripted literals, both
verified equivalent: an explicit match replaces expect_err (BoundTaps
is deliberately not Debug — it carries a TraceStreamer), and the test
helper takes &Path, not &PathBuf (clippy::ptr_arg under -D warnings).
Closes#77 as specced: zero per-cycle heap on the tap path
((Timestamp, Cell) payload, committed earlier this branch) and the
Recorder→Probe rename retired (no Probe symbol anywhere; decision
recorded on #77, 2026-07-21).
Verification: cargo test --workspace — 1517 passed, 0 failed;
cargo clippy --workspace --all-targets -- -D warnings clean;
tap_recording 5/5 (byte-compat anchors + both register arms).
refs #283 refs #77
The #283 registry model's assembly seam: tap_plan.rs carries
TapSubscription (Named{label, params} — fully data-expressible — |
Live(closure), the single non-data variant), TapPlan (name →
subscription + default; the plan travels with the FoldRegistry it
resolves against, the Env-at-resolve pattern, so layered hosts inject
vocabulary as data), and the registry itself: eight core entries
(record, count, sum, mean, min, max, first, last), each with a doc
line (help + roster-enumerating refusals) and a scalar-typed param
schema (all v1 entries param-less; the seam ships now because it sits
in every entry's build signature). Typed TapPlanError refusals fire
pre-bootstrap, before any store I/O; the terminal Store variant keeps
the established "writing tap traces failed" register bytes.
bind_tap_plan/BoundTaps is the shared wiring pair both declared-tap
entry points call; run_signal_r is re-wired onto it here (Recorder
bind + post-run try_iter drain replaced — no O(cycles) retention, no
per-cycle heap), with all six callers threaded (both CLI run-arm and
the four example-identity test calls pass TapPlan::record_all()).
run_measurement follows in the next commit.
Registry growth stays a new Rust entry (C25); a layer-registered
entry is pinned by the injection test, param validation by a
schema-carrying test entry (missing/mismatched/unknown all refused
with named messages).
Verification: cargo build --workspace clean; aura-runner 85+ tests
green incl. both example-identity anchors; tap_recording byte-compat
anchors green (recorded traces byte-identical, tap-free writes
nothing, duplicate-tap refusal unchanged); clippy -D warnings clean.
refs #283 refs #77
aura {process|campaign} show now accepts the 8-hex prefix every surface
prints: an exact match is tried first, then the prefix is resolved
against the store's candidate ids — a unique match prints the identical
stored bytes as the full id (#164 byte discipline), an ambiguous prefix
refuses listing every sharing candidate, and a genuinely-unknown id
keeps its established refusal. The resolution mirrors the semantics
reproduce shipped with #298, closing the asymmetry the #300 fieldtest
named (df_4). Registry gains list_process_ids/list_campaign_ids
(missing store dir reads as empty, matching the blueprint-scan
discipline); campaign_not_found_prose becomes the single source for
show_campaign's two not-found arms. The ambiguity arm is pinned by a
unit test at the resolution seam — hash-derived ids make a
shared-prefix E2E fixture impractical.
aura {process|campaign} show is exact-match-only while every surface
prints 8-hex prefixes and reproduce has resolved prefixes since #298;
the fieldtest's prefix paste (df_4) hits the same flat not-found prose
as a genuinely-unknown id. The E2E test pins the wanted headline: a
unique 8-hex prefix of a registered id prints byte-identically what the
full-id show prints (#164 byte discipline), both verbs. The ambiguity
refusal is deliberately left to a unit test at the resolution seam —
hash-derived ids make a shared-prefix E2E fixture impractical.
register_process and register_campaign now carry the same
env.provenance() no-project guard as their show/runs siblings, refusing
with exit 1 and the missing-Aura.toml prose instead of letting
Env::runs_root()'s relative fallback materialise ./runs/ in an innocent
cwd. Five pre-existing register tests ran from a bare temp cwd and were
reaching their asserted behaviour only through this bug; they now run
inside the fresh_project() fixture, preserving their original property
(content addressing, intrinsic-validation refusals) in a real project.
Verified: the RED E2E test (register_outside_a_project_refuses_and_
leaves_no_store, both verbs, refusal + no side-effect store) observed
GREEN by the loop's independent verify alongside the full suite.
register_process/register_campaign call env.registry() without the
no-project guard their show/runs siblings carry, so Env::runs_root()'s
relative fallback silently creates ./runs/ in an innocent cwd and
registers the document there (exit 0). The E2E test pins both verbs and
both halves of the wanted refusal: exit 1 naming the missing Aura.toml,
and no store directory created as a side effect.
show_process/show_campaign now guard env.provenance().is_none() before
touching the registry — the guard their siblings (campaign_runs,
validate_campaign_file) already carry — refusing with the honest
degradation prose ('needs a project ... no Aura.toml found up from
{cwd}') instead of blaming the id with 'not found in the project
store' against a store that does not exist. Refusal exit 1 unchanged.
The register-verb sibling gap (silently creating a stray ./runs/) is
deliberately NOT touched here — tracked as #305.
RED-first: the in-process pin landed at 803c970; independent verify
re-ran it green with the suite.
closes#304
Outside any project, show_process/show_campaign print 'not found in the
project store' — asserting a store that does not exist and blaming the
id, where the sibling verbs (campaign_runs, validate) degrade honestly
naming the missing Aura.toml. The in-process RED pins: the refusal
stays Err, its prose names 'no Aura.toml found up from', and does NOT
claim 'not found in the project store'. Diagnosed cause: both show fns
lack the env.provenance().is_none() guard their siblings use, so the
no-project fallback store's Ok(None) masquerades as an unregistered id.
refs #304
Architect review (cycle 757e3ac): core clean — show is pure shell
dispatch over Env::registry() (C28); both ledger amendments match the
F1-F8 decision log exactly; document schema untouched; the #164
no-framing pin is stricter than the spec snippet asked. Four peripheral
drift items, resolved:
- fix: docs/authoring-guide.md verb rosters now name show (process) and
show|runs (campaign) — the guide teaches exactly the
generate->retrieve->extend loop this cycle closed.
- fix: docs/glossary.md campaign/process document entries carry the
settled verb sets (record-reality glossary write).
- fix: show_process's not-found refusal now sources its prose from
ref_fault_prose(ProcessNotFound) instead of a hand-copied format!
(byte-identical, test-pinned; the campaign-side prose has no RefFault
sibling and stays the single campaign instance).
- ratify (log-only): the shipped show helpers use print! (no framing,
#164) where the spec snippet sketched println! — the code is the
stricter, correct form; recorded on the reference issue's decision
log. The spec is a git-ignored working file swept at this close.
Regression: aura-bench report-only run, all fingerprints OK, deltas in
noise (host load 9.6 noted; engine/ingest/campaign/cli lanes all within
±5%). Full aura-cli gates re-run green after the tidy edits.
refs #300
The #300 keystone cycle: the residual run-quintet flags were already
document vocabulary on the --real arm (#210/#220); this closes the loop
and ratifies the surface.
- `aura process|campaign show <content-id>` prints a registered
document's canonical bytes (print!, no framing — #164), so
generate -> retrieve -> hand-extend -> re-register needs no direct
store filesystem access. Refusals reuse the store-hint prose, exit 1.
- walkforward/mc --stop-length/--stop-k become clap-typed i64/f64
(generalize's form); stop_knob_or and its parse_csv_list helper are
deleted (multi-value/invalid input is now clap's exit-2 rejection —
the two dissolved-verb refusal tests retarget, the type-dead unit
test is deleted).
- --select accepts bare `plateau` as the documented default
plateau:mean (#227 carry); document schema untouched — the two
spellings generate content-id-identical campaigns (test-pinned).
- family.rs's quadruplicated demo stop literal collapses onto one
DEFAULT_STOP const (maintainer-lens drift finding).
- Ledger: C25's "which projection next" line resolved (document-first
delivered, per-verb identity re-ratified — F8; host/MCP stay
demand-driven); C18 records the read-back.
Design triage, fork decisions F1-F8, and the auto-signed spec live on
the reference issue. Verification: full workspace suite 1482 passed /
0 failed with the real GER40 archive exercised (the byte-identity
round-trips ran, not skipped); clippy -D warnings clean.
closes#300
Cycle-close audit (architect: drift_found; design core clean). What
holds, architect-confirmed with the guard run and the diff read in
full: the shell boundary is real and enforced (full-workspace
c28_layering incl. completeness + shell-content checks, acceptance
grep clean — no member-run symbol left under crates/aura-cli/src);
aura-campaign keeps zero production dependency on the runner; the IC
move is verbatim against the anchor; the new library-only E2E pins C1
(two independent runners, byte-identical RunReport).
Drift items, all resolved as fixes in this commit:
- design ledger: the C28 status sentence "No crate exists yet for
measurement" contradicted this cycle's own phase-3 done line — now
records aura-measurement as seeded (#295), execution still unbuilt.
- the #147 registry-dispatch acceptance tests (IC deflation null,
seeded determinism, cross-vocabulary refusal) were pure library
properties stranded in the shell's test module — relocated to
aura-measurement/tests/registry_dispatch.rs (engine/registry as
dev-only, layering-exempt edges), per the cycle's own tests-move-
with-their-module rule.
- aura-runner's direct production edge on the external data-server
tree was structurally invisible (the direction table checks aura-*
keys only) and unrecorded — the C28 assembly prose now names it, and
a new c28_layering test pins the external tree's entry points to
exactly {aura-ingest, aura-runner, aura-cli}.
- the C28 shell prose named three buckets that did not cover two real
shell residents — it now names the op-script construction front-end
(translation) and the `aura new` scaffolder (authoring-tooling,
shell-resident like rendering until a second consumer wants it).
Deliberately deferred, tracker homes exist (not drift): #297
(process::exit sites in aura-runner's single-run verb paths), #294
(IC duplicate-timestamp semantics), #288-era rustdoc unresolved-link
warnings in aura-std/aura-backtest (pre-existing byte-identically at
the anchor).
No regression scripts are configured (the bench is report-only); the
architect review is the gate. Verification: cargo test --workspace
green (1474 passed, 0 failed) incl. the relocated registry-dispatch
tests and the new data-server guard; clippy --workspace --all-targets
-D warnings clean.
refs #295
Completes the shell-boundary cycle (tasks 10-13):
- c28_layering now enumerates the FULL workspace: every crate row is
reconciled against its real production [dependencies], a completeness
assertion pins the table to the on-disk crates/ set (a new crate can no
longer escape the guard silently), the shell/assembly imported-by-
nothing rule is asserted, and a shell-content check pins aura-cli to
no-[lib] plus a closed allow-list of argv/translation/presentation
modules. A new domain module in the shell now fails the suite.
- Ledger amendments: C28 gains the assembly position (aura-runner) and
the corrected import-rule prose (the ratified aura-campaign ->
aura-backtest production edge, #291/#292); phase 3 is marked done with
the #297 process::exit residual named; a provenance note records this
as structural-debt closure, not a demonstrated downstream blocker.
C25 records the control-surface decision: the text artifact vocabulary
is canonical, every control surface (CLI executor verbs, a future
host, an MCP face, a World program) is a projection/executor over it —
the which-projection-next ranking deliberately open on #295. C14 gets
the executor-face amendment, C26 the binding-module relocation.
- aura_campaign::MemberRunner's doc names the shipped default
implementor (aura-runner::DefaultMemberRunner) while the column keeps
zero dependency on it.
- New library-only E2E fixture (aura-runner/tests/world_member_run_e2e):
runs the canonical member recipe over a tiny synthetic archive with no
aura-cli in the link graph, and pins C1 determinism (two independently
constructed runners produce a byte-identical RunReport).
- campaign_run.rs module doc: intra-doc MemberRunner link re-anchored to
aura_campaign::MemberRunner (the impl moved out with part 1).
Verification: cargo test --workspace green (1473 passed, 0 failed);
clippy --workspace --all-targets -D warnings clean; cargo doc clean of
NEW warnings (the five unresolved-link warnings in aura-std/aura-backtest
predate this cycle byte-identically at the anchor — #288-era doc drift,
left for the cycle audit).
refs #295
The shell no longer defines what an aura backtest is. Tasks 1-9 of the
shell-boundary cycle — structural extraction, behaviour byte-identical:
- aura-runner (new; the C28 assembly position): input binding (the C26
module, moved whole), the C1-load-bearing param<->config translators,
harness assembly (wrap_r / run_signal_r / run_blueprint_member and the
probe/reopen cluster), axis + risk-regime conventions (bind_axes et
al.), the campaign family builders + MC guards, reproduce
(process::exit -> RunnerError, shell remaps to identical bytes), the
measurement-run orchestration, project loading (Env / cdylib load /
charter / staleness, moved whole), the coverage gap-walk (deduplicated
onto interior_gap_months), and DefaultMemberRunner — the public
implementation of aura_campaign::MemberRunner (ex CliMemberRunner).
The MemberRunner trait stays in aura-campaign; the column still
imports no harness/data-binding machinery.
- aura-measurement (new; seeds C28 rung 3): the IcMetrics/IcKey
vocabulary + information_coefficient, verbatim incl. serde derives
(#294 duplicate-timestamp semantics move as-is, unresolved).
- aura-backtest: the pure per-run scaffold (point_from_params,
wf_ms_sizes / fit_wf_ms_sizes, intersect_shared_window).
- shell residue: main.rs / campaign_run.rs keep argv/dispatch,
argv->document translation, and presentation only;
walkforward_summary_json_from_reports now calls the public
aura_engine::param_stability instead of its inline twin.
- worked example (crates/aura-runner/examples/world_member_run.rs): a
World program runs a member backtest through the library alone.
Held quality findings, adjudicated: the plan's literal pub-visibility
list for binding.rs kept (cosmetic); ~20 refusal sites inside
aura-runner (family/member/measure/translate) still process::exit —
behaviour-identical today, conversion to RunnerError is filed forward
(refs #297); rustfmt line-width drift on re-pathed call sites left for
a repo-wide fmt decision.
Verification: cargo test --workspace green (1471 passed, 0 failed);
cargo clippy --workspace --all-targets -D warnings clean. Byte-identity
is pinned by the untouched shell E2E suites (cli_run, measure_ic,
run_measurement, research_docs, run_refuses_unrunnable_blueprint,
tap_recording — zero assertion edits).
Remaining in this cycle: full-workspace c28_layering + shell-content
check, ledger amendments (C28 assembly position, C25/C14 control-surface
consequence, C26 realization note).
refs #295
The A1 cut of #147 item 2, part 2 of 2 (the consumers), completing what
part 1 (the MetricVocabulary substrate) set up:
- aura-campaign's RANKABLE_METRICS hand-copy becomes a re-export of the
backtest vocabulary's roster; the #190 guard test extends to pin the
roster nesting (rankable == vocabulary roster, rankable within
per-member, per-member within the research vocabulary) and the legacy
exposure_sign_flips alias, so any drift fails a test loudly. The
research and per-member rosters themselves stay hand-copies by ratified
isolation (#190 accepted residual) — now oracle-pinned instead of trusted.
- aura-cli gains IcMetrics + IcKey implementing MetricVocabulary: the
second PRODUCTION implementor. The standalone `aura measure ic`
reduction now computes its permutation p via the shared null_draw +
one_sided_p_laplace building blocks (independent draws instead of
chained shuffles — statistically equivalent, deterministic per seed;
emitted p values may differ in the last digits from #290's).
- Acceptance proven at the registry boundary: an engineered
SweepFamily<IcMetrics> deflates through optimize_deflated to a LOW
overfit probability and a noise family to a HIGH one (the permutation
null, dispatched by the vocabulary — the deflation machinery is no
longer baked-in R-only); same seed reproduces the identical
FamilySelection; cross-vocabulary names are refused with the family's
own roster in the prose (the C10 guard). A new cli_run E2E exercises
the legacy alias through the real `aura runs family … rank` boundary.
Deviation from the plan, forced by the compiler: IcMetrics additionally
derives Debug (unwrap_err on the deflation Result needs it; mirrors
RunMetrics).
Verification: full workspace suite green (no FAILED, no compile errors),
clippy -D warnings clean, C18 goldens (cli_run, measure_ic, c28_layering)
green unchanged. A2 — measurement runs as sweep-family citizens — stays
deliberately deferred until a concrete campaign demand exists.
closes#147
Give a measurement run a standalone post-run quality score: the Information
Coefficient (IC), corr(signal_t, forward_return_{t+h}), with a permutation
null model. Before this a measurement run persisted only tap names and series
(MeasurementReport) — inspectable but not rankable or deflatable. IC is the
first metric measurement supplies: the tap-to-scalar reduction the C28
process-column / metric-interface seam anticipates ("a named metric vocabulary
supplied by measurement and backtest instead of baked in R-only").
Surface: `aura measure ic <run> --signal <tap> --price <tap> [--horizon]
[--permutations] [--seed]`. It reads the run's two recorded tap traces, builds
the forward-return series offline over the recorded price spine, aligns
signal -> return by exact timestamp, computes the IC, and prints it with a
one-sided permutation-null overfit probability (Laplace-smoothed, mirroring the
R deflation arm's formula in optimize_deflated).
Placement follows the C28 ladder: the generic pieces — pearson_corr and a
Fisher-Yates permute over SplitMix64 — land in aura-analysis (the domain-free
statistics foundation, beside resample_block); the IC semantics (forward-return
horizon, the offline join) and the CLI verb land in aura-cli (the shell). The
run path (run_measurement), MeasurementReport, the trace store, and the registry
metric vocabulary are untouched — existing `aura run` output stays byte-identical
(the C18 golden run_prints_json_and_exits_zero stays green).
Causality (C2): the forward-return read close_{t+h} is over a completed run's
recorded trace, not an in-graph node. A look-ahead signal is structurally
impossible in a causal run, so the engineered-vs-noise acceptance property is
unit-tested over hand-built offline series (perfect signal -> IC ~= 1, p < 0.05;
a varying but exactly-uncorrelated signal -> IC = 0, not significant;
constant/degenerate -> the (0.0, 1.0) floor), while the E2E validates the CLI
wiring, report well-formedness, determinism, and the error paths over a real run.
This lands the second, structurally distinct null-model computation (permutation,
beside the moving-block bootstrap behind R). That makes the deferred registry
deflation-vocabulary abstraction (#147 item 2) demand-driven rather than
speculative — the next cycle.
Verified: cargo test --workspace green (incl. the new aura-analysis unit tests,
the aura-cli ic_* unit tests, and the measure_ic E2E); cargo clippy --workspace
--all-targets -D warnings clean; the C18 byte-identity golden unchanged.
closes#290
refs #147
C28 phase 2 (Stratification); realizes item 1 of the deferred #147. The
engine's production surface no longer names a backtest-metric type:
- RunReport becomes generic over its metric payload M; sweep/mc/walkforward/
blueprint thread the parameter (SweepPoint<M>, SweepFamily<M>, WindowRun<M>,
WalkForwardResult<M>). RunManifest stays concrete and engine-owned (its
selection: Option<FamilySelection> embeds the foundation-grade analysis type).
- summarize and the MC assembly (McDraw/McFamily/McAggregate/RBootstrap/
r_bootstrap/monte_carlo) move to aura-backtest - McAggregate::from_draws reads
RunMetrics fields by name, so generifying it is the phase-6 metric-vocabulary
abstraction (#147 item 2), still deferred; wholesale relocation is the honest
cut. The concrete instantiation lives in aura-backtest as
`type RunReport = aura_engine::RunReport<RunMetrics>` + sibling aliases.
- the statistics kernel (MetricStats/quantile/resample_block/SplitMix64) moves
to the aura-analysis foundation; the engine re-imports it (inner->foundation,
legal) and re-exports it so existing consumers stay source-compatible.
Dependency inversion in one commit: aura-engine drops aura-backtest from
[dependencies] (back to dev-deps for its SimBroker/RunMetrics test fixtures);
aura-backtest gains aura-engine. Cycle-free for lib targets - the cycle closes
only through the engine's dev-dep edge, the pattern aura-vocabulary already
uses. aura-backtest reaches the kernel transitively through the engine
re-export, so no aura-backtest -> aura-analysis edge exists (the C28 ladder
permits backtest -> {core, engine} only). run_indexed / SplitMix64::next_f64
widened pub(crate) -> pub for cross-crate use.
Consumers (registry/campaign/cli/composites/ingest/bench) rewired by import
path only, no call-site logic changed. The c28_layering structural test extends
to the full ladder: aura-analysis (no aura-* deps), aura-engine ⊆ {core,
analysis}, aura-backtest ⊆ {core, engine}.
Behaviour-preserving: 1448/0 tests, clippy -D warnings clean, serde shapes
byte-identical (C18 - RunReport<M> keeps field order manifest,metrics; the
CLI pre-serialized-splice contract unchanged), moved code traceable via git
rename detection. Cycle-introduced broken intra-doc links fixed.
closes#292
Phase 4 of the Stratification milestone. aura-std held four C28 ladder
layers in one roster; this cuts them into layer-aligned, aura-core-only
node crates so the import direction is enforced by the crate graph:
- aura-std — engine nodes only (arithmetic/logic/rolling + sinks)
- aura-market — session, resample
- aura-strategy — bias, stops, sizer, cost-model machinery
- aura-backtest — sim_broker, position_management
- aura-vocabulary — the relocated closed std_vocabulary roster
Node modules move verbatim (byte-identical renames); consumers are
rewired by import path only. A new structural test
(aura-vocabulary/tests/c28_layering.rs) asserts each node crate's
[dependencies] stay within its C28-permitted inner set, catching the
acyclic-but-outward violation the compiler misses.
Behaviour byte-identical: full workspace suite green (1448 tests), no
golden edited, clippy -D warnings clean. C28 Status block updated.
closes#288
Phase 3 of the Stratification milestone (contract C28). `aura run` now
shape-dispatches on the loaded blueprint: a `bias` output takes the
existing strategy path (wrap_r + R evaluation), byte-identical; a
blueprint that declares taps but exposes no `bias` runs bare via the new
`run_measurement` (bootstrap, run, drain the declared taps) with no
SimBroker, no risk executor, and no per-cycle equity/exposure/r
recorders; a blueprint exposing neither is refused. This gives the
measurement layer its own run verb and removes the O(cycles) recording
retention (the measured ~2 GB peak RSS over 12y UK100 M1) the mandatory
R scaffold imposed on a measurement-shaped run.
A new `MeasurementReport { manifest, taps }` (aura-engine::report, beside
RunReport) is the stdout/record shape — no R metrics, since a bare run
has no broker. The manifest's `broker` carries an interim "measurement"
sentinel; the honest `Option<String>` model is deferred to the #147
metric-genericity block, keeping this phase #147-free and the
strategy-path C18 goldens byte-identical.
`run_signal_r` is untouched (the tap machinery is duplicated into
`run_measurement`, not extracted). The measurement arm's closed-blueprint
guard uses `signal.param_space()` rather than `blueprint_axis_probe`,
which welds wrap_r and would panic on a no-`bias` signal.
refs #286
Declared taps (C27, #282) were authorable only by hand-patching the raw
blueprint envelope's `taps` array, where `node` is a raw interior index —
an index miscount was the expected mistake and the root friction behind the
two `aura run` panics the measurement-milestone fieldtest found.
Add a `tap` op to the construction op-script vocabulary — the exact
output-side twin of `expose`. It names its source wire by the dotted
`"node.field"` identifier the op surface uses everywhere (`split_port` +
`resolve_output_field` + `node_index`), so
`{"op":"tap","from":"fast.value","as":"fast_ma"}` replaces patching
`{"node":14,"field":0}`.
Engine (construction.rs): `Op::Tap`, its `tap()` handler (a verbatim twin
of `expose` — same resolvers, same name-dedup, appending a `Tap` to a new
`taps`/`tap_names` namespace), and `OpError::DuplicateTap` (the twin of
`DuplicateOutput`, stricter than the raw envelope, which enforces no
tap-name uniqueness). `finish()` now threads `.with_taps(self.taps)` into
the built `Composite` — fixing a latent drop #282 left (it built
`.with_gangs` but never `.with_taps`, so op-built composites silently lost
their taps).
CLI (graph_construct.rs): `OpDoc::Tap` (externally tagged, `"as"`-renamed
like `expose`) + the `kind_label` / `From` / `format_op_error` arms (the
last compiler-enumerated by the exhaustive match).
No change to the `Tap` / `FlatTap` / `bind_tap` / `aura run` machinery — the
op-declared tap flows through the existing #282 compile + record path
unchanged.
Verification: 7 new tests green (4 engine: resolve+append, bad-port /
ghost-node refusal, duplicate-name refusal, finish-threads-into-FlatGraph;
1 CLI serde round-trip; 2 CLI E2E over the real `aura graph build` /
`aura run` subprocess — one pinning the exact serialized
`"taps":[{"name":"fast_ma","from":{"node":0,"field":0}}]`, one proving an
op-authored tap round-trips through build -> run -> persisted trace). Full
workspace suite green; clippy clean.
The RED-before-GREEN step was verified by a temporary stash of the
production edits (the `Tap` / `with_taps` machinery pre-existed from #282,
so a fresh RED-only test could not be authored) — the new tests fail
without the edits, pass with them.
Docs (the authoring-guide `tap` row, the ledger op-list `tap` line, and a
worked tap-authoring example) are deliberately out of this commit's scope —
they land in #285, the docs follow-up in this same cycle.
closes#284
Two user-authorable blueprint shapes crashed `aura run` with an internal
.expect instead of the CLI's clean aura: refusal register:
- a tap wire pointing out of range: run_signal_r's compile now handles
CompileError (TapWireOutOfRange) as an aura: refusal + exit 1, naming that
the blueprint does not compile to a runnable harness.
- an exposed output not named bias: a pre-check in dispatch_run (the single
loaded-blueprint call site, before the free-knob axis probe that welds the
hardcoded bias port and would itself panic) refuses with a message telling
the user aura run expects a strategy with a bias output and listing what the
blueprint exposes. The sweep path (run_blueprint_member) is untouched.
Surfaced by the measurement-milestone fieldtest as the sharp edge of raw-index
tap authoring (#284 tracks the name-addressed tap op that removes the miscount
class at the source).
closes#282
Two RED tests pinning that `aura run` refuses cleanly (exit 1 + an aura:
message) rather than panicking on a user-authorable blueprint: a tap wire
pointing out of range (run_signal_r's compile .expect swallows
TapWireOutOfRange into a panic, main.rs:1753) and an exposed output not named
bias (wrap_r's g.build().expect panics on UnknownOutPort from the hardcoded
bias weld, main.rs:1667). Surfaced by the measurement-milestone fieldtest.
refs #282
The payoff: a hand-authored blueprint's declared taps become recorded output
from one `aura run`. run_signal_r binds each tap (hoisted into flat.taps after
wrap_r nests the signal) to a fresh Recorder before bootstrap, drains the
series after the run, and persists each as a ColumnarTrace via the existing
env.trace_store() — the same trace surface the campaign path feeds, so the
existing chart tooling reads them. Duplicate tap names are refused (the
caller-owned DuplicateBind dedup) before anything persists. A tap-free run
writes no trace store — byte-identical to today. Sweep/reduce leaves taps
unbound (run_blueprint_member untouched). Covered end-to-end: a persisted tap
series, the tap-free no-write guard, and the duplicate-name refusal.
refs #282
Second half of the #281 clock-enable set. CumSum is the generic
run-length accumulator with O(1) Neumaier-compensated state — the
branching Neumaier variant, not Sma's plain Kahan, so it stays exact
when a large running total absorbs a tiny sample (the standalone
accumulator has no windowed-magnitude guarantee; module doc explains
why the helper is not reused). When is the clock-enable driver: forwards
value iff gate, else a quiet cycle (eval->None), so every stateful
reducer composes gated unmodified — SMA(When(x,g),N) is the gated SMA.
Rosters CumSum and When (both count pins 31->33) and ships two engine
E2E fixtures: When forwarding/quiet-downstream through the real
GraphBuilder->compile->run seam, and a quiet When leg suppressing a
whole Resample Barrier(0) group (the deliberate stall semantics).
refs #281
First half of the #281 clock-enable set: Sign promotes the private
sign0() semantics to vocabulary (strict comparisons; NaN and -0.0
fall to 0.0), Select is the stateless 2:1 mux that consumes Bool
back into the F64 plane. Both zero-arg, Firing::Any, rostered
(count pins 29→31); an engine E2E drives both through the real
GraphBuilder→compile→run seam.
refs #281
Replace the per-call O(store) scan in find_blueprint_by_identity with a
persistent identity-id -> content-id sidecar index
(blueprint_identity_index.jsonl, sibling of runs.jsonl). Lookups consult
the index first and VERIFY every hit by loading that one blueprint under
the current roster and recomputing its identity id -- the index is a
cache, never an oracle, so results stay scan-identical under roster
drift, store surgery, or index corruption (up to the same-identity-twin
choice, unspecified in both paths and documented on the fn). Any miss or
failed verification runs the old scan as a full-store repair pass: no
early return, best-effort append of every absent-or-different mapping,
last line wins so later repairs heal stale lines. No write-path, engine,
or caller changes; a pre-index store backfills itself on the first miss;
a read-only store keeps scanning as before.
Decided against put-time index maintenance: it would need a roster-free
doc-level identity function whose equivalence to the loaded-composite
path no green test ratifies (the composite path canonicalizes
structurally on load); the repair path is the load-bearing mechanism
either way. Decision log: the issue's comments.
Verification: 7 new aura-registry tests (backfill, verified-hit-no-walk
via a poison-dir probe, one-pass legacy backfill, stale-line healing,
unloadable-under-roster preservation, garbage tolerance) plus a CLI e2e
(campaign validate resolving an identity ref cold then warm); workspace
suite 1346 passed / 0 failed; clippy -D warnings clean; doc build clean.
closes#191
The two fieldtest findings, resolved:
- friction: --parallel-instruments 0 now refuses in domain terms ("must be
a whole number of at least 1 — it bounds how many distinct instruments
are resident in parallel") via a custom clap value-parser, instead of
leaking Rust's NonZeroUsize wording; the zero-bound test pins the prose.
- spec_gap: the authoring guide's exit-code contract now states that a
gate-emptied cell (a std::gate leaving no survivors) is a successful
cell — exit 0 with an informational aura: note and a truncated
realization — and that exit 3 is reserved for cells that could not be
evaluated. Records observed behaviour; no code change.
Gates re-verified: workspace suite green, clippy -D warnings clean.
refs #277
Resolutions for the architect's four drift items (all fix/document, none
ratified away):
- [medium] execute enforced family-name uniqueness only via the CLI's
validate tier: preflight now refuses duplicate campaign instruments
itself (defense in depth for direct callers), RED-first
(execute_refuses_duplicate_instruments).
- [medium] the parallel cell loop's C1 relationship lived only in the
git-ignored spec: C1 gains a realization note (docs/design/INDEX.md)
recording the chunked instrument-major schedule, the structural
residency bound, and the two scheduling-dependent fatal-path carve-outs
(fault attribution among completed cells; already-written family lines)
— both inert and outside the success-path bit-identity.
- [low] the fatal-path orphan-line honesty is part of that note.
- [low] the --parallel-instruments zero-reject acceptance criterion had no
protecting test: campaign_run_rejects_a_zero_parallel_instruments_bound
pins clap's NonZeroUsize usage error (exit 2).
Gates re-verified: workspace suite green, clippy -D warnings clean.
refs #277
The flip: within each K-instrument chunk (the structural residency bound
from the previous commit), all cells run concurrently on rayon's process-
global pool — the same pool the engine's member/window fan-out already
shares, so cells x members x windows feed one work-stealing scheduler and
the per-cell serial seams (ingest transpose, single-threaded bootstrap)
overlap with other cells' work. Results land in index-addressed slots and
the ordered outputs are rebuilt in document order: byte-identical outcomes
across worker counts and bounds (C1).
Fault routing, restructured for a loop that cannot mid-iterate abort:
member/window faults stay #272-contained inside run_cell; non-containable
faults (incl. registry writes — infrastructure, not cell pathology) latch
an atomic abort flag so unstarted cells never run, and after the join the
lowest document-order fault among completed cells propagates as the run's
error. Nothing partial is persisted at run level on the fatal path.
Property pins: determinism across thread counts and bounds (1-thread/K=1
== 8-thread/K=2), distinct live instruments never exceed K (refcounting
stub runner, 8-thread pool), a dead family store is run-fatal with no
partial campaign-run record, member faults stay contained under the
parallel loop. E2E fixtures (previous commit) pin the CLI prose and
document-order persistence across K.
Verified: workspace suite green, clippy -D warnings clean. rayon enters
aura-campaign under the amended C16 per-case policy (comment in
Cargo.toml, mirroring aura-engine).
closes#277
Two pieces of the parallel-cell-loop iteration (spec: parallel-cell-loop),
still sequential — behaviour-identical results, suite green unchanged:
- validate_campaign refuses a campaign document listing the same instrument
twice (DocFault::DuplicateInstrument + CLI prose). A duplicate enumerates
two identical cells and is the one input that could make two cells share a
registry family name; with distinct instruments the per-cell family name
(all four cell axes + stage ordinal) is unique by construction, so the
upcoming parallel appends can never race one name's run-index assignment.
- aura_campaign::execute flattens the 4-nested cell loop into a document-
order-indexed cell list, groups it by instrument ordinal, and walks
sequential chunks of K instrument groups; results land in index-addressed
slots and the ordered outputs (outcomes, realizations, nominee groups) are
rebuilt in document order. K arrives as a new NonZeroUsize parameter
(default DEFAULT_PARALLEL_INSTRUMENTS = 4), exposed as
--parallel-instruments on aura campaign run and threaded through the
run_campaign chain; the dissolved-verb sugar paths pass the default.
The chunk walk is the structural RAM bound for the parallel flip that
follows: no cell of an instrument outside the current chunk can be in
flight at all (chosen over a semaphore gate — blocking inside pool tasks
risks worker starvation; decision log on #277). Verified: workspace suite
green unchanged, clippy -D warnings clean, E2E fixtures pin the CLI prose
and the document-order persistence across K.
refs #277
refs #275
Drift review (architect) over ad4249f..HEAD found the cycle drift-clean at the core
and confirmed what holds: C1 preserved (`run()`'s k-way merge / event loop has zero
diff — `run_bound` only reorders the supply via `bind_sources` then delegates to
`run`, byte-identity pinned); the C4 realization and C23 refinement ledger notes are
accurate and tightly scoped (`SourceSpec.role` is read in exactly one place, sourced
only from the lowering — no leak); and all three binding-carrying production run sites
moved to `run_bound` + `key_supply`, every remaining `.run(` being `#[cfg(test)]`. No
regression harness exists, so the architect is the sole drift gate.
One medium drift item fixed:
- FIX: the CLI's `.expect()` on `run_bound` covered Missing/Extra/Unnamed feed but not
`DuplicateFeed`. The blueprint path does not enforce root-role-name uniqueness (only
the construction op-script path does), so a duplicate-role blueprint would reach
`bind_sources`' `DuplicateFeed` and panic behind the `.expect()` instead of refusing.
Closed at the binding boundary: `resolve_binding` now refuses a duplicate input-role
name — a named `aura:`-register refusal on every CLI run path (single run, sweep, and
campaign all resolve the binding upfront), matching its existing price/close-ambiguity
refusal. The `.expect()` invariant is now genuinely true on all paths. Pinned by
`binding::duplicate_role_name_refuses`.
Two low items:
- `SourceBindError::Display` had no test (its renderer stays dead until a
decoupled-supply caller lands, #124). Added `source_bind_error_display_names_the_role`
pinning the message strings; reachability is deferred by design.
- The reverted fieldtest `SourceSpec` sweep leaves the corpus one more revival break
against the current engine API — carried, as the corpus is pre-existing bit-rot
unrelated to this cycle (documented in 9e30805 and on #275).
Suite: `cargo test --workspace` green (1321 passed, 0 failed); `cargo clippy
--workspace --all-targets -- -D warnings` clean. Spec + plan (git-ignored working
files) discarded.
closes#275
The CLI half of by-name source binding, plus the ledger record.
Every production run site that carries a ResolvedBinding — `run_signal_r`,
`run_blueprint_member` (sweep / reproduction), and the campaign re-run/trace path —
now keys its opened sources by role name via `key_supply(binding, sources)` and
drives the harness through `Harness::run_bound` instead of the positional
`run(sources)`. `key_supply` pairs each opened column with its declared role from
`binding.entries()` — the one place open-order and role-order meet, made explicit so
`bind_sources` verifies the wiring↔supply role match by name rather than by a
maintained canonical-order convention.
Because the current single-binding CLI derives both the `SourceSpec` roles (via
`wrap_r`) and the supply roles (via `key_supply`) from the same `binding.entries()`,
the bind cannot fail on this path — so the call site asserts the invariant with
`.expect`, matching the adjacent `close_handle.expect("ResolvedBinding guarantees a
close entry")` idiom. The named `SourceBindError` refusal path lives in
`bind_sources` for future decoupled-supply callers (independently-built multi-feed /
recorded sources, #124), where a mismatch becomes reachable.
Ledger: a C4 realization note (supply resolved by name into declaration order, so
supply order is no longer load-bearing; the tie-break guarantee is unchanged) and a
scoped C23 refinement (a `SourceSpec.role` is load-bearing for source binding; every
other flat-graph name stays a non-load-bearing raw-index symbol).
The fieldtest-corpus `SourceSpec` sweep (planned Task 5) was reverted: the fieldtest
packages already fail to build against the current engine API for reasons unrelated
to `SourceSpec` (bootstrap arity, removed `InputSpec`, drifted
`Recorder`/`SimBroker`/`RMetrics`, renamed `Scalar` helpers) — pre-existing bit-rot
from earlier cycles. A partial `SourceSpec` migration neither revives nor further
breaks them, so the scope decision to touch them (premised on their being
otherwise-buildable) was withdrawn; reviving the corpus is separate from #275.
Verification: `cargo test --workspace` green (0 failed; the real-data OHLC channel
e2e exercises the migrated `run_bound` path byte-identically); `cargo build
--workspace --all-targets` clean; `cargo clippy --workspace --all-targets -D
warnings` clean.
Drift review (architect) over a55e4cf..HEAD found the cycle substantially
clean — C25 closed vocabulary, C1 determinism/behaviour-preservation, the
additive-serde widening, exhaustive StageBlock matches, and the uniform exit-3
convention all hold. Two drift items resolved:
- RATIFY: the `SilencedPanic` member-boundary panic-hook silencer (added by the
#272 implementer, not named in the spec) is a legitimate mechanism — it keeps
"recorded, campaign continues" observably true on stderr by suppressing the
default crash backtrace around each contained `catch_unwind`. Its mutex
serialises only the ref-count/hook-swap (O(1)), never member computation, so
C1 disjoint-parallel determinism is preserved. Documented in the ledger's
#272 realization paragraph rather than left as undocumented global state.
- FIX: `cell_fault_kind_label` hand-wrote the snake_case strings that must match
`CellFaultKind`'s serde `rename_all` (two sources of truth an aggregate over
campaign_runs.jsonl could silently diverge from). Pinned with a test asserting
each label equals the serialized form; the efficient `&'static str` stays.
(The #272 commit's own concern-driven doc fixes and the wf panic-containment
test landed in d3b1a1a; this commit carries only the two audit-phase items.)
Suite: cargo test --workspace green (1311 tests, 0 failed); clippy clean.
closes#272
A member fault (no-data, bind, run, or a caught panic) is now a recorded
per-cell outcome instead of aborting the whole campaign and discarding every
already-computed cell. The incident that motivated this (a 22-instrument
campaign lost ~36 healthy cells ~6.7 min in because Copper had an archive gap)
now completes: the healthy cells persist, the gap cell is recorded as failed,
and the run exits 3.
Direction (owner decision 2026-07-14): run to completion and report
compromised results; no coverage preflight, no window synthesis.
Containment granularity:
- The CELL for a sweep-stage member fault (a grid hole structurally
compromises winner selection, so the whole cell fails).
- The FOLD for a walk_forward member fault (independent time windows): the
surviving folds pool into the family, failed folds are recorded as
StageRealization.window_faults, and the summary names the ratio.
- aura-registry: additive CellFault / CellFaultKind (closed:
no_data|bind|run|panic|window) / WindowFault / CellCoverage, plus
fault/coverage fields on CellRealization and window_faults on
StageRealization — all serde-default-skipped, so pre-#272 campaign_runs
lines parse and round-trip byte-identical.
- aura-campaign: run_cell returns a fault-annotated CellRealization instead of
Err (execute's accumulate-then-append-once tail is unchanged and now
persists every healthy cell + the one run record); a `contain` split keeps
ExecFault::Registry and doc-shape preflight faults global while Member/Window
become per-cell/per-fold. Member panics are caught with
catch_unwind(AssertUnwindSafe) at all three member-run sites (sweep IS/OOS)
and recorded as MemberFault::Panic — a member panic no longer aborts the
process. The wf stage partitions Registry faults (global) from Member/Window
(per-fold) and filters faulted-fold placeholders (the
"faulted-member-placeholder" broker sentinel) out of the persisted family.
- aura-cli: exec_fault_prose gains the Panic arm; CliMemberRunner::window_coverage
derives effective bounds + interior gap months from the #264 archive
primitives; present_campaign prints per-cell failure notes + a completion
summary and threads the failed-cell count; a run with >=1 failed cell exits 3
("completed with failed cells") uniformly across `aura campaign run` and the
dissolved sweep/walkforward/mc/generalize verbs (exit_on_campaign_result).
Usage stays 2, refused-before-running stays 1, clean stays 0.
Tests: the global-abort pins flip to containment (execute + the two wf fault
tests → fold-containment + all-folds-fail-the-cell); new panic-containment
tests on both the sweep path (PanicRunner) and the wf path (this commit adds
the wf mirror the loop left uncovered); a new gapped-archive e2e (one covered
cell + one gap cell → exit 3); the ~14 CLI exit-1 pins move to the exit-3
register; a pre-#272-line byte-identical round-trip guard.
Suite: cargo test --workspace green (1309 tests, 0 failed); clippy clean.
Decision log: #272 comments (fork rationale, the fold Registry/Member split,
the placeholder sentinel, uniform exit-3).
Follow-up (minor, not blocking): the plan under-scoped Task 1 to aura-registry
though the additive fields also touch aura-campaign's exec.rs literals — the
loop absorbed it mechanically; a future plan for a cross-crate additive-field
change should scope every crate's construction sites in the first task.
closes#256
Fork B (owner decision 2026-07-14): the dissolved walkforward/mc
translations' leading sweep executed the full grid over the whole campaign
window and persisted a Sweep family, yet only the enumerated parameter
points ever crossed the stage seam (the wf stage re-sweeps them per IS
window itself). The leading stage is now the fieldless vocabulary block
std::grid: it enumerates, executes nothing, persists nothing.
- aura-research: StageBlock::Grid ({"block":"std::grid"}), schema-strict
parse arm (empty slot list: every key but "block" is refused by the
generic unknown-slot check), PROCESS_BLOCKS entry, intrinsic-tier no-op
arm; the vocabulary test's non-empty-slots guard carries a pinned
std::grid-only exception (a nominal slot would misdescribe the
vocabulary to describe_block consumers).
- aura-campaign: the inter-stage seam is a typed two-armed StageFlow
(points-only vs executed members); gate / mc-per-survivor fence the
points-only arm with defensive PipelineShape faults; preflight admits
std::grid only as the first stage and only immediately before
std::walk_forward (every other neighbor consumes executed reports).
- aura-cli: translate_walkforward / translate_mc lead with
StageBlock::Grid; the two family-shape E2E pins flip to zero Sweep
families; translate_generalize keeps its executed sweep(argmax) — its
generalize stage consumes the argmax winner report as the cell nominee.
- docs: dated #256 amendment in the ledger's verb-dissolution narrative;
the authoring-guide vocabulary transcript gains the std::grid line.
Behaviour preservation: the exact-grade real-data pins
(walkforward_real_e2e_pins_the_exact_current_grade,
mc_r_bootstrap_real_e2e_pins_the_exact_current_grade) pass unmodified —
survivor points reach the wf stage in the same odometer order as before.
Measured (the #256 acceptance measurement; debug build, real GER40 2025,
2x2 grid, `aura walkforward --real`, 3 runs each):
before (a55e4cf): 6.27 / 6.18 / 6.18 s
after: 4.52 / 4.49 / 4.45 s (~ -27% wall clock)
Suite: cargo test --workspace green (0 failed); clippy clean.
Decision log: #256 comments (fork rationale incl. the StageFlow seam and
the slot-guard exception).
The second risk-regime variant vol_tf{period_minutes, length, k}
computes the vol estimator over completed time buckets: an H1 signal
gets an H1-matched stop in the research matrix instead of a hand-scaled
minute stop (the issue's k-inflation workaround retires). Additive
externally-tagged serde (stored Vol documents keep their content ids);
the executor arm wires the VolTfStop primitive via the builder+bind
chain; validate checks period_minutes/length/k positivity per regime.
The member-manifest round-trip holds for both variants (C1): vol_tf
members stamp stop_period_minutes/stop_length/stop_k and the reproduce
path re-derives the VolTf stop whenever stop_period_minutes is present
— never a silent default-Vol fallback. The Regimes slot label and the
unit notes name the new variant (period_minutes IS the stop's
timescale); glossary, authoring guide, and design ledger record the
second variant. Default regime and sugar paths byte-untouched.
Coverage: node units (rollover-only emission, bucket-delta math, the
constant-|delta| correspondence with the per-cycle regime), executor
fold, serde/validate units, the manifest round-trip unit, and a
hostless two-regime e2e (distinct ordinals; vol_tf members stamp their
timescale, vol members do not).
closes#262
Each cost component's knob (cost_per_trade / slip_vol_mult /
carry_per_cycle) now accepts one number for every cell — today's form,
byte-identical on the wire so stored documents keep their content ids
(CostValue's custom serde, the Axis precedent) — or an instrument-keyed
map resolved per cell at member construction. A mixed-scale matrix
(GER40 ~2e4, EURUSD ~1.1) keeps consistent cost units in ONE document:
the N-copies workaround retires and generalize-under-constant-costs
becomes expressible again.
Validation is strict both ways at the intrinsic tier: a map missing a
campaign instrument, or naming one the campaign does not list, refuses
at validate with prose naming the component and instruments (no silent
default — a partial map would reproduce exactly the silent unit
inconsistency the issue reports). The instrument threads through
cost_knob/cost_nodes_for/run_blueprint_member from the cell (run side
and persist re-run side receive the same value — the C1 drift-alarm
lockstep); the manifest stamps the resolved per-cell value under the
unchanged cost[k].<knob> key, and the sugar/reproduce paths pass an
inert instrument (their specs are scalar by construction). Fork
decisions and the discarded first attempt: issue #260 comments.
New coverage: serde round-trips (map form; scalar stays bare), validate
cross-check units, both prose directions pinned, and two hostless e2es
over the synthetic SYMA+SYMB archive — per-cell resolution visible in
the member manifests, and the validate refusal.
closes#260
A Frankfurt-baked zero-arg Session preset (open 09:00 Europe/Berlin as
literals) joins the std vocabulary as SessionFrankfurt, declaring
period_minutes: I64 (default 15) as its one scalar knob — the
single-knob roster pattern the cost nodes established. A data-only
blueprint can now reach a session/time-of-day stream (bars_since_open),
unblocking time-of-day conditioners for the confirm/refute research
model. Roster count pins bump 28 -> 29 in both conscious-act sites.
Fork decision on the issue: the preset variant over a param-vocabulary
extension — a timezone param would need a string kind, breaching the
four-scalar discipline; the preset keeps the vocabulary closed and is
the additive extension the roster-exclusion comment anticipates. The
4-arg Session::builder stays untouched; in_session/session_open_ts
stay deferred (#154).
closes#261
Cycle-close audit over the /boss batch 84e1075..bdafbde (#258#259#264#266#247#265#269). Architect verdict: substance clean — C1
byte-identity (conduit serde-skipped, pinned floats verbatim, uncosted
bit-identical), C10 one-home-for-cost (all four conduit consumers read
the already-netted series), C18 (conduit off the wire, --version and
manifest single-sourced) all hold. Two medium debt items, both fixed
here rather than carried:
- render_bind_error's catch-all Debug-framed the remaining BindError
variants, making #269's no-identifier-on-stderr claim argv-gated
rather than structural. The match is now exhaustive: DuplicateBinding,
EmptyAxis, EmptyRange render as axis-usage prose; a Compile fault — a
blueprint defect, not a usage error — renders as a prose frame with
the compile detail explicitly labelled internal (per-variant
CompileError prose belongs to the graph-build surface, not this
boundary). Unit tests pin the arms.
- The #265 knob-unit notes covered constant and vol_slippage but not
carry.carry_per_cycle, which carries the identical price-unit-vs-R
trap; the note set and its pins now cover all three cost components.
No regression scripts are configured (the architect is the sole gate);
full workspace suite green, clippy clean. Spec and plan working files
discarded per the audit lifecycle.
refs #265 refs #269
render_bind_error's catch-all Debug-framed UnknownKnob although its
payload is already a fully-prosed message; the variant now renders the
payload alone. The two tests that pinned the frame verbatim repoint to
the prose fragments. Exit 2 unchanged. Completes the #247 defect
family: no Rust identifier reaches a user's stderr on either verb path.
closes#269
The rejection must reach stderr as the prose payload alone — no
UnknownKnob(..) Debug frame around it. Fails on today's frame; the
prose fragments already pass, so the sole pinned defect is the frame.
refs #269
campaign introspect --unwired now annotates the four silently
misreadable knobs with unit/semantics sub-lines: cost_per_trade is a
price-unit numerator charged as cost/|entry−stop| in R (not a cost in
R); slip_vol_mult multiplies the per-cycle vol estimate; vol.length
smooths the estimator and does not set the stop's timescale; vol.k
scales the stop distance (the risk-unit lever). Rendered additively
via a new OpenSlot.notes field, so the sibling tests pinning the slot
hint lines verbatim stay green byte-identically.
The glossary's cost-model and risk-regime entries mirror the same
semantics inline (two-sentence convention kept), the risk entry
pointing at #262 for the timescale-matched variant.
closes#265
The four silently-misreadable cost/risk knobs (cost_per_trade,
slip_vol_mult, vol.length, vol.k) must carry one-line unit/semantics
annotations in campaign introspect --unwired — blocking the price-vs-R
and length-as-timescale misreadings that produce no error today.
refs #265
The blueprint-sweep terminal rendered BindError via {:?}, leaking
KindMismatch { .. } and MissingKnob(..) Rust Debug structs where the
sibling diagnostic on the same verb ships prose. Both now render in
that register — the axis name, the expected-vs-supplied kinds resp.
the unbound knob, and the --list-axes pointer; exit stays 2.
Scoped out (same defect family, different verb path): the walkforward
path's UnknownKnob wrapper still prints its Debug-ish frame around an
already-prose payload and is pinned verbatim by two existing tests;
filed as a follow-up rather than widened into this slice.
closes#247
Both blueprint-sweep bind faults (wrong-kind axis value; an open param
no axis covers) must print prose in the unknown-axis sibling's register
— axis name, kind/knob specifics, a --list-axes pointer — never the raw
Debug structs KindMismatch{..}/MissingKnob(..). Exit stays 2. Fails on
the Debug leakage present today, not on scaffolding.
refs #247
aura --version now prints 'aura 0.1.0 (<commit>)', the commit sourced
from the same AURA_COMMIT build provenance the run manifest stamps
(option_env! at build time, 'unknown' fallback, dirty suffix included)
— a pre-run freshness probe: a stale binary is now distinguishable from
an engine bug at the CLI surface, and research docs pinning
'Engine: aura @ <sha>' can verify the binary they invoke.
The version string is memoized as a OnceLock-leaked &'static str:
clap 4.6's builder Str accepts only &'static str (no From<String>), so
the owned-String alternative a quality pass suggested does not build —
verified, held with evidence in the loop report.
closes#266
aura --version must print 'aura 0.1.0 (<commit>)', the commit sourced
from the same AURA_COMMIT build provenance the run manifest stamps —
a pre-run freshness probe distinguishing a stale binary from an engine
bug. Fails on the absent parenthesized commit, not on scaffolding.
refs #266