vike
Concepts

Architecture

A Cargo workspace whose dependency direction is down-only and machine-checked, and one startup sequence that every binary runs through the same owner.

Vike Trader is a Cargo workspace of small crates. Three things about its shape are load-bearing and worth knowing before any other concept page: dependencies point in one direction only and a test enforces it; every crate declares its own rank; and the five binaries that boot a process do so through one shared sequence rather than five copies of it.

Dependency direction is down only, and it is a gate

Every workspace member declares its rank in its own manifest:

[package.metadata.vike]
layer = 30

crates/vike-ops/tests/layer_gate.rs's every_normal_vike_dependency_goes_strictly_down asserts that for every normal (non-dev) vike-* dependency edge c -> d, layer(d) < layer(c). It is text-only over the manifests — no cargo invocation — and it fails a pull request rather than producing a review comment. A dev-dependency may point upward, and the workspace does so on purpose: vike-mm dev-depends on vike-core so its white-box tests can build a concrete LiveBroker, and vike-bridge-core dev-depends on ten venue crates to run the shared conformance harnesses. dev_dependencies_genuinely_point_upward in the same file asserts that the exemption is still needed, so nobody can fold dev edges into the rule and have it pass for the wrong reason. [build-dependencies] are not exempt.

The gate exists because of one edge that actually shipped. A single normal dependency from vike-tradehub — the headless daemon that runs on the box that signs real orders — to vike-app-core dragged the entire egui widget stack into that daemon, for three pure modules. The cost was never CI minutes: it was daemon build time, binary size, and the cargo deny --all-features supply-chain audit surface on a production trading node. It was found by somebody noticing build times — no test, no gate, no diff comment. vike-ops exists because of it.

The tiers

The ranks were established by reading the real graph — all 275 normal vike-* edges in the tree the gate was written against — not from a narrative order, and the tree had no violation to fix when the gate landed. layer_gate.rs's TIERS is the authority, and a crate's layer must be one of its ranks, so a new rank costs a row with a written meaning; the gaps are deliberate, so a crate can be slotted between tiers without renumbering the workspace.

ranktierwhat sits here
10vocabularyvike-model itself — the shared domain vocabulary every other layer names
15leafa pure leaf with no vike-* dependency of its own
20domainone hop over the vocabulary: a single-purpose pure core
25bootthe composition-root startup sequence, below every binary that runs it
30enginea runtime, simulation or transport core over the domain crates
35user-hosta build.rs host that compiles user-authored code out of user_data
40venuea per-venue bridge built on vike-bridge-core
41venue-stackeda venue bridge built on a sibling venue's family driver
45researchan offline study crate that mounts no venue — vacant today, kept deliberately
50servicea library composed over the engine crates
55service-stackeda service composed over another service crate
60mountthe venue composition root (make_engine)
62mount-stackeda composition root over vike-mount
65daemona headless top-of-graph binary
68multicallthe multicall dispatcher: depends on every headless binary, nothing depends on it
70ui-foundationthe egui foundation leaf — everything above here links egui
75ui-widgetan egui widget or engine crate, no eframe/wgpu
80ui-shellan egui shell assembled from the widget crates
90appthe desktop application: the outermost shell

A rank is a total order; the architecture is a partial one

Where two families are genuinely incomparable the numbers still have to put one above the other, and the gate then permits the wrong-direction edge in whichever direction was chosen. Those choices are made toward the mistake that has actually happened. The whole UI stack is ranked above every headless daemon — which is what makes the motivating vike-tradehub -> vike-app-core edge fail; ranked by topological depth instead, it would pass. And venues (40) sit below the simulator (50), so a bridge crate that reaches for vike-backtest is red; the reverse edge is the one the numbers let through, because pulling the engine into a venue adapter is the "dragged the whole tree in" shape the gate exists for.

The layering rule that keeps being re-learned

When two sides must not disagree, the cure is a shared crate below both, not a shared crate containing both. vike-fills (crates/vike-fills/src/lib.rs) is the worked example: one definition of what a fill costs, when it happens and at what price, extracted verbatim out of the backtest engine so that the paper exchange and the simulator share a dependency rather than a crate. While both lived inside vike-backtest their agreement was true by co-location, and the price was that every consumer wanting only the paper executor compiled the whole simulator to get it. That is what lets backtest == paper be gated bit-for-bit rather than asserted — the paper exchange page carries the law and its scope.

One startup sequence, one owner

Five composition roots — vike-desktop, vike-tradehub, vike-recorder, vike-cli and vike-datahub — used to run the same ordered steps from five copies: refuse a removed environment variable, resolve <project>/settings, load the credentials (and refuse a credential file that arms real money), load the settings, derive the project-relative paths and build the log subscriber, then disclose what was resolved. vike_boot::boot (crates/vike-boot/src/lib.rs) owns that order now, and crates/vike-boot/tests/one_owner.rs is the gate that a root may not re-spell it: a call to a step of the sequence must be in vike-boot itself or in a declared exception row carrying its reason, and a crate that boots may not call a project resolver anywhere under its own src/. The exceptions today are re-reads rather than second startups — the CLI's config check pre-flight, which re-runs a refusal in order to report it, and the daemon's runtime hot-apply and the Data Manager's arming screen, which both re-read settings out of the directory the boot already resolved and handed them.

Each root declares how it departs from the sequence with a BootSpec whose arms carry a reason — RemovedEnv::Ignore, SettingsLoad::Skip, Credentials::Deferred, LogHome::Elsewhere, Disclosure::Skip — never a bool. Turning a startup refusal off is exactly the change that should have to justify itself in a diff. vike-cli runs the refusal for every subcommand but builds no subscriber and defers the credential read; vike-datahub loads no settings at all.

The load-bearing property is not the order, which every root got right by copying. It is that one project walk decides per process, and every project-relative path is derived from its answer: the settings load, the state root (Booted::state_dir), the log home and the startup disclosure. Before that, four sites reached a resolver that did not honour $VIKE_SETTINGS_DIR — so the second answer was specifically the one that ignored the override, on exactly the deployments that set it: a desktop whose rolling log file and startup block named two different projects.

Two resolutions remain outside the boot, and neither can disagree with it. The credential store is reached through the root's own loader (Credentials::LoadWith) over the same pure resolver and the same override, so it is a duplicated cost, never a second answer. And the halt sentinel's default location is memoized deep inside a library with no boot in reach, so the two booting trading roots hand it Booted::state_dir as a parameter — with one declared residual: two mount binaries in the tree never call boot at all, so their sentinel still walks, and nothing gates it either way.

Three properties of the boot worth carrying

It runs the arming refusal before the settings load — a tree that arms real money and has a broken policy.toml must fail with the arming message, the more urgent of the two. It logs nothing and prints nothing, because it runs before vike_log::init and the log directory and both log levels are themselves settings; everything it would say comes back as data and the root emits it once a subscriber exists. And it never opens the credential store itself — the loader is a function the root supplies, so vike-cli can defer the read entirely and no consumer inherits the bridge crates' HTTP and TLS stack; measured on the tree the crate was written against, the one vike-log edge it refuses would add 43 packages to vike-cli.

A layering failure is read on the structure gates page; the rule itself lives here.

On this page