soma-control is a single Rust binary. It boots IAM, a secrets vault, feature flags, and a licensing engine on one port, from one process, sharing one Postgres connection pool. Total route count: 341.
Why one binary
IAM, vault, flags, and licensing are not independent services. IAM issues the tokens that vault and flags check on every request. Licensing meters the usage that flags gate. The trust relationship between them is internal and fixed at deployment time — there is no scenario where vault runs without IAM, or where flags talks to a vault from a different tenant.
Splitting them into separate binaries would give you four Postgres connection pools, four TLS configurations, four Docker containers to orchestrate, and four places to look when something behaves unexpectedly. And a single-tenant soma-control idle at tens of megabytes of RSS would instead be four processes each paying the minimum Rust binary overhead.
The isolation argument assumes adversarial boundaries. These four modules have none. One binary is the correct architecture.
How it works
Each functional area is a Rust crate: soma-iam, soma-vault, soma-flags, soma-licensing. Each exposes an Axum router and the state it requires. At startup, soma-control creates one pool from the environment and wires the four routers together:
let pool = soma_infra::connect_from_env().await?;
let app = Router::new()
.nest("/iam", iam::router(pool.clone()))
.nest("/vault", vault::router(pool.clone()))
.nest("/flags", flags::router(pool.clone()))
.nest("/licensing", licensing::router(pool.clone()));
axum::serve(listener, app).await?;
This is a simplified sketch — the state types carry more than just the pool, and there is middleware, a Redis client, and background workers involved — but the structure is accurate. One pool, four routers, one axum::serve. The 341 routes mount under their path prefixes and the modules never call into each other at runtime.
Migrations run at startup via soma-schema, the platform's manifest-ordered migration runner (v0.4.0, published on crates.io). It acquires a Postgres advisory lock before running so concurrent restarts from multiple replicas are safe. IAM has 52 applied migrations. Vault has 8. Each module owns its schema; nothing crosses schema boundaries through ORM magic.
Dashboards at compile time
Each of the four modules has a Leptos WASM dashboard: 30 pages for IAM, 15 for vault, 10 for flags, 11 for licensing. The dashboards are written in Rust using soma-ui, a component library with 160+ typed Leptos 0.8 components.
When you run cargo build on soma-control, the build process compiles all four dashboards to WASM and the resulting dist/ directories are embedded into the binary via rust-embed. At request time, GET /iam/dashboard/ serves bytes out of a static map in the binary's read-only data section.
There is no CDN to configure, no nginx serving a separate frontend, no separate deployment step when you update the dashboards. You rebuild the binary; the dashboards update with it.
Crypto
The cryptographic primitives across soma-control are:
- AES-256-GCM: symmetric encryption for secret payloads and WebAuthn credential blobs
- AES-KW: key wrapping in the vault's envelope encryption scheme
- ECDSA P-256: OIDC token signing, with the public keys published at a JWKS endpoint
- Argon2id: password hashing
- HKDF-SHA256: key derivation, with service-specific info strings set by the caller, not the library
- HMAC-SHA256: audit chain integrity
All of these come from pure Rust crates in the RustCrypto ecosystem. OpenSSL is not a dependency. The TLS stack is rustls, also pure Rust. Every server crate in the platform carries #![forbid(unsafe_code)].
The practical benefits: supply-chain auditing is simpler with one coherent set of crates rather than a C library that varies by distribution. Cross-compilation to musl targets is straightforward — soma-analytics builds as a musl static binary on distroless/static with no shared libraries in the runtime image. And there is an entire class of deployment bugs — libssl version mismatches, FIPS-mode surprises — that simply do not exist.
Release profile
Every crate in the platform sets:
[profile.dev]
debug = false
strip = true
A Rust workspace of this size with full debuginfo generates gigabytes in target/. Dropping debuginfo and stripping the binary keeps cargo build outputs manageable on a laptop and keeps the deployed binary small. cargo check and cargo test for iteration, --release for anything actually running. The debug profile exists for compilation speed, not for artifacts.
The footprint claim
When I say soma-control idles at a small RSS, there is no qualifier needed about "for a service of this kind." Rust allocates what the code asks for. There is no GC heap idling at 64 MB waiting to compact, no JVM warmup phase, no V8 isolate per request.
The actual idle number depends on pool size, Redis client buffers, and how many tenants have state loaded in LRU caches. I will publish a concrete measurement once I have a stable baseline configuration. The point is that the number will be honest — the runtime does not obscure allocations behind a layer you cannot inspect.
#![forbid(unsafe_code)] means the Rust compiler's memory-safety guarantees hold throughout. The footprint is small because there is genuinely less work happening, not because of unverifiable claims about native efficiency.