Task 6 of the bound-override cycle: SIGNAL_OPEN_JSON_STD and its write
retire — aura new scaffolds signal.json alone, and the project CLAUDE.md
quickstart advertises run AND sweep against that one file (the sweep line
overrides the bound fast.length; --list-axes shows open + bound axes).
The data-only e2e loop proves it end to end: scaffold, run the closed
starter, sweep it via an override axis, and assert signal.json is the
only starter blueprint. The authoring-guide's starter-pair sentence is
retargeted; the last signal_open reference outside the unrelated #196
fixture string is gone.
refs #246
Task 5 of the bound-override cycle. The campaign executor and its gates
accept an axis naming a bound param everywhere the open surface was
already accepted: CliMemberRunner::run_member re-opens the override set
on the cell's probe space and raw reload (raw_bound_overrides_of — the
campaign document's axis names live in strategy coordinates, matched
against bound_param_space directly), the P3 preflight in
validate_before_register threads the same set so the executable-shape
check passes, and aura-registry's validate_campaign_refs treats a bound
name as a valid axis (open space wins the arm order; the kind check
still faults a mismatched axis over a bound path).
The CLI-side helper family is named by coordinate system now:
wrapped_bound_overrides_of (wrap-prefixed, sweep/wf/reproduce) beside
raw_bound_overrides_of (campaign). A fresh post-block quality review
found no correctness defect; its two stale-comment findings (renamed
helpers cited under their old names) are fixed in this commit.
refs #246
Task 3-4 of the bound-override cycle. The family boundary derives the
override set from the invocation's axes (override_paths: an axis in the
open space passes through, one naming a bound param re-opens it in
strategy coordinates, one matching neither is refused with a message
pointing at --list-axes) and threads it through the axis probe and every
per-member reload (blueprint_axis_probe_reopened / reopen_all), so probe
and members stay slot-identical. The 'fully bound; nothing to sweep'
refusal retires; identity stays the authored document (the topology_hash
probe reload is never re-opened).
--list-axes now prints the bound surface after the open knobs — one
'<bp>.<node>.<param>:<KIND> default=<value>' line per bound param,
unconditionally (a bound param is an equally re-openable axis regardless
of how many knobs are open beside it).
Walk-forward (blueprint_sweep_over, blueprint_walkforward_family,
run_oos_blueprint) and reproduce (reproduce_family_in plus the
campaign-side trace re-run) derive the same set silently from recorded
manifest param names (bound_overrides_of), so a family that swept an
overridden bound param re-derives bit-identically.
refs #246
Task 1-2 of the bound-override cycle: PrimitiveBuilder::bind/try_bind stop
capturing the bound value in a wrapped build closure and store it only as
BoundParam data; build() merges bound values back into the full-arity vector
at call time (ascending original position — behaviour-identical to the
retired closure chain). This makes the reversal possible: unbind(slot)
returns the param to the declared surface at its original order.
aura-engine gains the path-addressed Composite::reopen (mirroring
collect_params' prefix rules, lockstep with expansion_map) plus the
read-only bound_param_space() enumeration (path-qualified BoundSpec with
values) and the ReopenError/BoundSpec exports. An e2e proves a reopened
param rebuilds from the freshly supplied value through compile_with_params,
not the stale default.
refs #246
The op-script error seam (OpError::UnknownNodeType) now consults the
same tier-hint logic as the blueprint load path: outside a project the
hint names the missing Aura.toml, inside a data-only project it names
the missing node crate and the attach verb. Hint texts shared, load-path
behaviour and the op-error prose byte-stable, exit codes unchanged.
closes#244
RED pin: graph_build_hints_attach_for_namespaced_ids_in_a_data_only_project
(graph_construct.rs e2e) — the op-script path reports OpError::
UnknownNodeType without consulting the tier-aware hint the blueprint
load path carries.
refs #244
An existence pre-check on the [nodes] pointer target fires before the
crate pipeline: the refusal names the absent directory and keeps the
[nodes] context, instead of leaking cargo metadata's os-error wrap.
Existing-but-broken crates keep the ArtifactMissing/CargoMetadata
errors under the pointer prefix.
closes#245
RED pin: a_missing_nodes_pointer_dir_refuses_without_the_cargo_metadata_leak
(project.rs unit tests) — the refusal must name the absent directory and
keep the [nodes] context, with no cargo-metadata wrap.
refs #245
aura new and aura nodes new follow their git init with a best-effort
initial commit (inline committer identity, warning-never-fatal, skipped
whenever the init itself was skipped per #204), so a fresh project's
first run stamps a real commit instead of an empty provenance block.
closes#243
aura new git-inits but never commits, so the first run stamps empty
project provenance. RED pin:
new_outside_a_work_tree_leaves_a_resolvable_head.
refs #243
probe_window's two empty-result exits now route through a distinct
no_data_in_window refusal (requested bounds + archive path); its callers
have already proven the symbol present via has_symbol, so the old
"no local data for symbol" reuse misattributed a window fact to symbol
absence. The genuinely-unknown-symbol message and exit codes stay.
closes#242
An inverted or uncovered --from/--to window on a symbol the archive DOES
hold refuses via the symbol-absence message ("no local data for symbol")
instead of naming the window. RED pin:
run_real_empty_window_refusal_names_the_window_not_the_symbol
(archive-gated). Cause: probe_window routes both empty-in-window cases
through no_real_data although has_symbol already proved presence.
refs #242
Consumer-POV fieldtest evidence for the two-tier project model: the
data-only quickstart, the role-2 attach loop, real-data usage inside a
data-only project, and the refusal surfaces all match the docs.
Inline fixes for two of the friction findings:
- docs/project-layout.md: the day-in-the-life run example now shows real
Unix-ms window bounds instead of bare years (--from/--to take ms).
- `aura new` --help one-liner no longer calls the project a crate.
Remaining findings tracked on the Gitea queue (empty first-manifest
provenance on a fresh scaffold; misleading no-data message for an
uncovered --real window; the tier hint missing on the graph-build path;
the raw cargo-metadata leak for a missing [nodes] dir).
refs #241
- CLAUDE.md invariant 9 amended end to end: a project is a directory
anchored by a static Aura.toml, data-only by default; native node
logic lives in attached node crates (the nodes/ antecedent removed —
user decision 2026-07-12, re-opened by the role model's diagnosis).
- docs/project-layout.md reworked to the two tiers (data-only default
tree + sibling node crate, workspace note under the crate half); the
day-in-the-life walkthrough now attaches the project's node crate
once and wires the namespace the shown command actually produces.
- docs/authoring-guide.md: data-only quickstart (closed run target,
open sweep target) + `aura nodes new` as the native entry; section
cross-reference updated.
- docs/glossary.md: new "data-only project" and "node crate" entries;
Aura.toml and manifest entries carry the tier split.
- docs/design/INDEX.md: realization note (wiring-only tier, #241) in
the project-environment section.
- rustdoc: `[nodes]` intra-doc-link escapes in project.rs/main.rs.
Full gates green: workspace suite (all binaries), clippy -D warnings,
doc build without new warnings.
closes#241
- New clap family `aura nodes new <name> [--engine-path] [--namespace]`:
scaffolds a node crate as a SIBLING of the project root (Cargo.toml
with the aura-core path-dep, the parameterized Scale starter node,
roster macro, crate-scoped .gitignore + CLAUDE.md, own git repo) and
appends the `[nodes] crates = ["../<name>"]` pointer to the project's
Aura.toml. Refuses outside a project, on any existing [nodes] section
(incl. an empty crates array), and on an existing destination; joins
the load-phase bypass beside `aura new` (a scaffolder must work in an
unbuilt tree).
- The old single-tier scaffold() is gone; the crate templates live on
in scaffold_node_crate; dead-code bridges removed with their callers.
- tests/project_nodes.rs: attach + refusals + namespace override + the
full role-2 loop (scaffold, cargo build, vocabulary resolves
<ns>::Scale end to end).
- tests/project_sweep_campaign.rs fixture reworked to the two-tier
shape (aura new + aura nodes new --namespace knob_lab); the two
campaign acceptance tests are byte-unchanged and green.
refs #241
- 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
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
- 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
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.
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
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
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
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
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
--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
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
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
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
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
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.
- 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.
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.
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
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.
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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