# Функциональный QA AGIL Calls — стенд calls.guardify.ru

> Независимое функциональное тестирование **развёрнутого** приложения (не по unit/e2e-тестам).
> Версия отчёта: **v1.2 (13.11.2026, финал)** — базовый прогон v1.0, ретест fix-билда v1.1 (§10)
> и тотальный прогон финального деплоя c40faccb14f3 (§11).
> Все баги подтверждены фактическим воспроизведением/логом; выдуманных багов нет.
>
> Окружение: реальный стенд `https://calls.guardify.ru` (Caddy/HAProxy → контейнер
> `agil-calls` на 95.182.94.7, данные в Docker volume `agil-calls_agil-data`),
> Chrome (puppeteer-core, fake media, puppeteer 25 / Node 23), прямой SSH и admin API.
> Секреты в отчёт не попадают. Скрипты, JSON-результаты и скриншоты — в `qa/functional/`.

---

## 1. Сводка

| Показатель | Значение |
|---|---|
| **Всего проверок** | **296** (infra 48, public API 60, admin API 61, WS 38, browser widget 37, dashboard 35, editor 17) |
| **Pass** | **285** |
| **Fail** | **11**, из них подтверждённых дефектов — **5 проверок / 4 бага** |
| Переклассифицировано (ошибки ожиданий QA-харнесса, не дефекты продукта) | 3 (`API-31/32`, `API-38`) |
| Флаки/ограничения среды (не подтверждённые баги) | 3 (`UI-09/10`, `UI-22`) |
| **P0** | **0** |
| **P1** | **1** — F-01 (превью редактора на стенде не работает) |
| **P2** | **2** — F-02 (markdown-доки 404), F-03 (файловый API не закрыт feature-флагом) |
| **P3** | **3** — F-04 (схема темы на demo), F-05 (soft-404), F-06 (дубли заголовков) |

Стенд в целом рабочий: TLS/HTTPS, публичный и admin REST, WS-сигналинг, реальный
WebRTC-звонок двух участников с видео/чатом/файлами/записью — работают. Блокеров P0 нет.

> **Ретест fix-билда (13.11.2026, см. §10):** 120 проверок, 115 pass, 5 fail = 3 бага.
> Закрыты F-02, F-03, F-04, F-05, F-06. **F-01 закрыт частично и переоткрыт**
> (same-origin исправлен, но preview по-прежнему offline из-за отсутствия room-параметра).
> Новые: **F-07 P2** (ссылки редактора на localhost:8787), **F-09 P3** (код `feature_disabled`
> не описан в доке). Новые функции (роли CRUD/roleId, /guardify.html, island, /docs explorer,
> новая секция «Масштабирование») — проверены, работают.
>
> **Финальный деплой c40faccb14f3 (§11, тотальный прогон):** F-01 и F-07 **закрыты** (подтверждено
> в браузере), контраст demo 15/15 комбинаций PASS, seeded-панель 20/20, island/guardify/docs —
> без регрессий, клик-прогон публичных страниц без визуальных дефектов. Новые дефекты:
> **F-12 P2** (тост-флуд/перекрытие UI при 60 участниках), **F-18 P2** (панель упирается в
> собственный admin rate-limit и показывает сырую ошибку), **F-13 P2-ops** (join-лимит 10/мин
> ломает 60-пользовательский нагрузочный сценарий/NAT), **F-14/F-15/F-16/F-17 P3**
> (контраст primary-кнопки Guardify, мелкая типографика, флаки блюра/шорткатов).

Разбивка по наборам (последние прогоны, `qa/functional/results/*-latest.json`):

| Набор | Pass/Всего | Подтверждённые дефекты |
|---|---|---|
| 01-infra | 45/48 | F-02 (2 проверки), F-05 (1) |
| 02-api-public | 57/60 (после реклассификации 60/60) | — |
| 03-ws | 38/38 | — |
| 04-admin | 61/61 | — |
| 05-browser-widget | 33/37 | F-04 (1), флаки UI-09/10, UI-22 |
| 06-browser-dashboard | 35/35 | — |
| 07-browser-editor | 16/17 | F-01 (1) |

---

## 2. Таблица багов

| ID | Sev | Зона | Что делали (шаги) | Ожидаемо | Фактически | Воспроизв. | Доказательство | Гипотеза причины / файл |
|---|---|---|---|---|---|---|---|---|
| **F-01** | **P1** | editor | Открыть `https://calls.guardify.ru/editor/`; посмотреть `iframe.preview-frame` и `#preview-status`; проверить собранный bundle `/editor/assets/index-*.js` | Превью показывает встраиваемый виджет с того же origin (`/embed.html`) и реагирует на правки темы | iframe src = `http://localhost:5173/embed.html` (жёстко зашито в production-bundle). На публичном стенде localhost:5173 нет → навсегда `preview offline — JSON/export work`, правки цвета/схемы в превью не видны. Остальной редактор работает (JSON/импорт/сохранение) | 100% (2 прогона) | `results/07-browser-editor-latest.json` (EDIT-04a), `results/editor-bundle.js` (`const Ds="http://localhost:5173/embed.html"`), `screens/editor-preview-*.png` | `apps/editor/src/preview.ts:40` (`DEFAULT_PREVIEW_URL`) + `Dockerfile` собирает без `VITE_PREVIEW_URL`. Исправление: production-дефолт same-origin `/embed.html` или build-arg `VITE_PREVIEW_URL=https://<host>/embed.html` |
| **F-02** | P2 | server/api | `GET /docs/api.md`, `GET /docs/security-audit.md`, ссылки из API-документации | Markdown-доки репозитория отдаются по `/docs/:file` (docs/api-exposure.md §1, docs/api.md §17) | `404 {"error":{"code":"not_found","message":"Document api.md not found"}}`; `/docs` explorer работает, но md-ссылки мертвы | 100% | `results/01-infra-latest.json` (INFRA-DOCS-MD, INFRA-DOCS-LIST) | `Dockerfile` (runtime stage) не копирует `docs/`, при `AGIL_DOCS_ENABLED=1`/`AGIL_DOCS_DIR=docs`; `packages/server/src/index.ts:630-648` ищет файлы в `/app/docs`. Исправление: `COPY docs ./docs` |
| **F-03** | P2 | server/security | Join-guest токен с эффективными правами **без** `fileShare`; `POST /v1/files` (`x-file-name`, тело) при `features.fileShare=false` | Сервер применяет те же права, что UI: фича выключена/нет permission → 403 | Файл загружается (200 + подписанный URL), хотя UI кнопку вложения скрывает. Сервер проверяет только `session.fileShare.enabled`, не `features.fileShare` и не effective permissions | 100% | `results/02-api-public-latest.json` (API-42), `results/api-public-artifacts-*.json` (FileRecord) | `packages/server/src/public-api.ts:601-618`. Риск: тенант «выключает файлы» флагом фичи, а API продолжает принимать вложения; расхождение UI/API |
| **F-04** | P3 | demo UI | Demo-страница: пресет `light`, переключить «Тема» light→dark; читать CSS-переменные `--agil-color-background/--agil-color-primary` на `<html>` | Смена схемы меняет палитру страницы (как в виджете для `auto`) | `data-theme` меняется (`light`→`dark`), но палитра CSS-переменных **не меняется** (`bg=#f3f6fb`, `primary=#2563eb` в обоих); визуально страница не темнеет. Работает только переключатель пресетов | 100% (3/3 прогона) | `results/05-browser-widget-latest.json` (UI-04a/UI-04b), `screens/demo-dark-*.png` | `apps/demo/src/main.ts:79-89`: `resolveTheme(themePresets[currentPreset], currentScheme)` — пресет содержит полную палитру и перекрывает базовую схему (`packages/protocol/src/theme.ts:353-359`) |
| **F-05** | P3 | server/infra | `GET /definitely-not-a-route-qa`, `/mcp`, `/api/` | 404 (или 404-эквивалент, как для docs при выключенном docs) | `200 text/html` — SPA `index.html` demo-страницы (8.8 КБ) на любой extension-less путь. Маскирует опечатки в API-путях, ломает мониторинг/SEO | 100% | `results/01-infra-latest.json` (INFRA-404) | `packages/server/src/static.ts:172-175` — SPA fallback; `/v1/*` и `/ws` исключены, `/mcp`, `/api/*` попадают в fallback |
| **F-06** | P3 | infra | `HEAD /`, разбор `rawHeaders` | По одному заголовку каждого типа | `referrer-policy`, `strict-transport-security`, `x-content-type-options`, `vary` приходят **дважды** (Caddy + приложение; HSTS с разными max-age: 31536000 и 15552000) | 100% | `results/headers-home-*.json`, `results/01-infra-latest.json` (notes) | Дублирующие security-заголовки в Caddyfile и `packages/server/src/http.ts` |

