Architecture Graph
The reference pages answer “what is this type”. This page answers the questions a table cannot: who implements this trait, what breaks if I change this struct, and how far does this crate reach.
Click any node for its details. Hover to isolate its neighbourhood. The data is extracted from the Rust sources — see how it is built.
Select a node to see its contracts, what it owns, and where it is defined.
What the views show
Section titled “What the views show”Traits & implementors is the default because it is the architecture skeleton.
Twenty-nine traits and everything that realizes them — this is the view that
answers “if I change CacheStore, what has to change with it” (four cache
implementations across one crate) or “what actually satisfies Filter” (two
production types, one calling into Python and one into a subprocess).
Ownership draws composition. A solid edge is owned outright — Box, Vec,
or by value, and it dies with its owner. A dashed edge is shared: Arc or a
reference, which may outlive the owner. The dyn marker in the detail panel is
where the ownership crosses a trait object, which is where a change stops
propagating structurally and starts propagating through a contract.
Everything is all 276 public types at once. It is dense on purpose — use the crate chips to cut it down, or the search box.
What this is not
Section titled “What this is not”It is not a call graph, and specifically not cargo-call-stack output.
That tool cannot run on this workspace, for three reasons that compound. It fails
to parse the manifest at all — from the root, missing field 'package' on a
virtual workspace; from a crate directory, invalid type: map on
version.workspace = true, which its vendored cargo-project 0.3.0 predates. It
also requires the exact toolchain nightly-2023-11-13.
And if both were fixed it would still be the wrong instrument. cargo-call-stack
computes worst-case stack depth for no_std firmware, and its model assumes
every indirect call resolves statically. Soma is built on dynamic dispatch — 33
dyn CacheStore sites, Arc<dyn Filter>, Arc<dyn Step>, dyn EffectHandler,
dyn Transport. The graph would fall apart into islands joined by “unknown”
nodes at precisely the boundaries worth understanding.
The honest substitutes, both of which exist:
- For structure — this page, plus the execution traces, which are hand-written call chains with
file:lineat every hop. A static tool cannot tell you thatrun_node_inneris the branch that distinguishes a filter from a step; a person reading the code can. - For what actually ran — the runtime already records it.
soma reportemits per-node timings, cache hits and health flags from a real run, andrun.to_mermaid()folds them onto the graph as an overlay.
How this is built
Section titled “How this is built”docs/scripts/gen-arch-graph.mjs reads the Rust sources directly — no cargo, no
toolchain, no network — and rewrites the JSON blob embedded in this page:
cd docs && node scripts/gen-arch-graph.mjsIt collects public declarations, impl Trait for Type lines, and struct fields
whose type names another known node. Everything from the first #[cfg(test)] in
a file is skipped: test fixtures implement Filter and Step dozens of times
and would otherwise swamp the real graph.
Two limits worth knowing, because a graph that hides its uncertainty is worse than one that admits it:
- Ownership edges are textual. A field is linked to a type when its declared type names it. A field typed with an alias, or built from generics, is missed.
- Impl edges only connect two workspace types.
impl Debug for Xandimpl From<Vec<f64>> for Valueare dropped, because the other end is not a node here.
The blob is committed, so the page is static and needs no build-time data. Rerun
the generator after adding or moving a public type, and npm run check will
verify the file:line anchors on every other Internals page still resolve.