ADR 0203: the account's environment is one module's (seat node-environment); every module contributes variables and PATH entries, rendered by the controller as a POSIX file and as environment.d. ADR 0204: shell code is contributed to the login shell in named slots, and login-shell becomes the mesh's node-login-shell. ADR 0205: software the distribution does not package ships as a pinned archive of the module. Issue 225: undeclaring a user stops a node applying; the shell is never given back or checked. To-be 41 carries the work packages; to-be 38 WP5 points to it.
6.7 KiB
topic, status, date, deciders, reconstructed, extends
| topic | status | date | deciders | reconstructed | extends |
|---|---|---|---|---|---|
| what runs on it | accepted | 2026-10-04 | jochen | false | 02-DECISIONS/0176-the-login-shell-is-a-node-seat-and-execute-is-its-contract.md |
204. A module contributes shell code to the login shell in named slots, and the login shell is the mesh's seat
Context
Some of what a shell runs at start is code in that shell's own syntax, and it belongs to other modules:
- a prompt theme loads itself and its configuration;
- plugins load themselves;
- a version manager sources its loader.
Order matters: a prompt's instant-prompt cache must run first, and syntax highlighting last. The predecessor kept all of this in one file per machine, and installed the theme and plugins by cloning them in a hook. Research 025 measured that file as byte-identical on four machines. It carries:
- the shell's defaults;
- code belonging to four other pieces of software;
- a handful of the operator's own lines.
Nothing gave the other pieces a way in.
Two further facts bear on the seat itself:
- The seat is declared by the zsh module (ADR 0176 §1, under ADR 0126). The controller refuses a second module declaring a seat name, so fish or bash could only ever claim it, and the seat exists only while zsh's definition is registered.
- The kept region is the other way round. ADR 0174 describes a kept region as a marked block holding the operator's lines. The host built the inverse: the mesh's region is the marked block, and every byte outside it is kept, verified unchanged, and given back when the module goes.
Considered Options
- A drop-in directory that each module places a file in, and the shell sources. Rejected: every contributor names a path inside the shell module's territory (ADR 0112); order becomes a naming convention nothing checks; and nothing ties the file to the shell actually being the one it is written for.
- Facts the holder renders, through
contributes/receives. Rejected: code is not a fact, and the holder would only paste it. - Code contributed for a named shell in a named slot, assembled by the controller into the holder's file. This is what the controller already does for fail2ban jails: each module supplies text in the tool's own format, and the controller sorts and concatenates it into the holder's file without interpreting it. Chosen.
For order, numbers (10, 50, 90) were rejected: every contributor guesses one, and collisions are
silent. Three named slots were chosen: first, normal, last.
Decision
1. node-login-shell is a node seat in the mesh's own set, with the verb execute. It replaces
the module-declared login-shell. Everything else ADR 0176 decided stands: the holder sets the
account's login shell through the user shape, execute is the contract, and any node may call it.
A shell module claims the seat; none declares it.
2. Any module contributes shell code with shell: entries naming the shell they are for (zsh,
bash, fish), the slot, and the code. The controller does not read the code.
3. The holder places the code with placeholders in its own files: ${shell:<shell>:<slot>}. Each
is filled with that shell's code for that slot, from every module on the node:
- ordered by module name;
- each piece preceded by a line naming its module;
- empty when nothing is contributed.
A shell-code placeholder in a module that does not claim node-login-shell is refused.
4. The holder's duties, which are the seat's protocol:
- Source the account's environment (ADR 0203)
from the startup file every start of that shell reads. For zsh that is
.zshenv, which a script, a login andexecuteall read. - Write its interactive block at the start of the interactive startup file, so the operator's own lines run after the mesh's and win.
- Run
executeas a non-interactive login shell in the account's home:- bounded below the runtime's call limit;
- its output bounded;
- its whole process group ended on timeout.
5. The marked block is the mesh's; everything outside it is the operator's. This is how ADR 0174's "kept region" is built. That record keeps its decision and gains a note saying where the mechanism lives.
Consequences
- A prompt, a plugin and a version manager are each a module with its own package or archive, its own configuration file, and a contribution. Assigning one adds its line to the shell, and unassigning it takes the line away at the next composition.
- Assigning the shell module loses nothing the machine does today:
- what is common to every machine becomes the shell module's default or another module's contribution;
- what is the operator's stays below the block.
- The one-off migration is a person's act, listed in the shell module's documentation (ADR 0182): delete the lines the block now carries from the found file.
login-shell.executebecomesnode-login-shell.execute. Nothing has called it yet; the shell module was never assigned.- What got harder: a module wanting a line in the shell must say which shell and which slot, and a module supporting three shells writes its code three times. That is the honest cost of code in three syntaxes.
How it is checked
| Rule | Checked by |
|---|---|
| Code lands in its slot, in module order, only for its shell | the controller's shell-contribution tests |
| A shell-code placeholder outside the holder is refused | the catalogue check |
node-login-shell is the mesh's, and no module may declare it |
the seat table's tests |
The zsh block sits at the start, sources the environment from .zshenv, and holds the three slots |
the catalogue's zsh test |
execute is bounded in time and output and kills its process group |
the zsh module's tool tests over real child processes |