### Принятые риски стенда (не баги в рамках демо, важны для прода)

| ID | Тема | Факт | Комментарий |
|---|---|---|---|
| R-01 | Публичная admin-спека | `GET /openapi.admin.json` → 200 без авторизации (110 КБ, все admin-пути) | В `docs/deployment-record.md` для demo-стенда это заявлено публичным; для прода — `AGIL_DOCS_ENABLED=0`/`AGIL_DOCS_ALLOW_IPS`. Сам admin API защищён ключом (401), PII не отдаёт |
| R-02 | `CSP frame-ancestors *` на `/embed.html` | Встраивание разрешено с любого origin (`integrations.allowedOrigins` пуст) | Задокументированный дефолт; для прода задать allowlist |
| R-03 | `session.fileShare.enabled=true` при `features.fileShare=false` | Флаги фичи и сессии настроены независимо; см. F-03 | Рекомендуется дублировать проверку флага фичи на сервере |

### Флаки / ограничения среды (не подтверждённые дефекты)

1. **UI-09/10 (inline join на demo-странице):** в 2 из 3 автоматизированных прогонов
   `#join-inline` оставался в состоянии `joining` >60 с; в изолированном сценарии тот же
   путь присоединяется за ~2 с, второй участник видит 2 плитки и remote video. Причина не
   локализована — вероятно взаимодействие headless/getUserMedia/синтетических событий.
   Зафиксировано: `results/05-browser-widget-latest.json`. Требуется ретест на реальном Chrome.
2. **UI-22 (blur):** в одном прогоне клик по кнопке блюра применился (`blur` radio = on), в
   другом — radio остался `off`, при этом явного предупреждения о деградации не было (в
   консоли — тосты про слабый канал/заблокированный popup). Блюр зависит от CDN MediaPipe;
   по дизайну при недоступности должен показывать degraded-предупреждение. Нужен ретест с
   контролем сети — потенциальный P3 «тихий отказ блюра».
3. **Puppeteer `page.click()` зависает после появления 2 видео-плиток** в headless — артефакт
   харнесса: in-page `element.click()` работает мгновенно, панели/чат открываются. Не дефект
   продукта; учтено в скриптах (клики через `evaluate`).
4. `room_full` срабатывает только при WS-connect (слоты занимаются соединением), `POST /join`
   токен выдаёт. Соответствует дизайну (WS-29), но REST-клиент не узнаёт о заполненности
   заранее — UX-замечание.
5. `GET /v1/tenants/<неизвестный>/config` отдаёт дефолтный конфиг — задокументировано.

---

## 3. Matrix покрытия — API/WS/Infra

