# Batch 4 QA Report **Date:** 2026-05-24 **Scope:** Workspace build validation, Telegram API constraint analysis, regression checklist **Blockers:** None --- ## 1. Build Results | Check | Result | Details | |---|---|---| | `cargo check --workspace` | ✅ PASS | Finished in 5.38s, all crates compile cleanly | | `cargo test --workspace` | ✅ PASS | All test suites pass (0 unit tests present across crates, all doc-tests pass) | | `cd frontend && npm run build` | ✅ PASS | Built in 1.44s. Warning: `lib-BKGKj-wr.js` (497 kB) and `index-5C1xoqEL.js` (1,038 kB) exceed 500 kB after minification. This is a Vite chunk-size warning, not a build failure. | --- ## 2. Telegram API Constraint Analysis ### 2.1 Caption Length — ⚠️ FLAGGED **Location:** `crates/cgcx-bot/src/main.rs`, `handle_forward_callback`, `"approve"` action (~line 1810) **Code:** ```rust let caption = format!( "{}\n\nSubmitted by: {}\nDirect link: {}\nForward link: {}", escape_html(&forward_def.forward_message), author_line, link, forward_link ); ``` **Analysis:** - `forward_def.forward_message` is stored as `TEXT NOT NULL DEFAULT ''` in SQLite (`migrations/003_forward_system.sql`). SQLite `TEXT` has no practical length limit. - The fixed parts of the caption (`Submitted by: …`, `Direct link: …`, `Forward link: …`) add ~60–120 characters. - `author_line` can be up to ~60 characters (escaped username + ID). - `link` is ~80–120 characters (base URL + 12-char CXID + 12-char password). - `forward_link` is ~70–100 characters (`t.me/{bot}?start=submitfwdid{code}`). - **Telegram API limit for captions:** 1,024 characters. **Risk:** If an admin sets a `forward_message` longer than ~800 characters, the total caption will exceed 1,024 characters. Telegram will reject the `send_media_group` or `send_message` call with a `Bad Request: MEDIA_CAPTION_TOO_LONG` error. The code does not truncate or validate caption length before sending. **Recommendation:** Truncate `forward_def.forward_message` to a safe limit (e.g., 700 chars) before interpolating into the caption, or split into a separate text message if the message is long. --- ### 2.2 Media Type Handling — 📋 NOTED **Locations:** - `finalize_upload`, review-group media batch (~line 1340) - `handle_forward_callback`, destination media batch (~line 1850) **Code:** ```rust let media = if mime_type.starts_with("image/") { InputMedia::Photo(InputMediaPhoto::new(input_file)) } else { InputMedia::Document(InputMediaDocument::new(input_file)) }; ``` **Analysis:** - Images are correctly sent as `InputMediaPhoto`. - **All non-image files are sent as `InputMediaDocument`**, regardless of MIME type. **Impact:** - **Video files** (`video/mp4`, etc.) lose native Telegram playback UI (no inline player, no duration badge, no thumbnail generation). - **Audio files** (`audio/mpeg`, etc.) lose native audio player UI. - Telegram treats them as generic documents, which degrades UX in review and destination groups. **Recommendation:** Map MIME types more precisely: | MIME prefix | Current | Better | |---|---|---| | `image/*` | `InputMediaPhoto` | ✅ Keep | | `video/*` | `InputMediaDocument` | `InputMediaVideo` | | `audio/*` | `InputMediaDocument` | `InputMediaAudio` | | other | `InputMediaDocument` | ✅ Keep | --- ### 2.3 Memory Usage — 📋 NOTED **Locations:** - `finalize_upload`, review-group decryption (~line 1320) - `handle_forward_callback`, destination decryption (~line 1830) **Code pattern:** ```rust match cgcx_crypto::decrypt_bytes(&ciphertext, &file.encrypted_key_wrapped, &ctx.master_key) { Ok(bytes) => decrypted.push((file.mime_type.clone(), bytes)), ... } ... let input_file = InputFile::memory(bytes.clone()); ``` **Analysis:** 1. `tokio::fs::read(&file.stored_path)` loads the entire encrypted file into memory as `ciphertext`. 2. `decrypt_bytes` decrypts in-memory and returns a new `Vec` (`bytes`). At this point, two copies of the file exist in RAM (ciphertext + plaintext). 3. `InputFile::memory(bytes.clone())` clones the plaintext bytes again for the `InputMedia` struct. Now three copies may exist transiently. 4. Files are batched in chunks of 10 (`decrypted.chunks(10)`), so up to 10 files are held in memory simultaneously. **Risk:** For large uploads (e.g., a 100 MB video), this can easily exhaust RAM, especially on constrained hosts or when multiple submissions are processed concurrently. The bot does not stream or chunk-decrypt files. **Recommendation:** Consider streaming decryption to temporary files and using `InputFile::file(path)` instead of `InputFile::memory(bytes)`. This keeps only one copy on disk instead of multiple copies in RAM. --- ## 3. Regression Checklist — Batch 4 Use this checklist before merging or deploying Batch 4 changes: ### Build & Compile - [ ] `cargo check --workspace` passes with zero errors - [ ] `cargo test --workspace` passes (all suites green) - [ ] `cd frontend && npm run build` produces `dist/` without errors - [ ] No new compiler warnings introduced in `cgcx-bot` ### Bot Runtime - [ ] Bot starts successfully and connects to Telegram (`get_me` succeeds) - [ ] InMemStorage dialogue state machine transitions correctly (Start → TermsPending → MainMenu → UploadStaging → UploadOptions → UploadFinalizing) - [ ] Service message cleanup works in groups/channels and is silently skipped in private chats - [ ] Punishment expiration timer revokes bans/mutes after duration elapses - [ ] Global ban propagation (`propagate_punishment`) only runs when `config.groups.global_ban == true` ### Upload & Submission Flow - [ ] Staging accepts media, documents, and text up to `max_batch_size` - [ ] Upload options (destroy, download, password, show_author) toggle correctly - [ ] `finalize_upload` respects `max_total_batch_bytes` limit - [ ] Disk-space check (`fs2::available_space`) blocks uploads when temp space < 2× batch size - [ ] Blocked-hash detection (`CgcxError::BlockedHash`) aborts upload and cleans up ### Forward & Review System - [ ] `/create_submit_forward` validates bot admin status in both destination and review groups - [ ] Submission links (`?start=submitfwdid{code}`) work and enforce allow-lists - [ ] Review message is sent to review group with correct inline keyboard - [ ] **Approve action sends media batch to destination without `MEDIA_CAPTION_TOO_LONG` error** - [ ] Approve action sends DM confirmation to submitter with posted link - [ ] Ignore/Ban/Blacklist/Ban+Blacklist callbacks update review message and submitter correctly - [ ] **Media batching handles >10 files by splitting into multiple `send_media_group` calls** ### Admin Commands - [ ] `/reload` refreshes moderation lists - [ ] `/blacklist_uid`, `/whitelist_uid` update DB and moderation engine - [ ] `/sban`, `/smute`, `/mute`, `/pban`, `/kick` resolve target user and apply restrictions - [ ] `/rmute`, `/rban` revoke active punishments - [ ] `/get_id` returns chat ID or searches admins by username/display name ### Security & Stability - [ ] Panic hook logs location and message - [ ] `CatchPanicLayer`-swallowed panics are traceable via logs - [ ] 8MB thread stack prevents stack overflow during dptree dispatch --- ## 4. Blockers **No critical blockers.** All builds pass. **Non-blocking issues identified:** 1. **Caption length risk** — can cause Telegram API rejection on approval; should be mitigated before relying on forward system in production. 2. **Media type mapping** — video/audio UX is degraded; nice-to-have improvement. 3. **Memory usage** — large files may cause OOM during forward review/approval; should be monitored or mitigated for production load.