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 = 30crates/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.
| rank | tier | what sits here |
|---|---|---|
| 10 | vocabulary | vike-model itself — the shared domain vocabulary every other layer names |
| 15 | leaf | a pure leaf with no vike-* dependency of its own |
| 20 | domain | one hop over the vocabulary: a single-purpose pure core |
| 25 | boot | the composition-root startup sequence, below every binary that runs it |
| 30 | engine | a runtime, simulation or transport core over the domain crates |
| 35 | user-host | a build.rs host that compiles user-authored code out of user_data |
| 40 | venue | a per-venue bridge built on vike-bridge-core |
| 41 | venue-stacked | a venue bridge built on a sibling venue's family driver |
| 45 | research | an offline study crate that mounts no venue — vacant today, kept deliberately |
| 50 | service | a library composed over the engine crates |
| 55 | service-stacked | a service composed over another service crate |
| 60 | mount | the venue composition root (make_engine) |
| 62 | mount-stacked | a composition root over vike-mount |
| 65 | daemon | a headless top-of-graph binary |
| 68 | multicall | the multicall dispatcher: depends on every headless binary, nothing depends on it |
| 70 | ui-foundation | the egui foundation leaf — everything above here links egui |
| 75 | ui-widget | an egui widget or engine crate, no eframe/wgpu |
| 80 | ui-shell | an egui shell assembled from the widget crates |
| 90 | app | the 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.
Concepts
How Vike Trader is put together — the crate layering, the single-writer core, the seam every venue implements, the order state machine, the gates in front of a live order, and the data the whole system shares.
The execution core
One dedicated thread owns all mutable trading state and is its only mutator; everything reaches it through one totally-ordered queue, so determinism is by construction rather than by locking.