| Функция | Статус | Комментарий (IDs в results) |
|---|---|---|
| HTTPS/TLS, HSTS, HTTP→HTTPS redirect | ✅ | TLS 1.3, Let's Encrypt, SAN `calls.guardify.ru`, 90 дней; 308 (INFRA-01..03) |
| `/v1/health`, отсутствие утечек env | ✅ | флаги docs/admin/stt/ai/mcp; секретов/имён env нет (INFRA-04..07) |
| Публичные страницы/ассеты: status, content-type, кэш | ✅ | 16 ресурсов; `no-store` для API/docs, `public,max-age` для ассетов (INFRA-PAGE/CACHE) |
| Security-заголовки | ⚠️ pass с замечанием | все есть; дубли — F-06; CSP только у `/embed.html` — R-02 |
| CORS, preflight, expose-headers | ✅ | `*` + `vary: origin`, документированные заголовки/методы |
| Публичный OpenAPI без admin-путей и apiKey | ✅ | 18 путей, нет `/admin`, нет `apiKey`-схемы |
| Admin OpenAPI | ✅ 200 | публично — R-01 |
| Docs explorer `/docs` | ✅ | self-contained, без CDN |
| Markdown `/docs/:file` | ❌ | F-02 |
| Создание/чтение/удаление комнат (admin и anonymous) | ✅ | roomId, name, mode, context, configOverrides, TTL (API-08..17, ADM-05..07,56..57) |
| Join без пароля / пароль / инвайт | ✅ | 400 `password_required`, 401 `password_invalid`, 200 `passwordAccepted`, invite обходит пароль (API-18..26, ADM-09..10) |
| Lock/unlock, kick | ✅ | 423 `room_locked`, WS close 1008 `kicked`, 404 неизвестный (API-27..30, ADM-20..23, WS-25/26) |
| Room full | ✅ по WS | 409 на REST не срабатывает (слоты по WS), WS-29: `room_full` + 1008 |
| Чат-инъекция, идемпотентность, system, история | ✅ | дедуп по `id`, 403 гостю на `kind=system`, история 200 (API-33..41) |
| Файлы: upload / signed download / tamper / MIME / лимит | ✅ (F-03 — гейт) | 200/403/415/413, байты совпадают (API-42..47) |
| Запись: upload при выключенном сторе | ✅ | 403 `forbidden` (API-48) |
| STT без ключа | ✅ 501 | `stt_not_configured` — ограничение MVP (API-49) |
| In-call assistant: local/hybrid | ✅ | 501 `assistant_local_only`, hybrid fallback 501 (API-51, ADM-42) |
| Backgrounds builtin | ✅ | 3 фона, provider=builtin (API-53) |
| Токены pinned permissions, чужой тенант | ✅ | 200/403/400 (API-54..56) |
| Валидация/лимиты: JSON 1 МиБ, неизвестный метод, x-request-id | ✅ | 413/404/заголовок (API-57..59) |
| Rate limit join 10/мин | ✅ | 10×200, затем 429 (API-60) |
| Admin auth / чужой тенант | ✅ | 401/401/403 (ADM-01..03) |
| Admin overview/rooms/invites/lock/kick/chat | ✅ | ADM-04..23 |
| Транскрипт из caption, analysis/run, broadcast в WS | ✅ | сегмент, report/findings/notes, WS `analysis` (ADM-15..18) |
| Кастомное compliance-правило | ✅ | правило дало finding `critical` (ADM-36/36b) |
| Audit: фильтры type/severity/roomId, пагинация, валидация, отсутствие секретов | ✅ | 400 на неизвестный severity, только `ipHash` (ADM-24..30) |
| Security overview | ✅ | events/failedJoins/countries, без сырых IP (ADM-31..32) |
| Admin files/recordings, 404 | ✅ | ADM-33..35 |
| AI-agent settings persist/validation | ✅ | merge `policyRules`, 400 на невалидный mode (ADM-36..37) |
| Knowledge-agent + ask | ✅ | persist/GET, 502 на недоступный endpoint, 403 выключен (ADM-38..40) |
| In-call-agent persist / hybrid fallback | ✅ | ADM-41..42 |
| Access-policy: PUT, effective, restore, валидация | ✅ | guest=["chat"] при restrictive, восстановление (ADM-43..46) |
| Webhooks: PUT/test/SSRF/secretEnv | ✅ | реальная доставка на https 200 + запись в deliveries, SSRF/пустой секрет → 400 (ADM-47..49b) |
| Providers: 6 слотов, external, SSRF, recording 501 | ✅ | put/get/use-time `provider_unsupported` (ADM-50..55) |
| **WS: welcome/presence/config/ice/history** | ✅ | WS-01..05 |
| **WS: SDP/ICE relay, unknown_peer** | ✅ | WS-06..09 |
| **WS: state/reaction/caption/chat/история** | ✅ | WS-10..15 |
| **WS: ping/pong, hello, мусор** | ✅ | WS-16..19 |
| **WS: reconnect, replaced, kick, locked/full, unauthorized, bye** | ✅ | WS-20..32 |
| **WS: config broadcast, assistant-query, analysis-request** | ✅ | WS-33..35 |
| **WS: flood 40/с, payload >1 МиБ, admin close** | ✅ | close 1008/1009 (WS-36..38) |

---

## 4. Matrix покрытия — браузер

### 4.1 Demo / embed / виджет (`/`, `/embed.html`) — 33/37

| Функция | Статус | Комментарий |
|---|---|---|
| Demo: health-pill, 3 схемы, 5 пресетов, сниппет, ссылки | ✅ | 3/5, сниппет с реальным токеном (UI-01..08) |
| Тема light/dark на странице | ❌ P3 | F-04: `data-theme` меняется, палитра — нет |
| Создание комнаты через UI | ✅ | комната подтверждена admin API, join/embed/token (UI-06..08) |
| Inline-виджет: join, self-плитка, local video | ⚠️ флаки | UI-09/10: в suite >60 c, изолированно ~2 c |
| 2 участника: 2 плитки + remote video + имя | ✅ | fake media, оба видят 2 плитки (UI-12..14) |
| Чат A→B и B→A через UI | ✅ | доставка + история (UI-15..16) |
| Файл через чат | ✅ | сообщение с кнопкой скачивания, peer видит, signed download 200 (UI-26) при включённой фиче |
| Свернуть/развернуть | ✅ | `data-agil-minimized` true→restore (UI-18) |
| Fullscreen | ⚠️ ограничение | в headless trusted gesture нет; кнопка есть, состояние не переключается — не баг без headed-проверки (UI-19) |
| Новая вкладка / deep link | ✅ | URL с room+token; popup в headless может блокироваться (UI-20) |
| Настройки устройств/панель | ✅ | 4 select, blur-контролы (UI-21) |
| Блюр фона | ⚠️ флаки/CDN | 1 прогон — on, 1 — не применился без предупреждения (UI-22); нужен ретест с headed+сетью |
| Запись: старт/стоп/индикатор/upload/download | ✅ | индикатор, файл в `/v1/admin/recordings`, download 200 (66 КБ) — после временного включения и восстановления конфига (UI-27) |
| STT/субтитры | ⚠️ ограничение | `features.captions=false`, сервер STT не настроен → 501; кнопка скрыта (UI-28) |
| In-call assistant (local): команда + spotlight | ✅ | «включи микрофон» → ответ+toggle_mic; «где запись» → overlay + target (UI-23..24) |
| Права гостя | ✅ | attach/запись скрыты при выключенных фичах; `guest denied recording` в effective permissions |
| Iframe embed + postMessage | ✅ | iframe joined; `agil:ready/agil:state/agil:event(joined)`; `agil:action minimize` → `agil:minimized`; unknown → `action-denied` (UI-30..33) |
| Offline demo mode | ✅ | при недоступном сервере — экран «Сервер недоступен … Открыть демо-режим» (UI-35) |
| Консоль/сеть | ✅ | 0 pageerror; единственный console.error — намеренный `ERR_UNSAFE_PORT` offline-теста (UI-36) |

### 4.2 Панель `/dashboard/` — 35/35

| Функция | Статус | Комментарий |
|---|---|---|
| Вход: неверный/верный ключ, shell + 9 nav-разделов | ✅ | DASH-01..03 |
| 10 маршрутов (overview, rooms, room, security, audit, access, integrations, knowledge, recordings, settings) | ✅ | все рендерятся без error-state; консоль чистая (DASH-SCREEN, DASH-26) |
| Overview KPI | ✅ | DASH-04 |
| Создание комнаты через диалог (name/password/max/mode/TTL) | ✅ | комната подтверждена admin API, таблица обновилась (DASH-05..08) |
| Room detail: 5 вкладок (Чат/Транскрипт/Заметки/Compliance/Записи) | ✅ | все вкладки с контентом (DASH-09..11) |
| Live-участники (WS-guest виден) | ✅ | DASH-10 |
| «Сообщение от бота» → POST admin chat + toast + история | ✅ | DASH-12..13 |
| Invite-диалог: TTL/роль → ссылки/токен | ✅ | DASH-14 |
| Lock/unlock | ✅ | состояние комнаты true→false (DASH-15) |
| Audit: таблица 50 строк, 3 фильтра, смена severity | ✅ | DASH-16..17 |
| Access: матрица/эффективные права, сохранение no-op | ✅ | DASH-18 |
| Integrations: 6 карточек (providers/webhooks/AI/in-call/identity/scale) | ✅ | DASH-19 |
| Knowledge screen | ✅ | DASH-20 |
| Recordings screen | ✅ | DASH-21 |
| Settings: подключение + embed-генератор + выпуск токена | ✅ | DASH-22..23 |
| «Сохранить и проверить» (server+key) | ✅ | «Подключение работает» (DASH-24) |
| Logout: возврат на login, очистка localStorage | ✅ | DASH-25 |
| Консоль/сеть | ✅ | console.errors=0, pageerrors=0, 5xx=0 (DASH-26) |

