Skip to content

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.

traitstructenumpyclass— implements— owns (dashed = shared, Arc/&)scroll to zoom · drag to pan

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.

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:line at every hop. A static tool cannot tell you that run_node_inner is 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 report emits per-node timings, cache hits and health flags from a real run, and run.to_mermaid() folds them onto the graph as an overlay.

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:

Terminal window
cd docs && node scripts/gen-arch-graph.mjs

It 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 X and impl From<Vec<f64>> for Value are 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.