What the wire carries that the companion does not read
Rendered from spec at76d46f3 (2026-10-25) · its source · Edit this page
Status: Register · Measured 28 September 2026, against the companion at dab906bd and the core at 950df25b
The stack publishes sixty-two envelopes and serves fifty-seven. The companion app follows thirty-nine of them. This is the other twenty-three, written down so that what fills them is a decision somebody made rather than whatever the next person happened to notice.
Five of the sixty-two are published and not served. They are not envelopes the
app has not got to; they are envelopes it cannot reach. Three have a heading of
their own below, setup is marked so under Guided setup, and the fifth,
pull, is a line of the command line’s own, said in the next section.
It is a register of facts, not of requirements. No row here proposes a
screen. N1-R17 is the rule that matters: a field wants a requirement before it
wants a surface, and a surface built because the wire happens to carry a field is
a surface nobody asked for. Every row is an invitation to decide, and no
requirement asks for this remains a complete answer.
How it was measured
Reproducible, from the companion’s checkout:
ls vendor/lemonfiber/sdk-php/src/Generated/*Envelope.php | wc -l # 62
Tests\Support\WhatTheReadersRead::envelopes() # 39
git grep -lw '<Name>Envelope' -- app-modules bridge/src ':!*/tests/*' # 40 of the 62 names
| Envelopes the SDK ships | 62 |
| Followed by a reader | 39 |
| Not followed | 23 |
| — never named by the app’s code, tests aside | 21 |
| — named but not followed | 2 |
The companion measures the same thing and states it. Its
tests/Feature/EveryActionTheStackOffersTest.php puts every kind the SDK ships
in one of three lists: OFFERED, held to exactly the envelopes
WhatTheReadersRead::envelopes() answers with, which is thirty-nine;
ELSEWHERE, Setup and Wizard, each excused by N1-R4; and NOT_YET, the
other twenty-one. Its .docs/requirements/what-a-screen-owes.md says 62, 39, 21
and 2, and that test holds the page to what the lists come to. This register
counts the same way and agrees with it: named means named in a file under
app-modules or bridge/src outside a tests directory.
The grep answers forty names rather than forty-one, because Log is followed
without being named: a log window arrives through the SDK’s LogWindow, held in
its envelopes, and WhatTheReadersRead seats the reads on it all the same.
Three of the twenty-one are named by tests and by nothing the app ships: Start
and Wiring by the app’s root test suite, and Word by a test of the
development stand-in, under app-modules/dx/tests.
Admission and Pull are the two named without being followed. The app reads
admission through Admitted rather than through the envelope, and its code
names AdmissionEnvelope only in the development stand-in that answers for a
stack. Pull carries a bare string, and is named in that stand-in’s comments.
Word is what /api/explain answers with when one word is asked for: the app
asks for the whole glossary, which answers with Glossary. Word has a row
below; Admission and Pull have none, and these sentences are their entry.
From the core’s checkout, contract/web-api/ publishes sixty-two
kinds. Fifty-three are the variants of Outcome, in
crates/lemonfiber-core/src/app/outcome.rs, each written by the command that
produces it; the other nine are written with Envelope::new where they arise. A
kind is served when crates/lemonfiber-api answers with it: through a command
its reads (read/table.rs), its actions (actions/named.rs) or setup’s
endpoints (setup.rs) build, or an envelope it writes itself — admission,
error, job and log, and on the event stream dashboard, start and
step.
| Kinds the contract publishes | 62 |
| Served over HTTP | 57 |
| Published and not served | 5 |
plugins, wiring and substitution are not served because no read, action or
endpoint builds Command::Plugins or Command::Wiring. pull and setup are
written only by the command line, in crates/lemonfiber/src/engine.rs and
crates/lemonfiber/src/setup.rs; the HTTP crate names both only to describe them
in the contract, and setup’s endpoints answer with wizard.
pull is the command line’s alone. Its bare strings are the lines
docker compose pull prints, which the command line writes under that kind as
they arrive. No read, action or event produces it over HTTP: the pull action
answers with a job whose outcome is lifecycle, and nothing is said on the event
stream while it runs. N2-R24 asks the app to offer the fetch, and N2 records
the progress that does not reach it.
What the thirty-nine answer
Each is followed by a reader in app-modules/sdk. The requirements are the
rows in the companion’s .docs/requirements/ that name the envelope, its
reader, or what that reader builds. The last column counts the envelope’s rows in
WhatTheContractCarriesThatNothingReadsTest: each is a path the app has decided
not to read, with the reason.
| Envelope | Requirements it answers | Where the companion records them | Unread rows |
|---|---|---|---|
Alerts |
N10-R8, N10-R11 |
what-leaves-a-machine.md |
2 |
Archives |
N6-R9, N6-R11 |
what-a-machine-keeps.md |
0 |
Backup |
N6-R1, N6-R2, N6-R3, N6-R4 |
what-a-machine-keeps.md |
2 |
Bandwidth |
N10-R4, N10-R5, N10-R6, N10-R7 |
what-leaves-a-machine.md |
11 |
Bundle |
N22-R3, N22-R4, N22-R5, N22-R6, N22-R7, N22-R9 |
asking-for-help.md |
0 |
Clients |
N9-R8, N9-R9, N9-R11 |
who-gets-in.md |
0 |
Config |
F7-R3, F7-R4, F7-R10, F7-R11, F7-R12; N1-R5 |
what-a-machine-says.md; what-the-rules-keep.md |
5 |
Credentials |
N9-R1, N9-R2, N9-R3, N9-R4, N9-R11 |
who-gets-in.md |
6 |
Dashboard |
G7-R5, G7-R8, G7-R9, G7-R10, N1-R67, N1-R69, N2-R1; N23-R6, N23-R7, N23-R8 |
the-one-line.md; what-a-machine-says.md |
9 |
Doctor |
N2-R3, G4-R4, C1-R15, F7-R3 |
what-a-machine-says.md, reaching-a-stack.md |
6 |
Error |
G4-R2 |
what-the-rules-keep.md |
3 |
Forms |
N2-R7; N18-R9 |
the-screens-themselves.md; running-part-of-it.md |
3 |
FrontDoor |
N9-R9, N9-R10, N9-R11 |
who-gets-in.md |
0 |
Glossary |
N15-R3, N15-R4, N15-R9, N15-R10 |
what-the-words-mean.md |
0 |
Held |
none outright; see below | what-the-rules-keep.md |
2 |
History |
N11-R1, N11-R2, N11-R3, N11-R5, N11-R9, N11-R10 |
what-was-done-here.md |
0 |
Hosting |
N10-R10, N23-R1, N23-R2, N23-R3, N23-R4, N23-R5 |
what-a-machine-says.md |
3 |
Household |
N2-R11, N2-R14, D7-R3, D7-R4, D7-R7, N3-R4, N3-R5, N3-R6, N3-R7 |
reaching-a-stack.md, what-a-machine-says.md, what-the-rules-keep.md |
12 |
Invitation |
N9-R5, N9-R6; N21-R2, N21-R5, N21-R6, N21-R9, N21-R10 |
who-gets-in.md; asking-somebody-in.md |
0 |
Job |
N1-R41 |
reaching-a-stack.md |
1 |
Lifecycle |
N2-R7, N16-R6, N6-R1, N18-R3, N7-R5 |
what-a-machine-says.md, the-screens-themselves.md |
19 |
Log |
N2-R10; G3-R10 |
reaching-a-stack.md; what-the-rules-keep.md |
0 |
Migration |
N7-R5, N7-R6, N7-R11, N7-R12, N7-R13, N7-R14 |
moving-in.md |
5 |
Music |
N8-R3, N24-R2, N24-R10 |
choosing-how-good.md |
0 |
Outbound |
N10-R1, N10-R2, N10-R3, N10-R12, F7-R9 |
what-leaves-a-machine.md |
0 |
Preview |
N18-R4, N18-R5 |
running-part-of-it.md |
4 |
Provenance |
N11-R6, N11-R7, N11-R8 |
what-was-done-here.md |
0 |
Quality |
N8-R2, N8-R3, N24-R2, N24-R3, N24-R5, N24-R10 |
choosing-how-good.md |
1 |
Repair |
N2-R4, N2-R6 |
reaching-a-stack.md |
2 |
Restore |
N6-R1, N6-R2, N6-R5, N6-R10 |
what-a-machine-keeps.md |
6 |
SelfUpdate |
N14-R1, N14-R2, N14-R5, N14-R6, N14-R7, N14-R8 |
what-is-running-here.md |
4 |
Space |
N12-R1, N12-R2, N12-R3, N12-R4, N12-R6, N12-R9, N12-R10 |
how-full-a-machine-is.md |
8 |
Status |
N2-R7, N2-R21, B2-R15; N18-R1, N18-R3, N18-R6 |
reaching-a-stack.md, what-a-machine-says.md; running-part-of-it.md |
7 |
Stored |
N6-R7, N6-R11 |
what-a-machine-keeps.md |
1 |
Stuck |
N2-R9, N16-R10, N16-R12, N16-R13, N23-R9 |
reaching-a-stack.md, what-a-machine-says.md |
0 |
Trace |
N8-R4, N8-R5, N8-R6, N8-R8, N8-R9 |
where-an-item-got-to.md |
0 |
Update |
N2-R15, N2-R16, N2-R19, N2-R20, N2-R22, E5-R6, E5-R10; N14-R3, N14-R4 |
reaching-a-stack.md, what-a-machine-says.md; what-is-running-here.md |
21 |
Upgrade |
N24-R4 |
choosing-how-good.md |
0 |
Walkthrough |
N15-R6, N15-R7, N15-R8, N15-R9 |
watching-one-thing-arrive.md |
0 |
Held is the shelf a member sees, drawn by the household module’s
WhatYouCanWatch. The one row that names it is N3-R14, which it does not
answer: that requirement is about a player, and the companion records the player
as not built.
Error is read as the kernel’s Problem — code, severity, state, meaning and
remedies — and a severity outside the four G4-R2 defines is refused rather
than read. Its detail, cause and each remedy’s detail are the three unread
rows.
Bandwidth’s eleven are applied, which a reading that never writes always
answers the same way; clients, a surface of its own; metered, which belongs
beside the cap with its exclusions said; respite and respite_says, the
override’s countdown; rhythm and zone, the household’s hours, which are a
setting; and per direction limit and resolved, which the says sentence
already carries.
Alerts’ two are changed and rehearsed. Only a call that changes the
setting answers them otherwise, and this app makes none, which is also why
N10-R9, a rehearsed alert, is not drawn.
Hosting’s three are caveat, and per command definition and runs.
History does not answer N11-R4, the reason a change was made: the envelope
carries no such field, because the stack’s journal records none, and because
is why putting a change back stops short rather than why it was made.
Config, Doctor and Outbound carry the same origin on each setting, each
finding and each service: bundled, operator, a named plugin, a named plugin’s
override with the value it replaced, a value a removed plugin left, or unknown
with the stack’s reason. Its eight paths on each of the three are read, into one
type, and shown. A setting says its origin on every row. A check or a service
says it only where it is not the stack’s own, with one line under the list saying
what an unmarked row is, which is how the stack’s own terminal draws them. An
origin that cannot be read refuses the reading rather than defaulting to bundled
(F7-R11).
What watches the envelopes
The companion has a machine-checked register for unread fields,
WhatTheContractCarriesThatNothingReadsTest, and its rule is that every path
on an envelope the app reads is either followed to a reader or listed with a
reason. A hundred and forty-three rows, over the one thousand and twenty-seven
paths the contract declares on the thirty-nine envelopes the app reads.
It cannot see any of the twenty-three, and the reason is one line of its own scaffolding:
foreach (WhatTheReadersRead::envelopes() as $envelope) {
It walks the envelopes a reader already reads. An envelope nothing touches contributes no paths, so it can never be flagged — the blind spot is exactly the shape of the gap. The rule watches fields arriving on envelopes the app already knows about.
Whole envelopes are held by EveryActionTheStackOffersTest, which names every
kind in one of its three lists, so a kind the stack adds fails there as
unclassified. NOT_YET claims only that somebody looked: it carries no reason
and no account of what the envelope holds. This register is that account.
Connecting it together
The surface that lets an operator wire the stack up. The app draws none of it.
Its kernel holds the types a wiring would be read into, recorded in the
companion’s connecting-the-stack.md.
| Envelope | What it carries |
|---|---|
plugins (not served) |
Which plugins are installed, and what installing, updating or removing one came to — install, update and removal — and every capability the operator chose one of a plugin’s services to fill, substituted. The whole of what an operator opens a plugin screen to see. |
wiring (not served) |
Which service asks for which capability, and how each is settled: outright, each, contested with its claimants, chosen with what it was chosen over, whose choice it was — stack or operator — and why, or unfilled. Plus unfilled: services asking for something nothing answers. |
substitution (not served) |
Applying one service in place of another for a capability: who asked for it, what it was was and is now, the setting that carries it, and leaves_unfilled — what the swap breaks. |
catalogue |
What each service is for — every service the stack’s manifest holds, not only the ones a form would start: its criticality, what it describes, and without_it — what the household loses by not having it. Plus removed, with the reason and what replaced it. |
Three of these are not served at all. wiring, substitution and
plugins are published in the contract and generated into the SDK, and no HTTP
route or action produces any of them. Nothing under lemonfiber-api’s src
names any of them. Its read table, read/table.rs, declares every read it
serves, with catalogue, forms and alerts among them and none of these
three. The single action route accepts only the names in actions/named.rs’s
OFFERED — forty of them, and not one is a plugin, wiring or substitute verb —
so an action outside that list is refused before a command is built. Recorded in
lemonfiber#731, which is
open.
The kind exists because the command line produces it, which is why the
generated surface lists it. contract/web-api-surface/index.json maps every kind to
its type, including these three, and that file is not evidence of a route.
Outcome::Plugins is produced by Command::Plugins, which the core and the
command line run; nothing on the HTTP surface asks for it. A register that read
the surface map would call this served, and be wrong.
The plugin lifecycle plugins reports on is built at the command line: installing,
rehearsing, updating and removing are plain subcommands (F6-R13), and an unmet
capability is refused by name (F6-R10). wiring and substitution each have
a settled report in the core, and Command::Wiring produces Outcome::Wiring
from the command line. What all three lack is a route.
Serving them is required, and scheduled. F6-R14 requires what is installed
to be a read of the web API, F6-R15 requires installing, updating and removing a
plugin to be actions of it, and F6-R16 requires each of those to be rehearsable
there. F4-R26 requires the wiring to be a read, F4-R27 requires choosing which
service fills a capability to be an action, and F4-R28 requires that choice to be
rehearsable. 0.18.0, which is planned, locks all six as goals, with F4-R29,
which gives the choice an optional reason on both surfaces.
So they are a different kind of row from everything else here. Every other
envelope in this register is a decision waiting to be made, or, for setup and
wizard, one N1-R4 has made; these three have no decision about the companion
that can reach them while no route serves them. The companion records the same
for N5: N5-R2, N5-R4, N5-R7, N5-R10, N5-R11 and N5-R13 cannot be
answered there until a read exists.
The wiring row names a choice nothing can make. The core will not resolve
a contested capability — the command line says nothing wires to it until the
operator chooses, and lemonfiber does not choose by install order. The contract
models that choice, down to whose: 'operator'.
There is nowhere in the companion to make that choice, and nowhere on the wire
to read that it is waiting. F4-R26 requires the read that says it is waiting,
and F4-R27 the action that makes it: for a contested capability too, recorded
and journalled as the command records it, and read back as the operator’s, with
their reason where they gave one (F4-R29).
Guided setup
The companion excuses setup and wizard under N1-R4: setup is performed at
the machine, and the app says so rather than offering it.
| Envelope | What it carries |
|---|---|
wizard |
Where first run has got to: at one of fourteen named stages from welcome through vpn, credentials, library, household to review; whether it asks, whether it was offered, and the phase. |
setup (not served) |
What setup settled: the data_root, which protocols are on, the service_user, and whether it was applied, abandoned or was already-set-up. Written by the command line’s own setup; the setup endpoints answer with wizard. |
step |
One step of a walkthrough while it runs, on the event stream: its stage — choosing, searching, grabbing, downloading, importing, scanning, available — with what was said and the detail under it. The app follows a running walkthrough by asking after its job, and reads its lines from the record it finishes with. |
word |
One entry of the glossary, alone: word, short, deep, also_called and forms, from GET /api/explain?word=. N15-R11 asks for it where the glossary the app holds has no entry. |
Putting things back
Copies are read: archives, backup and restore, above. These two put the
stack back without one.
| Envelope | What it carries |
|---|---|
undo |
Putting back one run of changes — the last repair, or a run named by the stamp its entry in the history carries: what was reversed, each with what it does — remove, restore, delete, withdraw, repin or reconfigure — what was left and noted and why, and whether it was rehearsed. |
reset |
Putting configuration back to lemonfiber’s own: what was reverted, with a diff per path, and which connections went with it. |
Moving in
The survey of what is already on the machine, migration, is read, drawn by
WhatIsAlreadyOnThisMachine. These are the acts, and the companion records
N7-R1 to N7-R4, carrying out a mode, and N7-R7 to N7-R10 and N7-R15 to
N7-R17, the seed action, as asked for and not drawn.
| Envelope | What it carries |
|---|---|
adoption |
Taking over an existing setup: what would be upgraded, service by service, with the verdict, the reason, whether it was refused and whether it wants a backup first. Plus a stance and what to back_up. |
beside |
Standing lemonfiber beside a setup already on the machine — A5’s beside mode: where each service would listen instead, as ports with the service, the port it moves from and the one it moves to; where the Compose file that says so was written; the refusal where nothing was; and a stance of unchanged, pending, blocked or applied. |
replacement |
Standing in place of a setup already on the machine — A5’s replace mode, which stops that project’s containers and deletes none: what would_stop, what was stopped, what is still_running, the refusal where nothing was, and a stance. N7-R18 asks for it. |
import |
What an existing setup brings across: what is carried, what is not_carried and because of what, and what would_carry. |
seed |
What the stack wires up on the operator’s behalf, each wiring with a severity that names the breakage and its remediation, and what is unsupported and why. |
A service’s life
| Envelope | What it carries |
|---|---|
start |
A bare string: one line of what a start is waiting for, on the event stream, the newest replacing the one before. N2-R23 asks for it. |
uninstall |
Taking the stack off: an agreement, the bytes, what is foreign and left behind, and a confidence naming what could not be read. N13 asks for it. |
watch |
The data-root guard, once the data location it was guarding was lost: the forms it stopped, whether stopping them succeeded (stopped) and the reason, and on a rehearsal what it would keep — the root, how often it looks (every) and the command it would run. Over HTTP it is a job that lives only while somebody asks about it. N23-R11, N23-R12 and N23-R13 ask for it, and N8-R7 for the reason it stopped. |
version |
What is running and what has shipped: the binary, and a changelog of releases with what each delivers, what it patches, whether it is user_facing and whether it was withdrawn. |
stop-seeding |
Stopping a torrent: the download, its standing — never imported, seeding at a ratio, or left alone — and what stopping it costs. |
Household and access
| Envelope | What it carries |
|---|---|
removal |
Removing a person’s access: how widely it was revoked — everywhere, the media server only, or nothing — how many requests they had, whether they ask through the request service, and the findings. N13-R1, N13-R3 and N13-R19 ask for it. |