### 4.3 Редактор `/editor/` — 16/17

| Функция | Статус | Комментарий |
|---|---|---|
| Загрузка, 15 секций навигации и контролов | ✅ | Бренд, Тема, Фичи, Доступы, Лейаут, Медиа, Fallback, Безопасность, Сессия, AI, Knowledge, Интеграции, Провайдеры, In-call, Комната (EDIT-01..03) |
| Превью iframe / postMessage | ❌ P1 | F-01: iframe на localhost:5173, preview offline (EDIT-04a); заглушка объясняет, что JSON/export работают (EDIT-04b) |
| 36 цветовых токенов + контраст-бейджи, изменение цвета → JSON | ✅ | EDIT-05..06 (превью-часть недоступна из-за F-01) |
| Смена схемы превью → config | ✅ | EDIT-07 (config применяется; iframe offline) |
| Тема самого редактора light/dark | ✅ | EDIT-08 |
| JSON + embed-сниппет, кнопки копирования/скачивания | ✅ | EDIT-09 |
| Импорт JSON: невалидный → issues, валидный → draft | ✅ | EDIT-10..11 |
| «Сохранить в тенант»: неверный ключ → 401; верный → PATCH, etag, персист | ✅ | EDIT-12..13 (после сохранения GET config содержит `#123456`) |
| Ошибка при недоступном сервере | ✅ | «Сервер недоступен…» (EDIT-14) |
| Восстановление конфига тенанта после теста | ✅ | PUT исходного снапшота, etag совпал (EDIT-15) |
| Консоль/сеть | ✅ | console.errors=0, pageerrors=0 (EDIT-16) |

---

## 5. «Всё создаётся и сохраняется»

| Сущность | Создано | Прочитано | Изменено | Удалено/восстановлено | Персистентность |
|---|---|---|---|---|---|
| Комната (UI/admin/anonymous) | ✅ | ✅ список/детали | ✅ lock, mode, context | ✅ DELETE (WS bye) | ✅ живёт до TTL; конфиг — в volume |
| Токен/инвайт | ✅ | ✅ join/welcome | — (TTL) | ✅ истечение/замена | ✅ stateless JWT |
| Конфиг тенанта | ✅ PATCH (admin и editor UI) | ✅ GET+etag | ✅ theme/branding/features/session | ✅ восстановлен, etag совпал | ✅ `tenants/demo.json` в Docker volume (mtime обновлялся) |
| AI-agent / knowledge / in-call | ✅ PUT | ✅ GET | ✅ merge | ✅ восстановлено (ADM-58) | ✅ в конфиге |
| Access policy | ✅ PUT | ✅ GET/effective | ✅ roleDefaults/deniedFeatures | ✅ восстановлено (ADM-58) | ✅ в конфиге |
| Webhooks | ✅ PUT | ✅ GET+recentDeliveries | ✅ test (реальная доставка 200) | ✅ `[]` | ✅ в конфиге |
| Providers | ✅ PUT | ✅ GET/status | ✅ external-слоты | ✅ builtin (ADM-55) | ✅ в конфиге |
| Файл | ✅ upload | ✅ signed URL/bytes | — | TTL 24 ч (API удаления нет — by design) | ✅ `.data/files` |
| Запись | ✅ upload (temp config) | ✅ list/download | — | ✅ DELETE в тесте | ✅ `.data/recordings` |
| Audit | ✅ пишется всеми действиями | ✅ фильтры/пагинация | — | append-only | ✅ `audit/demo.jsonl` |

**Изменения стенда в ходе QA и восстановление.** Временно включались
`features.fileShare/recording` + `session.*` (для файлов/записи), менялись
AI/knowledge/in-call/webhooks/providers/access-policy, выполнялось сохранение конфига из
редактора. Всё восстановлено и сверено: финальный `etag` demo-конфига =
`27fb70da71de9dd16df50768a09e77377c4b13fcaaf53e10669edfcf201bd376` (тот же, что в начале QA),
`fileShare=false`, `recording=false`, `footer=""`, `primary=#2563eb`. QA-комнаты удалены
(остались только созданные предоставленными инструментами/другими сессиями
`Remote deploy check`, `Скриншот light/dark`). Контейнер не рестартовался: `Up (healthy)`.
Пароли/ключи не менялись. Запись, созданная в тесте, удалена; тестовый файл останется до TTL 24 ч.

---

## 6. Артефакты и запуск регрессии

| Файл | Что |
|---|---|
| `qa/functional/lib.mjs` | харнесс: fetch, suite/checks, WS-клиент, Chrome, retry 429 |
| `qa/functional/01-infra.mjs` | HTTPS/TLS/страницы/заголовки/утечки/OpenAPI/docs |
| `qa/functional/02-api-public.mjs` | публичный REST (комнаты/join/пароли/файлы/записи/STT/ассистент/лимиты) |
| `qa/functional/03-ws.mjs` | WS-сигналинг, 38 проверок, 2 независимых клиента |
| `qa/functional/04-admin.mjs` | admin API + восстановление настроек |
| `qa/functional/05-browser-widget.mjs` | demo/embed: 2 участника, чат, файл, темы, запись, ассистент, iframe, offline |
| `qa/functional/06-browser-dashboard.mjs` | `/dashboard/`: 10 экранов, CRUD, audit/access/integrations/settings |
| `qa/functional/07-browser-editor.mjs` | `/editor/`: 16 секций, JSON/import, save-to-tenant, restore |
| `qa/functional/08-regression-fixes.mjs` | ретест fix-билда: F-02/F-03/F-05/F-06, роли CRUD/roleId, docs explorer, /guardify.html |
| `qa/functional/09-browser-fixbuild.mjs` | ретест в браузере: F-01 preview, F-04 палитра, island, /guardify.html, /docs curl |
| `qa/functional/debug-*.mjs` | изолированные репро (join, chat, control embed) для разбора UI-флаки |
| `qa/functional/results/*-latest.json` | машинные результаты последних прогонов |
| `qa/functional/results/editor-bundle.js` | доказательство F-01 (строка `localhost:5173`) |
| `qa/functional/screens/*.png` | скриншоты demo/embed/chat/spotlight/recording/dashboard/editor |

```bash
export AGIL_ADMIN_KEY='demo-…'   # ssh root@95.182.94.7 'cat /root/agil-admin-key.txt'
node qa/functional/01-infra.mjs
node qa/functional/02-api-public.mjs
node qa/functional/03-ws.mjs
node qa/functional/04-admin.mjs
node qa/functional/05-browser-widget.mjs
node qa/functional/06-browser-dashboard.mjs
node qa/functional/07-browser-editor.mjs
```

