kcode-tg-kennedy-bot 0.5.1

A host-integrated Telegram transport, durable queue, and fail-closed group-security library
Documentation
# kcode-tg-kennedy-bot

`kcode-tg-kennedy-bot` is the in-process Telegram transport for a host application. It long-polls Telegram, stores private and group transport state in SQLite, enforces fail-closed group security, and exposes that behavior through a cloneable Rust `Service`. It does not start an HTTP server.

## Lifecycle

The host constructs a `Config` with:

- `database`: SQLite storage path;
- `bot_token`: optional validated `BotToken`;
- `identity_sink`: application-owned identity and whitelist integration;
- `max_voice_bytes`: nonzero byte limit for every inbound and outbound media payload.

`open(config)` opens and migrates storage, initializes group-session cursors, builds the Telegram client, and validates a configured token with `getMe`. It returns `Runtime`. Obtain the cloneable service with `Runtime::service()` before moving the runtime into `Runtime::run()`.

The runtime owns long polling only. A disabled runtime waits indefinitely so it can participate in the host's normal task supervision without creating a listener. The service owns durable state operations and outbound Telegram calls. A configured token is redacted from `Debug`, never persisted or serialized, and zeroized on drop.

`migrate_storage(path)` performs only idempotent SQLite migration.

## Service surface

Status and queue discovery:

- `status`
- `list_private_sessions`
- `list_events`
- `list_group_ingress`
- `list_group_session_updates`

Delivery:

- `send_cold_private_message`
- `send_cold_private_attachment`
- `send_private_message`
- `send_private_attachment`
- `send_group_message`
- `send_group_attachment`
- `send_event_attachment`
- `reply_event`

Event state:

- `bind_event`
- `save_transcription`
- `abort_event`
- `interrupt_event`
- `complete_reset`

Group state:

- `complete_group_ingress`
- `detach_group_session`
- `acknowledge_group_session_context`
- `complete_silent_group_reset`
- `save_group_message_preparation`

Media:

- `event_media`
- `event_media_metadata`
- `group_message_media`
- `group_message_media_metadata`

`send_cold_private_message` and `send_cold_private_attachment` deliver to an
established private chat without reading or changing its current conversation
binding. The existing private methods retain their compare-and-swap binding
semantics, while `reply_event` and `send_event_attachment` remain the bound
active-event delivery path.

`list_private_sessions` returns `Vec<PrivateSession>` with typed
`telegram_user_id` and `current_conversation_id` fields. Other queue and state
payloads remain `serde_json::Value` where the transport projection is
heterogeneous and already consumed as a dynamic conversation context. Method
arguments, attachment bytes, media results, errors, lifecycle, and status are
typed. `Error::code()` exposes stable programmatic categories and
`Error::message()` exposes the safe host-facing explanation.

## Host boundary

The transport has no inbound network surface. It does not accept CORS origins, parse routes, expose bearer tokens, or bind port 4324. The embedding application is responsible for its own public API and may report `Service::status()` through its own health endpoint. An HTTP adapter, if a different process ever genuinely needs one, belongs outside this crate and requires an explicit authenticated authority boundary.

The only HTTP performed by the library is communication with Telegram's Bot API. Finite Telegram operations retry only plausibly transient provider, network, timeout, and download failures, with at most five total attempts. `RetryAfter` is respected and permanent errors stop immediately. A send can be duplicated if Telegram accepts it but its response is lost; there is deliberately no durable delivery outbox or exactly-once claim.

## Identity and group security

The host is authoritative for identity observations, numeric-user whitelist membership, handle pinning, `/adduser` authorization, and application-owned roots. The transport stores no whitelist table and makes authorization decisions using stable numeric Telegram user IDs.

Groups have random opaque IDs that survive Telegram basic-group to supergroup migrations. The permanent group ledger retains every human identity observed in membership updates, joins, leaves, kicks, administrator lists, and message envelopes.

A group is eligible only when:

1. Telegram confirms that the bot is an administrator or owner.
2. Telegram's member count matches the observed active-human ledger plus the bot.
3. Every human ever recorded for the group is in the current host whitelist.

Failure quarantines the group before invocation parsing, message archival, feedback, or media download. Eligibility is recomputed on later updates. Because Telegram cannot enumerate all ordinary members of an existing group, reliable strict onboarding starts with a new group, promotes the bot, and then adds humans so their joins are observed.

