# Inbound telegram Last verified: 06-08-2026 @ c61b0b3 V-637, under V-628. Reads with `22-correcting-a-turn.md`. ## What was missing Telegram was a reach and nothing else. `telegramsink` pushed an away message and the chat had no way to answer, so the correction gesture reached the web and voice only. That skews the labels. V-546 fits routing heads on them, and a sample drawn from wherever the owner happens to be sitting is the wrong sample. ## Long-poll, not a webhook The box takes no inbound connections and reaches api.telegram.org through a relay, so the connection has to open outward. `getUpdates` with a 25 second hold, one goroutine in the daemon's WaitGroup. A failed poll waits 15 seconds and retries without escalating. The relay going down is the normal cause and it comes back on its own. ## The backlog is dropped on start Telegram keeps undelivered updates for 24 hours. A daemon that was down overnight would otherwise wake and answer every queued message in order. That is worse than missing them. A question asked eight hours ago has been answered already. A reminder set from it lands at the wrong time. So the first call moves the offset past whatever is queued and acts on none of it. ## One chat `ChatID` is the only accepted sender, and it is the same chat the push half already sends to. A message from anywhere else is dropped with no reply, because a reply confirms the bot exists and whose it is. Chat ids are not guessable. They are also not secret, since they travel in every forwarded message. So this is the whole authorisation and it is an allowlist of one. ## The gesture Two taps at most. The reply carries one button, `не то`. Tapping it writes nothing and opens the seven intents plus `просто неверно`. The untargeted negative stays reachable, because he may have opened the row without meaning to name anything. Callback data carries the trace id and the target, under telegram's 64 byte cap. It comes off the wire. So an id that will not parse is dropped, and so is a target that is not one of the seven. A label nothing can score is worse than no label. A failed write says so on the button and leaves the keyboard up. A successful one takes the keyboard off, because a live keyboard on an answered turn invites correcting it twice. ## The seam `NewPoller` takes two functions and no daemon type. `cmd/mavend/telegramintake.go` fills them from `ipc.CoreAPI`: `Chat` returns the reply and the trace id it collected off the context, and `CorrectTurn` writes the label. So a chat turn takes the path `POST /api/chat` already takes, and nothing in `internal/delivery` knows what a handler is. ## What is not done The turn source is still `tap:text`, which telegram shares with the web. Provenance cannot tell a chat turn from a typed one, so a label's `source` column cannot either. That matters the first time someone asks whether corrections given in the chat differ from corrections given at the desk. Voice messages are ignored. The poller reads `message.text` and nothing else, so a voice note in the chat does not reach `mavsttd`.