---

## 7. Ограничения MVP (не баги)

1. **STT серверный не настроен** (`AGIL_STT_URL` нет) → 501 `stt_not_configured`; браузерный
   Web Speech в headless недоступен. Субтитры/транскрипт ограничены; кнопка скрыта флагом.
2. **Блюр/замена фона** зависят от CDN MediaPipe; при недоступности — degraded-предупреждение
   (дизайн graceful degradation). См. флаки UI-22.
3. **Mesh-звонок** ограничен лимитом тенанта (в demo `maxParticipants=12`); тестировался
   2-участниковый сценарий. SFU/50+ — roadmap.
4. **Fullscreen/PiP/new-tab** требуют реального user gesture — в headless ограничены;
   нужна headed-проверка.
5. **Комнаты in-memory**: после рестарта контейнера активные комнаты пропадают (конфиги,
   файлы, записи, аудит — в volume). Задокументированное поведение, не баг.
6. **Admin API/OpenAPI публичны на demo-стенде** — осознанная конфигурация (R-01).

---

## 8. Что не проверено / следующая итерация

- [ ] Headed-Chrome: fullscreen/PiP, стабильность inline join и блюра (флаки UI-09/10/22).
- [ ] External chat mode (`session.chat.ui='external'`/`hidden`) и внешний чат провайдера.
- [ ] Внешние AI/knowledge/STT провайдеры на реальных endpoint (сейчас проверены пути
      «недоступен → 501/502», SSRF, подпись; успешный ответ компании не проверялся).
- [ ] Persistence после рестарта контейнера (по согласованию; сейчас — чтение volume).
- [ ] Mesh 4–8, TURN-relay при ограничении UDP, деградация сети (low bandwidth) end-to-end.
- [ ] Гео/лобби/пароли в UI (API-часть проверена; в demo гео выключено).
- [ ] MCP HTTP/stdio (`/mcp` выключен, health `mcpHttpEnabled=false`) — вне веб-стенда.
- [ ] Мобильные viewport/адаптив, a11y-аудит, i18n en-локаль.
- [ ] Admin rate-limit 120/мин под нагрузкой (наблюдали 429 при параллельных прогонах).

---

## 9. Итог для заказчика

**Показывать можно:** звонок (2 участника), видео, чат и файлы, темы/пресеты, запись,
локальный AI-ассистент со spotlight, панель разработчика (комнаты/чат-бот/аудит/безопасность/
интеграции/провайдеры/вебхуки/access-policy/settings), редактор (тема/токены/JSON/импорт/сохранение).

**Исправить до показа:** F-01 (превью редактора на стенде выглядит сломанным), F-02 (битые
markdown-ссылки в доках), F-03 (гейт файлов на сервере по feature-флагу — безопасность/консистентность).
F-04 — поправить demo-переключатель схемы (или явно ограничить его роль).

**P0 нет.** После фиксов достаточно ретеста: `01-infra`, `07-browser-editor` (+ выборочно
`02-api-public` для F-03). Рекомендуется завести в CI проверки F-01/F-02 (наличие same-origin
preview URL и `/docs/api.md` → 200) — они дешёвые и ловят регрессии деплоя.

---

## 10. Ретест fix-билда (13.11.2026, вечер) — v1.1

### 10.1 Что проверено и цифры

Fix-билд задеплоен на `https://calls.guardify.ru` (контейнер `Up (healthy)`, health содержит
новые поля `mediaMode`, `scaleConfigured`). Прогон ретеста: **120 проверок, 115 pass, 5 fail**
(3 бага: F-01 — 3 проверки, F-07 — 1, F-09 — 1).

| Набор ретеста | Pass/Всего | Что покрывает |
|---|---|---|
| `01-infra.mjs` | **48/48** | docs markdown, JSON 404, заголовки, страницы, OpenAPI |
| `08-regression-fixes.mjs` | **40/41** | F-02/F-03/F-05/F-06 + новые роли CRUD/roleId + guardify/docs explorer |
| `09-browser-fixbuild.mjs` | **10/14** | F-01 preview, F-04 палитра, island, /guardify.html, /docs explorer+curl |
| `07-browser-editor.mjs` | **17/17** | 16 секций редактора, JSON/import, save-to-tenant, restore |

Артефакты ретеста: `qa/functional/results/08-regression-fixes-*.json`,
`09-browser-fixbuild-*.json`, `01-infra-*.json`, `07-browser-editor-*.json`,
скриншоты `qa/functional/screens/fix-*.png`.

### 10.2 Статусы багов после фиксов

| ID | Sev | Статус | Факт после фикса | Доказательство |
|---|---|---|---|---|
| **F-01** | **P1** | ❌ **НЕ ЗАКРЫТ (переоткрыт)** | Same-origin исправлен: `iframe src=https://calls.guardify.ru/embed.html` (localhost:5173 исчез, ERR_CONNECTION_REFUSED нет). **Но** URL не содержит `room`/`tenant` → `embed.html` отвечает «Не указан параметр room. Откройте /embed.html?room=<id>», виджет не монтируется, `agil:ready` не приходит, через ~3.2 с статус `preview offline — JSON/export work`. Превью по-прежнему не работает | `09-browser-fixbuild-*.json` (FIX-F01-SRC — pass, FIX-F01-ROOM/READY/FRAME — fail; `preview frame body: … Не указан параметр room …`), `screens/fix-editor-preview-*.png`; `apps/editor/src/preview.ts` (resolvePreviewUrl возвращает `/embed.html` без параметров) |
| **F-02** | P2 | ✅ **ЗАКРЫТ** | `/docs/api.md`, `/docs/api-guide.md`, `/docs/security-audit.md`, `/docs/features.md` → 200 `text/markdown; charset=utf-8` с содержимым; ссылки из `/docs` explorer живые | `01-infra-*.json` (INFRA-DOCS-MD/LIST), `08-regression-fixes-*.json` (REG-DOCS*) |
| **F-03** | P2 | ✅ **ЗАКРЫТ** | guest при `features.fileShare=false` → **403 `feature_disabled`**; host при выключенном флаге → 403; после включения флага + права: upload 200, signed download 200 (байты совпадают), admin files list содержит файл; токен с pinned `[chat]` → 403; записи по-прежнему 403 (session.recording=false), STT 501; конфиг восстановлен | `08-regression-fixes-*.json` (REG-F03-GUEST-OFF/HOST-OFF/ENABLED/ADMIN-LIST/PINNED/RECORDINGS/STT/RESTORE) |
| **F-04** | P3 | ✅ **ЗАКРЫТ** | light→dark меняет computed-палитру: `bg #f3f6fb→#080d16`, `surface #ffffff→#111a2b`, `text #0f172a→#f1f5f9` (не только `data-theme`) | `09-browser-fixbuild-*.json` (FIX-F04-PALETTE), `screens/fix-demo-dark-*.png` |
| **F-05** | P3 | ✅ **ЗАКРЫТ** | `/nonexistent`, `/mcp`, `/api/whatever`, `/v1/no-such-endpoint` → **404 `application/json` `not_found`** (без SPA HTML) | `08-regression-fixes-*.json` (REG-404*), `01-infra-*.json` (INFRA-404) |
| **F-06** | P3 | ✅ **ЗАКРЫТ** | На `/`, `/v1/health`, `/embed.html` по одному `referrer-policy`, `strict-transport-security`, `x-content-type-options`, `permissions-policy`; дублируется только `vary` (добавляет компрессия Caddy — допустимо) | `08-regression-fixes-*.json` (REG-HDR*) |
| **F-07** | **P2** | 🆕 **НОВЫЙ** | На задеплоенном `/editor/` ссылки в шапке «API docs», «OpenAPI», «API guide», «API.md» ведут на **`http://localhost:8787/...`** (у посетителя стенда это мёртвые ссылки). Причина: `loadServerSettings()` дефолт `DEFAULT_SERVER_URL='http://localhost:8787'` (`apps/editor/src/api.ts`), `updateApiLinks()` в `apps/editor/src/main.ts:153-170` строит href от него; same-origin fallback не применён (в отличие от preview) | `09-browser-fixbuild-*.json` (FIX-EDITOR-LINKS, `guide=http://localhost:8787/docs/api-guide.md api=http://localhost:8787/docs/api.md`); для сравнения dashboard-ссылки корректно same-origin |
| **F-09** | P3 | 🆕 **НОВЫЙ** | Новый код ошибки `403 feature_disabled` (введён фиксом F-03) отсутствует в таблице кодов `docs/api.md` §13 и в `/docs/api-guide.md` (поиск `feature_disabled` → 0 вхождений). Клиент не знает семантику кода из документации | `08-regression-fixes-*.json` (REG-DOCS-FEATURE — fail), `docs/api.md §13` |

