Files
mesh-catalog/modules/messenger/README.md
T
jochen 0ae7933d54 Tell the operator what the mesh finds wrong, and watch the watcher (hq to-be 45 phase 1)
The mesh noticed 48 core failures in six days and told nobody (ADR 0227).
messenger holds the operator-channel seat: it consumes the controller's
condition events and sends them to Telegram and the desktop notifier,
deduplicated by key, reminded once, edited on clear, capped at 20 an hour
with the rest folded, and refusing anything carrying an address, a path or
a secret. mesh-watcher, on a machine other than the control node, sends to
Telegram directly when the self-check heartbeat or the bus goes silent.
2026-10-06 09:35:55 +02:00

78 lines
4.6 KiB
Markdown

# messenger
The holder of the `operator-channel` seat: how the mesh tells its operator what it noticed (novox/hq
to-be 45 §5, ADR 0227, research 028 — the minimal form, Q1a, Q5a, Q8).
- **Declares and holds `operator-channel`**, held once for the mesh, serving `open`, `history` and
`notify`.
- **Consumes the controller's condition events** — `mesh-controller.condition-raised`, `-changed`,
`-cleared` — and decides what is sent. The controller calls nobody. The events are read in one
place, `cmd/messenger/condition.go`, which states every assumption it makes about their shape.
- **Two channels:** Telegram (a bot to the operator's chat) and the desktop notifier — the
`node-notifier` seat's `send` verb on the machine the operator sits at (ADR 0208), asked through the
mesh. Nothing new runs on that machine.
- **Keeps its open messages in its own state** (`open`; the recent sends in `sent`), so a restart
forgets nothing (ADR 0201).
## What is sent, and when
| When | What |
|---|---|
| `condition-raised` | one message, deduplicated by the condition's key |
| still open after 1 h (urgent) or 12 h (warning) | once more |
| `condition-changed` from warning to urgent | once more, to both channels |
| `condition-cleared` | the first message edited to say so (both channels can) |
| cleared and raised again within 10 min | the same message, edited back to open — not a new one |
| silenced | nothing, its clearing included |
- **Routing:** urgent to Telegram and the desktop; warning to the desktop when a session there
answers, otherwise to Telegram. The notifier answering is how "the operator's session is there" is
read until presence is decided (research 028 Q4).
- **Rate:** at most 20 messages an hour per channel. The rest are held and folded into one message
naming them all, sent at most every ten minutes — the cap is said, never silent.
- **What may leave the mesh:** roles and words. A message carrying an address (IP, host name, URL,
mail address), a path, or anything shaped like a secret (a token, a key block, a long random or
hexadecimal string, `password=…`) is refused, logged, stated as the `refused` event, and sent in its
place as `channel-refused` with the words that carried it withheld. The rule is `cmd/messenger/content.go`.
- **Nothing silent:** a channel that cannot send says so in `messenger_status` and in the log, and
the message is tried again every minute while the condition is open. An event that cannot be read
is refused by name, counted, and told to the operator once an hour.
- **No answering back.** Acknowledging is `conditions silence`, through the mesh.
## What the operator gives
Nothing is sent until these are given; `messenger_status` says which is missing.
1. **The bot token**, as this module's own secret: `secret accept <machine> messenger telegram-token`,
then push that machine. A value the mesh minted because none was accepted is recognised as not a
bot token and said so.
2. **The chat id**, as a setting: `settings` for `messenger` with `{"telegram-chat-id": "<id>"}`.
3. **The desktop machines**, as a setting: `{"desktop-machines": ["<the machine the operator sits at>", …]}`.
Without it, warnings go to Telegram.
## Tools
| tool | does |
|---|---|
| `operator-channel.open` | what is open now, urgent first, with where it went, silenced, reminded, held, refused, unsent |
| `operator-channel.history` | what was said lately, and the refusals |
| `operator-channel.notify` | a message from a module using the seat: key, severity, summary; `clear` to end it |
| `messenger_status` | whether the operator can be reached and why not; `check` asks Telegram whether the token works |
| `messenger_recent` | the recent sends, edits, folds and failures |
| `messenger_test` | a test message now, to telegram, desktop or both |
| `messenger_check` | whether some words may leave the mesh |
## Where it runs
Held once for the mesh: assign it to one machine whose tool runtime runs as root, since its secret
and settings are root's files at 0600 (as every runtime-carried module's are).
## Not yet
- **`notify` is a served verb, not a work queue.** The design has the seat *accept* `notify` so a
message waits for a holder; the node's tool runtime does not yet hand a bundle its seat's queue,
and an accepted queue nobody takes would hold messages silently. It is answered request-and-reply
until the runtime takes a seat's queue for a bundle (mesh-tools).
- **Channels are inside the holder**, not modules contributing to the seat: a seat a module declares
cannot receive contributions yet (ADR 0212 kinds are compiled for the mesh's own seats).