# C16 — Engine / project separation; three-tier node reuse **Guarantee.** aura is the reusable **engine**; each research project is a separate external repo that depends on aura via cargo (the game-engine / game split). **Node reuse is cargo-native, in three tiers:** `aura-std` (universal blocks, shipped with the engine) / **shared node crates** (cross-project-reusable, their own repos, pulled as cargo git deps) / **project-local node crates** (experimental, project-specific). A reusable node is an `rlib` dependency; the hot-reload unit is the project-side `cdylib` that composes it (C13). **A project is a directory anchored by a static `Aura.toml`** (project context, paths only: data archive root, runs dir), in two tiers (#241, ratified 2026-07-12): - **Data-only** — the default, smallest project: `Aura.toml` + `blueprints/` + `runs/`, blueprints and research documents over the std vocabulary, **no crate, no build step**. - **Native** — node logic lives in **node crates**: separate `cdylib` crates referenced via `Aura.toml [nodes]`, scaffolded and attached by a dedicated verb (the visible role-2 switch, C25). The load boundary **tier-selects**: a `[nodes]` pointer list → load that crate; a root `Cargo.toml` → the pre-#241 native project, unchanged; neither → std-vocabulary-only. `aura new` scaffolds the data-only tier; `aura nodes new` scaffolds a node crate beside the project and attaches it. During research the `aura` host loads and runs the project (C13 hot-reload); for deploy the chosen strategy + broker freeze into a standalone binary. **Dependency policy (amended 2026-06-10).** Dependencies are admitted by deliberate, **per-case review** — what a crate pulls in weighed against what it buys — with **particular scrutiny for anything that enters the frozen deploy artifact** (C13: this bot = this commit). Well-established standard crates (`serde`, `rayon`, …) pass that review and are used wherever they do the job, including in the bot. There is **no blanket zero-dependency commitment** and **no blanket admission**; hand-rolling what a vetted standard crate already does is the anti-pattern, not the dependency. `aura-ingest` is the data-source ingestion edge, **not** a dependency firewall. **Forbids.** Project-specific signals in the aura repo (it keeps at most example/fixture nodes under `examples/` for its own tests); a multi-project manager inside aura; a bespoke node registry/marketplace (cargo + Gitea *is* the package mechanism). Dependency admission is governed by the per-case policy above, not a blanket ban. **Why.** The engine/game split keeps the engine sharp and reusable while each project versions its own research with its own forward-queue. Promotion (local → shared → std) is the ordinary Rust reuse gradient, no new mechanism. The original "a project is always a Rust crate" invariant was deliberately amended (#241) once the role model diagnosed that the smallest project carries no native logic at all. ## Current state The engine workspace carries the non-node crates beyond `aura-std`; the full roster and its layering are C28's ladder — this contract does not re-enumerate it. `aura-ingest` is the data-source ingestion edge, where the `data-server` external tree enters. **Engine ↔ domain seam — the metric-generic engine** (decision log: #136 → #147/#290/#291/#292). `aura-engine` is the **domain-free, metric-generic** run loop: `RunReport` (`crates/aura-engine/src/report.rs`) is parametrized over a metric type. The concrete trading instantiation `M = RunMetrics` and the backtest metric reductions (`RunMetrics`, `RMetrics`, `summarize_r`, `PositionEvent` / `PositionAction` / `derive_position_events`) live in the outer-rung crate **`aura-backtest`** (`crates/aura-backtest/src/metrics.rs`). The domain-neutral statistics substrate and selection provenance — the multiple-comparison hurdle math (`inv_norm_cdf`, `expected_max_of_normals`), the bootstrap/permutation kit (`MetricStats`, `resample_block`, `permute`), the `MetricVocabulary` trait, and `FamilySelection` / `SelectionMode` — live in **`aura-analysis`** (`crates/aura-analysis/src/lib.rs`). `aura-registry`'s selection vocabulary and `aura-measurement`'s IC (`IcMetrics`) are the trait's two production implementors. This is the realized form of the once-deferred "make `RunReport` generic / turn the orchestration layer into a domain-agnostic substrate" work. **Campaign-execution leaf.** `aura-campaign` owns campaign-execution *semantics* — cell enumeration, preflight, gate evaluation, winner selection, walk-forward rolling, realization assembly (C18) — as a leaf library whose deps deliberately exclude `aura-ingest` / `aura-std` / `aura-composites`; harness construction and data binding enter only through the one-method `MemberRunner` seam (`&dyn MemberRunner`), so each consumer binds its own runner while the semantics live here once. It is explicitly **not** C21's World: it realizes one campaign document and owns no topology, data sources, or UI. **Two-tier scaffolding.** `aura new` emits the data-only project (`crates/aura-cli/src/scaffold.rs`: `Aura.toml` + `blueprints/*.json` + `runs/`, no crate); `aura nodes new` emits a node crate beside the project and appends the `[nodes]` pointer (`crates/aura-cli/src/main.rs`; the load-boundary tier hints live in `graph_construct.rs::tier_hint_for_type_id`). Provenance widened additively: a data-only run stamps **commit-only** (`crates/aura-runner/src/project.rs::Env::provenance`). ## See also - [C1](c01-determinism.md) — the behaviour-preserving invariant the crate extractions kept - [C13](c13-hot-reload-frozen-deploy.md) — hot-reload authoring loop / frozen deploy artifact - [C18](c18-registry.md) — the run registry and campaign realization assembly - [C21](c21-world.md) — the World the campaign leaf is deliberately not - [C25](c25-role-model.md) — the role-2 switch `aura nodes new` makes visible - [C28](c28-stratification.md) — the full crate roster and the layer ladder > History: [c16-engine-project-split.history.md](c16-engine-project-split.history.md)