diff --git a/.planning/phases/02-bot-skeleton-initdata-security/02-03-APPROVAL.md b/.planning/phases/02-bot-skeleton-initdata-security/02-03-APPROVAL.md new file mode 100644 index 0000000..d986207 --- /dev/null +++ b/.planning/phases/02-bot-skeleton-initdata-security/02-03-APPROVAL.md @@ -0,0 +1,75 @@ +# 02-03 Deployment-Gate Approval Record (D-16, ONBD-01, PRIV-01) + +Recorded: 2026-10-22, after the real-client walkthrough of the plan 02-03 checkpoint +(`checkpoint:human-verify`, task 3). Committed under `.planning/` gitignore via +force-add because the approval trail is planning metadata, not shipped code. + +**Verdict: APPROVED.** The user verified the deployed bot in the real Telegram +client and explicitly said "approved" after the final check. + +## Deployed target + +- Worker (public, not a secret): `https://araharah-bot.araharah-bot.workers.dev` +- Route: `/telegram/webhook` (secret-token header armed) + `/api/session` gate +- KV namespace: `chat_sessions` → `59f58a54bc44483e89872b20a005a5eb` +- Commands: setMyCommands registered in default scope + `language_code:"ms"` scope + +## User's response (verbatim) + +> "it works! Menu or / has /help and /privacy! approved!" + +## Real-client walkthrough results (checks a–e from 02-03-PLAN.md) + +| Check | Expected | Observed | Result | +|-------|----------|----------|--------| +| a. `/start` | one clinical sentence, no buttons | one sentence, text-only, no reply/inline keyboard | ✓ | +| b. menu button + `/` autocomplete | exactly `/help` and `/privacy` | both surfaces show exactly those two commands | ✓ | +| c. `/privacy` | stored-facts block (user id, chat id, language, ~15 min) + never-block (location, routes, history, profile) | both blocks presented as written | ✓ | +| d. `/mula` | behaves exactly like `/start`; reply language follows the app's `language_code` (D-05) | alias routed to the same handler; user's app is English → English reply is correct behavior (user did not request forcing ms) | ✓ | +| e. gibberish `/xyz` | no crash, no menu drift | silent, no error, menu unchanged | ✓ | + +## Live-debug remediations during the walkthrough (deployment-environment fixes, not code bugs in the tested suites) + +1. **Webhook registered without the route path.** First `setWebhook` armed the + bare workers.dev origin; Telegram answered ok but updates landed on `/` → + 404, silent drop. Remedy: re-registered with `url` = + `https://araharah-bot.araharah-bot.workers.dev/telegram/webhook` via + `node scripts/set-webhook.mjs`. `set-webhook.mjs` was then hardened + (same commit as this file) to auto-append `/telegram/webhook` when the + configured URL has no path, preventing recurrence. +2. **Worker runtime secret held a revoked bot token.** `wrangler secrets` had a + stale `BOT_TOKEN`; webhook updates 401'd inside the Worker (`Error: Empty + token!` / token validation failure surfaced via `wrangler tail`). + Remedy: `npx wrangler secret put BOT_TOKEN` re-run from `bot/` with the + value from `.dev.vars`; verified via `wrangler secret list` and live + `/start` delivery. +3. **Empty webhook + pending update queue.** After the URL fix the queue was + still draining to nothing; re-armed by re-running `setWebhook` and confirming + `getWebhookInfo` (pending_update_count → 0, url/secret header correct). +4. **Menu/or-`/`-autocomplete cache.** The menu initially showed nothing new + because Telegram's client caches `setMyCommands`; even correct default-scope + results were not picked up. Resolved by explicitly calling + `setChatMenuButton` (type `commands`) **and** registering the + `all_private_chats` scope; the user then confirmed the menu and the `/` + autocomplete both listed exactly `/help` and `/privacy` and approved. + +## Evidence commands (run by the orchestrator session, in `bot/`) + +- `node scripts/set-webhook.mjs` → `setWebhook ok:true` (post-fix) +- `curl -s https://api.telegram.org/bot$BOT_TOKEN/getWebhookInfo` → url correct, + secret header armed, pending_update_count 0, last_error freed +- `node scripts/register-commands.mjs` → default + ms scopes `ok:true` +- `curl -s https://api.telegram.org/bot$BOT_TOKEN/getMyCommands` and + `.../getMyCommands?language_code=ms` → exactly `/help`, `/privacy` +- `curl -s https://api.telegram.org/bot$BOT_TOKEN/getChatMenuButton` → `commands` +- Live Worker smokes: webhook without secret header → 401; wrong header → 401; + `POST /api/session` garbage → 401 `{"error":"malformed"}` (category-only); + `GET /api/session` → 405; unknown route → 404; KV key list baseline `[]` + +## Post-approval state + +- ONBD-01 / PRIV-01 real-client gates observed by the user; PRIV-02 KV + spot-check command remains available: + `cd bot && npx wrangler kv key list --namespace-id 59f58a54bc44483e89872b20a005a5eb` + (only `session:*`/`chat:*`-shaped TTL keys, gone after ~15 min) +- No STATE.md / ROADMAP.md changes made by the executor — orchestrator-owned. diff --git a/.planning/phases/02-bot-skeleton-initdata-security/02-03-SUMMARY.md b/.planning/phases/02-bot-skeleton-initdata-security/02-03-SUMMARY.md index 84bfd2b..3c7aa29 100644 --- a/.planning/phases/02-bot-skeleton-initdata-security/02-03-SUMMARY.md +++ b/.planning/phases/02-bot-skeleton-initdata-security/02-03-SUMMARY.md @@ -56,15 +56,15 @@ completed: 2026-10-22 # Phase 2 Plan 3: Command Surface + Worker Wiring + Deploy Gate Summary -**The full user-facing Telegram surface (en + ms, text-only, honest privacy copy) and the Worker entry (secret-token webhook + validateInitData→mintSession /api/session gate) are built, CI-verified (27/27), and LIVE on workers.dev — webhook secret-token auth and session gate 401/405 discipline confirmed against the deployed Worker; the real-client walkthrough (checks a–e) remains the phase's final human gate** +**The full user-facing Telegram surface (en + ms, text-only, honest privacy copy) and the Worker entry (secret-token webhook + validateInitData→mintSession /api/session gate) are built, CI-verified (27/27), LIVE on workers.dev, and APPROVED by the user in a real Telegram client (checks a–e all green, 2026-10-22); four deployment-environment issues found during the walkthrough were remediated live — plan complete** ## Performance - **Duration:** ~12 min (tasks 1–2) + deploy chain session - **Started:** 2026-10-22T03:46Z - **Completed:** 2026-10-22 (deploy chain through checkpoint) -- **Tasks:** 3 of 3 — task 3 deploy chain complete; final real-client walkthrough pending user verification -- **Files created:** 9; **modified:** 2 (+ KV namespace provisioned) +- **Tasks:** 3 of 3 — task 3 deploy chain + real-client walkthrough complete, user **approved** (`02-03-APPROVAL.md`) +- **Files created:** 9; **modified:** 3 (+ KV namespace provisioned) ## Accomplishments @@ -88,11 +88,13 @@ completed: 2026-10-22 3. **task 3: deploy gate (checkpoint:human-verify)** — deploy chain executed after user provisioned credentials: - KV namespace created + id wired: `492a97b` (fix) - Worker deployed live; secrets set; setWebhook + setMyCommands registered — no code commits required (see Deployment Record) + - **APPROVAL:** real-client walkthrough a–e observed green by the user — *"it works! Menu or / has /help and /privacy! approved!"* — recorded in `02-03-APPROVAL.md` + - Hardening from walkthrough findings: `d600d6e` (set-webhook auto-appends /telegram/webhook) ## Files Created/Modified - Created: `bot/src/i18n/strings.ts`, `bot/src/bot/bot.ts`, `bot/src/bot/commands/{start,help,privacy}.ts`, `bot/src/webhook.ts`, `bot/scripts/{register-commands,set-webhook}.mjs`, `bot/test/commands.test.mjs` -- Modified: `bot/package.json` (deploy chain), `bot/wrangler.jsonc` (workers_dev + real KV namespace id, `492a97b`) +- Modified: `bot/package.json` (deploy chain), `bot/wrangler.jsonc` (workers_dev + real KV namespace id, `492a97b`), `bot/scripts/set-webhook.mjs` + `bot/.dev.vars.example` (walkthrough hardening, `d600d6e`) ## Deployment Record (task 3, D-16) @@ -110,10 +112,26 @@ completed: 2026-10-22 - GET /api/session → 405; unknown route → 404 "Not found" - KV key list pre-/start → `[]` (PRIV-02 baseline) -## Awaiting: real-client walkthrough (final gate for ONBD-01/PRIV-01/PRIV-02 + SC1–SC4) +## Real-Client Walkthrough Outcome (APPROVED — final gate for ONBD-01/PRIV-01/PRIV-02 + SC1–SC4) -Human checks in Telegram against the bot (menu button → bot name: commands a–e as planned). KV spot-check command after a real /start: -`cd bot && npx wrangler kv key list --namespace-id 59f58a54bc44483e89872b20a005a5eb` — only `session:*`/`chat:*`-shaped keys, gone after ~15 min. +User verified in the real Telegram client (2026-10-22) and said verbatim: +*"it works! Menu or / has /help and /privacy! approved!"* + +| Check | Result | +|-------|--------| +| a. `/start` → one clinical sentence, no buttons | ✓ | +| b. menu + `/` autocomplete list exactly `/help` and `/privacy` | ✓ | +| c. `/privacy` stored/never-stored blocks as written | ✓ | +| d. `/mula` ≡ `/start` (reply language follows app `language_code` per D-05; user's app is English → English reply is correct behavior; no force-ms) | ✓ | +| e. gibberish `/xyz` → silent, no crash, no menu drift | ✓ | + +Four deployment-environment issues were found and remediated live during the +walkthrough (see Deviations); the user approved after all four were resolved. +Full record with verbatim response + evidence commands: `02-03-APPROVAL.md`. + +KV spot-check (PRIV-02, available any time): +`cd bot && npx wrangler kv key list --namespace-id 59f58a54bc44483e89872b20a005a5eb` +— only `session:*`/`chat:*`-shaped TTL keys, gone after ~15 min. ## Decisions Made @@ -134,6 +152,14 @@ Also: a first `wrangler secret put` attempt ran from the wrong working directory (One internal test-path fix in `commands.test.mjs` before its first green run in task 1 — the suite's own bug, not a scope change.) ` ExecutionContext` parameter in `webhook.ts` is accepted-but-unused per the standard Workers fetch signature. +**Live-debug remediations during the task 3 walkthrough (deployment-environment findings, all resolved before user approval):** +- **[Deployment misconfiguration] webhook registered without the route path** — first `setWebhook` armed the bare workers.dev origin; Telegram answered ok but updates landed on `/` → 404, silent drop. Remedy: re-registered with `/telegram/webhook` appended; then hardened in-repo: `set-webhook.mjs` now auto-appends `/telegram/webhook` when the configured URL has no path (and fails fast on unparseable URLs, never echoing values — T-02-04). Commit `d600d6e`. +- **[Stale runtime secret] production `BOT_TOKEN` was revoked** — Worker-runtime secret held a dead token, so webhook updates 401'd inside the Worker; `wrangler tail` surfaced the failure. Remedy: `npx wrangler secret put BOT_TOKEN` re-run from `bot/` with the value from `.dev.vars`; verified via `wrangler secret list` + live `/start` delivery. +- **[Empty webhook + pending update queue] re-armed via scripts** — after the URL/secret fixes, re-ran `set-webhook.mjs` and confirmed `getWebhookInfo` (url correct, pending_update_count → 0, last_error freed). +- **[Menu cache] menu/`/`-autocomplete initially empty** — Telegram client-side caching of `setMyCommands`; resolved by explicitly calling `setChatMenuButton` (type `commands`) **and** registering the `all_private_chats` scope in addition to the default + ms scopes. User then confirmed both surfaces listed exactly `/help` and `/privacy` and approved. + +Note for future deploys: command-menu registration and the menu button were found to need the `all_private_chats` scope + explicit `setChatMenuButton` in addition to the default scope; `register-commands.mjs` may be extended to include these one-time calls in a later plan (out of scope here). + ## Threat Model Dispositions Applied - **T-02-03** (webhook flooding/DoS): secret-token header constant-time compare happens in grammY's adapter before bot processing; no KV touched pre-check @@ -148,9 +174,7 @@ None. Every export is fully wired; no placeholder copy, no TODO/FIXME, no compon ## User Setup Required (deploy checkpoint — plan task 3) -RESOLVED — credentials were provisioned by the user (@BotFather token + WEBHOOK_SECRET in `.dev.vars`, `wrangler login` active). Deploy chain complete — see "Deployment Record". Remaining: the real-client walkthrough a–e (above). - -Then the orchestrated walkthrough (OpenCode automates): `npx wrangler kv namespace create chat_sessions` → replace `KV_ID_PLACEHOLDER` in `bot/wrangler.jsonc` → `npm run bot:deploy` (deploy + setWebhook; copy printed URL into `WEBHOOK_URL` and re-run deploy chain per checkpoint) → `npm run bot:commands` → real-client checks a–e (/start intro text-only, menu lists exactly /help /privacy, /privacy stored/never blocks, /mula + /privasi alias co-routing, gibberish /xyz no crash) → KV spot-check for `session:*`/`chat:*` TTL-only keys. +RESOLVED — credentials were provisioned by the user (@BotFather token + WEBHOOK_SECRET in `.dev.vars`, `wrangler login` active). Deploy chain complete — see "Deployment Record". Real-client walkthrough a–e observed and **approved** — see "Real-Client Walkthrough Outcome". Nothing remains for the user on this plan. ## Next Phase Readiness @@ -164,9 +188,9 @@ Then the orchestrated walkthrough (OpenCode automates): `npx wrangler kv namespa - Commits verified in history: `b4393fb` (task 1), `df3b7d6` (task 2); no unintended file deletions in either commit - `npm test -w bot` → `[runner] 27 passed, 0 failed`; `npm run bot:typecheck` exit 0 - `wrangler deploy --dry-run` exit 0; `node --check` clean on both scripts -- **task 3:** commits `492a97b` (KV id) verified in history; deployed Worker URL reachable (404 fallback), secrets in `wrangler secret list`, getWebhookInfo ok, getMyCommands read-back ok (both scopes), live 401/401/401/405/404 smokes green, KV list baseline `[]` +- **task 3:** commits `492a97b` (KV id) and `d600d6e` (set-webhook hardening) verified in history; deployed Worker URL reachable (404 fallback), secrets in `wrangler secret list`, getWebhookInfo ok (pending 0), getMyCommands read-back ok (both scopes), getChatMenuButton → `commands`, live 401/401/401/405/404 smokes green, KV list baseline `[]`, user-approved walkthrough a–e (see `02-03-APPROVAL.md`) - STATE.md / ROADMAP.md untouched (orchestrator-owned) --- *Phase: 02-bot-skeleton-initdata-security* -*Completed: 2026-10-22 (tasks 1–3; task 3 deploy chain done, real-client walkthrough pending user)* +*Completed: 2026-10-22 (tasks 1–3 all complete; task 3 deploy + real-client walkthrough APPROVED by user)*