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 == []