# Phase 2: Bot Skeleton + initData Security - Context **Gathered:** 2026-10-22 **Status:** Ready for planning ## Phase Boundary Users can reach araharah through Telegram with a working command surface (/start, /help, /privacy, plus Malay aliases) served by a grammY bot on one Cloudflare Worker, and the backend's identity + privacy architecture is provably sound from the first deploy: HMAC-validated Telegram WebApp initData with CI test vectors, one-time opaque session tokens in TTL-only KV (`chat_sessions`). Mini App shell, search, and the `guidance_sessions` envelope design are later phases. ## Implementation Decisions ### Bot copy + interaction style - **D-01:** Tone is clinical + honest (Rams-aligned): what araharah is in one sentence, no emoji clutter, no marketing flourish. Privacy statement states what is NOT stored as plainly as what is. - **D-02:** /start is a plain text-only message — no reply keyboard, no inline buttons. Telegram commands are the interface (the Mini App launch path is Phase 4's decision). - **D-03:** Permanent command menu contains only commands that exist in this phase (/help, /privacy) registered via setMyCommands — no stub entries for future commands. - **D-04:** Malay command aliases are first-class citizens of the surface from this phase: `/mula` runs the same flow as `/start`, `/privasi` as `/privacy`. Later flow aliases are named now and implemented by their phases: `/pergi_ke` (~ goto), `/cari` (~ search, Phase 4). setMyCommands registers localized command descriptions via a `ms` language_code scope in addition to the default. ### Copy localization - **D-05:** All bot copy lives in a strings module (`src/i18n/strings.ts`): English default + Bahasa Melayu `ms` locale. Telegram's per-user `language_code` picks the locale; unhandled codes fall back to English. Command aliases are handlers on the same command stack, not separate flows. ### Session token mechanics - **D-06:** Session tokens are opaque random token ids stored in KV `chat_sessions` alongside a minimal bootstrap claim. Not JWTs — revocable and audit-friendly. Handing out the token is the DSGN-equivalent security gate: only successful initData validation mints one. - **D-07:** Rotate-on-new: when a validated identity arrives for a chat that already holds a live session token, the old token is invalidated (overwritten). One live token per chat. The recent-stale-replay risk that a replicated validation could mint extra tokens is thereby closed structurally. - **D-08:** The stored claim is deliberately minimal: Telegram `user.id`, `chat.id`, `issued_at`, and the user's `language_code` (needed to route the bot's future chat cards in the user's locale). No location, no routes, no profile. ### Backend repo layout - **D-09:** Backend lives in a `bot/` workspace sibling to `theme/` (npm workspaces, root package.json gets thin delegate scripts, mirroring the Phase 1 pattern). - **D-10:** Language is TypeScript for everything backend (grammY typings first-class, `@cloudflare/workers-types` for the runtime). - **D-11:** bot/ layout is an explicit `src/` tree: `src/webhook.ts` (worker entry), `src/security/initdata.ts` (HMAC validation — isolated, testable without provider dependencies), `src/kv/sessions.ts` (TTL-only access layer), `src/i18n/strings.ts`, `src/bot/commands/{start,help,privacy}.ts`, wrangler config colocated. ### Validation + test harness - **D-12:** Test runner is a hand-rolled node runner in the same shape as `theme/scripts/audit.test.mjs` (Phase 1's pattern) — no extra test dependency for this pure-Crypto HMAC vector suite. - **D-13:** initData freshness policy: reject payloads whose `auth_date` is older than ~15 minutes; the Mini App re-validates with fresh initData on launch. This pairs with the `chat_sessions` minutes-TTL envelope. - **D-14:** Rejection logging is category-only: tampered / stale / replayed / expired — never the raw payload, never coordinates, no user-marking timestamps. Satisfies the "logs carry no coordinate data" criterion from day one. ### Secrets + deploy setup - **D-15:** Secrets (bot token, HMAC secret) live in Cloudflare Wrangler secrets. Local dev mirrors via a gitignored `.dev.vars` (only `.dev.vars.example` ships in the repo). - **D-16:** Phase 2 deploy is local `npm run deploy` (wrangler deploy + setWebhook). No CI workflow yet — it arrives in Phase 3 with real Mini App users. - **D-17:** Strict secret hygiene: no secret value ever committed, webhook URL in wrangler config, and the /privacy copy stays verifiable by reading the code's storage calls — the architecture can't hold more than it says it does. ### OpenCode's Discretion - Exact setMyCommands scope/locale structuring details - Webhook secret token mechanics (Telegram's `secret_token` param) at the researcher's discretion - Exact KV TTL values inside the "minutes" envelope - How local wrangler dev simulates KV for testing the product loop - How the researcher verifies exact grammY initData typings (flagged research gap) ## Specific Ideas - Command menu and bot surface should feel like the Rams product inside Telegram: spare, honest, functional. /privacy is a promise visibly kept, not a legal disclaimer. - Malay aliases are the user's explicit addition — locals should be able to drive in their own language from day one. - Single-file source of truth predilection carries over: the strings module is the copy's tokens.css — no copy pasted inline in handlers. ## Canonical References **Downstream agents MUST read these before planning or implementing.** ### Phase scope + decisions - `.planning/ROADMAP.md` §Phase 2 — goal, 4 success criteria, notes (KV namespace, grammY research gap) - `.planning/REQUIREMENTS.md` — ONBD-01, PRIV-01, PRIV-02 (this phase's requirements) - `.planning/phases/01-design-tokens-rams-foundation/01-CONTEXT.md` — Phase 1 pattern precedent (workspace structure, hand-rolled test runner, TDD discipline established) ### Codebase - `.planning/codebase/CONCERNS.md`, `STACK.md`, `ARCHITECTURE.md` — pre-existing repo caveats (secret handling from day one) - `AGENTS.md` — coding conventions ## Existing Code Insights ### Reusable Assets - `theme/` npm workspace pattern (root delegates + per-workspace package.json + scripts) — the repo's established structure for every subsequent workspace - `theme/scripts/audit.test.mjs` — hand-rolled test runner pattern: node:assert, minimal, no deps (D-12 builds on this shape) - Package-layout precedent: isolated concerns in their own module files (e.g. tokens/components/audit separation) ### Established Patterns - TDD discipline from Phase 1 (RED→GREEN commits where sensible) - Type-only imports, zero-radius kebab-case vocabulary, strict gitignored `.planning/` - npm workspaces with self-hosted assets — same mental model applies to backend secrets ### Integration Points - `theme/dist/rams.css` + basemap style become Mini App static assets served by the same Worker in Phase 3 — Phase 2's worker must implicitly plan where static asset binding lands (researcher's structural hint, not Phase 2 implementation) - `chat_sessions` KV namespace is the first durable namespace — its TTL/shape is what Phase 5's envelope design extends ## Deferred Ideas - Webhook dispatch via static-asset router to inline Telegram Mini App serving (Phase 3 shapes the mixed static+api worker, not this phase) - Metrics/diagnostics beyond category logs (Phase 7 audit scope decides) None — discussion stayed within phase scope. --- *Phase: 02-bot-skeleton-initdata-security* *Context gathered: 2026-10-22*