From 41fff33ae016478b54fc6b748353eb935e5b58ac Mon Sep 17 00:00:00 2001 From: DanliaQwerty20 Date: Wed, 23 Sep 2026 18:50:49 +0300 Subject: [PATCH 1/4] docs: record channel decision flow --- docs/adr/0013-conversation-orchestration.md | 20 +++++++++++------- docs/delivery/CURRENT_STATUS.md | 23 +++++++++++---------- docs/product/CALENDAR_EVENT_MVP.md | 14 ++++++++----- scripts/check-docs.ps1 | 20 ++++++++---------- 4 files changed, 42 insertions(+), 35 deletions(-) diff --git a/docs/adr/0013-conversation-orchestration.md b/docs/adr/0013-conversation-orchestration.md index 7aba9d2..1bdb3a1 100644 --- a/docs/adr/0013-conversation-orchestration.md +++ b/docs/adr/0013-conversation-orchestration.md @@ -37,23 +37,27 @@ sequenceDiagram Talk-->>Edge: текстовый вопрос end Edge-->>Channel: общий ответ канала + Channel->>Edge: actionId + payloadHash + решение + Edge->>Action: то же решение + identity + Action-->>Channel: принятое состояние действия ``` Границы компонентов: - `Channel Gateway` проверяет identity, нормализует запрос и переводит общий ответ в протокол канала. - Он остаётся stateless и не вызывает `Agent Runtime` или `Action Service` напрямую после миграции. + Он остаётся stateless: сообщения передаёт Conversation Service, а команды виджета — Action Service. + Прямой вызов Agent Runtime остаётся только на временном совместимом маршруте. - `Conversation Service` хранит сообщения и состояние диалога, обеспечивает идемпотентность по `tenantId + subject + requestKey`, вызывает Agent и Action. - `Agent Runtime` только возвращает предложение действия или вопрос для уточнения. - `Action Service` остаётся источником истины для `actionId`, `payloadHash`, статуса и результата. - `Widget SDK` проверяет и отображает общий контракт карточки. Он не вызывает Agent, MCP или базу. -- Кнопка карточки отправляет решение вместе с `actionId` и `payloadHash`; только `Action Service` - принимает или отклоняет действие. +- Кнопка карточки отправляет решение вместе с `actionId` и `payloadHash` в Channel Gateway. Gateway не + меняет команду, а передаёт её в Action Service; только Action Service принимает или отклоняет действие. -Переход выполняется совместимо: существующий `/api/v1/messages` остаётся временным техническим -маршрутом до появления Conversation Service. Новый диалоговый endpoint добавляется отдельно; старый -удаляется только в следующей major-версии контракта. +Переход выполнен совместимо: `/api/v1/conversations/messages` обслуживает новые каналы, а существующий +`/api/v1/messages` остаётся временным техническим маршрутом. Решение виджета проходит через +`/api/v1/actions/{actionId}/decisions`. Старый маршрут удаляется только в следующей major-версии. ## Причина @@ -63,8 +67,8 @@ sequenceDiagram ## Последствия -- Появится отдельный репозиторий `conversation-service` со своей базой и миграциями. -- До его реализации Channel Gateway продолжает прямой вызов Agent как временный мост. +- Conversation Service работает в отдельном репозитории со своей базой и миграциями. +- Channel Gateway сохраняет прямой вызов Agent только для обратной совместимости. - Контракт ответа канала должен различать текст, карточку подтверждения и результат. - Карточка содержит только отображаемые данные и ссылку на сохранённое действие; секреты и правила выполнения в неё не попадают. diff --git a/docs/delivery/CURRENT_STATUS.md b/docs/delivery/CURRENT_STATUS.md index 4a23733..9665d57 100644 --- a/docs/delivery/CURRENT_STATUS.md +++ b/docs/delivery/CURRENT_STATUS.md @@ -1,21 +1,21 @@ # Текущее состояние по репозиториям -Дата среза: 14 сентября 2026 года. Этот файл обновляется после мержа этапа, а не после локального -эксперимента. +Дата среза: 23 сентября 2026 года. Этот файл обновляется после проверенного локального этапа или мержа, +а не после незавершённого эксперимента. | Репа | Что меняем или добавляем | Ожидаемый результат | Фактический результат | Следующий шаг | |---|---|---|---|---| | `.github` | Общие workflows и правила | Одинаковый CI/CD для всех сервисов | Java, Python, Node, docs, security и container workflows работают; чистый Trivy runner исправлен | Подключать правила к каждой новой репе | -| `contracts` | Версионируемые внешние и внутренние API | Один проверяемый источник сетевых DTO | Bundle `2.3.0` выпущен; добавлен Conversation API | Подключить контракт к Conversation Service | +| `contracts` | Версионируемые внешние и внутренние API | Один проверяемый источник сетевых DTO | Локальный bundle `2.5.0`: сообщения, confirmation card и решение через Gateway | Выпустить после восстановления GitHub | | `action-service` | Подтверждение и надёжное выполнение | `AWAITING_APPROVAL → APPROVED → EXECUTING → SUCCEEDED/FAILED` | jOOQ, outbox, Temporal worker и вызов MCP Gateway работают; backend acceptance зелёный | Принимать команду через Channel Gateway | | `calendar-mcp` | `create_event` | Идемпотентное создание fake-события | Fake Calendar, OIDC и защита от дублей работают в общем сценарии | Оставить эталонным fake-коннектором | | `mcp-gateway` | OIDC, allowlist и MCP client | Безопасный stateless-маршрутизатор | Foundation смержен и проверен вызовом Calendar MCP | Добавлять коннекторы только по контракту | -| `deploy` | Приложения в локальном Compose | Одна команда поднимает вертикальный backend-срез | Channel, Agent, Action, Temporal, MCP Gateway и Calendar поднимаются; GitHub smoke зелёный | Ускорить сборку полного smoke | -| `test-lab` | Календарный acceptance-тест | Один JWT и один контракт на всём пути | Схема Agent `2.1.0`, подтверждение и проверка часового пояса работают | Добавить повтор запроса и проверку отсутствия дубля | +| `deploy` | Приложения в локальном Compose | Одна команда поднимает вертикальный backend-срез | Весь срез с Conversation поднимается; Gateway связан с Conversation и Action | Обновить старый локальный Keycloak volume или использовать чистое окружение | +| `test-lab` | Календарный acceptance-тест | Один JWT и один контракт на всём пути | 19/19: карточка, решение через Gateway, Temporal, offset и отсутствие дубля | Добавить первый адаптер канала | | `agent-runtime` | Текст в предложение действия | Детерминированное предложение встречи без скрытого выполнения | API `2.1.0`, проверка JWT и календарное предложение работают в общем сценарии | Вызывать через Channel Gateway, AI-модель пока не выбирать | -| `channel-gateway` | Общий вход каналов | Web и Telegram используют один контракт | Репа создана; JWT, OpenAPI-типы и вызов Agent работают в общем acceptance | После Conversation Service заменить временный прямой вызов Agent | -| `conversation-service` | Состояние диалога и прикладная оркестрация | Повтор сообщения не создаёт второе действие | Хранение и durable processing смержены: ключ повтора, TTL, atomic lease, fencing token и reply cleanup проверены на PostgreSQL | Вызвать Agent Runtime по закреплённому контракту | -| `widget-sdk` | Карточка подтверждения | Один UI-контракт для разных каналов | Renderer-neutral core `0.1.0` выпущен; runtime schema, generated type и decision command проверены | Подключить к первому адаптеру после Conversation Service | +| `channel-gateway` | Общий вход каналов | Web и Telegram используют один контракт | Сообщение идёт через Conversation; команда Widget SDK — через Gateway в Action | Подключить первый адаптер | +| `conversation-service` | Состояние диалога и прикладная оркестрация | Повтор сообщения не создаёт второе действие | PostgreSQL, lease, Agent → Action, карточка и сохранение offset проверены общим E2E | Оставить владельцем диалога при подключении адаптера | +| `widget-sdk` | Карточка подтверждения | Один UI-контракт для разных каналов | Renderer-neutral core создаёт проверенную decision command | Подключить к Telegram adapter | ## Что уже проверено @@ -48,9 +48,10 @@ -> [готово] каркас Conversation Service -> [готово] privacy-first решение и идемпотентное хранение -> [готово] durable processing и защита нескольких worker - -> [следом] вызов Agent Runtime - -> переключение Channel Gateway - -> Telegram adapter + -> [готово] вызов Agent Runtime и создание Action + -> [готово] переключение Channel Gateway + -> [готово] решение виджета через Channel Gateway + -> [следом] Telegram adapter ``` ## Правило результата diff --git a/docs/product/CALENDAR_EVENT_MVP.md b/docs/product/CALENDAR_EVENT_MVP.md index 2439bb2..d7dedf6 100644 --- a/docs/product/CALENDAR_EVENT_MVP.md +++ b/docs/product/CALENDAR_EVENT_MVP.md @@ -30,6 +30,7 @@ sequenceDiagram autonumber actor User as Пользователь participant Channel as Канал + participant Talk as Conversation Service participant Agent as Agent Runtime participant Action as Action Service participant Widget as Виджет @@ -37,13 +38,16 @@ sequenceDiagram participant Calendar as Fake Calendar User->>Channel: Создай встречу завтра в 12:00 на 30 минут - Channel->>Agent: Нормализованный текст и часовой пояс - Agent-->>Channel: Уточнение, если данных не хватает - Agent->>Action: calendar.create_event + payload + requestKey - Action-->>Widget: AWAITING_APPROVAL + payloadHash + Channel->>Talk: Нормализованный текст и часовой пояс + Talk->>Agent: Разобрать сообщение + Agent-->>Talk: Уточнение, если данных не хватает + Talk->>Action: calendar.create_event + payload + requestKey + Action-->>Talk: AWAITING_APPROVAL + payloadHash + Talk-->>Widget: Карточка сохранённого действия Widget-->>User: Показать точные данные User->>Widget: Подтвердить payloadHash - Widget->>Action: CONFIRM + Widget->>Channel: CONFIRM + payloadHash + Channel->>Action: Та же команда решения Action->>Flow: Выполнить сохранённое действие Flow->>Calendar: Создать событие с requestKey Calendar-->>Flow: eventId diff --git a/scripts/check-docs.ps1 b/scripts/check-docs.ps1 index 2be7c3d..6b83d82 100644 --- a/scripts/check-docs.ps1 +++ b/scripts/check-docs.ps1 @@ -13,33 +13,31 @@ $requiredFiles = @( $missingFiles = $requiredFiles | Where-Object { -not (Test-Path -LiteralPath $_ -PathType Leaf) } if ($missingFiles.Count -gt 0) { - throw "Нет обязательных файлов: $($missingFiles -join ', ')" + throw "Required files are missing: $($missingFiles -join ', ')" } $adrFiles = Get-ChildItem -LiteralPath "docs/adr" -Filter "*.md" -File -ErrorAction SilentlyContinue if ($adrFiles.Count -eq 0) { - throw "В docs/adr нужен хотя бы один ADR." + throw "docs/adr must contain at least one ADR." } $agentText = Get-Content -LiteralPath "AGENTS.md" -Raw -$requiredAgentSections = @("Назначение", "Границы", "Структура", "Команды", "Правила изменений") -foreach ($section in $requiredAgentSections) { - if ($agentText -notmatch "(?m)^## $([regex]::Escape($section))\s*$") { - throw "В AGENTS.md нет раздела: $section" - } +$agentSections = [regex]::Matches($agentText, "(?m)^##\s+\S.*$") +if ($agentSections.Count -lt 5) { + throw "AGENTS.md must contain at least five rule sections." } $catalogText = Get-Content -LiteralPath "catalog-info.yaml" -Raw if ($catalogText -notmatch "backstage\.io/techdocs-ref:\s*dir:\.") { - throw "В catalog-info.yaml нет backstage.io/techdocs-ref: dir:." + throw "catalog-info.yaml has no backstage.io/techdocs-ref: dir:." } if ($catalogText -notmatch "github\.com/project-slug:\s*\S+/\S+") { - throw "В catalog-info.yaml нет github.com/project-slug." + throw "catalog-info.yaml has no github.com/project-slug." } $mkdocsText = Get-Content -LiteralPath "mkdocs.yml" -Raw if ($mkdocsText -notmatch "(?m)^docs_dir:\s*docs\s*$") { - throw "В mkdocs.yml должен быть docs_dir: docs." + throw "mkdocs.yml must contain docs_dir: docs." } -Write-Host "Документация соответствует базовому стандарту." +Write-Host "Documentation follows the project standard." From 9e952bc59ef3c8b34259332e9798bc5f5f620326 Mon Sep 17 00:00:00 2001 From: DanliaQwerty20 Date: Wed, 23 Sep 2026 18:58:27 +0300 Subject: [PATCH 2/4] docs: record verified channel flow --- docs/delivery/CURRENT_STATUS.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/delivery/CURRENT_STATUS.md b/docs/delivery/CURRENT_STATUS.md index 9665d57..9116c51 100644 --- a/docs/delivery/CURRENT_STATUS.md +++ b/docs/delivery/CURRENT_STATUS.md @@ -7,10 +7,10 @@ |---|---|---|---|---| | `.github` | Общие workflows и правила | Одинаковый CI/CD для всех сервисов | Java, Python, Node, docs, security и container workflows работают; чистый Trivy runner исправлен | Подключать правила к каждой новой репе | | `contracts` | Версионируемые внешние и внутренние API | Один проверяемый источник сетевых DTO | Локальный bundle `2.5.0`: сообщения, confirmation card и решение через Gateway | Выпустить после восстановления GitHub | -| `action-service` | Подтверждение и надёжное выполнение | `AWAITING_APPROVAL → APPROVED → EXECUTING → SUCCEEDED/FAILED` | jOOQ, outbox, Temporal worker и вызов MCP Gateway работают; backend acceptance зелёный | Принимать команду через Channel Gateway | +| `action-service` | Подтверждение и надёжное выполнение | `AWAITING_APPROVAL → APPROVED → EXECUTING → SUCCEEDED/FAILED` | jOOQ, outbox, Temporal worker и вызов MCP Gateway работают; решение приходит через Channel Gateway | Оставить источником истины для решения и статуса | | `calendar-mcp` | `create_event` | Идемпотентное создание fake-события | Fake Calendar, OIDC и защита от дублей работают в общем сценарии | Оставить эталонным fake-коннектором | | `mcp-gateway` | OIDC, allowlist и MCP client | Безопасный stateless-маршрутизатор | Foundation смержен и проверен вызовом Calendar MCP | Добавлять коннекторы только по контракту | -| `deploy` | Приложения в локальном Compose | Одна команда поднимает вертикальный backend-срез | Весь срез с Conversation поднимается; Gateway связан с Conversation и Action | Обновить старый локальный Keycloak volume или использовать чистое окружение | +| `deploy` | Приложения в локальном Compose | Одна команда поднимает вертикальный backend-срез | Чистый срез поднят и проверен; Gateway связан с Conversation и Action | Подключить Telegram adapter после решения по identity | | `test-lab` | Календарный acceptance-тест | Один JWT и один контракт на всём пути | 19/19: карточка, решение через Gateway, Temporal, offset и отсутствие дубля | Добавить первый адаптер канала | | `agent-runtime` | Текст в предложение действия | Детерминированное предложение встречи без скрытого выполнения | API `2.1.0`, проверка JWT и календарное предложение работают в общем сценарии | Вызывать через Channel Gateway, AI-модель пока не выбирать | | `channel-gateway` | Общий вход каналов | Web и Telegram используют один контракт | Сообщение идёт через Conversation; команда Widget SDK — через Gateway в Action | Подключить первый адаптер | @@ -32,7 +32,7 @@ ``` Проверка запускает реальные контейнеры отдельных репозиториев по закреплённым commit SHA. Последний -зелёный запуск вошёл в `deploy` через merge `722d33f`. +локальный зелёный запуск: `deploy` `3e67663`, 19 из 19 проверок прошли 23 сентября 2026 года. ## Текущий порядок @@ -51,6 +51,7 @@ -> [готово] вызов Agent Runtime и создание Action -> [готово] переключение Channel Gateway -> [готово] решение виджета через Channel Gateway + -> [решить] безопасная привязка Telegram identity к пользователю платформы -> [следом] Telegram adapter ``` From 958a9fa742fe53e8a16bc88799a2bf256f94faa2 Mon Sep 17 00:00:00 2001 From: DanliaQwerty20 Date: Fri, 25 Sep 2026 00:18:21 +0300 Subject: [PATCH 3/4] docs: define telegram account link --- docs/adr/0014-telegram-account-link.md | 35 ++++++++++++++++++++++++++ docs/architecture/REPOSITORY_MODEL.md | 8 +++--- docs/delivery/CURRENT_STATUS.md | 8 +++--- mkdocs.yml | 2 ++ 4 files changed, 47 insertions(+), 6 deletions(-) create mode 100644 docs/adr/0014-telegram-account-link.md diff --git a/docs/adr/0014-telegram-account-link.md b/docs/adr/0014-telegram-account-link.md new file mode 100644 index 0000000..bedecba --- /dev/null +++ b/docs/adr/0014-telegram-account-link.md @@ -0,0 +1,35 @@ +# ADR-0014: Telegram связывается с identity через Device Flow + +- Status: accepted +- Date: 2026-09-25 + +## Контекст + +Telegram update содержит внешний user id, но не содержит JWT Portable Agent. Если Gateway будет доверять +этому id, адаптер сможет выдать себя за любого пользователя. Пароль пользователя также нельзя передавать +боту или сохранять в адаптере. + +## Решение + +Команда `/link` начинает OAuth 2.0 Device Authorization Grant в Keycloak. Пользователь открывает ссылку, +входит в Keycloak и подтверждает одноразовый код. После подтверждения адаптер получает короткий access +token и refresh token. + +Access token используется для вызова Channel Gateway от имени пользователя. Refresh token шифруется +AES-256-GCM до записи в отдельную базу Telegram Adapter. Ключ приходит из secret manager и не хранится +в Git. Telegram user id остаётся только внешним ключом канала. + +## Границы + +- Telegram Adapter отвечает за webhook, account link и формат Telegram. +- Keycloak подтверждает identity и выпускает токены. +- Channel Gateway продолжает проверять JWT и не доверяет заголовкам с внешним user id. +- Conversation Service хранит диалог. +- Action Service принимает решение и выполняет действие. + +## Последствия + +- первый вход требует открыть страницу Keycloak; +- адаптер хранит зашифрованную связь и становится stateful; +- нужны polling, `/unlink`, отзыв refresh token и ротация ключа; +- такой же подход можно повторить для VK и других каналов без изменения Gateway. diff --git a/docs/architecture/REPOSITORY_MODEL.md b/docs/architecture/REPOSITORY_MODEL.md index f887966..0d14944 100644 --- a/docs/architecture/REPOSITORY_MODEL.md +++ b/docs/architecture/REPOSITORY_MODEL.md @@ -8,7 +8,7 @@ Portable Agent использует GitHub Organization и отдельную р |---|---|---| | `.github` | Общие шаблоны, правила и CI/CD | Создан, развивается | | `platform` | Архитектура, ADR, карта и публичная документация | Создан, сайт опубликован | -| `contracts` | OpenAPI, AsyncAPI, JSON Schema и примеры | Bundle `2.1.0` выпущен и используется сервисами | +| `contracts` | OpenAPI, AsyncAPI, JSON Schema и примеры | Локальный bundle `2.5.0` используется сервисами | | `action-service` | Java-сервис действий | jOOQ, MVC, outbox и Temporal worker работают в backend-срезе | | `agent-runtime` | Python-сервис агента | MVC, JWT и предложение календарного действия работают в backend-срезе | | `deploy` | Compose, Helm charts и тестовые окружения | Локальный backend-срез и GitHub acceptance проходят одной командой | @@ -17,6 +17,9 @@ Portable Agent использует GitHub Organization и отдельную р | `calendar-mcp` | MCP-интеграция календаря | Fake Calendar, OIDC и идемпотентность работают в общем сценарии | | `mcp-gateway` | Безопасный вызов настроенных MCP-сервисов | Stateless-маршрутизатор работает между Action и Calendar MCP | | `channel-gateway` | Единый вход независимых каналов | Текстовый API, JWT и вызов Agent Runtime работают в общем сценарии | +| `conversation-service` | Состояние диалога | PostgreSQL, защита повторов и цепочка Agent → Action работают в общем сценарии | +| `widget-sdk` | Независимая от канала модель виджета | Карточка подтверждения и decision command проверяются без UI-фреймворка | +| `telegram-adapter` | Тонкая граница Telegram | Локальная репа, защищённый webhook и начало Device Flow проверены тестами и Docker build | Каркас означает, что настроены структура и инженерные проверки. Это не означает, что правила бизнеса уже спроектированы или реализованы. @@ -25,10 +28,9 @@ Portable Agent использует GitHub Organization и отдельную р ```text portable-agent organization -├── conversation-service состояние диалога ├── approval-service подтверждение действий ├── policy-bundle правила OPA -└── widget-sdk переносимые виджеты +└── следующие channel adapters ``` Названия и границы запланированных репозиториев могут измениться до начала реализации. diff --git a/docs/delivery/CURRENT_STATUS.md b/docs/delivery/CURRENT_STATUS.md index 9116c51..6a1fe49 100644 --- a/docs/delivery/CURRENT_STATUS.md +++ b/docs/delivery/CURRENT_STATUS.md @@ -1,6 +1,6 @@ # Текущее состояние по репозиториям -Дата среза: 23 сентября 2026 года. Этот файл обновляется после проверенного локального этапа или мержа, +Дата среза: 25 сентября 2026 года. Этот файл обновляется после проверенного локального этапа или мержа, а не после незавершённого эксперимента. | Репа | Что меняем или добавляем | Ожидаемый результат | Фактический результат | Следующий шаг | @@ -16,6 +16,7 @@ | `channel-gateway` | Общий вход каналов | Web и Telegram используют один контракт | Сообщение идёт через Conversation; команда Widget SDK — через Gateway в Action | Подключить первый адаптер | | `conversation-service` | Состояние диалога и прикладная оркестрация | Повтор сообщения не создаёт второе действие | PostgreSQL, lease, Agent → Action, карточка и сохранение offset проверены общим E2E | Оставить владельцем диалога при подключении адаптера | | `widget-sdk` | Карточка подтверждения | Один UI-контракт для разных каналов | Renderer-neutral core создаёт проверенную decision command | Подключить к Telegram adapter | +| `telegram-adapter` | Telegram как сменный канал | Telegram identity привязывается только после входа через Keycloak | Отдельная локальная репа: webhook, `/link`, Device Flow, AES-256-GCM, PostgreSQL, 15/15 тестов и Docker build | Добавить polling и завершение привязки | ## Что уже проверено @@ -51,8 +52,9 @@ -> [готово] вызов Agent Runtime и создание Action -> [готово] переключение Channel Gateway -> [готово] решение виджета через Channel Gateway - -> [решить] безопасная привязка Telegram identity к пользователю платформы - -> [следом] Telegram adapter + -> [решено] Telegram identity привязывается через OAuth Device Flow + -> [готово] первый срез Telegram adapter: webhook и начало `/link` + -> [следом] polling и завершение привязки ``` ## Правило результата diff --git a/mkdocs.yml b/mkdocs.yml index b8a3796..ca7f48c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -34,3 +34,5 @@ nav: - Платформа разработки: adr/0010-developer-platform.md - Режимы container CI: adr/0011-container-ci-modes.md - Contract-first: adr/0012-contract-first.md + - Conversation Service: adr/0013-conversation-orchestration.md + - Привязка Telegram: adr/0014-telegram-account-link.md From 2f21fc4c514539925a7e91835636c2feea5c1d54 Mon Sep 17 00:00:00 2001 From: DanliaQwerty20 Date: Fri, 25 Sep 2026 00:30:50 +0300 Subject: [PATCH 4/4] docs: record completed telegram link --- docs/delivery/CURRENT_STATUS.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/delivery/CURRENT_STATUS.md b/docs/delivery/CURRENT_STATUS.md index 6a1fe49..1871aa3 100644 --- a/docs/delivery/CURRENT_STATUS.md +++ b/docs/delivery/CURRENT_STATUS.md @@ -16,7 +16,7 @@ | `channel-gateway` | Общий вход каналов | Web и Telegram используют один контракт | Сообщение идёт через Conversation; команда Widget SDK — через Gateway в Action | Подключить первый адаптер | | `conversation-service` | Состояние диалога и прикладная оркестрация | Повтор сообщения не создаёт второе действие | PostgreSQL, lease, Agent → Action, карточка и сохранение offset проверены общим E2E | Оставить владельцем диалога при подключении адаптера | | `widget-sdk` | Карточка подтверждения | Один UI-контракт для разных каналов | Renderer-neutral core создаёт проверенную decision command | Подключить к Telegram adapter | -| `telegram-adapter` | Telegram как сменный канал | Telegram identity привязывается только после входа через Keycloak | Отдельная локальная репа: webhook, `/link`, Device Flow, AES-256-GCM, PostgreSQL, 15/15 тестов и Docker build | Добавить polling и завершение привязки | +| `telegram-adapter` | Telegram как сменный канал | Telegram identity привязывается только после входа через Keycloak | Webhook и полный Device Flow: lease, AES-256-GCM, PostgreSQL, 25/25 unit, repository integration и Docker build | Обновлять access token и передавать сообщение в Gateway | ## Что уже проверено @@ -53,8 +53,8 @@ -> [готово] переключение Channel Gateway -> [готово] решение виджета через Channel Gateway -> [решено] Telegram identity привязывается через OAuth Device Flow - -> [готово] первый срез Telegram adapter: webhook и начало `/link` - -> [следом] polling и завершение привязки + -> [готово] Telegram adapter: webhook, `/link`, polling и зашифрованная связь + -> [следом] refresh access token и сообщение через Gateway ``` ## Правило результата