ADR 0259: after the review of 2026-10-09 — root-free as a positive measure, warrants under ids nobody can guess, canonical digests, the code shown to a person or not at all, unlinking bound to the account and possible in the first hour, break-glass named, give as built; the operator's desktop client and the controller's write to the router's state stated as known gaps (issue 344)
This commit is contained in:
+100
-20
@@ -220,7 +220,10 @@ says the refusal on K's channel:
|
||||
3. S is on the router's list of the operator's identities for K;
|
||||
4. K's declared capabilities, **read from the controller's record of its claim and never from the
|
||||
channel**, meet the option's level: `acknowledge` needs `choice` and `exact-render`; `approve` needs
|
||||
`verified-sender` as well (P1); `destroy` needs two proofs, one of them a code (P2);
|
||||
`verified-sender` as well (P1); `destroy` needs two proofs, one of them a code (P2); and for `approve`
|
||||
and above, **the controller's `root-free` verb answers, at that moment, that no agent can become root
|
||||
without a person** on the router's machine and on K's channel's machine (§8 rule 2, added on 2026-10-09:
|
||||
an absent condition is no pass);
|
||||
5. the ask is closed with a compare-and-set on its record, so a second answer loses.
|
||||
|
||||
Then it publishes the warrant on `decided.<asker>`, and sends every channel that showed the ask an edit
|
||||
@@ -243,12 +246,27 @@ met, and when. It carries no secret.
|
||||
The router puts the digest of the ask as it took it in the warrant. The asker acts only when that digest
|
||||
is the one of the ask it keeps, and only when the act it is about to perform is the one the option bound:
|
||||
an ask published again under the same id, or a record of the act changed after the ask, is refused and
|
||||
nothing is done.
|
||||
nothing is done. **Both digests are over named fields in a canonical encoding** (each value written as its
|
||||
length, a colon and its bytes, in a fixed order, versioned), never over a language's encoding of a
|
||||
structure, so any language reproduces them and a field added to an ask changes no digest until it is
|
||||
added to the encoding on purpose (the review of 2026-10-09).
|
||||
- **The router publishes each warrant under a message id nobody can guess** (a random one per publish), on
|
||||
the asker's `decided` subject, which only the router may publish; the asker deduplicates on its own record
|
||||
(a compare-and-set claim), never on the bus's duplicate window. A guessed id published first by another
|
||||
principal into the same stream would otherwise have the real warrant dropped as a duplicate (the review of
|
||||
2026-10-09).
|
||||
- **Its record of the act names the warrant**: a hand-act's `by`, `via`, `ask` and `proofs` (ADR 0234 §8),
|
||||
a delivery's transition, a silence's why. The hand-act log counts it as a person's decision, not a repair.
|
||||
- **The controller only asks and acts.** For a condition that needs the operator it asks with the
|
||||
condition's actions as options, and on the warrant it calls the verb it named, with its own grant. It
|
||||
holds no channel, no identity and no factor.
|
||||
- **A warrant is not yet the only way to these acts.** The acts a warrant authorises today —
|
||||
`mesh-delivery.release` and `stop`, the controller's `plans` go and stop, `node-service-manager.restart`,
|
||||
and `conditions silence` — are still verbs an agent may call directly with its own grant. Until
|
||||
[research 039](../01-RESEARCH/039-a-tool-that-needs-a-persons-approval/00-overview.md) (a tool that needs a
|
||||
person's approval) withholds them from every caller without a warrant, a warrant proves that the operator
|
||||
chose an act, not that only the operator could have caused it. Said here so no reader takes the warrant for
|
||||
that second guarantee.
|
||||
|
||||
### 7. Who the operator is on a channel: linking
|
||||
|
||||
@@ -260,26 +278,48 @@ met, and when. It carries no secret.
|
||||
and the name the service shows. The router shows a six-digit code on the desk, with that name, valid
|
||||
for ten minutes. The operator sends the code to the bot; the channel passes it on the `proof` subject;
|
||||
the router links the identity when it matches, and says so on every channel.
|
||||
- **A code is shown to a person or not at all** (added on 2026-10-09, after the review). The router issues a
|
||||
code only while the desk channel says it is ready, and tells the sender the desk's reason when it is not;
|
||||
a code is never queued for later. The desk is ready only while a named desk machine's notifier said within
|
||||
five minutes that it is not paused (the notifier's `paused` event), and the code goes only to such a
|
||||
machine; shown nowhere, it is said back to the router as failed. Every message carrying a code is marked secret: no
|
||||
channel and no router record keeps a copy, the desk channel stores none and hands it to the node's
|
||||
notifier as transient, and the notifier keeps it out of its history, where an agent could read it later.
|
||||
- **The desk channel runs as an account of its own**, like the router and Telegram (§8 rule 1): the codes
|
||||
pass through it.
|
||||
- **One identity per kind.** A second link of a kind is refused until the first is unlinked.
|
||||
- **Unlinking is an approve ask, answered on the account that is linked** (revised after the security
|
||||
review of 2026-10-08). It is never a plain tool: whoever unlinks the operator's account could link one of
|
||||
their own in its place. The notice of an unlink, and of any link for an hour after it, is sent to the
|
||||
account being replaced by name, before anything changes, and to every other channel.
|
||||
- **A link within a day of an unlink waits for the replaced account's approval**, asked there. When that
|
||||
account is lost with its phone, the operator unlinks at a terminal on the control node as root (`messenger
|
||||
unlink --at-the-machine`, refused over the bus), recorded and announced as break-glass, as ADR 0234 §8 does
|
||||
for a lost factor.
|
||||
- **Linking is bounded**: at most three links asked per kind an hour; the code's notice on the desk names the
|
||||
account by the name and the number the service gives, and only a channel declaring `private` (the desk)
|
||||
may show a code.
|
||||
account is lost with its phone, the operator acts as root at the router's machine (break-glass, as ADR 0234
|
||||
§8 does for a lost factor): a regular file `unlink.<kind>` or `allow-link.<kind>`, owned by root, in the
|
||||
router's own state directory (mode 0700, owned by the router's account). The router acts on it within a
|
||||
minute, deletes it, ends every open ask of its own, and announces on every channel what root did. A file of
|
||||
any other owner, or not a regular file, is refused and said: an agent without root cannot make one.
|
||||
- **Linking is bounded**: at most three links asked per account an hour and ten per kind, counted in the
|
||||
router's own state so a restart forgets nothing (a count that cannot be read or written refuses the link); the code's notice on the desk names the account by
|
||||
the name and the number the service gives, and only a channel declaring `private` (the desk) may show a
|
||||
code.
|
||||
- **The ask to unlink or relink binds the account**, not only the kind (added on 2026-10-09): a warrant for
|
||||
unlinking one account never unlinks the one linked after it. A link re-checks that its kind has no account
|
||||
when it is made, and an account just unlinked cannot link again at once from a code shown before.
|
||||
- **An account still in its first hour can be unlinked without its own approval** (added on 2026-10-09, after
|
||||
the review): it cannot approve yet, so its consent protects nothing. When another account sends `/start`
|
||||
while one is in its first hour, the router asks at the desk, as an acknowledgement bound to that account,
|
||||
whether to unlink it, and tells the sender so. If the hour has passed when the answer arrives, the unlink
|
||||
is refused. After the hour, unlinking needs the linked account's approval, or break-glass.
|
||||
- **A new link answers `acknowledge` at once and `approve` only after one hour.** The hour runs from the
|
||||
moment the link is made, is kept in the identity's record, and is said in the announcement ("it can
|
||||
approve from 15:40"). An approve answer inside the hour is refused and said on the channel. Decided by
|
||||
the operator on 2026-10-08, until an authenticator-app code exists.
|
||||
- **Once a TOTP factor exists**, linking and unlinking are destroy asks (ADR 0234 §8).
|
||||
- **A residual risk is accepted until then:** on the X11 desk an agent can read the code and send it from
|
||||
an account of its own. It is said loudly on every channel when it happens, the hour gives the operator
|
||||
time to unlink it before it can approve anything, and the factor closes it.
|
||||
- **A residual risk is accepted until then:** on the X11 desk an agent running as the operator can read the
|
||||
code and send it from an account of its own. It is said loudly on every channel when it happens; during
|
||||
the hour the account cannot approve, and the operator can unlink it from the desk without its approval (the
|
||||
rule above — corrected on 2026-10-09: before that rule, the hour gave no way to unlink an account that
|
||||
would not consent); and the factor closes it.
|
||||
|
||||
### 8. Who may say what on the bus: the trusted parties' own accounts
|
||||
|
||||
@@ -314,11 +354,34 @@ The revision rests on three rules.
|
||||
**2. `verified-sender` is honoured only where the controller can show who holds it.**
|
||||
|
||||
The controller passes a kind's `verified-sender` to the router only when its holder runs as its own
|
||||
account, with its own bus account, on a machine where the self-check finds that **no account an agent runs
|
||||
as can become root without a person** (no passwordless sudo, no membership of a group that grants root).
|
||||
Otherwise it strips the capability from the membership, and the router answers that kind only as far as
|
||||
acknowledgements, saying why. The same check holds the router: while it fails on the router's machine, the
|
||||
router issues no approve warrant, and the condition says what to change.
|
||||
account, with its own bus account, on a machine where **no account an agent runs as can become root without
|
||||
a person**. Otherwise it strips the capability from the membership, and the router answers that kind only
|
||||
as far as acknowledgements, saying why. The same check holds the router: while it fails on the router's
|
||||
machine, the router issues no approve warrant, and the condition says what to change.
|
||||
|
||||
*Revised on 2026-10-09, after the review of the first build.* **Only a positive, recent measure is a pass;
|
||||
the absence of a condition never is.** The first build let the router approve while no `agent-root`
|
||||
condition was open — so before the first self-check, after a probe error, with the conditions view lost, or
|
||||
with the router not knowing its own machine, it approved. Now:
|
||||
|
||||
- **The controller's `root-free` verb** answers, for each machine asked, whether no agent can become root
|
||||
there without a person, judged at the moment it is asked. It is a pass only when the machine names an
|
||||
account its agents run as, the machine's node-engine judged that account unable to become root (ADR 0266:
|
||||
the node-engine reads it as root, on every look, and the controller takes a verdict at most fifteen
|
||||
minutes old), and the login shell's `execute` is not served there (ADR 0268). Anything else — no agent
|
||||
account named, so agents run as the operator; a verdict missing, stale or unhealthy; a store or the bus not
|
||||
answering — is not a pass, and says why.
|
||||
- **The measure is the node-engine's, not the measured account's.** The first build asked the sudo module,
|
||||
served by the machine's runtime — whose account is the one an agent's calls run as — so the account
|
||||
being measured answered its own measure. The node-engine runs as root, which an agent that cannot become
|
||||
root cannot be. The machine's runtime account is counted as able to become root, always: the mesh's
|
||||
acting tools give it passwordless sudo.
|
||||
- **The router asks `root-free`** for its own machine and the answering channel's, when it shows an
|
||||
approve option and again when an approve answer arrives; an error, a missing machine, or a router that does
|
||||
not know its own machine is not a pass. **The controller asks it too when it composes**, and strips
|
||||
`verified-sender` from a kind without a pass; the router's live question covers a pass that ends between
|
||||
compositions. The `agent-root` condition is raised on the same judgement, so the condition and the verb
|
||||
never disagree.
|
||||
|
||||
**3. What remains, said plainly.**
|
||||
|
||||
@@ -342,9 +405,21 @@ router issues no approve warrant, and the condition says what to change.
|
||||
ADR 0266 (hq pull request 225, not yet merged): agents on the control node run under an account of their
|
||||
own, without sudo.
|
||||
- **What cannot be measured is not a pass** (added on 2026-10-09). A machine where a trusted party runs and
|
||||
the self-check cannot measure who can become root — the sudo module not assigned there, or not answering —
|
||||
raises the same urgent condition, saying it was not measured, so the router approves nothing proven
|
||||
there until it is.
|
||||
the node-engine's verdict on its agent account is missing, stale or not given raises the same urgent
|
||||
condition, saying it was not measured, and `root-free` answers no for it.
|
||||
- **A known gap, accepted by the operator on 2026-10-09: the operator's own Telegram client on a machine
|
||||
where agents run as the operator** (issue 344, hq pull request 244, not yet merged). The operator's Telegram may also run on such a
|
||||
machine — their laptop, with Telegram Desktop. There an agent can read the client's data (found readable by
|
||||
everyone) and drive the X display, and tap Approve as the operator. The router cannot tell such a tap from
|
||||
one on the phone. Closing it needs agents under an account of their own on that machine, without access to
|
||||
the operator's X display, and the client's data readable by the operator alone; none of it is built or
|
||||
measured now, and approvals are not held back for it. Until then, approve from the phone.
|
||||
- **A known gap, accepted on 2026-10-09: the controller's credential can rewrite the router's state**
|
||||
(issue 344, hq pull request 244, not yet merged). The controller composes every account and grant on the bus, so a grant withheld from
|
||||
its own credential — no write to the router's identities or asks — would be withheld by the very principal
|
||||
that writes the grants, and undone by its next composition. Moving the router's buckets where the
|
||||
controller cannot write therefore protects nothing against a controller that is not trusted, and is not
|
||||
done; the controller is trusted with every record, and root on the control node is the mesh.
|
||||
- **The controller holds the whole JetStream API** today. Narrowing it to the streams and buckets it owns is
|
||||
planned work of its own; until then the controller is trusted with every record.
|
||||
- **On the X11 desk an agent can read a code as it is shown or typed** (ADR 0234's accepted risk). The hour
|
||||
@@ -378,7 +453,12 @@ router issues no approve warrant, and the condition says what to change.
|
||||
6. **The controller as an asker**: conditions that need the operator are asked; it acts on warrants and
|
||||
records them; the words "from the mesh MCP server" give way to the router's own line, below.
|
||||
7. **The vault** gains a verb by which the operator gives a module's own secret through a hidden prompt
|
||||
on the desk, so a bot's token never passes through an agent's session.
|
||||
on the desk, so a bot's token never passes through an agent's session. *As built (2026-10-09):* the
|
||||
controller's `give`, through its `secret accept` path. It refuses `broker` and any secret the mesh makes
|
||||
itself; the prompt is written by the desk's launcher from names alone (module, secret, machine), never
|
||||
from words a caller chose, and says the controller asks; the bus lets only the controller publish the
|
||||
prompt's verb; no secret value is accepted through a verb otherwise; and every value given raises the
|
||||
urgent condition `secret-given` on every channel until the operator silences it.
|
||||
|
||||
**Where an ask can be answered is said by the router**, from the channels that qualify: "Answer on
|
||||
Telegram, or from the mesh MCP server." The asker's words never name a channel.
|
||||
|
||||
Reference in New Issue
Block a user