Files
shturman/docs/domains/l-cloud-companion.md
kk0t9 1eaa0b8896 docs(domain L): v2 — облако/компаньон (Companion+OTA-канал+телеметрия), после adversarial-ревью (21 находка) + кросс-док
Новый домен L: device-Companion-сервис + моб.приложение + опц.self-hostable облако.
Облако опционально, local-first, никогда не управляет машиной. Многоагентный
adversarial-ревью: 30 находок, 21 подтверждена (default-refute), все применены.

Ключевое из ревью:
- OTA untrusted-host неполон без anti-rollback → добавлена монотонность security-version (downgrade/replay-защита); подпись ≠ свежесть. Зеркально в a-base §5.
- fscrypt-ключ eFuse-bound непереносим → честно: cross-device restore требует отдельного backup-ключа (открыто); убрана ложная «ключ у пользователя».
- DOWNLOAD(L) ≠ APPLY(A): разведены фазы; download-fail-safe (ENOSPC/resume/отбраковка битого до RAUC) симметрично J/H.
- Time-gate (a-base §7): холодный boot 1970 → TLS «not yet valid» — OTA/sync/телеметрия ждут вменяемых часов.
- Captive-portal (G): egress только при State==online, не на portal/limited.
- Телеметрия: consent/буфер стираются на factory-reset (инвариант «после reset все opt-in выкл»); не ретроспективна; отзыв → дроп буфера; облако: retention+удаление (152-ФЗ).
- Поездки — из trip-плагина (app-storage), не из core E.
- dashcam-бэкап: отдельный носитель + consent-гейт J §4.
- Staged-rollout halt-петля + парадокс с opt-in телеметрией; push-доставка; модель аккаунта (recovery/multi-vehicle/multi-user); version-skew моб↔головблок — зафиксированы.

Кросс-док: a-base §5 (anti-rollback + Журнал), §12 (telemetry-consent в wipe);
architecture §3 (Companion first-party) + §5 (ребро Companion→Connectivity).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 13:55:40 +03:00

