← The specification

Architecture Decision Records

Rendered from spec at76d46f3 (2026-10-25) · its source · Edit this page

Status: Accepted

An ADR captures a decision that was contested — where a reasonable engineer could have chosen otherwise. It records the alternatives and why they lost, so that revisiting the decision later starts from evidence rather than from scratch.

Rules

  1. ADRs are immutable once Accepted. To change a decision, write a new ADR that supersedes the old one and link both ways. The record of changing your mind is the valuable part.
  2. Only genuinely contested decisions get an ADR. “We use serde for JSON” is not a decision, it’s a default.
  3. Numbers are permanent and never reused.

Index

# Decision Status
0001 Docker Compose as the execution engine Accepted
0002 Profiles are facts; forms are intent Accepted
0003 Rust + Ratatui for the CLI/TUI Accepted
0004 Four repos rather than a monorepo Accepted
0005 lemonfiber embeds the stack at build time Accepted
0006 One /data mount, subdirectories beneath Accepted
0007 Jellyfin supports both Docker and native Accepted
0008 Compose CLI for writes, Docker API for reads Accepted
0009 Pin every action to a SHA, including our own reusables Accepted
0010 A container engine is a v2 detail, not a v1 assumption Proposed
0011 The web surface is a fifth repo behind the JSON contract Proposed
0012 The web surface ships inside the binary, as a pinned submodule Proposed
0013 The API gets an SDK, and the SDK gets its own repo Proposed
0014 Every SDK generates its types from one artefact the server emits Proposed
0015 The documentation site renders content it does not own Proposed
0016 Dependabot updates the dependencies, and the gate cites for it Proposed
0017 The companion app is a fourth surface, reached over the network Accepted
0018 A paired fingerprint decides which machine the app will talk to Accepted
0019 A screen paints what it knows before it reaches the stack Accepted
0020 An action the stack did not receive did not happen Proposed
0021 A plugin is data, and lemonfiber writes its container Proposed
0022 A recipe declares pairs, not lists Proposed
0023 A pin is a digest, and a signature is a different question Proposed
0024 Data about this installation opens; behaviour lemonfiber implements does not Proposed
0025 A pin is the certificate’s digest, and it is what permits an address off this machine Accepted
0026 A screen may hold the event stream, and holding it is its one read Proposed
0027 A member plays what the core authorised, on the member’s own account Accepted
0028 A supported major is data the stack declares, proved by its own recordings Accepted
0029 A household service declines an invitation, holding one media-server key and nothing else Accepted
0030 An assertion declared to fail on a recording is reported as failing as declared Accepted
0031 Installing a plugin and choosing a filler are web API writes; replacing a credential is not Accepted
0032 The request service reaches the *arrs and the media server through a gate, holding only the gate’s tokens Accepted
0033 Each image lemonfiber builds for the stack has its own repository, named for the service it runs Accepted
0034 A catalogue release is signed with a key pair, and lemonfiber carries the public half Accepted
0035 What the phone keeps, and how Accepted
0036 A plugin may come from any source the command takes, over the web API too Accepted
0037 An integration key is a credential the web API may mint, where the operator proves themself again Accepted
0038 lemonfiber ships as an image that holds the host’s Docker Accepted
0039 Each repository records what it built, one row per requirement Accepted
0040 Three sites, each for one reader, and the specification on the frontpage Accepted
0041 Every part is a plugin, and the core speaks contracts Accepted
0042 The bundle is first-party plugins, pinned and embedded Accepted
0043 The core writes every Compose entry from one typed service model Accepted
0044 A phone proves the operator with a device key, so it never holds the password Accepted
0045 Every bundled part has its repository now, and its adapter later Accepted
0046 The stores are filled once, at the cutover, and a secret never enters the journal Accepted
0047 A phone hears its stacks without a vendor’s push service, in the kinds the core declares Accepted
0048 A part without a port is proved by running its own program Accepted
0049 A moved pin is recorded again by the core Accepted
0050 A contract hands its filler all it enforces, and reads back no secret Accepted
0051 A fresh claimant answers its probe, and the core asks what nothing else does Accepted
0052 A curator’s downloads complete into one folder, and the core tells each part what it holds Accepted
0053 A part claimed first is claimed over a contract, and an indexer source is pulled over its own Accepted

Not here: licensing. It’s a project-governance choice, not an architectural one — no component’s design depends on it. See licence rationale.

Template

# ADR-NNNN: <title>

**Status:** Proposed | Accepted | Superseded by ADR-NNNN
**Date:** YYYY-MM-DD

## Context
What forces are at play? What makes this non-obvious?

## Decision
What we're doing, stated plainly.

## Alternatives considered
| Option | Why it lost |

## Consequences
### Positive
### Negative
### Neutral

## Revisit if
Concrete conditions that would justify a new ADR.