# ADR-001: Community architecture for Hermes Kanban Telegram Mini App Status: Proposed for implementation Date: 2026-07-10 Decision owner: project maintainer Scope: open-source community release, before rewriting the current local FastAPI prototype ## 1. Context The current repository contains an uncommitted prototype with a Vite/React frontend and a Python/FastAPI backend that shells out to `hermes kanban`. The target changed: the public release should be best-practice, secure, easy to install, documented in English and Russian, pleasant as a Telegram Mini App and normal browser/mobile app, and work well on Vercel. Hard constraint: a static Vercel frontend cannot safely execute a user's local `hermes` CLI or read local `~/.hermes/kanban.db`. Any architecture that claims otherwise is untrustworthy. A remote browser UI must talk to a trusted server-side component that is already authorized to reach the user's Hermes instance. ## 2. Verified facts used for this ADR ### Hermes Agent From the official Hermes docs and local checked source at `/home/kisskin/.hermes/hermes-agent`: - Hermes API Server exposes Hermes as an OpenAI-compatible HTTP endpoint for external frontends (`POST /v1/chat/completions`, `POST /v1/responses`, `POST /v1/runs`, sessions, jobs, skills/toolsets discovery, health). Source: `/home/kisskin/.hermes/hermes-agent/website/docs/user-guide/features/api-server.md`. - API Server is enabled with env vars including `API_SERVER_ENABLED=true`, `API_SERVER_KEY`, `API_SERVER_HOST`, `API_SERVER_PORT`, and optional `API_SERVER_CORS_ORIGINS`. Default bind host is `127.0.0.1`; bearer auth is required. Source: same file, Configuration and Authentication sections. - Hermes API Server is powerful: it gives access to the agent's toolset including terminal commands, so the key must never be exposed to a browser. Source: same file, Security warning. - Current API Server capabilities do not advertise first-class Kanban CRUD endpoints. The capabilities list includes chat/responses/runs, sessions, jobs, skills, and toolsets; searching `gateway/platforms/api_server.py` found no `/api/kanban` route. - Hermes Kanban is a durable SQLite-backed board. Tasks, comments, links, workspaces, logs, dispatcher behavior, boards, and board isolation are documented in `/home/kisskin/.hermes/hermes-agent/website/docs/user-guide/features/kanban.md`. - Hermes Kanban has two official surfaces today: agents use `kanban_*` tools, while humans/scripts use `hermes kanban ...` CLI, slash command, or the Hermes dashboard. Source: Kanban docs section "Two surfaces". ### Telegram Mini Apps From official Telegram Mini Apps docs and Telegram core docs: - Mini App init data is intended to be used as an authentication/authorization factor. - Server validation requires excluding `hash`, sorting key-value pairs, joining them with `\n`, deriving a secret with HMAC-SHA256 over the bot token using `WebAppData`, then HMAC-SHA256 over the data-check string and comparing hex digest to `hash`. - The bot token must remain server-side. A frontend may forward raw init data to its backend, but must not validate with the bot token in the browser. - The official React ecosystem template for Telegram Mini Apps uses React, TypeScript, Vite, `@tma.js/sdk-react` / Telegram Apps SDK packages, React Router HashRouter, and Telegram UI components. ### Vercel / browser deployment - Vercel is a good fit for Vite static frontend hosting and for lightweight Node.js serverless API routes. - Vercel serverless functions can call HTTPS APIs, but they cannot be trusted to execute a user's private local `hermes` CLI or access a local SQLite board unless the user separately exposes a backend/bridge over HTTPS. - Browser-side direct calls to Hermes API Server are unsafe for a community default because they would require exposing `API_SERVER_KEY` to JavaScript. If direct browser mode is documented, it must be explicitly marked local/dev only. ### Comparable open-source Telegram Mini Apps GitHub search verified active public projects/templates: - `Telegram-Mini-Apps/reactjs-template`: React, TypeScript, Vite, Telegram Mini Apps SDK, Telegram UI; suitable baseline for project structure. - `telegram-mini-apps-dev/TelegramUI`: React component library inspired by Telegram interface; suitable UI dependency. - Other public Telegram Mini App examples commonly combine React/TypeScript with Next.js or Vite and place secrets in a backend/server component, not in static browser code. ## 3. Decision Use this stack decisively: 1. Frontend: React + TypeScript + Vite, hosted as static assets on Vercel or any static host. 2. Telegram SDK/UI: `@telegram-apps/sdk-react` (or current successor package from Telegram Mini Apps docs) + `@telegram-apps/telegram-ui` for native-feeling Telegram UX. 3. API layer for Vercel/community default: TypeScript serverless BFF (Backend-for-Frontend) API routes deployed on Vercel. 4. Local integration: optional self-hosted Hermes Kanban Bridge, implemented in TypeScript/Node.js, running on the same trusted machine as Hermes. 5. Data source: the bridge uses stable `hermes kanban ... --json` CLI commands first, with fixed argv and `shell: false`. It may later switch to official Hermes Kanban HTTP endpoints if Hermes adds them. 6. Authentication: Vercel BFF validates Telegram `initData` server-side and issues a short-lived app session. The BFF then calls the self-hosted bridge using a separate bridge token. The bridge never trusts the browser directly. 7. Documentation: docs must include English and Russian install paths for Vercel + bridge, Docker Compose, and local dev. Short version: `Vercel static React Mini App + Vercel TS BFF + optional self-hosted TS bridge near Hermes`. ## 4. Why not keep the prototype stack as-is? The Python/FastAPI prototype proved the shape, but it is not the best public target because: - The user prefers TypeScript/JS and Vercel. - A Vercel-centered release is simpler if frontend and BFF share TypeScript types and validation schemas. - A Python backend on Vercel is possible but less ergonomic for this use case than a small Node BFF plus a separate self-hosted bridge. - The current prototype mixes concerns: Telegram auth, browser CORS, Hermes CLI subprocess, and static serving in one backend. For community release, the trust boundary must be explicit. Python/FastAPI is not rejected forever; it becomes a migration source and optional legacy adapter, not the recommended architecture. ## 5. Target architecture ```text Telegram client / mobile browser / desktop browser | | HTTPS, static assets, no secrets v Vercel-hosted React + TypeScript + Vite app | | Authorization: tma | or app session cookie/JWT after validation v Vercel TypeScript BFF API routes | - validates Telegram initData with TELEGRAM_BOT_TOKEN | - enforces allowed Telegram user IDs / roles | - rate limits and normalizes errors | - holds no Hermes CLI access | | HTTPS + Bridge token, server-to-server only v Optional self-hosted Hermes Kanban Bridge (Node.js) | - runs on trusted host near Hermes | - validates bridge token/mTLS or reverse-proxy auth | - maps narrow REST endpoints to fixed Hermes Kanban operations | - uses child_process.spawn/execFile with shell=false v Hermes install / profile | v Hermes Kanban board SQLite + dispatcher + logs/workspaces ``` ### Local-only shortcut For local development only: ```text Vite dev server -> local bridge on 127.0.0.1 -> hermes kanban CLI ``` This mode may use `AUTH_MODE=dev` and a fake Telegram user fixture, but it must be impossible to enable in production without an explicit env var. ## 6. Trust boundaries ### Public browser / Mini App Trusted for display only. Not trusted for identity, permissions, board slug, task IDs, or action payloads. It must not receive: - Telegram bot token. - Hermes API Server key. - Bridge token. - Local filesystem paths beyond what the bridge explicitly exposes as display-safe metadata. - Raw command output containing secrets. ### Vercel BFF Trusted to authenticate Telegram users and authorize UI actions. It has: - `TELEGRAM_BOT_TOKEN`. - `SESSION_SECRET` or JWT signing secret. - `BRIDGE_BASE_URL` and `BRIDGE_TOKEN` if a bridge is configured. It does not have direct local CLI access. ### Self-hosted bridge Trusted local integration process. It has: - Access to `hermes` binary and `HERMES_HOME`. - Optional `API_SERVER_KEY` only if a future bridge mode calls Hermes API Server for non-Kanban agent actions. - A bridge token accepted only from the BFF/reverse proxy. ### Hermes API Server Powerful agent API. Do not expose directly to the Mini App browser for community default. If users expose it, they must keep bearer auth server-side and set narrow CORS only when absolutely needed. ## 7. Authentication model ### Telegram initData validation Recommended request from frontend to BFF: ```http Authorization: tma ``` Server validation steps: 1. Parse query-string-like init data. 2. Remove and store `hash`. 3. Sort remaining key-value pairs alphabetically. 4. Join as `key=value` lines separated by `\n`. 5. Derive secret key: HMAC-SHA256 with key `WebAppData` and message `TELEGRAM_BOT_TOKEN`. 6. Compute HMAC-SHA256 of the data-check string using the derived key. 7. Compare hex digest to received `hash` using constant-time comparison. 8. Reject missing/invalid `auth_date`, expired data, missing user, or malformed JSON. 9. Apply allowlist/RBAC: `TELEGRAM_ALLOWED_USER_IDS`, optional `TELEGRAM_ADMIN_USER_IDS`. 10. Issue a short-lived app session cookie/JWT or validate `initData` on every API request. Recommended TTL: 1 hour for write actions, configurable up to 24 hours for read-only dashboards. ### App roles Minimum roles: - `viewer`: boards/tasks read-only, logs redacted/truncated. - `operator`: create/comment/assign/promote/block/unblock/archive/dispatch. - `admin`: configure bridge target, manage board allowlist, view diagnostics. Initial open-source release may implement allowlist-only authorization with `operator` for all allowed users and document RBAC as planned, but the API contract should reserve `role` fields now. ### Bridge authentication Recommended: - `Authorization: Bearer ` between BFF and bridge. - HTTPS at the public edge if bridge is reachable from Vercel. - Optional reverse-proxy IP allowlist, Cloudflare Access, Tailscale, or mTLS for advanced users. - Bridge should reject requests without `X-Request-Id` and should log audit metadata without storing Telegram raw initData. ## 8. Threat model | Threat | Risk | Mitigation | |---|---|---| | Browser steals Hermes API key | Full agent/tool compromise | Never put `API_SERVER_KEY` or bridge token in frontend env. Use BFF. | | Forged Telegram user | Unauthorized board control | Validate initData server-side using official HMAC flow and max age. | | Replay of old initData | Unauthorized action reuse | Enforce `auth_date` TTL; short-lived session; optional nonce/idempotency for writes. | | CLI injection | Arbitrary local commands | Bridge maps endpoints to fixed argv; use `execFile`/`spawn` with `shell:false`; validate task IDs/board slugs. | | Path traversal via board/workspace | Filesystem escape | Accept only Hermes-valid board slugs; never pass raw paths from browser except documented workspace kind with strict validation. | | Leaking secrets in logs/errors | Token disclosure | Redact `token`, `secret`, `password`, `api_key`; truncate logs; avoid returning raw stderr. | | CSRF against BFF | Actions from malicious site | Use SameSite cookies or Authorization header; verify Origin; require Telegram/session auth for writes. | | CORS overexposure | Browser exfiltration | BFF CORS only for app origin; bridge CORS disabled by default. | | SSRF from BFF to arbitrary bridge URL | Internal network probing | `BRIDGE_BASE_URL` env only; no user-provided target URLs. | | Bridge public exposure | Local Hermes takeover | Strong token, HTTPS, rate limiting, reverse-proxy auth, optional local-only mode. | | Denial of service by refresh/log tail | Resource exhaustion | Pagination, `limit` caps, debounce refresh, server-side rate limits, timeouts. | | Confused deputy across boards | User controls wrong board | Server-side board allowlist; all write endpoints include normalized board slug and audit actor. | | Supply-chain compromise | Public package risk | Pin lockfile, Dependabot/Renovate, CI tests, minimal dependencies. | ## 9. Deploy matrix | Mode | Frontend | BFF | Bridge | Hermes access | Recommended for | Notes | |---|---|---|---|---|---|---| | Vercel + self-hosted bridge | Vercel static Vite | Vercel Node serverless routes | Node bridge on user's server/Pi/VPS | Bridge runs local `hermes kanban` | Community default | Best UX for Telegram HTTPS and simple updates. Requires exposing bridge securely. | | Docker Compose single host | Containerized static frontend served by BFF or Nginx | Node BFF container | Node bridge container or same process | Bind-mount Hermes home / host `hermes` | Self-hosters | Strong docs needed for volumes and user permissions. Avoid mounting Docker socket. | | Local dev | Vite dev server | Local Node BFF | Local bridge | Local `hermes kanban` | Contributors | Supports Telegram mock/dev auth. No public exposure. | | Fully static Vercel only | Vercel static | None | None | None | Demo/readme only | Cannot control real Hermes. Can show mock data only. | | Browser -> Hermes API direct | Static or local | None | None | Hermes API Server over HTTPS | Not recommended | Exposes bearer token to browser unless using a trusted private network. Document as unsafe for public/community default. | | Future native Hermes Kanban API | Vercel static | Vercel BFF | Optional/none | BFF calls official `/api/kanban` | Future | Adopt only when official Hermes exposes least-privilege Kanban CRUD separate from full agent API. | ## 10. API contract for v1 All public frontend calls go to the BFF under `/api/*`. The BFF calls the bridge under `/bridge/v1/*` or equivalent. The frontend should not know whether data came from a bridge or future native Hermes API. ### Common headers Frontend -> BFF: ```http Authorization: tma Content-Type: application/json X-Request-Id: ``` BFF -> Bridge: ```http Authorization: Bearer Content-Type: application/json X-Request-Id: X-Actor-Telegram-Id: X-Actor-Role: viewer|operator|admin ``` ### Common response envelope ```ts type ApiOk = { ok: true; data: T; requestId: string; }; type ApiError = { ok: false; error: { code: string; message: string; details?: unknown; }; requestId: string; }; ``` ### Core models ```ts type Board = { slug: string; name?: string; description?: string; icon?: string; current?: boolean; }; type TaskStatus = 'triage' | 'todo' | 'ready' | 'running' | 'blocked' | 'done' | 'archived'; type TaskSummary = { id: string; title: string; status: TaskStatus; assignee?: string | null; priority?: number | null; createdAt?: string; updatedAt?: string; blockedReason?: string | null; parentIds?: string[]; childIds?: string[]; }; type TaskDetail = TaskSummary & { body?: string | null; comments?: TaskComment[]; runs?: TaskRun[]; events?: TaskEvent[]; attachments?: TaskAttachment[]; }; type TaskComment = { id?: string; author?: string; body: string; createdAt?: string; }; type TaskRun = { id?: string; status?: string; startedAt?: string; finishedAt?: string; logAvailable?: boolean; }; type TaskEvent = { id?: string; type: string; message?: string; createdAt?: string; }; type TaskAttachment = { id: string; filename: string; sizeBytes?: number; mimeType?: string; }; ``` ### BFF endpoints Read: - `GET /api/health` -> `{ status, version, bridge: { configured, reachable } }` - `GET /api/me` -> current Telegram user + role + auth expiry - `GET /api/boards` -> `Board[]` - `GET /api/boards/:board/tasks?status=&assignee=&q=&limit=&cursor=` -> paginated `TaskSummary[]` grouped client-side or server-side - `GET /api/boards/:board/tasks/:taskId` -> `TaskDetail` - `GET /api/boards/:board/tasks/:taskId/log?lines=200` -> redacted log tail - `GET /api/boards/:board/events/stream` -> SSE for future live updates; polling fallback required Write: - `POST /api/boards/:board/tasks` - `POST /api/boards/:board/tasks/:taskId/comment` - `POST /api/boards/:board/tasks/:taskId/assign` - `POST /api/boards/:board/tasks/:taskId/promote` - `POST /api/boards/:board/tasks/:taskId/block` - `POST /api/boards/:board/tasks/:taskId/unblock` - `POST /api/boards/:board/tasks/:taskId/archive` - `POST /api/boards/:board/dispatch` Write payload examples: ```json { "title": "Implement mobile task drawer", "body": "Acceptance criteria...", "assignee": "developer", "priority": 3, "workspaceKind": "worktree", "workspacePath": null, "goalMode": false, "maxRuntimeSeconds": 3600, "idempotencyKey": "optional-client-generated-key" } ``` ```json { "body": "Looks good, unblock after config is set." } ``` ```json { "assignee": "reviewer" } ``` ```json { "reason": "Needs maintainer decision on API shape." } ``` ### Bridge endpoint behavior Bridge can mirror BFF paths under `/bridge/v1/boards/...`, but it must be internal-only. It should return the same envelope. It must: - Validate all slugs and IDs before spawning Hermes CLI. - Use a command allowlist, not arbitrary pass-through. - Set `HERMES_HOME` from env, not request payload. - Set `HERMES_KANBAN_BOARD` or `--board` from validated board slug. - Enforce timeouts per command. - Redact and normalize errors. ## 11. UX requirements Telegram Mini App: - Respect Telegram theme variables and safe areas. - Use Telegram-style components for lists, section headers, buttons, badges, bottom sheets/drawers, destructive confirmations. - Use haptic feedback only for meaningful actions if SDK supports it. - `MainButton`/bottom action area for primary task action on mobile. - HashRouter or equivalent router compatible with Telegram webview and static hosting. Browser/mobile: - Responsive board: columns on desktop, status tabs/list on mobile. - Fast read path: skeleton loading, optimistic comment append only after server accepts writes or clearly mark pending. - Polling fallback; SSE enhancement later. - Accessible color contrast and keyboard navigation in browser. Community docs/quality: - `.env.example` only placeholders. - No real bot tokens, Hermes keys, domains, local IPs, or passwords. - README in English plus Russian quickstart or `README.ru.md`. - `SECURITY.md`, `CONTRIBUTING.md`, CI, tests, build checks. ## 12. Documentation outline ### English docs 1. `README.md` - What is Hermes Kanban Mini App? - Screenshots/GIFs - Architecture summary - Quickstart matrix: Vercel + bridge, Docker Compose, local dev - Security warning: static frontend cannot run Hermes CLI - Links to full docs 2. `docs/installation.en.md` - Prerequisites: Node.js, Hermes Agent, Telegram bot, HTTPS URL - Create Telegram bot and configure Mini App domain - Vercel frontend/BFF deploy - Self-hosted bridge setup - Docker Compose setup - Local dev setup with mock Telegram auth 3. `docs/configuration.en.md` - All env vars - Auth modes - Board allowlist - Rate limit settings - Reverse proxy examples 4. `docs/security.en.md` - Threat model - Token handling - Telegram initData validation - Bridge exposure hardening - Responsible disclosure 5. `docs/api.en.md` - BFF and bridge endpoint contracts - Error codes - SSE/polling behavior 6. `docs/migration-from-fastapi-prototype.en.md` - Mapping current prototype endpoints to new TS BFF/bridge - Removed assumptions - Compatibility notes ### Russian docs 1. `README.ru.md` - Что это и кому нужно - Рекомендуемая схема установки - Главное предупреждение про Vercel/static и локальный Hermes CLI 2. `docs/installation.ru.md` - Telegram BotFather - Vercel - self-hosted bridge - Docker Compose - локальный dev 3. `docs/configuration.ru.md` - Переменные окружения - режимы авторизации - allowlist пользователей/досок 4. `docs/security.ru.md` - модель угроз - где хранить токены - как безопасно открыть bridge 5. `docs/api.ru.md` - контракт API - ошибки 6. `docs/migration-from-fastapi-prototype.ru.md` - план миграции с текущего прототипа ## 13. Environment variables for the new stack Frontend build-time public vars: ```bash VITE_APP_NAME=Hermes Kanban VITE_BFF_BASE_URL= # empty for same-origin Vercel deployment VITE_TELEGRAM_BOT_USERNAME= # optional display/link only, not token ``` Vercel BFF server vars: ```bash TELEGRAM_BOT_TOKEN= # secret, server-only TELEGRAM_ALLOWED_USER_IDS= # comma-separated Telegram IDs TELEGRAM_ADMIN_USER_IDS= # optional SESSION_SECRET= # random 32+ bytes BRIDGE_BASE_URL= # https://bridge.example.com BRIDGE_TOKEN= # secret, server-to-server ALLOWED_ORIGINS= # exact frontend origins AUTH_MAX_AGE_SECONDS=3600 READ_AUTH_MAX_AGE_SECONDS=86400 RATE_LIMIT_PER_MINUTE=60 ``` Bridge server vars: ```bash BRIDGE_TOKEN= # must match BFF HERMES_BIN=hermes HERMES_HOME= # target Hermes profile home HERMES_KANBAN_BOARD=default BRIDGE_HOST=127.0.0.1 BRIDGE_PORT=8787 BRIDGE_ALLOWED_BOARDS=default COMMAND_TIMEOUT_SECONDS=30 LOG_TAIL_MAX_LINES=500 ``` Docker Compose vars should mirror the same names. `.env` must stay gitignored; `.env.example` must contain placeholders only. ## 14. Migration plan from current FastAPI prototype Do not modify the current Python implementation until this ADR is accepted. Phase 0: Freeze and document - Keep current prototype uncommitted as reference only. - Add this ADR. - Record endpoint inventory and data shapes from `backend/app/main.py`, `adapter.py`, `auth.py`, and frontend calls. Phase 1: Extract contracts - Create shared TypeScript schemas for Board, TaskSummary, TaskDetail, API envelope, and write payloads. - Write contract tests against fixtures copied from current prototype responses. - Define error code taxonomy. Phase 2: Build TypeScript bridge - Implement Node bridge with fixed command allowlist matching current `HermesCliKanbanAdapter` behavior. - Port Telegram-independent validation helpers: board slug, task ID, safe log limits, error redaction. - Add tests that assert `shell:false` / `execFile` usage and reject invalid IDs/slugs. - Verify against local `hermes kanban` commands. Phase 3: Build Vercel BFF - Implement Telegram initData validation using a maintained Node package such as `@tma.js/init-data-node` or a small audited HMAC helper with tests from Telegram examples. - Add allowlist auth and bridge proxy endpoints. - Add rate limiting and CORS/origin checks. Phase 4: Rebuild frontend UX - Keep React + TypeScript + Vite. - Replace ad-hoc Telegram integration with Telegram Apps SDK React package. - Add Telegram UI components, mobile task drawer, board status tabs, desktop columns. - Use BFF API only; no direct bridge/Hermes calls from browser. Phase 5: Docs and release hardening - Write EN/RU docs from outline. - Add `.env.example`, `SECURITY.md`, `CONTRIBUTING.md`, CI. - Add unit tests, contract tests, frontend tests, build check. - Add Docker Compose example. - Add Vercel deployment guide. Phase 6: Optional legacy compatibility - Either remove Python prototype from the public release or move it to `legacy/fastapi-prototype` with clear unsupported status. - Do not publish both as equal recommended paths; it confuses users. ## 15. Consequences Positive: - Clear Vercel-first story without lying about local CLI access. - Secrets stay server-side. - TypeScript types can be shared across frontend, BFF, and bridge. - Bridge is optional but honest: users who want real Hermes control must run a trusted server near Hermes. - Future Hermes native Kanban HTTP API can replace bridge internals without changing frontend UX. Negative/tradeoffs: - Requires two deploy targets for real use: Vercel app and bridge. - Public bridge exposure needs careful docs. - Slightly more moving parts than a single FastAPI container. - Until Hermes exposes first-class Kanban HTTP endpoints, the bridge still depends on CLI JSON behavior. ## 16. Rejected alternatives ### Static Vercel app directly runs Hermes CLI Rejected. Browsers/static hosting cannot execute the user's local CLI. Any workaround would require unsafe local agents or browser extensions and is not community-ready. ### Static app directly calls Hermes API Server with API key Rejected for public/community default. Hermes API Server bearer key controls a full agent toolset including terminal; exposing it to JavaScript is a critical security issue. ### Keep single Python/FastAPI backend as recommended public stack Rejected as recommended stack. It works locally and may be a useful prototype, but it conflicts with TypeScript/Vercel preference and mixes trust boundaries. ### Full Next.js monolith Not selected. Next.js could host frontend+BFF on Vercel, but Vite static + small serverless BFF is simpler, lighter, and closer to official Telegram Mini Apps React template patterns. If future SSR/auth needs grow, migration to Next.js remains possible. ### Bridge reads `kanban.db` directly Rejected for v1. Direct DB reads couple the project to Hermes internals and risk schema drift. Use official CLI behavior first; switch to official HTTP API when available. ## 17. Precise next implementation milestones M1. Contract foundation - Add `packages/shared` or `src/shared` TypeScript schemas. - Add API envelope and error code tests. - No UI rewrite yet. M2. Bridge MVP - Create `apps/bridge` TypeScript service. - Implement `GET /health`, `GET /bridge/v1/boards`, `GET /bridge/v1/boards/:board/tasks`, `GET /bridge/v1/boards/:board/tasks/:taskId`. - Use fixed `execFile`/`spawn` calls to `hermes kanban` with tests. M3. BFF auth MVP - Create Vercel API routes. - Validate Telegram initData in server code. - Proxy read endpoints to bridge. - Add auth fixtures/tests. M4. Frontend read-only UX - Migrate current React UI to new BFF contract. - Add Telegram SDK initialization and Telegram UI theme handling. - Ship read-only boards/tasks/log tail. M5. Write actions - Add create/comment/assign/promote/block/unblock/archive/dispatch. - Add confirmations and role checks. - Add audit log fields passed to bridge. M6. Release docs and CI - EN/RU install docs. - `.env.example` for frontend, BFF, bridge, Docker Compose. - `SECURITY.md`, `CONTRIBUTING.md`. - CI: typecheck, lint, unit tests, frontend build, bridge tests. M7. Optional live updates - SSE endpoint from BFF to frontend. - Bridge polling or future Hermes dashboard/events integration. - Graceful fallback to polling. ## 18. Acceptance criteria for starting app-code rewrite Do not write implementation code until these are accepted: - This ADR stack decision is approved. - Maintainer confirms whether bridge is a separate `apps/bridge` service or same package as BFF with a runtime mode switch. - Maintainer confirms whether the public repo should delete the Python prototype or keep it under `legacy/`. - First milestone is limited to contracts + tests + scaffold, not feature-complete UI.