Skip to content

t9 telegram: single-id allowlist delivery — notify-once via claim-send-commit, thin bot api client, lifespan-owned poller - #4

Merged
vlobus merged 2 commits into
mainfrom
t9-telegram
Jul 16, 2026
Merged

vlobus merged 2 commits into
mainfrom
t9-telegram

Conversation

@vlobus

@vlobus vlobus commented Jul 14, 2026

Copy link
Copy Markdown
Owner

what

  • delivery/telegram.py — a hand-rolled thin Bot API client (sendMessage + getUpdates) over the shared connector resilience (request_json extended with method/json_body/http_timeout), behind two Protocol halves: Messenger (what the notify job needs) and UpdateSource (what the poller reads). Telegram's HTTP-200-with-ok:false counts as failure. This supersedes the old python-telegram-bot pin (D16): two endpoints don't justify a framework that wants to own the event loop — DECISIONS entry to follow post-merge.
  • delivery/formatting.py — pure formatting for HTML parse mode; every dynamic field is html-escaped at this one boundary, because the reasoning is LLM prose over untrusted headlines and must never become markup.
  • delivery/poller.pyUpdatePoller long-poll loop as a lifespan-owned background task (cancellation is the stop signal, and the only thing that stops it). Default-deny allowlist: chat id and sender id must both equal the single allowlisted id, or the update is dropped in silence — strangers learn nothing. Allowlisted messages get the latest explained anomaly; the reply text arrives through an injected async callable so the poller has no DB dependency.
  • ingest/jobs.py — fifth recurring job notify_anomalies (60s tick): queue = explained-but-unnotified anomalies oldest-first; once-semantics = compare-and-set on the (already-migrated) anomalies.notified_at: claim (uncommitted) → send → commit per item. A failed send rolls the claim back for the next tick; Telegram down aborts the batch as an error run. Deliberately at-least-once: a rare duplicate alert beats a silently missing one.
  • store/repositories.pyexplained_unnotified, latest_explained, mark_notified (the atomic claim).
  • config: telegram_bot_token/base_url/chat_id/rate_per_min, required, no defaults; .env.example updated. main.py composes the client + poller; create_app gains an optional poller (PollerLike).

review fixes (subagent)

  • secret redaction: httpx error text quotes the full URL — with the bot token in the path — and SourceUnavailable text is persisted to ingest_runs.detail. request_json now takes secrets=(...) and scrubs them at the wrap; applied to Telegram and the pre-existing Finnhub/Massive query-param variant of the same leak. Regression-tested.
  • poller immortality: a DB blip while building a reply used to kill the background task silently and its stored exception would re-raise inside the lifespan finally, aborting the rest of teardown. The loop now absorbs any exception (backoff on failed polls, ack-and-skip on poisoned updates — never redelivered into the same crash), and shutdown suppresses a crashed task's exception.

tests

  • unit: escaping (hostile <a href>/& reasoning stays literal), respx client tests (payload shape, ok:false, 5xx retry + token-redaction assertion), poller allowlist driven through run() with a scripted API (stranger silence, both-ids-must-match, crash-survival with ack).
  • integration (real Postgres): the allowlisted chat receives the formatted anomaly+why exactly once across ticks, a failed send releases the claim and the next tick delivers, an unexplained anomaly is never sent.
  • no test can reach api.telegram.org; no real token anywhere.
  • full gate green: ruff, mypy strict, import-linter, pytest (114 passed).

vlobus added 2 commits July 14, 2026 22:17
…ngle-id allowlist poller in lifespan, notify job with claim-send-commit once semantics on notified_at, html-escaped formatting
…token was landing in ingest_runs.detail), poller survives any exception + acks poisoned updates, shutdown tolerates a crashed poll task
@vlobus
vlobus merged commit 02ea604 into main Jul 16, 2026
1 check passed
@vlobus
vlobus deleted the t9-telegram branch July 16, 2026 19:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant