Skip to content

Confirm schedule create/delete with inline Telegram buttons - #12

Merged
valuecodes merged 3 commits into
mainfrom
inline-buttons
Apr 26, 2026
Merged

Confirm schedule create/delete with inline Telegram buttons#12
valuecodes merged 3 commits into
mainfrom
inline-buttons

Conversation

@valuecodes

Copy link
Copy Markdown
Owner

What

  • Replaces the typed-YES confirmation flow for schedule create/delete with one-tap ✅/❌ inline buttons; typed YES remains as a fallback.
  • Acks callback queries (toast + clears buttons) and uses an opaque server-issued token for replay protection; ALLOWED_CHAT_ID is enforced on the new callback path too.
  • D1 migration 0004 adds a token column to pending_action.
  • Tooling: adds /pnpm-update and /review-plan slash commands; sets pnpm minimumReleaseAge to 2 weeks.

How to test

pnpm --filter @repo/operator db:migrate:local
pnpm typecheck
pnpm lint
pnpm test
pnpm format:check

Recommended: in the allowed Telegram chat, ask the bot to create / delete a schedule, then exercise: ✅ tap, ❌ tap, > 2 min expiry, and typed-YES fallback. Verify toast appears and buttons clear after each tap.

Security review

  • Auth / session: Callback queries enforce the same ALLOWED_CHAT_ID allowlist as the message path; callbacks without a chat.id are acknowledged but ignored.
  • Data handling / PII: Adds a token column to pending_action (server-issued opaque string, no PII).

Copilot AI review requested due to automatic review settings April 26, 2026 08:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the operator Telegram bot to use inline ✅/❌ buttons (with token-backed replay protection) for schedule create/delete confirmation, while keeping typed YES as a fallback. This modernizes the confirmation UX and adds the necessary Telegram API/supporting DB changes.

Changes:

  • Add Telegram callback_query support (types + webhook handling) and new Telegram API helpers for answering callbacks and clearing inline keyboards.
  • Replace pending-action confirmation reads with atomic “consume” deletes and add a server-issued token stored in D1 for replay protection.
  • Add D1 migration to persist the pending-action token; update webhook tooling and documentation accordingly.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
pnpm-workspace.yaml Adds workspace-level minimumReleaseAge configuration.
apps/operator/src/types/telegram.ts Extends Telegram Zod schemas/types to include callback queries and inline keyboards.
apps/operator/src/services/telegram.ts Adds allowed_updates to webhook registration and introduces answerCallbackQuery / editMessageReplyMarkup.
apps/operator/src/services/telegram.test.ts Adds coverage for the new Telegram service methods and reply_markup support.
apps/operator/src/services/pending-action.ts Adds token generation + atomic consume-by-token / consume-by-chat-id flows.
apps/operator/src/modules/telegram/routes.test.ts Adds callback_query route tests and updates pending-action mocks for new consume APIs.
apps/operator/src/modules/telegram/controller.ts Implements callback_query handler, inline button confirmation flow, and typed-YES fallback via atomic consume.
apps/operator/src/db/schema.ts Adds nullable token column to pending_actions.
apps/operator/scripts/set-webhook.ts Registers webhook with allowed_updates including callback_query.
apps/operator/migrations/meta/_journal.json Records new migration metadata.
apps/operator/migrations/meta/0004_snapshot.json Adds updated schema snapshot including pending_actions.token.
apps/operator/migrations/0004_minor_rictor.sql Adds token column to pending_actions.
apps/operator/README.md Updates documentation to reflect inline-button confirmations and typed-YES fallback.
.claude/commands/review-plan.md Adds a new slash command doc for reviewing plan/design docs.
.claude/commands/pnpm-update.md Adds a new slash command doc for triaging/updating pnpm dependencies.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +342 to +349
const allowedChatId = c.env.ALLOWED_CHAT_ID;
const fromAllowed = String(cq.from.id) === allowedChatId;

if (!fromAllowed) {
logger.warn("callback_query not allowed, ignoring", {
callbackId: cq.id,
});
return c.json({ ok: true });

Copilot AI Apr 26, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Callback queries that fail the allowlist check return without calling answerCallbackQuery. Telegram clients will keep showing a loading spinner until the callback is acknowledged, which contradicts the PR’s goal of always acking callbacks. Consider answering the callback query (optionally without text) before returning on the not-allowed path.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional — unauthorized callbacks are silently dropped here, mirroring the message path (controller.ts:145-148). The spinner on an untrusted client is acceptable cost for not acknowledging unauthorized input.

Comment on lines +352 to +358
const cqChatId = cq.message?.chat.id;
if (cqChatId !== undefined && String(cqChatId) !== allowedChatId) {
logger.warn("callback_query not allowed, ignoring", {
callbackId: cq.id,
});
return c.json({ ok: true });
}

Copilot AI Apr 26, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Callback queries with a message.chat.id that doesn’t match ALLOWED_CHAT_ID are ignored without calling answerCallbackQuery. Even though the request is rejected logically, the Telegram client still needs the callback to be acknowledged to stop the “loading” state. Suggest acking the callback query before returning here as well.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same intent as the from.id branch above — disallowed chats get the same silent drop the message path uses, so we're not acking on the unauthorized path.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 84cb11b697

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

payload: JSON.stringify(action.payload),
description: action.description,
expiresAt,
token,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Gate token column writes on migration rollout

PendingActionService.set now unconditionally writes the new token column, so any instance running this commit against a DB that has not applied 0004_minor_rictor.sql will throw no such column: token on schedule create/delete. I checked the repo workflows: deployment runs on every main push in .github/workflows/main.yml while migrations run in a separate path-filtered workflow (.github/workflows/migrations.yml), so code can be live before the schema change. This makes confirmations fail in production during rollout unless migration ordering is enforced or writes are made backward-compatible.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged — keeping the workflows split for now. Operationally we land migrations ahead of code that depends on the new column; if this becomes a recurring footgun we'll fold the migrate step into the deploy job.

@valuecodes
valuecodes merged commit 3c13e4a into main Apr 26, 2026
11 checks passed
@valuecodes
valuecodes deleted the inline-buttons branch April 26, 2026 08:48
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.

2 participants