← The specification

The plugin catalogue and what vouches for a plugin

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

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


Purpose

Say where a plugin comes from, and what that origin does and does not vouch for.

Two audiences pull in opposite directions here. Someone who wants Plex wants to type one command and have it work, and is not equipped to audit a manifest. Someone who evaluates this project publicly wants to know they are not locked into a curated list, and will install from their own fork on the first afternoon. Serving only the first produces a walled garden; serving only the second produces an ecosystem where nothing is vouched for and the first audience gets hurt.

So there are two routes, and lemonfiber is explicit about which one an operator is on. A reviewed catalogue where every plugin landed by human review, validated in CI and signed. And any git source the operator names, installed on the same technical terms but with lemonfiber stating plainly that nothing has reviewed it and refusing to present its proofs as vouched for.

The distinction is not a warning banner. It is carried on the plugin for as long as it is installed, so an operator debugging a strange stack six months later can see which of their plugins nobody ever read.

Behaviour

The catalogue is a repository, and it is reviewed like one

The catalogue is lemonfiber-plugins — a git repository in the project’s own organisation. A plugin enters it by pull request. Its CI validates every manifest against the published schema, runs every declared proof, and refuses a contribution whose proofs do not pass. A proof failing on a recording its manifest declares it fails on is reported as failing as declared (F10), is not a pass, and does not refuse the contribution; one that passes there has gone stale, and does. The acceptance bar is mechanical before it is human. A human then reads the diff, which is possible precisely because a plugin is data.

Releases from the catalogue are signed. A release is a tag whose assets are an index — each plugin id with its origin, the revision that was reviewed and the digest of its manifest there — and a signature over exactly that index, made with a key pair whose public half lemonfiber carries and checks offline (ADR-0034). An operator installing from the catalogue is relying on: a schema that was checked, proofs that ran, a maintainer who merged it, and a signature that ties what they fetched to what was reviewed.

An operator may install from anywhere, and is told what that means

lemonfiber plugin install accepts a plugin’s name, a git URL or a local path. A name is resolved only through a catalogue index whose signature verified, and installs the revision the index names. A git URL may name a revision (<url>@<rev>); either way it is pinned to the commit it resolves to, and that commit is what is recorded. The manifest is validated, the proofs are run, and the same rules about undeclared reach apply — the technical bar does not move. What moves is what lemonfiber claims: an unreviewed plugin is installed as unreviewed, said at install and carried on the plugin afterwards.

lemonfiber never refuses an operator their own source. F1 exists because a tool that stands between an experienced operator and their stack loses exactly the audience that decides whether anyone else adopts it.

What a plugin may reach is part of what is vouched for

A plugin declares the hosts its recipes reach. That declaration extends the account of what leaves this machine — which is currently a closed list, and whose closedness is a promise the product makes. The promise is not abandoned; it is made precise. The account becomes these destinations, and these others which this named plugin added — so an operator reading it sees the same completeness they had before, plus who is responsible for each addition.

A plugin reaching a host it did not declare is a refusal, not a log line.

Provenance is verifiable, not asserted

Where a plugin came from, which revision, and what signed it are recorded when it is installed and readable afterwards. A plugin whose source can no longer be reached does not become untrusted retroactively — but it does become unupdatable, and that is said rather than discovered at the next attempt. Whether a source can still be reached is asked when the operator lists what is installed or runs the doctor, and never in the background; a local path that is no longer there cannot be reached.

The catalogue is not a single point of failure

Nothing about the design requires the catalogue to exist for lemonfiber to work, or for a plugin already installed to keep working. If the catalogue is unreachable, installed plugins are unaffected and installing from a named source still works. The catalogue is a convenience and a review mechanism, not a runtime dependency.

On the companion

The catalogue is offered on the companion (N5), and N5-R8 holds it to the same thing this feature does: an entry carries what the household is without it, rather than a name and a description. N5-R9 requires a removed entry to name what replaced it, so an operator looking for something they remember is answered rather than told it does not exist.

