ADR 0234, to-be 46: let the operator answer, and let only the controller act on an answer

Research 028 graduates: the mesh needs to ask as well as tell, over
channels that are seats rather than code inside one notifier, and an
action a person must approve cannot rest on a desk click an agent can
forge. TOTP verified by the controller is the proof; a key stays optional.
Amends to-be 45 section 5 as ADR 0227 left it to.
This commit is contained in:
jochen
2026-10-06 16:39:11 +02:00
parent e77ce351cd
commit fb30b75f20
8 changed files with 777 additions and 8 deletions
+11
View File
@@ -82,6 +82,17 @@ another — and a mesh you cannot name precisely is a mesh two people describe d
capacity-1 seat is exclusive (one holder); a higher-capacity seat is a **bench** (several holders
coexist). The first bench is `mesh-dns-resolver`, a *replicated* mesh seat: one holder per machine,
each on record and each answering the same names ([ADR 0223](../02-DECISIONS/0223-the-mesh-has-two-resolvers-and-a-machine-lists-only-them.md)).
The second sort is the **kinded** bench: holders are different modules, each claiming one **kind**,
and a verb's subject carries the kind. `channel` and `intake` are the only ones
([ADR 0234](../02-DECISIONS/0234-the-mesh-holds-a-conversation-with-its-operator.md)).
- **channel / intake** — the two kinded benches the mesh talks to its operator through: `channel`
sends, `intake` turns what arrives into one envelope. A holder of either declares **capabilities**
from the fixed vocabulary `channel-capabilities/1`. Not "notifier" (that is the desktop's node seat,
one holder of kind `desktop`) and not "bot" (that is one service's account).
- **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** (a verified sender, a TOTP code, optionally a security key's touch)
([ADR 0234](../02-DECISIONS/0234-the-mesh-holds-a-conversation-with-its-operator.md)).
- **claim** — a module taking a spot on a seat. `claims: [{name, scope}]` in a manifest. A
mesh-scoped exclusive claim is how the mesh says "there is one of me". A foundation seat is
named after the server it guards: the `mesh-controller`, `postgres` and `lavinmq` modules claim
@@ -1,5 +1,5 @@
---
status: active
status: graduated
initiated: 2026-10-04
touches:
- 04-ISSUES/187-the-mesh-tells-nobody-when-it-stops-working/00-report.md
@@ -18,7 +18,9 @@ touches:
- 02-DECISIONS/0228-a-value-given-by-hand-lives-only-until-its-modules-first-good-start.md
- 02-DECISIONS/0230-a-consumer-the-mesh-stops-asking-for-is-retired-and-deleted-only-by-a-person.md
- 02-DECISIONS/0232-a-binding-to-a-consumers-data-moves-only-by-a-person.md
became: []
became:
- 02-DECISIONS/0234-the-mesh-holds-a-conversation-with-its-operator.md
- 03-DESIGN/01-to-be/46-the-conversation-with-the-operator.md
---
# 028 — The mesh's output channel
@@ -144,6 +144,15 @@ answering back except through the mesh's own verbs; a message carries roles and
address, a path or a secret, refused by the holder otherwise (028 Q8). Routing by presence, quiet hours,
answering back and the external dead-man service stay open in 028, whose graduation amends to-be 45.
> **The mechanism changed — 2026-10-06, by [ADR 0234](0234-the-mesh-holds-a-conversation-with-its-operator.md).**
> What still stands: the output channel is built first in this minimal form, as Phase 1 of to-be 45, and
> everything above about conditions, deduplication, the content rule and the watcher. What moved, as
> this paragraph said it would on 028's graduation: "no answering back" no longer holds. The operator
> answers and is asked over channels that are holders of two kinded benches, `channel` and `intake`,
> not contributions to `operator-channel`, whose holder becomes the router; an answer that performs an
> action is checked and performed by the controller. The design is
> [to-be 46](../03-DESIGN/01-to-be/46-the-conversation-with-the-operator.md).
**ADR 0224's provider standing becomes the first condition kind**, unchanged in what it says and
when; its storage moves into the condition store.
@@ -0,0 +1,326 @@
---
topic: the mesh
status: accepted
date: 2026-10-06
deciders: jochen
reconstructed: false
extends: 02-DECISIONS/0227-the-core-holds-nine-rules-each-checked-and-is-built-to-them-in-six-phases.md
---
# 234. The mesh holds a conversation with its operator, over channels that are seats, and an answer that performs an action is authorised by the controller
## Context
**The output channel was built in its smallest form** ([ADR 0227](0227-the-core-holds-nine-rules-each-checked-and-is-built-to-them-in-six-phases.md),
[to-be 45](../03-DESIGN/01-to-be/45-a-core-that-cannot-fail-silently.md) §5): one mesh seat,
`operator-channel`, whose single holder carries the router, a Telegram client and a desktop adapter in
one process, and **no answering back**. ADR 0227 left routing by presence, quiet hours, answering back
and the outside dead-man service open in [research 028](../01-RESEARCH/028-the-meshs-output-channel/00-overview.md),
"whose graduation amends to-be 45". This record is that graduation.
The evidence, from 028:
- **The mesh tells nobody.** 15 of the 236 issue reports say the fault was found because a person
happened to look; 74 describe something failing silently. On the day 028 opened the mesh knew of a
machine refusing every declaration for ninety minutes, a machine out of touch for ten minutes, and a
failure repeated thirteen times, and told nobody ([028/01](../01-RESEARCH/028-the-meshs-output-channel/01-what-the-mesh-already-knows.md)).
- **The built Telegram code has ten defects**, two of which silence exactly what the operator most
needs to hear: nothing bounds a message to Telegram's 4096 characters, so one long message wedges the
channel (D1); and a reopened urgent condition is said by an edit, which on Telegram rings nothing (D2)
([028/04](../01-RESEARCH/028-the-meshs-output-channel/04-telegram-as-the-first-holder.md)).
- **Actions that need a person are ordinary verbs.** `retire approve`, `cleanup delete` and `pin` while
a binding is kept are callable by any principal granted the verb, agents included, and a hand-act
records the calling bus principal, not the person ([028/08](../01-RESEARCH/028-the-meshs-output-channel/08-asks-that-authorise.md)).
`retire approve` re-reads the set when it runs, so "approve what you were shown"
([ADR 0230](0230-a-consumer-the-mesh-stops-asking-for-is-retired-and-deleted-only-by-a-person.md))
does not hold end to end.
- **The desk cannot prove who clicked.** The graphical session is X11: any client of the display can
inject input and read keystrokes, `dunstctl action` invokes a notification's action for any program
of the account, and the desktop holder's bus credential is readable by every agent running as the
operator ([028/08](../01-RESEARCH/028-the-meshs-output-channel/08-asks-that-authorise.md)).
The operator's directions, 2026-10-06:
- Telegram is one channel among many to come; it simply fills a seat. Input (a mail, a message from the
operator) has the same shape.
- Each channel has capabilities, and they decide what may travel on it.
- Agents and modules must be able to **ask** the operator anything, not only for permission.
- The **work context** chooses the channel: in a Telegram conversation, Telegram; at the desk, the desk.
- Approving and rejecting must be possible through Telegram, and while talking to an agent through
Telegram, everything must be completable there.
- **There is no hardware security key.** The proof the operator gives is a code from the authenticator
app on the phone. A key may be added later and must never be required.
### Against GENESIS
- **Mission — intake, process, deliver; agents, some of whom are human.** A human agent "acts through a
shell, a desktop, a message from a phone". The conversation is that modality made a first-class part
of the mesh, for asking as well as telling. The distinction this record draws is **not** human versus
non-human as a category: it is which **identity** can present which **proof**. A spawned agent holds
no enrolled factor, so it cannot authorise; nor can a person without one.
- **Core value — failure must be loud.** A conversation that cannot carry something says so, and the
away channel is checked to carry every tier.
- **Core value — sovereignty.** Telegram is an outside dependency, taken deliberately: free, on both
phone platforms, no server of the mesh's own, and the only candidate that carries every tier today.
It holds a seat, so it is replaceable by a holder of the mesh's own (Matrix, once its push is
measured) without changing anything that speaks to the seat. It never sees a factor's secret.
- **Context — one human, usually asleep; nodes are personal and mobile.** Hence routing by where the
operator is, escalation when unanswered, quiet hours, and a watcher's watcher that does not depend on
the control node.
- **Effect — "when something genuinely needs a decision, you are asked, with the context, not a log
line".** That sentence is this record's purpose.
Nothing in 028 conflicts with GENESIS.
## Considered Options
### Where a channel attaches
1. **Each channel contributes itself to the output seat** (to-be 45 §5 as written). Rejected: a
contribution is content a holder places ([ADR 0210](0210-a-tools-configuration-is-its-seat-holders-and-every-other-module-extends-it-through-the-seat.md));
a channel is running code with a secret, a connection and answers of its own. Making it fit puts
every channel's client back in the router.
2. **One seat per channel kind** (`telegram-channel`, `desktop-channel`, …). Rejected: the router must
learn every seat, and every new channel is a change to the router.
3. **Channel modules found by a manifest field and called by module address.** Rejected: callers use
seats, never modules ([ADR 0126](0126-a-module-declares-its-own-seats.md)).
4. **One notifier with every channel built in.** Rejected: shared secrets, shared failure, a release
of the router per channel.
5. **A per-service module with its own question or approval path** (a "Telegram module" that decides).
Rejected: it locks the conversation to one service, and the next channel repeats it.
6. **Two kinded benches, `channel` (out) and `intake` (in); a fixed capability vocabulary; the output
seat's holder as the router that orders by work context; an authorising layer held by the
controller.** Chosen.
### How answers come in
- **A webhook.** Rejected: it needs a public route into the mesh, and a stolen token can redirect it.
- **Long polling.** Chosen: outbound only; a stolen token can steal updates (visible as a conflict) but
cannot inject one.
### Who performs an authorised action
- **The channel module calls the authorising verb itself.** Rejected: the hand-act would name the
module, a compromised channel could act, and every channel would repeat the checks.
- **The asker performs it on hearing "yes".** Rejected: an agent relaying "the operator said yes" is
not an answer to anything.
- **The controller holds the ask, checks the answer and performs.** Chosen.
### What proves the operator answered
- **A click at the desk.** Rejected: on X11 an agent on the operator's account can produce it.
- **The screen's unlock or a fingerprint reader.** Rejected: only the local holder sees the result,
and an agent can forge what it reports.
- **A security key's touch (FIDO2), required.** Rejected: the operator has no key. Kept as an
optional proof for whoever enrols one; never required for any tier.
- **A TOTP code from the operator's authenticator, verified by the controller.** Chosen as the proof
every tier above acknowledge can rest on.
- **A verified Telegram sender**, through a holder no agent shares. Chosen as a proof for approve, never
enough alone for destroy.
### Whether context may lower the bar
- **Being at the desk, or in a recent conversation, counts as presence and so as proof.** Rejected:
presence proves someone was there, not who answered.
- **Context orders the channels that already qualify, and never makes one qualify.** Chosen.
### The seat shape, against ADR 0223
[ADR 0223](0223-the-mesh-has-two-resolvers-and-a-machine-lists-only-them.md) made `mesh-dns-resolver`
the first bench, of the **replicated** sort (the same module, one holder per machine, every holder
answering the same), and said making another bench is a decision, recorded.
- **Model channels as a replicated bench.** Rejected: the holders are different modules that answer
differently, and a send must reach one chosen holder, not any.
- **Model each holder as a node seat keyed by machine.** Rejected: a channel's identity is its service,
not where it runs.
- **A second sort of bench, kinded.** Chosen, decided here (below).
## Decision
### 1. The conversation
**The mesh holds a conversation with its operator.** Three things are said: a **message** (the mesh
tells; no answer expected), an **ask** (someone wants the operator's input, of a declared kind), and an
**operator message** (the operator writes first). An input from outside that is not the operator (a
mail, a webhook) is the same envelope with another sender; this record decides its shape only, and its
consumers are later work.
### 2. Channels and intake are kinded benches
- **A kinded bench is the mesh's second sort of bench**: a mesh seat whose holders are **different
modules, each claiming one kind**. Two claims of one kind are refused at registration. A verb's
subject carries the kind, as a node seat's carries the machine.
- **`channel`** (out) and **`intake`** (in) are kinded benches, and the only ones. Making another is a
decision, recorded, as ADR 0223 requires of every bench.
- `channel` serves `send`, `edit` and `standing`, and emits `delivered` and `failed` (permanent or
transient). `intake` emits one envelope per input. A service read and written by one program is held
by one module claiming both seats under one kind.
- **A claim on a kinded bench carries `kind` and `capabilities`.** Capabilities come from the fixed,
versioned vocabulary **`channel-capabilities/1`**; a word outside it is refused. Each capability has
a contract test the holder's build runs and a drill its `standing` can run; one that fails its drill
is withdrawn from routing and reported until it passes.
- The vocabulary has three groups: delivering (`deliver`, `reaches-away`, `loud`, `silent`, `edit`,
`max-length:N`, `reaches-when-mesh-down`, `private`), conversing (`choice`, `reply`, `threads`,
`operator-first`) and trusting (`verified-sender`, `exact-render`, `code-factor`, `key-factor`).
### 3. The router
- **The holder of `operator-channel` is the conversation's router.** It loses its Telegram client to a
module of its own and keeps no channel's client in its process.
- **It routes by required capability, then by work context, then by severity**, and escalates along a
fixed chain when unanswered. When nothing can carry something, it says so as a condition of its own.
- **Context orders, never qualifies.** The work context chooses among channels whose capabilities
already satisfy the message or ask. It never adds one, and never lowers what an ask requires.
- **The content rule stays the router's**, applied before anything reaches any holder. A holder
declaring `private` is not exempted; exempting one is a later decision.
- **Presence is current state on the bus**: a value per machine and per intake kind, overwritten, never
a history, and never in a message's words.
### 4. Asks
- **Kinds:** `yes-no`, `one-of` (at most eight options), `text`, `number`, `date`, `acknowledge`, each
requiring its capabilities (`choice` or `reply`).
- **Life:** open → answered | defaulted | expired | cancelled. The first answer wins; every other copy is
edited to say where it was answered. A default is delivered marked as a default, never as the
operator's answer.
- **An authorising ask never defaults.** It expires.
- An asker holds **at most three open asks**; a fourth is refused in words. Asks count against the
router's hourly cap. Asks burst-batched to one channel go out together under one heading, each its
own message. **History is kept 30 days.**
- **The answer returns to the asker as an event**; an asker may also wait a bounded time, or poll.
### 5. Asks that authorise
**An ask whose answer performs an action is held by the controller.** The router carries it like any
other ask; only the controller performs.
- **Every authorising verb declares its tier** in the controller's verb table (`authorises`: the tier,
and the arguments that make up the exact state the operator must see).
- **The controller's verbs:** `authorise request` (anyone may call it; it renders the ask, stores it
with a digest of the state shown, and performs nothing); `authorise answer` (granted to intake holders
only); `authorisations` (open and recent).
- **The proofs** that the operator, and not someone else, answered:
- **P1, a verified sender:** a tap from the operator's own Telegram account, through a holder placed
where no agent runs as the operator;
- **P2, a TOTP code** from the operator's authenticator app, verified by the controller — valid for
the current or previous 30-second step, and accepted once;
- **P3, a security key's touch bound to the ask** — **optional**. It counts where a key is enrolled,
and no tier ever requires it.
- **The tiers:**
| Tier | Required | On the away channel (Telegram) | At the desk |
|---|---|---|---|
| acknowledge | `choice`, `exact-render` | tap | click |
| approve | the above and **one** proof | tap (P1) | click **and a code** (P2) |
| destroy | the above and **two** proofs, at least one of them P2 | tap and a code (P1 + P2) | click, code and key touch (P2 + P3) where a key is enrolled; otherwise carried by the away channel |
Acknowledge performs only what any granted principal may already do (silencing, announced); it is
not an authorisation, and needs no proof.
- **A desk click alone never authorises.** The desk declares no `verified-sender`; at the desk, approve
and destroy rest on a code the controller verifies (or a key touch, where enrolled).
- **Every tier is completable on the away channel.** The self-check verifies it every run; a setting
that would make a tier possible only at a desk is refused unless the operator chose it for that tier
explicitly.
- **The controller checks from its own records, never the request's claims:** the ask is open and
unexpired; the caller holds an intake kind; that kind's declared capabilities meet the tier; a P1
sender is on the controller's list of the operator's identities; a code is valid and unused; a key
assertion verifies over this ask's challenge; and the state now has the digest it had when shown.
It refuses at the first failure, then performs as itself, closes the ask with a compare-and-set (a
second answer loses), and emits `ask-answered`.
- **No agent authorises.** An agent asks; the operator answers where they are. Agents' grants lose the
authorising verbs. Those verbs refuse direct calls except through `authorise answer`, or as
**break-glass** at the console with a code, recorded and announced as such.
- **`retire approve` takes `expect`**, the set it approves, and refuses if the set differs.
- **The hand-act records** `via` (kind and holder), `requested-by` (agent principal or condition key),
`ask` (its id) and `proofs` (which were present). `by` names the operator as that kind's identity.
- **The factors' secrets are the controller's own.** The TOTP seed is made by the mesh and shown once,
to a terminal, never through a channel or an event. Re-enrolling a factor, or changing the operator's
identities or the away channel, is a destroy ask.
- **A code travels by request and reply only**, from the holder to the controller; never in an
envelope or an event, and deleted from the conversation where the service allows.
### 6. First holders
- **Telegram** is the first holder of both seats and the away channel: its own module, kind
`telegram`, long polling, buttons, replies, codes by a forced reply, a linking verb; placed where no
agent runs as the operator, on which its `verified-sender` depends.
- **The desk** holds both seats as kind `desktop`, through the machine's `node-notifier` (gaining
actions) and `node-launcher` (a prompt for text, numbers, dates and codes). It carries every ordinary
ask with no account anywhere.
- **The watcher's watcher stays outside the seats**, with its own bot, on a machine that is not the
control node. An outside dead-man service is pinged by the self-check and by the watcher.
### 7. The order of work
**Fix D1–D4 of the built Telegram code first**, before the channel is configured; then the desk's
actions; then the seats and the router; then asks; then authorising with TOTP; then Telegram live. The
phases and their owners are in [to-be 46](../03-DESIGN/01-to-be/46-the-conversation-with-the-operator.md).
## Consequences
- **To-be 45 §5 is amended**: the operator answers. ADR 0227's minimal form is the first step of this
design, not reversed; ADR 0227 left this to 028's graduation and carries a dated note pointing here.
- **The catalogue gains a second sort of bench**, `kind` and `capabilities` on a claim, two seats in
the closed set ([ADR 0110](0110-a-seat-is-a-module-assignment-from-a-closed-set.md)), and the
vocabulary with its contract tests.
- **The shared library must publish on a seat's event subjects** (to-be 32 §1's open gap), and the
permission model must grant it; until then the seats' events cannot be emitted as designed.
- **`node-notifier.send` gains actions**, and the screen-lock holder emits lock and idle changes.
- **The controller grows**: the `authorises` field, three verbs, refusals on direct calls, the
hand-act fields, the operator's identities, the TOTP seed and enrolled keys as its own state, and a
self-check probe for the away channel.
- **Harder:** approving at the desk now costs picking up the phone for a code. That is the price of an
X11 desk shared with agents; it falls if agents run under an account of their own on a display that
isolates clients, which is noted, not decided.
- **A residual risk is accepted:** on X11 an agent can read a code as it is typed at the desk and race
to use it. Each code is accepted once and only within its step; the session leaving X11 closes it.
- **Telegram sees the words**, held to the content rule, and never a factor's secret. If the operator's
Telegram account is taken, approve is reachable (announced, reversible), destroy is not without the
code.
- **The predecessor wording in to-be 45** ("each a module contributing itself to the seat") is replaced
there, not here; this record's option 1 says why.
## How it is checked
| Rule | Checked by |
|---|---|
| A capability outside `channel-capabilities/1` is refused | catalogue registration test |
| Two holders claiming one kind on a kinded bench are refused; only `channel` and `intake` are kinded | catalogue registration test over the compiled seat set |
| Each declared capability has a contract test that runs in its holder's build | catalogue lint: a declared capability without its test fails the build |
| A capability failing its drill is withdrawn from routing and reported | router test with a holder whose drill fails |
| An ask goes only to channels whose capabilities satisfy it | router test |
| Context reorders but never adds a channel, and never lowers what an ask requires | router test: the same ask in every context yields subsets of the same qualified set |
| An authorising ask never defaults | router test |
| A cancelled or answered ask's other copies are edited | router test against channel doubles |
| A fourth open ask from one asker is refused | router test |
| Presence is never written as history and never in a message's words | router test over its state buckets; the content rule's test |
| A desk click alone never authorises | controller test: `authorise answer` from kind `desktop` with no code is refused for approve and destroy |
| A TOTP code is verified by the controller, once, within its step | controller tests: wrong code, used code, code from two steps back, all refused |
| Destroy needs two proofs, one of them a code; a key is never required | controller tests: P1 alone refused; P1 + P2 accepted; P2 + P3 accepted; no tier's requirement names P3 |
| `authorise answer` refuses a non-intake caller, a kind below the tier, an identity not on the list, a key assertion over another ask's challenge, a stale digest, a second answer | one controller test per refusal |
| No agent authorises; authorising verbs refuse direct calls | controller test: direct `retire approve`, `cleanup delete`, `pin` (while kept) refused without a code; catalogue check that no agent module's grant names an authorising verb |
| `retire approve` approves only the set shown | controller test with `expect` differing from the current set |
| The hand-act records `via`, `requested-by`, `ask`, `proofs` | controller test reading the hand-act after an authorised action |
| Every tier is completable on the away channel | self-check probe, every run; raises a condition when not |
| A code never appears in an event | intake holder contract test (`code-factor`) |
| D1–D4 are fixed before Telegram is configured | holder tests: 4097 characters arrive cut and said; a reopening is a new message; clearing edits the newest; warnings and clearings are silent |
| End to end | live drills: an agent's question answered at the desk; the same with the desk locked, answered on the phone; an approve and a destroy on a test condition, answered on the phone with a code, hand-acts read |
## References
- [Research 028](../01-RESEARCH/028-the-meshs-output-channel/00-overview.md), above all
[06](../01-RESEARCH/028-the-meshs-output-channel/06-a-conversation-with-the-operator.md),
[07](../01-RESEARCH/028-the-meshs-output-channel/07-the-work-context-and-the-desk.md),
[08](../01-RESEARCH/028-the-meshs-output-channel/08-asks-that-authorise.md) and the proposed record in
[09](../01-RESEARCH/028-the-meshs-output-channel/09-a-proposed-decision.md).
- [ADR 0227](0227-the-core-holds-nine-rules-each-checked-and-is-built-to-them-in-six-phases.md): the
minimal output channel this record grows, and the place it left answering back.
- [ADR 0223](0223-the-mesh-has-two-resolvers-and-a-machine-lists-only-them.md): the first bench, and
the rule that another is a recorded decision.
- [ADR 0230](0230-a-consumer-the-mesh-stops-asking-for-is-retired-and-deleted-only-by-a-person.md):
approve what you were shown; a timer is the mesh acting alone again.
- [ADR 0208](0208-the-graphical-session-is-one-module-per-piece-on-the-meshs-seats.md): the desktop
notifier as a node seat.
- [Issue 187](../04-ISSUES/187-the-mesh-tells-nobody-when-it-stops-working/00-report.md): the class.
- [To-be 46](../03-DESIGN/01-to-be/46-the-conversation-with-the-operator.md): the design.
+1
View File
@@ -203,6 +203,7 @@ python3 00-META/checks/index.py fail if stale
- **0229** — [The core's order is a lease the store remembers, and an epoch a machine is sent once it reads one](0229-the-cores-order-is-a-lease-the-store-remembers-and-an-epoch-a-machine-is-sent-once-it-reads-one.md)
- **0230** — [A consumer the mesh stops asking for is retired, and deleted only by a person](0230-a-consumer-the-mesh-stops-asking-for-is-retired-and-deleted-only-by-a-person.md)
- **0231** — [A healer acts on what observation raised, and only observation says it worked](0231-a-healer-acts-on-what-observation-raised-and-only-observation-says-it-worked.md)
- **0234** — [The mesh holds a conversation with its operator, over channels that are seats, and an answer that performs an action is authorised by the controller](0234-the-mesh-holds-a-conversation-with-its-operator.md)
### Its tiers, from the bottom up
@@ -4,6 +4,7 @@ status: in-progress
code: [mesh-controller, mesh-host, mesh-tools, mesh-catalog, mesh-sdk, mesh-lab]
updated: 2026-10-06
decisions:
- 02-DECISIONS/0234-the-mesh-holds-a-conversation-with-its-operator.md
- 02-DECISIONS/0227-the-core-holds-nine-rules-each-checked-and-is-built-to-them-in-six-phases.md
- 02-DECISIONS/0229-the-cores-order-is-a-lease-the-store-remembers-and-an-epoch-a-machine-is-sent-once-it-reads-one.md
- 02-DECISIONS/0231-a-healer-acts-on-what-observation-raised-and-only-observation-says-it-worked.md
@@ -220,16 +221,20 @@ itself — an unanswered probe is never a pass.
## 5. The output channel, minimal form (rule 6)
From [research 028](../../01-RESEARCH/028-the-meshs-output-channel/00-overview.md), the smallest form
that works; its open questions stay open and its graduation amends this section.
that works. **Amended 2026-10-06 by [ADR 0234](../../02-DECISIONS/0234-the-mesh-holds-a-conversation-with-its-operator.md)**,
028's graduation: this form is the first step of [to-be 46](46-the-conversation-with-the-operator.md),
the conversation with the operator, which replaces the points marked below. Phase 1 builds this
section; to-be 46 §10 builds what follows from it.
- **One mesh seat, `operator-channel`**, held once. It **accepts** `notify` as a work queue, so a
message waits for a holder; it keeps its **open messages** in its own key-value state, so a restart
forgets nothing; it **serves** `open` and `history`.
- **The holder consumes the controller's condition events** and decides what is sent. The controller
calls nobody.
- **Two channels**, each a module contributing itself to the seat: **Telegram** (a bot to the
operator's chat; its token and chat id are the channel module's secrets) and the **desktop
notifier** of the machine the operator is at.
- **Two channels**: **Telegram** (a bot to the operator's chat) and the **desktop notifier** of the
machine the operator is at. *Amended:* a channel is not a contribution to this seat. Each is the
holder of a kind on the kinded benches `channel` and `intake`, a module of its own holding its own
secrets, and this seat's holder becomes the router (to-be 46 §2, §3, §6).
- **A message** is: the condition's key, its subject (a machine's role, a module, a plan), kind,
severity, the one-line summary, since when, and the verb that shows more. **Deduplicated by the key.**
- **When:** on `condition-raised`; once more if still open after 1 hour (urgent) or 12 hours
@@ -239,7 +244,10 @@ that works; its open questions stay open and its graduation amends this section.
- **Rate:** at most twenty messages an hour; the excess is folded into one message naming them all.
- **What may leave the mesh:** roles and words. A message carrying an address, a path or anything
shaped like a secret is refused by the holder and raises `channel-refused` instead.
- **No answering back** in this form; acknowledging is `conditions silence`, through the mesh.
- **Answering back.** *Amended:* the minimal form had none, and acknowledging was `conditions
silence`, through the mesh. The operator now answers and is asked over the intake seat; an answer
that performs an action is checked and performed by the controller (to-be 46 §4, §7). `conditions
silence` stays callable.
- **The watcher's watcher.** A module, `mesh-watcher`, assigned to one machine that is not the control
node, holds the Telegram channel's secret too. It listens for the self-check heartbeat (S10) and for
the bus itself; when either has been silent past its bound it sends to Telegram **directly over
@@ -533,5 +541,6 @@ a new core issue cannot resolve without a replay or a stated reason.
- The bus as a cluster of three, to upgrade it live.
- Routing by presence, quiet hours, answering back through a channel, and an external dead-man
service — research 028.
service — decided by [ADR 0234](../../02-DECISIONS/0234-the-mesh-holds-a-conversation-with-its-operator.md),
designed in [to-be 46](46-the-conversation-with-the-operator.md).
- A condition that needs judgement handed to an agent as work — research 017.
@@ -0,0 +1,410 @@
---
layer: to-be
status: designed
code: []
updated: 2026-10-06
decisions:
- 02-DECISIONS/0234-the-mesh-holds-a-conversation-with-its-operator.md
- 02-DECISIONS/0227-the-core-holds-nine-rules-each-checked-and-is-built-to-them-in-six-phases.md
---
# 46 — The conversation with the operator
**The mesh, its modules and its agents tell the operator things and ask them things; the operator
answers, or writes first. Each exchange travels on a channel whose declared capabilities can carry it,
chosen by where the operator is working. An answer that performs an action is checked and performed by
the controller, never by the channel or the asker** ([ADR 0234](../../02-DECISIONS/0234-the-mesh-holds-a-conversation-with-its-operator.md),
from [research 028](../../01-RESEARCH/028-the-meshs-output-channel/00-overview.md)).
This design grows the minimal output channel of [to-be 45](45-a-core-that-cannot-fail-silently.md) §5.
That form stays the first step; §10 below is the order in which it becomes this one.
## The parts
```
sources ROUTER (holder of operator-channel) channel bench (out)
─────── ─────────────────────────────────── ───────────────────
controller's conditions ──┐ messages and asks ┌──► channel/telegram
modules (notify) ────────┼──► required capability → work context ──┼──► channel/desktop
agents (ask) ────────┤ → severity → escalation └──► channel/<next kind>
controller (authorise │ presence (current state only)
request) ────────────────┘ ▲ ▲
│ │ answer (ordinary asks) intake bench (in)
seen ───┘ └────────────────────────────── intake/telegram
intake/desktop
CONTROLLER ◄──── authorise answer (authorising asks, intake holders only) ─┘
│ checks capabilities, identity, code, digest; performs; records the hand-act
└──► ask-answered
watcher's watcher (outside the seats, own bot, not on the control node) ──► Telegram directly
self-check and watcher ──► outside dead-man service
```
## 1. The three things said
- **A message:** the mesh tells. A condition raised, a reminder, a clearing, a notice from a module.
No answer is expected. It may be edited later.
- **An ask:** someone wants the operator's input, of a declared kind (§4). The answer goes back to
whoever asked.
- **An operator message:** the operator writes first — to an agent, to the mesh, or answering an ask in
its thread instead of tapping.
Input from outside that is not the operator (a mail, a webhook) is the same envelope (§3) with
another sender. Only its shape is designed here; its consumers are later work.
## 2. The `channel` seat (out)
**A mesh seat, kinded bench.** A kinded bench is the second sort of bench, beside the replicated one
([ADR 0223](../../02-DECISIONS/0223-the-mesh-has-two-resolvers-and-a-machine-lists-only-them.md)): its
holders are different modules, each claiming one **kind** (`telegram`, `desktop`, later `matrix`,
`ntfy`, `mail`, …). Two claims of one kind are refused at registration. A verb's subject carries the
kind, as a node seat's carries the machine. A new channel is a new module claiming a new kind, with no
change to the router. `channel` and `intake` are the only kinded benches.
**A claim on it carries** `kind` and `capabilities` (§5).
**Served:**
- **`send`** — the words, a priority, whether silent, an optional ask block (the ask's id, its kind,
the options each with an opaque token) and an optional thread (a conversation handle, or the message
this replies to). Answers the channel's own id for what it sent, or a refusal in words.
- **`edit`** — replace a sent message by its id, where `edit` is declared.
- **`standing`** — ready; not configured (naming what is missing, never a value); or failing (since
when, why); the last delivery; the declared capabilities and the result of each drill.
**Emitted:** `delivered`; `failed`, marked **permanent** or **transient**.
**Honoured by every holder:** its declared maximum length, by cutting and saying so; silence where
declared; an identical edit is a success; no secret of its own in any error or event.
## 3. The `intake` seat (in)
**A mesh seat, kinded bench**, as `channel`. A service read and written by one program (a Telegram
bot) is held by one module claiming both seats under one kind.
**A holder turns what arrives into one envelope:**
| Field | What it is |
|---|---|
| `id` | unique, for deduplication |
| `kind` | the holder's kind |
| `what` | `message` (written first), `choice` (a button or reaction), `reply` (written in an ask's thread), `mail`, `call` (a webhook), `seen` (activity, for the work context) |
| `sender` | the identity on that service, and whether the service authenticated it |
| `operator` | whether that identity is on the **controller's** list of the operator's identities — filled from that list, never from the holder's own |
| `conversation` | an opaque handle; sending on it reaches the same chat, room or thread |
| `in-reply-to` | the ask or message it answers, if any |
| `payload` | the text, or the option's token. **Never a code** (§7) |
| `at` | when |
**Answers go to the ask's owner by request and reply**: the router's `answer` for an ordinary ask, the
controller's `authorise answer` for an authorising one. **Everything else is an event on the seat**,
taken by `what`: the router takes `seen`, an agent bridge takes `message`, a later mail rule takes
`mail`.
**A prerequisite:** the shared library publishes on a seat's event subjects, and the permission model
grants it (the open gap of [to-be 32](32-what-a-module-declares.md) §1).
## 4. Asks
### Kinds
| Kind | The operator gives | The channel needs |
|---|---|---|
| `yes-no` | yes or no | `choice`, or `reply` read as yes or no |
| `one-of` | one of at most eight labelled options | `choice`, or `reply` with the option's number |
| `text` | free text | `reply` |
| `number`, `date` | a value within bounds | `reply`; parsed by the router, asked again once if it does not parse |
| `acknowledge` | "seen" | `choice` |
An **authorising** ask is any of these with the authorising flag; it adds the tier's requirements (§7).
`text`, `number` and `date` never authorise.
### What an ask carries
An id; the asker (bus principal and machine); the kind with its options or bounds; the words; a
priority (urgent or normal); optionally a timeout and a default; optionally a conversation handle;
optionally a group, for batching. The words pass the content rule.
### Life
**open → answered | defaulted | expired | cancelled.**
- **Answered:** the first answer wins. Every other copy is edited to say where it was answered.
- **Defaulted:** the timeout passed and a default was declared. The asker receives it marked as a
default, never as the operator's answer.
- **Expired:** the timeout passed with no default; the asker is told. **An authorising ask never
defaults; it expires** (ADR 0230: a timer is the mesh acting alone again).
- **Cancelled:** by the asker (`ask cancel`), or by its owner when moot (the condition behind it
cleared). Copies are edited to "no longer needed".
### Limits, batching, history
- An asker holds **at most three open asks**; a fourth is refused in words.
- Asks count against the router's hourly cap, as messages do; answers do not.
- Asks to one channel within the burst window go out together under a heading ("3 questions waiting"),
each as its own message, answerable and editable alone.
- **`asks`** lists open asks and closed ones for **30 days**: asker, kind, outcome, channel, answer. A
free-text answer stays in the router's state and is never forwarded to another channel.
### How the asker gets the answer
The event **`ask-answered`**, with the ask's id and any conversation handle. An asker may also call
`ask` with a bounded wait (a few minutes), or poll `asks <id>`.
### The router's verbs and events
`ask`, `ask cancel`, `asks`, `answer` (intake holders only), on the `operator-channel` seat beside its
existing `notify`, `open` and `history`. Events `ask-opened`, `ask-answered`, `ask-closed`. The router
owns every ask that authorises nothing; an authorising ask is owned by the controller (§7) and carried
by the router like any other.
## 5. The capability vocabulary, `channel-capabilities/1`
**Fixed and versioned.** A word outside it is refused at registration. **Each word has a contract test**
the holder's build runs, and a **drill** `standing` can run. A capability that fails its drill is
withdrawn from routing and reported until it passes. A new word is a new version of the vocabulary.
| Group | Capability | Promise | Contract test / drill |
|---|---|---|---|
| delivering | `deliver` | it arrives, or `failed` says why | against a test double / a live test message |
| | `reaches-away` | it reaches a phone away from the operator's machines | by kind / the operator acknowledges a drill |
| | `loud` | it can break through the phone's quiet hours | the service's override is set for urgent |
| | `silent` | it can arrive without sound | the service's silent flag is set |
| | `edit` | a sent message can be replaced in place | edit and read back, against a double |
| | `max-length:N` | up to N characters arrive whole; longer is cut and the cut said | N+1 characters give a cut message, not a failure |
| | `reaches-when-mesh-down` | delivering needs neither the bus nor the control node | the holder's placement and send path |
| | `private` | the words stay on the operator's machines, or are end-to-end encrypted | by kind; reviewed |
| conversing | `choice` | one offered option in one act, and the pick comes back | a simulated tap yields a `choice` envelope with the option's token |
| | `reply` | free text comes back | a simulated reply yields a `reply` envelope |
| | `threads` | an answer is tied to what it answers | a reply to A carries A in `in-reply-to` |
| | `operator-first` | the operator can write unprompted | a simulated message yields a `message` envelope |
| trusting | `verified-sender` | the answer came from the operator's own account, through a holder no agent shares | a choice from an identity not on the list is dropped and reported; placement checked |
| | `exact-render` | the ask is shown as the controller rendered it | rendered text equals the controller's, byte for byte |
| | `code-factor` | a typed code reaches the controller by request and reply, never judged by the holder | a code never appears in an event; it is deleted from the conversation where the service allows |
| | `key-factor` | a security key's assertion over the controller's challenge reaches the controller | the challenge is the controller's; the signature is checked by the controller |
What the first holders declare:
| Kind | Declares |
|---|---|
| `telegram` | `deliver`, `reaches-away`, `silent`, `edit`, `max-length:4096`, `choice`, `reply`, `threads`, `operator-first`, `verified-sender` (only while placed where no agent runs as the operator), `exact-render`, `code-factor` |
| `desktop` | `deliver`, `loud`, `silent`, `edit`, `private`, `choice`, `reply` (through the launcher's prompt), `threads`, `exact-render`, `code-factor`; `key-factor` only where a key is enrolled and present. **Never `verified-sender`.** |
## 6. Routing, the work context and presence
### The rule
**A message or ask goes to the most direct channel in the operator's current context, among those whose
capabilities already satisfy it. Unanswered in time, it escalates along a fixed chain. Context orders
the candidates; it never adds one, and never lowers the bar.** When nothing qualifies, the router raises
a condition of its own saying so.
### The signals
| Signal | Source | Read as |
|---|---|---|
| A graphical session unlocked with input in the last 5 minutes on machine M | the `node-lock-screen` seat's holder on M, emitting an event on each lock and idle change (logind's lock and idle hints underneath) | at the desk on M |
| That session locked, or idle longer | the same | not at the desk |
| An agent asking from machine M | the ask's asker | strengthens "at the desk on M"; alone it proves nothing |
| A verified intake `message`, `reply` or `choice` in the last 15 minutes | the intake seat | in a conversation on that kind |
| An ask carrying a conversation handle | the ask | that conversation, whatever else is true |
| The hour, against quiet hours | the router's setting | night: only urgent wakes |
### Where things go
"The away channel" is the operator's setting, Telegram to begin with. "The loud holder" is an optional
second away holder for waking.
| Context | An ask | Urgent message | Warning | Unanswered → |
|---|---|---|---|---|
| in a conversation through Telegram | that chat, in the thread | that chat | that chat, silent | after 10 min (urgent) or 1 h: also the desk, if active |
| at the desk on M | the desk on M if it can carry it; else the away channel, and the desk says where it went | the desk on M, and the away channel silently | the desk on M | after 5 min (urgent) or 30 min: the away channel, with sound |
| away | the away channel | the away channel | the away channel, silent | after 15 min (urgent): the loud holder, if configured |
| night, away | non-urgent asks wait for morning; urgent as away | the away channel and the loud holder | the morning digest | as away |
| the desk locks while an ask is shown there | moves at once to the away channel | — | — | — |
All bounds are provisional, set as to-be 45's are: from what the first live weeks measure.
### Presence stays in the mesh
Presence facts are events on the bus. The router keeps them as **current state only** — one value per
machine and per intake kind, overwritten — never as a history, and never in a message's words. A
consumer that wants a timeline of the operator's day is a decision of its own.
### The content rule
The router's, applied before anything reaches any holder: roles and words, never an address, a path or
anything shaped like a secret (to-be 45 §5). A `private` holder is not exempted.
## 7. Asks that authorise
### The verb table and the controller's verbs
- **The controller's verb definition gains `authorises`:** the tier, and the arguments that make up
the exact state a person must see (for `retire approve`, the set of consumers).
- **`authorise request`** — callable by anyone (an agent, the router on a condition's behalf). Carries
the verb and its exact arguments, why, and optionally a conversation handle. The controller renders
the ask, stores it with an opaque id, its expiry and a digest of the state shown, and emits
`ask-opened` with itself as owner. **Nothing is performed.**
- **`authorise answer`** — granted to intake holders only. Carries the ask's id, the option, the
sender's identity, and the code or key assertion where the tier needs them.
- **`authorisations`** — open and recent authorising asks.
### What may be authorised, and its tier
| Action | Tier |
|---|---|
| approve or reject a waiting retirement set (`retire approve` / `retire reject`) | approve |
| confirm a kept binding moves (`pin`, while `binding-kept` names it) | approve |
| end a stuck plan; send a machine its declaration by hand; reset a bus consumer; retry a healer | approve |
| silence a condition | acknowledge |
| delete a retired consumer's data, one or by age (`cleanup delete`) | destroy |
| change the operator's identities, the away channel, or a factor's enrolment | destroy |
### Proofs and tiers
- **P1, a verified sender:** a Telegram tap from the operator's own account, through a holder placed
where no agent runs as the operator.
- **P2, a TOTP code** from the operator's authenticator app, verified by the controller. Valid for the
current or previous 30-second step; accepted once. **This is the proof everything above acknowledge
can rest on.**
- **P3, a security key's touch bound to the ask** (the challenge is a hash of the ask's id and the
state digest; user presence required). **Optional:** counted where a key is enrolled; required by no
tier.
| Tier | Required | Telegram (away) | Desk |
|---|---|---|---|
| acknowledge | `choice`, `exact-render` | tap | click |
| approve | + one proof | tap (P1) | click + code (P2) |
| destroy | + two proofs, at least one P2 | tap + code (P1 + P2) | click + code + key touch (P2 + P3), only with an enrolled key; otherwise carried by Telegram |
- Approve: single use, bound to the exact state shown, expiring when that state changes or after 24 h.
- Destroy: valid 10 minutes after it is shown; at most one destroy answered per 10 minutes.
- **A desk click alone never authorises.** An agent at the operator's terminal answers nothing; the
console offers only break-glass with a code.
### The rules
1. Every authorising verb declares its tier in the verb table.
2. An authorising ask is offered only on a channel whose capabilities satisfy its tier. An answer from
any other channel is refused, and the refusal is said there.
3. **The away channel satisfies every tier.** The self-check verifies it every run. A setting making a
tier possible only at a desk is refused, unless the operator chose that for the tier explicitly.
4. The work context chooses among channels that qualify; it never makes one qualify.
5. **No agent authorises.** An agent asks. Agents' grants hold no authorising verb.
### The controller's checks, in order, refusing at the first failure
1. The ask is open and unexpired.
2. The caller holds an intake kind — by the controller's own seat records, never the request's claim.
3. That kind's declared capabilities, from the controller's records, satisfy the tier, and the proofs
present are enough.
4. A P1 answer's sender is on the controller's list of the operator's identities for that kind.
5. A code is valid for the current or previous step, and unused.
6. A key assertion verifies against the enrolled credential, over this ask's challenge, with user
presence.
7. The state now has the digest it had when shown; otherwise the ask is void and a new one is
requested.
Then it performs as itself, records the hand-act, closes the ask with a compare-and-set so a second
answer loses, and emits `ask-answered`. Every copy is edited to the outcome and its buttons removed.
### Direct calls
`retire approve|reject`, `cleanup delete`, and `pin` while `binding-kept` names it refuse any caller
except through `authorise answer`, or **break-glass** at the console with a code — recorded and
announced on every channel as break-glass. `retire approve` takes **`expect`**, the set it approves,
and refuses if the set now differs. `conditions silence` stays callable; a silence an agent sets is
said on the away channel, with its why.
### The hand-act
Gains **`via`** (kind and holder), **`requested-by`** (agent principal or condition key), **`ask`** (the
id) and **`proofs`** (which of P1, P2, P3). `by` reads "the operator, as <kind> identity <id>".
### The factors
The operator's identities, the TOTP seed and any enrolled key are the controller's own state. The seed
is made by the mesh and shown once, as a URI, to a plain terminal — never through a channel or an
event. Re-enrolling a factor is a destroy ask. A code travels only by request and reply from an intake
holder to the controller, and is deleted from the conversation where the service allows.
## 8. The first holders
### Telegram — kind `telegram`, both seats, the away channel
- **Its own module**, holding the bot token and chat id as its own secrets; the router keeps no
Telegram client.
- **Long polling**, never a webhook; the update offset kept in its own state.
- **Buttons** carry only an opaque `a1:<ask>:<option>` (Telegram allows 64 bytes); everything else is
in the router or the controller. A tap is dropped and reported unless both the user and the chat are
the operator's; otherwise it is acknowledged at once (the phone shows a spinner until then), handed
on, and the message edited to the outcome.
- **A destroy ask** answers the tap with a forced-reply prompt for the code; the holder reads it,
deletes it from the chat, and hands it to the controller by request and reply.
- **A linking verb** with a one-time deep-link code replaces reading the chat id by hand.
- **Placement:** where no agent runs as the operator. Its `verified-sender` depends on it, and the
self-check reads the placement.
### The desk — kind `desktop`, both seats
- **`node-notifier.send` gains actions** (token, label). It still answers at once; the dunst holder
listens for the chosen action and emits it as an event on its node seat.
- The router's desktop adapter, holding `channel/desktop` and `intake/desktop`, turns that into a
`choice` envelope.
- **For `text`, `number`, `date` and codes**, the notification's single action opens the
`node-launcher` seat's prompt; what is typed returns as a `reply` — or, for a code, straight to the
controller by request and reply.
- **The screen-lock holder emits lock and idle changes** for the work context.
- It carries every ordinary ask with no account anywhere, and an authorising one only with a code (or
an enrolled key's touch).
### The watcher's watcher
Outside the seats, deliberately: it must speak when the bus or the control node is what failed. A
minimal sender with its own bot, on a machine that is not the control node; it never reads and never
asks. The self-check and the watcher both ping an outside dead-man service, which speaks when both are
silent.
## 9. The defects to fix first
Found in the built Telegram code of the output seat's holder and the watcher
([028/04](../../01-RESEARCH/028-the-meshs-output-channel/04-telegram-as-the-first-holder.md)).
| # | What | Fix | Weight |
|---|---|---|---|
| D1 | nothing bounds a message to 4096 characters; one long message wedges the channel | cut at the declared maximum and say so; a refused send never blocks those behind it | high |
| D2 | a condition reopened within ten minutes is said by an edit, which rings nothing | a reopening is a new message | high |
| D3 | clearing edits only the first message; the newest still says "still open" | clearing reaches the newest message | medium |
| D4 | `disable_notification` is never set | warnings and clearings are silent; urgent rings | medium |
| D5 | a 429's `retry_after` is ignored | wait as told | low |
| D6 | refusals (400, 403) are retried as transport failures; "not modified" on an edit sends a duplicate | refusals are permanent `failed`; an identical edit is a success | low |
| D7 | a deprecated preview flag | dropped | low |
| D8 | a marshalling error is discarded | reported locally | low |
| D9 | the watcher's unsent "silent" is overwritten by its "cleared" | say both, or one line naming how long it was silent | low |
| D10 | "ready" means a token and a chat id, not that the bot can reach the operator | `standing` checks the chat at status time | low |
## 10. The build, in order
Each phase ends at its own *done when*. Owning repositories from [`repos.md`](../../00-META/repos.md).
| Phase | What | Owner | Done when |
|---|---|---|---|
| **1 — Fix the first holder in place** | D1–D4 in the output seat's holder and the watcher, then D5–D10; no change of shape. The operator then makes the two bots and the mesh is given their values through the controller | mesh-catalog | the holder's tests for D1–D4 pass; a live test message reaches the phone from both the holder and the watcher |
| **2 — The desk's actions** | `node-notifier.send` gains actions; the dunst holder emits the chosen one; the launcher's prompt returns typed text; the screen-lock holder emits lock and idle changes | mesh-catalog | a notification with two actions returns the chosen token as an event; locking emits an event |
| **3 — The seats, and the router** | the kinded bench in the seat set; `kind` and `capabilities` on a claim; `channel-capabilities/1` and its contract tests; publishing on a seat's event subjects and its grant; `channel` and `intake` seats; the output seat's holder becomes the router and holds `channel/desktop` and `intake/desktop`; the content rule and presence as current state | mesh-controller (seat set, claim fields, registration refusals, grants); mesh-sdk and mesh-tools (seat-event publishing in the shared library and the node tools); mesh-catalog (the seats' definitions, the router) | the catalogue refuses an unknown capability and a second holder of one kind; a message is routed to the desk by capability and context |
| **4 — Asks** | `ask`, `ask cancel`, `asks`, `answer`; the events; kinds, life, limits, batching, history; escalation; the agent bridge for operator messages | mesh-catalog | the router tests of ADR 0234 pass; live: an agent's question answered at the desk, and with the desk locked, on the phone |
| **5 — Authorise, with TOTP** | `authorises` in the verb table; `authorise request`, `authorise answer`, `authorisations`; the seven checks; direct calls refused, break-glass; `retire approve` takes `expect`; the hand-act fields; the operator's identities and the TOTP seed as the controller's state, enrolled at a terminal; optional key enrolment; the self-check probe for the away channel; agents' grants lose the authorising verbs | mesh-controller; mesh-catalog (agents' grants, the desk's code prompt) | one controller test per refusal passes; at the desk, approve needs a code |
| **6 — Telegram live** | the `telegram` module holding both seats, with buttons, replies, forced-reply codes and the linking verb, placed where no agent runs as the operator; the operator's two bots configured; the watcher assigned off the control node; the dead-man ping | mesh-catalog | the self-check says the away channel carries every tier; live drills: an approve and a destroy on a test condition answered on the phone, with the hand-acts read |
**Not touched:** the node engine (mesh-host repository). Presence comes from the screen-lock seat's
holder, not the engine; if a later measure shows the engine must relay it, that is a change here.
## What is not decided here
- Whether a `private` holder may be exempted from the content rule.
- Further holders: Pushover for waking, Matrix once its push is measured, mail out for the digest and
mail in as an intake; each is a module claiming a kind, with no change to this design.
- Consumers of non-operator input (mail rules, webhooks).
- Agents running under an account of their own on a display that isolates clients, which would let the
desk earn `verified-sender`.
+1
View File
@@ -46,6 +46,7 @@ document is written and this one's status becomes `implemented`.
| [`41-the-shell-and-the-accounts-environment.md`](41-the-shell-and-the-accounts-environment.md) | **In progress.** The shell and the account's environment as modules: an environment module every module contributes variables and `PATH` entries to, shell code contributed to the login shell in named slots, the prompt and plugins as modules, the host giving a login back, and the service manager's module finished | [ADR 0203](../../02-DECISIONS/0203-the-accounts-environment-is-one-modules-and-every-module-contributes-to-it.md), [ADR 0204](../../02-DECISIONS/0204-a-module-contributes-shell-code-to-the-login-shell-in-named-slots.md), [ADR 0205](../../02-DECISIONS/0205-software-the-distribution-does-not-package-ships-as-a-pinned-archive-of-the-module.md) |
| [`42-the-machines-modules-in-order.md`](42-the-machines-modules-in-order.md) | **In progress.** The order the machines' modules of research 026 and 027 are built and rolled out: every machine's first (sudo, localization, time sync, pacman, logrotate, avahi, systemd, docker, `~/.ssh`, scripts, kernel), then both workstations', then one machine model's; each proven on one workstation before the rest | [ADR 0173](../../02-DECISIONS/0173-the-operators-machine-is-the-meshs-and-a-module-is-what-it-declares.md), [ADR 0182](../../02-DECISIONS/0182-inside-a-home-the-mesh-owns-what-it-places-and-holds-the-rest-as-found.md), [ADR 0205](../../02-DECISIONS/0205-software-the-distribution-does-not-package-ships-as-a-pinned-archive-of-the-module.md) |
| [`45-a-core-that-cannot-fail-silently.md`](45-a-core-that-cannot-fail-silently.md) | **Designed.** The core says when it is wrong, refuses what is stale or unreadable, heals what it knows, upgrades one machine at a time with a witness that rolls it back, and is checked against the real mesh before merge: the writers and signals tables, the condition store, `doctor`, the minimal output channel, healers, the lease and report order, staged upgrades, the facts snapshot and replays, in six phases | [ADR 0227](../../02-DECISIONS/0227-the-core-holds-nine-rules-each-checked-and-is-built-to-them-in-six-phases.md), [ADR 0224](../../02-DECISIONS/0224-a-provider-that-keeps-failing-a-consumer-is-a-problem-the-controller-reports.md), [ADR 0218](../../02-DECISIONS/0218-a-plan-sends-grants-before-code-rolls-out-one-machine-first-and-a-newer-merge-takes-over-an-older-plan.md) |
| [`46-the-conversation-with-the-operator.md`](46-the-conversation-with-the-operator.md) | **Designed.** The mesh tells and asks its operator over channels that are holders of two kinded benches, `channel` and `intake`, declaring capabilities from a fixed vocabulary; the router orders them by work context and never lowers the bar; an answer that performs an action is checked and performed by the controller, on a TOTP code or a verified Telegram sender, never on a desk click alone; Telegram first, in six phases | [ADR 0234](../../02-DECISIONS/0234-the-mesh-holds-a-conversation-with-its-operator.md), [ADR 0227](../../02-DECISIONS/0227-the-core-holds-nine-rules-each-checked-and-is-built-to-them-in-six-phases.md) |
## Not yet written