← The specification

Capabilities of the bundled services

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

Status: Accepted · Audience: Operator · Area: F — Extensibility


Purpose

Make the capability vocabulary real by having something implement it, and convert the bundled stack’s wiring to ask in it.

F4 defines what a capability is: a named, contracted thing a service can do, carrying probes that demonstrate the contract holds. On its own that is a document. A vocabulary nothing speaks is not a vocabulary, and a capability nothing implements is one nobody has tested — so it would be published, versioned, and wrong in ways no one would discover until a plugin author trusted it.

This is where the twenty-three bundled services declare what they can do, pass the probes that prove it, and the wiring that currently names them is changed to ask instead. It is the larger half of the work by a wide margin and the smaller half of the thinking, which is why it is separable: F4 is a model and a set of rules, and this is a stack’s worth of applying them.

The payoff is the one the whole extensibility area exists for. Once the request service asks for an identity source rather than for Jellyfin, a plugin claiming that capability can stand in — and nothing that consumes it has to know a plugin exists.

Behaviour

Every bundled service declares what it can do

Every one of them is given capabilities and the probes that demonstrate them. This is not bookkeeping. The bundled stack becomes the reference implementation of every core capability, so a plugin author has something concrete to satisfy rather than a paragraph to interpret.

The declarations live in each part’s first-party plugin manifest, which the bundle pins, not in lemonfiber’s source. A fork that adds a service declares its capabilities the same way, and adding one still requires no code change (F1-R5).

No capability is published that nothing demonstrates

Every capability in the core vocabulary is declared by at least one bundled service. A capability with no implementation is a contract nobody has had to satisfy, and it will be subtly wrong in exactly the way that costs a plugin author a day.

That constraint runs the other way too: it stops the vocabulary growing speculatively. A capability earns its place by something needing it, not by seeming likely to be useful.

The wiring asks, rather than naming

The stack asks for a capability rather than naming a service. The request service asks for an identity source; the media-filing services ask for a download client. Each ask is the asking part’s own, in its plugin manifest’s [[ask]], and where two parts claim what is asked, the stack’s choice between them is the bundle’s, with its reason.

Wiring by name stays possible where it is genuinely about one service — but it is the exception, declared in the bundle and shown as one, so the reason it is by name is visible to whoever reads it next.

A capability lost at a pin bump is a failure, not a surprise

A bundled service that stops satisfying a capability it declares — because an upstream release moved something — fails exactly as a plugin failing a claim does. The probes are the source of truth for both, and there is no separate, gentler treatment for the services this project happens to ship. A version does not go out with a bundled claim its own probes refuse.

The evidence is the plugin’s, in the part’s plugin manifest: each capability a service provides carries a claim binding every probe to a request and a response recorded from the pinned image, under recordings/<service>/ in the part’s repository (stack-manifest, ARCH-R136). A pin bump that moves a digest re-records in the same change. Every claim is judged against its recordings on every change to the stack and again before lemonfiber is tagged; a refuted probe refuses both, and one that cannot be judged is reported unproven rather than counted (ARCH-R137).

Substitution stops being a rewrite

The test of whether this landed is not that the declarations exist. It is that replacing a bundled service with a plugin claiming the same capability requires no change to anything that consumes it. If a consumer still has to be found and edited, the wiring was not really converted.

On the companion

What the bundled services already answer is what makes a plugin’s claim a contest rather than a filling, so the companion renders both from here (N5). N5-R5 makes a capability nothing fills a state with what is asking for it, rather than a fault: a stack with an unfilled capability is not broken, and a surface that showed it as broken would be wrong about the product.

States

State Meaning
declared A bundled service declares the capability in the manifest
demonstrated The capability’s probes ran against the bundled service and passed
unproven The probes could not be run; the claim is not treated as met
refuted The probes ran and failed; the version does not ship
unimplemented A capability the vocabulary publishes that no bundled service declares
by-name A wiring deliberately kept to a named service, shown as the exception

Edge cases

Situation Behaviour
A bundled service loses a capability at a pin bump The same failure as a plugin failing a claim. The probes are the source of truth for both, and the version does not ship.
A capability is published that nothing bundled declares Refuse it into the vocabulary. An untested contract is worse than an absent one, because a plugin author will trust it.
Two bundled services genuinely do the same thing Both declare it. That is what a vocabulary is for, and the operator chooses which fills it (F4-R8).
A wiring cannot honestly be expressed as a capability Keep it by name, and show it as by-name so the exception is visible rather than assumed to be an oversight.
A fork adds a service It declares capabilities in its own manifest, with no lemonfiber change, like every other thing a manifest carries.
A probe needs a service that is not running Report unproven rather than failed. Nothing was demonstrated, and nothing was refuted.
Converting a wiring changes behaviour subtly It is a change like any other: journalled, and reversible through the same machinery.

Acceptance criteria

ID Requirement
F9-R1 Every bundled service MUST declare the capabilities it provides, and the declaration MUST live in the first-party plugin manifest the bundle pins rather than in lemonfiber’s source.
F9-R2 Every capability a bundled service declares MUST pass that capability’s probes, and a version MUST NOT ship while a bundled claim’s probes refuse it.
F9-R3 Every capability in the core vocabulary MUST be declared by at least one bundled service, and a capability nothing implements MUST NOT be published.
F9-R4 The bundled stack’s wiring MUST ask for capabilities rather than name services, each ask declared in the asking part’s plugin manifest, and the stack’s choice of filler for a contested ask MUST be declared in the bundle; a wiring genuinely about one service MUST be a by-name wiring in the bundle and MUST be shown as one.
F9-R5 A bundled service that stops satisfying a declared capability MUST fail in the same way a plugin failing a claim does, with no gentler treatment for being bundled.
F9-R6 Replacing a bundled service with a plugin that claims the same capability MUST require no change to anything that consumes that capability.