Files
mesh-catalog/modules/telegram/README.md
T
jochen ea1dfdce5b
mesh/delivery-group group fix/383-an-ask-reads-at-a-glance checking: composed check out of date: novox/mesh-catalog@ea1dfdce5bc7 was judged against trunk 5f491…
mesh/merge-gate out of date: judged against trunk 5f491e0c097d, the trunk is now 4d15a155e634; judged again on a recheck or when it is merged
mesh/repo-check out of date: judged against trunk 5f491e0c097d, the trunk is now 4d15a155e634; judged again on a recheck or when it is merged
mesh/delivery superseded: a newer head of the same pull request
An ask reads at a glance on the phone: a headline, a summary and its points, the rest in a note (issues 383 and 639)
The operator would not read the Telegram ask for ADR 0343: thirty plain lines, every clause on one, no bold,
no summary. A settings proposal was the same wall of text, its values masked so the approval was blind.

- messenger: an ask's message is the asker's headline and explanation alone; who asked and when, what each
  answer does, the expiry, where else it is answered and the asker's Details travel as the message's note
  (asks.Message.Note). Answered, the message is one line, "You chose Approve at 16:06 on Telegram ·
  <headline>". An explanation that does not read at a glance (asks.Glance) is shown and logged. On a kind
  that proves who answers, an ask's values are shown whole (asks.Ask.Whole), held to the secret rule alone
  (from the earlier work on issue 383, #178).
- telegram: every message in HTML, never reworded: the title bold, a key line monospace, the note apart in
  a collapsed blockquote; markup escaped. A tap is compared by its words, whitespace aside, and a message
  shown before is still compared by the digest it was kept under.
- desk-channel: the note follows the body.
- mesh-knowledge: a record that needs the operator carries a brief (headline, summary, at most three
  points), the 30th per-record rule; its ask shows the brief and never the clauses. An ask made before is
  composed again as it was asked (form), so its warrant still holds.
- claude-code: an instructions proposal's explanation is one summary line (the proposer's, or the
  module's), its text in the Details; a question's context is at most 300 characters and its recommendation
  one point. Older proposals and questions keep their form.

The SDK is pinned at mesh-sdk 9588871 (fix/639-an-ask-reads-at-a-glance), which adds Message.Note and
asks.Glance.
2026-10-13 16:33:45 +02:00

105 lines
7.3 KiB
Markdown

# telegram
A channel of the operator's conversation: kind `telegram` of the `channel` and `intake` benches the router
(`messenger`) declares (novox/hq ADR 0234, ADR 0259). It reaches the operator away from the machines, and it
proves who answered: Telegram authenticates the sender, and the router compares it with the account linked
as the operator.
- **Shows what the router sends** (its kind's worker on the `channel` queue): a message, a question with
buttons — each button carries only the router's one-time ticket — or an edit of a message shown before.
It sends to the account the router names as linked, or, before one is, to the chat a `/start` came from.
A message sent under a handle already shown (a reminder, an escalation) **replaces** it (issue 373): the
new message is sent, ringing as its kind rings (an edit here notifies nobody), and then the earlier one is
deleted; one the service will not delete loses its buttons and says a newer message follows. A send that
fails leaves the earlier message.
- **Says what the operator chose** on `intake` under its own kind: a tapped button (`choice`), with the
sender as Telegram authenticated it; `/start` (`link`); a message it could not deliver (`failed`); and
whether it can send (`standing`). Only a private chat with the account itself counts.
- **A tap is acknowledged on the phone with the router's word on it** (issue 372): the router answers a
choice with a message in reply to it — "You chose Approve: recorded, and claude-code was told.", or why it
was not taken — and this channel shows that as the tap's own acknowledgement, the short line Telegram
shows over the chat. A word with more to it (a refusal's reason, Details) is also said in the chat, where it
stays. Nothing is acknowledged before the router's word, so the phone never says "recorded" for what was
not; without a word in 13 seconds the tap is acknowledged with words that claim nothing, and the word when it
comes is said in the chat. A secret is never an acknowledgement.
- **A code** (six digits, the one shown on the desk when linking) is asked of the router as a proof, by
request and reply: never in an event, never kept, and deleted from the chat at once.
- **Judges nothing.** Whether the sender is the operator, and what an answer may do, is the router's.
## The defects of the client it replaces (novox/hq to-be 46 §12)
| | fixed here |
|---|---|
| D1 | up to 4096 characters arrive whole; longer is cut and the cut said |
| issues 383, 639 | shown formatted (HTML), never reworded: the title bold, the body as it is with a key line in monospace, the note apart in a blockquote collapsed to its first lines; `<`, `>` and `&` escaped first, so nothing in the words is read as markup. A tap is compared by the plain words, whitespace aside |
| D4 | a warning or a clearing arrives without sound; an urgent message or a question rings |
| D5 | a rate limit is waited out as told |
| D6 | a refusal is a permanent failure, said to the router and not retried; an edit to the same text is a success |
| D7 | the deprecated preview flag is gone |
| D8 | a request that cannot be written is said |
| D10 | `standing` checks the token with Telegram (`getMe`), not only that a value is present |
D2 and D3 (a reopening said by an edit; a clearing edits the first message) are the router's, and D9 the
watcher's.
## How it keeps the token from the agents (novox/hq ADR 0259 §8, as revised)
- **It runs as the account `telegram`, in a runtime of its own, on a bus account of its own** (`runs-as`). The
machine's runtime runs as the operator's account — which every agent runs as — and never carries it; the
controller refuses to compose it there.
- **Its state directory, its bot token and its bus credential belong to `telegram`**, 0700 and 0600
(`secrets-owner`). The operator's account reads none of them, and cannot attach to its process.
- **Only this kind's holder says this kind's choices, links and codes**: the bus grants those subjects to its
own account alone.
- **The router believes it proves its sender only so placed**, and only while the controller's self-check
finds no agent able to become root without a person on its machine or the router's (`agent-root`).
- **If an agent holds root on the machine, none of this holds**: root reads the token and every credential.
- **A tap counts only on a message showing the words it put there.** It keeps a digest of what each message
shows; a tap on one whose words differ — changed by another holder of the token — or on one it never showed
is refused on the phone and never said to the router.
## Where an approval from Telegram means something (a known gap, accepted by the operator on 2026-10-09)
The router does not measure or enforce what follows, and does not hold approvals back for it: the operator
accepted the gap for now, and novox/hq issue 344 records it with what closes it.
An approval from Telegram proves only that the linked account tapped. It is meaningful where nothing working
for the operator can tap in their name, which on a machine where agents run needs all three of:
1. **The agents run under an account of their own, without sudo** (novox/hq ADR 0266), so they cannot become
root or the operator.
2. **That account has no access to the operator's X display** — no Xauthority cookie for it. X11 has no
isolation between the clients of one display: a program on it reads every key typed and can click any
window, the Telegram client's buttons among them.
3. **The operator's Telegram Desktop data is readable by the operator alone** (`~/.local/share/TelegramDesktop/tdata`,
0700). Found 0755 with its files 0644: any account on the machine can copy the session and be the operator
on Telegram from elsewhere.
Until agents have their own account on a machine, the operator's Telegram client on that machine is not
counted as safe: **approve from the phone.** No module of the catalogue installs Telegram Desktop today, so
nothing of the mesh keeps `tdata` 0700 yet; the operator sets it by hand (`chmod -R go-rwx` on it) until one
does.
## Where it runs
Held once for the mesh: assign it to the control node, the stably reachable one. Long polling is outbound
only, so no route into the mesh is needed, and a stolen token can read updates (a conflict says so) but
never inject one.
## What the operator gives
1. **A bot**: in Telegram, talk to @BotFather, `/newbot`, a name and a username. BotFather answers with the
bot's token. Keep it on the phone; never paste it into a chat with an agent.
2. **The token, to the vault**, as this module's own secret `telegram-token`: the vault's verb for a secret
given at the desk, or `secret accept <control node> telegram telegram-token` at a terminal on the control
node, which reads it without echoing it. Then push the control node.
3. **The link**: send the bot `/start`. A six-digit code appears on the desk; send it to the bot within ten
minutes. The account answers acknowledgements at once and approves an hour later; every channel says so.
`messenger_identities` shows it. `messenger_unlink` asks the linked account itself whether to unlink it.
## Tools
| tool | does |
|---|---|
| `telegram_status` | whether it can send and why not (never the token), the bot's name, when updates last arrived, the last delivery and error, counts since start, and its rules |