Прочие замечания ретеста (не баги):
- PUT `/v1/admin/roles/{id}` требует **полное** определение роли (`name`, `baseRole`, `features`),
  частичный апдейт → 400 `validation_failed`; в доке пример «изменить роль» стоит дополнить.
- `welcome.self` отдаёт `roleName` (как обещает docs), но **не** `roleId`; `roleId` есть в ответе
  `/v1/tokens`. Если нужен в welcome — добавить, но контракт этого не требует.
- В demo-конфиге фикс-билда `branding.logoUrl`/`faviconUrl` пустые — проверка `INFRA-BRANDING`
  стали вакуумной (не дефект, но стоит держать эталонный конфиг).
- Во время ретеста параллельные QA-сессии меняли `features.recording/captions/aiAssistant` и
  возвращали назад; мой финальный `etag` совпал с базовым fix-билда
  (`379c63f19025a7a4bb6f398d09a2cd8c53f77b0e3e9ff685af84f0e809500f18`), мои мутации
  (`features.fileShare`, `layout.controlsStyle`, сохранение из редактора) восстановлены.

### 10.3 Новые функции — проверены (багов не найдено)

| Функция | Статус | Проверки |
|---|---|---|
| **Роли CRUD** `GET/POST/PUT/DELETE /v1/admin/roles` | ✅ | список 3 builtin с `baseRole/features/canModerate/builtin`; создание кастомной роли; полный PUT меняет name/features; дубль id → 409; невалидный `baseRole` → 400; удаление builtin → 403; аудит-события (REG-ROLES-*) |
| **Токен с `roleId`** | ✅ | `/v1/tokens` → `roleId`, `roleName`, `permissions` только из фич роли (с учётом включённых `features.*`); невалидный/удалённый `roleId` → 400 |
| **`welcome.self.roleName`** | ✅ | имя кастомной роли в welcome; при удалении роли живой токен откатывается на `baseRole` (moderator), новые токены с удалённой ролью → 400 |
| **Island-лейаут** (`layout.controlsStyle='island'`) | ✅ | `[data-agil-controls]` `data-agil-controls-style="island"`, компактные основные кнопки + «more», состояние восстановлено после теста (FIX-ISLAND, FIX-ISLAND-RESTORE) |
| **`/guardify.html`** (новая демо) | ✅ | страница + `/assets/guardify-*.js/.css` 200; бренд GUARDIFY; создание комнаты через UI; встроенный island-виджет, `chrome:none`, чат скрыт; звонок join-ится; консоль чистая (FIX-GUARDIFY-*) |
| **`/docs` explorer (блоки + curl)** | ✅ | блоки «С чего начать (5 шагов)», «Авторизация», «Коды ошибок», «Ссылки»; кнопка Copy curl строит `curl -X …` и копирует (FIX-DOCS-BLOCKS/CURL) |
| **Новая секция редактора «Масштабирование»** + ссылки API guide/API.md в шапке | ✅ (16 секций) | все секции рендерят контролы; ссылки присутствуют (но их href — F-07) |
| **OpenAPI / health** | ✅ | admin OpenAPI 49 путей включая `/v1/admin/roles{,/{id}}`; публичный — 18 путей без admin; health `mediaMode/scaleConfigured` |

### 10.4 Итог ретеста

- **Закрыто:** F-02, F-03, F-04, F-05, F-06.
- **Открыто:** **F-01 P1** (превью редактора всё ещё offline — теперь из-за отсутствия
  `?room=`/`?tenant=` в iframe URL; правка на 1 строку в `resolvePreviewUrl`), **F-07 P2**
  (ссылки редактора на localhost), **F-09 P3** (документировать `feature_disabled`).
- **Новые функции** (роли, roleId-токены/roleName, island, /guardify.html, /docs explorer,
  секция «Масштабирование») — работают; новых дефектов в них не найдено.
- **Рекомендация к следующему фиксу:** в `resolvePreviewUrl` добавить
  `?room=preview&tenant=<tenant>` (или поддержать preview-режим embed.html без комнаты);
  в `updateApiLinks` использовать `new URL('/docs', location.origin)` при пустых настройках;
  добавить `feature_disabled` в таблицу кодов. Ретест: `09-browser-fixbuild.mjs` + `01-infra.mjs`.

---

## 11. Тотальный прогон (финальный деплой c40faccb14f3) — v1.2

### 11.1 Что и как проверялось

Финальный деплой: `c40faccb14f3`, контейнер `Up (healthy)`. Проверены: фиксы F-01/F-07,
контраст смешанных preset×scheme, seeded-данные панели, новые функции (роли, island,
`/guardify.html`, `/docs` explorer), а также **тотальный клик-прогон** публичных страниц
(предоставленный `scripts/qa-clickall.mjs` + его копия `qa/functional/qa-clickall-fixed.mjs`
с поддержкой query-параметров и localStorage), **60 живых участников** (обход лимита join через
admin-токены), сценарии обрыва/офлайна/кика/лока/full/rate-limit, клавиатурные шорткаты,
визуальные проверки (CLS, растянутые/битые картинки, эмодзи, overflow) и скриншоты.

### 11.2 Цифры финального прогона