Cold group delivery uses the opaque group ID and rechecks the complete security predicate for every send. A chat migration or eligibility change during validation fails closed. Cold sends do not create a conversation, transcript row, or durable outbox.

## Sessions and reconciliation

Private users and each `(group ID, Telegram user ID)` pair have independent conversation pointers. Binding is compare-and-swap guarded. Reply, reset, timeout, attachment completion, context acknowledgement, and orphan detachment verify the expected binding before changing durable state.

Ordinary private messages are gathered until the stream has been quiet for 20
seconds. An eligible group with exactly one active human is invoked by every
message from that human and uses the same quiet-window behavior without losing
its distinct group session or root. Each newly accepted message restarts the
full interval. `list_events` withholds a pending batch until its durable
`batch_ready_at` deadline, then returns its last event as the reply target and
includes every ordered source item in `batchedEvents`. Binding, reply,
attachment completion, interruption, timeout, and edit invalidation apply to
the whole batch. Larger groups still require a mention, bot command, reply to
Kennedy, or reset and expose those invocations immediately.

Reset commands remain immediate singleton control events rather than joining a
conversational batch. Existing events from before the batching migration also
remain immediately eligible, so an upgrade cannot strand accepted work.

`detach_group_session` clears only the exact current `(group ID, Telegram user ID, conversation ID)` pointer. Missing, already-detached, or rebound state returns `state_conflict`; messages, events, cursors, resets, membership, and other users are preserved.

Telegram acceptance followed by a local archive or compare-and-swap conflict is an ambiguous external side-effect boundary. The host must reconcile current state rather than blindly retry an effectful delivery.

## Media

Authorized input accepts voice notes, documents, photos, videos, animations, audio, video notes, and stickers. Generic documents stay `document` regardless of MIME type. The size limit is checked before download when Telegram supplies a size and while streaming.

Telegram photos are provider renditions. The transport keeps the rendition with greatest pixel area, then declared size, then stable provider order; it does not claim this is the sender's original upload.

`Media` contains exact stored bytes and the resolved media type. Metadata methods expose size and type when callers do not need bytes.

`Attachment` contains:

- `bytes`: required, nonempty, bounded payload;
- `file_name`: optional path-free, control-character-free name of at most 255 characters;
- `media_type`: optional bounded content type;
- `kind`: optional native kind (`photo`, `video`, `animation`, `audio`, `video_note`, or `sticker`); absence means generic document;
- `caption`: optional exact caption.

Captions are limited to 1024 UTF-16 code units and are not accepted for video notes or stickers. Native kinds map to their matching Telegram methods and never silently fall back to `sendDocument`.

Event attachments require the active conversation ID and an explicit `complete` choice. `complete=false` leaves the event active for later files or text; `complete=true` completes only after Telegram accepts the attachment and the binding still matches. Private delivery also carries the expected current conversation pointer. Group delivery resolves and reauthorizes its opaque group ID internally.

`interrupt_event` completes an in-progress event with the `user_stopped` reason while retaining its exact private or group conversation binding. It sends no Telegram message and cannot detach or replace a session.

## Polling, dispatch, and persistence

The next polling offset is durable. Updates are sorted, offered to bounded per-principal dispatch, and then durably skipped past even when an individual update is malformed, saturated, or fails processing. Failure to persist the cursor pauses polling.

Private work is keyed by numeric user ID. Ordinary group work is keyed by Telegram chat ID and sender ID; group-control work has its own key. Each key is FIFO, independent keys may execute concurrently, and host callbacks, Telegram requests, downloads, and retry sleeps run without the SQLite mutex.

SQLite stores Telegram events and media, durable batch identities and quiet
deadlines, session pointers, polling cursor, opaque group IDs and aliases, the
permanent member ledger, bounded working context, reset work, and
background-ingress batches. Completed ingress payloads and group messages no
live workflow needs are reclaimed. It stores no bot token, application
credential, whitelist, prompt, Kmap root, or capability table.

## Text fidelity

User-facing text is trimmed only to test whether it contains non-whitespace. Stored and delivered text remains byte-for-byte unchanged. Long responses are split by Telegram's UTF-16 limit, preserving leading, trailing, and inter-chunk whitespace.

## Managed-library maintenance

The literal `[package].version` in `Cargo.toml` is canonical. Keep source and migrations as ordinary UTF-8 files and generated output outside the managed-library directory. Standard validation includes formatting, build, Clippy with warnings denied, unit and documentation tests.