ADR 0259 (proposed): let any module ask a person and any channel answer, so Telegram is not built into the controller

This commit is contained in:
jochen
2026-10-09 15:36:36 +02:00
committed by jschoubben
parent 80b02dc9a0
commit 9f66452fff
3 changed files with 333 additions and 1 deletions
+5 -1
View File
@@ -477,7 +477,11 @@ asking, never the act. **Decided by** ADR 0152, 0175, 0234, 0272.
([ADR 0253](../02-DECISIONS/0253-a-condition-says-itself-to-the-operator-in-plain-words-beside-its-summary.md)).
- **ask** — a request for the operator's input, of a declared kind (yes-no, one-of, text, number,
date, acknowledge). An **authorising ask** is one whose answer performs an action; the controller
holds it and checks its proofs (ADR 0234).
holds it and checks its proofs (ADR 0234). *Proposed in ADR 0259:* the router holds every ask, and
the asker performs the chosen option on a **warrant**.
- **warrant** — the router's record that a person chose one option of one ask: the ask, the option,
who chose (the operator, as a channel kind's identity), through which channel, the proofs and when,
published for that asker alone. The only thing an asker acts on (ADR 0259, proposed).
- **operator message / input** — what arrives on `intake`, once the router has checked the sender
against the controller's list of the operator's identities: a **trusted** one is an operator message,
addressed to an agent by `@name` or thread or else to the **responder** (`@mesh`, the router's own
@@ -0,0 +1,322 @@
---
topic: the mesh
status: proposed
date: 2026-10-08
deciders: jochen
reconstructed: false
supersedes-in-part:
- 0234-the-mesh-holds-a-conversation-with-its-operator.md
extends: 02-DECISIONS/0234-the-mesh-holds-a-conversation-with-its-operator.md
---
# 259. An ask is a message on the bus, any channel answers it, and the asker acts on a warrant
## Context
On 2026-10-08 the operator chose Telegram as the next channel for the operator's decisions. "Needs you:
release it, or stop it" must be answerable from the phone, with real authorisation, and without an agent
session.
What the mesh had on its main branches that day:
- **The desk performs only an acknowledgement**
([ADR 0258](0258-a-desk-click-performs-only-an-acknowledgement-and-an-answer-is-a-one-time-ticket.md)).
Release, stop, start and restart are said in words: "from the mesh MCP server; this notification cannot do
it". The messenger (the `operator-channel` seat's holder) calls `conditions silence` itself when the
operator chooses Silence on the desk.
- **The messenger holds the Telegram client in its own process**, says conditions there as words without
buttons, and keeps the bot token as its own secret `telegram-token`. Its status said on 2026-10-08 that the
token held is not a bot token: no bot has been given yet.
- **[ADR 0234](0234-the-mesh-holds-a-conversation-with-its-operator.md) §8 gives authorising asks to the
controller**: it holds the ask, keeps the operator's identities and factors, checks the proofs and
performs the action itself. None of it is built ([to-be 46](../03-DESIGN/01-to-be/46-the-conversation-with-the-operator.md)
phase 5).
- **Any node's tool runner may call any tool on any node** (ADR 0175). A tool call therefore proves
nothing about who made it: the laptop's runner carries the agents' calls. What the bus *does* prove is the
publisher of a module's events and a seat's events: only the principal granted a subject may publish it,
which is why a condition's events need no signature (design 29 §2).
The operator's directions, 2026-10-08, on the first proposal (a Telegram path through the controller):
- "I don't think we should mangle Telegram into our controller. Now we're using Telegram, but tomorrow we
could be using Signal. And today the controller needs to receive a human decision, but tomorrow a
completely different module might require human approval."
- **Two generic boundaries.** Asking a person is a mesh-wide service any module uses; the asking module
performs the action itself, with its own grants, and records who decided. Channels are pluggable: a
channel renders the question, receives the answer and verifies the sender by its own means. Nothing
outside the channel module knows which service it talks to.
- **The controller only asks and acts.**
- **The flow is native to the bus**: a request is a message, pending requests survive a restart, each
answer is recorded once, the asker reads its request's state from a bucket, and the bus's permissions
decide who may publish what. Events carry no secret; request and reply may. No HTTP between modules.
### Against GENESIS
- **Core value — sovereignty.** Telegram stays an outside dependency taken deliberately (ADR 0234), and
is now one module that can be removed without touching anything that asks.
- **Core value — failure must be loud.** An ask that no channel can carry is refused in words to its asker
at once, never queued in silence. An answer that is refused is said on the channel it came from.
- **Effect — "when something genuinely needs a decision, you are asked, with the context".** Any module
can now ask, not only the controller.
## Considered Options
### Where an ask is held
1. **The controller holds authorising asks** (ADR 0234 §8). Rejected by the operator: the next module
that needs a person (a backup module, the delivery owner) would ask through the controller, and the
controller would perform actions in other domains.
2. **A new seat for decisions, beside `operator-channel`.** Rejected: an ask needs the router's routing,
the work context and the content rule; a second seat would ask the router for each of them, and two
holders would keep two lists of who the operator is.
3. **The `operator-channel` seat's holder, the router, holds every ask, authorising or not, and judges
the answers.** Chosen. ADR 0234 already gives it ordinary asks; this adds the authorising ones.
### Who performs the action
1. **The decision holder calls the asker's verb.** Rejected: the router would act in every domain with a
grant to every verb, which is what ADR 0258 took away from the desk.
2. **The asker performs on hearing "yes" from anyone.** Rejected in ADR 0234: a relayed "yes" is not an
answer.
3. **The asker performs on a warrant**: the router's record that the operator chose an option, published
on a subject only the router may publish, for that asker alone. Chosen. The bus proves the warrant's
source as it proves a condition's. A signature was weighed and rejected: it needs a key in every asker
and proves nothing the server's permissions do not.
### How an ask, a warrant and an answer travel
1. **Tool calls** (an `ask` verb, an `answer` verb). Rejected: any tool runner may call any tool, so the
router could not tell the controller's ask from an agent's, nor a channel's answer from anybody's.
2. **HTTP between modules.** Rejected by the operator's direction, and it would carry its own
authentication beside the bus's.
3. **Subjects whose last token names the publisher**, granted to that publisher alone, on streams that
survive a restart. Chosen. The mesh already grants a machine its own token on a node seat's events
(ADR 0219); this is the same rule for a caller's module and a channel's kind.
### Who is the operator on a channel
1. **A setting naming the operator's account on the service.** Rejected: any principal granted
`mesh-controller.settings` may write a setting, agents included, so the operator's identity would be
anyone's to set.
2. **The channel module's own allow-list.** Rejected in ADR 0234: the channel would decide who the
operator is.
3. **The router's list of the operator's identities, per channel kind, written only by linking.**
Chosen. A channel module passes the sender as the service authenticated it, as an opaque string; the
router alone compares it with its list.
### How a channel takes its answers (Telegram)
1. **A webhook.** The control node is the stably reachable node, so it could take one. Rejected all the
same: it needs a public route, a name and a certificate into the control node, guarded only by a
secret header; a stolen token could point it elsewhere.
2. **Long polling**, as ADR 0234 chose. Chosen: outbound only, unaffected by the control node's address,
answered as fast as a webhook, and a stolen token can read updates (visible as a conflict) but not
inject one.
### Where the desk's buttons live
1. **Inside the router**, as its desktop adapter (to-be 46 §11). Rejected: the router would know one
channel, and the operator asked that the desk be just another channel.
2. **The dunst module** claims the channel. Rejected: dunst holds a node seat on each machine, and a
channel kind has one holder for the mesh.
3. **A small module of its own, kind `desktop`**, sending through each machine's `node-notifier` and
reading dunst's `action-chosen`. Chosen.
## Decision
### 1. The words
- An **ask** keeps its glossary sense; an **authorising ask** is one whose chosen option the asker
performs. Each option carries its **assurance level**: `acknowledge`, `approve` or `destroy` (ADR 0234's
tiers, the glossary's word).
- A **warrant** (new) is the router's record that a person chose one option of one ask: the ask, the
option, who chose (the operator, as a channel kind's identity), through which channel, the proofs, and
when. It is the only thing an asker acts on.
- **The router** keeps its sense (the `operator-channel` seat's holder). It now holds every ask and
issues warrants. It never performs an option.
### 2. The boundaries
| Party | Does | Never |
|---|---|---|
| **an asker** (any module; first the controller) | publishes an ask under its own name; reads its asks' state; acts on its own warrants with its own grants; records the warrant in its own record of the act | performs without a warrant; reads another asker's asks |
| **the router** (`operator-channel` holder, the `messenger` module) | keeps asks, tokens and the operator's identities; routes an ask to the channels whose capabilities meet each option's level; judges each answer; issues one warrant per ask | calls an asker's verb; knows a service's API |
| **a channel** (a module claiming a kind on `channel` and `intake`: `telegram`, `desktop`) | shows what the router sends, with the router's tokens on its buttons; passes back the token chosen and the sender as its service authenticated it | judges whether the sender is the operator; holds another channel's secret |
### 3. The subjects, streams and buckets
Every name below follows the mesh's existing shapes (`mesh.seat.<seat>.<accept|event|tool>.…`, a module's
state bucket, the EVENTS stream). `<asker>` is the asking module's name; `<kind>` a channel's kind.
| What | Subject or bucket | Kept in | Publisher (granted) | Reader (granted) |
|---|---|---|---|---|
| an ask | `mesh.seat.operator-channel.accept.ask.<asker>` | the seat's work queue `SEAT_OPERATOR_CHANNEL` (kept 7 days, removed when the router has recorded it) | the asker, its own name only | the router |
| an ask cancelled by its asker | `mesh.seat.operator-channel.accept.cancel.<asker>` | the same | the asker, its own name only | the router |
| the asks and their state | the router's bucket `asks`, key `<asker>.<id>` | its own state, closed records kept 30 days | the router | the router; each asker reads its own prefix only (direct get) |
| answer tokens | the router's bucket `tokens`, key the token, each with a time to live equal to its ask's expiry | its own state | the router | the router |
| the operator's identities | the router's bucket `identities`, key `<kind>.<identity>` | its own state | the router | the router |
| a warrant, or an ask's end without one | `mesh.seat.operator-channel.event.decided.<asker>`, message id `<asker>.<id>` | EVENTS (7 days), deduplicated by the message id | the router only | the asker, its own name only, through its durable consumer |
| show, edit or say on a channel | `mesh.seat.channel.accept.<show\|edit\|send>.<kind>` | the seat's work queue `SEAT_CHANNEL`, one worker per kind | the router | the holder of that kind only |
| an answer chosen, a link asked | `mesh.seat.intake.event.<choice\|link>.<kind>` | EVENTS | the holder of that kind only | the router |
| a code typed (a link code; later a TOTP code) | `mesh.seat.intake.proof.<kind>`, core request and reply | nothing: never on a stream | the holder of that kind only | the router, which answers |
**The grants, composed by the controller as it composes every bus account:**
- **A caller-named verb.** A seat may mark an accepted verb or an emitted event as named by its caller
(`ask.*`, `cancel.*`, `decided.*`). A module that uses the seat is granted to publish the accept, and to
subscribe the event, with its own module name as the last token, and no other. A tool runner carrying
modules is granted the same for each module it carries. The controller asks as `mesh-controller`.
- **A kinded bench** (ADR 0234 §2, built here for `channel` and `intake`). A claim carries `kind`; two
claims of one kind are refused. A holder subscribes its own kind's accepts, publishes its own kind's
events and proofs, and nothing of another kind. Each kind has its own worker on the seat's queue, so a
channel that is offline keeps its work and holds up no other.
- **A seat's `proof` verbs** are core request and reply on `mesh.seat.<seat>.proof.<kind>`: no stream
covers them, so a code is never persisted (the mesh's rule: events carry no secret).
- **A record readable by its asker.** The router's `asks` bucket is read by an asker under its own prefix
only: direct get on `$KV.<bucket>.<asker>.>`.
- **No `answer` tool.** A tool runner may call any tool, so an answer that arrived as a tool call could
come from anyone. An answer arrives only as an intake event of a kind.
### 4. An ask
An ask carries: an `id` the asker chose, unique to it; the **question** in plain words, held to the plain
rule and the content rule (ADR 0253, ADR 0234 §6); its **options**, at most four, each a label, what it
does in plain words, its assurance level and an option id; **who may answer** (the role `operator`, the
only role today); an **expiry**; **what happens on expiry**, in words the operator is shown ("the delivery
stays held"); and optionally a subject it is about (a condition's key), so a newer ask about the same
thing replaces the older.
- **Refused at once, in words, on the decided subject**: an ask whose words break a rule, whose options
include a level no ready channel can carry (`destroy` until a TOTP factor exists), or a fourth open ask
from one asker.
- **An authorising ask never defaults.** At its expiry the router ends it with `expired`; the asker does
what it said it would.
- **An approve option expires within 24 hours; a destroy option within 10 minutes of being shown**
(ADR 0234).
### 5. The router judges an answer
On an intake `choice` of kind K carrying token T and sender S, the router refuses at the first failure and
says the refusal on K's channel:
1. T is known, unused, unexpired, and was issued for kind K;
2. the ask is open, and the option T names belongs to it;
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);
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
naming the outcome and removing its buttons.
**The desk declares no `verified-sender`**, so it answers only `acknowledge` options, as ADR 0258 decided.
Telegram declares it while placed where no agent runs as the operator, so it answers `approve`.
### 6. The warrant, and the asker's act
The warrant carries the ask's id and asker, the option, `by` (the operator, as a kind's identity: "the
operator, via telegram, user id verified"), the channel module, the proofs present, the assurance level
met, and when. It carries no secret.
- **The asker acts once per ask id**, keeping that in its own record, and only on a warrant from its own
`decided` subject.
- **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.
### 7. Who the operator is on a channel: linking
- **The router's list is the only list.** It is changed only by linking and unlinking, never by a setting.
- **Linking:** the operator sends the bot `/start`. The channel publishes `link.<kind>` with the sender
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.
- **One identity per kind.** A second link of a kind is refused until the first is unlinked. Unlinking
(a tool of the router) is said on every channel, the unlinked one included.
- **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, and the factor closes it.
### 8. What is superseded in ADR 0234
- **§8 "an ask whose answer performs an action is held by the controller", the controller's verbs
`authorise request`, `authorise answer` and `authorisations`, and the controller performing:** the
router holds the ask and issues a warrant; the asker performs.
- **§3 "the controller's list of the operator's identities"** and §8 "the factors' secrets are the
controller's own": the identities and, when built, the TOTP factor and its recovery codes are the
router's own state, with ADR 0234's rules on showing them once, at a terminal, never on a channel.
- Everything else in ADR 0234 stands: the tiers and proofs, the kinded benches, the content rule, the
desk never authorising, long polling, every tier completable on the away channel.
### 9. The first channels and the order of work
1. **The controller:** the kinded bench with `kind` on a claim; the `channel` and `intake` seats; the
caller-named and per-kind grants; `proof` subjects; the asker's read of its own prefix.
2. **The shared library (mesh-sdk):** an asker's client (ask, cancel, read its state, take its warrants)
and a channel's (take show, edit and send for a kind; publish a choice or a link; send a proof).
3. **The messenger** becomes the router of this record: asks, tokens, identities, judging, warrants,
expiry, linking. Its Telegram client and its desk adapter leave it.
4. **`telegram`**, a new module claiming kind `telegram` on both benches: long polling, buttons carrying
only a token, `/start` linking, ADR 0234's defects D1 to D10 fixed in the move, its token as its own
secret. **`desk-channel`**, a new module claiming kind `desktop`.
5. **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.
6. **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.
**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.
## Consequences
- **A channel is a module and nothing else changes when one is added.** Signal or mail is a module
claiming a kind and declaring capabilities.
- **Any module may ask a person**, through one seat, and acts on the answer with its own grants. ADR
0258's desk Silence becomes an ask of the controller's, and the messenger loses its grant to
`conditions silence`.
- **The controller loses three verbs it never built** and gains an asker's client.
- **The router's state grows**: asks, tokens and identities, each a bucket of its own.
- **Harder:** an asker must keep which warrants it acted on; the shared library carries it.
- **Harder:** the controller composes two new grant rules, and an asking module must use the seat in its
manifest to be granted anything.
- **The bot token moves** from the messenger's own secret to the `telegram` module's.
- **Accepted risk:** a linked Telegram account, if taken, can approve (announced, reversible), never
destroy. Linking before a TOTP factor exists carries the X11 risk in §7.
## How it is checked
| Rule | Checked by |
|---|---|
| An asker publishes asks only under its own name, and hears only its own warrants | controller grant tests: a module using `operator-channel` is granted `accept.ask.<itself>` and `event.decided.<itself>`, no wildcard; a live drill publishing under another name is refused by the server |
| Only the router publishes a warrant | controller grant test: `event.decided.*` is in no principal's publish list but the holder's |
| A channel publishes only its own kind's answers, links and proofs | controller grant tests over a kinded claim; a second claim of a kind is refused |
| A code is never on a stream | controller test: no stream's subjects cover `mesh.seat.*.proof.>` |
| A token is used once, expires, and is bound to its ask, option and kind | router tests: the same token twice; a token past its expiry; a token from another kind; a token of a closed ask |
| An answer from an identity not linked is refused and said | router test |
| A level a channel's declared capabilities do not meet is refused | router tests: an approve option answered from `desktop`; capabilities taken from the controller's record, a channel's own claim ignored |
| A second answer to one ask loses | router test: two answers raced; one warrant |
| An authorising ask never defaults | router test: expiry publishes `expired`, never an option |
| An ask no ready channel can carry is refused at once, in words | router test |
| Linking needs the code shown on the desk, and is said on every channel | router tests: a wrong code, a late code, a second link of a kind |
| The asker acts once per warrant and records the warrant | shared library test; controller test reading the hand-act after a warrant |
| The controller holds no channel code | review; the controller's module imports no channel package |
| End to end | live drill: a held delivery released from the phone; the hand-act reads "the operator, via telegram, user id verified" |
## References
- [ADR 0234](0234-the-mesh-holds-a-conversation-with-its-operator.md) (the conversation, the tiers and
proofs; §3 and §8 in part superseded here)
- [ADR 0258](0258-a-desk-click-performs-only-an-acknowledgement-and-an-answer-is-a-one-time-ticket.md)
(the desk performs only an acknowledgement; one-time answer tokens)
- [ADR 0253](0253-a-condition-says-itself-to-the-operator-in-plain-words-beside-its-summary.md) (a
condition's actions and plain words)
- [ADR 0246](0246-a-seats-new-verb-is-promised-before-it-is-required.md) (a new seat's verbs are required
from the start)
- [ADR 0255](0255-the-bar-takes-blocks-as-data-from-the-module-that-knows-the-hardware-placed-where-the-machine-has-it.md)
(a generic boundary in front of pluggable modules)
- [To-be 46](../03-DESIGN/01-to-be/46-the-conversation-with-the-operator.md) §3, §4, §10, §11 and §13
- [Issue 320](../04-ISSUES/320-the-operator-could-not-read-the-meshs-notifications/00-report.md),
[issue 324](../04-ISSUES/324-a-desk-click-could-answer-in-the-operators-name/00-report.md)
@@ -316,6 +316,12 @@ is not exempted.
## 10. Asks that authorise
*Proposed 2026-10-08, [ADR 0259](../../02-DECISIONS/0259-an-ask-is-a-message-on-the-bus-any-channel-answers-it-and-the-asker-acts-on-a-warrant.md):*
the router, not the controller, holds authorising asks, keeps the operator's identities and judges each
answer; the asker performs the chosen option on a warrant the router publishes for it alone; asks, warrants
and answers travel on the bus under subjects whose last token names their publisher. Once that record is
accepted, this section and §3, §4, §11 and §13 are amended to it.
### The verb table and the controller's verbs
- **The controller's verb definition gains `authorises`:** the tier, and the arguments that make up