The Inbound Intake Boundary for Telegram Updates
Telegram updates become operational only when the API boundary, normalization, and listener shell stay separate.
Thesis
The foreground listener becomes useful only when Telegram updates cross a clear intake boundary. The inbound-intake work adds that boundary without letting Telegram's API shape leak into the listener lifecycle.
The source-layout and foreground-shell work established the filesystem contract and foreground shell. The inbound-intake work answers the next question: what happens when the listener has to read real Telegram updates?
The answer is not "put an API call in the loop." The answer is a small intake boundary with explicit offsets, injectable HTTP behavior, representative payload tests, and a normalization step that preserves raw data.
The API Boundary
The Telegram API client lives in:
src/pkgs/core/channels/telegram/api.py
TelegramUpdatesClient calls the Telegram getUpdates endpoint. It accepts a
bot token, an injectable HTTP getter, and a URL template. Its main method takes
an explicit offset and timeout:
get_updates(offset=<next offset>, timeout=<seconds>)
That shape is deliberate. Offset and timeout are not hidden globals. They are the core operational controls for long polling. Tests can assert that they are sent to Telegram, and the listener can pass the current high-water mark from state.
The HTTP boundary is injectable so tests do not need real Telegram credentials. The suite also includes a loopback HTTP server test so the client is exercised against a real local request path, not only a fake function.
Why The Listener Does Not Own Telegram Payloads
The foreground server owns process lifecycle. It starts, stops, checks the lease, and decides when to tick intake. It should not also know the details of Telegram update payloads.
The inbound-intake work keeps that split:
TelegramChannelServerowns the foreground loop and stop contract.TelegramUpdatesClientowns the Telegram API request.TelegramUpdateIntakeowns poll, normalize, write, and state advancement.TelegramUpdateRecordowns payload normalization.TelegramInboxWriterowns filesystem persistence.
That separation keeps debugging concrete. If the process does not stop, look at the listener shell. If Telegram returns an unexpected payload, look at update normalization. If a file is missing, look at the inbox writer.
Representative Payloads
Telegram updates are not all text messages. The inbound-intake work added representative fixture coverage for:
- plain text messages,
- captioned photo messages,
- service-style updates such as new chat members,
- and multi-update API responses.
The normalizer preserves the raw payload and emits stable metadata plus a markdown summary. That matters because incomplete normalization should not destroy evidence. If the summary is partial, the raw JSON still shows exactly what Telegram sent.
The Normalized Record
TelegramUpdateRecord turns a payload into a small operational record. It
preserves:
update_id- raw payload data
- normalized kind
- summary text
- chat id and type
- received timestamp
- next update offset
It also exposes a markdown summary for humans and a metadata mapping for YAML persistence.
The current normalizer handles text, photo captions, photo-only messages, and service updates. It does not claim to understand every Telegram update type. That is the right production posture for this stage: preserve everything, make the common cases readable, and keep room to add richer normalization later.
Intake As A Transaction Boundary
TelegramUpdateIntake coordinates the handoff from API response to filesystem
record. It loads listener state, asks the client for updates using the next
offset, converts payloads into records, writes them to the inbox, and advances
state only after writes succeed.
This is where the listener becomes operational. The update is not acknowledged by Matic merely because Telegram returned it. It is acknowledged when the record is durable on disk and state has advanced.
That rule keeps the listener restart-safe. A process can crash after polling. If it did not write and advance state, the next run can ask for the same update again. If it did write and advance state, the next run starts after that update.
Tests As API Memory
The inbound-intake work's tests do more than assert code paths. They encode the API memory of the system:
- the client must send explicit
offsetandtimeout, - loopback HTTP must produce parseable updates,
- payloads must normalize into stable records,
- service updates must still produce useful summaries,
- and the listener must keep using saved offsets across restarts.
That memory is important because Telegram is outside the repo. The fixtures and loopback test give the implementation a local contract that can fail fast when the boundary drifts.
What This Unlocks
After the intake-boundary work, the listener has an inbound boundary that is small enough to test and explicit enough to operate. It can request updates, preserve raw payloads, normalize common cases, and stay separate from the foreground shell.
The inbox work turns those records into durable artifacts and ties persistence to offset advancement.