7.6 KiB
Phase 2: Bot Skeleton + initData Security - Context
Gathered: 2026-10-22 Status: Ready for planning
## Phase BoundaryUsers 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.
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:
/mularuns the same flow as/start,/privasias/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 amslanguage_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 Melayumslocale. Telegram's per-userlanguage_codepicks 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_sessionsalongside 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'slanguage_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 totheme/(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-typesfor 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_dateis older than ~15 minutes; the Mini App re-validates with fresh initData on launch. This pairs with thechat_sessionsminutes-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.exampleships 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_tokenparam) 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)
- 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_refs>
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
</canonical_refs>
<code_context>
Existing Code Insights
Reusable Assets
theme/npm workspace pattern (root delegates + per-workspace package.json + scripts) — the repo's established structure for every subsequent workspacetheme/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_sessionsKV namespace is the first durable namespace — its TTL/shape is what Phase 5's envelope design extends
</code_context>
## 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