From 1ff8fd7102cd7b75316d64f39cf68ab8d78cb318 Mon Sep 17 00:00:00 2001 From: Yera All Date: Mon, 6 Apr 2026 23:08:41 +0500 Subject: [PATCH] docs: recreate documentation structure with updated specs --- deployment/.env.images.example | 2 - docs/architecture/api-matrix.md | 91 --- docs/architecture/asterisk-user-events.md | 59 -- docs/architecture/call.recording.ready.json | 44 -- docs/architecture/event-envelope.json | 30 - docs/architecture/event-schemas.md | 58 +- docs/architecture/ivr.completed.json | 54 -- docs/architecture/overview.md | 72 +- docs/architecture/voice-ai-v1.md | 744 ------------------ docs/architecture/voice-ai.md | 39 + docs/gates/gate-01-foundation.md | 8 - docs/gates/gate-02-operator-core.md | 8 - docs/gates/gate-03-supervisor-reporting-kb.md | 12 - docs/gates/gate-04-pilot-hardening.md | 35 - docs/gates/mvp-pilot-baseline.md | 71 -- docs/gates/p1-p2-defects.md | 16 - docs/gates/uat-protocol-template.md | 12 - docs/kpi/definitions.md | 37 - docs/releases/v1.0.0-mvp.md | 22 - docs/roadmap/01-foundation.md | 25 - docs/roadmap/02-operator-core.md | 21 - docs/roadmap/03-supervisor-reporting-kb.md | 20 - docs/roadmap/04-pilot-hardening.md | 22 - docs/roadmap/05-wave2-backlog.md | 126 --- .../06-track10-live-voice-reliability.md | 65 -- .../07-track11-live-operator-call-control.md | 60 -- .../08-track12-operator-voice-ux-hardening.md | 57 -- .../09-track14-browser-softphone-webrtc.md | 45 -- docs/roadmap/10-track15-telegram-chat.md | 30 - docs/roadmap/README.md | 11 - docs/runbooks/.env.production.checklist.md | 132 ---- docs/runbooks/asterisk-lab-linux-vm.md | 315 -------- docs/runbooks/backup-restore.md | 20 - docs/runbooks/demo-showcase.md | 79 -- docs/runbooks/deployment-onprem.md | 175 ---- docs/runbooks/deployment.md | 60 ++ docs/runbooks/event-bus-local.md | 83 -- docs/runbooks/gitlab-cicd.md | 80 -- docs/runbooks/gitlab-server-deploy.md | 86 -- docs/runbooks/ivr-local.md | 102 --- docs/runbooks/keycloak-oidc.md | 128 --- docs/runbooks/load-test-plan.md | 20 - docs/runbooks/local-run.md | 65 -- docs/runbooks/local-setup.md | 94 +++ docs/runbooks/management-demo-brief.md | 39 - docs/runbooks/pilot-uat.md | 88 --- docs/runbooks/postgres-dev-cutover.md | 275 ------- docs/runbooks/postgres-server-docker.md | 88 --- .../postgres-server-parallel-stack.md | 105 --- docs/runbooks/recordings-local.md | 65 -- .../track10-live-voice-reliability.md | 77 -- .../track11-live-operator-call-control.md | 76 -- docs/runbooks/track12-operator-voice-ux.md | 53 -- docs/runbooks/track14-browser-softphone.md | 69 -- docs/runbooks/track15-telegram-chat.md | 44 -- docs/runbooks/track7-scale-validation.md | 134 ---- docs/runbooks/track9-2-production-cutover.md | 190 ----- docs/runbooks/voice-baseline.md | 49 -- docs/security/checklist.md | 24 - docs/uat/README.md | 59 -- docs/uat/defect-log-template.csv | 2 - docs/uat/evidence/.gitignore | 3 - docs/uat/evidence/README.md | 8 - docs/uat/scenario-checklist.md | 223 ------ docs/uat/session-template.md | 72 -- docs/uat/signoff-template.md | 48 -- tests/test_track7_scripts.py | 107 --- tests/test_track7_stage.py | 49 -- tests/test_track8_event_bus.py | 419 ---------- 69 files changed, 274 insertions(+), 5427 deletions(-) delete mode 100644 deployment/.env.images.example delete mode 100644 docs/architecture/api-matrix.md delete mode 100644 docs/architecture/asterisk-user-events.md delete mode 100644 docs/architecture/call.recording.ready.json delete mode 100644 docs/architecture/event-envelope.json delete mode 100644 docs/architecture/ivr.completed.json delete mode 100644 docs/architecture/voice-ai-v1.md create mode 100644 docs/architecture/voice-ai.md delete mode 100644 docs/gates/gate-01-foundation.md delete mode 100644 docs/gates/gate-02-operator-core.md delete mode 100644 docs/gates/gate-03-supervisor-reporting-kb.md delete mode 100644 docs/gates/gate-04-pilot-hardening.md delete mode 100644 docs/gates/mvp-pilot-baseline.md delete mode 100644 docs/gates/p1-p2-defects.md delete mode 100644 docs/gates/uat-protocol-template.md delete mode 100644 docs/kpi/definitions.md delete mode 100644 docs/releases/v1.0.0-mvp.md delete mode 100644 docs/roadmap/01-foundation.md delete mode 100644 docs/roadmap/02-operator-core.md delete mode 100644 docs/roadmap/03-supervisor-reporting-kb.md delete mode 100644 docs/roadmap/04-pilot-hardening.md delete mode 100644 docs/roadmap/05-wave2-backlog.md delete mode 100644 docs/roadmap/06-track10-live-voice-reliability.md delete mode 100644 docs/roadmap/07-track11-live-operator-call-control.md delete mode 100644 docs/roadmap/08-track12-operator-voice-ux-hardening.md delete mode 100644 docs/roadmap/09-track14-browser-softphone-webrtc.md delete mode 100644 docs/roadmap/10-track15-telegram-chat.md delete mode 100644 docs/roadmap/README.md delete mode 100644 docs/runbooks/.env.production.checklist.md delete mode 100644 docs/runbooks/asterisk-lab-linux-vm.md delete mode 100644 docs/runbooks/backup-restore.md delete mode 100644 docs/runbooks/demo-showcase.md delete mode 100644 docs/runbooks/deployment-onprem.md create mode 100644 docs/runbooks/deployment.md delete mode 100644 docs/runbooks/event-bus-local.md delete mode 100644 docs/runbooks/gitlab-cicd.md delete mode 100644 docs/runbooks/gitlab-server-deploy.md delete mode 100644 docs/runbooks/ivr-local.md delete mode 100644 docs/runbooks/keycloak-oidc.md delete mode 100644 docs/runbooks/load-test-plan.md delete mode 100644 docs/runbooks/local-run.md create mode 100644 docs/runbooks/local-setup.md delete mode 100644 docs/runbooks/management-demo-brief.md delete mode 100644 docs/runbooks/pilot-uat.md delete mode 100644 docs/runbooks/postgres-dev-cutover.md delete mode 100644 docs/runbooks/postgres-server-docker.md delete mode 100644 docs/runbooks/postgres-server-parallel-stack.md delete mode 100644 docs/runbooks/recordings-local.md delete mode 100644 docs/runbooks/track10-live-voice-reliability.md delete mode 100644 docs/runbooks/track11-live-operator-call-control.md delete mode 100644 docs/runbooks/track12-operator-voice-ux.md delete mode 100644 docs/runbooks/track14-browser-softphone.md delete mode 100644 docs/runbooks/track15-telegram-chat.md delete mode 100644 docs/runbooks/track7-scale-validation.md delete mode 100644 docs/runbooks/track9-2-production-cutover.md delete mode 100644 docs/runbooks/voice-baseline.md delete mode 100644 docs/security/checklist.md delete mode 100644 docs/uat/README.md delete mode 100644 docs/uat/defect-log-template.csv delete mode 100644 docs/uat/evidence/.gitignore delete mode 100644 docs/uat/evidence/README.md delete mode 100644 docs/uat/scenario-checklist.md delete mode 100644 docs/uat/session-template.md delete mode 100644 docs/uat/signoff-template.md delete mode 100644 tests/test_track7_scripts.py delete mode 100644 tests/test_track7_stage.py delete mode 100644 tests/test_track8_event_bus.py diff --git a/deployment/.env.images.example b/deployment/.env.images.example deleted file mode 100644 index ac953e9..0000000 --- a/deployment/.env.images.example +++ /dev/null @@ -1,2 +0,0 @@ -APP_IMAGE=registry.example.com/group/project/app:latest -ASTERISK_IMAGE=registry.example.com/group/project/asterisk:latest diff --git a/docs/architecture/api-matrix.md b/docs/architecture/api-matrix.md deleted file mode 100644 index 496b950..0000000 --- a/docs/architecture/api-matrix.md +++ /dev/null @@ -1,91 +0,0 @@ -# API Matrix by Stage - -## Stage 1 -- auth-service: `/auth/login`, `/auth/me`, `/users` -- audit-service: `/audit/events` -- all services: `/health` - -## Stage 2 -- customer-service: `/customers` -- interaction-service: `/interactions` -- routing-service: `/queues` -- voice-adapter-service: `/integrations/voice/events` -- telegram-adapter-service: `/integrations/telegram/webhook` - -## Stage 3 -- kb-service: `/knowledge/*` -- reporting-service: `/reports/kpi`, `/reports/export` -- supervisor-service: `/supervisor/realtime` - -## Stage 4 -- recording-service: `/recordings`, `/recordings/*` - -## Stage 5 -- ivr-service: `/ivr/flows`, `/ivr/flows/*`, `/ivr/sessions`, `/ivr/sessions/*` -- routing-service: `/queues/{queue_id}/route` with optional `ivr_session_id` - -## Stage 6 -- reporting-service: `/reports/kpi` with optional `channel` -- reporting-service: `/reports/coverage` -- reporting-service: `/reports/export` with optional `queue_id` and `channel` - -## Stage 7 -- no new business REST routes -- ops CLI: - - `scripts/load_test.py` with profile-driven mixed workload - - `scripts/track7_check.py` for scale/hardening evidence validation - -## Stage 8 -- no new business REST routes -- event-bus-service (ops only): `/bus/outbox`, `/bus/outbox/*` -- ops CLI: - - `scripts/event_bus_smoke.py` - - `scripts/track8_check.py` - -## Stage 9 -- no new business REST routes -- recording-service: `/recordings/import-upload` (internal bridge upload) -- asterisk-bridge-service (ops only): `/asterisk/status`, `/asterisk/events`, `/asterisk/events/*` -- bridge-to-platform auth modes: - - `legacy_headers` (QA baseline) - - `bearer_first` / `bearer` (Track 9.1 hardening) -- ops CLI: - - `scripts/track9_preflight.py` - - `scripts/asterisk_lab_smoke.py` - - `scripts/track9_check.py` - - `scripts/track9_collect_evidence.py` - - `scripts/track9_2_cutover.ps1` (controlled Helm cutover automation) - -## Stage 11 -- no new business REST routes -- asterisk-bridge-service call-control routes: - - `/asterisk/live-calls` - - `/asterisk/live-calls/{call_id}/claim` - - `/asterisk/live-calls/{call_id}/hangup` - - `/asterisk/live-calls/{call_id}/blind-transfer` - - `/asterisk/live-calls/{call_id}/actions` -- operator shell uses these routes for live call handling with external softphone media - -## Stage 12 -- no new business REST routes -- additive operator UX route: - - `/asterisk/recent-calls` -- operator shell uses this route for the short-lived recent-calls bucket - -## Stage 14 -- no new business REST routes -- additive browser softphone config route: - - `/asterisk/browser-softphone/config` -- operator shell may use direct browser media over Asterisk WSS in QA - -## Stage 15 -- telegram-adapter-service real-bot webhook: - - `/integrations/telegram/bot/webhook` -- telegram-adapter-service operator thread workspace: - - `/integrations/telegram/threads` - - `/integrations/telegram/threads/{thread_id}` - - `/integrations/telegram/threads/{thread_id}/messages` - - `/integrations/telegram/threads/{thread_id}/claim` - - `/integrations/telegram/threads/{thread_id}/close` - - `/integrations/telegram/threads/{thread_id}/escalate` -- operator shell gets a separate `Telegram` page as the canonical surface for this channel diff --git a/docs/architecture/asterisk-user-events.md b/docs/architecture/asterisk-user-events.md deleted file mode 100644 index 3bb1885..0000000 --- a/docs/architecture/asterisk-user-events.md +++ /dev/null @@ -1,59 +0,0 @@ -# Asterisk UserEvent Contract - -Track 9 uses dialplan-generated `UserEvent` frames over AMI as the stable bridge contract -between Asterisk and the platform. - -The bridge listens only for `UserEvent` names starting with `MVPCC`. - -## `MVPCCCallStarted` - -Required fields: - -- `CallID` -- `LinkedID` -- `CallerNumber` -- `CallerName` -- `QueueCode` -- `Extension` -- `Context` -- `Direction` - -Example: - -```text -Event: UserEvent -UserEvent: MVPCCCallStarted -CallID: 1740912000.12 -LinkedID: 1740912000.12 -CallerNumber: 1001 -CallerName: Lab Caller -QueueCode: voice_lab -Extension: 7000 -Context: from-softphones -Direction: inbound -``` - -## `MVPCCCallEnded` - -Required fields: - -- `CallID` -- `LinkedID` -- `HangupCause` -- `DurationSeconds` - -## `MVPCCRecordingReady` - -Required fields: - -- `CallID` -- `LinkedID` -- `RemotePath` -- `FileName` -- `MimeType` -- `DurationSeconds` - -Notes: - -- `RemotePath` is the absolute path on the Asterisk Linux VM. -- The platform downloads the file over SFTP, then uploads it into `recording-service`. diff --git a/docs/architecture/call.recording.ready.json b/docs/architecture/call.recording.ready.json deleted file mode 100644 index 2ca774e..0000000 --- a/docs/architecture/call.recording.ready.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "call.recording.ready.json", - "title": "call.recording.ready", - "type": "object", - "required": ["event_type", "call_id", "payload"], - "properties": { - "event_type": { - "const": "recording.ready" - }, - "call_id": { - "type": "string", - "minLength": 1 - }, - "interaction_id": { - "type": ["string", "null"] - }, - "payload": { - "type": "object", - "required": ["source_path"], - "properties": { - "source_path": { - "type": "string", - "minLength": 1 - }, - "file_name": { - "type": ["string", "null"] - }, - "mime_type": { - "type": ["string", "null"] - }, - "duration_seconds": { - "type": ["integer", "null"], - "minimum": 0 - }, - "recorded_at": { - "type": ["string", "null"] - } - }, - "additionalProperties": true - } - }, - "additionalProperties": true -} diff --git a/docs/architecture/event-envelope.json b/docs/architecture/event-envelope.json deleted file mode 100644 index 011a70f..0000000 --- a/docs/architecture/event-envelope.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://mvp-cc.local/docs/architecture/event-envelope.json", - "title": "EventEnvelope", - "type": "object", - "required": [ - "event_id", - "event_type", - "event_version", - "occurred_at", - "producer", - "entity_type", - "entity_id", - "routing_key", - "payload" - ], - "properties": { - "event_id": {"type": "string", "minLength": 1}, - "event_type": {"type": "string", "minLength": 1}, - "event_version": {"type": "integer", "minimum": 1}, - "occurred_at": {"type": "string", "format": "date-time"}, - "producer": {"type": "string", "minLength": 1}, - "entity_type": {"type": "string", "minLength": 1}, - "entity_id": {"type": "string", "minLength": 1}, - "correlation_id": {"type": ["string", "null"]}, - "routing_key": {"type": "string", "minLength": 1}, - "payload": {"type": "object"} - }, - "additionalProperties": true -} diff --git a/docs/architecture/event-schemas.md b/docs/architecture/event-schemas.md index 501e6b0..b744d46 100644 --- a/docs/architecture/event-schemas.md +++ b/docs/architecture/event-schemas.md @@ -1,37 +1,41 @@ -# Event Schema Index +# Контракты Событий (Event Schemas) -- `interaction.created.json` -- `interaction.assigned.json` -- `interaction.escalated.json` -- `interaction.closed.json` -- `agent.state.changed.json` -- `call.recording.ready.json` -- `call.connected.json` -- `call.transferred.json` -- `ivr.completed.json` +Платформа контакт-центра использует событийно-ориентированную (Event-Driven) архитектуру поверх брокера сообщений **RabbitMQ** (с использованием паттерна Transactional Outbox). Все события (сообщения) следуют подходу "Contract-First" и описаны JSON-схемами. -Each file is a JSON Schema (draft 2020-12) and can be used as a validation contract between services. +Исходники всех актуальных JSON-схем хранятся в директории `contracts/events/`. -Track 8 introduces a canonical transport envelope for bus-delivered events: +## Список доменных событий (Domain Events) -- `docs/architecture/event-envelope.json` +### Жизненный цикл звоноков (Telephony & Media Events) +Шлюз телефонии (Asterisk Bridge Service) инфраструктура генерирует события о состоянии вызовов в реальном времени. -Payload-specific schemas under `contracts/events` remain the authoritative contracts for the -domain payload inside that envelope. +- `call.connected.json` — Выстреливает, когда звонок успешно сопряжен между абонентом и конечной точкой (оператором или ИИ-помощником). +- `call.transferred.json` — Срабатывает при физическом переключении звонка (например, слепой трансфер от оператора к оператору, или Handoff от ИИ к живой очереди). +- `call.recording.ready.json` — Ивент о том, что аудиозапись звонка завершена, обработана и скопирована в хранилище (управляется `recording-service`). +- `ivr.completed.json` — Генерируется сервисом `ivr-service`, когда абонент завершил обход голосового меню (нажал необходимые DTMF цифры) и готов к маршрутизации. -Track 4 now provides the concrete schema file for the recording import placeholder: +### Жизненный цикл обращений (Interaction Events) +Отвечают за высокоуровневую бизнес-логику тикетов и многоканальных чатов. В основном генерируются в `interaction-service`. -- `docs/architecture/call.recording.ready.json` +- `interaction.created.json` — В систему поступило новое обращение (клиент позвонил, написал в Telegram/Webchat или Email). Содержит метаданные канала. +- `interaction.assigned.json` — Обращение захвачено живым агентом (или назначено routing-движком). +- `interaction.escalated.json` — Агент или ИИ эскалировал тикет на уровень выше (например, на вторую линию поддержки - L2, или пометил тикет как критичный). +- `interaction.closed.json` — Диалог или звонок успешно завершен, подведены итоги работы. -Track 5 adds the concrete schema for IVR completion: +### Состояния операторов (Agent State Events) +- `agent.state.changed.json` — Транслирует изменения статуса сотрудника ("Готов", "Перерыв", "В разговоре"). Используется в `supervisor-service` для отрисовки Dashboard в реальном времени, а также потребляется `routing-service` для распределения звонков по свободным операторам. -- `docs/architecture/ivr.completed.json` +## Структура Конверта (Event Envelope) +Любое доменное сообщение оборачивается в стандартизированный JSON-конверт для успешного трансфера через шину `event-bus-service`: -Track 9 adds the dialplan-to-platform `UserEvent` contract reference: - -- `docs/architecture/asterisk-user-events.md` - -Track 11 adds additive voice lifecycle event types produced by call-control flow: - -- `call.connected` (operator connected to customer) -- `call.transferred` (blind transfer requested by operator/admin) +```json +{ + "event_id": "8f39b1a0-54f3-...", + "event_type": "call.connected", + "timestamp": "2026-04-06T12:00:00Z", + "source_service": "asterisk-bridge-service", + "payload": { + // Внутреннее содержимое строго по одной из JSON-схем, описанных выше + } +} +``` diff --git a/docs/architecture/ivr.completed.json b/docs/architecture/ivr.completed.json deleted file mode 100644 index 14945c5..0000000 --- a/docs/architecture/ivr.completed.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://mvp-cc.local/contracts/events/ivr.completed.json", - "title": "ivr.completed", - "type": "object", - "required": ["event_type", "call_id", "payload"], - "properties": { - "event_type": { - "const": "ivr.completed" - }, - "call_id": { - "type": "string", - "minLength": 1 - }, - "interaction_id": { - "type": ["string", "null"] - }, - "payload": { - "type": "object", - "required": ["session_id", "flow_id", "outcome_code", "resolved_queue_id", "digits", "terminal_node_id"], - "properties": { - "session_id": { - "type": "string", - "minLength": 1 - }, - "flow_id": { - "type": "string", - "minLength": 1 - }, - "outcome_code": { - "type": "string", - "minLength": 1 - }, - "resolved_queue_id": { - "type": "string", - "minLength": 1 - }, - "digits": { - "type": "array", - "items": { - "type": "string", - "pattern": "^[0-9]$" - } - }, - "terminal_node_id": { - "type": "string", - "minLength": 1 - } - }, - "additionalProperties": true - } - }, - "additionalProperties": true -} diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md index 69ab2c6..de43941 100644 --- a/docs/architecture/overview.md +++ b/docs/architecture/overview.md @@ -1,25 +1,53 @@ -# Architecture Overview +# Обзор Архитектуры Контакт-Центра (MVP) -## Services -- api-gateway -- auth-service -- audit-service -- customer-service -- interaction-service -- routing-service -- voice-adapter-service -- telegram-adapter-service -- kb-service -- reporting-service -- supervisor-service +Контакт-центр спроектирован на базе микросервисной архитектуры с использованием событийно-ориентированного подхода (Event-Driven Architecture). Описываемая платформа поддерживает омниканальные коммуникации (Голос, Telegram, Email, Webchat, WhatsApp) с глубокой интеграцией AI-операторов (Voice AI V1, Telegram AI) и полноценной системой маршрутизации. -## Data and event strategy -- All services use SQL storage via `DATABASE_URL` -- Default local mode: SQLite (`.data/mvp_cc.db`) -- Target mode: PostgreSQL (supported in `deployment/docker-compose.yml`) -- Contract-first API and JSON event schema artifacts -- Event contracts are fixed and ready for message bus integration (RabbitMQ/Kafka) in next phase +## 1. Основные компоненты (Микросервисы) -## Deployment -- Local: Docker Compose -- On-prem: Kubernetes manifests and Helm chart +Архитектура состоит из множества независимых сервисов, написанных на Python (FastAPI): + +### Базовые и инфраструктурные сервисы +- **`api-gateway`** — Единая точка входа для клиентских интерфейсов (UI оператора/супервизора/админа). +- **`auth-service`** — Сервис аутентификации и RBAC. Поддерживает OIDC (Keycloak) и авторизацию по `APP_TOKEN_SECRET`. +- **`audit-service`** — Служба записи логов аудита системных действий (подключена к шине событий). +- **`event-bus-service`** — Асинхронная шина событий. Реализует паттерн Transactional Outbox/Inbox поверх брокера **RabbitMQ** для надежной доставки сообщений между сервисами. + +### Клиентоцентричные сервисы и роутинг +- **`customer-service`** — Хранит профили клиентов. Управляет связыванием номеров телефонов и Telegram/Webchat-аккаунтов в единый профиль (`customer_external_identities`). +- **`interaction-service`** — Канонический источник истины об "обращениях" (Interactions). Хранит таймлайн общения клиента и статусы тикетов. +- **`routing-service`** — Умная маршрутизация: оценка занятости сотрудников, скилл-бейзд роутинг, распределение по очередям. + +### Телефония и Медиа каналы (Adapters) +- **`asterisk-bridge-service`** — Главный мост с Asterisk телефонией (перехват AMI-событий с префиксом `MVPCC`). Отслеживает звонки, трансферы и командует Asterisk'ом. +- **`ivr-service`** — Сервис голосового меню. Настраивает IVR потоки (flows) через интерфейс администратора и обрабатывает DTMF клики. +- **`recording-service`** — Сервис управления записями звонков. Позволяет скачивать (SFTP) и локально управлять аудиофайлами звонков. +- **`voice-adapter-service`** — Сохраняет события жизненного цикла звонков в БД. +- **`telegram-adapter-service`** / **`email_adapter_service`** / **`webchat_adapter_service`** / **`whatsapp_adapter_service`** — Адаптеры интеграции с внешними цифровыми каналами (мессенджерами и веб-чатами). + +### Интеллект и База Знаний (AI & KB) +- **`kb-service`** — Локальная База Знаний. Содержит категории, статьи и обеспечивает быстрый поиск. +- **`ai_orchestrator_service`** — Детерминированный мозг ИИ. Принимает решения о генерации ответа или передаче диалога (Handoff) оператору на основе контекста и бизнес-правил. +- **`ai_voice_runtime_service`** — Реал-тайм прослойка аудио-моста (Speech-to-Text / Text-to-Speech) для голосового робота. Обрабатывает перебивания (barge-in) в реальном времени. + +### Аналитика, Отчетность и Надзор +- **`reporting-service`** — Агрегация KPI (Service Level, ASA, AHT, Abandon Rate, FCR) в разрезе каналов и агентов. +- **`supervisor-service`** — "Живой" мониторинг контакт-центра (статусы агентов, метрики, прослушка записей). + +## 2. Пользовательские Интерфейсы (UI Shells) + +Проект предоставляет несколько разделенных рабочих сред: +- `/operator` — Единое окно оператора (Обработка звонков: «Взять в работу», «Трансфер», «Сброс», переписка Telegram/Webchat). ИИ-сводки по звонкам. +- `/supervisor` — Окно супервизора (Мониторинг очередей, статусов агентов, поиск и прослушивание записей звонков из `recording-service`). +- `/admin` — Администрирование (Назначение ролей, управление очередями, конфигурация IVR деревьев, превью маршрутизации). +- `/analyst` — Выгрузки и детальная панель исторических KPI и отчетов. + +## 3. Стратегия Данных и Развертывание + +1. **Хранение данных:** Каждый микросервис спроектирован так, чтобы иметь изолированную логическую базу. + - По умолчанию используется локальный **SQLite**. + - Для K8s Scale/Продакшена используется **PostgreSQL** DB Pooling (настраивается через переменные `DB_POOL_SIZE`, `DB_MAX_OVERFLOW`). + - Исходным механизмом миграций является `Alembic` (скрипт `scripts/migrate_core_db.py`). +2. **Инфраструктура / Развертывание:** + - Разработка ведется через локальные Docker Compose конфигурации (`docker-compose.yml`, `docker-compose.server.yml`). Готов набор PS/Bash скриптов в директории `scripts/` (например, `scripts/prepare_demo.ps1`). + - Для серверов и Enterprise масштабов написаны Kubernetes Helm Chart (`deployment/helm`) и K8s манифесты. + - Шина **RabbitMQ** используется как канонический брокер сообщений для `event_outbox`/`event_inbox` логики обмена. diff --git a/docs/architecture/voice-ai-v1.md b/docs/architecture/voice-ai-v1.md deleted file mode 100644 index a6c9e2b..0000000 --- a/docs/architecture/voice-ai-v1.md +++ /dev/null @@ -1,744 +0,0 @@ -# Voice AI V1 Technical Design - -Status: `Proposed` - -## 1. Goal - -Add an AI-first voice operator on top of the accepted Asterisk voice baseline without replacing the current telephony stack. - -Voice AI V1 must: - -- answer selected inbound calls before a human joins; -- reuse the existing `interaction`, `customer`, `KB`, `queue`, and operator UI model; -- keep Asterisk as the system of record for call lifecycle, transfer, recording, SIP/WebRTC, and queue ownership; -- store business state in platform tables, not only inside model context; -- support deterministic AI-to-human handoff into the existing human queue. - -## 2. Non-goals - -Voice AI V1 does not: - -- replace Asterisk, AMI, dialplan, queueing, SIP/WebRTC, or recording; -- introduce a separate AI telephony platform or second source of truth for calls; -- push full CRM history, full call transcript, or raw recordings into every model turn; -- introduce a multi-agent swarm; -- add attended transfer, conference, or full human-to-AI return in the same live call. - -`return-to-ai` after human takeover is intentionally deferred to V2. It requires a second telephony redirect and fresh media re-attachment, which is riskier than AI-first plus human handoff. - -## 3. Existing Baseline We Build On - -The current repo already has the pieces needed for Voice AI V1: - -- `asterisk_bridge_service` owns AMI ingestion, live call tracking, claim/hangup/transfer, and recording import. -- `voice_adapter_service` stores telephony lifecycle events. -- `interaction_service` owns the canonical interaction lifecycle. -- `customer_service` plus `customer_external_identities` already provide cross-channel identity linking. -- `ai_orchestrator_service` already implements the Telegram AI pattern: - - deterministic orchestration; - - explicit AI disclosure; - - `ai_sessions` and `ai_turns`; - - AI-to-human handoff; - - source-of-truth separation between channel service and AI service. -- `/operator` already has a hybrid Telegram AI UX with AI badges and AI summary cards. - -Voice AI V1 should copy the Telegram AI service boundary pattern, not the Telegram channel model itself. - -## 4. Target Architecture - -### 4.1 Service responsibilities - -| Service | Role in Voice AI V1 | -| --- | --- | -| `asterisk_bridge_service` | Keeps telephony truth, selects AI path for eligible calls, starts/stops AI runtime sessions, performs AI-to-human redirect, exposes operator-facing voice AI summary. | -| `ai_voice_runtime_service` | New realtime service. Owns media session state, ASR/TTS streaming, turn detection, barge-in, runtime latency budget, and handoff requests. | -| `ai_orchestrator_service` | Reused and extended. Owns deterministic voice decisioning, filtered context assembly, KB/tool usage, safe business policies, and AI summary generation. | -| `interaction_service` | Remains the owner of interaction status and timeline; gains an internal timeline append endpoint for additive AI events. | -| `voice_adapter_service` | Stays the owner of telephony event persistence only. It does not become the AI runtime. | -| `ui/operator` | Reuses the current live call and popup flow; adds voice AI badges and handoff summary, but no separate `AI telephony` workspace. | - -### 4.2 High-level component view - -```mermaid -flowchart LR - A["Asterisk
queue, redirect, recording, SIP/WebRTC"] --> B["asterisk_bridge_service
call truth + control plane"] - B --> C["ai_voice_runtime_service
media bridge + ASR/TTS + barge-in"] - C --> D["ai_orchestrator_service
policy + tools + summaries"] - D --> E["customer_service / shared DB
customer profile + memory"] - D --> F["interaction_service
interaction status + timeline"] - D --> G["kb_service
KB lookup"] - B --> H["operator UI
live call popup + voice AI summary"] -``` - -### 4.3 Source-of-truth rule - -- Telephony truth stays in `AsteriskCallLinkRow`, AMI events, queue redirect, and recording flow. -- AI truth stays in `voice_ai_sessions`, `ai_sessions`, `ai_turns`, and transcript rows. -- Interaction truth stays in `Interaction` plus `InteractionTimeline`. -- Customer truth stays in `Customer` plus `CustomerExternalIdentity`. - -No single prompt becomes the long-term state container. - -## 5. Call Flow - -### 5.1 AI-first inbound voice flow - -```mermaid -sequenceDiagram - participant A as Asterisk - participant B as asterisk_bridge_service - participant R as ai_voice_runtime_service - participant O as ai_orchestrator_service - participant I as interaction_service - - A->>B: MVPCCCallStarted - B->>I: create / reuse interaction - B->>B: create or update AsteriskCallLinkRow - B->>R: POST /internal/voice-ai/sessions - R->>O: POST /ai/voice/sessions/{id}/start - R->>A: play AI disclosure + greeting - A->>R: caller audio stream - R->>O: POST /ai/voice/sessions/{id}/turns - O-->>R: reply plan or handoff decision - R->>A: TTS playback -``` - -Detailed steps: - -1. `asterisk_bridge_service` receives the existing `MVPCCCallStarted` event. -2. It keeps the current behavior of creating or resolving `interaction_id` and `AsteriskCallLinkRow`. -3. It evaluates whether this queue or IVR outcome should go to AI-first mode. -4. If not AI-eligible, the current human flow stays unchanged. -5. If AI-eligible: - - create `voice_ai_session`; - - create or link generic `ai_session` with `channel="voice"`; - - mark the call row as AI-owned; - - start a runtime control session in `ai_voice_runtime_service`; - - attach the customer leg to the AI media bridge. -6. `ai_voice_runtime_service` plays a short disclosure and greeting. -7. Finalized caller utterances are sent to `ai_orchestrator_service`. -8. The orchestrator loads filtered business context, runs deterministic policy and KB/tool lookup, and returns a structured decision. -9. The runtime either: - - plays TTS back to the caller; or - - requests handoff to a human queue. - -### 5.2 AI-to-human handoff - -```mermaid -sequenceDiagram - participant R as ai_voice_runtime_service - participant O as ai_orchestrator_service - participant B as asterisk_bridge_service - participant A as Asterisk - participant U as Operator UI - - R->>O: POST voice turn - O-->>R: needs_handoff=true + reason + summary - R->>B: POST /internal/voice-ai/calls/{call_id}/handoff - B->>B: mark ai_state=handoff_required - B->>A: redirect caller leg to existing human queue - A->>U: normal operator incoming call path - U->>B: GET /asterisk/live-calls/{call_id}/ai-summary - B-->>U: AI handoff summary -``` - -Handoff rules: - -- handoff happens only through the existing Asterisk queue path; -- the AI runtime never calls an operator directly and never becomes a second queueing owner; -- `interaction_id` stays the same through AI and human phases; -- the operator receives a concise AI summary before or during claim. - -### 5.3 Call end - -- `MVPCCCallEnded` and `MVPCCRecordingReady` continue to come from Asterisk through the existing bridge. -- The bridge informs the runtime session that telephony ended. -- The runtime closes `voice_ai_session`. -- The orchestrator writes the final AI summary and marks `ai_session` closed. -- The interaction timeline gets final AI events if they were not written yet. - -## 6. Queue Selection and AI Eligibility - -Voice AI V1 should start with config-driven AI eligibility, not a new admin UI. - -Recommended V1 config: - -`AI_VOICE_QUEUE_CONFIG_JSON` - -Example shape: - -```json -{ - "voice_lab": { - "mode": "ai_first", - "agent_profile": "voice_support", - "handoff_queue_code": "voice_lab", - "language": "ru" - } -} -``` - -Why config-first: - -- it avoids touching the accepted routing UI and queue schema in the first step; -- it keeps rollout reversible per queue; -- it matches the current repo style where telephony behavior is still largely env-driven. - -The longer-term path can move this policy into routing/admin later. - -## 7. Data Model - -## 7.1 Reused tables - -These tables stay authoritative and should be reused: - -- `interactions` -- `interaction_timelines` -- `customer_external_identities` -- `asterisk_call_links` -- `voice_events` -- `call_recordings` -- `ai_sessions` -- `ai_turns` - -Voice customer linking should reuse `customer_external_identities` with: - -- `channel = "voice"` -- `external_subject = normalized caller number` - -No new identity table is needed. - -## 7.2 New table: `voice_ai_sessions` - -This table stores runtime state that does not fit cleanly into generic `ai_sessions`. - -Suggested columns: - -| Column | Type | Purpose | -| --- | --- | --- | -| `id` | integer pk | Internal row id | -| `session_id` | string unique | Public runtime session id | -| `call_id` | string unique index | Current Asterisk customer-leg call id | -| `linked_id` | string index | Asterisk linked id for recovery if call id changes | -| `interaction_id` | string index | Shared interaction | -| `customer_id` | string index nullable | Linked customer | -| `queue_id` | string index | Original platform queue | -| `ai_session_id` | string index | Generic AI session link | -| `agent_profile` | string index | Voice policy profile | -| `status` | string index | `queued`, `greeting`, `listening`, `thinking`, `speaking`, `handoff_requested`, `human_owned`, `completed`, `error` | -| `language` | string index nullable | Current active language | -| `asr_provider` | string nullable | Selected ASR backend | -| `tts_provider` | string nullable | Selected TTS backend | -| `handoff_reason` | text nullable | Last handoff reason | -| `handoff_target_queue_id` | string nullable | Human target queue | -| `disclosure_played_at` | string nullable | When AI disclosure was first spoken | -| `last_user_utterance_at` | string nullable | Latest finalized caller speech | -| `last_ai_reply_at` | string nullable | Latest completed AI reply | -| `started_at` | string index | Session start | -| `updated_at` | string index | Last state update | -| `ended_at` | string index nullable | Session end | - -## 7.3 New table: `voice_transcript_segments` - -This table stores finalized voice transcript units separately from generic AI turns. - -Suggested columns: - -| Column | Type | Purpose | -| --- | --- | --- | -| `id` | integer pk | Internal row id | -| `segment_id` | string unique | Public segment id | -| `session_id` | string index | `voice_ai_sessions.session_id` | -| `call_id` | string index | Voice call correlation | -| `interaction_id` | string index | Interaction correlation | -| `speaker` | string index | `caller`, `assistant`, `system`, `operator` | -| `source_type` | string index | `asr`, `tts`, `handoff_summary`, `system` | -| `sequence_no` | integer index | Ordered segment number | -| `text` | text | Final transcript text | -| `confidence` | number nullable | ASR confidence when applicable | -| `is_final` | boolean | Finalized transcript only in V1, but keep the flag for future partials | -| `barge_in_interrupted` | boolean | Whether the assistant segment was interrupted | -| `payload_json` | text | Provider metadata, timestamps, tool refs | -| `created_at` | string index | Write time | - -V1 should store finalized transcript segments only. Partial ASR events can stay in memory inside the runtime. - -## 7.4 Additive columns on `asterisk_call_links` - -This mirrors the Telegram pattern where the channel source-of-truth row also exposes AI status. - -Add: - -- `voice_session_id VARCHAR(64) NULL` -- `ai_session_id VARCHAR(64) NULL` -- `ai_state VARCHAR(32) NULL` -- `ai_handoff_reason TEXT NULL` -- `ai_last_model_at VARCHAR(64) NULL` - -Suggested `ai_state` values: - -- `queued` -- `greeting` -- `listening` -- `thinking` -- `active` -- `handoff_required` -- `human_owned` -- `closed` -- `error` - -This lets `/asterisk/live-calls` and `/asterisk/recent-calls` drive UI chips directly without extra joins on every poll. - -## 7.5 Additive column on `ai_sessions` - -Add: - -- `call_id VARCHAR(128) NULL` - -Reason: - -- Telegram already uses `thread_id`; -- voice needs a direct telephony key for fast lookup and summary generation; -- this keeps `ai_sessions` truly cross-channel instead of Telegram-shaped. - -## 7.6 Reuse of `ai_turns` - -Reuse `ai_turns` for: - -- finalized user turn seen by the orchestrator; -- model reply plan; -- tool invocation results; -- summary turn written during handoff or closure. - -Do not reuse `ai_jobs` for voice V1. Telegram jobs are thread-triggered and synchronous voice turn processing does not need that queue model. - -## 8. API Design - -## 8.1 `asterisk_bridge_service` -> `ai_voice_runtime_service` - -New internal control-plane endpoints: - -### `POST /internal/voice-ai/sessions` - -Starts a runtime session for an already accepted telephony call. - -Request: - -```json -{ - "call_id": "1740912000.12", - "linked_id": "1740912000.12", - "interaction_id": "int_...", - "queue_id": "que_...", - "caller_number": "+7701...", - "caller_name": "Lab Caller", - "agent_profile": "voice_support", - "language_hint": "ru", - "handoff_queue_id": "que_...", - "metadata": { - "queue_code": "voice_lab", - "direction": "inbound" - } -} -``` - -Response: - -```json -{ - "voice_session_id": "avs_...", - "ai_session_id": "ais_...", - "status": "queued" -} -``` - -### `POST /internal/voice-ai/sessions/{session_id}/telephony-events` - -Bridge notifies runtime about: - -- `call.connected` -- `call.ended` -- `recording.ready` -- `operator.connected` - -This keeps telephony truth in the bridge while runtime stays current. - -## 8.2 `ai_voice_runtime_service` -> `ai_orchestrator_service` - -### `POST /ai/voice/sessions/{session_id}/start` - -Creates or reopens the generic AI session and returns greeting policy: - -```json -{ - "voice_session_id": "avs_...", - "call_id": "1740912000.12", - "interaction_id": "int_...", - "customer_id": "cus_...", - "language_hint": "ru", - "agent_profile": "voice_support" -} -``` - -Response: - -```json -{ - "session_id": "ais_...", - "language": "ru", - "greeting_text": "Здравствуйте. Я AI-оператор компании...", - "disclosure_required": true -} -``` - -### `POST /ai/voice/sessions/{session_id}/turns` - -Main deterministic decision endpoint. - -Request: - -```json -{ - "voice_session_id": "avs_...", - "call_id": "1740912000.12", - "interaction_id": "int_...", - "transcript_text": "Хочу узнать статус заявки", - "language": "ru", - "sequence_no": 3, - "barge_in": false, - "metadata": { - "turn_duration_ms": 4200 - } -} -``` - -Response: - -```json -{ - "language": "ru", - "intent": "status_check", - "reply_text": "Я AI-оператор компании. Проверяю данные по обращению...", - "confidence": 0.83, - "needs_handoff": false, - "handoff_reason": null, - "case_action": "keep_open", - "kb_refs": ["art_..."], - "summary_text": "Клиент уточняет статус заявки.", - "model": "gpt-4o-mini", - "latency_ms": 780 -} -``` - -`case_action` should stay aligned with the existing Telegram pattern: - -- `none` -- `keep_open` -- `close` -- `escalate` - -### `POST /ai/voice/sessions/{session_id}/close` - -Finalizes AI state and writes the terminal summary. - -## 8.3 `ai_voice_runtime_service` -> `asterisk_bridge_service` - -### `POST /internal/voice-ai/calls/{call_id}/handoff` - -Requests redirect of the current customer leg into the existing human queue. - -Request: - -```json -{ - "voice_session_id": "avs_...", - "ai_session_id": "ais_...", - "interaction_id": "int_...", - "target_queue_id": "que_...", - "reason": "Нужен человек для чувствительного запроса.", - "summary": { - "customer_request_text": "Клиент просит изменить договор", - "ai_outcome_text": "AI собрал контекст и не выполнял чувствительное действие", - "recommended_next_step": "Проверить договор и продолжить вручную" - } -} -``` - -Behavior: - -- bridge validates the call is still active; -- bridge updates `ai_state` to `handoff_required`; -- bridge appends timeline `ai.handoff_requested`; -- bridge redirects the customer leg into the configured human queue; -- when the normal operator-connected flow happens, the same call row becomes `human_owned`. - -This endpoint is internal-only and trusted for service actors such as `svc:ai-voice-runtime`. - -## 8.4 `interaction_service` - -Add one internal endpoint: - -### `POST /interactions/{interaction_id}/timeline` - -Request: - -```json -{ - "action": "ai.reply_generated", - "metadata": { - "call_id": "1740912000.12", - "voice_session_id": "avs_...", - "ai_session_id": "ais_..." - } -} -``` - -Why add this now: - -- Voice AI should not write cross-service timeline rows by reaching into another service's DB contract ad hoc; -- the same endpoint can later be reused by Telegram AI without changing its current behavior immediately; -- it makes the AI event contract explicit. - -Required Voice AI timeline actions: - -- `ai.session_started` -- `ai.reply_generated` -- `ai.handoff_requested` -- `ai.handoff_completed` -- `ai.error` - -Existing interaction endpoints remain reused as-is: - -- `PATCH /interactions/{id}/status` -- `POST /interactions/{id}/escalate` -- `PATCH /interactions/{id}/assign` - -## 8.5 Operator-facing read APIs - -Extend `asterisk_bridge_service` output: - -### `GET /asterisk/live-calls` - -Add to each row: - -- `voice_session_id` -- `ai_session_id` -- `ai_state` -- `ai_handoff_reason` -- `ai_last_model_at` - -### `GET /asterisk/recent-calls` - -Expose the same additive AI fields. - -### `GET /asterisk/live-calls/{call_id}/ai-summary` - -Return a summary shape intentionally aligned with Telegram: - -```json -{ - "call_id": "1740912000.12", - "session_id": "ais_...", - "voice_session_id": "avs_...", - "status_label": "AI передал звонок оператору", - "status_tone": "handoff", - "customer_request_text": "Клиент хочет узнать статус обращения и изменить способ оплаты", - "ai_outcome_text": "AI собрал контекст и объяснил рамки, но не выполнил чувствительное действие", - "handoff_reason": "Запрос требует человека и проверки вручную", - "recommended_next_step": "Проверить карточку обращения и продолжить звонок вручную", - "generated_at": "2026-03-09T12:34:56Z" -} -``` - -This should be produced from `voice_ai_sessions`, `ai_turns`, and the latest transcript segments. - -## 9. Context Filtering Rules - -Voice AI must not send the whole call history into the model each turn. - -For every voice turn, `ai_orchestrator_service` should assemble a filtered context from: - -- customer profile: - - `customer_id` - - display name - - preferred phone - - tags -- customer memory: - - recent resolved issues - - notable preferences -- interaction state: - - `interaction_id` - - status - - queue - - last relevant timeline events -- voice session state: - - language - - disclosure already played or not - - previous handoff flag - - current turn number -- recent transcript window: - - last `6-10` finalized segments, not the entire transcript -- KB: - - top `3` matched articles or fewer -- business policy: - - allowed actions - - mandatory disclosure - - sensitive-topic escalation rules - -This matches the Telegram AI design principle already present in the repo. - -## 10. Runtime Behavior - -## 10.1 ASR/TTS abstraction - -`ai_voice_runtime_service` should expose provider interfaces and start with one configured provider per environment. - -Recommended internal modules: - -- `providers/asr.py` -- `providers/tts.py` -- `session_manager.py` -- `media_bridge.py` -- `barge_in.py` - -V1 supports one active ASR provider and one active TTS provider, behind interfaces. Multi-provider fallback is not required in the first version. - -## 10.2 Barge-in - -Barge-in is a V1 requirement because voice UX breaks if the caller cannot interrupt TTS. - -Required behavior: - -- while TTS is playing, incoming speech activity stops or fades out current playback; -- interrupted assistant output is marked with `barge_in_interrupted=true` in transcript; -- only finalized caller speech creates a new orchestrator turn; -- if interruption happens repeatedly or ASR confidence is poor, handoff rules may trigger. - -## 10.3 Latency budget - -Target budget for one AI turn: - -- end-of-utterance to finalized ASR text: `<= 600 ms` -- orchestrator decision: `<= 900 ms` -- first TTS audio chunk: `<= 500 ms` -- total pause before AI speech starts: `<= 2.0 s` - -If the runtime cannot stay within the budget repeatedly, it should prefer human handoff over a degraded long-silence experience. - -## 11. Operator UI Integration - -Voice AI V1 should integrate into the current operator shell, not create a second voice console. - -### 11.1 Existing surfaces to extend - -- browser call popup overlay; -- `Voice debug` live/recent calls list; -- unified customer history on `/operator`. - -### 11.2 Required UI changes - -1. Extend live call rows with an AI chip using the same language as Telegram: - - `AI active` - - `Ждёт человека` - - `AI error` -2. When a transferred AI-owned call reaches the operator popup: - - load `GET /asterisk/live-calls/{call_id}/ai-summary`; - - show a compact `Сводка AI` card above call actions; - - keep existing `Принять в работу`, `Передать`, `Завершить` controls unchanged. -3. Add AI metadata to customer history: - - AI session started - - AI handoff requested - - AI handoff completed -4. Do not add a separate full transcript workspace in V1. - -### 11.3 UI behavior intentionally deferred - -Deferred to V2: - -- operator button `Вернуть AI` for live voice calls; -- inline live transcript for operators during the call; -- supervisor transcript explorer for full recordings plus transcripts. - -## 12. Deployment and Config - -## 12.1 New service in `docker-compose.server.yml` - -Add: - -- `ai-voice-runtime-service` - -New shared env: - -- `AI_VOICE_RUNTIME_SERVICE_URL` -- `AI_VOICE_ENABLED` -- `AI_VOICE_QUEUE_CONFIG_JSON` -- `AI_VOICE_ASR_PROVIDER` -- `AI_VOICE_TTS_PROVIDER` -- `AI_VOICE_TTS_CACHE_ENABLED` -- `AI_VOICE_TTS_CACHE_DIR` -- `AI_VOICE_MAX_CONTEXT_SEGMENTS` -- `AI_VOICE_HANDOFF_TIMEOUT_SECONDS` - -Service auth: - -- add trusted subject `svc:ai-voice-runtime` where internal bridge endpoints require it. - -## 12.2 Asterisk-side change - -The accepted human voice path stays intact. - -Voice AI adds one new AI media bridge path in dialplan only for AI-selected queues. Preferred V1 implementation is: - -- Asterisk dialplan redirects the customer leg into an external media bridge context dedicated to AI. - -Exact low-level primitive should be validated in the lab: - -- preferred: `AudioSocket` or equivalent bidirectional audio bridge; -- fallback: narrowly scoped external-media/ARI only for AI queues. - -The choice must keep Asterisk as the telephony owner. - -## 13. Rollout Order - -Recommended implementation order: - -1. schema changes only: - - `voice_ai_sessions` - - `voice_transcript_segments` - - additive AI columns on `asterisk_call_links` - - additive `call_id` on `ai_sessions` -2. control plane only: - - bridge starts and closes empty runtime sessions behind `AI_VOICE_ENABLED=0` -3. lab media path: - - one AI-enabled lab queue - - disclosure + greeting + ASR/TTS echo flow -4. orchestrator integration: - - KB lookup - - filtered context - - handoff decisioning -5. operator summary: - - popup card - - voice debug AI chips -6. staged pilot rollout per queue. - -## 14. Key Risks and Open Questions - -1. `call_id` stability during AI-to-human redirect must be validated in the Asterisk lab. - If redirect creates a new call id, bridge recovery must switch to `linked_id` first and only then reuse `interaction_id`. -2. The exact Asterisk media primitive must be confirmed before implementation. - The design assumes a bidirectional bridge is available without replacing the accepted telephony baseline. -3. Provider latency must be measured in the target environment before enabling AI-first for production queues. -4. Sensitive actions should stay read-only in V1. - Voice AI should use KB, customer lookup, and safe interaction updates, but not execute risky external business actions directly. - -## 15. Summary - -Voice AI V1 should be implemented as an additive AI layer over the accepted voice baseline: - -- `asterisk_bridge_service` remains telephony truth and handoff executor; -- `ai_voice_runtime_service` is the new realtime media layer; -- `ai_orchestrator_service` is reused for deterministic business decisioning; -- `interaction_service` remains the owner of the canonical AI timeline; -- operator UI gets AI badges and AI handoff summary, not a new telephony product. - -This keeps the current live voice contour intact while adding the same AI-first and human-handoff architecture that already works in Telegram. diff --git a/docs/architecture/voice-ai.md b/docs/architecture/voice-ai.md new file mode 100644 index 0000000..0c388d5 --- /dev/null +++ b/docs/architecture/voice-ai.md @@ -0,0 +1,39 @@ +# Архитектура Голосового ИИ (Voice AI V1) + +Этот документ описывает техническое устройство и жизненный цикл обработки входящих голосовых звонков с помощью ИИ-оператора. ИИ интегрирован поверх базовой телефонии Asterisk и не заменяет ее полностью, выступая как первая линия поддержки (L1). + +## 1. Ключевые принципы + +1. **AI-first**: Звонок сначала направляется ИИ, если очередь настроена соответствующим образом. +2. **Перехват звонка (Handoff)**: Исходный звонок детерминированно переключается на человека-оператора, если ИИ не может решить проблему или если клиент явно просит человека. +3. **Единый источник истины**: Asterisk остается мастер-системой для SIP-потока, записи звонков и маршрутизации. +4. **Безопасность (Read-Only)**: В версии V1 ИИ работает исключительно на чтение и выдачу справочной информации. Апдейт критичных финансовых данных в базе строго запрещен для ИИ и требует переключения на человека. + +## 2. Архитектура обработки звонка + +Процесс обработки звонка ИИ разделен на два основных микросервиса: + +- **`ai_voice_runtime_service` (Аудио-прослойка):** Осуществляет прием аудио-потока. Работает в Real-time. Слушает аудио (через ASR модель) и отправляет синтезированную речь обратно клиенту (через TTS модель). Также отлавливает попытки клиента перебить робота (Barge-in), мгновенно прерывая воспроизведение. +- **`ai_orchestrator_service` (Мозг ИИ):** Не работает со звуком. Получает только готовый текстовый транскрипт (реплику). Принимает решение: + 1. Найти знания в `kb-service` (RAG). + 2. Сгенерировать текстовый ответ для клиента (который будет озвучен через TTS). + 3. Если запрос слишком сложный — отправить команду "Переключить на живого человека". + +## 3. Флоу входящего звонка (Step-by-step) + +1. Абонент звонит на платформу. +2. **`asterisk-bridge-service`** улавливает событие и решает направить звонок в `voice_lab`. +3. Поднимаются две параллельные сессии: `voice_ai_session` (отслеживание аудио) и `ai_session` (бизнес-логика). +4. Абонент подключается к медиа-интерфейсу `ai_voice_runtime_service`. Проигрывается дисклеймер: "Здравствуйте, я голосовой помощник...". +5. Абонент озвучивает свой вопрос. +6. ASR распознает вопрос и отправляет текст в **`ai_orchestrator_service`**. +7. Оркестратор смотрит историю клиента, ищет ответ в базе знаний, генерирует ответ и возвращает команду в runtime "Скажи это". +8. Если оркестратор понимает, что нужна помощь: + - Отправляет сигнал `needs_handoff` с детальной "Сводкой ИИ" для оператора. + - Звонок перехватывается, и абонент слушает музыку ожидания в очереди к живым людям. + +## 4. Интеграция с Operator UI + +Когда звонок поступает к оператору после ИИ, оператор не видит отдельный ИИ-интерфейс. Он видит стандартную карточку звонка. Но внутри этой карточки динамически выводится: +- **AI Summary (Сводка):** Что клиент сказал, что ИИ попытался сделать и почему перевел звонок на человека. +- **Таймлайн:** В историю обращений клиента логгируется, что "ИИ запрашивал перевод на специалиста". diff --git a/docs/gates/gate-01-foundation.md b/docs/gates/gate-01-foundation.md deleted file mode 100644 index 8908cc8..0000000 --- a/docs/gates/gate-01-foundation.md +++ /dev/null @@ -1,8 +0,0 @@ -# Gate 1 Checklist (Foundation) - -- [ ] CI pipeline executes tests -- [ ] Gateway and stage 1 services expose `/health` -- [ ] `/auth/login` and RBAC restrictions validated -- [ ] Stage 1 OpenAPI published -- [ ] Event schemas approved -- [ ] Kubernetes + Helm manifests validated by lint/dry run diff --git a/docs/gates/gate-02-operator-core.md b/docs/gates/gate-02-operator-core.md deleted file mode 100644 index 7aa0462..0000000 --- a/docs/gates/gate-02-operator-core.md +++ /dev/null @@ -1,8 +0,0 @@ -# Gate 2 Checklist (Operator Core) - -- [ ] Create customer and interaction (voice/telegram) -- [ ] Route and assign interaction -- [ ] Escalate interaction and preserve timeline -- [ ] Voice event and Telegram webhook ingestion works -- [ ] Stage 2 OpenAPI frozen -- [ ] Functional tests pass diff --git a/docs/gates/gate-03-supervisor-reporting-kb.md b/docs/gates/gate-03-supervisor-reporting-kb.md deleted file mode 100644 index f213d16..0000000 --- a/docs/gates/gate-03-supervisor-reporting-kb.md +++ /dev/null @@ -1,12 +0,0 @@ -# Gate 3 Checklist (Supervisor + Reporting + KB-lite) - -- [x] KPI events ingested and core KPIs available -- [x] Supervisor realtime reflects agent and queue state -- [x] KB categories/articles/search operate by role -- [x] CSV export works -- [x] Stage 3 OpenAPI frozen -- [x] Integration tests pass - -Verification run (2026-02-26): -- `pytest -q` -- `python scripts/gate3_check.py --auto-start` diff --git a/docs/gates/gate-04-pilot-hardening.md b/docs/gates/gate-04-pilot-hardening.md deleted file mode 100644 index c5f0fe7..0000000 --- a/docs/gates/gate-04-pilot-hardening.md +++ /dev/null @@ -1,35 +0,0 @@ -# Gate 4 Checklist (Pilot Hardening) - -- [x] Load test target reached (100 voice + 100 digital) -- [x] Backup and restore verified -- [x] Security checklist completed -- [ ] UAT signed with real operators/supervisors -- [ ] P1/P2 defects closed -- [x] v1 release notes and runbooks delivered - -Verification run (2026-02-26): -- `python scripts/gate4_check.py` -- `pytest -q` - -Pilot scope freeze: -- `docs/gates/mvp-pilot-baseline.md` -- `docs/roadmap/05-wave2-backlog.md` - -Required manual evidence bundle before closing the last two checklist items: -- preflight report from `docs/uat/evidence/` -- completed session protocol from `docs/uat/session-template.md` -- filled defect log from `docs/uat/defect-log-template.csv` -- signed sheet from `docs/uat/signoff-template.md` -- updated `docs/gates/p1-p2-defects.md` - -Manual close-out sequence: -1. Run preflight and automated dry-run to prepare the evidence pack. -2. Execute real UAT with operators and supervisor on the frozen MVP scope. -3. Fix only `P1` and `P2` issues, then rerun regression checks. -4. Capture sign-off and only then mark the final two checklist items as done. - -Notes: -- UAT sign-off must be completed on pilot site with real operators and supervisor. -- P1/P2 closure is finalized after pilot UAT defect intake. -- UAT package location: `docs/uat/`. -- Automated dry-run command: `python scripts/uat_dry_run.py --auto-start --update-defect-register`. diff --git a/docs/gates/mvp-pilot-baseline.md b/docs/gates/mvp-pilot-baseline.md deleted file mode 100644 index 08e3486..0000000 --- a/docs/gates/mvp-pilot-baseline.md +++ /dev/null @@ -1,71 +0,0 @@ -# MVP Pilot Baseline - -This document freezes the scope for MVP pilot acceptance and separates pilot defects -from post-MVP feature requests. - -## Accepted Pilot Scope - -The following capabilities are part of MVP pilot acceptance: - -- auth login and RBAC enforcement -- customers create/search/history shell -- interactions create/assign/status/escalate/timeline -- queues and routing rules -- voice event intake -- Telegram webhook intake -- KB-lite categories/articles/search -- KPI core (`SL`, `ASA`, `AHT`, `Abandon`, `FCR`) and CSV export -- supervisor realtime API -- current operator UI shell (`/operator`) - -## Explicitly Out of Scope - -The following items are not treated as MVP defects during pilot acceptance: - -- LDAP/SSO -- full omnichannel beyond `voice` and `Telegram` -- standalone supervisor UI -- standalone admin UI -- full WFM module -- advanced IVR builder -- full recording suite and audio archive UX -- full KPI catalog from the extended technical specification -- scale target beyond MVP baseline (`100 voice + 100 digital`) - -All out-of-scope requests must be redirected to `Wave 2` backlog: -`docs/roadmap/05-wave2-backlog.md`. - -## Approved Pilot Environments - -- Primary mode: dedicated pilot gateway/environment maintained outside local auto-start. -- Fallback mode: local rehearsal via: - - `python scripts/uat_preflight.py --auto-start` - - `python scripts/uat_dry_run.py --auto-start --update-defect-register` - -## Scope Freeze Rules - -- No breaking API changes are allowed during pilot close-out. -- Only backward-compatible fixes are allowed for UAT remediation. -- Only `P1` and `P2` defects are fixed inside MVP close-out. -- `P3` and `P4` findings are logged and moved to post-MVP backlog unless they block sign-off. -- New feature requests must not be recorded as MVP defects. - -## Required Evidence Bundle - -Pilot close-out is based on the following artifacts: - -- preflight report from `docs/uat/evidence/` -- completed session protocol -- defect log with severity and verification steps -- signed sign-off sheet -- updated `docs/gates/p1-p2-defects.md` - -## Exit Criteria - -The MVP pilot baseline is considered closed only when all conditions are true: - -- all mandatory scenarios from `docs/uat/scenario-checklist.md` are executed -- no open `P1` defects -- no open `P2` defects -- business and IT owners sign the UAT package -- Gate 4 is updated to reflect final pilot closure diff --git a/docs/gates/p1-p2-defects.md b/docs/gates/p1-p2-defects.md deleted file mode 100644 index c29a4b8..0000000 --- a/docs/gates/p1-p2-defects.md +++ /dev/null @@ -1,16 +0,0 @@ -# P1/P2 Defect Register (Pilot) - -Last update: 2026-02-27 (UAT-DRY-20260227_085446) - -## Current Status -- Open P1: 0 -- Open P2: 0 -- Source: `scripts/uat_dry_run.py` and automated checks -- Latest dry-run evidence: `docs/uat/evidence/dry_run_UAT-DRY-20260227_085446/` - -## Triage Policy -- Only `P1` and `P2` issues belong to MVP remediation. -- `P3` and `P4` issues move to `docs/roadmap/05-wave2-backlog.md` unless they block sign-off. - -## Pilot Note -- Final P1/P2 closure is confirmed only after the real-operator UAT cycle and sign-off. \ No newline at end of file diff --git a/docs/gates/uat-protocol-template.md b/docs/gates/uat-protocol-template.md deleted file mode 100644 index 41b2ce8..0000000 --- a/docs/gates/uat-protocol-template.md +++ /dev/null @@ -1,12 +0,0 @@ -# UAT Protocol Template (Stage 4) - -Primary working template moved to: - -- `docs/uat/session-template.md` - -Additional artifacts: - -- Scenario checklist: `docs/uat/scenario-checklist.md` -- Defect log: `docs/uat/defect-log-template.csv` -- Sign-off: `docs/uat/signoff-template.md` -- Preflight evidence: `docs/uat/evidence/` diff --git a/docs/kpi/definitions.md b/docs/kpi/definitions.md deleted file mode 100644 index c76d3b7..0000000 --- a/docs/kpi/definitions.md +++ /dev/null @@ -1,37 +0,0 @@ -# KPI Definitions (MVP Core) - -## SL (Service Level) -`answered_within_threshold / total_interactions * 100` - -## ASA (Average Speed of Answer) -`sum(wait_seconds for answered) / answered` - -## AHT (Average Handle Time) -`sum(handle_seconds for answered) / answered` - -## Abandon -`abandoned / total_interactions * 100` - -## FCR -`resolved_first_contact / answered * 100` - -## AnswerRate -`answered / total_interactions * 100` - -## WaitP95 -`p95(wait_seconds for answered)` - -## HandleP95 -`p95(handle_seconds for answered)` - -## Occupancy -`sum(handle_seconds) / (sum(handle_seconds) + sum(wait_seconds)) * 100` - -## DigitalShare -`non_voice_interactions / total_interactions * 100` - -## Supported Dimensions -- `queue_id` -- `channel` -- `agent_id` -- time window (`from_ts`, `to_ts`) diff --git a/docs/releases/v1.0.0-mvp.md b/docs/releases/v1.0.0-mvp.md deleted file mode 100644 index 84a1542..0000000 --- a/docs/releases/v1.0.0-mvp.md +++ /dev/null @@ -1,22 +0,0 @@ -# Release Notes - v1.0.0-mvp - -Date: 2026-02-26 - -## Included -- Stage 1 foundation services, gateway, contracts, and deployment skeleton -- Stage 2 operator core flows (`voice + telegram`) with interaction lifecycle -- Stage 3 supervisor, KPI reporting, and KB-lite APIs -- SQL persistence for all services (SQLite/PostgreSQL support) -- Local and pilot runbooks, gate checklists, and automation scripts - -## Hardening Pack -- Load test script for `100 voice + 100 digital` -- Backup/restore scripts and runbook -- Gate 3 automation: `scripts/gate3_check.py` -- Gate 4 automation: `scripts/gate4_check.py` -- Security checklist: `docs/security/checklist.md` - -## Known Limits -- UAT sign-off by real operators/supervisors is a pilot activity -- P1/P2 closure depends on pilot defect intake -- LDAP planned for next wave (post-MVP) diff --git a/docs/roadmap/01-foundation.md b/docs/roadmap/01-foundation.md deleted file mode 100644 index 0f13b0a..0000000 --- a/docs/roadmap/01-foundation.md +++ /dev/null @@ -1,25 +0,0 @@ -# Stage 1 - Foundation + Environments (Weeks 1-3) - -## Goal -Prepare a runnable technical baseline: on-prem deployment patterns, CI/CD, API contracts, RBAC, service monitoring. - -## Scope -- Gateway skeleton -- auth-service, users API, audit-service -- health endpoints for all services -- OpenAPI stage 1 contracts -- event schema baseline -- docker-compose + Kubernetes + Helm baseline -- CI workflow - -## Deliverables -- `deployment/*` -- `.github/workflows/ci.yml` -- `contracts/openapi/stage1-auth-users-health-audit.yaml` -- `contracts/events/*.json` - -## Gate 1 DoD -- Auto deploy path documented -- Login + roles work -- Health endpoints available -- API/event contracts frozen diff --git a/docs/roadmap/02-operator-core.md b/docs/roadmap/02-operator-core.md deleted file mode 100644 index 523fb95..0000000 --- a/docs/roadmap/02-operator-core.md +++ /dev/null @@ -1,21 +0,0 @@ -# Stage 2 - Operator Core (Weeks 4-7) - -## Goal -Deliver real operator flow for `voice + Telegram` with interaction lifecycle. - -## Scope -- customer-service (cards/search/history) -- interaction-service (create/assign/status/escalate/timeline) -- routing-service (queues/rules/route) -- voice-adapter-service (Asterisk events) -- telegram-adapter-service (webhook/messages) - -## Deliverables -- operator core APIs in services -- stage 2 OpenAPI contracts -- smoke scripts and tests for e2e API-level scenarios - -## Gate 2 DoD -- Operator can process both channels from unified interaction model -- Status and history are consistent -- Escalation and assignment work without manual workaround diff --git a/docs/roadmap/03-supervisor-reporting-kb.md b/docs/roadmap/03-supervisor-reporting-kb.md deleted file mode 100644 index 3886913..0000000 --- a/docs/roadmap/03-supervisor-reporting-kb.md +++ /dev/null @@ -1,20 +0,0 @@ -# Stage 3 - Supervisor + Reporting + KB-lite (Weeks 8-10) - -## Goal -Add manageability and analytics: realtime supervisor view, KPI core reporting, knowledge base support. - -## Scope -- supervisor-service realtime state -- reporting-service KPI aggregation: SL, ASA, AHT, Abandon, FCR -- kb-service categories/articles/search -- stage 3 OpenAPI contracts - -## Deliverables -- dashboard-ready supervisor API -- reporting export endpoint -- KB-lite APIs for operational usage - -## Gate 3 DoD -- Supervisor sees realtime operational state -- KPI reports reproducible -- KB usable in operator flow diff --git a/docs/roadmap/04-pilot-hardening.md b/docs/roadmap/04-pilot-hardening.md deleted file mode 100644 index 13401a2..0000000 --- a/docs/roadmap/04-pilot-hardening.md +++ /dev/null @@ -1,22 +0,0 @@ -# Stage 4 - Pilot Hardening + Acceptance (Weeks 11-12) - -## Goal -Stabilize and pass pilot acceptance. - -## Scope -- load tests up to 100/100 -- backup/restore drills -- security hardening checks -- UAT protocol and defect closure (P1/P2) -- final documentation pack - -## Deliverables -- test protocols -- UAT evidence -- operational runbooks -- accepted MVP v1 baseline - -## Gate 4 DoD -- Pilot passed -- SLA/KPI acceptance criteria confirmed -- System ready for post-MVP expansion diff --git a/docs/roadmap/05-wave2-backlog.md b/docs/roadmap/05-wave2-backlog.md deleted file mode 100644 index cee63e0..0000000 --- a/docs/roadmap/05-wave2-backlog.md +++ /dev/null @@ -1,126 +0,0 @@ -# Wave 2 - Post-MVP Backlog - -Wave 2 starts only after MVP pilot sign-off is complete and Gate 4 is formally closed. -This document captures the first prioritized expansion backlog so pilot defects and -new features do not mix. - -## Priority Order - -### 1. LDAP / SSO - -Goal: -- replace local-only auth as the primary enterprise login path - -Expected outputs: -- LDAP or SSO integration design -- role mapping rules -- operational runbook for identity integration - -Acceptance: -- enterprise login works without breaking current RBAC behavior -- fallback admin access is documented - -### 2. Additional Channels - -Goal: -- expand beyond the current MVP channel baseline - -Expected outputs: -- `webchat` handling flow -- `email` intake flow -- external messenger backlog after `webchat` and `email` - -Acceptance: -- new channels reuse the unified interaction model -- routing, history, and KPI ingestion stay consistent - -### 3. Dedicated Supervisor/Admin UI - -Goal: -- move beyond the single operator shell and provide dedicated operational consoles - -Expected outputs: -- supervisor UI for realtime monitoring, control, and reporting -- admin UI for users, queues, rules, and configuration - -Acceptance: -- critical supervisor and admin workflows no longer depend on the operator shell - -### 4. Recording and Audio Operations - -Goal: -- turn the current event-level recording placeholders into a usable product block - -Expected outputs: -- recording storage model -- playback/download flow -- access control and retention rules - -Acceptance: -- supervisors can find, review, and export recording artifacts safely - -### 5. Advanced IVR and Voice Flow - -Goal: -- introduce configurable IVR flows beyond current baseline voice event intake - -Expected outputs: -- IVR flow model -- editable configuration or builder -- routing integration for IVR outcomes - -Acceptance: -- IVR outcomes are traceable in interaction lifecycle and routing decisions - -### 6. Full KPI Catalog - -Goal: -- expand reporting from MVP core KPIs to the wider target metric model - -Expected outputs: -- KPI mapping backlog based on the extended specification -- data model updates -- report coverage matrix - -Acceptance: -- KPI definitions are reproducible and traceable to the specification - -### 7. Scale-Up and Hardening - -Goal: -- move from MVP pilot performance to target production scale - -Expected outputs: -- capacity plan -- higher load targets -- scale validation on the target deployment profile - -Acceptance: -- agreed performance target is met with documented evidence - -### 8. Event Bus and Integrations - -Goal: -- evolve the platform from contract-ready integration to operational event exchange - -Expected outputs: -- message bus integration design -- service event publishing/consumption plan -- external integration sequencing - -Acceptance: -- core domain events are delivered through the selected bus without contract drift - -## Delivery Rules - -- Wave 2 must be planned as a separate delivery track from MVP pilot closure. -- MVP defects remain in the MVP remediation lane until sign-off is complete. -- New Wave 2 scope is approved only after MVP accepted baseline is frozen. - -## Status Update (2026-03-05) - -- Track 9 (Asterisk AMI lab bridge) is accepted in QA evidence packs. -- Track 9.2 cutover prep is ready; production execute requires target context/secrets window. -- Track 10 is closed: - - see `docs/acceptance/track10/track10-acceptance.md`. -- Next execution step: Track 11 (live voice control scope). diff --git a/docs/roadmap/06-track10-live-voice-reliability.md b/docs/roadmap/06-track10-live-voice-reliability.md deleted file mode 100644 index 73e1ccd..0000000 --- a/docs/roadmap/06-track10-live-voice-reliability.md +++ /dev/null @@ -1,65 +0,0 @@ -# Wave 2 / Track 10 - Live Voice Reliability and Realtime SLA - -Track 10 starts after Track 9 QA acceptance is collected and frozen. - -## Status Update (2026-03-05) - -- Track 10 is closed using acceptance package: - - `docs/acceptance/track10/track10-acceptance.md` -- Closure mode: `GO with exception` - - direct p95 `started->ended` measured `33s` vs target `30s` - - reliability gates for stuck lifecycle/import are green (`active_no_end=0`, `ended_no_recording_upload=0`) -- Follow-up latency tightening is moved to the next track. - -## Goal - -Move from "Asterisk integration works" to "voice lifecycle is predictably near-realtime". - -Required outcome: -- `call.started` appears in supervisor flow quickly and consistently. -- `call.ended` is not delayed by reconciliation paths. -- recording upload latency is measured and controlled. - -## Scope - -In scope: -- latency/SLA instrumentation for voice lifecycle (`started -> ended -> recording`), -- stricter reconcile behavior for stale AMI events, -- automated latency report as an acceptance artifact, -- Track 10 acceptance checklist and evidence structure. - -Out of scope: -- ARI call control, -- operator softphone controls, -- new business voice features. - -## Baseline SLO (Track 10 target) - -- p95 `call.started -> call.ended` <= `30s` for lab calls. -- p95 `call.ended -> recording uploaded` <= `45s`. -- unresolved `failed bridge events` = `0` for acceptance window. - -## Deliverables - -1. Latency report script: - - `scripts/track10_voice_latency_report.py` -2. Runbook: - - `docs/runbooks/track10-live-voice-reliability.md` -3. Acceptance evidence folder: - - `docs/acceptance/track10//` -4. Final decision doc: - - `docs/acceptance/track10/track10-acceptance.md` - -## Phase 1 (implemented slice) - -Completed: -- added Track 10 latency report script with SLO thresholds and JSON artifacts, -- added `direct` vs `reconciled` split in latency metrics, -- updated bridge path so `recording.ready` can close call lifecycle immediately when `call.ended` is missing. - -## Acceptance - -Track 10 is accepted when: -- latency report is generated from real calls, -- all mandatory checks pass for the acceptance window, -- `GO` is signed in `track10-acceptance.md`. diff --git a/docs/roadmap/07-track11-live-operator-call-control.md b/docs/roadmap/07-track11-live-operator-call-control.md deleted file mode 100644 index a0d30b2..0000000 --- a/docs/roadmap/07-track11-live-operator-call-control.md +++ /dev/null @@ -1,60 +0,0 @@ -# Wave 2 / Track 11 - Live Operator Voice Control - -Status: `Accepted` - -Track 11 introduced the first real operator call-control loop on top of the accepted Asterisk bridge. - -## Goal - -Move from event-only voice integration to operator-controlled live calls in the existing operator shell. - -Accepted control path: -- operator sees a live inbound call in `/operator` -- operator answers media in external `MicroSIP/Zoiper` -- operator fixes ownership in the browser with `Принять в работу` -- operator can `Завершить` or `Передать` - -Voice media stays outside the browser. No WebRTC in this track. - -## Scope - -In scope: -- bridge live-call routes under `/asterisk/live-calls` -- own-call RBAC for `operator` -- operator live-call block -- action log and replay-safe behavior -- Asterisk event `MVPCCOperatorConnected` -- QA acceptance with real call and recording playback - -Out of scope: -- ARI -- attended transfer or conference -- embedded browser softphone -- business API redesign - -## Deliverables - -1. Migrations: - - `migrations/sql/0011_track11_voice_control_sqlite.sql` - - `migrations/sql/0011_track11_voice_control_postgres.sql` -2. Backend: - - `services/asterisk_bridge_service/app.py` -3. Operator UI: - - `ui/operator/index.html` - - `ui/operator/app.js` -4. Asterisk templates: - - `deployment/asterisk/pjsip.conf` - - `deployment/asterisk/extensions.conf` -5. Evidence and docs: - - `docs/runbooks/track11-live-operator-call-control.md` - - `docs/acceptance/track11/track11-acceptance.md` - -## Accepted baseline - -Track 11 is frozen with these guarantees: -- live inbound call visibility works in `/operator` -- `claim`, `hangup`, and `blind transfer` persist in bridge action logs -- `call.connected` and `call.transferred` are written as voice events -- recording import and supervisor playback stay green - -For the current operator UX, use the Track 12 runbook. Track 11 remains the control-path baseline. diff --git a/docs/roadmap/08-track12-operator-voice-ux-hardening.md b/docs/roadmap/08-track12-operator-voice-ux-hardening.md deleted file mode 100644 index a6cd0ea..0000000 --- a/docs/roadmap/08-track12-operator-voice-ux-hardening.md +++ /dev/null @@ -1,57 +0,0 @@ -# Wave 2 / Track 12 - Operator Voice UX Hardening - -Status: `Accepted` - -Track 12 hardens the accepted Track 11 voice-control baseline without changing the telephony architecture. - -## Goal - -Keep the current Asterisk plus `MicroSIP/Zoiper` model, but make the operator voice flow predictable and readable. - -Accepted outcome: -- `/operator` shows only real bridge-backed calls -- active and recently ended calls are separated -- browser actions behave consistently -- recent calls stay visible briefly instead of disappearing immediately - -## Scope - -In scope: -- `GET /asterisk/recent-calls` -- additive live-call metadata -- operator UI split into `Активные звонки` and `Только что завершённые` -- clearer labels and SIP-versus-browser hint text -- removal of interaction-based live-call guessing -- QA acceptance for active/recent call behavior - -Out of scope: -- WebRTC or browser softphone -- claim-first telephony redesign -- ARI -- attended transfer or conference -- production cutover - -## Deliverables - -1. Backend: - - `services/asterisk_bridge_service/app.py` - - `services/shared/models.py` - - `gateway/app.py` -2. Operator UI: - - `ui/operator/index.html` - - `ui/operator/app.js` - - `ui/operator/styles.css` -3. Evidence and docs: - - `docs/runbooks/track12-operator-voice-ux.md` - - `docs/acceptance/track12/track12-acceptance.template.md` - -## Accepted baseline - -Track 12 is frozen with these guarantees: -- `Живые звонки` never falls back to `interaction:int_*` placeholder cards -- `reconciled` service traces do not remove an active call early -- `hangup` and `blind transfer` move the card into the recent bucket -- recent cards stay visible for the short configured window -- Track 11 control-path behavior and recording playback remain green - -This is the current operator voice UX baseline. diff --git a/docs/roadmap/09-track14-browser-softphone-webrtc.md b/docs/roadmap/09-track14-browser-softphone-webrtc.md deleted file mode 100644 index 55f15a6..0000000 --- a/docs/roadmap/09-track14-browser-softphone-webrtc.md +++ /dev/null @@ -1,45 +0,0 @@ -# Wave 2 / Track 14 - Browser Softphone (WebRTC, QA First) - -Status: `Implemented, QA validation pending` - -Track 14 adds the first browser-native operator media path on top of the accepted Track 11 and Track 12 voice baseline. - -## Goal - -Keep the accepted bridge-backed live-call model and move the operator media path into the browser for the first QA slice. - -Implemented direction: -- direct Asterisk WSS -- inbound MVP only -- browser softphone coexists with `MicroSIP/Zoiper` -- existing bridge `claim/hangup/recent-calls` remains authoritative - -## Deliverables - -1. Bridge: - - additive route: `/asterisk/browser-softphone/config` - - additive status fields: `webrtc_enabled`, `webrtc_ws_url` -2. Operator shell: - - `Browser Softphone` block - - register/disconnect state - - incoming call banner - - `Answer`, `Hang up`, `Mute/Unmute` - - microphone and speaker selectors -3. Asterisk lab assets: - - `deployment/asterisk/http.conf` - - `deployment/asterisk/rtp.conf` - - updated `deployment/asterisk/pjsip.conf` - -## Boundaries - -Track 14 does not introduce: -- production cutover -- ARI -- browser-only mandatory mode -- DB schema changes -- WebRTC backend service outside Asterisk - -The accepted voice system of record remains: -- bridge live/recent calls -- bridge action log -- recording import and supervisor playback diff --git a/docs/roadmap/10-track15-telegram-chat.md b/docs/roadmap/10-track15-telegram-chat.md deleted file mode 100644 index 16f9ce9..0000000 --- a/docs/roadmap/10-track15-telegram-chat.md +++ /dev/null @@ -1,30 +0,0 @@ -# Track 15 - Telegram Chat - -Status: `implemented` - -## Scope - -- real Telegram Bot API webhook -- one persistent thread per `chat_id` -- 2-way text chat -- separate `Telegram` page in `/operator` - -## What changed - -- `telegram-adapter-service` now owns Telegram threads and thread messages -- new thread/message routes exist under `/integrations/telegram/threads/*` -- inbound messages reuse the same thread and linked interaction -- a closed Telegram interaction is reactivated on new inbound activity -- operator replies go through Telegram `sendMessage` - -## Out of scope - -- Telegram voice/video calls -- media/file workflow -- admin UI for bot setup - -## Acceptance focus - -- first inbound message creates thread + interaction -- repeated inbound on the same `chat_id` reuses the same thread -- operator can claim and reply from the dedicated `Telegram` page diff --git a/docs/roadmap/README.md b/docs/roadmap/README.md deleted file mode 100644 index a182b01..0000000 --- a/docs/roadmap/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Sequential Execution Rules - -1. Stages are executed strictly in sequence. -2. A stage starts only when previous gate is formally closed. -3. Scope changes are deferred to the next stage backlog. -4. Weekly checkpoint + demo is mandatory. -5. Every stage has: - - scope - - deliverables - - tests - - gate checklist diff --git a/docs/runbooks/.env.production.checklist.md b/docs/runbooks/.env.production.checklist.md deleted file mode 100644 index d6c512f..0000000 --- a/docs/runbooks/.env.production.checklist.md +++ /dev/null @@ -1,132 +0,0 @@ -# `.env.production` Checklist (Track 9/11 QA Lab) - -Use this checklist before enabling `ASTERISK_BRIDGE_ENABLED=1`. - -## 1) File handling and secret hygiene - -- [ ] Create local `.env.production` from [`.env.production.template`](/e:/Zhan/.env.production.template). -- [ ] Keep `.env.production` out of git (it is ignored in [`.gitignore`](/e:/Zhan/.gitignore)). -- [ ] Replace all placeholder values: - - `` - - `` - - `` - - `` - - `` - - `` -- [ ] Verify that AMI/SFTP credentials are not copied into docs, commits, or screenshots. - -## 2) Required environment variables - -### Core - -- [ ] `DATABASE_URL` points to PostgreSQL (not SQLite). -- [ ] `INTERACTION_SERVICE_URL` is reachable from `asterisk-bridge-service`. -- [ ] `VOICE_ADAPTER_SERVICE_URL` is reachable. -- [ ] `RECORDING_SERVICE_URL` is reachable. - -### Track 9 bridge - -- [ ] `ASTERISK_BRIDGE_ENABLED=1` -- [ ] `ASTERISK_AMI_HOST` / `ASTERISK_AMI_PORT` are correct. -- [ ] `ASTERISK_AMI_USERNAME` / `ASTERISK_AMI_SECRET` are correct. -- [ ] `ASTERISK_AMI_EVENT_PREFIX=MVPCC` unless your dialplan intentionally changed it. -- [ ] `ASTERISK_QUEUE_MAP_JSON` contains valid JSON object. -- [ ] Every `queue_code` in dialplan has a mapped platform `queue_id`. -- [ ] `ASTERISK_BRIDGE_AUTH_MODE` chosen intentionally: - - `legacy_headers` for Track 9 QA baseline - - `bearer_first` for transition hardening - - `bearer` for strict service-token mode -- [ ] If bearer mode is used, `APP_TOKEN_SECRET` is the same across gateway/services/bridge runtime. -- [ ] `ASTERISK_BRIDGE_AUTH_USER` / `ASTERISK_BRIDGE_AUTH_ROLE` match expected service identity. -- [ ] `VOICE_ADAPTER_TRUSTED_SERVICE_SUBJECTS` includes `svc:asterisk-bridge`. -- [ ] `RECORDING_IMPORT_TRUSTED_SERVICE_SUBJECTS` includes `svc:asterisk-bridge`. -- [ ] For strict Track 9.1 mode, set `RECORDING_IMPORT_ALLOW_ADMIN=0`. - -### Track 11 call-control - -- [ ] `ASTERISK_CALLCONTROL_ENABLED=1` (в QA, если проверяете операторское управление звонком). -- [ ] `ASTERISK_CALLCONTROL_ACTION_TIMEOUT_SECONDS` задан (рекомендуемо `10`). -- [ ] `ASTERISK_OPERATOR_EXTENSION_MAP_JSON` содержит mapping `operator -> extension`. - - пример: `{"operator_a":"2001","operator_b":"2002"}` -- [ ] `ASTERISK_TRANSFER_TARGET_MAP_JSON` содержит mapping `queue_code -> extension` для blind transfer. - - пример: `{"voice_lab":"2001"}` -- [ ] `ASTERISK_CALLCONTROL_CLAIM_CONTEXT` и `ASTERISK_CALLCONTROL_TRANSFER_CONTEXT` совпадают с dialplan contexts. - -### SFTP pickup - -- [ ] `ASTERISK_SFTP_HOST` / `ASTERISK_SFTP_PORT` are correct. -- [ ] `ASTERISK_SFTP_USERNAME` / `ASTERISK_SFTP_PASSWORD` are correct. -- [ ] `ASTERISK_SFTP_BASE_PATH` points to the recording directory (default `/var/spool/asterisk/monitor`). - -### Voice AI V1 staging - -- [ ] `AI_VOICE_ENABLED=1` only for the pilot queues you intend to test. -- [ ] `AI_VOICE_QUEUE_CONFIG_JSON` contains the AI-first queue code from the dialplan. - - example: `{"voice_lab_ai":{"mode":"ai_first","agent_profile":"voice_support","handoff_queue_code":"voice_lab","language":"ru"}}` -- [ ] `AI_VOICE_RUNTIME_SERVICE_URL` points to the HTTP runtime service, not the AudioSocket media endpoint. -- [ ] `AI_VOICE_RUNTIME_TRUSTED_SERVICE_SUBJECTS` includes `svc:ai-voice-runtime`. -- [ ] `AI_VOICE_ASR_PROVIDER` / `AI_VOICE_TTS_PROVIDER` are set intentionally. -- [ ] `ASTERISK_TRANSFER_TARGET_MAP_JSON` includes both the human and AI queue codes when they can hand off to the same operator group. - - example: `{"voice_lab":"2001","voice_lab_ai":"2001"}` -- [ ] On the Asterisk VM, leave `MVPCC_AI_AUDIOSOCKET_SERVICE=` blank until the media listener is ready. -- [ ] After the media listener is available, verify `app_audiosocket.so` is loaded and `MVPCC_AI_AUDIOSOCKET_SERVICE=:` uses the raw AudioSocket address. - -### Event bus baseline (Track 8 compatibility) - -- [ ] `EVENT_BUS_ENABLED=1` in QA. -- [ ] `EVENT_BUS_URL` resolves to reachable RabbitMQ. - -## 3) Preflight checks before first call - -- [ ] Run migrations: - - `python scripts\migrate_core_db.py` -- [ ] Run full regression: - - `pytest -q` -- [ ] Start stack with production env loaded: - - `powershell -ExecutionPolicy Bypass -File scripts\start_track9_qa.ps1` -- [ ] Bridge health: - - `GET /proxy/asterisk-bridge/health` returns `ok` -- [ ] Bridge status: - - `GET /proxy/asterisk-bridge/asterisk/status` shows `ami_connected=true` - -## 4) Acceptance checks after test call `1001 -> 7000` - -- [ ] `voice` interaction created from Asterisk event flow. -- [ ] `voice_events` contain: - - `call.started` with `payload.source = "asterisk"` - - `call.ended` for the same `call_id` -- [ ] `MVPCCRecordingReady` led to uploaded recording in `recording-service`. -- [ ] Playback works in `supervisor`. -- [ ] Для Track 11: - - оператор видит звонок в `/operator` -> блок `Живые звонки` - - `claim` работает и пишет action log - - `hangup` работает - - `blind transfer` работает -- [ ] Run: - - `python scripts\asterisk_lab_smoke.py --base-url --database-url --require-recording` - - `python scripts\track9_check.py --base-url --database-url --require-recording` - - `python scripts\track9_collect_evidence.py --base-url --database-url --run-checks --require-recording` - -## 4.1) Voice AI control-plane smoke after test call `1001 -> 7100` - -- [ ] `MVPCCCallStarted` used `QueueCode=voice_lab_ai`. -- [ ] `voice_ai_sessions` row created for the call. -- [ ] `ai_sessions` row created with `channel=voice`. -- [ ] Interaction timeline contains `ai.session_started`. -- [ ] Operator UI shows an AI badge on the live or recent voice call card. -- [ ] `GET /proxy/asterisk-bridge/asterisk/live-calls/{call_id}/ai-summary` returns `200` once the AI summary exists. -- [ ] Human fallback path still works if `MVPCC_AI_AUDIOSOCKET_SERVICE=` is blank. - -## 5) Failure policy - -- [ ] If failed bridge events exist, inspect: - - `GET /proxy/asterisk-bridge/asterisk/events?status=failed` -- [ ] Retry after fixing root cause: - - `POST /proxy/asterisk-bridge/asterisk/events/{bridge_event_id}/retry` -- [ ] Do not mark Track 9 accepted until failed bridge events are resolved or explicitly waived. - -## 6) Track 9.2 cutover handoff - -- [ ] Prepare [track9-cutover-sheet.template.md](/e:/Zhan/docs/acceptance/track9/track9-cutover-sheet.template.md). -- [ ] Follow [track9-2-production-cutover.md](/e:/Zhan/docs/runbooks/track9-2-production-cutover.md) for controlled Helm rollout. -- [ ] Use `scripts\track9_2_cutover.ps1` first in dry-run mode, then with `-Execute` during the approved window. diff --git a/docs/runbooks/asterisk-lab-linux-vm.md b/docs/runbooks/asterisk-lab-linux-vm.md deleted file mode 100644 index 1511d42..0000000 --- a/docs/runbooks/asterisk-lab-linux-vm.md +++ /dev/null @@ -1,315 +0,0 @@ -# Asterisk Lab on Linux VM - -Use this runbook to stand up the Track 9 human baseline, the IVR staging entrypoint, and the Voice AI V1 staging entrypoint on the same Asterisk VM. - -## Target - -- Host: dedicated Linux VM -- OS: Ubuntu 22.04 LTS or Ubuntu 24.04 LTS -- Asterisk: 20 LTS -- Integration mode: AMI + custom `UserEvent` -- Call source: softphone lab only -- Human baseline entrypoint: `7000` -- IVR staging entrypoint: `7200` -- Voice AI staging entrypoint: `7100` - -## Prepare the VM - -1. Install Asterisk 20 LTS. -2. Ensure these ports are reachable from the platform host: - - `5060/udp` for SIP - - `5038/tcp` for AMI - - `22/tcp` for SFTP -3. Create the recording directory: - - `/var/spool/asterisk/monitor/mvpcc` - -## Apply repo configs - -Copy these files from the repository: - -- `deployment/asterisk/pjsip.conf` -- `deployment/asterisk/extensions.conf` -- `deployment/asterisk/manager.conf` - -Before reload: - -1. Replace default passwords. -2. Confirm extension `1001`. -3. Keep extension `7000` as the human-only inbound test entrypoint. -4. Keep extension `7100` reserved for Voice AI staging. -5. Keep extension `7200` reserved for IVR staging. - -Voice AI dialplan notes: - -- `deployment/asterisk/extensions.conf` now defines two queue codes: - - `voice_lab` for `7000` - - `voice_lab_ai` for `7100` -- `7100` falls back to the human dial targets unless `MVPCC_AI_AUDIOSOCKET_SERVICE` is populated. -- When you are ready to test the media bridge, load `app_audiosocket.so` and set `MVPCC_AI_AUDIOSOCKET_SERVICE=:`. -- Asterisk `AudioSocket()` requires a raw TCP media listener and a UUID per call. The template generates the UUID with `uuidgen`. -- Official app reference: - - [Asterisk AudioSocket application](https://docs.asterisk.org/Latest_API/API_Documentation/Dialplan_Applications/AudioSocket/) - -IVR dialplan notes: - -- `deployment/asterisk/extensions.conf` defines `voice_lab_ivr` for `7200`. -- `7200` calls FastAGI at `MVPCC_IVR_FASTAGI_HOSTPORT` and expects `prompt_audio_key` values to match pre-provisioned Asterisk sound files. -- If FastAGI does not return `MVPCC_IVR_TARGET_EXTENSION`, the call falls back to the human dial targets. - -## Configure the platform bridge - -Set these environment variables for `asterisk-bridge-service`: - -- `ASTERISK_BRIDGE_ENABLED=1` -- `ASTERISK_AMI_HOST=` -- `ASTERISK_AMI_PORT=5038` -- `ASTERISK_AMI_USERNAME=mvpcc` -- `ASTERISK_AMI_SECRET=` -- `ASTERISK_AMI_EVENT_PREFIX=MVPCC` -- `ASTERISK_QUEUE_MAP_JSON={"voice_lab":"","voice_lab_ai":"","voice_lab_ivr":""}` -- `ASTERISK_BRIDGE_AUTH_MODE=legacy_headers` (or `bearer_first` / `bearer`) -- `ASTERISK_BRIDGE_AUTH_FALLBACK_LEGACY=1` (used only with `bearer_first`) -- `ASTERISK_BRIDGE_AUTH_SUBJECT=svc:asterisk-bridge` -- `ASTERISK_BRIDGE_AUTH_USER=asterisk-bridge` -- `ASTERISK_BRIDGE_AUTH_ROLE=admin` -- `ASTERISK_BRIDGE_AUTH_TOKEN_TTL_SECONDS=300` -- `ASTERISK_IVR_FASTAGI_ENABLED=1` -- `ASTERISK_IVR_FASTAGI_HOST=0.0.0.0` (or the explicit bridge bind IP) -- `ASTERISK_IVR_FASTAGI_PORT=4573` -- `ASTERISK_IVR_DTMF_TIMEOUT_SECONDS=5` -- `ASTERISK_IVR_MAX_NO_INPUT_RETRIES=2` -- `ASTERISK_IVR_MAX_INVALID_RETRIES=2` -- `IVR_RUNTIME_TRUSTED_SERVICE_SUBJECTS=svc:asterisk-bridge` -- `VOICE_ADAPTER_TRUSTED_SERVICE_SUBJECTS=svc:asterisk-bridge,svc:ivr-service` -- `RECORDING_IMPORT_TRUSTED_SERVICE_SUBJECTS=svc:asterisk-bridge` -- `RECORDING_IMPORT_ALLOW_ADMIN=1` (set `0` in strict mode) -- `ASTERISK_SFTP_HOST=` -- `ASTERISK_SFTP_PORT=22` -- `ASTERISK_SFTP_USERNAME=` -- `ASTERISK_SFTP_PASSWORD=` -- `ASTERISK_SFTP_BASE_PATH=/var/spool/asterisk/monitor` -- `ASTERISK_TRANSFER_TARGET_MAP_JSON={"voice_lab":"2001","voice_lab_ai":"2001","voice_lab_ivr":"7200"}` -- `AI_VOICE_ENABLED=1` -- `AI_VOICE_QUEUE_CONFIG_JSON={"voice_lab_ai":{"mode":"ai_first","agent_profile":"voice_support","handoff_queue_code":"voice_lab","language":"ru"}}` -- `AI_VOICE_RUNTIME_SERVICE_URL=http://ai-voice-runtime-service:8000` -- `AI_VOICE_RUNTIME_TRUSTED_SERVICE_SUBJECTS=svc:ai-voice-runtime` -- `AI_VOICE_ASR_PROVIDER=openai` -- `AI_VOICE_TTS_PROVIDER=openai` -- `AI_VOICE_MAX_CONTEXT_SEGMENTS=8` -- `AI_VOICE_HANDOFF_TIMEOUT_SECONDS=8` - -For Track 9 QA acceptance baseline, keep: - -- bridge auth mode in compatibility headers (`X-User` / `X-Role`) -- this is expected for Track 9 baseline and moved to hardening in Track 9.1 - -For Track 9.1 hardening, switch to: - -- `ASTERISK_BRIDGE_AUTH_MODE=bearer_first` first -- then `ASTERISK_BRIDGE_AUTH_MODE=bearer` after successful smoke and no auth regressions -- set `RECORDING_IMPORT_ALLOW_ADMIN=0` -- in Helm-based environments, use `deployment/helm/values.track9-strict.yaml` as an overlay - -Voice AI V1 staging guidance: - -- Start with `MVPCC_AI_AUDIOSOCKET_SERVICE=` blank in `extensions.conf`. -- This lets `7100` validate the AI control-plane wiring while still falling back to the existing human voice path. -- Only point `MVPCC_AI_AUDIOSOCKET_SERVICE` to a live AudioSocket listener after the media listener is reachable from the VM. -- `AI_VOICE_RUNTIME_SERVICE_URL` is not the AudioSocket address; it is only the HTTP control-plane URL. - -## Register the softphone - -In your softphone: - -- username: `1001` -- password: the value set in `pjsip.conf` -- server: the Linux VM IP/hostname - -## QA preflight (required) - -1. Copy [`.env.production.template`](/e:/Zhan/.env.production.template) to local `.env.production`. -2. Complete [`.env.production.checklist.md`](/e:/Zhan/docs/runbooks/.env.production.checklist.md). -3. Start the stack with production env loaded: - -```powershell -powershell -ExecutionPolicy Bypass -File scripts\start_track9_qa.ps1 -``` - -4. Run: - -```powershell -python scripts\migrate_core_db.py -pytest -q -python scripts\track9_preflight.py --base-url http://127.0.0.1:8080 --check-sftp -python scripts\track9_preflight.py --base-url http://127.0.0.1:8080 --check-sftp --require-strict-service-auth -``` - -## Run the first test call - -1. Start the platform. -2. Start `asterisk-bridge-service`. -3. Call `7000` from extension `1001`. -4. Hang up after the playback finishes. - -Expected result: - -- `asterisk-bridge-service` receives: - - `MVPCCCallStarted` - - `MVPCCCallEnded` - - `MVPCCRecordingReady` -- platform creates a new `voice` interaction -- `voice_events` contain: - - `call.started` - - `call.ended` -- one recording is uploaded into `recording-service` - -## Run the IVR-over-Asterisk smoke - -1. Create or activate an IVR flow for the queue mapped from `voice_lab_ivr`. -2. Make sure every live node uses a valid `prompt_audio_key`, and the audio files are present in Asterisk sounds. -3. Call `7200` from extension `1001`. -4. Test both branches: - - enter `1` or `2` to complete IVR and transfer into the mapped queue target - - stay silent until retry exhaustion to verify fallback into the human baseline - -Expected result: - -- `asterisk-bridge-service` receives `MVPCCCallStarted` with `QueueCode=voice_lab_ivr`. -- `ivr_sessions` contains a live session for the `call_id`. -- interaction timeline contains `ivr.session.started` and `ivr.step.no_input` or `ivr.step.completed`. -- terminal completion writes `voice_events.event_type="ivr.completed"`. -- telephony returns to `mvpcc-transfer` for the resolved queue target, or to the human baseline on retry exhaustion. - -## Run the Voice AI control-plane smoke - -Use this smoke before turning on the real AudioSocket media path. - -1. Keep `MVPCC_AI_AUDIOSOCKET_SERVICE=` blank in `extensions.conf`. -2. Start the platform and `ai-voice-runtime-service`. -3. Call `7100` from extension `1001`. -4. Let the call ring through to the fallback human dial targets. - -Expected result: - -- `asterisk-bridge-service` receives `MVPCCCallStarted` with `QueueCode=voice_lab_ai`. -- platform creates: - - a `voice` interaction - - a `voice_ai_sessions` row - - an `ai_sessions` row with `channel=voice` -- operator UI shows the AI badge on the live/recent call card -- normal call recording flow stays green - -This smoke proves queue selection, bridge-to-runtime session creation, timeline events, and safe human fallback. It does not prove realtime ASR/TTS yet. - -## Verify from the platform - -1. Check: - - `GET /proxy/asterisk-bridge/asterisk/status` -2. Then run: - -```powershell -python scripts\track9_preflight.py --base-url http://127.0.0.1:8080 --check-sftp -``` - -3. For smoke validation: - -```powershell -python scripts\asterisk_lab_smoke.py --base-url http://127.0.0.1:8080 --database-url --require-recording -``` - -4. For formal validation: - -```powershell -python scripts\track9_check.py --base-url http://127.0.0.1:8080 --database-url --require-recording -``` - -5. For Voice AI control-plane validation, also verify: - - `GET /proxy/asterisk-bridge/asterisk/live-calls` - - `GET /proxy/asterisk-bridge/asterisk/live-calls/{call_id}/ai-summary` - - `ai.session_started` exists in the interaction timeline for the `7100` call - -## Build formal acceptance evidence pack - -After smoke/check pass: - -```powershell -python scripts\track9_collect_evidence.py --base-url http://127.0.0.1:8080 --database-url --run-checks --require-recording -``` - -Then fill: - -- `docs/acceptance/track9//track9-acceptance.md` - -At minimum include: - -- bridge status snapshot -- smoke output -- track9_check output -- sample rows from: - - `asterisk_event_log` - - `voice_events` - - recording linkage - -## Retry semantics and duplicate safety - -Bridge event statuses: - -- `received`: event accepted by bridge, no forward result yet. -- `forwarded`: event processed successfully and forwarded to platform services. -- `failed`: forwarding failed; inspect `last_error`, fix root cause, then retry. - -Retry endpoint: - -- `POST /proxy/asterisk-bridge/asterisk/events/{bridge_event_id}/retry` - -Expected behavior: - -1. Retry is safe for the same `bridge_event_id`. -2. Existing `call_id -> interaction_id` links prevent duplicate interaction creation. -3. Recording retry will update missing upload state if SFTP/upload issue is fixed. -4. Keep retries manual in QA until root cause is verified. - -## Timeout / retry policy baseline (QA) - -| Area | Current baseline | Operator action | -|---|---|---| -| AMI reconnect | `ASTERISK_BRIDGE_POLL_INTERVAL_SECONDS` (default `1s`) reconnect loop | Verify `ami_connected=true` in `/asterisk/status` | -| SFTP fetch | Single attempt per event processing | Fix credentials/path/network, then call retry endpoint | -| Recording upload | Single upload attempt per event processing | Fix `recording-service` reachability/auth, then retry | -| Failed event aging | No auto-waiver | Track 9 is not accepted with unresolved failed events | - -## Troubleshooting - -- `AMI is not connected` - - verify `manager.conf`, firewall, and credentials -- `Unknown QueueCode` - - ensure dialplan `QueueCode` matches `ASTERISK_QUEUE_MAP_JSON` - - for Voice AI staging, verify both `voice_lab` and `voice_lab_ai` - - verify mapped `queue_id` exists in platform: - - `GET /proxy/routing/queues` - - re-run failed event with retry endpoint -- `7100` immediately falls back to a human and no AI badge appears - - verify `AI_VOICE_ENABLED=1` - - verify `AI_VOICE_QUEUE_CONFIG_JSON` contains `voice_lab_ai` - - verify the `MVPCCCallStarted` event shows `QueueCode=voice_lab_ai` -- `7100` errors as soon as AudioSocket is enabled - - verify `app_audiosocket.so` is loaded on Asterisk - - verify `MVPCC_AI_AUDIOSOCKET_SERVICE` points to a raw TCP media listener, not `http://...:8000` - - verify the media host and port are reachable from the Asterisk VM -- `No uploaded recordings linked to Asterisk events` - - verify SFTP credentials and `/var/spool/asterisk/monitor/mvpcc` - - verify `ASTERISK_SFTP_BASE_PATH` - - verify `POST /proxy/recording/recordings/import-upload` path is reachable from bridge host - - retry failed `MVPCCRecordingReady` events after fix -- `Failed bridge events present` - - inspect: - - `GET /proxy/asterisk-bridge/asterisk/events?status=failed` - - then retry with: - - `POST /proxy/asterisk-bridge/asterisk/events/{bridge_event_id}/retry` - -## Track 9.2 production cutover - -After QA acceptance is signed, switch to controlled production rollout: - -- [track9-2-production-cutover.md](/e:/Zhan/docs/runbooks/track9-2-production-cutover.md) -- `scripts/track9_2_cutover.ps1` diff --git a/docs/runbooks/backup-restore.md b/docs/runbooks/backup-restore.md deleted file mode 100644 index ca4dcbe..0000000 --- a/docs/runbooks/backup-restore.md +++ /dev/null @@ -1,20 +0,0 @@ -# Runbook - Backup and Restore (Stage 4) - -## Backup -- Stop write-heavy operations (or enter maintenance window). -- Run backup script: - - `powershell -ExecutionPolicy Bypass -File scripts/backup_data.ps1` -- Keep backup config (`values.yaml`, contracts, runbooks). - -## Restore drill -1. Start clean environment -2. Re-apply deployment manifests -3. Restore snapshots: - - `powershell -ExecutionPolicy Bypass -File scripts/restore_data.ps1 -BackupZip ` -4. Validate smoke scenarios: - - login - - create interaction - - KPI endpoint - -## Acceptance -- RTO and data integrity documented in pilot protocol diff --git a/docs/runbooks/demo-showcase.md b/docs/runbooks/demo-showcase.md deleted file mode 100644 index fbcb4f9..0000000 --- a/docs/runbooks/demo-showcase.md +++ /dev/null @@ -1,79 +0,0 @@ -# Demo Showcase Runbook - -Use this flow before showing the MVP to management. - -## One-command prep - -```powershell -powershell -ExecutionPolicy Bypass -File scripts\prepare_demo.ps1 -``` - -What it does: - -- starts the full local stack in the background -- clears local demo data before start -- runs the smoke check against `http://localhost:8080` -- seeds demo data for customers, interactions, integrations, KB, supervisor, KPI, and one sample recording - -## What to open - -- Operator UI: `http://localhost:8080/operator` -- Supervisor UI: `http://localhost:8080/supervisor` -- Admin UI: `http://localhost:8080/admin` -- Logs: `.local_stack\logs` -- Demo summary: `.local_stack\demo-seed-summary.json` -- Login: `admin / admin123` - -## Demo sequence - -1. Log in as `admin / admin123`. -2. Open the interactions list and show: - - one voice interaction already assigned, escalated, and closed - - one active follow-up interaction -3. Show that assign / escalate uses default values (`operator_a`, `line2`) without browser prompts. -4. Trigger the voice channel, Telegram, Webchat, and Email checks. -5. Search KB for `demo-showcase`. -6. Refresh the supervisor block and read the summary cards. -7. Refresh KPI and read the summary cards. - - the JSON payload also includes extended metrics and a `by_channel` breakdown -8. Open `/supervisor`, load `Recordings`, and show playback/download of the seeded sample. -9. Open `/admin`, load the seeded IVR flow, show the completed IVR session, and preview route override with `ivr_session_id`. - -## Optional Telegram chat follow-up - -If the Telegram bot env is configured: - -1. Open `/operator`. -2. Go to the `Telegram` page in the sidebar. -3. Send one test message to the configured bot from a real Telegram client. -4. Show that: - - a persistent thread appears for that `chat_id` - - the linked interaction uses `channel="telegram"` - - the operator can claim the thread and reply from the same page - -## Optional live voice follow-up - -If the Asterisk lab bridge is enabled on a dedicated Linux VM: - -1. Open `/operator`. -2. Choose the QA media path: - - browser softphone via `Browser Softphone` - - or external fallback via `MicroSIP/Zoiper` -3. Place one lab call from softphone `1001` to `7000`. -4. If using browser media, connect the browser softphone first and answer in Chrome/Edge. -5. Use the current voice baseline from `docs/runbooks/voice-baseline.md`. - -## Optional separate shells - -After the main management flow, you can open: - -- `/supervisor` to show dedicated realtime and queue controls -- `/admin` to show separate user and queue management - -## Stop after the demo - -```powershell -powershell -ExecutionPolicy Bypass -File scripts\stop_all_local.ps1 -``` - -For a short speaking script during the meeting, use `docs/runbooks/management-demo-brief.md`. diff --git a/docs/runbooks/deployment-onprem.md b/docs/runbooks/deployment-onprem.md deleted file mode 100644 index eae95e8..0000000 --- a/docs/runbooks/deployment-onprem.md +++ /dev/null @@ -1,175 +0,0 @@ -# Runbook - On-Prem Deployment - -## Helm -- `helm upgrade --install mvp-cc ./deployment/helm -n mvp-cc --create-namespace` -- For PostgreSQL-backed Helm releases, the chart now runs `scripts/migrate_core_db.py` automatically via a pre-install / pre-upgrade migration Job. - -## Raw manifests -- `kubectl apply -f deployment/kubernetes/mvp-cc-platform.yaml` -- Raw manifests remain a minimal reference/bootstrap path. -- Helm is the canonical production-scale deployment path for Wave 2 / Track 7. - -## Core DB migration -- Helm path: - - keep `migrations.enabled=true` - - keep `SCHEMA_MANAGEMENT_MODE=migrations` - - the Helm hook Job applies migrations before rollout -- Raw manifests / manual path: - - run `python scripts/migrate_core_db.py` - - then start services with `SCHEMA_MANAGEMENT_MODE=migrations` - -## Wave 2 / Track 7 scale baseline - -Production-like K8s scale validation requires: - -- `DATABASE_URL` set to a shared PostgreSQL endpoint -- `DB_POOL_SIZE` -- `DB_MAX_OVERFLOW` -- `DB_POOL_TIMEOUT_SECONDS` -- `DB_POOL_RECYCLE_SECONDS` -- a working `metrics-server` in the target cluster - -Recommended Wave 2 / Track 7 rollout: - -1. Render Helm config for the scale profile. -2. Confirm `DATABASE_URL` points to PostgreSQL (not SQLite). -3. Confirm `SCHEMA_MANAGEMENT_MODE=migrations`. -4. Confirm `migrations.enabled=true`. -5. Deploy via Helm. -6. Verify the migration Job completed successfully. -7. Run `python scripts/load_test.py --base-url http:// --profile step_250_250 --auth-mode bearer`. -8. Run `python scripts/track7_check.py --namespace mvp-cc --report-dir `. -9. Repeat with `--profile target_500_500`. - -For the complete procedure, use: - -- [track7-scale-validation.md](/e:/Zhan/docs/runbooks/track7-scale-validation.md) - -## Wave 2 / Track 1 auth configuration - -Minimum enterprise auth variables: - -- `APP_TOKEN_SECRET` -- `APP_TOKEN_TTL_SECONDS` (default `3600`) -- `ALLOW_LEGACY_HEADER_AUTH=0` -- `OIDC_ENABLED=1` -- `OIDC_PROVIDER=keycloak` -- `OIDC_ISSUER_URL` -- `OIDC_CLIENT_ID` -- `OIDC_CLIENT_SECRET` -- `OIDC_REDIRECT_URI` -- `OIDC_SCOPES` (default `openid profile email`) -- `OIDC_ROLE_CLAIM` (default `groups`) -- `OIDC_ROLE_MAP_JSON` - -Helm and raw Kubernetes manifests now include placeholder values for these settings. -Replace all `change-me-*` values before deploying outside local/demo. - -Recommended rollout sequence: - -1. For Helm, keep migration hook enabled so schema upgrades run before deploy. -2. Deploy with `OIDC_ENABLED=1` and `ALLOW_LEGACY_HEADER_AUTH=1` in QA. -3. Run `python scripts/oidc_smoke.py --base-url http:// --require-enabled`. -4. Validate a real Keycloak login round trip. -5. Switch enterprise pilot to `ALLOW_LEGACY_HEADER_AUTH=0`. - -## Post-deploy checks -- Pods ready state -- `/health` probes green -- Gateway registry endpoint available -- `GET /proxy/auth/auth/oidc/config` returns `enabled=true` -- `GET /proxy/auth/auth/oidc/health` returns provider metadata status - -See also: - -- [keycloak-oidc.md](/e:/Zhan/docs/runbooks/keycloak-oidc.md) -- [postgres-dev-cutover.md](/e:/Zhan/docs/runbooks/postgres-dev-cutover.md) -- [postgres-server-docker.md](/e:/Zhan/docs/runbooks/postgres-server-docker.md) -- [postgres-server-parallel-stack.md](/e:/Zhan/docs/runbooks/postgres-server-parallel-stack.md) - -## Wave 2 / Track 9.1 strict bridge auth cutover - -Use Helm overlays to force strict bridge auth mode in QA/enterprise environments: - -```powershell -helm upgrade --install mvp-cc deployment\helm ` - -f deployment\helm\values.scale500.yaml ` - -f deployment\helm\values.track9-strict.yaml ` - --set-string auth.appTokenSecret= ` - --set-string asteriskBridge.enabled=1 ` - --set-string asteriskBridge.amiHost= ` - --set-string asteriskBridge.amiUsername= ` - --set-string asteriskBridge.amiSecret= ` - --set-string asteriskBridge.queueMapJson='{\"voice_lab\":\"\"}' ` - --set-string asteriskBridge.sftpHost= ` - --set-string asteriskBridge.sftpUsername= ` - --set-string asteriskBridge.sftpPassword= ` - -n mvp-cc --create-namespace -``` - -Expected strict baseline after deploy: - -- `ALLOW_LEGACY_HEADER_AUTH=0` -- `ASTERISK_BRIDGE_AUTH_MODE=bearer` -- `ASTERISK_BRIDGE_AUTH_FALLBACK_LEGACY=0` -- `VOICE_ADAPTER_TRUSTED_SERVICE_SUBJECTS` contains `svc:asterisk-bridge` -- `RECORDING_IMPORT_TRUSTED_SERVICE_SUBJECTS` contains `svc:asterisk-bridge` -- `RECORDING_IMPORT_ALLOW_ADMIN=0` - -Post-cutover validation: - -```powershell -python scripts\track9_preflight.py --base-url http:// --check-sftp -python scripts\asterisk_lab_smoke.py --base-url http:// --database-url --require-recording -python scripts\track9_check.py --base-url http:// --database-url --require-recording -``` - -## Wave 2 / Track 9.2 controlled production cutover - -Use the dedicated cutover runbook and script: - -- [track9-2-production-cutover.md](/e:/Zhan/docs/runbooks/track9-2-production-cutover.md) -- `scripts/track9_2_cutover.ps1` - -Dry-run (render/lint/snapshot only): - -```powershell -powershell -ExecutionPolicy Bypass -File scripts\track9_2_cutover.ps1 ` - -KubeContext ` - -Namespace ` - -Release ` - -GatewayBaseUrl http:// ` - -DatabaseUrl postgresql://<...> ` - -ImageTag ` - -AmiHost ` - -AmiUser ` - -AmiSecret ` - -SftpHost ` - -SftpUser ` - -SftpPassword ` - -QueueId ` - -AppTokenSecret -``` - -Execute cutover: - -```powershell -powershell -ExecutionPolicy Bypass -File scripts\track9_2_cutover.ps1 ` - -KubeContext ` - -Namespace ` - -Release ` - -GatewayBaseUrl http:// ` - -DatabaseUrl postgresql://<...> ` - -ImageTag ` - -AmiHost ` - -AmiUser ` - -AmiSecret ` - -SftpHost ` - -SftpUser ` - -SftpPassword ` - -QueueId ` - -AppTokenSecret ` - -Execute -``` - -If Helm reports HPA scale field conflicts, rerun the same command with `-ForceUpgrade`. diff --git a/docs/runbooks/deployment.md b/docs/runbooks/deployment.md new file mode 100644 index 0000000..a5b5a75 --- /dev/null +++ b/docs/runbooks/deployment.md @@ -0,0 +1,60 @@ +# Руководство по развертыванию (Deployment) + +Контакт-центр поддерживает два основных сценария развертывания на серверах: упрощенный (через Docker Compose для небольших стендов) и enterprise-grade (через Kubernetes и Helm для продакшена). + +## 1. Требования к Production-окружению + +Для запуска в боевых условиях (Scale Profile) **строго обязателен PostgreSQL**. Запуск на SQLite в Production категорически не поддерживается. +Также для связи микросервисов в продакшене требуется запущенный кластер **RabbitMQ**. + +## 2. Развертывание через серверный Docker Compose (Test / Stage) + +Если у вас один выделенный сервер (VPS/VM Linux) и вы хотите настроить автоматический деплой из GitLab: + +Готовые скрипты находятся в `scripts/`: +- `install_gitlab_runner.sh` — регистрирует раннер на сервере. +- `bootstrap_gitlab_deploy.sh` — подготавливает базовые папки и .env-файлы. +- `deploy_gitlab.sh` — сам скрипт деплоя. + +В директории `deployment/` лежат файлы конфигураций для сервера: +- `docker-compose.server.registry.yml` — тянет собранные образы напрямую из GitLab Container Registry. +- `.env.production.template` — шаблон боевых секретов, включая `APP_TOKEN_SECRET` и `OIDC_...` для Keycloak. + +**Как обновиться (CI/CD Pipeline):** +GitLab CI (файл `.gitlab-ci.yml`) собирает Docker-образы для каждого микросервиса (`services/*`) при merge в ветку `main` и перезапускает Compose-файл на вашем выделенном сервере. + +## 3. Развертывание в Kubernetes (On-premise / Cloud Production) + +Для крупных внедрений контакт-центра (когда нужен автомасштаб `ai_voice_runtime_service` или `routing-service` при высокой нагрузке) используется встроенный Helm-чарт. + +Исходники манифестов лежат в: +- `deployment/helm/` — основной Helm Chart микросервисной платформы. +- `deployment/kubernetes/` — статические манифесты. + +### 3.1. Установка Helm чарта +Для установки чарта в кластер перейдите в директорию `deployment/helm` и выполните: +```bash +# 1. Создание отдельного namespace +kubectl create namespace mvp-cc + +# 2. Установка/обновление платформы +helm upgrade --install mvp-cc-prod ./ \ + --namespace mvp-cc \ + -f values.yaml \ + -f values.prod.yaml +``` + +### 3.2. Масштабирование (Scale Profiles) +Шаблоны Helm поддерживают включение профилей нагрузки. Например, для запуска `scale500` (профиль на 500 одновременных линий/пользователей): +- Отключите встроенные SQLite-заглушки во `values.yaml`. +- Строго пропишите внешний `DATABASE_URL` до высокодоступного кластера PostgreSQL. +- Настройте пулинг коннектов: + - `DB_POOL_SIZE` + - `DB_MAX_OVERFLOW` + +## 4. Резервное копирование и Откат конфигураций + +Даже при работе в Kubernetes базу данных (PostgreSQL) желательно держать вне кластера (Managed Database/Patroni). + +**Бэкапы аудиозаписей:** +Если вы используете `recording-service` с локальным хранением, смонтированные PVC (Persistent Volume Claims) с аудиозаписями нужно регулярно копировать. В инфраструктуре без K8s для этого предусмотрен скрипт `scripts/backup_data.ps1`. diff --git a/docs/runbooks/event-bus-local.md b/docs/runbooks/event-bus-local.md deleted file mode 100644 index 758a9be..0000000 --- a/docs/runbooks/event-bus-local.md +++ /dev/null @@ -1,83 +0,0 @@ -# Runbook - Event Bus (Track 8, Local) - -## Goal - -Run the first RabbitMQ-backed event bus flow locally and verify: - -- outbox writes happen in business services -- `event-bus-service` publishes events -- `audit-service` consumes them -- `reporting-service` consumes them - -## Local compose path - -Use Docker Compose because it includes RabbitMQ: - -```powershell -cd deployment -docker compose up -d -cd .. -python scripts\migrate_core_db.py -``` - -RabbitMQ management UI: - -- `http://localhost:15672` -- default local credentials: `guest / guest` - -## Required env - -The compose profile already sets: - -- `EVENT_BUS_ENABLED=1` -- `EVENT_BUS_URL=amqp://guest:guest@rabbitmq:5672/` -- `EVENT_BUS_CONSUMER_ENABLED=1` - -For non-compose local runs, export them manually before starting services. - -## Smoke check - -```powershell -python scripts\event_bus_smoke.py --base-url http://localhost:8080 -``` - -Expected: - -- gateway and `event-bus-service` are healthy -- an interaction is created -- its outbox event becomes `published` -- `audit-service` processes the event -- `reporting-service` processes the event - -## Inspect outbox - -```powershell -curl http://localhost:8080/proxy/event-bus/bus/outbox -H "X-User: admin" -H "X-Role: admin" -``` - -Useful statuses: - -- `pending` -- `published` -- `failed` - -## Retry a failed event - -```powershell -curl -X POST http://localhost:8080/proxy/event-bus/bus/outbox//retry -H "X-User: admin" -H "X-Role: admin" -``` - -## Acceptance check - -```powershell -python scripts\track8_check.py --base-url http://localhost:8080 -``` - -## Compatibility mode - -If you want to disable the bus and keep the platform on pure HTTP-only behavior: - -- set `EVENT_BUS_ENABLED=0` -- restart services - -The business REST APIs keep working in that mode. diff --git a/docs/runbooks/gitlab-cicd.md b/docs/runbooks/gitlab-cicd.md deleted file mode 100644 index 92ca539..0000000 --- a/docs/runbooks/gitlab-cicd.md +++ /dev/null @@ -1,80 +0,0 @@ -# GitLab CI/CD for call-center - -## What this setup does - -- a push to `main` triggers GitLab CI -- the job runs on a dedicated `shell` runner with tag `call-center-prod` -- the runner syncs the repository into `/home/gitlab-runner/deploy/call-center` -- Docker builds `call-center-app:` and also tags `call-center-app:latest` -- `docker compose` recreates the stack from `deployment/docker-compose.server.yml` -- the public gateway container is named `call-center-app` - -## Required server prerequisites - -- Docker installed and running -- outbound access to `https://gitlab.konturai.kz` -- runner token from GitLab with prefix `glrt-...` or a valid project/group runner token - -## Install and register the runner - -Run on the target server as `root`: - -```bash -cd /path/to/call-center -RUNNER_TOKEN=glrt-xxxxxxxx bash scripts/install_gitlab_runner.sh -``` - -The script: - -- installs `gitlab-runner` from the official GitLab repository -- adds user `gitlab-runner` to the `docker` group -- creates deploy directory `/home/gitlab-runner/deploy/call-center` -- registers runner `call-center-prod-runner` with tag `call-center-prod` - -If you first want to install the service without registration: - -```bash -SKIP_REGISTER=1 bash scripts/install_gitlab_runner.sh -``` - -## Production environment file - -Create the production env file once on the server: - -```bash -install -m 600 /dev/null /home/gitlab-runner/deploy/call-center/.env.production -``` - -Then fill it with the values required by `deployment/docker-compose.server.yml`. - -If the project is already running from `/root/call-center`, migrate the current env file and SQLite/files before the first CI deploy: - -```bash -bash scripts/bootstrap_gitlab_deploy.sh -``` - -Alternative: - -- keep the env file elsewhere -- pass `DEPLOY_ENV_FILE=/absolute/path/to/.env.production` in GitLab CI/CD variables - -## GitLab CI/CD variables - -Optional project variables: - -- `DEPLOY_DIR` if you want a different deploy directory -- `APP_IMAGE_NAME` if you want a different Docker image name -- `HEALTHCHECK_URL` if the gateway health URL differs -- `DEPLOY_ENV_FILE` if `.env.production` should be copied from another location - -## First deployment - -1. Register the runner. -2. Add the production env file. -3. Push this configuration to the `main` branch. -4. Confirm the pipeline completes successfully. -5. Verify the container: - -```bash -docker ps --filter name=call-center-app -``` diff --git a/docs/runbooks/gitlab-server-deploy.md b/docs/runbooks/gitlab-server-deploy.md deleted file mode 100644 index c42cfcf..0000000 --- a/docs/runbooks/gitlab-server-deploy.md +++ /dev/null @@ -1,86 +0,0 @@ -# GitLab -> Server Deploy - -This project is prepared for a GitLab-first delivery flow: - -1. Push source code to GitLab. -2. GitLab CI runs tests. -3. GitLab CI builds and pushes container images to GitLab Container Registry. -4. The server only receives the deployment bundle and pulls images from the registry. - -For a same-host shell-runner flow that rebuilds `call-center-app` directly on push to `main`, see: - -- `docs/runbooks/gitlab-cicd.md` - -## Files used - -- `.gitlab-ci.yml` -- `deployment/docker-compose.server.registry.yml` -- `deployment/docker-compose.asterisk.server.registry.yml` -- `deployment/.env.images.example` - -## GitLab CI variables - -Required for image publishing: - -- `CI_REGISTRY` -- `CI_REGISTRY_USER` -- `CI_REGISTRY_PASSWORD` -- `CI_REGISTRY_IMAGE` - -Required for the optional deploy job: - -- `DEPLOY_HOST` -- `DEPLOY_USER` -- `DEPLOY_PATH` -- `DEPLOY_SSH_PRIVATE_KEY` - -`DEPLOY_PATH` should point to a minimal runtime directory on the server, for example: - -```text -/opt/call-center -``` - -The server runtime directory should contain: - -- `.env.production` -- `.data_local/` -- `.asterisk_assets/` (can stay empty) -- `deployment/` - -## Server deploy bundle - -The server no longer needs the full project checkout. It only needs: - -- `deployment/` -- `.env.production` -- `.data_local/` -- `.asterisk_assets/` if you use custom prompt files - -The app image is resolved through `APP_IMAGE`. -The Asterisk image is resolved through `ASTERISK_IMAGE`. - -Store them in `deployment/.env.images`, for example: - -```text -APP_IMAGE=registry.gitlab.example.com/group/project/app: -ASTERISK_IMAGE=registry.gitlab.example.com/group/project/asterisk: -``` - -## Manual server deploy - -From the server: - -```bash -cd /opt/call-center/deployment -docker login -docker compose --env-file .env.images -f docker-compose.server.registry.yml pull -docker compose --env-file .env.images -f docker-compose.server.registry.yml up -d -docker compose --env-file .env.images -f docker-compose.asterisk.server.registry.yml pull -docker compose --env-file .env.images -f docker-compose.asterisk.server.registry.yml up -d -``` - -## Notes - -- `deployment/docker-compose.server.yml` is still the source-build variant. -- `deployment/docker-compose.server.registry.yml` is the registry/pull-only variant for the server. -- The deploy job in `.gitlab-ci.yml` is manual on the default branch by design. diff --git a/docs/runbooks/ivr-local.md b/docs/runbooks/ivr-local.md deleted file mode 100644 index 0400c3b..0000000 --- a/docs/runbooks/ivr-local.md +++ /dev/null @@ -1,102 +0,0 @@ -# IVR Local Runbook - -Use this runbook to validate both the preview IVR runtime and the live Asterisk-backed IVR staging path. - -## What Track 5 adds - -- `ivr-service` for IVR flow CRUD and DTMF runtime sessions -- one active IVR flow per voice queue -- `routing-service` support for `ivr_session_id` -- admin-side IVR preview flow in `/admin` - -## Preview baseline vs live IVR - -- Preview baseline: use the `/admin` IVR block to create flows, start sessions, send DTMF, and preview route overrides without telephony. -- Live Asterisk IVR: call `7200` on the lab PBX after `asterisk-bridge-service` is configured with `ASTERISK_IVR_FASTAGI_*` and the flow nodes have valid `prompt_audio_key` values. - -## Create a flow - -1. Open `http://localhost:8080/admin` -2. Go to the `IVR` block -3. Set: - - `queue_id` - - `entry_node_id` (`root`) - - `flow_json` -4. Click `Create flow` - -The flow JSON is validated before it is saved. - -## Start a session - -1. In the same `IVR` block, set: - - `call_id` - - `queue_id` - - optional `interaction_id` -2. Click `Start session` - -The response returns: - -- `session_id` -- `current_node_id` -- current node prompt metadata - -## Send DTMF - -1. Enter a single digit (`0`-`9`) -2. Click `Send DTMF` - -If the transition reaches a terminal node: - -- the session becomes `completed` -- `outcome_code` is set -- `resolved_queue_id` is set -- `resolved_queue_code` is set -- a `voice_event` with `event_type="ivr.completed"` is written - -## Preview route override - -1. Keep the completed `session_id` -2. Click `Preview IVR route` - -This calls `routing-service` with `ivr_session_id` and shows: - -- `original_queue_id` -- `resolved_queue_id` -- `ivr_outcome_code` - -## Demo seed - -`scripts/prepare_demo.ps1` now seeds: - -- one demo IVR flow -- one completed demo IVR session -- one route preview stored in `.local_stack/demo-seed-summary.json` - -Backstage fields: - -- `ivr_flow_id` -- `ivr_session_id` -- `ivr_outcome_code` -- `ivr_resolved_queue_id` - -## Live Asterisk smoke - -1. Enable: - - `ASTERISK_BRIDGE_ENABLED=1` - - `ASTERISK_IVR_FASTAGI_ENABLED=1` - - `IVR_RUNTIME_TRUSTED_SERVICE_SUBJECTS=svc:asterisk-bridge` -2. Make sure `ASTERISK_QUEUE_MAP_JSON` contains `voice_lab_ivr`. -3. Provision the `prompt_audio_key` sound files in Asterisk. -4. Call `7200` and verify: - - DTMF completes the IVR and transfers through `mvpcc-transfer` - - silence triggers `ivr.step.no_input` - - retry exhaustion abandons the IVR session and falls back to the human baseline - -## Troubleshooting - -- `404 No active IVR flow for queue` - - create or activate a flow for the target queue first -- `400 Digit must be a single character 0-9` - - send exactly one DTMF digit -- `400 IVR session is not completed` on route preview - - finish the session with a terminal node before previewing routing diff --git a/docs/runbooks/keycloak-oidc.md b/docs/runbooks/keycloak-oidc.md deleted file mode 100644 index 40f1094..0000000 --- a/docs/runbooks/keycloak-oidc.md +++ /dev/null @@ -1,128 +0,0 @@ -# Runbook - Keycloak OIDC (Wave 2 Track 1) - -## Scope - -This runbook covers the enterprise identity baseline introduced in Wave 2 Track 1: - -- Keycloak-backed OIDC login -- signed application bearer tokens -- controlled fallback to local login - -It is for QA and enterprise-like environments, not the default demo flow. - -## Modes - -### Local / demo - -- `OIDC_ENABLED=0` -- `ALLOW_LEGACY_HEADER_AUTH=1` - -Use this when: - -- running local demos -- using seeded local users -- preserving existing Stage 1-4 scripts - -### QA transition - -- `OIDC_ENABLED=1` -- `ALLOW_LEGACY_HEADER_AUTH=1` - -Use this when: - -- validating OIDC without breaking old header-based scripts -- checking dual-mode compatibility before cutover - -### Enterprise pilot - -- `OIDC_ENABLED=1` -- `ALLOW_LEGACY_HEADER_AUTH=0` - -Use this when: - -- OIDC is the primary login path -- bearer-token validation is enforced end-to-end - -## Required configuration - -- `APP_TOKEN_SECRET` -- `APP_TOKEN_TTL_SECONDS` (default `3600`) -- `OIDC_ENABLED` -- `OIDC_PROVIDER=keycloak` -- `OIDC_ISSUER_URL` -- `OIDC_CLIENT_ID` -- `OIDC_CLIENT_SECRET` -- `OIDC_REDIRECT_URI` -- `OIDC_SCOPES` (default `openid profile email`) -- `OIDC_ROLE_CLAIM` (default `groups`) -- `OIDC_ROLE_MAP_JSON` -- `ALLOW_LEGACY_HEADER_AUTH` - -Default role map: - -```json -{ - "kc_admin": "admin", - "kc_supervisor": "supervisor", - "kc_operator": "operator", - "kc_analyst": "analyst" -} -``` - -## Keycloak client settings - -Recommended client type: - -- confidential client -- standard authorization code flow enabled -- PKCE enabled - -Redirect URI: - -- `https:///proxy/auth/auth/oidc/callback` - -Web origins: - -- `https://` - -## Validation flow - -1. Apply migrations: - - `python scripts/migrate_core_db.py` -2. Start the stack with Track 1 env vars. -3. Check OIDC endpoints: - - `python scripts/oidc_smoke.py --base-url http:// --require-enabled` -4. Open: - - `http:///operator` -5. Click `Корпоративный вход`. -6. Complete Keycloak login. -7. Confirm the operator UI receives a session and continues using bearer auth. - -## Failure handling - -### OIDC health fails - -- verify `OIDC_ISSUER_URL` -- verify Keycloak realm is reachable from the auth-service -- verify TLS and reverse proxy settings - -### Login returns `403` - -- verify user groups in Keycloak -- verify `OIDC_ROLE_MAP_JSON` -- verify `OIDC_ROLE_CLAIM` matches the actual claim in the ID token - -### Existing scripts stop working - -- in QA only, temporarily set `ALLOW_LEGACY_HEADER_AUTH=1` -- in enterprise pilot, do not revert silently; update the script/client to bearer auth or use documented break-glass local login only - -## Break-glass local access - -Local `/auth/login` remains available in Track 1 for: - -- local development -- demo mode -- break-glass admin access - -It is not the primary path in enterprise mode. diff --git a/docs/runbooks/load-test-plan.md b/docs/runbooks/load-test-plan.md deleted file mode 100644 index 40219ca..0000000 --- a/docs/runbooks/load-test-plan.md +++ /dev/null @@ -1,20 +0,0 @@ -# Load Test Plan (Stage 4) - -## Target -- 100 concurrent voice sessions -- 100 concurrent digital sessions -- Script entrypoint: - - `python scripts/load_test.py --base-url http://localhost:8080 --voice 100 --digital 100` - - or the profile-based equivalent: - - `python scripts/load_test.py --base-url http://localhost:8080 --profile baseline_100_100` - -## Metrics -- API error rate -- P95 response latency -- queue processing delay -- service availability during load - -## Success criteria -- No critical errors -- Stable response times under threshold -- No data loss in interaction lifecycle diff --git a/docs/runbooks/local-run.md b/docs/runbooks/local-run.md deleted file mode 100644 index c6c1c5f..0000000 --- a/docs/runbooks/local-run.md +++ /dev/null @@ -1,65 +0,0 @@ -# Runbook - Local Startup - -1. Install dependencies: - - `python -m pip install -r requirements.txt` -2. Run tests: - - `pytest -q` -3. SQLite local stack: - - `python scripts/local_stack.py start` -4. PostgreSQL local stack: - - start PostgreSQL and RabbitMQ locally - - `python scripts/postgres_dev_preflight.py --env-file .env.postgres.local.template` - - `python scripts/migrate_core_db.py` - - `python scripts/local_stack.py start --env-file .env.postgres.local.template` - - `python scripts/postgres_dev_preflight.py --env-file .env.postgres.local.template --base-url http://127.0.0.1:8080` -5. Gateway URL: - - `http://localhost:8080` -6. Check health: - - `GET /proxy/auth/health` -7. Run Gate 1 quick check: - - `python scripts/gate1_check.py` -8. Run live Gate 1/2 smoke with auto-started services: - - `python scripts/live_smoke_gate12.py` - - PostgreSQL mode: `python scripts/live_smoke_gate12.py --database-url postgresql://mvp:mvp@localhost:5432/mvpcc` -9. Run Gate 3 check: - - Existing running stack: `python scripts/gate3_check.py` - - Auto-start local stack: `python scripts/gate3_check.py --auto-start` -10. Run Gate 4 automated checks: - - `python scripts/gate4_check.py` - - Keep artifacts: `python scripts/gate4_check.py --keep-artifacts` -11. Run UAT preflight: - - Existing running stack: `python scripts/uat_preflight.py --base-url http://localhost:8080` - - Auto-start local stack: `python scripts/uat_preflight.py --auto-start` -12. Run UAT dry-run package: - - Existing running stack: `python scripts/uat_dry_run.py --base-url http://localhost:8080 --update-defect-register` - - Auto-start local stack: `python scripts/uat_dry_run.py --auto-start --update-defect-register` -13. Optional OIDC smoke-check: - - `python scripts/oidc_smoke.py --base-url http://localhost:8080` - - Require enterprise path enabled: `python scripts/oidc_smoke.py --base-url http://localhost:8080 --require-enabled` -14. Open operator UI: - - `http://localhost:8080/operator` -15. Optional dedicated Wave 2 shells: - - `http://localhost:8080/supervisor` - - `http://localhost:8080/admin` -16. Reset local runtime artifacts when needed: - - `powershell -ExecutionPolicy Bypass -File scripts\clean_workspace.ps1` - -## Runtime artifacts - -- `.data*`, `.local_stack`, and `.artifacts` are regeneratable local runtime directories. -- PostgreSQL local profile keeps runtime artifacts in `.data_local_pg` by default. -- Use `scripts/clean_workspace.ps1` to clear local DBs, logs, cached recordings, and test/runtime leftovers without touching source files or backups. - -## Local auth modes - -- Demo/dev default: - - `OIDC_ENABLED=0` - - `ALLOW_LEGACY_HEADER_AUTH=1` -- Enterprise-like local validation: - - set `OIDC_ENABLED=1` - - set `ALLOW_LEGACY_HEADER_AUTH=0` - - configure `APP_TOKEN_SECRET`, `OIDC_ISSUER_URL`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET`, `OIDC_REDIRECT_URI` - -See also: - -- [keycloak-oidc.md](/e:/Zhan/docs/runbooks/keycloak-oidc.md) diff --git a/docs/runbooks/local-setup.md b/docs/runbooks/local-setup.md new file mode 100644 index 0000000..403fd32 --- /dev/null +++ b/docs/runbooks/local-setup.md @@ -0,0 +1,94 @@ +# Руководство по локальному запуску (Local Setup) + +В этом документе описаны шаги для поднятия проекта на рабочей машине разработчика или для проведения презентации (демо). Проект поддерживает как легковесный запуск на SQLite, так и масштабируемый запуск с использованием PostgreSQL и RabbitMQ. + +## 1. Системные требования +- **Python 3.10+** (для микросервисов) +- **Docker и Docker Compose** (для запуска PostgreSQL, RabbitMQ и т.д.) +- **PowerShell** (основные launch-скрипты написаны на нём) + +## 2. Быстрый старт: Режим ДЕМО (Автоматически) + +Самый удобный способ поднять весь стек контакт-центра одной командой. Скрипт запустит сервисы, прогонит смоук-тесты и заполнит базу моковыми данными: + +```powershell +powershell -ExecutionPolicy Bypass -File scripts\prepare_demo.ps1 +``` + +**Что делает `prepare_demo.ps1`**: +- Поднимает все микросервисы проекта в фоне (логи складываются в папку `.local_stack\logs`). +- Создает тестового клиента и историю его взаимодействия. +- Симулирует звонок с IVR и создает запись в `recording-service`. +- Наливает статьи Базы Знаний. +- Забрасывает метрики для KPI отчетов в `reporting-service`. + +**Остановка и очистка:** +```powershell +# Остановить все фоновые процессы сервисов +powershell -ExecutionPolicy Bypass -File scripts\stop_all_local.ps1 + +# Полностью очистить рабочее пространство сервера (удалить временные БД и артефакты) +powershell -ExecutionPolicy Bypass -File scripts\clean_workspace.ps1 +``` + +## 3. Ручной запуск (Local Development) + +Если вы разрабатываете конкретный микросервис и вам нужен горячий рестарт (Hot Reload), поднимайте сервисы вручную. + +### Шаг 3.1: Установка зависимостей +```bash +python -m pip install -r requirements.txt +``` + +### Шаг 3.2: Запуск инфраструктуры (Docker) +Если вы не хотите использовать fallback-режим с SQLite, поднимите PostgreSQL и системные сервисы: +```bash +cd deployment +docker compose up -d postgres +cd .. +``` + +### Шаг 3.3: Миграции Базы Данных +Перед запуском бизнес-логики необходимо проинициализировать схему БД: +```bash +python scripts/migrate_core_db.py +``` + +### Шаг 3.4: Запуск Gateway и Микросервисов +Для маршрутизации запросов обязательно должен работать `api-gateway`: +```bash +uvicorn gateway.app:app --reload --port 8080 +``` +Далее в новых консолях вы можете точечно поднимать необходимые вам микросервисы (по умолчанию Gateway проксирует запросы на порты `8001`, `8004` и т.д.): +```bash +uvicorn services.auth_service.app:app --reload --port 8001 +uvicorn services.interaction_service.app:app --reload --port 8004 +``` + +## 4. Точки входа (Пользовательские интерфейсы) + +Если `gateway.app` успешно запущен на порту 8080, вы можете попасть в веб-интерфейсы платформы по следующим адресам: + +- **Рабочее место Оператора**: [http://localhost:8080/operator](http://localhost:8080/operator) +- **Панель Супервизора**: [http://localhost:8080/supervisor](http://localhost:8080/supervisor) +- **Панель Администратора**: [http://localhost:8080/admin](http://localhost:8080/admin) +- **Аналитика (Дашборды)**: [http://localhost:8080/analyst](http://localhost:8080/analyst) + +## 5. Полезные скрипты + +В папке `scripts/` лежит множество полезных инструментов для разработки: + +```bash +# Прогон всех автотестов +pytest -q + +# Смоук-чек авторизации (проверка выдачи JWT токенов) +python scripts/oidc_smoke.py --base-url http://localhost:8080 + +# Снятие и распаковка бэкапа (дамп данных лок. среды) +powershell -ExecutionPolicy Bypass -File scripts\backup_data.ps1 +powershell -ExecutionPolicy Bypass -File scripts\restore_data.ps1 -BackupZip e:\Zhan\backups\mvp_cc_data_YYMMDD.zip + +# Нагрузочное тестирование (позволяет сгенерировать фейковый трафик звонков) +python scripts/load_test.py --base-url http://localhost:8080 --profile baseline_100_100 +``` diff --git a/docs/runbooks/management-demo-brief.md b/docs/runbooks/management-demo-brief.md deleted file mode 100644 index 84a5481..0000000 --- a/docs/runbooks/management-demo-brief.md +++ /dev/null @@ -1,39 +0,0 @@ -# Management Demo Brief - -Use this note during the meeting so the demo stays short and clear. - -## One-minute version - -1. Open `http://localhost:8080/operator`. -2. Log in as `admin / admin123`. -3. Show one closed interaction and one active interaction. -4. Show the supervisor summary. -5. Show the KPI summary. - -## Three-minute version - -1. Show the demo checklist at the top of the screen. -2. Explain that the stand is started by one command and loaded with demo data. -3. Open customers and interactions. -4. Show assignment, transfer to second line, and closing. -5. Trigger the voice, Telegram, Webchat, and Email channel checks. -6. Search KB for `demo-showcase`. -7. Show supervisor and KPI summary cards. - -## What to say by block - -- Session: role-based access and a working login flow. -- Customers: the platform stores and finds client cards. -- Interactions: the request can be created, assigned, escalated, and closed. -- Integrations: the system accepts voice, Telegram, Webchat, and Email events. -- Knowledge Base: the operator can quickly find a prepared answer. -- Supervisor: the lead sees current queue and agent state. -- KPI: the system already calculates core operational metrics. - -## Five short answers - -- What is ready: MVP with operator flow, integrations, KB, supervisor, KPI, and the first Wave 2 webchat/email channels. -- What is not yet final: this is a pilot-ready MVP, not a full production rollout. -- What is the stack: Python/FastAPI backend, browser UI, microservice layout. -- What about telephony: the voice integration contour is ready and demonstrated by event intake. -- What is next: a separate Wave 2 with SSO, extra channels, expanded admin UX, and scaling. diff --git a/docs/runbooks/pilot-uat.md b/docs/runbooks/pilot-uat.md deleted file mode 100644 index 67d0463..0000000 --- a/docs/runbooks/pilot-uat.md +++ /dev/null @@ -1,88 +0,0 @@ -# Runbook - Pilot UAT - -## Participants - -- Operators -- Supervisor -- Analyst -- Admin -- Business owner -- IT owner - -## Scope Rules - -- Freeze the pilot scope using `docs/gates/mvp-pilot-baseline.md`. -- Do not record out-of-scope feature requests as MVP defects. -- Route deferred requests to `docs/roadmap/05-wave2-backlog.md`. - -## Preparation - -1. Run environment preflight: - - Existing environment: `python scripts/uat_preflight.py --base-url http://:8080` - - Local auto-start: `python scripts/uat_preflight.py --auto-start` -2. Run UAT dry-run package generator: - - Existing environment: `python scripts/uat_dry_run.py --base-url http://:8080 --update-defect-register` - - Local auto-start: `python scripts/uat_dry_run.py --auto-start --update-defect-register` -3. Create session protocol from template or generated artifact: - - `docs/uat/session-template.md` - - Recommended bundle generator: - - `python scripts/uat_manual_prepare.py --environment-url http://:8080` -4. Prepare defect log file: - - `docs/uat/defect-log-template.csv` -5. Confirm the pilot gateway URL and the build under test in the session protocol. - -## Mandatory scenarios - -- Execute all scenarios from: - - `docs/uat/scenario-checklist.md` -- Required scope: - - auth login and RBAC deny - - customer create/search - - voice lifecycle - - assignment and escalation - - routing and timeline verification - - voice event intake - - Telegram lifecycle - - supervisor realtime - - KPI report and CSV - - KB usage in handling flow - - backup/restore verification - - load test baseline (`100 voice + 100 digital`) - -## Defect triage - -- `P1`: acceptance impossible, fix in MVP immediately -- `P2`: key journey broken, fix in MVP immediately -- `P3`: acceptable with limitation, defer unless it blocks sign-off -- `P4`: cosmetic or backlog-only - -Only `P1` and `P2` defects belong to the MVP remediation cycle. - -## Evidence package - -- Preflight report from `docs/uat/evidence/` -- Completed scenario checklist -- Completed session protocol -- Filled defect log -- Signed sheet from `docs/uat/signoff-template.md` -- Updated `docs/gates/p1-p2-defects.md` - -## Formal closure - -After manual signatures are collected and no open `P1/P2` items remain: - -- Optional validation only: - - `python scripts/finalize_mvp_pilot.py --session-dir docs/uat/evidence/manual_ --dry-run` -- Run: - - `python scripts/finalize_mvp_pilot.py --session-dir docs/uat/evidence/manual_` -- This updates: - - `docs/gates/gate-04-pilot-hardening.md` - - `docs/gates/p1-p2-defects.md` - - `docs/releases/v1.0.0-mvp-accepted.md` - -## Exit criteria - -- No open P1 defects -- No open P2 defects -- Critical user journeys approved -- Sign-off captured from business and IT owners diff --git a/docs/runbooks/postgres-dev-cutover.md b/docs/runbooks/postgres-dev-cutover.md deleted file mode 100644 index cf2da55..0000000 --- a/docs/runbooks/postgres-dev-cutover.md +++ /dev/null @@ -1,275 +0,0 @@ -# PostgreSQL Dev Cutover - -Этот runbook описывает безопасный перевод `dev`-окружения с SQLite на PostgreSQL без переноса старых данных. - -## Что считается "без простоя" - -В этом проекте без переноса SQLite-данных безостановочный cutover означает не hot-swap одной и той же базы, а: - -1. поднять новый PostgreSQL-backed stack параллельно; -2. прогнать smoke на новом stack; -3. переключить dev-трафик на новый gateway; -4. оставить старый SQLite stack живым до подтверждения GO; -5. при проблеме вернуть трафик обратно без восстановления данных. - -Если сейчас у dev только один instance и нет возможности держать parallel stack, нужен короткий maintenance window. - -## Scope - -Включает: - -- подготовку PostgreSQL и RabbitMQ; -- применение SQL migrations; -- запуск сервисов в `SCHEMA_MANAGEMENT_MODE=migrations`; -- smoke-проверки до и после переключения; -- rollback на старый SQLite-backed dev stack. - -Не включает: - -- перенос данных из SQLite; -- cleanup legacy SQLite-path; -- production cutover. - -## Required Inputs - -- `DEV_GATEWAY_OLD` -- `DEV_GATEWAY_NEW` -- `POSTGRES_DATABASE_URL` -- `APP_TOKEN_SECRET` -- `PUBLIC_SWITCH_METHOD` - - DNS - - reverse proxy - - load balancer route -- `RABBITMQ_URL` - -Рекомендуемые значения для локально-управляемого dev: - -- `POSTGRES_DATABASE_URL=postgresql://mvp:mvp@:5432/mvpcc` -- `SCHEMA_MANAGEMENT_MODE=migrations` -- env template: [\.env.postgres.local.template](/e:/Zhan/.env.postgres.local.template) - -## Phase 0 - Preconditions - -Подтвердить перед началом: - -1. Текущий SQLite-backed dev stack стабилен. -2. Перенос данных не нужен. -3. PostgreSQL доступен по сети с host, где запускаются сервисы. -4. RabbitMQ доступен по сети, если в dev нужен `EVENT_BUS_ENABLED=1`. -5. Новая dev-конфигурация использует: - - `DATABASE_URL=postgresql://...` - - `SCHEMA_MANAGEMENT_MODE=migrations` -6. Старый SQLite stack не останавливается до завершения smoke на новом stack. - -## Phase 1 - Preflight - -На будущем PostgreSQL-backed dev host: - -1. Проверить Python зависимости: - -```powershell -python -m pip install -r requirements.txt -``` - -2. Подготовить env-файл: - - скопировать [\.env.postgres.local.template](/e:/Zhan/.env.postgres.local.template) - - заполнить `DATABASE_URL`, `APP_TOKEN_SECRET`, интеграционные URL и при необходимости AI/Telegram/WhatsApp переменные - -3. Запустить DB-only preflight: - -```powershell -python scripts\postgres_dev_preflight.py --env-file .env.postgres.local.template -``` - -4. Убедиться, что mode не legacy: - -```text -SCHEMA_MANAGEMENT_MODE=migrations -``` - -5. Применить миграции в PostgreSQL: - -```powershell -python scripts\migrate_core_db.py -``` - -Ожидается: - -- миграции применились без ошибок; -- в PostgreSQL появилась таблица `schema_migrations`; -- сервисы ещё не стартовали. - -## Phase 2 - Bring Up Parallel Stack - -Поднять новый stack параллельно старому. - -Если запуск локальный: - -```powershell -python scripts\local_stack.py start ` - --runtime-dir .local_stack_pg ` - --env-file .env.postgres.local.template ` - --force-restart -``` - -Если это dev server / VM: - -1. развернуть те же сервисы в отдельный deployment set; -2. прокинуть им: - - `DATABASE_URL=postgresql://...` - - `SCHEMA_MANAGEMENT_MODE=migrations` -3. не направлять внешний dev-трафик на новый gateway до завершения smoke. - -Важно: - -- новый stack должен использовать отдельный runtime/log directory; -- старый SQLite-backed stack остаётся доступным; -- нельзя смешивать новый dev-трафик со старым gateway до smoke. - -## Phase 3 - Smoke Before Switch - -Выполнить проверки на `DEV_GATEWAY_NEW`. - -Сначала прогнать HTTP-aware preflight: - -```powershell -python scripts\postgres_dev_preflight.py ` - --env-file .env.postgres.local.template ` - --base-url http://127.0.0.1:8080 -``` - -1. Health: - -```powershell -python scripts\live_smoke_gate12.py --base-url http:// --database-url -``` - -2. Focused PostgreSQL smoke: - -```powershell -pytest -q tests/test_postgres_readiness.py -``` - -3. Дополнительно проверить вручную: - -- `GET /proxy/auth/health` -- `GET /proxy/interaction/health` -- `GET /proxy/voice/health` -- `GET /proxy/ai-voice-runtime/health` -- открыть `http:///operator` - -4. Если в dev включён event bus: - -- проверить подключение к RabbitMQ; -- убедиться, что `event-bus-service` поднимается в `healthy`; -- убедиться, что новые записи появляются в `event_outbox`. - -GO в следующую фазу только если: - -- health green; -- smoke green; -- новый stack не пытается auto-create schema на старте; -- нет ошибок вида `run python scripts/migrate_core_db.py` после фактического применения миграций. - -## Phase 4 - Traffic Switch - -Переключение выполняется только после успешного Phase 3. - -Рекомендуемый порядок: - -1. уменьшить TTL у dev DNS / подготовить proxy route заранее; -2. переключить `DEV_GATEWAY` на `DEV_GATEWAY_NEW`; -3. не останавливать старый SQLite stack; -4. сразу после switch выполнить быстрый post-switch smoke. - -Примеры post-switch smoke: - -```powershell -python scripts\live_smoke_gate12.py --base-url http:// --database-url -``` - -Проверить руками: - -- логин; -- создание interaction; -- digital thread/message flow; -- voice event ingest; -- создание voice AI session. - -## Phase 5 - Stabilization Window - -В течение первых 15-30 минут после switch: - -1. наблюдать логи gateway и сервисов; -2. следить за ошибками подключения к PostgreSQL; -3. следить за ошибками `schema mismatch`; -4. следить за ошибками блокировок/unique conflict в voice transcript path; -5. сравнивать user-visible поведение со старым dev stack. - -Если всё стабильно: - -1. объявить GO; -2. зафиксировать, что `dev` теперь PostgreSQL-backed; -3. оставить SQLite stack выключенным, но не удалённым до конца рабочего дня. - -## Rollback - -Rollback делается только через возврат трафика на старый SQLite-backed stack. - -Триггеры rollback: - -- новый gateway не проходит smoke; -- сервисы падают на startup; -- критичный API path broken; -- UI не работает; -- ошибки подключения к PostgreSQL не устраняются быстро; -- event bus / voice path деградирует. - -Шаги rollback: - -1. вернуть `DEV_GATEWAY_PUBLIC` на `DEV_GATEWAY_OLD`; -2. убедиться, что старый SQLite stack всё ещё жив; -3. выполнить быстрый smoke на старом stack; -4. запретить новый трафик на PostgreSQL-backed stack; -5. собрать логи с нового stack. - -Rollback успешен, если: - -- dev UI снова открывается на старом gateway; -- CRUD и smoke снова зелёные; -- команда работает на старом dev без дополнительных действий. - -## Cutover Sheet - -Перед cutover заполнить: - -- время старта; -- кто выполняет switch; -- `DEV_GATEWAY_OLD`; -- `DEV_GATEWAY_NEW`; -- `POSTGRES_DATABASE_URL`; -- результат preflight; -- результат smoke до switch; -- время switch; -- результат smoke после switch; -- GO / ROLLBACK; -- короткий список замечаний. - -## Minimal Command Sequence - -Если нужен самый короткий practical path для dev: - -```powershell -python scripts\migrate_core_db.py -python scripts\postgres_dev_preflight.py --env-file .env.postgres.local.template -python scripts\local_stack.py start --runtime-dir .local_stack_pg --env-file .env.postgres.local.template --force-restart -python scripts\postgres_dev_preflight.py --env-file .env.postgres.local.template --base-url http://127.0.0.1:8080 -pytest -q tests/test_postgres_readiness.py -python scripts\live_smoke_gate12.py --database-url postgresql://mvp:mvp@localhost:5432/mvpcc -``` - -После этого: - -1. переключить dev gateway/public route на новый stack; -2. прогнать smoke ещё раз уже через публичный dev URL; -3. оставить старый SQLite stack как rollback target. diff --git a/docs/runbooks/postgres-server-docker.md b/docs/runbooks/postgres-server-docker.md deleted file mode 100644 index 8a22f69..0000000 --- a/docs/runbooks/postgres-server-docker.md +++ /dev/null @@ -1,88 +0,0 @@ -# Runbook - Shared Docker Host PostgreSQL for call-center - -Use this runbook when the server already hosts other Docker projects and `call-center` still runs on SQLite. This flow creates an isolated PostgreSQL instance for `call-center` only and does not restart the current application stack. - -## Assets - -- Compose file: `deployment/docker-compose.postgres.server.yml` -- Secret env file: `/home/mvpcc/call-center/.env.postgres.server` -- Data directory: `/home/mvpcc/call-center/.data_pg` -- Host-local PostgreSQL port: `127.0.0.1:5434` - -Future application connection string: - -- containers on the same Docker host: `postgresql://mvpcc_app:@call-center-postgres:5432/mvpcc` -- host-local checks: `postgresql://mvpcc_app:@127.0.0.1:5434/mvpcc` - -## Bring-up - -From the repository root on the target server: - -```bash -cd /home/mvpcc/call-center -python3 - <<'PY' -from pathlib import Path -import secrets - -target = Path(".env.postgres.server") -target.write_text( - "POSTGRES_DB=mvpcc\n" - "POSTGRES_USER=mvpcc_app\n" - f"POSTGRES_PASSWORD={secrets.token_urlsafe(32)}\n", - encoding="utf-8", -) -PY -chmod 600 .env.postgres.server -mkdir -p .data_pg -chmod 700 .data_pg -docker compose -f deployment/docker-compose.postgres.server.yml up -d -``` - -## Verification - -Container status and logs: - -```bash -cd /home/mvpcc/call-center -docker compose -f deployment/docker-compose.postgres.server.yml ps -docker compose -f deployment/docker-compose.postgres.server.yml logs postgres --tail 50 -docker exec call-center-postgres pg_isready -U mvpcc_app -d mvpcc -``` - -Host-local connection check without installing `psql` on the server: - -```bash -cd /home/mvpcc/call-center -set -a -. ./.env.postgres.server -set +a -docker run --rm --network host \ - -e PGPASSWORD="$POSTGRES_PASSWORD" \ - postgres:16-alpine \ - psql -h 127.0.0.1 -p 5434 -U "$POSTGRES_USER" -d "$POSTGRES_DB" \ - -Atqc "select current_database(), current_user;" -``` - -Port exposure check: - -```bash -ss -ltn | grep 5434 -``` - -Expected result: - -- the container is `healthy` -- `pg_isready` returns `accepting connections` -- the SQL check returns `mvpcc|mvpcc_app` -- `ss` shows `127.0.0.1:5434`, not `0.0.0.0:5434` - -## Scope guardrails - -Do not do these actions in this step: - -- do not edit `deployment/docker-compose.server.yml` -- do not switch `DATABASE_URL` for the running `call-center` services -- do not run application migrations against the new PostgreSQL instance yet -- do not touch existing PostgreSQL containers used by other projects - -When a second Dockerized `call-center` stack is added later on the same host, connect it to the external network `call-center-postgres_default` and use `call-center-postgres:5432` from the application containers. diff --git a/docs/runbooks/postgres-server-parallel-stack.md b/docs/runbooks/postgres-server-parallel-stack.md deleted file mode 100644 index a7cb616..0000000 --- a/docs/runbooks/postgres-server-parallel-stack.md +++ /dev/null @@ -1,105 +0,0 @@ -# Runbook - Parallel PostgreSQL-backed Stack on a Shared Docker Host - -Use this runbook after the isolated PostgreSQL server from `postgres-server-docker.md` is already running. The goal is to bring up a second `call-center` stack on PostgreSQL without stopping the current SQLite-backed stack on port `8080`. - -## Assets - -- Compose file: `deployment/docker-compose.parallel.server.yml` -- Base env: `/home/mvpcc/call-center-pg/.env.production` -- Override env: `/home/mvpcc/call-center-pg/.env.postgres.parallel.server` -- Runtime data dir: `/home/mvpcc/call-center-pg/.data_pg_parallel` -- Gateway port: `18080` - -This parallel stack intentionally disables active external integrations so it stays passive while the SQLite-backed stack remains live: - -- Telegram bot disabled -- WhatsApp outbound disabled -- Asterisk bridge disabled -- FastAGI disabled -- AI voice disabled - -The parallel stack reaches PostgreSQL over the external Docker network created by `deployment/docker-compose.postgres.server.yml`, using the hostname `call-center-postgres` on port `5432`. - -## Server bring-up - -Prepare a separate build workspace from the current repository checkout: - -```bash -rm -rf /home/mvpcc/call-center-pg -mkdir -p /home/mvpcc/call-center-pg -``` - -Copy the current `.env.production` from the live SQLite stack, then add a PostgreSQL override file: - -```bash -cd /home/mvpcc/call-center-pg -cp /home/mvpcc/call-center/.env.production .env.production -cp .env.postgres.parallel.server.template .env.postgres.parallel.server -``` - -Update `.env.postgres.parallel.server`: - -- replace `` with the real server host or IP -- replace the PostgreSQL password placeholder with the password from `/home/mvpcc/call-center/.env.postgres.server` -- keep `DATABASE_URL` pointed at `call-center-postgres:5432`, not `127.0.0.1:5434` - -Build the image and prepare the runtime directory: - -```bash -cd /home/mvpcc/call-center-pg -mkdir -p .data_pg_parallel -docker compose -f deployment/docker-compose.parallel.server.yml build -``` - -The parallel compose builds and uses the dedicated image tag `call-center-app:pg-parallel`. - -Run migrations before starting the stack: - -```bash -cd /home/mvpcc/call-center-pg -docker compose -f deployment/docker-compose.parallel.server.yml run --rm auth-service python scripts/migrate_core_db.py -``` - -Bring up the PostgreSQL-backed stack: - -```bash -cd /home/mvpcc/call-center-pg -docker compose -f deployment/docker-compose.parallel.server.yml up -d -``` - -## Verification - -Health and status: - -```bash -cd /home/mvpcc/call-center-pg -docker compose -f deployment/docker-compose.parallel.server.yml ps -curl -fsS http://127.0.0.1:18080/health -curl -fsS http://127.0.0.1:18080/proxy/auth/health -curl -fsS http://127.0.0.1:8080/health -``` - -Focused PostgreSQL preflight from inside the parallel stack: - -```bash -cd /home/mvpcc/call-center-pg -docker compose -f deployment/docker-compose.parallel.server.yml run --rm auth-service \ - sh -lc 'python scripts/postgres_dev_preflight.py --database-url "$DATABASE_URL" --base-url http://api-gateway:8000' -``` - -Expected result: - -- the new stack is reachable at `http://:18080` -- the old SQLite-backed stack stays reachable at `http://:8080` -- no host ports except `18080` are added for the parallel stack -- the PostgreSQL-backed containers stay healthy -- application containers talk to PostgreSQL over Docker network `call-center-postgres_default` - -## Scope guardrails - -Do not do these actions in this step: - -- do not stop or restart the existing `call-center` compose project -- do not change `/home/mvpcc/call-center/deployment/docker-compose.server.yml` -- do not reuse the existing `call-center-app:local` image tag for the new stack -- do not re-enable Telegram, WhatsApp, or Asterisk on the parallel stack until cutover is planned diff --git a/docs/runbooks/recordings-local.md b/docs/runbooks/recordings-local.md deleted file mode 100644 index 079ad6c..0000000 --- a/docs/runbooks/recordings-local.md +++ /dev/null @@ -1,65 +0,0 @@ -# Local Recordings Runbook - -Use this flow to validate Track 4 locally. - -## Storage location - -- Default root: `.data_local\recordings` -- Override with `CC_RECORDINGS_DIR` - -Files are copied into a managed layout: - -- `YYYY\MM\DD\rec__` - -## Import flow - -1. Submit a `recording.ready` voice event with: - - `payload.source_path` - - optional `file_name`, `mime_type`, `duration_seconds`, `recorded_at` -2. Import it through: - -```text -POST /proxy/recording/recordings/import-from-voice-event/{event_id} -``` - -You can also register directly by server path: - -```text -POST /proxy/recording/recordings/register -``` - -## Review in supervisor shell - -1. Open `http://localhost:8080/supervisor` -2. Go to `Recordings` -3. Load recordings or import by `voice_event_id` -4. Select a row to open metadata and preview -5. Use: - - `Open` for metadata + playback - - `Download` for file export - - `Archive` to mark the recording as archived - -## Missing file handling - -If the DB row exists but the stored file is gone: - -- content request returns `410 Gone` -- the row status becomes `missing` -- the metadata remains available for diagnosis - -## Limits - -- Default max size: `25 MB` -- Override with `RECORDING_MAX_BYTES` - -## Demo seed - -`scripts\prepare_demo.ps1` now creates: - -- one sample `.wav` file -- one `recording.ready` voice event -- one imported managed recording - -The generated `recording_id` is stored in: - -- `.local_stack\demo-seed-summary.json` diff --git a/docs/runbooks/track10-live-voice-reliability.md b/docs/runbooks/track10-live-voice-reliability.md deleted file mode 100644 index f501c4e..0000000 --- a/docs/runbooks/track10-live-voice-reliability.md +++ /dev/null @@ -1,77 +0,0 @@ -# Runbook - Track 10 Live Voice Reliability - -Use this runbook to validate lifecycle latency after Track 9 integration. - -## Preconditions - -- Platform stack is running. -- Asterisk bridge is connected (`/proxy/asterisk-bridge/asterisk/status`). -- Real lab calls are already made (`1001 -> 7000`). - -## 1) Generate latency report - -```powershell -python scripts\track10_voice_latency_report.py ` - --database-url sqlite:///.data_local/mvp_cc.db ` - --since-hours 8 ` - --breach-mode direct ` - --json-out .artifacts/track10/voice-latency-latest.json -``` - -For post-fix short-window validation use minutes: - -```powershell -python scripts\track10_voice_latency_report.py ` - --database-url sqlite:///.data_local/mvp_cc.db ` - --since-minutes 30 ` - --breach-mode direct ` - --json-out .artifacts/track10/voice-latency-30m.json -``` - -Optional strict gate mode: - -```powershell -python scripts\track10_voice_latency_report.py ` - --database-url sqlite:///.data_local/mvp_cc.db ` - --since-hours 8 ` - --max-started-to-ended-seconds 30 ` - --max-ended-to-recording-seconds 45 ` - --breach-mode direct ` - --fail-on-breach -``` - -## 2) Review report fields - -Mandatory fields: -- `summary.total_calls` -- `summary.active_no_end` -- `summary.ended_no_recording_upload` -- `summary.start_to_end.p95` -- `summary.end_to_recording_upload.p95` -- `summary.ended_direct_calls` -- `summary.ended_reconciled_calls` -- `summary.direct_start_to_end.p95` -- `summary.direct_end_to_recording_upload.p95` - -## 3) Save acceptance evidence - -Create a timestamp folder: - -`docs/acceptance/track10//` - -Place: -- `voice-latency-latest.json` -- command output log -- note with latest call IDs included in report - -## 4) Decision guidance - -GO candidate: -- `active_no_end = 0` -- `ended_no_recording_upload = 0` -- direct p95 metrics within Track 10 thresholds - -NO-GO candidate: -- repeated high latency outside thresholds, -- stale active calls without `call.ended`, -- missing recordings after completed calls. diff --git a/docs/runbooks/track11-live-operator-call-control.md b/docs/runbooks/track11-live-operator-call-control.md deleted file mode 100644 index bb83142..0000000 --- a/docs/runbooks/track11-live-operator-call-control.md +++ /dev/null @@ -1,76 +0,0 @@ -# Runbook - Track 11 Live Operator Call Control - -Use this runbook to verify the accepted call-control path introduced in Track 11. - -For the current operator UX and active/recent call behavior, use `docs/runbooks/track12-operator-voice-ux.md`. - -## Preconditions - -- Track 9 bridge works: `/proxy/asterisk-bridge/asterisk/status` returns `ami_connected=true` -- operator SIP extensions exist in Asterisk: `2001`, `2002` -- bridge env includes: - - `ASTERISK_CALLCONTROL_ENABLED=1` - - `ASTERISK_OPERATOR_EXTENSION_MAP_JSON` - - `ASTERISK_TRANSFER_TARGET_MAP_JSON` - -## 1) Start the QA stack - -```powershell -powershell -ExecutionPolicy Bypass -File scripts\start_track9_qa.ps1 -``` - -Verify: - -```powershell -curl http://127.0.0.1:8080/proxy/asterisk-bridge/asterisk/status -``` - -## 2) Real call scenario - -1. Register caller softphone `1001`. -2. Register operator softphone `2001`. -3. Make call `1001 -> 7000`. -4. Open `http://127.0.0.1:8080/operator` as `operator`. -5. In `Живые звонки`: - - select the active call - - click `Принять в работу` -6. Speak in `MicroSIP/Zoiper`. -7. Finish with one of: - - `Завершить` - - `Передать` - -## 3) Verify persisted events - -Expected event types for the same call: -- `call.started` -- `call.connected` -- `call.ended` -- `recording.ready` -- optional `call.transferred` - -## 4) Verify recording playback - -1. Open `http://127.0.0.1:8080/supervisor`. -2. In `Записи`, load the latest recording for the call. -3. Check inline playback: `audio/*`, HTTP `200`. - -## 5) Inspect action log - -```powershell -curl -H "X-User: operator_a" -H "X-Role: operator" ^ - http://127.0.0.1:8080/proxy/asterisk-bridge/asterisk/live-calls//actions -``` - -Each action should have: -- `action_type` in `claim|hangup|blind-transfer` -- `result_status=ok` -- `ami_action_id` for a successful AMI call - -## 6) Failure handling - -- `403 Operator can control only own claimed call` - - use the correct operator account or claim the call first -- `409 Unable to resolve active channel for call` - - check AMI permissions and channel resolution -- `Unknown transfer queue_code` - - fix `ASTERISK_TRANSFER_TARGET_MAP_JSON` diff --git a/docs/runbooks/track12-operator-voice-ux.md b/docs/runbooks/track12-operator-voice-ux.md deleted file mode 100644 index bf7ffcf..0000000 --- a/docs/runbooks/track12-operator-voice-ux.md +++ /dev/null @@ -1,53 +0,0 @@ -# Runbook - Track 12 Operator Voice UX - -Use this runbook to validate the frozen operator voice baseline after Track 12. - -## Preconditions - -- Platform is running locally or in QA -- `ASTERISK_CALLCONTROL_ENABLED=1` -- operator softphone is registered: `2001` or `2002` -- caller softphone `1001` can call `7000` - -## Operator model - -- The phone answer happens in `MicroSIP/Zoiper` -- Browser action `Принять в работу` does not answer media -- Browser actions manage ownership and call-control in the platform -- `Активные звонки` shows only live bridge-backed calls -- `Только что завершённые` shows short-lived recent terminal calls - -## Happy path - -1. Open `/operator` and log in as `operator / op12345`. -2. Place a call `1001 -> 7000`. -3. Answer in `MicroSIP/Zoiper`. -4. Confirm the card appears in `Активные звонки`. -5. Click `Принять в работу`. -6. Confirm the card stays in `Активные звонки`. -7. Click `Завершить`. -8. Confirm the card moves to `Только что завершённые`. -9. Open `/supervisor` and verify the imported recording. - -## Transfer path - -1. Repeat the call flow. -2. Click `Принять в работу`. -3. Set: - - `target type = extension` - - `target value = 2002` -4. Click `Передать`. -5. Confirm the card moves to `Только что завершённые`. -6. Confirm the recent card shows transfer as the terminal action. - -## Passive path - -1. Place a call and answer it in SIP. -2. Do not click any browser action for at least 30-40 seconds. -3. Confirm the call stays in `Активные звонки` until the real hangup. - -## Failure cues - -- If `Живые звонки` shows `interaction:int_*`, the browser is using stale assets -- If a call disappears before the real hangup, inspect bridge reconciliation behavior -- If `Передать` succeeds in SIP but not in UI, inspect `/asterisk/live-calls/{call_id}/actions` diff --git a/docs/runbooks/track14-browser-softphone.md b/docs/runbooks/track14-browser-softphone.md deleted file mode 100644 index 40f3e6d..0000000 --- a/docs/runbooks/track14-browser-softphone.md +++ /dev/null @@ -1,69 +0,0 @@ -# Track 14 Browser Softphone Runbook - -## Preconditions - -- `ASTERISK_WEBRTC_ENABLED=1` -- `ASTERISK_WEBRTC_WS_URL` points to the Asterisk WSS endpoint -- `ASTERISK_BROWSER_SIP_MAP_JSON` contains a mapping for the operator app user -- Asterisk `http.conf`, `rtp.conf`, and `pjsip.conf` from `deployment/asterisk/` are applied on the lab VM -- The operator workstation trusts the QA certificate used by Asterisk WSS -- Chrome or Edge is used for `/operator` - -## Browser config contract - -Bridge route: -- `/proxy/asterisk-bridge/asterisk/browser-softphone/config` - -Expected config fields: -- `enabled` -- `ws_url` -- `sip_uri` -- `authorization_username` -- `password` -- `display_name` -- `ice_servers` -- `operator_extension` - -If the logged-in app user has no browser SIP mapping, the route returns `403`. - -## QA startup flow - -1. Open `/operator` in Chrome or Edge. -2. Log in with a user that has: - - operator role access - - a browser SIP mapping in `ASTERISK_BROWSER_SIP_MAP_JSON` -3. Click `Подключить browser softphone`. -4. Allow microphone access. -5. Confirm the `Browser Softphone` block reaches `Зарегистрирован`. - -## Inbound QA call flow - -1. Place a lab call from `1001` to `7000`. -2. Wait for the incoming browser banner. -3. Click `Answer`. -4. The browser tries to auto-claim the live call. -5. If auto-claim does not happen because matching is ambiguous, click `Принять в работу` manually. -6. Click `Hang up` in the browser block or `Завершить` in the live-call block. -7. Confirm the call moves to `Только что завершённые`. -8. Confirm the recording is available in `/supervisor`. - -## Coexistence notes - -- `MicroSIP/Zoiper` remains supported in QA. -- Browser softphone does not replace the accepted Track 11/12 control-path. -- Browser `Hang up` still uses the accepted bridge hangup route when a live call match exists. - -## Common failures - -### Browser softphone config missing -- Check `ASTERISK_BROWSER_SIP_MAP_JSON` -- Check that the app user name matches the mapping key exactly - -### WSS registration fails -- Check Asterisk `http.conf` -- Check that the workstation trusts the QA certificate -- Check `ASTERISK_WEBRTC_WS_URL` - -### Browser answer works but auto-claim does not -- The current rule requires exactly one active ringing call for the operator extension -- If more than one candidate exists, use manual `Принять в работу` diff --git a/docs/runbooks/track15-telegram-chat.md b/docs/runbooks/track15-telegram-chat.md deleted file mode 100644 index f6d5025..0000000 --- a/docs/runbooks/track15-telegram-chat.md +++ /dev/null @@ -1,44 +0,0 @@ -# Track 15 - Telegram Chat Runbook - -## Required env - -- `TELEGRAM_BOT_ENABLED=1` -- `TELEGRAM_BOT_TOKEN` -- `TELEGRAM_WEBHOOK_SECRET` -- `TELEGRAM_DEFAULT_QUEUE_ID` - -Optional: - -- `TELEGRAM_BOT_API_BASE` when running against a stub or proxy - -## Register the bot webhook - -Use Telegram Bot API `setWebhook` with: - -- URL -> your public `telegram-adapter-service` webhook path: - - `/integrations/telegram/bot/webhook` -- secret token -> `TELEGRAM_WEBHOOK_SECRET` - -## Operator workflow - -1. Open `/operator` -2. Go to the `Telegram` page -3. Wait for a real inbound bot message -4. Select the thread from the left list -5. Click `Принять в работу` -6. Reply from the composer -7. Close or escalate the thread if needed - -## Runtime behavior - -- one Telegram thread is stored per `chat_id` -- new inbound messages reuse the same `thread_id` and `interaction_id` -- if the linked interaction was `closed`, a new inbound message reactivates it to `new` -- unsupported inbound Telegram content is stored as a visible system placeholder - -## QA check - -- send one first message from Telegram -> thread appears -- send a second message from the same `chat_id` -> same thread is reused -- operator claim works -- operator reply appears in the thread and is sent via Bot API diff --git a/docs/runbooks/track7-scale-validation.md b/docs/runbooks/track7-scale-validation.md deleted file mode 100644 index 97fc537..0000000 --- a/docs/runbooks/track7-scale-validation.md +++ /dev/null @@ -1,134 +0,0 @@ -# Runbook - Track 7 Scale Validation - -## Goal - -Validate the production-like `scale500` profile for Wave 2 / Track 7 on `K8s on-prem`. - -This runbook assumes: - -- Helm is the canonical deployment path -- `DATABASE_URL` points to a shared PostgreSQL endpoint -- `metrics-server` is installed and healthy -- Helm migration hook is enabled - -## Prerequisites - -- `kubectl` access to the target cluster -- `helm` installed locally -- namespace chosen (examples below use `mvp-cc`) -- Helm chart configured with: - - `global.schemaManagementMode=migrations` - - `migrations.enabled=true` - -## Configure Helm values - -Before deployment, confirm: - -- `profiles.scale500.enabled=true` -- `global.databaseUrl` is set to PostgreSQL -- `global.schemaManagementMode=migrations` -- `migrations.enabled=true` -- `db.poolSize`, `db.maxOverflow`, `db.poolTimeoutSeconds`, `db.poolRecycleSeconds` match the target baseline - -Render and inspect: - -```powershell -helm lint deployment\helm -helm template mvp-cc deployment\helm -n mvp-cc -``` - -## Deploy - -```powershell -helm upgrade --install mvp-cc deployment\helm -n mvp-cc --create-namespace -``` - -Wait until: - -- migration Job `mvp-cc-db-migrate` completed successfully -- all expected pods are `Ready` -- HPA objects are created for the hot-path services -- `recording-service` stays singleton - -## Step validation (`250 + 250`) - -Run the staged profile first: - -```powershell -python scripts\load_test.py --base-url http:// --profile step_250_250 --auth-mode bearer -python scripts\track7_check.py --namespace mvp-cc --report-dir .artifacts\track7\ --require-success-rate 99 --require-p95-seconds 1.5 --require-p99-seconds 3.0 -``` - -Expected: - -- success rate `>= 99%` -- no crash loops or OOM kills -- no unexpected pod restarts - -You can also run the staged helper, which writes a formal acceptance pack automatically: - -```powershell -python scripts\track7_stage.py --base-url http:// --namespace mvp-cc --auth-mode bearer -``` - -This creates: - -- `.artifacts/track7/staged_/step_250_250` -- `.artifacts/track7/staged_/target_500_500` -- `.artifacts/track7/staged_/acceptance_summary.json` -- `.artifacts/track7/staged_/acceptance_summary.md` - -## Target validation (`500 + 500`) - -Only after the step profile passes: - -```powershell -python scripts\load_test.py --base-url http:// --profile target_500_500 --auth-mode bearer -python scripts\track7_check.py --namespace mvp-cc --report-dir .artifacts\track7\ --require-success-rate 99 --require-p95-seconds 2.0 --require-p99-seconds 3.5 -``` - -Expected: - -- `500 voice + 500 digital` mixed profile -- success rate `>= 99%` -- p95 `<= 2.0s` -- p99 `<= 3.5s` -- `5xx` / transport failure rate `<= 0.5%` - -## Evidence pack - -Keep the following together: - -- rendered Helm values/profile -- `summary.json` -- `latency_samples.csv` -- `error_samples.json` -- `mix_profile.json` -- output from `track7_check.py` -- rollout notes (cluster, namespace, date, profile, result) - -## Rollback - -If the target profile fails: - -1. Roll back the Helm release: - -```powershell -helm rollback mvp-cc -``` - -2. Keep the failed report directory for analysis. -3. Reduce to the last passing profile and re-run validation only after tuning. - -## Canonical scale500 values - -Use the built-in preset as the base overlay for the production-like scale profile: - -```powershell -helm upgrade --install mvp-cc deployment\helm -f deployment\helm\values.scale500.yaml -n mvp-cc --create-namespace -``` - -Then override at least: - -- `global.databaseUrl` -- image tag / repository as needed for the target cluster diff --git a/docs/runbooks/track9-2-production-cutover.md b/docs/runbooks/track9-2-production-cutover.md deleted file mode 100644 index ef5c5df..0000000 --- a/docs/runbooks/track9-2-production-cutover.md +++ /dev/null @@ -1,190 +0,0 @@ -# Wave 2 / Track 9.2 - Production Cutover (K8s Helm, Controlled Window) - -This runbook executes Track 9.2 as an operations cutover, not a feature track. - -## Goal - -Move the accepted Asterisk bridge flow from QA to production Helm deployment with: - -- strict bridge auth (`bearer` only), -- one real softphone call validation, -- recording import + supervisor playback proof, -- formal GO/NO-GO evidence and rollback path. - -## Scope - -Includes: - -- production Helm upgrade in strict mode, -- post-cutover checks: - - `track9_preflight --check-sftp --require-strict-service-auth` - - `asterisk_lab_smoke --require-recording` - - `track9_check --require-recording` -- evidence package collection. - -Excludes: - -- ARI/call-control, -- new voice features, -- business API contract changes. - -## Required inputs - -Fill [track9-cutover-sheet.template.md](/e:/Zhan/docs/acceptance/track9/track9-cutover-sheet.template.md) first. - -Mandatory values: - -- `KUBE_CONTEXT` -- `NAMESPACE` -- `RELEASE` -- `GATEWAY_BASE_URL` -- `DATABASE_URL` (PostgreSQL in production) -- `IMAGE_TAG` -- `ASTERISK_AMI_HOST`, `ASTERISK_AMI_USERNAME`, `ASTERISK_AMI_SECRET` -- `ASTERISK_SFTP_HOST`, `ASTERISK_SFTP_USERNAME`, `ASTERISK_SFTP_PASSWORD` -- `QUEUE_ID` for `voice_lab` -- `APP_TOKEN_SECRET` - -## Phase 0 - Entry checks (T-1 day) - -1. Baseline reference is fixed: - - [docs/acceptance/track9/track9-acceptance.md](/e:/Zhan/docs/acceptance/track9/track9-acceptance.md) -2. Confirm `voice_lab -> queue_id` exists in target routing DB. -3. Confirm cluster can reach Asterisk VM: - - AMI `5038/tcp` - - SFTP `22/tcp` -4. Confirm maintenance window and owners. - -## Phase 1 - Pre-cutover dry-run (T-4h to T-1h) - -Run dry-run command (no deploy): - -```powershell -powershell -ExecutionPolicy Bypass -File scripts\track9_2_cutover.ps1 ` - -KubeContext ` - -Namespace ` - -Release ` - -GatewayBaseUrl ` - -DatabaseUrl ` - -ImageTag ` - -AmiHost ` - -AmiUser ` - -AmiSecret ` - -SftpHost ` - -SftpUser ` - -SftpPassword ` - -QueueId ` - -AppTokenSecret -``` - -Expected: - -- `helm lint` PASS -- `helm template` PASS -- rendered manifest contains: - - `ALLOW_LEGACY_HEADER_AUTH=0` - - `ASTERISK_BRIDGE_AUTH_MODE=bearer` - - `ASTERISK_BRIDGE_AUTH_FALLBACK_LEGACY=0` - - `VOICE_ADAPTER_TRUSTED_SERVICE_SUBJECTS` with `svc:asterisk-bridge` - - `RECORDING_IMPORT_TRUSTED_SERVICE_SUBJECTS` with `svc:asterisk-bridge` - - `RECORDING_IMPORT_ALLOW_ADMIN=0` - -Artifacts are saved under: - -- `.artifacts/track9_2//` - -## Phase 2 - Controlled cutover execute (T0) - -Run same command with `-Execute`: - -```powershell -powershell -ExecutionPolicy Bypass -File scripts\track9_2_cutover.ps1 ` - -KubeContext ` - -Namespace ` - -Release ` - -GatewayBaseUrl ` - -DatabaseUrl ` - -ImageTag ` - -AmiHost ` - -AmiUser ` - -AmiSecret ` - -SftpHost ` - -SftpUser ` - -SftpPassword ` - -QueueId ` - -AppTokenSecret ` - -Execute -``` - -If Helm reports conflicts on HPA-managed `Deployment.spec.replicas`, rerun with: - -```powershell -... -Execute -ForceUpgrade -``` - -Script actions: - -1. snapshot: - - `kubectl get pods -o wide` - - `helm history` (before) -2. `helm upgrade --install` with strict overlay -3. wait for the migration Job to complete -4. rollout wait: - - `api-gateway` - - `asterisk-bridge-service` - - `voice-adapter-service` - - `recording-service` -5. strict preflight + smoke + track9_check -6. evidence package via `track9_collect_evidence` -7. writes rollback hint from Helm history - -## Phase 3 - Live acceptance (mandatory) - -During window, place one real call: - -- softphone `1001 -> 7000` - -Then confirm: - -- `call.started` from Asterisk source exists -- `call.ended` for same call exists -- `recording.ready` uploaded to `recording-service` -- supervisor playback is `HTTP 200` and `audio/*` - -## Phase 4 - Evidence and GO/NO-GO - -Use generated evidence folder from script and finalize: - -- `track9-acceptance.md` -- `playback-proof.md` -- optional `playback-proof.json` - -GO only if all true: - -1. preflight PASS -2. smoke PASS -3. track9_check PASS -4. `failed bridge events = 0` -5. supervisor playback confirmed - -## Rollback policy - -Rollback triggers: - -- preflight FAIL -- track9_check FAIL -- no recording import after live call -- failed bridge events keep growing - -Rollback command pattern: - -```powershell -helm -n rollback -kubectl -n rollout status deploy/api-gateway -kubectl -n rollout status deploy/asterisk-bridge-service -``` - -After rollback: - -- rerun `track9_preflight` in previous baseline, -- log incident note + root cause item in cutover sheet. diff --git a/docs/runbooks/voice-baseline.md b/docs/runbooks/voice-baseline.md deleted file mode 100644 index 9802b02..0000000 --- a/docs/runbooks/voice-baseline.md +++ /dev/null @@ -1,49 +0,0 @@ -# Voice Baseline - -Status: `Track 11/12 accepted, Track 14 browser QA path added` - -This document describes what the voice path already supports today. - -## What works - -- Asterisk 20 on a Linux VM forwards inbound lab calls into the platform through `asterisk-bridge-service` -- A call from `1001` to `7000` creates a real voice interaction and writes: - - `call.started` - - `call.connected` - - `call.ended` - - `recording.ready` -- `/operator` shows real bridge-backed live calls in `Активные звонки` -- The operator can use accepted browser controls: - - `Принять в работу` - - `Завершить` - - `Передать` -- `/operator` keeps short-lived terminal cards in `Только что завершённые` -- Recording import is automatic and `/supervisor` can play the audio back -- A QA browser softphone path is now available when: - - bridge WebRTC config is enabled - - the operator user has a browser SIP mapping - - Asterisk WSS config is applied - -## What this baseline does not do - -- no ARI -- no attended transfer or conference -- no production cutover workflow -- no telephony redesign beyond the accepted Track 11 and Track 12 behavior -- no production browser softphone rollout yet - -## Source documents - -- Track 11 roadmap: `docs/roadmap/07-track11-live-operator-call-control.md` -- Track 12 roadmap: `docs/roadmap/08-track12-operator-voice-ux-hardening.md` -- Track 14 roadmap: `docs/roadmap/09-track14-browser-softphone-webrtc.md` -- Track 11 runbook: `docs/runbooks/track11-live-operator-call-control.md` -- Track 12 runbook: `docs/runbooks/track12-operator-voice-ux.md` -- Track 14 runbook: `docs/runbooks/track14-browser-softphone.md` - -## Acceptance references - -- Track 11 accepted package: - - `docs/acceptance/track11/track11-acceptance.md` -- Track 12 accepted package: - - `docs/acceptance/track12/20260307_161629/track12-acceptance.md` diff --git a/docs/security/checklist.md b/docs/security/checklist.md deleted file mode 100644 index 09649c0..0000000 --- a/docs/security/checklist.md +++ /dev/null @@ -1,24 +0,0 @@ -# Security Checklist (Stage 4) - -## Scope -- MVP on-prem deployment baseline -- API gateway + service-level RBAC -- Data persistence and backup handling - -## Checklist -- [x] RBAC enforced on privileged endpoints (`auth/users`, `routing/queues`, `knowledge/categories`) -- [x] Role-denied requests return `403` without side effects -- [x] Request payload validation enabled via Pydantic models -- [x] SQL access uses parameterized ORM operations (SQLAlchemy) -- [x] Health and registry endpoints do not expose secrets -- [x] Backup archives created and restored via controlled scripts -- [x] Security smoke checks automated in `scripts/gate4_check.py` - -## Verification Commands -- `python scripts/gate4_check.py` -- Optional artifact retention: `python scripts/gate4_check.py --keep-artifacts` - -## Out of Scope for MVP -- LDAP/SSO integration -- External secrets manager integration -- WAF and SIEM integration diff --git a/docs/uat/README.md b/docs/uat/README.md deleted file mode 100644 index 0be32e1..0000000 --- a/docs/uat/README.md +++ /dev/null @@ -1,59 +0,0 @@ -# UAT Pilot Package - -This folder contains the operational UAT package for Stage 4 pilot acceptance. - -## Scope Control - -- The frozen MVP pilot scope is defined in `docs/gates/mvp-pilot-baseline.md`. -- Out-of-scope requests must be redirected to `docs/roadmap/05-wave2-backlog.md`. -- During pilot close-out, only `P1` and `P2` defects are remediated in MVP. - -## Workflow - -1. Run preflight before participants join: - - Existing environment: `python scripts/uat_preflight.py --base-url http://:8080` - - Local auto-start: `python scripts/uat_preflight.py --auto-start` -2. Run automated dry-run and generate UAT package: - - Existing environment: `python scripts/uat_dry_run.py --base-url http://:8080 --update-defect-register` - - Local auto-start: `python scripts/uat_dry_run.py --auto-start --update-defect-register` -3. Create/adjust a session protocol from template (or from generated dry-run package): - - `docs/uat/session-template.md` - - Or prepare a full manual bundle: - - `python scripts/uat_manual_prepare.py --environment-url http://:8080` -4. Execute all scenarios: - - `docs/uat/scenario-checklist.md` -5. Register all defects: - - `docs/uat/defect-log-template.csv` -6. Capture sign-off: - - `docs/uat/signoff-template.md` -7. Attach preflight report from: - - `docs/uat/evidence/` -8. After signatures and zero open `P1/P2`, close Gate 4: - - Optional validation only: - - `python scripts/finalize_mvp_pilot.py --session-dir docs/uat/evidence/manual_ --dry-run` - - `python scripts/finalize_mvp_pilot.py --session-dir docs/uat/evidence/manual_` - -## Triage Rules - -- `P1`: pilot acceptance is impossible -- `P2`: key MVP user journey is broken -- `P3`: acceptable with limitation, defer unless it blocks sign-off -- `P4`: cosmetic or low-priority backlog item - -`P3` and `P4` findings should be logged and moved to post-MVP backlog instead of -stretching the MVP remediation cycle. - -## Mandatory Evidence Bundle - -- preflight report -- completed session protocol -- completed scenario checklist -- filled defect log -- signed sign-off sheet -- updated `docs/gates/p1-p2-defects.md` - -## Exit Criteria - -- All mandatory scenarios executed -- No open P1/P2 defects -- Signed protocol by business and IT owners diff --git a/docs/uat/defect-log-template.csv b/docs/uat/defect-log-template.csv deleted file mode 100644 index eec4ec0..0000000 --- a/docs/uat/defect-log-template.csv +++ /dev/null @@ -1,2 +0,0 @@ -defect_id,severity,status,scenario,service,risk_level,summary,repro_steps,actual_result,expected_result,verification_step,owner,opened_at,closed_at,backlog_bucket,notes -UAT-001,P3,Open,S4,routing-service,Medium,"Example defect summary","1) ... 2) ...","Actual behavior","Expected behavior","Re-run scenario S4 after fix",owner_name,2026-02-26T00:00:00Z,,Wave2, diff --git a/docs/uat/evidence/.gitignore b/docs/uat/evidence/.gitignore deleted file mode 100644 index 4076b43..0000000 --- a/docs/uat/evidence/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -dry_run_*/ -manual_*/ -preflight_*.md diff --git a/docs/uat/evidence/README.md b/docs/uat/evidence/README.md deleted file mode 100644 index 795a9cb..0000000 --- a/docs/uat/evidence/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# UAT Evidence - -Store generated pilot artifacts in this folder, for example: - -- Preflight reports from `python scripts/uat_preflight.py` -- Session protocol copies -- Signed acceptance documents -- Exported logs/screenshots diff --git a/docs/uat/scenario-checklist.md b/docs/uat/scenario-checklist.md deleted file mode 100644 index 97b56d3..0000000 --- a/docs/uat/scenario-checklist.md +++ /dev/null @@ -1,223 +0,0 @@ -# UAT Scenario Checklist - -## S0 Login and RBAC deny - -- Preconditions: - - Test users are available (`admin`, `supervisor`, `operator`, `analyst`) -- Steps: - - Sign in through the gateway - - Verify role-dependent access to main flows - - Attempt a denied privileged action as `operator` (for example create a user) -- Expected: - - Login succeeds - - Allowed actions work for the selected role - - Denied privileged action returns `403` -- Result: - - [ ] Pass - - [ ] Fail -- Evidence: - - user: - - screenshot/log: - -## S1 Customer create and search - -- Preconditions: - - Operator UI or API access is available -- Steps: - - Create a customer - - Search the customer by name or phone - - Open the customer card -- Expected: - - Customer is created once - - Search returns the same customer - - Customer card data is consistent -- Result: - - [ ] Pass - - [ ] Fail -- Evidence: - - customer_id: - - screenshot/log: - -## S2 Voice interaction lifecycle - -- Preconditions: - - Operator account is available - - Voice queue is available -- Steps: - - Create a voice interaction - - Assign interaction to operator - - Change status to `closed` - - Open the interaction timeline -- Expected: - - Status transitions are correct - - Timeline contains creation, assignment, and close events -- Result: - - [ ] Pass - - [ ] Fail -- Evidence: - - interaction_id: - - screenshot/log: - -## S3 Assignment and escalation to second line - -- Preconditions: - - Queue routing rule for escalation exists -- Steps: - - Create or select an active interaction - - Assign it to an operator - - Escalate it to the target queue -- Expected: - - Assignment is accepted - - Queue is changed to the target queue - - Status changes to `escalated` -- Result: - - [ ] Pass - - [ ] Fail -- Evidence: - - interaction_id: - - target_queue: - -## S4 Routing and timeline verification - -- Preconditions: - - Queue rules are configured - - At least one completed interaction exists -- Steps: - - Execute a route request for the active queue - - Verify assigned operator and SLA result - - Re-open interaction timeline -- Expected: - - Routing returns assignee and SLA - - Timeline remains complete and chronological -- Result: - - [ ] Pass - - [ ] Fail -- Evidence: - - queue_id: - - route output: - -## S5 Voice event intake - -- Preconditions: - - Voice adapter endpoint is reachable -- Steps: - - Submit a voice event (`call.started`, `ivr.completed`, or `call.ended`) - - Verify event is stored and retrievable -- Expected: - - Voice event is accepted - - Event ID is returned - - Event can be listed afterwards -- Result: - - [ ] Pass - - [ ] Fail -- Evidence: - - event_id: - - call_id: - -## S6 Telegram interaction lifecycle - -- Preconditions: - - Telegram adapter endpoint is reachable -- Steps: - - Send webhook payload to Telegram adapter - - Verify message registration - - Verify interaction visibility in operator workflow -- Expected: - - Message is accepted - - Related interaction or adapter state is traceable -- Result: - - [ ] Pass - - [ ] Fail -- Evidence: - - message_id: - - screenshot/log: - -## S7 KB usage in active handling - -- Preconditions: - - KB category/article created by analyst/supervisor -- Steps: - - Search KB by keyword - - Open article - - Use article during interaction processing -- Expected: - - Relevant article found - - Operator confirms article usability -- Result: - - [ ] Pass - - [ ] Fail -- Evidence: - - article_id: - - keyword: - -## S8 Supervisor realtime - -- Preconditions: - - At least two agent states are submitted -- Steps: - - Set agent states (`READY`, `BUSY`) - - Update queue metrics - - Open realtime view/API -- Expected: - - Agent totals and states are visible - - Queue snapshot is visible -- Result: - - [ ] Pass - - [ ] Fail -- Evidence: - - timestamp: - - screenshot/log: - -## S9 KPI report and export validation - -- Preconditions: - - KPI events are ingested for the test queue -- Steps: - - Open `/reports/kpi` - - Verify KPI payload - - Open `/reports/export` and capture CSV output -- Expected: - - KPI payload contains `SL`, `ASA`, `AHT`, `Abandon`, `FCR` - - Export contains the expected header and queue rows -- Result: - - [ ] Pass - - [ ] Fail -- Evidence: - - queue_id: - - export snippet: - -## O1 Backup and restore verification - -- Preconditions: - - Pilot environment contains test data -- Steps: - - Run backup script - - Restore into a clean target - - Verify the restored data set -- Expected: - - Archive is created - - Restore succeeds - - Core interaction data remains intact -- Result: - - [ ] Pass - - [ ] Fail -- Evidence: - - archive: - - restore check: - -## O2 Load test baseline (`100 voice + 100 digital`) - -- Preconditions: - - Pilot environment is stable -- Steps: - - Run `python scripts/load_test.py --base-url http://:8080 --voice 100 --digital 100` - - Record success and latency output -- Expected: - - Load completes without blocking errors - - MVP baseline target is met -- Result: - - [ ] Pass - - [ ] Fail -- Evidence: - - command: - - summary: diff --git a/docs/uat/session-template.md b/docs/uat/session-template.md deleted file mode 100644 index 3cd1ca7..0000000 --- a/docs/uat/session-template.md +++ /dev/null @@ -1,72 +0,0 @@ -# UAT Session Protocol - -## Session Metadata - -- Session ID: -- Date: -- Start time: -- End time: -- Environment URL: -- Build/version: -- Deployment date: -- Cluster/namespace: - -## Scope Confirmation - -- [ ] MVP pilot scope reviewed against `docs/gates/mvp-pilot-baseline.md` -- [ ] Out-of-scope requests redirected to `docs/roadmap/05-wave2-backlog.md` -- [ ] API stabilization rules acknowledged (backward-compatible fixes only) - -## Participants - -- Operators: -- Supervisor: -- Analyst: -- Admin: -- Business owner: -- IT owner: - -## Preconditions - -- [ ] Preflight report attached (`docs/uat/evidence/preflight_*.md`) -- [ ] Test accounts are active -- [ ] Test queue is configured -- [ ] Recording and audit logging are enabled -- [ ] Defect log prepared from `docs/uat/defect-log-template.csv` - -## Execution Summary - -- Mandatory scenarios executed: -- Passed: -- Failed: -- Blocked: - -## Defect Summary - -- P1: -- P2: -- P3: -- P4: -- Defect log attachment: - -## Decision - -- [ ] Accepted with conditions -- [ ] Accepted for pilot completion -- [ ] Not accepted - -Accepted with conditions is allowed only when all `P1` and `P2` items are closed and -remaining `P3`/`P4` findings are explicitly deferred to `Wave 2`. - -## Comments - -- Business comments: -- IT comments: -- Deferred backlog references: - -## Signatures - -- Business owner: -- IT owner: -- Supervisor representative: -- Date: diff --git a/docs/uat/signoff-template.md b/docs/uat/signoff-template.md deleted file mode 100644 index 9732ee9..0000000 --- a/docs/uat/signoff-template.md +++ /dev/null @@ -1,48 +0,0 @@ -# UAT Sign-off Sheet - -## Scope Confirmation - -- MVP pilot scope confirmed against `docs/gates/mvp-pilot-baseline.md` -- Out-of-scope items acknowledged and deferred to `docs/roadmap/05-wave2-backlog.md` -- No new feature requests are included as MVP defects - -## Acceptance Statement - -The undersigned confirm that UAT scenarios were executed for MVP pilot scope and -results are recorded in the session protocol. - -## Conditions - -- No open P1 defects -- No open P2 defects -- Mandatory scenarios completed - -## Known Exclusions - -- LDAP / SSO -- additional channels beyond `voice` and `Telegram` -- standalone supervisor/admin UI -- advanced IVR builder -- full recording suite -- extended KPI catalog - -## Decision - -- [ ] Accepted for pilot completion -- [ ] Accepted with conditions (only `P3`/`P4` deferred) -- [ ] Not accepted - -## Signatures - -- Business owner: - - Name: - - Signature: - - Date: -- IT owner: - - Name: - - Signature: - - Date: -- Supervisor representative: - - Name: - - Signature: - - Date: diff --git a/tests/test_track7_scripts.py b/tests/test_track7_scripts.py deleted file mode 100644 index 732b062..0000000 --- a/tests/test_track7_scripts.py +++ /dev/null @@ -1,107 +0,0 @@ -from pathlib import Path - -from scripts import load_test, track7_check - - -def test_load_test_profiles_and_mix(): - profile = load_test.resolve_profile("target_500_500") - compat_profile = load_test.resolve_profile("baseline_100_100", voice=120, digital=80) - mix = load_test.build_mix_profile(profile, include_read_traffic=True) - - assert profile["voice"] == 500 - assert profile["digital"] == 500 - assert compat_profile["voice"] == 120 - assert compat_profile["digital"] == 80 - assert mix["channels"]["voice"] == 500 - assert mix["channels"]["telegram"] == 200 - assert mix["channels"]["webchat"] == 150 - assert mix["channels"]["email"] == 150 - assert "/proxy/reporting/reports/kpi" in mix["read_endpoints"] - - -def test_load_test_request_builders_use_real_adapter_routes(): - telegram_request = load_test.build_request("telegram", 1) - webchat_request = load_test.build_request("webchat", 1) - email_request = load_test.build_request("email", 1) - - assert telegram_request[1] == "/proxy/telegram/integrations/telegram/webhook" - assert webchat_request[1] == "/proxy/webchat/integrations/webchat/messages" - assert email_request[1] == "/proxy/email/integrations/email/messages" - - -def test_load_test_report_writing_and_threshold_evaluation(tmp_path: Path): - mix = load_test.build_mix_profile(load_test.resolve_profile("baseline_100_100"), include_read_traffic=False) - results = [ - {"ts": 1.0, "target": "voice", "status_code": 200, "latency_seconds": 0.5, "ok": True}, - {"ts": 2.0, "target": "telegram", "status_code": 200, "latency_seconds": 0.7, "ok": True}, - {"ts": 3.0, "target": "voice", "status_code": 502, "latency_seconds": 1.2, "ok": False}, - ] - summary = load_test.build_summary( - results, - mix, - { - "require_success_rate": 50.0, - "require_p95_seconds": 2.0, - "require_p99_seconds": 3.0, - }, - "2026-03-02T10:00:00", - "2026-03-02T10:01:00", - ) - - load_test.write_report(tmp_path, summary, results, [{"error": "bad gateway"}]) - - assert summary["passed"] is True - assert (tmp_path / "summary.json").exists() - assert (tmp_path / "latency_samples.csv").exists() - assert (tmp_path / "error_samples.json").exists() - assert (tmp_path / "mix_profile.json").exists() - - -def test_track7_check_evaluate_summary_flags_failures(): - summary = { - "results": { - "success_rate": 97.5, - "p95_seconds": 2.5, - "p99_seconds": 4.1, - "five_xx_rate": 0.8, - } - } - - issues = track7_check.evaluate_summary( - summary, - require_success_rate=99.0, - require_p95_seconds=2.0, - require_p99_seconds=3.5, - ) - - assert any("Success rate" in issue for issue in issues) - assert any("P95" in issue for issue in issues) - assert any("P99" in issue for issue in issues) - assert any("5xx" in issue for issue in issues) - - -def test_track7_artifacts_present_in_templates_and_runtime(): - root = Path(__file__).resolve().parents[1] - db_py = (root / "services" / "shared" / "db.py").read_text(encoding="utf-8") - values_yaml = (root / "deployment" / "helm" / "values.yaml").read_text(encoding="utf-8") - services_yaml = (root / "deployment" / "helm" / "templates" / "services.yaml").read_text(encoding="utf-8") - migration_job_yaml = (root / "deployment" / "helm" / "templates" / "migration-job.yaml").read_text(encoding="utf-8") - - assert "DB_POOL_SIZE" in db_py - assert "DB_MAX_OVERFLOW" in db_py - assert "profiles:" in values_yaml - assert "scale500:" in values_yaml - assert "databaseUrl:" in values_yaml - assert "migrations:" in values_yaml - assert "HorizontalPodAutoscaler" in services_yaml - assert "PodDisruptionBudget" in services_yaml - assert "startupProbe" in services_yaml - assert "livenessProbe" in services_yaml - assert "DATABASE_URL" in services_yaml - assert "ai-orchestrator-service:" in values_yaml - assert "ai-voice-runtime-service:" in values_yaml - assert "AI_VOICE_RUNTIME_SERVICE_URL" in services_yaml - assert "AI_VOICE_QUEUE_CONFIG_JSON" in services_yaml - assert '"helm.sh/hook": pre-install,pre-upgrade' in migration_job_yaml - assert "scripts/migrate_core_db.py" in migration_job_yaml - assert "SCHEMA_MANAGEMENT_MODE" in migration_job_yaml diff --git a/tests/test_track7_stage.py b/tests/test_track7_stage.py deleted file mode 100644 index 1ce6fb5..0000000 --- a/tests/test_track7_stage.py +++ /dev/null @@ -1,49 +0,0 @@ -from pathlib import Path - -from scripts import track7_stage - - -def test_track7_stage_resolve_profiles(): - assert track7_stage.resolve_profiles("step_250_250,target_500_500") == ["step_250_250", "target_500_500"] - - -def test_track7_stage_rejects_unknown_profile(): - try: - track7_stage.resolve_profiles("step_250_250,unknown") - except ValueError as exc: - assert "Unsupported staged profiles" in str(exc) - else: - raise AssertionError("Expected ValueError for unknown staged profile") - - -def test_track7_stage_writes_acceptance_pack(tmp_path: Path): - payload = { - "namespace": "mvp-cc", - "base_url": "http://example.test", - "auth_mode": "bearer", - "include_read_traffic": True, - "started_at": "2026-03-02T10:00:00", - "completed_at": "2026-03-02T10:05:00", - "passed": False, - "stages": [ - { - "profile": "step_250_250", - "report_dir": str(tmp_path / "step_250_250"), - "passed": False, - "issues": ["P95 too high"], - "results": { - "success_rate": 100.0, - "p95_seconds": 2.5, - "p99_seconds": 3.0, - }, - } - ], - } - - track7_stage.write_acceptance_pack(tmp_path, payload) - - assert (tmp_path / "acceptance_summary.json").exists() - markdown = (tmp_path / "acceptance_summary.md").read_text(encoding="utf-8") - assert "Track 7 Staged Validation" in markdown - assert "step_250_250" in markdown - assert "P95 too high" in markdown diff --git a/tests/test_track8_event_bus.py b/tests/test_track8_event_bus.py deleted file mode 100644 index 07560a0..0000000 --- a/tests/test_track8_event_bus.py +++ /dev/null @@ -1,419 +0,0 @@ -from __future__ import annotations - -import json -from pathlib import Path -from uuid import uuid4 - -from fastapi.testclient import TestClient -from sqlalchemy import create_engine, select -from sqlalchemy.orm import Session - -import gateway.app as gateway_module -from scripts import event_bus_smoke, track8_check -from services.audit_service import app as audit_module -from services.event_bus_service.app import app as event_bus_app -from services.interaction_service.app import app as interaction_app -from services.ivr_service.app import app as ivr_app -from services.recording_service.app import app as recording_app -from services.reporting_service import app as reporting_module -from services.shared.event_bus import append_outbox_event, build_envelope, mark_outbox_failed -from services.shared.sql_models import ( - AuditEventRow, - Base, - EventInboxRow, - EventOutboxRow, - ReportingEventLogRow, - ReportingEventRow, -) -from services.supervisor_service.app import app as supervisor_app - - -def _admin_headers() -> dict[str, str]: - return {"X-User": "admin", "X-Role": "admin"} - - -def _supervisor_headers() -> dict[str, str]: - return {"X-User": "supervisor", "X-Role": "supervisor"} - - -def _operator_headers() -> dict[str, str]: - return {"X-User": "operator", "X-Role": "operator"} - - -def _write_audio_fixture(path: Path) -> Path: - path.parent.mkdir(parents=True, exist_ok=True) - path.write_bytes(b"RIFF" + b"\x00" * 32) - return path - - -def _build_valid_flow(queue_id: str, resolved_queue_id: str) -> dict: - return { - "name": f"Track8 Flow {queue_id}", - "description": "Track8 test flow", - "queue_id": queue_id, - "entry_node_id": "root", - "flow_json": { - "nodes": [ - { - "node_id": "root", - "prompt_text": "Press 1 for support", - "prompt_audio_key": "ivr/track8-root", - "is_terminal": False, - "invalid_target_node_id": "root", - "options": [{"digit": "1", "target_node_id": "support"}], - }, - { - "node_id": "support", - "prompt_text": "Routing to support", - "prompt_audio_key": "ivr/track8-support", - "is_terminal": True, - "outcome_code": "support_route", - "resolved_queue_id": resolved_queue_id, - "resolved_queue_code": "support", - "options": [], - }, - ] - }, - "is_active": True, - } - - -def test_append_outbox_event_persists_full_envelope(): - from services.shared.db import get_session - - session = get_session() - try: - entity_id = f"int_{uuid4().hex[:12]}" - row = append_outbox_event( - session, - event_type="interaction.created", - producer_service="interaction-service", - entity_type="interaction", - entity_id=entity_id, - payload={"interaction_id": entity_id, "queue_id": "q_track8"}, - correlation_id="corr_track8", - ) - session.commit() - stored = session.execute( - select(EventOutboxRow).where(EventOutboxRow.event_id == row.event_id) - ).scalar_one() - envelope = json.loads(stored.payload_json) - assert stored.status == "pending" - assert stored.routing_key == "interaction.created" - assert envelope["event_id"] == stored.event_id - assert envelope["correlation_id"] == "corr_track8" - assert envelope["payload"]["interaction_id"] == entity_id - finally: - session.close() - - -def test_interaction_supervisor_recording_and_ivr_write_outbox(tmp_path: Path, monkeypatch) -> None: - monkeypatch.setenv("EVENT_BUS_ENABLED", "1") - interaction_client = TestClient(interaction_app) - supervisor_client = TestClient(supervisor_app) - recording_client = TestClient(recording_app) - ivr_client = TestClient(ivr_app) - - interaction = interaction_client.post( - "/interactions", - json={ - "channel": "voice", - "subject": "Track8 Outbox Interaction", - "queue_id": f"q_{uuid4().hex[:8]}", - "priority": 3, - }, - headers={"X-User": "operator", "X-Role": "operator"}, - ) - assert interaction.status_code == 200 - interaction_id = interaction.json()["interaction_id"] - - assigned = interaction_client.patch( - f"/interactions/{interaction_id}/assign", - json={"assignee": "operator_a"}, - headers=_admin_headers(), - ) - assert assigned.status_code == 200 - - escalated = interaction_client.post( - f"/interactions/{interaction_id}/escalate", - json={"target_queue_id": "line2"}, - headers=_supervisor_headers(), - ) - assert escalated.status_code == 200 - - closed = interaction_client.patch( - f"/interactions/{interaction_id}/status", - json={"status": "closed"}, - headers={"X-User": "operator", "X-Role": "operator"}, - ) - assert closed.status_code == 200 - - agent_state = supervisor_client.post( - "/supervisor/agent-states", - json={"agent_id": f"agent_{uuid4().hex[:6]}", "state": "READY", "queue_id": "line2"}, - headers=_supervisor_headers(), - ) - assert agent_state.status_code == 200 - - monkeypatch.setenv("CC_RECORDINGS_DIR", str(tmp_path / "recordings")) - source_path = _write_audio_fixture(tmp_path / "fixtures" / "track8.wav") - registered = recording_client.post( - "/recordings/register", - headers=_supervisor_headers(), - json={ - "call_id": f"call_{uuid4().hex[:8]}", - "interaction_id": interaction_id, - "source_path": str(source_path), - "file_name": "track8.wav", - "mime_type": "audio/wav", - "duration_seconds": 2, - }, - ) - assert registered.status_code == 200 - - queue_id = f"ivr_q_{uuid4().hex[:6]}" - flow = ivr_client.post("/ivr/flows", headers=_admin_headers(), json=_build_valid_flow(queue_id, "support_line")) - assert flow.status_code == 200 - started = ivr_client.post( - "/ivr/sessions/start", - headers=_supervisor_headers(), - json={"call_id": f"call_{uuid4().hex[:6]}", "queue_id": queue_id, "interaction_id": interaction_id}, - ) - assert started.status_code == 200 - session_id = started.json()["session"]["session_id"] - completed = ivr_client.post( - f"/ivr/sessions/{session_id}/dtmf", - headers=_supervisor_headers(), - json={"digit": "1"}, - ) - assert completed.status_code == 200 - assert completed.json()["completed"] is True - - from services.shared.db import get_session - - session = get_session() - try: - event_types = { - row.event_type - for row in session.execute( - select(EventOutboxRow).where( - EventOutboxRow.event_type.in_( - [ - "interaction.created", - "interaction.assigned", - "interaction.escalated", - "interaction.closed", - "agent.state.changed", - "call.recording.ready", - "ivr.completed", - ] - ) - ) - ).scalars() - } - assert "interaction.created" in event_types - assert "interaction.assigned" in event_types - assert "interaction.escalated" in event_types - assert "interaction.closed" in event_types - assert "agent.state.changed" in event_types - assert "call.recording.ready" in event_types - assert "ivr.completed" in event_types - finally: - session.close() - - -def test_audit_and_reporting_consumers_are_idempotent() -> None: - envelope = build_envelope( - event_type="ivr.completed", - producer="ivr-service", - entity_type="ivr_session", - entity_id=f"ivs_{uuid4().hex[:12]}", - payload={ - "interaction_id": f"int_{uuid4().hex[:12]}", - "queue_id": "line2", - "resolved_queue_id": "support_line", - "channel": "voice", - "outcome_code": "support_route", - }, - ).model_dump() - - audit_module._handle_event(envelope) - audit_module._handle_event(envelope) - reporting_module._handle_event(envelope) - reporting_module._handle_event(envelope) - - from services.shared.db import get_session - - session = get_session() - try: - audit_rows = session.execute( - select(AuditEventRow).where(AuditEventRow.action == "ivr.completed") - ).scalars().all() - audit_inbox = session.execute( - select(EventInboxRow).where( - EventInboxRow.consumer_name == "audit-service", - EventInboxRow.event_id == envelope["event_id"], - ) - ).scalars().all() - reporting_log = session.execute( - select(ReportingEventLogRow).where(ReportingEventLogRow.event_id == envelope["event_id"]) - ).scalars().all() - reporting_events = session.execute( - select(ReportingEventRow).order_by(ReportingEventRow.id.desc()) - ).scalars().all() - reporting_inbox = session.execute( - select(EventInboxRow).where( - EventInboxRow.consumer_name == "reporting-service", - EventInboxRow.event_id == envelope["event_id"], - ) - ).scalars().all() - - assert len(audit_inbox) == 1 - assert len(reporting_inbox) == 1 - assert len([row for row in audit_rows if envelope["event_id"] in row.metadata_json]) == 1 - assert len(reporting_log) == 1 - assert any(row.queue_id == "support_line" for row in reporting_events) - finally: - session.close() - - -def test_event_bus_service_retry_endpoint_and_gateway_contracts(monkeypatch) -> None: - monkeypatch.setenv("EVENT_BUS_ENABLED", "0") - from services.shared.db import get_session - - session = get_session() - try: - row = append_outbox_event( - session, - event_type="interaction.created", - producer_service="interaction-service", - entity_type="interaction", - entity_id=f"int_{uuid4().hex[:12]}", - payload={"interaction_id": f"int_{uuid4().hex[:12]}"}, - ) - mark_outbox_failed(session, row, "broker down") - event_id = row.event_id - session.commit() - finally: - session.close() - - client = TestClient(event_bus_app) - retried = client.post(f"/bus/outbox/{event_id}/retry", headers=_admin_headers()) - assert retried.status_code == 200 - assert retried.json()["status"] == "pending" - - denied = client.get("/bus/outbox", headers=_operator_headers()) - assert denied.status_code == 403 - - gateway_client = TestClient(gateway_module.app) - contracts = gateway_client.get("/contracts") - assert contracts.status_code == 200 - body = contracts.json() - assert "stage_8" in body - assert "/bus/outbox" in body["stage_8"] - - -def test_event_bus_smoke_and_track8_check_with_temp_database(tmp_path: Path, monkeypatch) -> None: - db_path = tmp_path / "track8.db" - engine = create_engine(f"sqlite:///{db_path.as_posix()}", future=True) - Base.metadata.create_all(engine) - - class DummyResponse: - def __init__(self, status_code: int, payload: dict | None = None): - self.status_code = status_code - self._payload = payload or {} - - def raise_for_status(self) -> None: - if self.status_code >= 400: - raise RuntimeError("HTTP error") - - def json(self) -> dict: - return self._payload - - class DummyClient: - def __init__(self, *args, **kwargs): - self.base_url = kwargs.get("base_url") - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc, tb): - return None - - def get(self, path: str, headers: dict | None = None, timeout: int | None = None): - if path == "/proxy/event-bus/health": - return DummyResponse(200, {"status": "ok"}) - raise AssertionError(f"Unexpected GET path {path}") - - def post(self, path: str, headers: dict | None = None, json: dict | None = None, timeout: int | None = None): - if path == "/proxy/interaction/interactions": - interaction_id = f"int_{uuid4().hex[:12]}" - with Session(engine) as session: - outbox = EventOutboxRow( - event_id=f"evt_{uuid4().hex[:12]}", - event_type="interaction.created", - event_version=1, - producer_service="interaction-service", - entity_type="interaction", - entity_id=interaction_id, - correlation_id=None, - routing_key="interaction.created", - payload_json=json and __import__("json").dumps( - { - "event_id": f"evt_payload_{uuid4().hex[:12]}", - "event_type": "interaction.created", - "event_version": 1, - "occurred_at": "2026-03-02T12:00:00+00:00", - "producer": "interaction-service", - "entity_type": "interaction", - "entity_id": interaction_id, - "routing_key": "interaction.created", - "payload": {"interaction_id": interaction_id}, - } - ) - or "{}", - status="published", - attempt_count=0, - last_error=None, - available_at="2026-03-02T12:00:00+00:00", - published_at="2026-03-02T12:00:01+00:00", - created_at="2026-03-02T12:00:00+00:00", - updated_at="2026-03-02T12:00:01+00:00", - ) - session.add(outbox) - session.add( - EventInboxRow( - consumer_name="audit-service", - event_id=outbox.event_id, - event_type=outbox.event_type, - processed_at="2026-03-02T12:00:02+00:00", - status="processed", - notes=None, - ) - ) - session.add( - EventInboxRow( - consumer_name="reporting-service", - event_id=outbox.event_id, - event_type=outbox.event_type, - processed_at="2026-03-02T12:00:02+00:00", - status="processed", - notes=None, - ) - ) - session.commit() - return DummyResponse(200, {"interaction_id": interaction_id}) - raise AssertionError(f"Unexpected POST path {path}") - - monkeypatch.setattr(event_bus_smoke, "_engine_for", lambda database_url=None: engine) - monkeypatch.setattr(event_bus_smoke.httpx, "Client", DummyClient) - - smoke = event_bus_smoke.run_smoke_check(base_url="http://example.local", auth_mode="legacy_headers") - assert smoke["passed"] is True - - issues = track8_check.evaluate_track8( - database_url=f"sqlite:///{db_path.as_posix()}", - require_bus_enabled=False, - base_url=None, - ) - assert issues == []