Repository navigation
Description
Актуальная доработка по замечаниям инженера — 2026-09-11
Доработка той же VOCA-13, в ветке VOCA-13 и открытом PR #14. Это не новый тикет и не реализация голосового этапа. Требования ниже уточняют организацию первоначального фундамента и заменяют противоречащие им пути/структуру первого плана; остальные требования к поведению, диагностике, надёжности и экономии тестов сохраняются.
Замечание инженера
ну пока организация кода выглядит очень плохо. Я вижу файлы огромные содержащие много кода и методов. Давай для бэкенда используем Simple Clean Architecture подход а для веба FSD подход. При этом нужно явно разделить backend и frontend, а то сейчас все намешано в кучу.
После анализа причин и применимости инженер подтвердил: «давай обновляй задачу и приступай к plan и затем /do по применению правок».
Проблема и подтверждённая причина
На исходном для доработки commit c127d19f6f0d2d94b8a16514eec4c3a7adef3562 рабочая session-основа есть, но границы ответственности выражены недостаточно:
src/voca/adapters/control.py(374 строки) объединяет HTTP bootstrap, WS lifecycle, schemas/validation, routing команд, wire responses и deadlines. На строке 115 transport вызываетservice.store.get, обходя application boundary.src/voca/core/session.pyсовмещает сущность, storage port, сценарии, ошибки и parsing wire cursoreN.src/voca/diagnostics.py(222 строки) совмещает configuration, formatter/filter/rotation/failure policy и instrumentation операций.web/src/App.tsx(289 строк) совмещает UI, storage, socket ownership, state projection/dedup и пользовательские сценарии;web/src/control.tsсмешивает HTTP, WS, команды, wire types и decoding.- Backend packaging/src/tests находятся в корне, frontend внутри web/: границы двух приложений структурно несимметричны.
Число строк — свидетельство мест для анализа, не квота и не единственный критерий. Простое перемещение всего App в один большой hook или разрезание файла по N строк не решает проблему.
Согласованные рекомендации к реализации
-
Явные приложения backend/ и frontend/ в одном репозитории. Перенести Python src/tests/pyproject/uv.lock/.python-version в backend/, frontend web/ в frontend/ с package.json/pnpm-lock.yaml. Общие docs, CI и root README остаются общими. Обновить команды, paths, ignores, CI, bootstrap и documentation. Python — uv, frontend — pnpm; без нового workspace/build orchestrator или второго бизнес-backend без потребителя. Между приложениями только HTTP/WS-контракт.
-
Backend: Simple Clean Architecture. domain — сущности/независимые правила; application — use cases и узкие требуемые порты; presentation — HTTP/WS schemas/validation/transport/error mapping; infrastructure — SQLite и конкретная диагностика; bootstrap — composition/lifespan. Исходные зависимости направлены внутрь. Domain не знает FastAPI, SQL, handlers, файлов логов и wire envelopes; application не импортирует конкретную инфраструктуру. Transport вызывает use cases, не repository через service.store. Wire parsing/mapping остаются снаружи; внутри проверенные простые значения. Resource open/close — ответственность composition, не бизнес-порта. Для instrumentation необходимый маленький контракт, без зависимости domain от реализации логирования.
-
Декомпозиция backend по ответственности. Разделить HTTP и WS входы, decoder/schema, wire presentation, прикладные сценарии, persistence и instrumentation/configuration там, где меняются по разным причинам. Связанный небольшой код оставлять вместе. Не требовать отдельного класса/файла на каждую функцию, Input/Output/Interactor/Presenter wrappers на каждый вызов. Не вводить generic Repository/UoW, DI/plugin framework, CQRS/event bus и пустые реализации будущих adapters.
-
Frontend: Feature-Sliced Design. Для существующего экрана начать с app, pages/session-console и только необходимого shared. В page slice разделить ui, model, api и действительно нужный lib; публичный API слайса. App только собирает приложение. UI не владеет HTTP/WS/localStorage. Применение событий/проверка cursor отделены от транспорта; lifecycle socket имеет одного владельца, callbacks старого socket не меняют новое состояние. Не переносить всю сложность App в один giant hook. Слайсы не импортируют внутренности соседних слайсов; зависимости идут на нижние слои через public API. Shared не склад session-domain логики. Entities/features добавляются по реальной ответственности/повторному использованию, не ради полного дерева FSD; не создавать пустые widgets/processes или feature на каждую кнопку. React/TypeScript/Vite/pnpm сохраняются, новый state manager/UI-kit не требуется.
-
Сохранить поведение и диагностику. Не менять публичные маршруты, wire envelope/error/cursor semantics, SQLite schema, session identity/recovery, состояние off/disconnected и существующие logging guarantees ради реорганизации. Backend/frontend separation не должно подменять старую БД пустой из нового cwd: явно описать и проверить сохранение выбранного VOCA_DB_PATH, не перемещать/удалять пользовательские данные автоматически. Startup/shutdown, cancellation/timeout, correlation, WARNING для rejected commands, bounded safe logs и все исправления первого review должны сохраниться. LangSmith/gates и будущий голос по-прежнему не входят.
-
Тесты сохраняют поведение, не зеркалят слои. Перенести и адаптировать существующую meaningful real-SQLite suite; не удалять/ослаблять проверки и не создавать копию на каждом слое. Добавлять focused tests только для самостоятельных правил/реального риска (например event projection/cursor). Минимальная проверка запрещённых imports/public API допустима готовым инструментом или короткой узкой проверкой, но не собственный architecture testing framework. Не создавать mock infrastructure, coverage/LOC квоты и набор harnesses ради структуры. На review проверить нет ли новых бессодержательных обёрток, чрезмерно раздробленных файлов, giant modules/hooks, unsafe cross-imports и дублирования тестов.
-
Синхронизировать архитектурные документы. Обновить code architecture/testing/diagnostics/ADR/README/roadmap и карту всей очереди VOCA-4–12/1 под Simple Clean + FSD + backend/frontend. Исходные системные контракты VOCA-2 не переизобретаются. Изменение принятого решения фиксировать честно в ADR; не выдавать первый непрошедший приёмку layout за окончательно принятый. Не переписывать исторический ADR-0001.
Дополнительная приёмка доработки
- Открыв корень, инженер видит backend/ и frontend/ с собственными зависимостями/конфигами/командами; код приложений не остаётся в прежних src/tests/web.
- По HTTP → use case → storage и WS → use case → response можно пройти без обхода application boundary; domain/application не импортируют FastAPI/SQLite/logging handlers и другую конкретную infrastructure.
- В frontend App только собирает страницу; presentation, модель применения событий, API/transport и persistence имеют ясные границы. FSD layers/slices/public API и направление imports соответствуют документам; нет пустых слоёв для вида.
- Создание, resume/reconnect, repeat command, restart с тем же DB path, cursor и error behavior не регрессировали; SQLite schema и wire contract сохранены. Browser/live walkthrough проводится на /review, а /do не объявляет его пройденным по API/build.
- Correlation/terminal outcomes, безопасные ошибки, logging level/rotation/degraded behavior сохранены и подтверждены существующими/узкими необходимыми проверками.
- Полный актуализированный format/lint/type/test/build/CI набор зелёный; осмысленные тесты первого раунда не ослаблены. Нет искусственного требования увеличивать число тестов вместе с числом файлов.
- Независимое review проверило именно распределение ответственности и import boundaries, а не только прохождение проверок. Декомпозиция не свелась к переименованию папок или giant hook.
- Документы и команды соответствуют реальной новой структуре. Все изменения отправлены в тот же PR Separate session backend and frontend architecture #14; инженер получает его повторно на review. Merge и следующие задачи не запускаются.
Основания архитектурных решений
- Clean Architecture / dependency rule: https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
- FSD layers: https://feature-sliced.design/docs/reference/layers
- FSD slices/segments: https://feature-sliced.design/docs/reference/slices-segments
- FSD public API: https://feature-sliced.design/docs/reference/public-api
- FSD incremental migration/pages-first: https://feature-sliced.design/docs/guides/migration/from-custom
Исходный полный объём фундамента — сохраняется с уточнениями выше
Проблема
Согласованная в VOCA-2 (#2, PR #3) архитектура описывает системные контракты, но ещё не закреплена организацией исполняемого кода, диагностикой и экономной архитектурой тестов. Начало реализации всей очереди без этой основы создаёт риск сильной связанности компонентов, непрозрачных ошибок и тестовой инфраструктуры, которая становится сложнее полезного кода.
Ожидается: расширяемая, поддерживаемая и стабильная архитектура с заменяемыми компонентами, понятными точками диагностики и обоснованными тестами. Инженер должен самостоятельно просмотреть документацию и работающую основу в одном PR до продолжения голосовой реализации.
Причина
В main f1005a90bc2df365937bdb6c8d68b32963699f31 находятся восемь Markdown-документов и notification workflow. Прикладного кода v3, зависимостей, конфигурации запуска и тестов ещё нет. Системные контракты уже определяют владельца turn, provider interfaces, cancellation, control protocol и durable delivery; пробел — перевод этих решений в модули, граф зависимостей, управление ресурсами и проверяемую композицию.
Это не диагноз плохого существующего кода v3: его нет. Не требуется исследовать или переносить v2.
Путь
Одна фундаментальная задача перед VOCA-4. Анализ охватывает всю очередь: #4, #5, #6, #7, #8, #9, #10, #11, #12 и существующий эксперимент #1. Реализуется только согласованный ниже фундамент, не вся очередь.
1. Архитектура кода
- Один Python/FastAPI backend; явная сборка приложения и направление зависимостей. Прикладная логика не импортирует FastAPI/FastRTC, SDK внешних провайдеров или конкретный SQLite adapter.
- Небольшие контракты на реально заменяемых границах: transport, STT/LLM/TTS, внешние агенты, хранение и tracing. Различия capabilities, ошибок, streaming/cancel не скрываются под обещанием совместимости по URL.
- Каждый слой и абстракция объясняются конкретным потребителем или задачей очереди. Будущая задача оправдывает описание точки расширения, но не создание неиспользуемого Protocol, пустого класса или fake adapter сейчас.
- Не вводить универсальный Repository[T], общий agent gateway, plugin framework, DI-контейнер, брокер или вторую бизнес-state-machine в UI.
- Для состояния и ресурсов определены владельцы, lifecycle, startup/shutdown, границы транзакций, конкурентный доступ, timeouts, cancellation, late events и restart recovery.
- Голосовая generation, внешние поручения и tracing имеют независимые области отказа/отмены. Barge-in не отменяет поручение; trace outage не должен останавливать микрофон. Не объединять всё в один fail-fast TaskGroup и не ждать завершения старого inference перед новым capture.
- Составить карту всех задач очереди: затрагиваемые модули, сохраняемые границы, будущие проверки. Не переписывать заново все документы VOCA-2 и не реализовывать будущие функции.
- Документы архитектуры кода, тестирования и диагностики, ADR с согласованными решениями/альтернативами, актуальная навигация README/index и место фундамента в roadmap должны соответствовать реальному каркасу.
2. Минимальный исполняемый каркас — согласован первый вариант
- Python/FastAPI,
pyproject.toml, один Python lockfile; предложенный и включённый в согласованный пакет инструмент Python — uv. Воспроизводимый bootstrap, без установки зависимостей в системный Python. - React + TypeScript + Vite; пакетный менеджер frontend — pnpm, с
pnpm-lock.yaml. Пока без отдельного state-management framework и UI-kit. Vite — средство разработки/сборки, не второй бизнес-backend. - Небольшая диагностическая панель: создать сессию, увидеть её ID/состояние, переподключиться и восстановить существующую сессию.
- Настоящий путь: HTTP → прикладная логика → SQLite → WebSocket session.snapshot → восстановление после перезапуска backend.
- Реальные startup/shutdown, минимальная достаточная схема хранения и узкая используемая storage boundary. Не создавать будущую task/delivery модель заранее.
- Реализованное подмножество control protocol явно описано. Repeat resume не создаёт новую session. Невалидный ввод, неизвестный ID и неподдерживаемый start дают явные ошибки.
- Control WS сам по себе не означает готовое голосовое соединение: conversation остаётся off, без WebRTC нельзя объявлять voice connection connected/listening. Не создавать фиктивные messages/turns/tasks или audio.
- Нет synthetic turn, scripted LLM, STT/LLM/TTS/Pi/LangSmith adapters и их заглушек ради заполнения дерева.
- Офлайн означает отсутствие внешних сервисов при запуске и проверках после bootstrap. Первичная установка зависимостей требует registry либо подготовленного cache; air-gapped bootstrap без артефактов не обещается.
3. Логирование всех этапов — обязательная часть архитектуры
- Спроектировать диагностику всей будущей цепочки: компоненты и операции, начало, успешное завершение, ошибки, отмена, timeout, восстановление, очереди и отбрасывание поздних событий.
- Структурированные события содержат идентификатор события/операции, компонент, исход, длительность там, где применимо, и безопасную причину ошибки. Корреляция по session_id; turn_id/generation_id/task_id/trace IDs добавляются по мере появления соответствующих сущностей, без фиктивных IDs в каркасе.
- Единые уровни и настраиваемая подробность по компонентам. Локальная диагностика работает независимо от LangSmith.
- Разделить назначения диагностических логов, SQLite state и LangSmith traces, не копировать бессмысленно полный payload во все три. Наличие локальных логов не заменяет обязательный H7 первого голосового этапа.
- Credentials исключаются до записи; безопасная политика полей/ошибок и контролируемое включение чувствительного содержимого. Никаких дампов auth configs, environment или реальных секретов для проверки.
- Ограниченный объём, явная политика хранения/ротации, поведение при ошибке записи. Не логировать каждый аудиосэмпл или токен по умолчанию. Не строить собственную платформу observability.
- В текущем каркасе реализовать диагностику всех существующих этапов session-пути, startup/shutdown и ошибок. Для будущих компонентов зафиксировать карту событий и точек диагностики, не писать неиспользуемые реализации.
4. Архитектура тестов без разрастания
- Матрица «риск → самый дешёвый достаточный уровень»: unit, contract, integration, немного реальных browser/hardware проверок по мере появления функций. Один и тот же сценарий не дублируется механически на каждом уровне.
- Проверки устойчивого публичного поведения, а не выбранных классов, порядка всех внутренних вызовов или точного текста каждой строки лога.
- Небольшая начальная suite с настоящим временным SQLite: session create/snapshot/restart/resume, repeat resume, malformed/unknown/unsupported input, startup/shutdown и недоступный DB path. Не подставлять незаметно memory store при ошибке диска.
- Проверки логирования защищают диагностически полезные поля/корреляцию и отсутствие синтетического секретного маркера, а не snapshot всей трассы с нестабильными IDs.
- Никакого обязательного процента coverage, квоты тестов или искусственного отношения test/production LOC. Каждый тест должен назвать предотвращаемую ошибку. Реальное дублирование устранять при ревью.
- Минимум fixtures, mocks и helpers; выделять их по реальному повторению. Не создавать fake STT/LLM/TTS/Pi, тесты пустых интерфейсов или собственный test framework. Не использовать sleep для синхронизации, если нужны управляемые события.
- Обычная suite не требует GPU, моделей, credentials и внешних сервисов. Hardware/API experiments запускаются отдельно и отсутствие оборудования не выдаётся за успешную проверку.
- Один документированный набор format/lint/type/tests для реально существующего кода. Проверки должны использовать настоящую инициализацию/освобождение приложения, не обходить lifespan.
Исследованные основания
В планировании проверены первоисточники; в документации зафиксировать конкретную применимость и ограничения, а не импортировать архитектуру из учебника целиком:
- Python asyncio cancellation/TaskGroup: https://docs.python.org/3.13/library/asyncio-task.html#task-groups
- FastAPI lifespan/testing: https://fastapi.tiangolo.com/advanced/events/ и https://fastapi.tiangolo.com/advanced/testing-events/
- HTTPX ASGI lifespan caveat: https://www.python-httpx.org/advanced/transports/#asgi-startup-and-shutdown
- pytest src layout/import practices: https://docs.pytest.org/en/stable/explanation/goodpractices.html
- Стоимость storage/service-layer абстракций: https://www.cosmicpython.com/book/chapter_02_repository.html и https://www.cosmicpython.com/book/chapter_04_service_layer.html
- Проверять поведение, не реализацию: https://testing.googleblog.com/2013/08/testing-on-toilet-test-behavior-not.html
Не входит
Полноценный голос и synthetic turn; внешние provider/agent adapters; загрузка моделей и inference/Pi эксперименты; установка или покупка LangSmith; замена согласованного продукта; прохождение H1/H2/H7; ремонт v2 и посторонних служб; реализация VOCA-4–12/1; merge PR. LangSmith entitlement/image access, размещение и ресурсные ограничения остаются блокерами будущего голосового этапа, но не этого явно офлайн-фундамента.
Критерии готовности
- При чтении архитектуры видны модули, разрешённое направление зависимостей, владельцы ресурсов и путь реализации каждого тикета очереди. Реализованное отделено от будущего.
- После документированного bootstrap приложение и диагностическая панель запускаются; проверки воспроизводимы, не используют реальные модели/GPU/секреты/внешние сервисы.
- Пользователь создаёт сессию в панели, получает ID/snapshot, перезапускает backend на том же SQLite и восстанавливает ту же session. Проверен именно browser-вход, не только API.
- Repeat resume не создаёт вторую session и не включает разговор; невалидные команды и неизвестный ID дают понятные ошибки без ложной готовности голоса.
- Startup/shutdown действительно открывают/освобождают ресурсы; недоступная БД не подменяется скрытым memory store.
- По логам одного сценария можно проследить его этапы, корреляцию и найти компонент/причину сбоя без добавления временных print. Подробность регулируется по компонентам, политика хранения/объёма проверена, credentials не записываются.
- Тестовая стратегия привязывает проверки к рискам всей очереди; начальная suite защищает только существующее полезное поведение и не дублирует его на каждом уровне. Есть небольшие проверки диагностики/секретов без привязки к точному тексту логов.
- PR содержит документы, минимальный исполняемый каркас и основу тестов. Независимое ревью дополнительно проверило лишние абстракции, связанность, диагностические пробелы, ненужные fixtures и дублирование тестов; замечания исправлены.
- Инженеру предоставлены PR, команды запуска/проверок и сценарий приёмки. Задача доведена через /do и сабагентов до review; merge не выполняется.
Согласование
Инженер выбрал реальный persisted-session каркас без synthetic turn, React + TypeScript + Vite, pnpm; отдельно потребовал логирование всех этапов на уровне архитектуры. Полный объём подтверждён словами «Да, заводи задачу в полном обьеме». Ранее явно разрешён запуск нужного flow с /do и сабагентами до готового PR на его проверку.
Activity