| Набор | Pass/Всего | Что покрыто |
|---|---|---|
| `09-browser-fixbuild.mjs` | **14/14** | F-01 (preview room/tenant + ready), F-07 (same-origin ссылки), F-04 палитра, island, `/guardify.html`, `/docs` explorer+curl, консоль |
| `12-contrast.mjs` | **2/3** | demo: 5 пресетов × light/dark/auto+OS-dark — 15/15 комбинаций ≥4.5; embed join — pass; guardify landing — 1 кнопка 3.48 (F-14) |
| `13-dashboard-seeded.mjs` | **20/20** | обзор/KPI/страны, комнаты (18, из них 12 seed), room detail (5 вкладок, транскрипт/заметки/compliance/записи, live-участники), записи 8, роли 3 custom, аудит (total из UI = API), консоль/визуал |
| `14-clickall-auth.mjs` | **5/6** | dashboard: 500 кликов по 9 маршрутам + room detail, editor: 398 кликов по 16 секциям; 0 pageerror, 0 битых/растянутых картинок, нет overflow, CLS<0.25; CLK-01 переклассифицирован (харнесс: room-ссылка не открылась из-за 429-шторма) |
| `15-connection-smoke.mjs` | **11/13** | offline/reconnect/recovered chat, kick, lock, room_full, join 429 в UI, CLS при тостах — pass; keyboard-шорткаты — флаки (F-17) |
| `16-live-60.mjs` | **10/11** | 60 WS-участников, observer join 1.2 c, счётчик 61, панель 61, виртуализация 50 плиток + «+11 зрителей», fps 62, 0 console/pageerror; тост-флуд — fail (F-12) |
| `scripts/qa-clickall.mjs` (публичные) | **6 запусков, без fail** | `/` и `/guardify.html` × light/dark: 0 console/pageerror, 0 broken/stretched, 0 overflow, CLS≈0, эмодзи ok; `/embed.html?...` — 29 целей, 400-ответы `password_required` — ожидаемо для seed-комнаты с паролем |
| `scripts/live-room.mjs --users=60` | ⚠️ 10/60 | Ограничение стенда: join rate-limit 10/мин на IP+комнату (F-13) |

**Итого формальных проверок финального прогона: 67** (+ качественный клик-прогон и визуальный
аудит, 30+ скриншотов). PASS: 62, FAIL: 5 = 5 дефектов/флаки (F-12, F-14, F-17, а также
CLK-01/F-18 и F-13 — см. ниже).

### 11.3 Подтверждённые PASS (финальный билд)

- **F-01 закрыт:** `/editor/` iframe src = `https://calls.guardify.ru/embed.html?room=preview&tenant=demo&_r=…`,
  статус `preview подключён` (agil:ready), overlay скрыт, 0 console ошибок, нет запросов к localhost.
- **F-07 закрыт:** ссылки шапки редактора (`API docs/OpenAPI/API guide/API.md`) ведут на
  `https://calls.guardify.ru/...` и отдают 200; 0 console ошибок.
- **Контраст demo:** все 15 комбинаций (light/dark/corporate/night/minimal × light/dark/auto+OS-dark)
  ≥4.5; минимум по прогону — **4.57** (nav link `rgb(51,109,208)` на `rgb(243,246,251)`); палитра
  реально меняется при light→dark (bg/surface/text). Embed join screen — pass.
- **Seeded-данные панели:** 12 seed-комнат видны и открываются; транскрипт (6–18 сегментов),
  заметки (summary/выдержки/задачи), compliance (0 findings + отчёт), записи в комнате,
  live-участники в room detail, 8 записей (KPI «Записей 8», скачивание signed URL 200),
  3 кастомные роли (Стример/Оператор/Наблюдатель) в экране «Доступы», аудит: total из UI
  совпал с API (на момент проверки 496, к концу дня >=1000 из-за параллельных сессий),
  пагинация/фильтры работают, console/overflow/битых картинок нет.
- **Island-лейаут и `/guardify.html`:** без регрессий — `data-agil-controls-style="island"`,
  компактные контролы + «more», guardify создаёт комнату, встраивает island-виджет, `chrome:none`,
  чат скрыт, 0 console/pageerror; `/docs` explorer блоки «С чего начать/Авторизация/Коды
  ошибок/Ссылки» + Copy curl работают.
- **Соединение:** offline 6 c → reconnect (state joined), сообщение, отправленное пока страница
  была offline, **восстановлено из истории** после reconnect (без потери); чат до обрыва доставлен;
  viewport не прыгает; kick → state left без падения; lock/room_full/join-429 показывают понятные
  сообщения; CLS при входящем чате/тостах <0.05.
- **Публичные страницы (clickall):** 0 console/pageerror, 0 broken/stretched images, 0 horizontal
  overflow, CLS ≈0 на `/` и `/guardify.html` в light и dark.
- **Dashboard/editor click-through:** 500+ и 398 кликов, 0 pageerror, нет битых картинок,
  растяжений, overflow, крупных layout-сдвигов; 10 скриншотов (`qa/functional/results/clickall-auth/`).

### 11.4 Новые баги/дефекты финального прогона

