A peer-to-peer system lives or dies on two kinds of code: the code that decides, and the code that touches the world. The first kind must be replayable: given the same inputs it must produce the same outputs, byte for byte, on any machine, under a debugger or in a fuzzer. The second kind (sockets, clocks, filesystems, entropy) is none of those things. The architectural question is where you put the seam between them.
Valhalla’s answer is strict: every effect sits behind a port, and the cores hold no handle to the world at all.
the pattern
The workspace separates the system into a deterministic core and an effectful shell, and the line between them is not stylistic; it is a set of crate manifests. vhalla-core opens with #![no_std] and a doc comment that states the contract plainly: no filesystem, process, network, cryptography, model, or browser dependency. vhalla-session is no_std too, and its module doc says the same thing differently: “This crate has no network, clock, entropy, storage, policy, or host effects.” Inside vhalla-session there is a complete three-message handshake (Pending::initiate, Pending::respond, Pending::confirm, Pending::finish) and not one call that touches the outside.
How does a handshake work without a clock or a random source? The caller passes them in. initiate takes nonce: [u8; 32], now: u64, and deadline: u64 as plain values. The crate validates the nonce is not all zeros (a broken entropy source fails closed) and checks now against the deadline and the pairing’s expires_at. Freshness is a parameter, not a syscall. The session id is derived deterministically: SHA-256 over a domain string, the pairing digest, and both nonces. Two runs with the same inputs produce the same session id; that is not a bug, it is the property that makes the whole layer testable.
where the line runs
The ports are the edges of the pure crates. Time comes in as now: u64 parameters: verify_and_accept, receive, execute, check_deadline all take a clock reading instead of reading a clock. Entropy comes in as nonce: [u8; 32] parameters; the OS entropy lives in vhalla-identity’s Unix adapter, which calls getrandom::fill and passes the bytes inward. The transport keys a session binds to arrive as observed_local and observed_remote, values the adapter took from an authenticated connection, which the pure layer treats as claims to compare against the pinned Pairing, never as truth.
The shell is every crate allowed to touch the world: vhalla-identity (filesystem and entropy), vhalla-native (libp2p sockets), vhalla-journal and the three *-store crates (durable state, unix-gated), vhalla-rooms-node (a whole tokio runtime). Each is small relative to what it protects, and each translates between the world’s messy types (io::Result, Instant, file handles) and the core’s closed enums and fixed-width records.
why bother
The payoff is not purity for its own sake. Three concrete things fall out of the seam.
The core replays. crates/vhalla-ledger/tests/recovery_hegel.rs drives a stateful test (appends, checkpoints, snapshot, restore, replay attempts) with every choice drawn inside the loop from a seeded generator. Sixty-four cases run in milliseconds and shrink to minimal failures deterministically, because nothing in Ledger can reach a wall clock or a file. The same property is why vhalla-wire’s proptest round-trips are total: Envelope::decode is a pure function of its input bytes.
The boundary is auditable. When a reviewer asks “what can this code do,” the answer for vhalla-session is compute and return. Every effect is visible in the signature of the function that requests it. You do not have to grep for std::net: no_std makes it unnameable.
The shell is thin by necessity. Because the cores refuse to hold effect handles, the adapters cannot smuggle policy into themselves. vhalla-native’s job is reduced to: read bounded frames, observe transport keys, call Pending::respond with fresh nonces, write the returned bytes. The session logic, the part that can be wrong, is all in the pure crate where a test can reach it.