ADR 0272: the review's answers — every line a call, the socket grants nothing, the terminal does not follow assign, each claim and how it is checked
- §3: the node-engine vouches and decides nothing; deadline first, other namespaces refused, each account bounded and answered busy; the subject is the machine's own in the writers table. - §4: phase 1 refuses the agent account (phase 2 answers it); an ordinary settings line takes the settings verb's path; the controller's module is assigned at the terminal only, so the terminal does not follow assign; 'no wider than today' holds only while its two measured facts hold. - §5: every line is a call, followed to its answer; 'nothing ran' only of a line nobody took, otherwise 'not known' with the call; the answer is its asker's alone. - §7: after the four merge, the operator assigns mesh-cli to every node. - How it is checked: each claim with its test or measurement, the MESH_VERB refusal in mesh-cli named a courtesy, and the sudo and agent-account facts read on the control-node today.
This commit is contained in:
+112
-55
@@ -109,20 +109,28 @@ their refusals already use: `mesh-cli settings set zsh '{"execute":"withhold"}'
|
|||||||
|
|
||||||
`mesh-cli` holds no credential and reads no file of the mesh. It does one thing: it asks the node-engine on its own
|
`mesh-cli` holds no credential and reads no file of the mesh. It does one thing: it asks the node-engine on its own
|
||||||
machine over a local socket, and prints what comes back. A process that carries `MESH_VERB` (a verb's own process)
|
machine over a local socket, and prints what comes back. A process that carries `MESH_VERB` (a verb's own process)
|
||||||
is refused at once, and told why: a verb is never a person.
|
is refused at once, and told why. **That refusal is a courtesy, not the gate**: a process can unset a variable.
|
||||||
|
The gate is the controller's judgement of the account the node-engine vouches for (§4).
|
||||||
|
|
||||||
### 3. The node-engine vouches for the account
|
### 3. The node-engine vouches for the account; the socket grants nothing
|
||||||
|
|
||||||
The node-engine serves a socket every local account may connect to. For each connection it:
|
**The socket grants nothing; the controller judges.** What the node-engine adds is a fact only it can read, the
|
||||||
|
asking account, and it decides nothing with it. The node-engine serves a socket every local account may connect
|
||||||
|
to. For each connection it:
|
||||||
|
|
||||||
1. reads the connecting process's user id from the kernel (`SO_PEERCRED`), and names the account from the
|
1. sets the connection's deadline before anything else, and reads the connecting process's user id and process id
|
||||||
machine's user database;
|
from the kernel (`SO_PEERCRED`);
|
||||||
2. sends the line, the account and the user id to the controller on `mesh.control.<node>.cli`, as a request on
|
2. refuses a process whose mount or user namespace is not the engine's own: a container running with the
|
||||||
core NATS. Only that node's engine may publish under `mesh.control.<node>.`: its bus account is granted that
|
operator's user id shares the number, not the machine;
|
||||||
prefix and nothing of any other node's, so **the node is a fact the bus server enforces and the account is a
|
3. takes one of at most two lines at once for that user id and eight for the machine, and answers *busy* at once
|
||||||
fact the kernel gives**. The CONTROL stream names its own subjects, so the request is never persisted;
|
when there is none, so one account cannot hold the socket from another and accepting never waits;
|
||||||
3. answers the program with what the controller answered, or says that the controller did not answer within its
|
4. names the account from the machine's user database, reads the line, and sends the line, the account and the
|
||||||
call bound.
|
user id to the controller on `mesh.control.<node>.cli`, as a request on core NATS. Only that node's engine may
|
||||||
|
publish under `mesh.control.<node>.`: its bus account is granted that prefix and nothing of any other node's,
|
||||||
|
and the writers table names the subject as the machine's own, so **the node is a fact the bus server enforces
|
||||||
|
and the account is a fact the kernel gives**. The CONTROL stream names its own subjects, so the request is
|
||||||
|
never persisted;
|
||||||
|
5. follows the line to what came of it (§5) and answers the program.
|
||||||
|
|
||||||
Whether the node's agents run under an account of their own is not in the request. The node-engine already
|
Whether the node's agents run under an account of their own is not in the request. The node-engine already
|
||||||
says it: its judgement of the agent account is in its health statement (ADR 0266 §3, §4), and the controller reads
|
says it: its judgement of the agent account is in its health statement (ADR 0266 §3, §4), and the controller reads
|
||||||
@@ -135,45 +143,64 @@ the terminal is already the operator's, and §4 makes no other node the terminal
|
|||||||
rule stands. The socket is a file under the node-engine's runtime directory with no network address, so nothing
|
rule stands. The socket is a file under the node-engine's runtime directory with no network address, so nothing
|
||||||
dials it from another machine. Nothing sent to it acts on the node: the node-engine runs nothing it receives and
|
dials it from another machine. Nothing sent to it acts on the node: the node-engine runs nothing it receives and
|
||||||
changes nothing; it adds what the kernel says and sends the request out over its own link, to a controller that
|
changes nothing; it adds what the kernel says and sends the request out over its own link, to a controller that
|
||||||
judges it like any other caller. Only the node's operator account and its agent account are answered, and both
|
judges it like any other caller. Only the node's operator account is answered in phase 1 (§4), and it already
|
||||||
already reach the mesh's verbs through the mesh MCP server (§4).
|
reaches the mesh's verbs through the mesh MCP server.
|
||||||
|
|
||||||
### 4. The controller judges, and runs the line
|
### 4. The controller judges, and runs the line
|
||||||
|
|
||||||
A line from node N, vouched as account A, **runs as the controller's terminal** when A is N's operator account
|
A line from node N, vouched as account A, **runs as the controller's terminal** when A is N's operator account
|
||||||
([ADR 0181](0181-the-operator-account-is-a-node-fact-and-a-home-is-a-placement-root.md)) and either:
|
([ADR 0181](0181-the-operator-account-is-a-node-fact-and-a-home-is-a-placement-root.md)) and either:
|
||||||
|
|
||||||
- N is the control-node; or
|
- N is the control-node: the one node the controller's module is assigned to. Two or more, and nobody is the
|
||||||
- N names an agent account of its own (ADR 0266 §1), and the controller's own record of the engine's judgement,
|
terminal; or
|
||||||
heard within ADR 0266 §4's fifteen minutes, says it cannot become root without a person.
|
- (phase 2) N names an agent account of its own (ADR 0266 §1), and the controller's own record of the engine's
|
||||||
|
judgement, heard within ADR 0266 §4's fifteen minutes, says it cannot become root without a person.
|
||||||
|
|
||||||
The controller runs it as it runs a verb's line, in a fresh process of its own binary with its own environment, but
|
The controller runs it as it runs a verb's line, in a fresh process of its own binary with its own environment, but
|
||||||
without `MESH_VERB`, and records the caller as *A through mesh-cli on N*.
|
without `MESH_VERB`, and records the caller as *A through mesh-cli on N*.
|
||||||
|
|
||||||
**A line from the node's operator account or its agent account that is not the terminal is an ordinary
|
**Which node is the control-node does not follow a verb.** Whoever chooses where the controller's module runs
|
||||||
call.** It goes through exactly what the `command` verb does with a line: its refusals today, and its list of
|
chooses the terminal, so the module is assigned and unassigned at the controller's terminal only; `assign` and
|
||||||
|
`unassign` naming it through a verb are refused with the reason.
|
||||||
|
|
||||||
|
**A line from N's operator account that is not the terminal is an ordinary call.** A `settings` line is composed as
|
||||||
|
the settings verb composes its own and meets that verb's refusals, the terminal-only keys among them; any other
|
||||||
|
line goes through exactly what the generic `command` verb does with a line: its refusals today, and its list of
|
||||||
reads once ADR 0266 §7 builds one. It runs with `MESH_VERB=mesh-cli`, so every terminal-only change is refused
|
reads once ADR 0266 §7 builds one. It runs with `MESH_VERB=mesh-cli`, so every terminal-only change is refused
|
||||||
with the controller's own reason. The answer adds one sentence saying why this account on this node is not the
|
with the controller's own reason. The answer adds one sentence saying why this account on this node is not the
|
||||||
terminal: it is the agent account, or the node's agents may run as its operator. These two accounts already
|
terminal: the node's agents may run as its operator. That account already reaches the mesh's verbs through the
|
||||||
reach the mesh's verbs through the mesh MCP server, so this widens nothing.
|
mesh MCP server, so this widens nothing.
|
||||||
|
|
||||||
**A line from any other account is refused**, root and every service account included, and the answer says
|
**A line from any other account is refused, and runs nothing**: root, every service account, and **in phase 1 the
|
||||||
which account asked and that `mesh-cli` answers the node's operator and agent accounts only. Run as yourself,
|
agent account too**. The answer says which account asked and that `mesh-cli` answers the node's operator account.
|
||||||
not under `sudo`.
|
Phase 2 answers a node's agent account as an ordinary call (§7).
|
||||||
|
|
||||||
**The control-node rule is no wider than today.** On the control-node the operator's account holds `sudo`, and
|
**The control-node rule is no wider than today, while today lasts.** On the control-node the operator's account
|
||||||
until ADR 0266 is switched on so do the agents running as it: either could run the controller's binary as root
|
holds passwordless `sudo`, and until ADR 0266 is switched on agents run as that account: either could run the
|
||||||
with its environment. `mesh-cli` gives that account the same reach and no more. Once agents there run as `agent`,
|
controller's binary as root with its environment, which is the terminal. `mesh-cli` gives that account the same
|
||||||
`agent` is never the terminal.
|
reach and no more. Once agents there run as `agent`, `agent` is refused. The claim rests on two measured facts and
|
||||||
|
fails if one changes alone: if the operator's account lost its passwordless `sudo` while agents still ran as it,
|
||||||
|
`mesh-cli` would give those agents a terminal they no longer had (§ How it is checked).
|
||||||
|
|
||||||
### 5. What comes back
|
### 5. What comes back
|
||||||
|
|
||||||
|
**Every line is a call**, run, recorded and answered as a seat's verb is: the bus lets a request be answered for a
|
||||||
|
minute only, so a line answered when it ended lost every answer after that and was said as nothing having run. The
|
||||||
|
controller answers within ten seconds that a line is still running, with its call; the node-engine follows the
|
||||||
|
call, as the same account on the same node, until the line ends. The call's record keeps the line's command word
|
||||||
|
and who asked, never the rest of the line, and the controller's journal says each line, its call, who asked where
|
||||||
|
and how it ran. **The answer is its asker's alone**: kept in the memory of the controller that ran it for the asker
|
||||||
|
to follow, never on the bus, and never shown by `calls`, which answers anyone who may call the seat. Lines running
|
||||||
|
at once are bounded (eight); one over is answered busy, and nothing runs.
|
||||||
|
|
||||||
The answer is the command's standard output, its standard error, its exit status, whether it ran as the terminal,
|
The answer is the command's standard output, its standard error, its exit status, whether it ran as the terminal,
|
||||||
and why or why not. `mesh-cli` prints the two streams and exits with the status. An answer larger than one bus
|
and why or why not. `mesh-cli` prints the two streams and exits with the status. It says one of three things,
|
||||||
message is cut there and the cut is said; it is never silently short. Standard input is not carried: a command
|
never confused: what the line printed; that **nothing ran**, and why, only of a line no controller took; or that
|
||||||
that reads it gets nothing and fails saying so. Streaming the output and carrying the input are later steps.
|
**what came of it is not known** — no answer in time, so it may have run — naming the call to look at. An answer
|
||||||
A path in a line names a file where the controller runs, read by the controller's account, never a file beside
|
larger than one bus message is cut there and the cut is said; it is never silently short. Standard input is not
|
||||||
`mesh-cli`: settings are given inline, `"$(cat settings.json)"` in the operator's own shell.
|
carried: a command that reads it gets nothing and fails saying so. Streaming the output and carrying the input
|
||||||
|
are later steps. A path in a line names a file where the controller runs, read by the controller's account, never
|
||||||
|
a file beside `mesh-cli`: settings are given inline, `"$(cat settings.json)"` in the operator's own shell.
|
||||||
|
|
||||||
### 6. What is shared, and where it lives
|
### 6. What is shared, and where it lives
|
||||||
|
|
||||||
@@ -191,11 +218,13 @@ A path in a line names a file where the controller runs, read by the controller'
|
|||||||
### 7. In what order it is built
|
### 7. In what order it is built
|
||||||
|
|
||||||
1. **Phase 1.** The SDK package; the node-engine's socket and its request; the controller's answer, with the
|
1. **Phase 1.** The SDK package; the node-engine's socket and its request; the controller's answer, with the
|
||||||
control-node as the terminal for its operator account; the `mesh-cli` module on every node. From then on the
|
control-node as the terminal for its operator account; the `mesh-cli` module. **After the four merge, the
|
||||||
operator types `nox …` on the control-node, and gets the ordinary calls on every other node. An agent account
|
operator assigns `mesh-cli` to every node** at the terminal; until then it is on no node. From then on the
|
||||||
is answered as soon as a node names one (ADR 0266 §1), whichever phase is live.
|
operator types `nox …` on the control-node, and gets the ordinary calls on every other node. The agent account
|
||||||
2. **Phase 2.** The agent-account route of §4 on a node other than the control-node, once ADR 0266 runs there
|
is refused.
|
||||||
(on the operator's own machines after the follow-up decision ADR 0266 names).
|
2. **Phase 2.** The agent account: answered as an ordinary call on a node that names one (ADR 0266 §1), and the
|
||||||
|
agent-account route of §4 to the terminal on a node other than the control-node, once ADR 0266 runs there (on
|
||||||
|
the operator's own machines after the follow-up decision ADR 0266 names). Its tests are written with it.
|
||||||
3. **Later.** Streamed output and standard input; a second factor for a node whose agents must share the
|
3. **Later.** Streamed output and standard input; a second factor for a node whose agents must share the
|
||||||
operator's account.
|
operator's account.
|
||||||
|
|
||||||
@@ -214,29 +243,57 @@ A path in a line names a file where the controller runs, read by the controller'
|
|||||||
- The alias hides any other `nox` in interactive zsh (Arch's `python-nox` installs `/usr/bin/nox`);
|
- The alias hides any other `nox` in interactive zsh (Arch's `python-nox` installs `/usr/bin/nox`);
|
||||||
`command nox` still reaches it.
|
`command nox` still reaches it.
|
||||||
- The node-engine gains a local socket every account on the machine may reach. It accepts one line per
|
- The node-engine gains a local socket every account on the machine may reach. It accepts one line per
|
||||||
connection, bounded in size and in time, runs nothing itself, and is never reachable from another machine
|
connection, bounded in size, in time and per account, runs nothing itself, and is never reachable from another
|
||||||
(ADR 0004 stands, §3).
|
machine (ADR 0004 stands, §3).
|
||||||
|
- Assigning the controller's module moves to the terminal: an agent, or any caller of `assign`, can no longer move
|
||||||
|
the controller, and so the terminal, to a node of its choosing.
|
||||||
- The controller's own binary is still never on `PATH`. A record that says to type
|
- The controller's own binary is still never on `PATH`. A record that says to type
|
||||||
`mesh-controller <command>` at the terminal means `mesh-cli <command>` on the control-node from this record on.
|
`mesh-controller <command>` at the terminal means `mesh-cli <command>` on the control-node from this record on.
|
||||||
|
|
||||||
## How it is checked
|
## How it is checked
|
||||||
|
|
||||||
- **The SDK:** the request and the answer survive a round trip, and a request over the bound is refused.
|
Each claim, and what holds it:
|
||||||
- **The bus request:** a test in the node-engine and one in the controller hold the same field names.
|
|
||||||
- **The node-engine:** a test connects to the socket as the test's own process and the request carries that
|
- **The account is the kernel's** (§3): the node-engine's test connects as the test's own process and the request
|
||||||
process's user id and account; a connection whose credentials cannot be read is refused, never sent as
|
carries that process's user id and account; credentials that cannot be read are refused and never sent as
|
||||||
anyone; a line over the bound is refused.
|
anyone (mesh-host `internal/meshcli`).
|
||||||
- **The controller:** tests hold that a line from the control-node's operator account runs without `MESH_VERB`;
|
- **The socket grants nothing; the controller judges** (§3, §4): the node-engine's package for it runs no command
|
||||||
that a line from the agent account, or from another node's operator account without a judged agent account,
|
and changes no file — its one act is the forward, which review holds, and its tests hold every refusal before
|
||||||
runs with `MESH_VERB=mesh-cli` and a terminal-only change is refused with the reason; that a line from any
|
it. The controller's judge is tested case by case: the control-node's operator
|
||||||
other account, root included, is refused and runs nothing; that a judgement older than fifteen minutes is not
|
account runs without `MESH_VERB` (the test reads the variable in the process it ran); another node's operator
|
||||||
one; and that an ordinary line meets every refusal of the `command` verb.
|
account runs with `MESH_VERB=mesh-cli`; every other account, root and the agent account included, is refused
|
||||||
- **The catalogue:** the module's check passes, its archive places one file, and its shell contribution is the
|
and runs nothing; two control-nodes make nobody the terminal (mesh-controller `cmd/mesh-controller`, the
|
||||||
alias.
|
mesh-cli tests).
|
||||||
- **Live, at acceptance (the operator's view):** on the control-node, in the operator's own shell,
|
- **The node is the bus server's fact**: the writers table names `mesh.control.*.cli` as the machine's own, and
|
||||||
`nox settings show zsh --node <node>` answers as the terminal, and a terminal-only `nox settings set …` is taken.
|
its test refuses another machine or a module granted it (mesh-controller `internal/broker`).
|
||||||
On another node, the same `settings set` is refused and says why. From `agent`, once that account exists, it is
|
- **Another namespace is refused, and one account cannot hold the socket**: the node-engine's tests refuse a peer
|
||||||
refused too.
|
whose namespaces differ, and answer a second line of a busy account *busy* at once while the first is still
|
||||||
|
asked.
|
||||||
|
- **An ordinary `settings` line takes the settings verb's path**: the controller's test composes each form as the
|
||||||
|
verb does and refuses any other; an ordinary repair without `--why` meets the generic verb's refusal.
|
||||||
|
- **The control-node does not follow a verb**: the controller's test refuses assigning or unassigning its module
|
||||||
|
with `MESH_VERB` set, and lets the terminal do it.
|
||||||
|
- **A line is never lost, and never said as nothing having run when it may have**: the controller's test follows a
|
||||||
|
line that outlasts the answer window to its answer, over a bus, and refuses a follow by another account or
|
||||||
|
node; the node-engine's tests follow a running line to its answer, say *not known* with the call when no
|
||||||
|
answer comes, and say *nothing ran* only of a request nobody took. A line's record keeps neither its line's
|
||||||
|
values nor its answer on the bus, and `calls` never shows its answer (mesh-controller `internal/link`).
|
||||||
|
- **The `MESH_VERB` refusal in `mesh-cli` is a courtesy**: its test holds that the program refuses before asking;
|
||||||
|
nothing rests on it, since the controller judges the vouched account whatever the program does.
|
||||||
|
- **The module places one file**: the catalogue's test holds `mesh-cli` to one archive resource unpacking one
|
||||||
|
compiled Go bundle into `/usr/local/bin`; its shell contribution is the alias.
|
||||||
|
- **No wider than today, while today lasts** (§4), measured on the control-node, not assumed: the `sudo` module's
|
||||||
|
own check lists the operator account's rules (on 2026-10-08 it found every command as root without a password,
|
||||||
|
ADR 0266's context), and `mesh-controller.node` for the control-node shows whether an agent account is named.
|
||||||
|
Both are read before phase 1 is delivered and said in its pull request. Read on 2026-10-09: the `sudo` module's
|
||||||
|
`sudo_rules` on the control-node answered `passwordless_all: true` for the operator account (two rules, every
|
||||||
|
command as root, no password), and `mesh-controller.node` named no agent account there. Neither is checked by the controller at
|
||||||
|
each line: if the operator account's passwordless `sudo` goes while agents still run as it, this claim is false,
|
||||||
|
and the remedy is ADR 0266's agent account, not a check here.
|
||||||
|
- **Live, at acceptance (the operator's view):** after `mesh-cli` is assigned to every node, on the control-node,
|
||||||
|
in the operator's own shell, `nox settings show zsh --node <node>` answers as the terminal, and a terminal-only
|
||||||
|
`nox settings set …` is taken. On another node, the same `settings set` is refused and says why. A line that
|
||||||
|
runs longer than a minute (`nox push <node>`) is followed to its answer.
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user