| ID | Sev | Зона | Что делали | Ожидаемо | Фактически | Доказательство | Гипотеза/файл |
|---|---|---|---|---|---|---|---|
| **F-12** | **P2** | widget/UI | Комната с 60 WS-участниками (admin-токены), observer в Chrome 60 c | Стабильный UI: счётчик, participants panel, аккуратные уведомления | Счётчик 61 и панель 61 — ок, виртуализация 50+«+11 зрителей» — ок, fps 62; **но** одновременно висят 4 уведомления/баннера: «Слишком много попыток входа. Подождите немного.» ×2, «Не удалось восстановить соединение…», «Переподключение… (1, 500 ms)» — они **перекрывают панель участников**; статус-чип «Переподключение…» не успокаивается | `results/16-live-60-latest.json` (LIVE-06b fail), `screens/live-60-participants-*.png` | Тост-стек без дедупликации/лимита; mesh-реконнект к 59 WS-only пирам даёт шторм ошибок. Для поддержанного mesh ≤8/12 не проявляется; нужен dedupe/coalesce тостов + лимит видимых (2–3) + не перекрывать панели |
| **F-13** | **P2 (риск/ops)** | server/config | `node scripts/live-room.mjs --users=60` (предоставленный инструмент) | 60 живых участников | Подключилось **10/60**; остальные 50 получили `429 Too many join attempts` (лимит 10/мин на IP+комнату). В 60-участниковом тесте пришлось выдавать admin-токены (без join) | `job pwsh-8c97a2eac5be46e4` вывод `participants=10/60`; `results/16-live-60-*.json` (60/60 через admin-токены) | Лимит join задокументирован (docs/api.md §12), но: (а) ломает нагрузочный инструмент; (б) офис за одним NAT в первую минуту не сможет посадить >10 участников; (в) "Too many join attempts" виден в UI как ошибка. Бэклог/ops: whitelist по admin-токенам, иной лимит для комнат с `maxParticipants>12`, либо документированный warm-up |
| **F-14** | P3 | `/guardify.html` (landing) | Контраст-аудит кнопок/ссылок/бейджей/вторичного текста | ≥4.5 | Все тексты/ссылки/badge/secondary — pass; **primary-кнопка** `#gf-create` (белый текст на `#8576ff`) = **3.48** | `results/12-contrast-latest.json` (CON-02 fail), `results/contrast-guardify-*.json`, `screens/contrast-guardify-*.png` | Стили Guardify-демо (`apps/demo/src/guardify`/assets guardify css): primary `#8576ff` слишком светлый для белого текста; затемнить primary или использовать тёмный текст (onPrimary) |
| **F-15** | P3 (типографика) | widget | clickall/контраст-прогон embed join screen | Читаемая типографика (≥12px) | 6 элементов **10.5–10.92px**: `agil-join-preview-hint`, `agil-join-server`, `agil-join-shortcuts`, `agil-settings-hint`, индикатор «ИДЁТ ЗАПИСЬ», `agil-spotlight-label` | `results/clickall/*embed_html_room-light.json` (`audit.tinyText=6`, размеры), `screens/clickall-*embed*` | `packages/widget/src/style.css`: слишком мелкие `font-size` для hint/shortcuts/server; поднять до 12–13px или увеличить контраст/вес |
| **F-16** | P3 (флаки/CDN) | widget/blur | Клавиша `b`/кнопка блюра в headless | Блюр включается **или** показывается предупреждение о деградации | В 1 из 3 прогонов blur включался (radio on), в 2 — режим не применился и предупреждения о деградации **не было** (тост про слабый канал — не про CDN) | `results/05-browser-widget-latest.json` (UI-22), `results/15-connection-smoke-latest.json` (CONN-06b) | MediaPipe CDN не всегда доступен; `onDegraded` репортит только в консоль/баннер не всегда; добавить явный toast «Блюр недоступен — CDN» и/или CSS-фолбэк |
| **F-17** | P3 (флаки/автоматизация) | widget/shortcuts | После offline/reconnect: `m`, `v`, `c`, `b` | Все шорткаты работают | `m` стабильно; `v` сработал в 1/2 прогонов; `c`/`b` в сценарии после reconnect не переключили панель/блюр (в изолированном прогоне `c` открывает чат, `b` молча не применяет blur). Итог: часть шорткатов нестабильна в headless после reconnect | `results/15-connection-smoke-latest.json` (CONN-06/06b), `qa/functional/probe-kb.mjs` | Вероятно, фокус/активный элемент после reconnect + CDN-блюр; нужна headed-проверка; для `c` — toggle-семантика (панель уже была открыта), для `b` — см. F-16. Не подтверждён как стабильный дефект |
| **F-18** | P2 | dashboard/server | Быстрый клик-прогон по 9 экранам (500 кликов) и room detail polling каждые 3 c | UI не упирается в собственный лимит, ошибки понятны | В одном прогоне `SEED` панель показала «Слишком много запросов — подождите» и вкладки комнаты не отрисовались (timeout); в clickall — 429/404 в консоли; 1081 «aborted» запросов — в основном навигационные `ERR_ABORTED` при переходах | `results/13-dashboard-seeded-2026-11-13T14-18-24-134Z.json` (SEED-FATAL), `results/14-clickall-auth-*.json` (console 429/404, failed 1081) | Admin rate limit 120/мин общий с polling: room detail каждые 3 c (20/мин) + экранные запросы + несколько вкладок/сессий легко превышают лимит; добавить defer/backoff на 429, кэш, снизить частоту polling или поднять лимит для доверенного UI |

### 11.5 Ограничения и бэклог (не баги)

- **Mesh >8–12 — вне MVP** (SFU roadmap): 60 участников — искусственный сценарий (WS-only пиры),
  поэтому reconnect-шторм F-12 ожидаем по архитектуре; дефект — в подаче уведомлений (dedupe/UI).
- **Виртуализация >50 работает** как задумано: 50 плиток + «+11 зрителей», счётчик и панель
  участников корректны (проверено на 61).
- **Join rate-limit 10/мин** — документированное поведение (F-13 — ops/бэклог).
- **Иконки/типографика/таргеты:** битых SVG/PNG нет (clickall images broken=0, stretched=0);
  типографика — F-15 (мелкие подписи); размеры тач-таргетов (<44px) не аудировались системно —
  бэклог P3.
- **Stand-конфиг меняется параллельными сквадами:** на финальной проверке `features.fileShare=true`
  и `etag=31a89e3…` (кто-то из параллельных сессий включил файлы); seeded-данные не удалялись:
  12 seed-комнат, 8 seed-записей, 3 кастомные роли на месте; rooms=13, recordings=11, audit≈1000 —
  из-за активности других сессий. Рестартов контейнера не было (Up 31 min).
- **`scripts/qa-clickall.mjs`** не поддерживает `?query` в `--page=` (парсит `split('=')`), поэтому
  для embed использована копия `qa/functional/qa-clickall-fixed.mjs` (в зоне QA) с исправлением
  и поддержкой `--storage` для auth-страниц.

### 11.6 Артефакты финального прогона

- `qa/functional/09-browser-fixbuild.mjs`, `12-contrast.mjs`, `13-dashboard-seeded.mjs`,
  `14-clickall-auth.mjs`, `15-connection-smoke.mjs`, `16-live-60.mjs`, `qa-clickall-fixed.mjs`,
  probe-* для изоляции флаки.
- `qa/functional/results/`: `09-*`, `12-contrast-*`, `13-dashboard-seeded-*`, `14-clickall-auth-*`,
  `15-connection-smoke-*`, `16-live-60-*`, `clickall/*` (JSON+скриншоты публичных страниц).
- `qa/functional/screens/`: `live-60-*`, `conn-*`, `contrast-*`, `clickall-*`, `seed-*`.

### 11.7 Итог финального деплоя

- **PASS:** F-01 и F-07 закрыты и подтверждены; F-02–F-06 закрыты ранее; контраст demo/landing
  (15/15 комбинаций) и embed — PASS; seeded-панель — 20/20; роли/island/`/guardify.html`/`/docs`
  explorer — без регрессий; публичные страницы — 0 визуальных дефектов (кроме F-15).
- **FAIL/осталось:** F-12 P2 (тост-флуд/перекрытие UI при >50 участниках), F-18 P2 (панель
  упирается в собственный rate-limit и показывает сырую ошибку), F-13 P2-ops (join-лимит мешает
  нагрузочному сценарию/NAT), F-14 P3 (контраст primary-кнопки Guardify), F-15 P3 (мелкая
  типографика), F-16/F-17 P3 (блюр/шорткаты — флаки, нужен headed-ретест).
- **Критично до показа:** P1/P0 нет; для демонстрации «много участников» лучше использовать
  реальный сценарий ≤8 (mesh MVP) или подготовить комнату через admin-токены (F-13) и
  починить дедуп уведомлений (F-12), чтобы не показывать заказчику «Переподключение…» и
  наложенные ошибки. Показывать редактор теперь можно (F-01/F-07 закрыты).
