977 Commits

Author SHA1 Message Date
claude 3932333e19 feat(cli): aura new scaffolds the data-only tier (#241 T4)
- NewCmd drops --engine-path/--namespace (they belong to the node-crate
  scaffold); dispatch_new emits Aura.toml + .gitignore + two std-only
  starter blueprints + CLAUDE.md, git init, no crate, no build step.
- Two starter blueprints because run requires a closed blueprint while
  sweep requires open knobs: signal.json (closed, run target) and
  signal_open.json (fast.length free) — the scaffolded CLAUDE.md
  quickstart lines are both verified live.
- The crate templates survive untouched for the attach verb; old
  scaffold()/scaffold_spec() carry a dead-code bridge until it lands.
- project_new.rs realigned: data-only file set, no-build run headline
  (stdout report, C1 byte-identity, commit-only provenance, negative
  runs/ assert), advertised-sweep headline (#218 gate passes, store
  lands project-local).

refs #241
2026-07-12 16:07:43 +02:00
claude a41705b1c2 feat(cli): three-way unresolved-namespace hint (#241 T3)
A project-namespaced (::) unknown type id now hints by tier: outside
any project the existing "no Aura.toml was found" text stays
byte-identical; inside a data-only project the hint names the missing
node crate and the attach verb (aura nodes new); a loaded node crate
gets no hint (the vocabulary error carries the message).

refs #241
2026-07-12 15:05:56 +02:00
claude 1f6b55a8e1 feat(cli,engine): data-only project tier at the load boundary (#241 T1-T2)
- ProjectProvenance namespace/dylib_sha256 widen to Option (Tier-1
  additive serde): a data-only run stamps commit-only provenance; old
  string-field manifests keep parsing.
- The load boundary tier-selects: a [nodes] pointer list in Aura.toml
  (one entry honored; MultiCrate / pointer-context refusals) loads the
  pointed-at crate; a root Cargo.toml stays the pre-#241 native project,
  unchanged; neither means data-only (std vocabulary, project-local
  runs/, both-tier provenance).
- ProjectEnv splits into directory facts + Option<NativeEnv>; the crate
  pipeline (metadata -> artifact -> staleness -> ABI -> charter)
  relocates byte-identical into load_crate.
- e2e fixtures: dataonly-project, multicrate-project,
  nested-nodes-project (a pointer crate resolves and stamps its
  namespace end to end).

refs #241
2026-07-12 14:42:57 +02:00
Brummel 43a427bfed audit: run-tail close — ledger and glossary reconciled to the shipped state
The tail audit over d1e01ef..62f6592 found no code drift against the
contracts (C18 name-resolution read-only, C23 doc identity-blind and
inline-dissolved, C1 fit deterministic) but four lagging records, fixed
here: the #106 real-roller amendment gains the #239 fit-to-window note
(fixed sizes are a ceiling on the sugar path); a #125 realization note
records the composite doc as a C23 debug symbol with its serde/identity/
viewer treatment; the glossary identity-id and composite entries name
the doc; GraphSession::finish states the deliberate absence of a
doc-carrying op-script surface (refs #125) instead of leaving the
authoring asymmetry silent.
2026-07-11 20:25:09 +02:00
Brummel 62f6592ef6 feat(cli): surface the composite doc in the graph viewer (#125)
The viewer threads the model's doc through normalizeModel into both
composite view states — appended to INFO.B (collapsed box) and INFO.C
(expanded cluster frame) — and shows the root composite's doc as a muted
header line (#rootdoc span in GRAPH_HEAD, empty when absent; the DOM
population sits in the browser-only block, the same boundary as the
breadcrumb). viewer_tooltip.mjs pins the doc'd cases beside the
byte-exact un-doc'd pins; sample-model.json gains the additive doc key;
e2e drives aura graph over a nested-doc blueprint into the emitted page.

closes #125
2026-07-11 20:18:32 +02:00
Brummel 953d04a774 feat(engine): composite doc field — authoring, serde, identity, model emit (refs #125)
Composite gains doc: Option<String> (the prose twin of name, a C23 debug
symbol): with_doc/doc() on Composite, a doc knob on GraphBuilder, a
Tier-1 additive-optional CompositeData field (no format-version bump;
absent-field documents keep their exact bytes), identity-stripped like
the name. The graph model emits an optional trailing doc fragment in
both scopes; json_str is hardened for free text (\n/\t/\r named,
control chars as \u00XX — the doc is the first multi-line value through
it). e2e: a hand-authored doc'd blueprint renders with the doc embedded;
the doc moves the content id but never the identity id.

refs #125
2026-07-11 19:37:45 +02:00
Brummel c8b5acf6f0 test(cli): pin the viewer tooltip half — INFO maps and md() (refs #125)
The headless viewer suite covered only the DOT half (normalizeModel/
genDot); the tooltip half — INFO.B (collapsed), INFO.C (expanded
cluster), INFO.P (ports), and md()'s markdown-to-HTML step — was
asserted by no test. viewer_tooltip.mjs pins the current shapes;
graph-viewer.js only hoists md() above the browser guard and exports
it (location-only move, no behaviour change), so the pin can drive the
real conversion headlessly. Surfaced by the grounding-check on the #125
composite-doc spec: its viewer assumptions had no current-behaviour pin.

refs #125
2026-07-11 18:30:58 +02:00
Brummel 2532e8fcc3 test(cli): extract the shared demo-project fixture helpers into tests/common (#223)
built_project/project_lock/ScratchPath/ScratchGuard move from their three
per-binary copies (cli_run.rs, research_docs.rs, project_load.rs's
built_fixture near-copy) into tests/common/mod.rs; project_load's one
shared-store-mutating test now takes the lock it previously lacked. The
module doc states the boundary plainly: the Mutex serializes threads
within one process only — process-parallel runners still race on the
shared fixture store (documented, dormant under cargo test's sequential
binaries; see the issue thread for the observed process-parallel repro).

closes #223
2026-07-11 18:18:27 +02:00
Brummel 18f8e72946 feat(cli): chart resolves a --trace family by its chosen campaign name (#238)
The NotFound arm of emit_chart now queries the recorded campaign runs:
records with a persisted trace_name whose stored campaign document
carries name == ARG. A unique match re-dispatches the existing charting
path onto the resolved handle; several matches refuse listing the
candidate handles in append order (refuse-don't-guess — re-running a
named invocation legitimately yields {campaign8}-0, -1, ...); zero
matches keep the not-found message verbatim. The exact trace-store
handle always wins; the name never becomes a store key (C18 untouched).
Guide §3 and the glossary tap entry document the name alternative.

closes #238
2026-07-11 18:04:09 +02:00
Brummel c70b4cc2d2 test(cli): RED — chart cannot resolve a --trace family by its chosen name (#238)
--trace <NAME> lands the name in the generated campaign document, but no
lookup surface consumes it: the trace store knows only the content-derived
handle {campaign8}-{run}, so the user cannot address their own family by
the name they chose.

refs #238
2026-07-11 17:20:14 +02:00
Brummel 2a63c5d889 feat(cli): fit the injected walk-forward roller to a short real window (#239)
fit_wf_ms_sizes (pure, unit-tested) passes the fixed 90/30/30-day roller
through byte-identically whenever it fits the resolved campaign window,
and scales it down preserving the 3:1 IS:OOS ratio (step = OOS, one roll
at the minimum) when the window is shorter — so mc/walkforward --real
over a ~30-day window run to completion instead of dead-ending at the
executor with a remedy unreachable from the verb surface. Both dispatch
sites consume the fit; the executor's own check stays for authored
process documents. Verb help documents the fit.

closes #239
2026-07-11 17:14:51 +02:00
Brummel a081c14532 test(cli): RED — mc --real refuses a short window instead of fitting the roller (#239)
The dissolved mc/walkforward dispatches stamp the fixed 90/30/30-day
roller into the generated process document regardless of the resolved
campaign window; a window under 120 days dead-ends at the executor with
no remedy reachable from the verb surface.

refs #239
2026-07-11 16:24:33 +02:00
Brummel 2be6cf8860 feat(cli): dissolved --trace requests only producible taps (#240)
persist_taps_from now filters the tap vocabulary through campaign_run's
own tap_channel classification (exposed pub(crate)) and drops the net
channel: every generated sugar campaign carries an empty cost section by
construction, so net_r_equity was unproducible there and the executor's
skip notice named a remedy (add a cost block to the campaign document)
unreachable from the sweep/walkforward verb. The authored-campaign path
keeps the notice verbatim — there the user wrote the document and the
remedy is reachable. The two full-vocabulary pins move to the producible
subset (deliberate pin move, documented in their doc comments).

closes #240
2026-07-11 16:16:04 +02:00
Brummel 955c909769 test(cli): RED — dissolved --trace requests the unproducible net tap (#240)
The generated sugar campaigns carry an empty cost section by construction,
so net_r_equity can never be produced on the sweep/walkforward --trace path;
requesting it makes the executor print a remedy (add a cost block to the
campaign document) unreachable from the invoked verb.

refs #240
2026-07-11 15:44:05 +02:00
Brummel d1e01efebf audit: fieldtest-tail close — C22 --trace ledger notes reconciled
Tail audit (aaca18c..f591164) over #213, the milestone-fieldtest
fixtures, and the B1 chart fix: #213's intersection semantics conflict
with no ledger contract, the depth-2 trace resolution preserves C1
ordering read-side with the layout unchanged, and the new §0/§3/
glossary docs match the code vocabularies. One HIGH drift item
resolved: the two C22 --trace amendment notes still described the #168
refusal surface and deferred per-member trace-writing to #224 — both
now carry a delivered-2026-07-11 annotation (real-data --trace writes
the depth-2 fan-out, chart resolves it; synthetic still refuses).
Accepted LOW debt, recorded in-code: the disjoint-archive generalize
refusal has no e2e fixture (unreachable on this host's archives), the
pure helper is unit-tested.
2026-07-11 15:12:59 +02:00
Brummel f5911645f1 docs: ratify the milestone-fieldtest doc findings
- glossary "tap" + authoring-guide §3: the chart handle of a
  sweep/walkforward --trace family is the printed family handle
  (members keyed <cell>/<member>) — the campaign trace_name analog was
  documented, the sweep case was not (fieldtest spec_gap).
- authoring-guide §0: state the input window's financial-style ordering
  (index 0 = newest, index k = k cycles back) where the node pattern is
  taught — previously only in the design ledger's RingBuffer note.
- authoring-guide §3: the #234 cost block joins the open-slot list, the
  worked campaign example, and the prose (charge modes, net-vs-gross
  default, manifest stamping) — it was discoverable only via
  campaign introspect --unwired.

Fieldtest fixtures: 09da04f.
2026-07-11 15:05:31 +02:00
Brummel 5616aa6c6a fix(registry,cli): chart resolves the depth-2 sweep --trace family layout
TraceStore::name_kind and read_family resolved taps exactly one
directory below the family handle, so the per-member fan-out layout
sweep/walkforward --trace writes since #224
(<name>/<cell>/<member>/index.json) classified as NotFound and
`aura chart <printed handle>` exited 1. Both sides now resolve depth-1
(campaign nominee layout) and depth-2, with depth-2 members keyed
<cell>/<member> (deterministic sort, C1); the on-disk layout is
unchanged. emit_chart's not-found message no longer suggests re-running
with the unknown handle as --trace (the data-creating command cannot
take an output handle as input); the message prefix stays pinned.
Milestone-fieldtest finding B1 (fixtures: 09da04f); verified against
the fieldtest's own on-disk family and the campaign depth-1 sibling.
2026-07-11 15:05:31 +02:00
Brummel abb6fbaa87 test(cli): RED — chart cannot open a sweep --trace family by its printed handle
Milestone-fieldtest finding B1: TraceStore::name_kind/read_family
resolve taps exactly one directory below the handle, so the depth-2
per-member layout sweep/walkforward --trace writes (#224,
<name>/<cell>/<member>/index.json) classifies as NotFound and
`aura chart <handle>` exits 1 — with a remedy string that suggests
re-running the very command that produced the data. The new autonomous
test fabricates the depth-2 family via TraceStore and pins: chart on
the printed family handle exits 0 and emits the HTML.

refs milestone fieldtest 09da04f
2026-07-11 14:27:28 +02:00
Brummel 09da04fe18 fieldtest: milestone-33 — 4 examples, 11 findings
Milestone fieldtest for "Real-project readiness — role-complete
research loop (no freeze)": a from-scratch ger40-lab project (aura new)
with a brand-new native node (ger40_lab::Momentum) driven through the
full research loop on real GER40/FRA40 archive data — sweep,
walk-forward, mc, process+campaign with 2-regime risk and net-R cost
block, cross-instrument generalize, bit-identical reproduce, charts —
from the public interface only. Spine holds: 5 working, 1 bug (chart
cannot open a sweep --trace family by its printed handle), 4 friction,
1 spec gap. Findings triaged separately.
2026-07-11 14:20:48 +02:00
Brummel d3ad9f4e73 feat(cli): generalize no-window fallback resolves the intersection of all symbols' windows
The no---from/--to fallback in dispatch_generalize probes EACH listed
symbol's full archive window (same partial bounds threaded through) and
resolves the one shared campaign window as their intersection — latest
start, earliest end — instead of symbols[0]'s window alone, so the
cross-instrument floor is measured over a genuinely common period.
Disjoint archives refuse at runtime (exit 1) naming each symbol's span.
The intersect-or-refuse arithmetic lives in the pure
intersect_shared_window helper (unit-tested: single symbol, overlapping,
disjoint); the explicit --from+--to path stays byte-identical. The
superseded shape test is renamed to
generalize_without_explicit_window_resolves_a_shared_window_and_completes
with its doc comment reworded to the intersection contract.

closes #213
2026-07-11 14:02:41 +02:00
Brummel bb8c01895b test(cli): RED — no-window generalize resolves symbols[0], not the intersection
The new archive-gated e2e pins the #213 contract: a multi-symbol
generalize without --from/--to must resolve the shared campaign window
as the intersection (latest start, earliest end) of the listed symbols'
full archive windows. On the current tree it resolves symbols[0]'s full
window (AAPL.US listed first keeps AAPL's wider span on both bounds), so
the test fails RED. Expected windows are derived from the live archive
via single-symbol sweep probes, never hard-coded.

refs #213
2026-07-11 13:14:03 +02:00
Brummel aaca18c6f6 audit: net-r cycle close — ledger reconciled, drift items resolved
Architect review over dd23ea3..HEAD (the #234/#152 cycle, 6 commits).

What holds (architect-confirmed): CostSpec is a closed, deny-unknown-
fields vocabulary with typed slots mirroring RiskRegime, field names
conforming to the builders' ParamSpec names; cost-less docs hash
byte-identically (C18); cost threads through the single MemberRunner
seam so every campaign stage nets uniformly; costed families reproduce
bit-identically incl. Carry; the #221-deleted leg is rebuilt optional
(net = gross under the empty model); both cost_graph leaks replaced by
the interned single source (#152).

Resolved this close: [high] the C10 realization notes contradicted
shipped reality — the two 'wired on the run path via --cost-*' claims
carry superseded-annotations, the 0084 carried-debt note a discharged-
annotation, and a new cycle-net-r realization documents the campaign
cost block, the net-by-default decision, the manifest/reproduce
round-trip, and the returned net_r_equity tap; [medium] two dangling
persist_traces_r docstring references reworded to what exists;
[medium] the one-knob-per-cost-node invariant behind the manifest
round-trip is now unit-pinned (exactly one distinctly-named knob per
shipped builder).

Cycle spec and plan (git-ignored working files) discarded per
convention.

refs #234
2026-07-11 12:28:15 +02:00
Brummel c32c01a63c test(cli): archive-gated e2e — a costed campaign nets members and reproduces (#234 task 7)
The closing proof: a GER40 campaign with a constant+vol_slippage cost
block runs to exit 0; every member's net_expectancy_r < expectancy_r
(costs bite), the same doc without the cost block reproduces today's
exact gross numbers (golden parity), net_r_equity.json persists on
request, and the costed family reproduces bit-identically. One plan
literal corrected in place: the member-line filter exact-matches the
line shape so the campaign_run summary record (which nests a family_id)
no longer false-matches.

Verified: e2e green on the real archive, full workspace suite green,
clippy -D warnings clean.

closes #234
2026-07-11 12:16:57 +02:00
Brummel da7d54774b feat(cli): TapChannel::Net — the net_r_equity tap routes and persists (#234 task 6)
TapChannel gains Net; tap_channel routes the full closed vocabulary
(producibility moved to the caller); the trace path drains the sixth
channel and persists the cost-adjusted net curve. A cost-less document
requesting net_r_equity keeps a skip notice, reworded to name the
remedy (add a cost block) — not a refusal. Two deliberate pin moves:
the absence pin re-pins to routed, the skip-notice e2e re-pins to the
reworded form. Post-review: positive write-path e2e added (a costed
GER40 campaign lands net_r_equity.json with the curve differing from
r_equity by the accrued costs).

Verified: routing pin + positive e2e green (real archive), full
workspace suite green, clippy -D warnings clean.

refs #234
2026-07-11 11:46:22 +02:00
Brummel bb1f6c8bdd feat(cli): campaign cost models reach the member run and both re-run sides (#234 tasks 4-5)
cost_nodes_for (beside stop_rule_for_regime) is the one CostSpec ->
bound-builder binding — every component fully bound, so the wrapped
param space stays cost-invariant. run_blueprint_member joins the cost
rows into summarize_r (member metrics genuinely net) and stamps the
cost components into the manifest params; CliMemberRunner threads the
doc's cost. The re-run sides re-derive the model from the manifest:
reproduce via cost_specs_from_params (the #233 stop pattern — a costed
family reproduces bit-identically) and persist_campaign_traces binds
the same model so the C1 drift alarm compares like with like.
Hand-computed pin: a constant cost model yields
net_expectancy_r == expectancy_r - cost_per_trade/|entry-stop| on a
synthetic member.

Verified: full workspace suite green, clippy -D warnings clean; in-loop
spec + quality gates passed per task.

refs #234
2026-07-11 10:41:16 +02:00
Brummel a1afc2fd02 refactor(cli): wrap_r's optional cost leg — behaviour-preserving rebuild (#234 task 3)
wrap_r accepts an optional CostLeg (cost node builders + the tx_cost/
tx_net channels); non-empty rebuilds the #221-deleted wiring — the
cost_graph fed by the executor's four geometry outputs, the vol-proxy
RollingMax/RollingMin/Sub (back from cfg(test) to production) feeding
each vol_slippage component, the interned cost[k].* names, a gated
4-col cost recorder in reduce mode (the summarize_r join input), and
the LinComb(4) net_r_equity curve in !reduce. All callers pass None —
behaviour-preserving; the entire suite green unchanged is the gate.
Co-temporality of cost rows with R rows unit-pinned.

Verified: full workspace suite green unchanged, clippy -D warnings
clean; in-loop spec compliant + quality approved (one held Minor: the
CostLeg doc's forward reference to cost_nodes_for, which task 4 lands).

refs #234
2026-07-11 08:33:22 +02:00
Brummel bdcf0452f4 feat(research,cli): the CostSpec campaign vocabulary (#234 task 2)
CampaignDoc gains an additive cost: Vec<CostSpec> block — a closed,
externally tagged vocabulary over the three shipped cost nodes
(constant/vol_slippage/carry, field names = the builders' ParamSpec
names), mirroring the risk block: serde default + skip-if-empty (cost-
less documents keep their content ids, pinned), DocFault::BadCost
validation (finite/bounds per component), std::cost construct-
vocabulary block + SlotKind::Costs + open-slot mirror, prose arm, and
all CampaignDoc struct literals across crates carry cost: vec![]
(compile-driven).

Verified: aura-research suite green, campaign validate e2e green, full
workspace suite green, clippy -D warnings clean; in-loop spec review
compliant and quality review approved (real diff fingerprint — the
worktree-anchored review prompt works).

refs #234
2026-07-11 07:24:15 +02:00
Brummel 249aafdb0f refactor(std,composites): interned cost[k].* port names — the leaks drop (#152)
One process-global intern pool (intern_port/cost_port, the COL_PORTS
LazyLock pattern) in the cost module is now the single source of the
cost[k].<port>/cost[k].<field> names; CostSum::builder and cost_graph
consume it and the per-build .leak()s in cost_graph are gone — the
prerequisite before cost is rebuilt per member across a sweep. Two
unit pins (same pointer for the same pair, dedup for bare names).

Verified: aura-std 166/0, cost_graph 4/4, zero .leak() in the two
sites, full workspace suite green, clippy -D warnings clean.

closes #152
refs #234
2026-07-11 06:56:21 +02:00
Brummel dd23ea34a5 test(cli): e2e evidence pin — a project node's open param through sweep and campaign
The defining loop of a genuine external project, proven end-to-end and
green on arrival: a fresh aura-new project authors an OPEN blueprint
over its own node (knob_lab::Scale, factor unbound); --list-axes shows
the axis, a real-data GER40 sweep over it yields two members whose
manifests carry the project-node binding, the family persists and
reproduces 2/2 bit-identically, and one campaign cell over the same
blueprint runs to exit 0 linking a sweep family. No implementation was
needed — the loop already worked; this pin is the previously-missing
evidence (the deepest exercised path before was a single param-less
aura run). The axis-discovery leg is un-gated so the test stays
meaningful on a data-less host.

closes #235
2026-07-11 05:28:36 +02:00
Brummel 80f324c206 audit: real-project-readiness run close — drift-clean, one low debt noted
Architect review over 3016bd6..HEAD (15 commits: #232 #233 #226 #236
#237 #224 #228 and the five-slice #231 cycle incl. ledger C26).

What holds (architect-confirmed by diff read): C3/C4 one-merge with the
canonical column order shared between opener and role declaration
(hand-computed merge-order pin); C2 causality in the r_channel example
(Delay(1), prior-bars channel); C10/C1 — the pip fix restored label
truthfulness with R metrics byte-identical, the trace re-run drift
alarm compares at the resolved pip; C24/C25/C26 — blueprints carry
their input contract via role names, data.bindings serde-additive with
content ids byte-stable, two-tier validation, Blockly-litmus-clean;
lockstep pins all moved in pairs (vocabulary 23->28 both sites,
research/CLI vocabulary cross-pin).

Debt (low, carried, no issue filed): wrap_r's Box::leak of
override-renamed non-vocabulary role names — bounded per graph build
and capped to the rare case, but a candidate to revisit when wrap_r is
reused inside a long-running World (C21).

No regression scripts configured; the architect is the sole gate.
Cycle spec and plan (git-ignored working files) discarded per
convention.

refs #231
2026-07-11 05:17:08 +02:00
Brummel 7748b53a10 feat(cli,docs): the r_channel OHLC example + ledger amendment C26 (#231 tasks 6-7)
The acceptance proof: hl_channel — a causal Donchian channel (Delay(1)
excludes the current bar, C2) consuming high/low/close roles, built from
rostered vocabulary, shipped as the r_channel/r_channel_open example
pair (builder + regenerator + serialize pins, the r_* pattern). Proven
at three layers: a non-gated loaded-vs-carve equivalence over inline
sources (non-zero-bias hardened per review), the unconditional synthetic
multi-column refusal, and archive-gated CLI e2e — aura run on GER40
end-to-end plus a sweep whose members run and reproduce 2/2
bit-identically.

Ledger: new C26 entry (the binding vocabulary — closed column set +
price alias, name-driven default + campaign data.bindings override,
canonical order = the C4 tie-break, Blockly-litmus argument, the #71
extension point for recorded non-price sources) and the C20/C24
scaffolding-clause annotations: the single-price data weld is retired,
wrap_r's remaining R-scaffolding retirement stays #159.

Verified: all proof tests green, full workspace suite green, clippy -D
warnings clean; independent quality review approved.

closes #231
2026-07-11 05:10:05 +02:00
Brummel 43b1c7ff5d feat(research,registry,cli): campaign data.bindings — the 6b rebind seam (#231 task 5)
DataSection gains an additive bindings block (role -> column; serde
default + skip-if-empty, so binding-less documents keep their content
ids — pinned). Validation is two-tier along the existing line: the
intrinsic tier checks binding VALUES against the column vocabulary
(DocFault::UnknownBindingColumn, alias-annotated display register); the
resolver tier checks binding KEYS against the loaded strategies'
input_roles() (RefFault::BindingRoleUnknown — a key must name a role of
at least one campaign strategy). A cross-surface pin keeps the doc-tier
vocabulary and the CLI binding module from drifting.

CliMemberRunner threads the campaign overrides into resolve_binding on
BOTH halves (column opening and wrap), keeping the C1 drift alarm
comparing like-with-like; the verb sugar passes no bindings (name
defaults rule).

Proof: an archive-gated campaign e2e rebinds price -> open and yields
different realized metrics from the close-bound run; refusal prose
exact-pinned at both tiers.

Verified: full workspace suite green, clippy -D warnings clean;
independent quality review, all findings repaired.

refs #231
2026-07-11 04:35:01 +02:00
Brummel 1d14ebc1cd feat(cli,engine): multi-column opening on every real-data path (#231 task 4)
The six Close-only open sites route through the resolved binding's
column set via open_columns (probe_window, open_real_source,
run_sources/windowed_sources, the campaign member open, the trace
re-run open) — a strategy declaring high/low/close roles gets exactly
those columns, merged in the canonical C4 order. Synthetic data refuses
any binding beyond {close} with the honest --real remedy (the walk
generates a close series only); the sweep/mc family builders ride their
established Err contract, run/walkforward/reproduce refuse exit-1.

Proof at two layers: an engine e2e composes an OHLC blueprint from data
and runs it over four inline VecSources to hand-computed rows (the
merge-order pin), and an archive-gated CLI e2e sweeps a high/low
blueprint over GER40 opening the High and Low columns.

Verified: engine OHLC e2e green, full workspace suite green, clippy -D
warnings clean.

refs #231
2026-07-11 03:37:14 +02:00
Brummel 99a7f6603c refactor(cli): wrap_r declares its root roles from the resolved binding (#231 task 3)
Behaviour-preserving threading: wrap_r's hard-wired
source_role("price") weld is gone — root roles now come from a
ResolvedBinding, one per entry in canonical column order, each fed into
the signal's same-named port; the guaranteed close entry always feeds
broker + executor (whose 'price' is a node-schema port name, not a
role). Every existing blueprint resolves to the identical single-price
plan, so the entire suite is the gate: green unchanged.

Threaded through all callers: run_signal_r, run_blueprint_member (+ its
production and test callers), blueprint_axis_probe (lenient probe
binding), reproduce_family_in, the sweep/oos/walkforward/mc family
builders, CliMemberRunner::run_member, persist_campaign_traces.

Vocabulary role names use their static form at the port seam; only an
override-renamed role leaks its name once per graph build (post-review
polish capping the per-member leak).

Verified: full workspace suite green unchanged, clippy -D warnings
clean.

refs #231
2026-07-11 02:27:05 +02:00
Brummel 595c98b264 feat(cli,ingest): binding module + generalized column openers (#231 tasks 1-2)
crates/aura-cli/src/binding.rs: the closed column vocabulary (open/high/
low/close/spread/volume, price as the close alias) with ResolvedBinding
— one ordered plan both halves of the single-price weld will share.
resolve_binding applies override-wins-over-name-default, refuses unknown
roles / bad override values / the price+close double-alias / a rebound
reserved close role, synthesizes the broker-only close entry, and sorts
into canonical M1Field order (the C4 merge tie-break contract). 12
exact-prose unit tests. Module-wide dead_code allow is scaffolding until
the wrap seam consumes it next.

aura-ingest: open_columns / open_columns_window open any field set in
the given order (None if any field lacks an overlapping archive file);
open_ohlc now delegates with the fixed OHLC order — behaviour-identical,
still guarded by the open_ohlc_seam bit-identity test.

Post-review polish: RESERVED_CLOSE_ROLE constant couples the synthesized
close's two sites; dead_code note reworded condition-based.

Verified: binding 12/12, ingest absence test green, full workspace suite
green, clippy -D warnings clean; independent quality review approved.

refs #231
2026-07-11 00:49:22 +02:00
Brummel 753ab5f0ee docs: teach the Rust node-authoring pattern in the authoring guide (§0)
New top-level section covering the three-part shape every node type
follows — Node impl (lookbacks/eval/label with the C8 warm-up filter),
PrimitiveBuilder recipe (NodeSchema + positional build closure), and
rostering (project-side aura_project! vs std-side roster macro) — with
the aura-new Scale starter as the verbatim worked example and the five
fresh #236 nodes as arity-variant references. Cross-linked from the
op-script vocabulary note, project-layout, and the README.

closes #228
2026-07-10 22:24:24 +02:00
Brummel 00e15b9371 feat(cli): sweep/walkforward --trace writes every member's tap series (#168 refusal lifted)
--trace <fam> now translates through the verb sugar into a persist_taps
campaign-presentation block (was persist_taps: vec![] — the #168 no-op)
and persist_campaign_traces writes EVERY member of a selection-free
swept cell, one subdirectory per member under the cell dir, keyed by
the member's manifest params label in reproduce's exact format
(filesystem-sanitized, ordinal fallback); nominee cells keep their flat
layout. Members re-run at the cell's own stop regime and resolved pip,
streaming one member at a time, with the C1 drift alarm preserved per
member. The walkforward sibling lifts identically; run/mc --trace stay
refused by design (no campaign context / no family-name concept — their
help was already honest). Clap help lines and the chart NotFound hint
restored; the old #168 refusal pins re-pinned to the delivered
behaviour.

Post-review repairs: member_trace_key + cell_member_fanout extracted
pure and unit-pinned (6 tests), persisted_cells counts cells again,
cell-invariant space/geo hoisted out of the member loop, verb_sugar
tail comment corrected.

Verified: headline e2e green (GER40 archive), cli_run 124/0, full
workspace suite green, clippy -D warnings clean; independent spec
review compliant, quality review findings all repaired.

closes #224
refs #168
2026-07-10 22:16:09 +02:00
Brummel fe4ea0a08d test(cli): RED — sweep --real --trace refuses instead of writing tap series
Pins the #224 delivery headline: the #168 refusal lifts and at least
one non-empty tap-series file lands under runs/traces/. Layout-agnostic
on purpose — the per-member key convention is the GREEN slice's call.

refs #224
2026-07-10 21:13:52 +02:00
Brummel e44b0cbb3e feat(cli): warn when the loaded project dylib is stale against the source tree
Editing a project's src/ without cargo build silently ran the previous
dylib with plausible stale numbers — the role-2 authoring-loop footgun.
The loader now compares the newest mtime under src/ + Cargo.toml against
the dylib's mtime right after the ArtifactMissing check and prints one
stderr warning naming both timestamps (hand-rolled Hinnant
civil_from_days render, no new dependency), then proceeds unchanged:
warn, never refuse — a stale run is still deterministic and
manifest-stamped. Every I/O failure in the scan degrades to
skip-and-continue; staleness can never become a refusal. Fresh builds
stay silent (inverse unit-pinned).

Verified: headline e2e green, project_load 8/8, full workspace suite
green, clippy -D warnings clean; independent quality review approved.

closes #237
2026-07-10 21:04:00 +02:00
Brummel 143c87c0ae test(cli): RED — a stale project dylib loads silently with no staleness warning
Editing project src/ without cargo build silently runs the previous
dylib with plausible stale numbers (role-2 authoring-loop footgun).
Pins: source mtime newer than the dylib -> stderr warning naming both
timestamps, run proceeds unchanged (warn, not refuse — a stale run is
still deterministic and manifest-stamped).

refs #237
2026-07-10 20:16:02 +02:00
Brummel 99237e1d0a feat(std): fill the by-chance vocabulary gaps — Const, Div, Abs, Max, Min
Five new rostered node types (count-pin 23 -> 28, both the in-crate
shape test and the cross-boundary CLI vocabulary e2e): Const is unary
with an f64 'value' param — the clock input drives it, its value is
ignored, since a zero-input node never evaluates in the total-push
engine — mirroring EqConst's constant-as-param pattern; Div is binary
IEEE-754 (x/0 -> signed inf, 0/0 -> NaN, unit-tested, no error
channel); Abs unary mirroring Sqrt; Max/Min binary pairwise, distinct
from the windowed RollingMax/RollingMin.

Acceptance proof: the committed executable spec composes an RSI-class
gain/loss-split-and-ratio signal purely from blueprint data through
std_vocabulary and runs it to hand-computed RS values — the r_meanrev
constant-folding workaround is no longer forced.

Verified: headline test green, aura-std 163/0, full workspace suite
green (independent mini-verify), clippy -D warnings clean.

closes #236
2026-07-10 19:59:10 +02:00
Brummel a0098fc8c1 test(engine): RED — RSI-class blueprint needs the absent std vocabulary ops
Executable spec for the by-chance vocabulary gaps: an RSI-like
gain/loss-split-and-ratio composition authored purely as blueprint
data (Const/Div/Abs/Max/Min) must load through std_vocabulary, build,
and run to hand-computed RS values (2.0 at t3, 3.0 at t4 over
10,12,11,14). Fails with UnknownNodeType(Const) — feature absent.

Const is unary with an f64 'value' param (clock input, value ignored)
mirroring EqConst's constant-as-param pattern; Div/Max/Min binary
mirroring Add/Sub; Abs unary mirroring Sqrt; Div follows IEEE-754.

refs #236
2026-07-10 19:30:57 +02:00
Brummel c1971d4ed7 fix(cli): the graph render positional shape-discriminates op-scripts like its siblings
aura graph <op-script.json> exited 2 mislabeling the valid JSON array
as 'blueprint document is not valid JSON' — dispatch_graph parsed the
render positional with the envelope-only blueprint_from_json while the
introspect siblings (register, --params, --content-id) route through
composite_from_any. The render positional now uses the same
shape-discriminating path (made pub(crate)); an op-script renders
byte-identically to its built envelope, and a genuinely-bad document
gets the shared honest error including the unresolved-namespace hint
(preserved verbatim in composite_from_any's envelope arm).

Verified: RED test green, full workspace suite green (independent
mini-verify).

closes #226
2026-07-10 19:20:33 +02:00
Brummel 0d9fdf8f05 test(cli): RED — the graph render positional rejects op-scripts its siblings accept
aura graph <op-script.json> exits 2 mislabeling the valid JSON array as
'blueprint document is not valid JSON'; register/--params already
shape-discriminate via composite_from_any. Pins render parity: op-script
and its built envelope render byte-identically at exit 0.

refs #226
2026-07-10 18:58:45 +02:00
Brummel fb145f5a15 fix(cli): reproduce re-derives the stop regime from the member manifest
reproduce_family_in hardcoded the default StopRule::Vol{3,2.0} at both
the param-space probe and the member re-run, so a family minted under a
non-default vol-stop (campaign risk-regime cell, or wf/mc/generalize
with --stop-length/--stop-k) spuriously reported DIVERGED.

New stop_rule_from_params re-derives StopRule::Vol from the manifest's
stamped stop_length/stop_k, falling back to the defaults when the keys
are absent (pre-stamp members) — the same one-directional widening
point_from_params applies to missing manifest params, mirroring
campaign_run's stop_rule_for_regime None arm. The stop rides outside
the wrapped param space, so point_from_params could never recover it.

Verified: RED test green, full workspace suite green (independent
mini-verify), clippy -D warnings clean.

closes #233
2026-07-10 18:53:01 +02:00
Brummel ab14eeed3c test(cli): RED — reproduce re-runs members under the default stop regime
A member minted under a non-default vol-stop (manifest stamps
stop_length/stop_k) spuriously reports DIVERGED on reproduce: both the
param-space probe and the member re-run hardcode StopRule::Vol{3,2.0}.
Closed blueprint isolates the stop as the only differing dimension.

refs #233
2026-07-10 18:34:28 +02:00
Brummel ed1e8a0d10 fix(cli): thread the resolved pip into wrap_r so real-data runs hit the graph broker
wrap_r hardwired SimBroker::builder(SYNTHETIC_PIP_SIZE); the resolved
geometry-sidecar pip reached only the manifest broker label, so every
real-data run computed total_pips/max_drawdown at 0.0001 regardless of
instrument (GER40 pip=1: inflated 10^4) while the label claimed the
real pip.

wrap_r now takes pip_size. Real-pip call sites pass the resolved pip:
run_signal_r (resolve_run_data reordered before the wrap),
run_blueprint_member, and the campaign trace re-run in
persist_campaign_traces (instrument_geometry lookup mirroring the
run_member refusal — required so the C1 drift alarm compares re-run
and nominee at the same pip). Probe-only call sites (param-space and
axis probes; metrics discarded) keep SYNTHETIC_PIP_SIZE explicitly.

The two golden stitched_total_pips pins in cli_run.rs rescaled by
exactly the 10^4 bug factor while n_trades/expectancy_r stayed
byte-identical — confirming R metrics were never pip-contaminated.

Alternative rejected: dropping pip-denominated metrics from real-data
reports — the manifest already stamps the resolved pip, so running the
graph at it restores label truthfulness without shrinking the report.

Verified: full workspace suite green (0 failed incl. the real-data e2e
suite), clippy -D warnings clean.

closes #232
2026-07-10 18:28:42 +02:00
Brummel eb5fc2145b test(cli): RED — real-data member pips ignore the resolved sidecar pip
The in-graph SimBroker divides by SYNTHETIC_PIP_SIZE regardless of the
resolved instrument pip; the sidecar pip reaches only the manifest label.
Pins the pip-invariance property total_pips * pip == raw price move.

refs #232
2026-07-10 17:30:29 +02:00
Brummel 3016bd6b11 fix(cli): reproduce re-derives real-data families from the member manifest (#229)
reproduce_family no longer hardcodes DataSource::Synthetic: it reads the
family's instrument + window from the persisted manifest and rebuilds the
same local-archive source the dissolved verbs run over; a missing archive
refuses with the verbs' established named-data refusal (exit 1) instead of
silently re-running over wrong data. Sweep families now reproduce N/N
bit-identically; WalkForward families yield a verdict instead of the
empty-OOS-window panic. The synthetic-family path is byte-unchanged.

Accepted minimal duplication: reproduce_family performs its own family
lookup to read the instrument before delegating to reproduce_family_in,
whose signature and unit-test seam stay byte-identical (constraint:
minimal fix).

MonteCarlo reproduce still runs the synthetic seed walk regardless of a
real manifest — pre-existing, semantically distinct (bit-identity over
bootstrap resamples), filed forward.

Verified: both #229 RED tests green over the GER40 archive, full workspace
suite 1107/0, clippy -D warnings clean.

closes #229
2026-07-10 15:32:39 +02:00
Brummel 0408fc41a2 test(cli): RED — reproduce over real-data families diverges/panics (#229)
Two failing e2e pins (skip-on-no-data, GER40 archive present here):
- reproduce_real_sweep_family_re_derives_bit_identically: a --real sweep
  family must reproduce N/N bit-identically; currently 0/N DIVERGED because
  reproduce_family re-runs members over DataSource::Synthetic.
- reproduce_real_walkforward_family_does_not_panic: a --real WalkForward
  family must yield a verdict or a clean refusal; currently panics at the
  empty-OOS-window expect (main.rs:1123) for the same reason.

refs #229
2026-07-10 14:47:23 +02:00