# Требования к модулю: m09-phone-core > Модуль — телефонное ядро **Phone.WatchBody** (Android, Kotlin, > архитектура разд. 1.2): `WorkoutCore` (тренировка/допуск/дневник/ > перерывы — единственный писатель `LocalDb`), `LocalDb` (Room/SQLite, > офлайн-first), `SyncQueue` (очередь с `client_guid`, пакеты ≤ 50, > идемпотентная отправка `POST /api/v1/sync`). UI — m10 (Compose), > голосовой ввод — m11, канал часов — m12. Самодостаточная копия > требований (d-dev-6). ## 1. Назначение и границы **В границах модуля:** - Gradle-проект `3-app\4-phone\Phone.WatchBody` (app-модуль `:app`, Kotlin, minSdk 26, targetSdk 36; Compose-зависимость подключается в m10 — здесь только ядро и unit-тесты); - `LocalDb` (Room): таблицы-зеркала сервера с **UUID-PK телефона** (Р2-Б): `exercise`, `program_version`, `workout`, `workout_set`, `command_log`, `daily_log`, `breaks`, `profile` (singleton), плюс `sync_pending(guid, entity, payload, created_at)`; настройки — DataStore (`Server.BaseUrl`, `Server.ApiKey`, `Sync.WifiOnly`, `Rest.DefaultSec`, `Safety.*` — Р3-А, телефонные пороги допуска); - `WorkoutCore`: старт/финиш/отмена тренировки (`session_kind`, привязка активной версии программы); фиксация подхода с пометкой «по плану» (план сессии дня: вес ± 2,5 кг, повторения ± 0 — Ф-11.2); журнал команд (заглушка ручного ввода — голос m11); дневник самоконтроля (день уникален, upsert); **предстартовый допуск** (АД > 150/90 → отмена; 140–150/90–95 → только кардио; пульс сидя > 90 → перенос — пороги `Safety.*`, 🛡 блокировка обходится логированием, Ф-13.2); перерывы: объявление `source=manual` (тип/план. возврат), закрытие первой сессией (данные для правил сервера); 🛡 единственный писатель LocalDb; - `SyncQueue`: каждая запись получает `client_guid` (UUID v4) и payload по контракту sync (tech-req 2.1/2.2, snake_case); WorkManager-воркер: пакеты ≤ 50 по времени создания, отправка `X-Api-Key`, подтверждённые GUID удаляются; ретрай 1→64 мин экспоненциально; ограничение `Sync.WifiOnly`; тихий статус (баннеры — m10); - `ApiClient`: HTTP-клиент `/api/v1/sync` (+ `GET /api/v1/workouts`, `GET /api/v1/programs/active` — тянет активную программу); - unit-тесты (JUnit, pure JVM): WorkoutCore (in_plan, допуск, статусы), batcher очереди, сериализация payload (контракт snake_case против фикстур сервера). **Вне границ модуля:** экраны (m10 TrainingUi), STT/TTS и грамматика (m11), companion-канал (m12), LocalDb↔сервер маппинг серверных вычислений (метрики/правила — на сервере), восстановление пульса пропущенного периода (m12 — по каналу часов). ## 2. Контракт sync (tech-req 2.1/2.2) - конверт: `{"client_guid", "entity", "op": "upsert", "data"}`; - entity ∈ workout | workout_set | command_log | daily_log | break | exercise | program_version | profile; поля — snake_case, как серверные sync-сущности (m02 spec 03); - `workout_set.exercise_id`, `workout.program_version_id` — локальные UUID (Р2-Б: маппинг id не нужен); - `break.source` телефон передаёт только `manual` (🛡 авто-детект — сервер, Ф-14.1). ## 3. Инварианты (🛡 — нарушать запрещено) 1. **Офлайн-first** — сеть только на пути `SyncQueue`; запись в LocalDb никогда не ждёт сети (КУ-3). 2. **Единственный писатель** — LocalDb пишет только WorkoutCore (+ SyncQueue свой статус); UI/m10 — через WorkoutCore. 3. **UUID генерирует телефон** для всех клиентских сущностей (Р2-Б); `client_guid` = PK записи. 4. **Идемпотентность** — повторная отправка не меняет данные (сервер считает по GUID; очередь удаляет только подтверждённые). 5. **Тайны** — API-ключ только в DataStore, в логи не пишется (З-6). 6. **Допуск** — пороги только из `Safety.*` (Р3-А); обход блокировки («всё равно начать») логируется. ## 4. Критерии приёмки модуля 1. Unit-тесты зелёные: in_plan (вес ± 2,5 кг / повторы ± 0 от плана), допуск (отмена/кардио/перенос/норма), жизненный цикл тренировки, batcher (порция ≤ 50, порядок по created_at, удаление accepted). 2. Payload всех 8 сущностей соответствует фикстурам контракта (snake_case, обязательные поля). 3. Сборка проекта Gradle проходит (`assembleDebug` без UI — модуль app-библиотеки-каркаса m10); при отсутствии локального Android SDK — сборка у владельца по инструкции (решение гейта 1.3). 4. Интеграция со стеклянным (docker) сервером: сценарий «запись офлайн → sync → данные в `src.*`» проверен вручную curl-фикстурой payload-ов (при доступном SDK — инструментальным тестом).