States

State Meaning
reviewed Installed from the catalogue: schema-checked, proofs run in CI, human-reviewed, signed
unreviewed Installed from an operator-named source; technically validated, vouched for by nobody
signature-unverified The source claimed a signature that does not verify; refused
source-unreachable The origin can no longer be fetched; the installed plugin still runs and cannot be updated
undeclared-host A recipe reached a host the manifest did not declare; refused

Edge cases

Situation Behaviour
The catalogue is unreachable Installed plugins are unaffected; installing from a named source still works. The catalogue is not a runtime dependency.
A plugin’s signature does not verify Refuse. A claimed-but-invalid signature is worse than none and is treated as such.
An operator installs from their own fork Allow it, install it as unreviewed, and carry that on the plugin for as long as it is installed.
A catalogue plugin and a named-source plugin have the same name Refuse and name both origins; the operator chooses which they meant.
A plugin declares a host it never reaches Allowed. Declaring more than is used is conservative, and the account says what was declared.
A recipe reaches a host the manifest did not declare Refuse the plugin. The account of what leaves this machine must stay complete.
A plugin’s source disappears after installation Keep it running, mark it unupdatable, and say so rather than failing at the next update attempt.
A plugin installed from a local path whose directory is gone The same: it cannot be reached, so it is unupdatable.
An install by name finds an index whose signature does not verify, or none Refuse the install and resolve nothing through that index. Installing from a named source is unaffected.
An install by name finds a verified index older than one this machine has already verified Refuse the install and resolve nothing through it. A release the catalogue has replaced is not served again by anyone able to repeat an old answer.
A catalogue contribution’s proofs fail in CI Refuse the contribution before merge. The catalogue never serves a plugin whose proofs did not pass.
A contribution’s proof fails on a recording its manifest declares it fails on Accept it on that proof, and name it as failing as declared with its reason, never as passed (F5-R13, F10-R13).
An operator asks whether a plugin was reviewed Answerable from the installed plugin itself, not only from the catalogue.

Acceptance criteria

ID Requirement
F5-R1 A reviewed plugin catalogue MUST exist as a git repository in the project’s organisation, and every plugin MUST enter it by a pull request that passes the catalogue’s checks, with signed commits, merged by a maintainer.
F5-R2 Superseded by F5-R13: a proof failing as declared does not refuse a contribution. The number is not reused.
F5-R3 Catalogue releases MUST be signed, and a signature that does not verify MUST be refused.
F5-R4 An operator MUST be able to install a plugin from a git source or local path they name.
F5-R5 A plugin not installed from the catalogue MUST be installed as unreviewed, MUST be said so at install time, and MUST carry that for as long as it is installed.
F5-R6 Technical validation — schema, declared reach, proofs — MUST be identical for reviewed and unreviewed plugins.
F5-R7 Where a plugin came from, at which revision, and what signed it MUST be recorded at install and readable afterwards.
F5-R8 A plugin MUST declare every host its recipes reach, and reaching an undeclared host MUST be refused.
F5-R9 The account of what leaves this machine MUST include hosts declared by installed plugins, and MUST attribute each to the plugin that declared it.
F5-R10 An unreachable catalogue MUST NOT affect installed plugins, and MUST NOT prevent installing from a named source.
F5-R11 A plugin whose origin can no longer be fetched MUST keep working and MUST be reported as unupdatable.
F5-R12 Two plugins of the same name from different origins MUST be refused, naming both origins.
F5-R13 The catalogue’s CI MUST validate every manifest against the published schema and run every declared proof, and MUST refuse a contribution any of whose proofs is neither passed nor failing as declared (F10-R13). A proof failing as declared MUST be named as such and MUST NOT be counted as passed.
F5-R14 An install by name MUST resolve the name only through a catalogue index whose signature verified against the key lemonfiber carries, and MUST install the revision that index names.
F5-R15 An install by name MUST refuse a verified index whose serial is lower than the highest serial this machine has verified, and MUST resolve nothing through it.