Skip to content
df12 Productions Marrakesh Express Daemon
Docs · Architecture

Hexagonal, not by accident

mxd separates domain logic from transport and persistence using a ports-and-adapters architecture. The domain core — a library crate — owns protocol semantics without specifying how external needs are met.

Two adapters connect the domain to reality: Wireframe for transport, Diesel for storage. This boundary is enforced structurally. The domain crate has no wireframe imports. Transport-specific types do not cross the adapter threshold.

Domain core and adapters

The domain core is a library crate that owns protocol semantics: how a login is validated, how a news thread's linked-list pointers update, how privileges gate operations. It defines ports for what it needs from the outside world — a user record, a database connection, a way to push a notification — without specifying how those needs are met.

Wf

Wireframe transport adapter

Handles the Hotline binary protocol: TCP socket management, the 12-byte handshake, the 20-byte frame codec, transaction routing, XOR detection, client compatibility shims, and the streaming and push primitives that shape later subsystem work.

Db

Diesel storage adapter

Implements database operations for SQLite and PostgreSQL through a single set of repository functions, with backend selection at compile time via Cargo features.

Structural enforcement: The WireframeRouter is the sole public routing entrypoint; internal routing functions are pub(crate). New routes cannot accidentally bypass compatibility hooks because there is no public surface through which to do so.

Runtime modes and binary selection

mxd ships two operational binaries whose availability depends on the legacy-networking Cargo feature. With the feature enabled (the default), both binaries are produced. Disabling it removes the legacy frame handler, causes server::run() to delegate to the Wireframe runtime, and omits the mxd binary via required-features.

Lg

Legacy networking

The mxd binary. Uses the original frame handler with the legacy networking runtime. Available when the legacy-networking feature is enabled (the default).

cargo build produces both mxd and mxd-wireframe-server.

Wf

Wireframe-only

The mxd-wireframe-server binary only. Built with --no-default-features --features "sqlite toml".

The legacy binary is omitted entirely. This is a deliberate build configuration, not an accidental omission. Tested with make test-wireframe-only.

Shared CLI surface: Both binaries share the CLI module (mxd::server::cli), environment overrides, .mxd.toml defaults, and admin commands such as create-user (via server::admin::run_command). The operator experience is identical regardless of which binary is running.

Wireframe capabilities that power mxd

Wireframe is the transport adapter that sits behind mxd's transport port. The domain core defines what it needs — framed request dispatch, streaming bodies, server-initiated push — and Wireframe fulfils those contracts without leaking transport concerns back across the boundary.

Because the adapter owns connection lifecycle, codec selection, and middleware layering, the domain remains testable in isolation: mock the port, exercise the domain, and verify invariants without a running TCP stack. What follows are the adapter-side capabilities that make that contract work.

Framing and streaming

The adapter translates Hotline's binary wire format into the typed requests the domain port expects, and reassembles multi-fragment payloads before they cross the boundary.

  • FrameCodec trait: frame_payload(), wrap_payload(), correlation_id(), max_frame_length()
  • Fragmenter/Reassembler with MessageId, sequential FragmentIndex, max assembled size, purge_expired
  • RequestParts separation of routing metadata from streaming bodies
  • Response enum: single frame, vector, streamed, channel-backed multi-packet

Middleware and lifecycle

Connection setup, teardown, and middleware layering are adapter responsibilities. The domain sees dispatched requests; the adapter handles everything before and after.

  • from_fn async middleware with frame(), frame_mut(), Next continuation
  • Setup/teardown callbacks per connection with retained state
  • Read timeouts and preamble-timeout handling

Push queues and observability

Server-initiated push and metrics live in the adapter, fulfilling the domain's notification port without exposing transport-level queuing to the core.

  • PushQueues with high/low-priority capacities, rate limits, dead-letter queue
  • ConnectionActor::run() with biased select! over shutdown, push, response
  • Optional metrics feature: wireframe_connections_active gauge, frame counters, error counters, panic counter

Compatibility guardrails

WireframeRouter embeds a CompatibilityLayer that orchestrates request-side and reply-side hooks on every routed transaction. On the request path, it decodes XOR-encoded payloads and records the login version. On the reply path, it augments responses with client-specific extras — banner fields for Hotline 1.8.5 and 1.9 clients, omission of those fields for SynHX.

compatibility_layer.rs
// Request → Compatibility hooks → Domain → Reply hooks
impl CompatibilityLayer {
    fn on_request(&mut self, frame: &mut Frame) {
        if frame.is_xor_encoded() {
            frame.decode_xor();
            self.client_uses_xor = true;
        }
    }
    
    fn on_reply(&self, reply: &mut Frame) {
        if self.client_kind == ClientKind::Hotline185 {
            reply.add_banner_fields();
        }
    }
}

Hook ordering is verified by a spy-based test: on_request → dispatch → on_reply, asserted for both login and non-login transactions. This is the kind of property that looks obvious until someone adds a new route and forgets to wire it through the router. The test exists so that "forgets" becomes "gets a failing CI run."

Authentication and reply augmentation

Login handling splits into two responsibilities. AuthStrategy owns request validation and credential checking. LoginReplyAugmenter decorates the reply with fields determined by client compatibility metadata. Both are selected by ClientKind and orchestrated by the compatibility layer.

Client-specific behaviour

Default Hotline Supported

Conservative fallback path. Unknown and legacy versions stay protocol-valid without claiming feature parity.

SynHX Partial

Login flow omits banner fields. Broader field-level parity remains roadmap work.

Hotline 1.8.5 and 1.9 Partial

Login replies add banner fields 161/162, but user-list and messaging parity remain pending later roadmap items.

Extensibility: The split exists to give HOPE reply extensions and SynHX hashed authentication a place to land without reopening the core login handler. Those remain extension points, not current shipped behaviour.

Architecture diagrams

The architecture page is generated from docs/design.md and the three ADRs. The detailed diagrams and class relationships are pulled from the source documents.

Hexagonal architecture layers

Domain Core

Protocol semantics, business logic, pure functions

Wireframe

Transport adapter: TCP, frames, routing, compatibility

Diesel

Storage adapter: SQLite/PostgreSQL, repositories, migrations

Dependency direction: Domain depends on nothing. Adapters depend on domain. Application wires adapters to domain through dependency injection.