236 lines
28 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Домен L — Облако / компаньон
> Апсайд-слой поверх локальной системы: мобильное приложение-компаньон, синхронизация/бэкап,
> **OTA-канал** (сборка/хостинг/доставка) и **opt-in телеметрия**. **Облако опционально и
> self-hostable** — система полноценна без него (vision: открыта в любом случае, #3). Никогда не
> управляет машиной.
Статус: **v2 (на ревью).** v2 — после adversarial-ревью (21 находка).
Связано с: [a-base-system.md](a-base-system.md) (§5 OTA-механизм/подпись/anti-rollback, §3 fscrypt, §7 время/TLS-gate, §9 audit, §12 factory-reset) · [security-privacy.md](../contracts/security-privacy.md) (§5 грант, §7 телеметрия/152-ФЗ) · [ipc.md](../contracts/ipc.md) (`Settings`, `Power`, `Connectivity`) · [architecture.md](../architecture.md) (§3 Companion first-party, §5 карта связей) · домены **G** (Connectivity-транспорт: `State`/metered/portal), **D** (§7 память водителя — синк), **E** (live-данные/DTC — read-only), **F**/plugin-sdk (§8 trip-плагин — поездки), **J** (§4 dashcam-гейт), **B** (§4/§7 lifecycle OTA), **C** (consent/UI) · [principles.md](../principles.md) (#1,#2,#3,#7,#8,#12,#13)
---
## 1. Назначение и границы
- **Что делает:** (1) **компаньон** — моб.приложение, спаренное с головблоком (просмотр данных машины/DTC
и поездок, просмотр/правка памяти водителя, настройки, уведомления); (2) **синхронизация/бэкап** `/data`;
(3) **OTA-канал** — сборка/хостинг/доставка подписанных образов (применяет/верифицирует A);
(4) **телеметрия** — opt-in, выключена по умолчанию.
- **Облако опционально и self-hostable (#3):** все ключевые функции работают **локально device↔телефон**
(по близости, без облака); облако — лишь relay/хостинг для удалённого и OTA. Нет облака — система полноценна.
- **Границы (красные линии):**
- **Никогда не управляет машиной (#1/#2):** компаньон/облако — **read-only** к данным машины (через
головблок, сам read-only к CAN); удалённых actuator-команд нет, write-путь не существует.
- **Приватность (#7/152-ФЗ):** телеметрия opt-in; память водителя / контакты / точная локация / **dashcam-медиа**
**не в облако без явного отзываемого согласия** (security-privacy §7, J §4); данные в РФ; облако — RU/self-hosted.
- **L — «первый продукт»/ретрофит-слой** (vision-лестница): созревает поздно (v3–v4), апсайд, не ядро.
- **Plane:** control/синк/телеметрия — поверх IP (Connectivity, G); локально device↔телефон — лок.сеть/BT.
## 2. Архитектура L (три части, local-first)
- **Device-side — `Companion`-сервис** (first-party ап на SDK, головблок; architecture §3): локальный API для
моб.приложения, координирует синк (Settings/State + память D §7), **триггерит OTA** (применяет A §5), собирает
opt-in телеметрию. Поднимается на **Stage 2** (фоном, как Connectivity; architecture §6) — boot-окно-поверхности нет.
- **Моб.приложение** (off-device артефакт): iOS/Android; стек — 🟡 (§3).
- **Опциональное облако** (self-hostable, RU): relay синка (когда нет близости), **хостинг OTA-бандлов**
(untrusted — A верифицирует подпись + anti-rollback, §7), приём opt-in телеметрии. Provider-agnostic (#8).
- **Local-first:** по близости device↔телефон общаются напрямую (лок.WiFi/BT), облако не нужно.
## 3. Companion-приложение (моб.)
- **Функции:** просмотр **данных машины/DTC** (read-only, из E) и **поездок** (из storage trip-плагина,
plugin-sdk §8 — не из core E), **просмотр/правка/чистка памяти водителя** (`.md`, D §7), управление
настройками (зеркало Settings), уведомления (§5), статус OTA.
- **Стек 🟡:** моб.приложение **off-device**, поэтому Rust-first-правило прода (tech-stack — про головблок) на
него не распространяется. Кандидаты: Flutter (один кодбейс) **или** Rust-core + native-UI. MIT (#12). → §15.
- **Фаза:** v3.
## 4. Паринг и доверие
- **Локальный якорь доверия:** головблок показывает pairing-код/QR → ввод/скан в приложении → **per-peer общий
ключ** (BT/Tailscale-стиль). **Аккаунт в облаке не обязателен** для локального паринга/синка.
- Спаренный телефон — доверенный peer; список спаренных, **отзыв — в Companion-сервисе** (с головблока, без
телефона; слот C §4). **Модель ролей между несколькими peer'ами** (владелец vs гость: что гость видит/правит,
особенно память водителя) и **адресация N головблоков одним аккаунтом** — открыты (§15).
- **Удалённый доступ (через облако-relay)** — опц., требует аккаунта; E2E-шифрование канала (trust-якорь — тот
же pairing-ключ); account-recovery — §15. **Фаза:** v3 (локально) / v4 (удалённо).
## 5. Синхронизация, уведомления, удалённый статус
- **Что синкается:** настройки (Settings namespace), **память водителя** (`.md`, D §7), история поездок
(trip-плагин, app-storage §6). **Local-first**, device↔телефон по близости; облако-relay — опц. для удалённого.
- **Конфликты:** per-key LWW для настроек; для `.md`-памяти — merge/LWW/версионирование (выбор — §15).
- **Что НЕ синкается без явного согласия:** память водителя в **облако** (D §7), контакты/журнал (G §5),
**dashcam-медиа** (J §4), точная локация. Согласие — отзываемое (security-privacy §7), UI — C.
- **Уведомления:** локально/по близости — прямой канал device↔телефон (in-app, сигналы §11). Удалённо (телефон
не рядом / приложение в фоне) — через self-hosted relay (long-poll/WebSocket) **либо** честное ограничение
«без облака удалённых push нет». **APNs/FCM** — внешние зависимости, конфликт с self-hostable/152-ФЗ (#8) →
не first-party (🟡 §15). Фаза: v3 локально / v4 удалённо.
- **Egress-гейт связности:** синк/relay-egress стартует только при Connectivity `State==online` (G §11), **не на
`portal`/`limited`** (ложный online — captive-portal, G §2) и **не до «вменяемых часов»** (§9).
- **Удалённый статус машины (read-only, later):** «где машина / напряжение / есть ли DTC» — только данные,
которые головблок уже имеет (никакого нового доступа к CAN, #2); **локация в облако — opt-in + consent**;
свежесть/last-known vs live + порог согласия — §15. Фаза: later/v4.
- **Фаза:** синк v3; уведомления v3/v4; удалённый статус v4.
## 6. Бэкап / восстановление
- **Бэкап `/data`** (настройки, память `.md`, app-storage); опц. — **отдельный поток dashcam-медиа** (на
отдельном носителе/разделе, J §4 / A §12 — НЕ в `/data`): только по capability + **явному отзываемому
согласию**, факт — в audit-log (J §4 / security-privacy §7); по умолчанию нет.
- **Шифрование бэкапа:** fscrypt-ключ `/data` привязан к **eFuse конкретной платы и headless** (a-base §3) — у
пользователя его НЕТ и он непереносим. Поэтому: (а) restore **на том же устройстве** (после factory-reset) —
ок; (б) **cross-device restore** требует ОТДЕЛЬНОГО backup-ключа/парольной фразы (не из eFuse) + re-encrypt при
экспорте — **схема открыта (§15, расширить a-base §3)**; до резолва cross-device-restore зашифрованного не гарантируется.
- **Восстановление:** настройки/память на новом устройстве/после factory-reset; **device-identity/`machine-id`
не переносится** (a-base §11). **Совместимость:** бэкап несёт версию base-образа/схемы Settings/BSP-профиля;
при несовпадении BSP/vehicle-профиля (a-base §11/§13) BSP-зависимые ключи **не применяются молча** (видимая
индикация / миграция схемы), переносится BSP-независимое (память `.md` — безопасна; расхождение сигналов
штатно покрыто `Unavailable`, data-model §3). Детали миграции — §15.
- **Фаза:** v3/v4.
## 7. OTA-канал *(L владеет доставкой; A — применением)*
- **Шов с A (a-base §5):** **A** — механизм применения (RAUC A/B), подпись/keyring/верификация + **anti-rollback**,
откат (bootcount+mark-good). **L****сборка** (CI → подписанный бандл + метаданные min-version), **хостинг**
(сервер/CDN), **доставка** (device-side update-клиент: проверка/скачивание).
- **Untrusted-host (с anti-rollback):** бандл подписан ключом из OTP/eFuse (a-base §4) → подмену вредоноса хост
не протолкнёт. Но подпись гарантирует аутентичность/целостность, **НЕ свежесть** → A дополнительно проверяет
**монотонность security-version** (rollback-index в eFuse/U-Boot env, по модели AVB) и отвергает downgrade/replay
даже при валидной подписи. **Только с anti-rollback** тезис «хосту не нужно доверие, лишь TLS+доступность»
корректен — иначе скомпрометированный CDN откатит парк на подписанную дырявую прошивку.
- **Фазы DOWNLOAD (L) vs APPLY (A) — разведены:** скачивание (L) идёт в **staging-область**, ДО RAUC, и **не
трогает A/B-слоты**; применение (A) — атомарно (a-base §5). Прерывание питанием на DOWNLOAD теряет максимум
прогресс докачки (слоты целы); на APPLY — RAUC откат.
- **Отказ-пути доставки (симметрично J §4 / H §5):** проверка свободного места ДО скачивания (ENOSPC → отказ с
видимой индикацией, без частичной записи, не крэш); прерванная загрузка — **range-resume** или чистый
перезапуск; **обрезанный/битый бандл отвергается на стороне L** (ожидаемый размер+хэш из подписанного манифеста)
ДО передачи в RAUC, не полагаясь на подпись A как единственный backstop; недоступный CDN → отложить (#3).
- **Связность:** скачивание — только при `State==online` (G §11), не на `portal`/`limited` (G §2); metered-aware
(крупный бандл не по дорогой LTE без согласия, G §2); и не до «вменяемых часов» (§9, иначе TLS «not yet valid»).
- **Каналы релизов:** stable / beta (opt-in); **staged rollout** (% парка), delta — позже (§15).
- **Сигнал здоровья когорты + auto-halt:** staged rollout требует обратного канала о доле mark-good/откатов;
при пороге откатов — автоостановка раскатки на остальной парк. **Парадокс:** телеметрия (§8) opt-in и по
умолчанию молчит → единственный обратный канал ненадёжен. Резолв — §15 (мин. неотключаемый health-сигнал
результата OTA vs консервативная ручная раскатка). Без него плохой бандл (откатившийся локально, A) идёт дальше.
- **До secure boot (v0v3):** trust-by-build / ручной флеш (security-privacy §5); полноценный подписанный
OTA-канал + anti-rollback созревают с secure boot — **v4** (a-base §2/§4/§5).
- **Фаза:** v4.
## 8. Телеметрия *(opt-in, выключена по умолчанию)*
- **По умолчанию ВЫКЛЮЧЕНА** (security-privacy §7, #7); включается явным **отзываемым** согласием.
- **Что собирается (при opt-in):** краши/паники, результат OTA, watchdog-ресеты (источник — критичные события
a-base §9), агрегатная статистика использования (какие функции, без контента), perf/ошибки.
- **Что НИКОГДА (без отдельного явного согласия):** голос, контент данных машины, контакты, память водителя,
точная локация, dashcam.
- **Граница согласия:** **не ретроспективно** (собираем только события ПОСЛЕ согласия; ранее осевшие в audit-log/
pstore критичные события a-base §9 в egress не идут); **отзыв** → немедленно прекратить сбор + **дропнуть
локальный буфер** ещё-не-отправленного (строгая трактовка #7); системный audit-log/pstore (a-base §9) ведётся
независимо для recovery, телеметрии не равен.
- **Factory-reset (a-base §12):** стирает telemetry-consent (возврат к выкл-по-умолчанию #7) И буфер
несобранной/неотправленной телеметрии (мог содержать события прошлого владельца — кейс продажи авто).
**Инвариант:** после factory-reset **все opt-in-согласия = выкл** (синк памяти, локация — тоже).
- **Облачная сторона:** ingest имеет retention; **отзыв согласия → удаление в ingest**; factory-reset стирает
только локальное — облачная копия удаляется отдельным запросом (право 152-ФЗ; self-hosted — у владельца инстанса). Детали — §15.
- **Анонимизация** (без device-identity/VIN/локации), **RU/self-hosted** ingest, provider-agnostic (#8); факт
egress — в audit-log (security-privacy §7 / a-base §9).
- **Фаза:** v2/v3.
## 9. Lifecycle (шов с B)
- **OTA-тайминг:** применение (A) — **не на ходу**; предпочтительно на стоянке/`accessory` или по явному запросу
(Companion проверяет `Power`-тайминг, делегирует A); скачивание — фоном, metered/portal/online-aware (§7).
- **Time-gate (a-base §7 / B §8):** OTA-download, sync-relay и telemetry-egress подчиняются гейту «вменяемых
часов» — откладываются до валидного времени (fake-hwclock/NTP/GPS-фикс), иначе TLS к CDN/облаку/ingest падает
«cert not yet valid» (плата без RTC → холодный boot с epoch 1970). Без сети ≠ без валидного времени.
- **Sync/телеметрия:** фоновые, неблокирующие (#11); деградируют без сети (#3); **не инициируют/не продлевают
wake** — согласуется с энергобюджетом АКБ (B §7 battery-cutoff, #5).
- **На `ShutdownImminent` (B §4):** прервать фоновую загрузку/синк gracefully (staging цел, §7); доделать позже.
## 10. Приватность и безопасность (#1, #2, #7, #12)
- **Read-only к машине, без удалённого управления** (#1/#2): write-путь к CAN не существует ни локально, ни через облако.
- **Local-first, облако опционально**: память/контакты/локация/**dashcam** не в облако без явного согласия (D §7, G §5, J §4, security-privacy §7).
- **Untrusted OTA-host** (§7): доверие — в подписи **+ anti-rollback** (A/eFuse), не в хосте.
- **152-ФЗ:** телеметрия opt-in, данные в РФ, **право на экспорт/удаление из облака** (синк + egress-нувшая
телеметрия), оператор/основание ПДн — legal-трек; self-hostable снимает зависимость от чужого облака.
- **Лицензии (#12):** моб.стек и облако — MIT-совместимо; self-hostable reference.
## 11. IPC / интерфейсы
- **`ru.shturman.Companion`** (домен L; контракт — §15): локальный API для моб.приложения (через Connectivity/
лок.сеть, не публичная D-Bus); методы синка/бэкапа; `TriggerUpdateCheck()`, статус OTA; сигналы
`UpdateAvailable`/`SyncStateChanged`. Применение OTA — делегируется A (RAUC).
- **Потребляет:** `Settings` (зеркало/синк), память D (`.md`), **E (live-данные/DTC — read; поездки —
app-storage trip-плагина, plugin-sdk §8)**, `Connectivity` (G — транспорт/`State`/metered), `Power` (B —
тайминг). **Гейтинг:** `network` (security-privacy §3/§5); память/контакты — только по явному согласию (§5/§10).
- **UI:** consent/паринг/уведомления-на-головблоке — слот Shell (C §4); consent-UI — владелец C (security-privacy §7).
## 12. Dev-симулятор (#13)
- **Мок-облако/OTA-сервер** (раздаёт подписанные / намеренно-битые / **downgrade**-бандлы — тест верификации+
anti-rollback A и отказа L по размеру/хэшу), **фейковый companion-peer** (паринг/синк/конфликты/мульти-peer без
реального телефона), **мок-телеметрия-sink** (проверка opt-in-гейта: по умолчанию ноль egress; дроп буфера на
отзыв/reset). Поверх мок-сети G (включая `portal`/невалидное-время). → dev-environment.
## 13. Функции
| функция | MVP/later | зависит от | фаза |
|---------|-----------|------------|------|
| Companion-сервис (device-side, локальный API, Stage 2) | later | G (транспорт), Settings | v3 |
| Паринг device↔телефон (локальный якорь, per-peer ключ) | later | — | v3 |
| Моб.приложение: данные машины/DTC (read-only) + поездки | later | E, trip-плагин, стек 🟡 | v3 |
| Моб.приложение: просмотр/правка памяти водителя | later | D §7 | v3 |
| Синхронизация (настройки/память/поездки, local-first) | later | Settings, D, trip-плагин | v3 |
| Уведомления (local in-app / remote-relay) | later | relay (§5) | v3/v4 |
| Телеметрия (opt-in, выключена; consent-wipe на reset) | later | security-privacy §7, a-base §12 | v2/v3 |
| Бэкап/восстановление `/data` (+ совместимость BSP/схемы) | later | a-base §3/§11/§12 | v3/v4 |
| OTA-канал: сборка/хостинг/доставка + fail-safe + anti-rollback | later | a-base §5, secure boot | v4 |
| Каналы релизов (stable/beta) + staged rollout + halt-петля | later | OTA-канал | v4 |
| Опциональное облако (relay/хостинг, self-hostable) | later | — | v4 |
| Удалённый статус машины (read-only, opt-in локация) | later | E, consent | v4 |
| Удалённый доступ через облако-relay (E2E) | later | паринг, облако | v4 |
## 14. Зависимости
- **Вниз/вбок:** **A** (§5 OTA-механизм/подпись/**anti-rollback**/откат — L только канал; §3 fscrypt+backup-ключ;
§7 время/TLS-gate; §9 audit/критичные события — источник телеметрии; §11/§12 restore/wipe), **G** (Connectivity:
`State`/metered/portal — транспорт-гейт), **D** (§7 память — синк-цель), **E** (live-данные/DTC — read-only;
**поездки — app-storage trip-плагина, plugin-sdk §8**), **J** (§4 dashcam-гейт), **B** (§4/§7 OTA/sync-тайминг),
**C** (consent/паринг/уведомления-UI), security-privacy (§5/§7 гранты/телеметрия/152-ФЗ).
- **Вверх:** удалённый просмотр/настройки для пользователя; OTA-доставка для всего парка.
## 15. Открытые вопросы
- 🟡 **Стек моб.приложения** (Flutter vs Rust-core+native) — off-device, MIT. → реализация.
- 🟡 **Push-транспорт удалённых уведомлений** (self-hosted relay vs APNs/FCM vs только-локально; 152-ФЗ/#8). → §5 + реализация.
- ◻️ **Cross-device backup-ключ** (passphrase/user-held, не из eFuse; re-encrypt при экспорте) — restore зашифрованного на новой плате. → a-base §3.
- ◻️ **Совместимость restore** (версия схемы Settings + BSP/vehicle-профиль; миграция vs отказ). → реализация + a-base §11/§13.
- ◻️ **Модель аккаунта:** account-recovery (потеря пароля/устройства, для relay); multi-vehicle (один аккаунт — N головблоков, адресация); multi-user роли (владелец/гость, доступ к памяти водителя). → реализация/legal.
- ◻️ **Право на экспорт/удаление из облака** (синк + egress-нувшая телеметрия) + retention в ingest; factory-reset не трогает облачную копию. → security-privacy §7 + legal.
- ◻️ **`ru.shturman.Companion` контракт** — методы синка/OTA-trigger **+ версионный handshake моб↔головблок** (min-version, graceful-degrade при skew; on-device конвенция ipc §2/plugin-sdk §7 к off-device без манифеста неприменима). → ipc.
- ◻️ **Staged rollout: halt-петля + развязка с opt-in телеметрией** (мин. health-сигнал результата OTA vs ручной gate) + механика %-парка/delta. → a-base §5 + L.
- ◻️ **Разрешение конфликтов синка** памяти `.md` (merge vs LWW vs версионирование). → реализация.
- ◻️ **152-ФЗ роли** (оператор/обработчик ПДн для синка/телеметрии/удалённого) — legal-трек (как BYO-LLM).
- ◻️ **Download-staging бандла** (область, resume/cleanup-политика; вне durable-write контракта a-base §3, если в `/data`). → реализация.
---
## Журнал решений (домен L)
| Решение | Выбор | Дата |
|---------|-------|------|
| Позиционирование | апсайд-слой; облако **опционально и self-hostable**; система полноценна без него (#3) | 2026-06-23 |
| Архитектура | device-Companion-сервис (Stage 2) + моб.приложение + опц.облако; **local-first** device↔телефон | 2026-06-23 |
| Красные линии | read-only к машине, без удалённого управления (#1/#2); память/контакты/локация/dashcam — не в облако без согласия | 2026-06-23 |
| Паринг | локальный per-peer якорь (код/QR), аккаунт не обязателен; роли peer'ов / multi-vehicle / recovery — открыты | 2026-06-23 |
| OTA | A — механизм/подпись/**anti-rollback**/откат; L — сборка/хостинг/доставка; untrusted-host корректен ТОЛЬКО с anti-rollback; DOWNLOAD(L)≠APPLY(A) | 2026-06-23 |
| OTA fail-safe | проверка места/ENOSPC, range-resume, отбраковка битого бандла на L до RAUC; гейт `State==online`+время | 2026-06-23 |
| Телеметрия | **opt-in, выключена**; не ретроспективно; отзыв/reset → дроп буфера + consent выкл; облако: retention+удаление; анонимизация; RU/self-hosted | 2026-06-23 |
| Синк | local-first (настройки/память/**поездки из trip-плагина, не E**); конфликты `.md` — открыто; не в облако без согласия | 2026-06-23 |
| Бэкап-шифрование | fscrypt eFuse-bound непереносим → cross-device restore требует отдельного backup-ключа (открыто, a-base §3) | 2026-06-23 |
| Lifecycle | OTA не на ходу; time-gate (a-base §7); фоновые синк/телеметрия не держат wake; staging цел на shutdown | 2026-06-23 |
| Моб.стек | 🟡 off-device (Flutter / Rust-core+native), MIT; Rust-first-правило прода — про головблок, не сюда | 2026-06-23 |
| Фазы | телеметрия v2/v3; компаньон/синк/бэкап v3; OTA-канал/облако/удалённое v4 | 2026-06-23 |