Skip to content

Architecture

Status: Accepted

How the system is put together, and why each structural choice is the one the requirements demand.


Every architectural decision here cites a requirement it satisfies. A technical choice justified by nothing is unjustified, and should be challenged in review.

This is the practical form of writing the functional spec first: architecture is written against 10-functional, not alongside it. Where a decision has no requirement behind it, one of two things is true — the requirement is missing, or the decision is unnecessary. Both are worth finding.

Doc Covers
system-context.md What’s inside, what’s outside, trust zones, what crosses the boundary
component-model.md Crate layout, the core/UI boundary, Docker access split, async model
data-flow.md Content pipeline, control flow, observation, seeding
platform-matrix.md The five cross-platform differences that actually bite
contracts/stack-manifest.md stack.toml — the lemonfiber ↔ lemonfiber-media-stack interface, full schema
contracts/download-client.md Registering SABnzbd/qBittorrent into a Servarr app — the fields schema and per-app category
contracts/prowlarr-application.md Registering a Servarr *arr as a Prowlarr application — the /api/v1 fields schema and per-media sync categories
contracts/jellyfin-seerr-identity.md Making Jellyfin Seerr’s identity source — driving Jellyfin’s first-run setup and Seerr’s auth/jellyfin init
contracts/versioning.md Three version identifiers, compatibility, where skew is caught
contracts/web-api.md What the web surface may ask for, how live state arrives, what guards it
contracts/design-tokens.md The brandlemonfiber-web interface — the visual language as data the app consumes

Architectural requirements use ARCH-R##, alongside feature requirements (A2-R4), governance rules (GOV-R12) and quality rules (Q-R3).

They exist because some obligations are structural rather than behavioural. lemonfiber-core must not depend on any UI crate” is not something a user observes — but it’s what makes “surfaces are renderings, never capabilities” (G1-R2) true rather than merely intended.

Everything else follows from these:

Profiles are a Compose concept, and partial stacks are the product’s core proposition. Reimplementing them against the raw Docker API would mean reimplementing Compose — and would break the guarantee that the stack runs without lemonfiber at all. → ADR-0001

Writes go through docker compose because profiles live there. Reads go through the Docker API because a 1 Hz dashboard across 19 services cannot afford process spawns. Neither path can serve both jobs. → ADR-0008

Services, profiles and forms are declared in stack.toml. Adding a service is a data change with no Rust edit and no release — which is also what makes third-party stacks possible. → ADR-0002 · stack-manifest

lemonfiber-core has no UI dependency of any kind. A surface cannot grow behaviour of its own, because behaviour lives somewhere that cannot print. → component-model

This page lives in another repository Rendered from lemonfiber/spec at 1d10402, 2026-09-09. Read the source of this page