Files
shturman/docs/specs/v0.1-v0.6-foundation.md
T
kk0t9 a9aad21636 chore: убрать GitHub-Actions CI (триггерит Gitea) + CLAUDE.md цель → Lima E2E
- удалён .github/workflows/ci.yml: self-hosted Gitea ловит GitHub-Actions-формат, не нужно.
  Активный гейт — локальный just ci; авто-CI на Gitea — решение позже.
- CLAUDE.md § «Текущая цель»: фундамент (Планы 1–5 ч.1) в main; следующее — A (Lima E2E, План 5 ч.2).
- spec §8.3 — пометка об удалении CI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Alexander <akotenev2003@gmail.com>
2026-06-24 14:50:59 +03:00

677 lines
62 KiB
Markdown
Raw 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.
# Спека реализации: v0.1 (Образ-болванка) + v0.6 (dev-харнесс) + шагающий скелет
> **Тип документа:** имплементационная спека вехи (вход в TDD-цикл, не код).
> Фаза реализации (CLAUDE.md): **спека → правки → adversarial-ревью → TDD → verify в VM → коммит.**
> Код, `LICENSE`, `CONTRIBUTING.md`, `Cargo.toml`, крейты — **создаются после утверждения спеки.**
> Содержимое `LICENSE`/`CONTRIBUTING.md` приведено внутри спеки (§10–§11) для ревью.
Статус: **v1 — применены 17 правок adversarial-ревью + позиция Slint (A); ожидает финального «ок» на TDD-план (2026-06-23).**
Источник правды по дизайну — `docs/`. Эта спека секвенирует доки в код, не противоречит им; при
расхождении — синхронизируем док (двунаправленный шов, см. §13).
Связано: [roadmap §v0](../roadmap.md) · [architecture](../architecture.md) · [tech-stack](../tech-stack.md) ·
[dev-environment](../dev-environment.md) · [ipc](../contracts/ipc.md) · [data-model](../contracts/data-model.md) ·
[performance](../contracts/performance.md) · [plugin-sdk](../contracts/plugin-sdk.md) · [security-privacy](../contracts/security-privacy.md) · домены [A](../domains/a-base-system.md)/[B](../domains/b-power-lifecycle.md)/[C](../domains/c-shell-ux.md)/[F](../domains/f-plugin-host.md) · [principles](../principles.md)
> **Changelog v0→v1 (по adversarial-ревью, 17 находок):** every-boot bind `machine-id` (§7.2/§7.6); автомонтирование
> `/data` + ordering `Requires` (§7.1/§7.6); `deny.toml` — Slint=GPL-3.0 exception, позиция A (§3/§12); #5 доказывается
> unit-тестом атомарности, не reboot (§9.1/§9.3); измеримый eMMC-прокси (§7.5/§9.3); `grim`→software-renderer/
> `weston-screenshooter` (§6); срезы C05/C07/C02 в трассировке (§2.4/§6); шов §6↔C§2 разделён (§2.2); `barrier=1`
> убран (§7.1); zram-generator (§7.4); путь `fake-hwclock`→`/data` (§7.3); гейт `dev-mocks` (§5.2/§8.3); factory-reset
> тест (§9.1); per-unit `is-active` вместо degraded (§9.3); README мини-спека (§11); LGPL гранулярно (§3/§11); указатель
> security-privacy (§1/§3); новый §13 «двунаправленные швы».
---
## 1. Цель и первый артефакт
**Цель спеки:** заложить фундамент репозитория и dev-цикла так, чтобы получить **первый запускаемый
артефакт** (CLAUDE.md):
> **boot в Lima-VM → стаб-сервисы (`ru.shturman.Power` / `ru.shturman.Settings`) на D-Bus → первый
> Slint-кадр, читающий состояние с шины.**
Это **«шагающий скелет» (tracer bullet):** тонкий сквозной поток через все слои архитектуры
(Linux base → trusted core → SDK → first-party app), на стабах. Он доказывает, что плоскости
(D-Bus control-plane, Slint UI), границы крейтов, dev-харнесс и тест-пирамида работают **до** того,
как мы вложимся в реальную логику power-safe (v0.3), Vehicle-Data (v2) и полный shell (v0.5).
**Архитектурный тезис, который скелет валидирует рано:** «тонкое ядро + всё на SDK» — first-party
Shell общается с ядром (`Power`/`Settings`) **через тот же `shturman-sdk`**, что и будущие сторонние
плагины (architecture §1, principles #9).
---
## 2. Скоуп и границы
### 2.1 В скоупе (делаем сейчас)
| Трек | Что | ID каталога |
|------|-----|-------------|
| **v0.1** Образ-болванка (в VM) | layout RO-rootfs + overlay(tmpfs) + журналируемый `/data`; first-boot provisioning; локаль | A02 A06 A17 (+ A01 как dev-Ubuntu) |
| **v0.6 base day-1** | zram/OOM/cgroup-конфиг; journald volatile + критичное в `/data`; eMMC write-min; время (timesyncd + fake-hwclock); reference-«BSP» = Lima-профиль | A09 A10 A11 A07 A16 |
| **v0.6 dev-харнесс** | Lima-VM + provisioning; `justfile`; CI-гейт; vcan/моки-каркас; `shturman-sdk`; валидатор манифеста + scaffolding | F01 F02 (F03/F04/J06 — частично, см. §2.3) |
| **Шагающий скелет** | стаб `ru.shturman.Power` + `ru.shturman.Settings` на шине; минимальный Slint-кадр на SDK; systemd-таргет `shturman.target` | срезы B04 / Settings-core / C03 C04 **C05 C07 C02** / A15 |
| **Governance** | `LICENSE` (MIT) + `CONTRIBUTING.md` + `DCO` + `README.md`; `deny.toml` (cargo-deny, #12) | — |
### 2.2 Явно НЕ в скоупе (отложено, с указателем «куда»)
Скелет намеренно **стабит**, а не реализует, чтобы не тащить v0.3/v0.5/v2/v4 в первый артефакт:
| Отложено | Куда | Почему не сейчас |
|----------|------|------------------|
| Полная машина состояний `Power` + graceful shutdown sequencing + abort/PONR | **v0.3** (домен B §2/§4) | стаб лишь публикует интерфейс + эмитит сигналы по dev-триггеру |
| MCU-копилот / supercap, hold-up, thermal-trip, watchdog-арбитраж | **v0.3/v0.4** (B §5/§6, hardware) | требует железа (🟡 B08/B09) |
| smithay-композитор (мульти-клиент) + слот-поверхности (`ru.shturman.shell_slot`) | **v0.5 / с первым surface-апом** (C §2, C06) | v0-shell = одиночное Slint-приложение (де-рисковка C §2) |
| App-Host + Perm-Broker (запуск/песочница/прокси) | **v3 / с первым плагином** | в v0 нет сторонних плагинов и surface-апов; Shell — обычный systemd-сервис на шине |
| Vehicle-Data (`ru.shturman.VehicleData`), CAN/OBD, DTC | **v2** (домен E) | vcan поднимаем в VM, но Vehicle Simulator и сервис — позже |
| Реальный A/B boot-select (U-Boot env), secure boot, at-rest (fscrypt), OTA (RAUC) | **v4** (A §4/§5, hardware) | в VM нет U-Boot; моделируем **layout**, не boot-select |
| Полный home/тайлы/тема день-ночь по design-system, анимации | **v0.5** (C §3, design-system) | сейчас — каркас кадра + нейтральные плейсхолдеры под токены |
| Connectivity, аудио (PipeWire-роли), Location/GPS | **v1** | пакеты ставим для воспроизводимости, но не задействуем |
> **Два независимых шва v0 (проговариваю явно — это РАЗНЫЕ решения):**
> **(1) Композитор** — по C §2 v0-shell стартует как одиночное Slint-приложение; полноценный smithay-композитор +
> слот-поверхности включаем с первым surface-апом (v0.5). Это **прямая де-рисковка C §2**.
> **(2) App-Host / Perm-Broker** — architecture §6 ставит их в Stage 1 *до* Shell (Shell цепляется через них). C §2
> это **не** покрывает (там только про композитор). Обоснование их отсутствия в v0 — **другое:** в скелете нет
> песочных апов/плагинов → **нечего брокерить и хостить**, поэтому Shell поднимается обычным systemd-сервисом и
> цепляется к шине напрямую; Broker/App-Host включаются с **первым песочным клиентом** (плагин — v3). Это осознанное
> секвенирование. По двунаправленному шву — добавить ремарку в architecture §6 (см. §13).
### 2.3 Частично в скоупе (каркас сейчас, тело — позже в v0)
Честно фиксируем (principle: no silent caps):
- **F03 Dev-run плагина в VM** — зависит от **App-Host** (нет в v0-скелете). Делаем: `justfile`-таргет
`plugin-dev-run` + каркас, который **корректно сообщает** «App-Host появится в v3». Тело — когда App-Host.
- **F04 Тест-харнесс plugin-host** — делаем **сейчас**: библиотека «плохих манифестов» (`fixtures/manifests/`)
как фикстуры для валидатора (F02). Рантайм fault-injection (crash-loop, mem-hog) — **позже** (нужен App-Host).
- **J06 Dev-симулятор камер** — каркас в `sim/` + `just`-плейсхолдер; тело — в домене J (v2). На критпути
первого артефакта не нужен (камер нет до v2).
- **vcan + Vehicle Simulator** — vcan-модуль и `vcan0` **поднимаем** в provisioning (воспроизводимость
базы); сам симулятор (Python, ELM327-emu) — домен E (v2). `just sim` — плейсхолдер.
### 2.4 Трассируемость ID → статус в этой спеке
| ID | Функция | Статус сейчас | Раздел |
|----|---------|---------------|--------|
| A01 | base-образ | **dev = Lima Ubuntu ARM64**; прод-образ (Armbian/Debian vs Yocto 🟡) — v4 | §7.1, §8 |
| A02 | RO-rootfs A/B + overlay(tmpfs) + `/data` | **layout в VM** (boot-select — v4/HW); ФС dev=ext4 (f2fs 🟡 на HW) | §7.1 |
| A06 | First-boot provisioning | **полностью** (`shturman-firstboot`, идемпотентно) | §7.2 |
| A07 | Время (timesyncd + fake-hwclock) | **OS-уровень в provisioning**; B-owned save-on-shutdown — v0.3 | §7.3 |
| A09 | Память (zram/OOM/cgroup) | **конфиг день-1**; числа-бюджеты — на железе | §7.4 |
| A10 | Логи (journald volatile + критичное `/data` + pstore) | **полностью (конфиг)** | §7.5 |
| A11 | eMMC write-min | **дисциплина + измеримая проверка** | §7.5 |
| A15 | systemd-таргеты/оркестрация | **`shturman.target` + юниты + ordering** | §7.6 |
| A16 | reference-BSP | **dev-«BSP» = Lima-профиль**; реальный DT/HAL/DBC — HW | §8.1 |
| A17 | Локаль (ru_RU.UTF-8, tzdata, кириллица, keymap) | **полностью (provisioning)** | §7.1 |
| B04 | `ru.shturman.Power` сервис | **стаб-интерфейс + dev-mock** (срез v0.3) | §5.2 |
| C03/C04 | Shell первый кадр / Slint-shell | **срез** (минимальный кадр) | §6 |
| C05 | Декларативный рендер тайлов | **срез** (плейсхолдер-тайлы); полный — v0.5 | §6 |
| C07 | Статус-бар время+сеть-unknown | **срез** (минимум); полный — v0.5 | §6 |
| C02 | Тема день/ночь | **срез** (по локальному времени); GPS-восход/датчик — v1/later | §6 |
| F01 | `shturman-sdk` | **MVP-крейт** (proxy-обёртки + схема манифеста) | §4, §8.4 |
| F02 | scaffolding + валидатор манифеста | **полностью** | §8.4 |
| F03 | Dev-run плагина | **каркас**, тело — с App-Host (v3) | §2.3, §8.3 |
| F04 | Тест-харнесс plugin-host | **bad-manifest фикстуры**; рантайм — позже | §2.3, §8.4 |
| J06 | Dev-симулятор камер | **каркас**; тело — v2 | §2.3 |
| — | `ru.shturman.Settings` (core-инфра) | **стаб + атомарная запись в `/data`** | §5.3 |
---
## 3. Красные линии, безопасность, лицензии в v0
- **CAN только на чтение / никогда не safety-critical** держатся в v0 **тривиально и архитектурно:**
Vehicle-Data не существует, путей к CAN/actuator нет вообще. В `shturman-ipc`/`shturman-sdk` **не
определяется ни одного** write/actuator-метода или capability — «их не существует» (principles #2).
- **Power-safe (#5) с дня 1:** даже стаб `Settings` пишет в `/data` **только** durable-write-контрактом
(`write-temp → fsync → rename → fsync(dir)`, a-base §3). **Доказывается unit-тестом атомарности** (§9.1,
симуляция сбоя между `rename` и `fsync(dir)`), **не** graceful-reboot (reboot флашит кэш и проходит даже
при неатомарной записи — §9.3 шаг 4 переименован в функциональный). Реальный power-cut-тест — v0.3.
- **Лицензионная гигиена (#12):** `deny.toml` + CI-гейт `cargo-deny` с дня 1 — allow MIT/Apache-2.0/BSD/
ISC/Unicode/Zlib; deny GPL/AGPL (заразный копилефт). **LGPL — гранулярно** (не blanket-deny: динамическая/
системная линковка допустима), согласовано с principles #12.
- **⚠️ Slint = `GPL-3.0` для embedded.** Royalty-free лицензия Slint **исключает embedded**, а авто-приборка =
embedded (Slint сам называет «car dashboard» примером в FAQ). Бесплатный путь = только GPL-3.0 (шипимый
UI-бинарь — копилефт). **Решение по UI-тулкиту/лицензии — вариант A: отложено к v4** (§12 п.8). На v0 **dev в
Lima-VM не триггерит** копилефт-обязательств распространения (они срабатывают при поставке прод-образа, v4) →
в `deny.toml` явный **exception**: `slint = GPL-3.0, pending v4`. Makepad/Iced (MIT) — известные пермиссивные
запасные. Двунаправленный шов в tech-stack — §13.
- GPL-демоны (NM/MM/BlueZ) — отдельные процессы через D-Bus, не линкуются (вне графа cargo-deny), появляются в v1.
- **Приватность/безопасность (указатель-шов):** по умолчанию **нулевой egress**/телеметрия (security-privacy §7);
at-rest-шифрование и нормативный audit-log — отложены туда (v2v4); capability-таксономия валидатора (§8.4) — из
security-privacy §3. В v0 нет сети/plugin-host/Vehicle-Data → активной работы здесь нет, только шов.
- **Отзывчивость (#11):** Slint UI-поток не блокируем; D-Bus-вызовы из Shell — async (`zbus`+`tokio`).
Перф-**вердикт** — на RK3588 (performance §2); в VM/CI — функциональные проверки + seed perf-gate.
---
## 4. Раскладка Rust-воркспейса
### 4.1 Дерево
```
shturman/
├── Cargo.toml # [workspace] + [workspace.dependencies] (единые пины)
├── rust-toolchain.toml # пин тулчейна (stable, профиль)
├── deny.toml # cargo-deny: лицензии (#12) + advisories (+ slint GPL-3.0 exception)
├── justfile # единые dev-команды (§8.2)
├── LICENSE # MIT (§10)
├── CONTRIBUTING.md # governance (§11)
├── DCO # Developer Certificate of Origin 1.1 (§11 п.10)
├── README.md # точка входа репозитория (мини-спека §11 п.13)
├── lima/
│ └── shturman.yaml # Lima-шаблон VM (§8.1)
├── systemd/ # юниты + политика шины + drop-ins (§7)
│ ├── shturman.target
│ ├── data.mount # постоянный mount loop-/data (power-safe опции, §7.1/§7.6)
│ ├── shturman-firstboot.service # A06 (After/Requires data.mount)
│ ├── shturman-machineid.service # every-boot bind /data/state/machine-id → /etc/machine-id (§7.2/§7.6)
│ ├── shturman-power.service
│ ├── shturman-settings.service
│ ├── shturman-shell.service
│ ├── dbus/ru.shturman.conf # D-Bus policy (own-имена сервисов)
│ ├── dbus/ru.shturman.dev.conf # dev-only drop-in: policy для ru.shturman.dev.* (НЕ в прод-образе)
│ ├── journald-shturman.conf # Storage=volatile + RateLimit (A10)
│ ├── zram-generator.conf # zram через zram-generator, секция [zram0] (A09)
│ └── oomd-shturman.conf # systemd-oomd политика (A09)
├── crates/
│ ├── shturman-common/ # lib: tracing/journald init, layout `/data`, монотон. часы, durable-write
│ ├── shturman-ipc/ # lib: Error-тип, имена/пути/версии, zbus #[proxy] Power1/Settings1
│ ├── shturman-sdk/ # lib (F01): client-обёртки, схема манифеста (serde), helpers
│ ├── core/
│ │ ├── shturman-firstboot/ # bin (A06): идемпотентный init `/data` + генерация machine-id
│ │ ├── shturman-power/ # bin (B04 стаб): сервер Power1 + dev-mock (feature)
│ │ └── shturman-settings/ # bin (стаб): сервер Settings1 + атомарный стор в `/data`
│ ├── apps/
│ │ └── shturman-shell/ # bin (C03/C04 минимум): Slint-кадр на SDK
│ └── tools/
│ └── shturman-manifest-validator/ # bin (F02): валидатор manifest.yaml против схемы SDK
├── templates/
│ └── plugin/ # scaffolding для `just new-plugin` (F02)
├── fixtures/
│ └── manifests/ # F04: библиотека плохих манифестов + один валидный
├── sim/ # dev-симуляторы (Python): Vehicle Sim (v2), camera mock (v2) — каркас
└── tests/
└── e2e/ # E2E-харнесс в VM (boot→шина→кадр) — раннер + ассерты
```
`[workspace] members = ["crates/*", "crates/core/*", "crates/apps/*", "crates/tools/*"]`.
### 4.2 Ответственность крейтов и границы
| Крейт | Тип | Ответственность | Зависит от |
|-------|-----|-----------------|------------|
| `shturman-common` | lib | `tracing`→journald init; layout-константы `/data` (`/data/{apps,settings,state,log}`); монотонные часы (`CLOCK_MONOTONIC`, B §8); helper атомарной записи (durable-write, a-base §3) | — |
| `shturman-ipc` | lib | **Контракт шины**: `ru.shturman.Error.*` (zbus `DBusError`); константы well-known имён/путей/версий интерфейсов; `#[proxy]`-трейты `Power1`, `Settings1`; типы-энумы (`PowerState`, `IgnitionState`, `PowerSource`, `ShutdownReason`) с сериализацией в строки на проводе | `common` |
| `shturman-sdk` | lib (**F01**) | **Публичный API платформы**: `connect()` (бутстрап соединения), ergon-обёртки `SettingsClient`/`PowerClient` над proxy из `ipc`; **схема манифеста** (`manifest::Manifest`, serde, plugin-sdk §2); helpers | `ipc`, `common` |
| `shturman-firstboot` | bin (**A06**) | Идемпотентный first-boot: структура `/data`, **генерация** persistent `machine-id` в `/data/state/`, посев дефолт-настроек, durable-write маркера (привязку machine-id делает отдельный every-boot юнит — §7.2) | `common` |
| `shturman-power` | bin (**B04** стаб) | Сервер `ru.shturman.Power1` (стаб-поведение §5.2) + dev-mock-интерфейс (feature `dev-mocks`) | `ipc`, `common` |
| `shturman-settings` | bin (стаб) | Сервер `ru.shturman.Settings1` (§5.3) + атомарный стор в `/data/settings/` | `ipc`, `common` |
| `shturman-shell` | bin (**C03/C04**) | Slint-приложение: первый кадр; читает `Settings`/`Power` **через `shturman-sdk`** | `sdk`, `common`, `slint` |
| `shturman-manifest-validator` | bin (**F02**) | Валидирует `manifest.yaml` против `shturman_sdk::manifest` (схема + правила plugin-sdk §2/§3) | `sdk` |
**Границы (ключевое):**
- **Сервисы ядра (`power`/`settings`) зависят от `ipc` (контракт), НЕ от `sdk` (клиент).** Они реализуют
**server-side** (`#[interface]`). SDK — клиентская сторона. Это держит границу «ядро публикует ↔ апы
потребляют» (architecture §3).
- **Апы (`shell`) зависят от `sdk`** — реализуют тезис «first-party на том же SDK» (#1, #9).
- `ipc` — единственный источник имён/версий/ошибок; и сервер, и клиент берут их отсюда (нет рассинхрона
строк имён). Полные XML-сигнатуры фиксируются при реализации (ipc §2).
### 4.3 Общие зависимости (workspace.dependencies)
`tokio` (async-рантайм), `zbus` (D-Bus), `serde`/`serde_yaml`/`serde_json` (манифест/стор), `tracing` +
`tracing-subscriber` + `tracing-journald` (логи A10), `slint` (UI; **GPL-3.0**, exception в `deny.toml` — §3/§12),
`anyhow`/`thiserror` (ошибки), `clap` (CLI у bin-ов). Точные версии пинятся в `Cargo.lock` при реализации;
лицензии — через `cargo-deny`.
---
## 5. Контракты D-Bus-стабов
Соглашения (ipc §2): имя `ru.shturman.<Service>`, путь `/ru/shturman/<Service>`, интерфейс
`ru.shturman.<Service>1` (N = мажор). Всё async. Ошибки — `ru.shturman.Error.<Name>`.
### 5.1 Топология шины (dev)
- **Прод/VM-E2E:** одна **системная шина устройства** (ipc §1, ✅ зафиксировано 2026-06-23) + policy-файл
`systemd/dbus/ru.shturman.conf` (право `own` на `ru.shturman.Power`/`.Settings`). Policy для `ru.shturman.dev.*`
(dev-mock) — **отдельный dev-only drop-in** `ru.shturman.dev.conf`, **не входящий в прод-образ**. Зеркалит прод.
Decision-журнал ipc.md синхронизируем при реализации (§13).
- **Unit/Integration-тесты:** **приватная сессионная шина** на тест (`dbus-run-session` / встроенный
`dbus-daemon`) — герметично, параллелизуемо, без root (§9).
- Изоляция песочных апов — через прокси (появится с App-Host, v3).
### 5.2 `ru.shturman.Power` — стаб (B04, домен B §9)
- **Имя/путь/интерфейс:** `ru.shturman.Power` · `/ru/shturman/Power` · `ru.shturman.Power1`.
- **Методы:**
- `GetPowerState() → s` — текущее состояние, enum-строка ∈ `{off, accessory, running, shutting_down, sleep, battery_cutoff}`.
- `RequestSleep()` — внутренний; **в стабе no-op** (полная sleep/wake — v1/v2, B §7).
- **Сигналы:**
- `AccChanged(b on)`
- `ShutdownImminent(u seconds, s reason)``reason ∈ {acc_off, under_voltage, thermal, battery_cutoff}`
- `ShutdownAborted()` — re-power до PONR
- `Sleep()`, `Wake()`**объявлены в интерфейсе (канон ipc §3), но в v0-стабе НЕ эмитятся**
зарезервированы до v1/v2 (B §7), как `RequestSleep` и неиспользуемые `Error`-варианты (§5.4).
- **Properties (+ `PropertiesChanged`):**
- `IgnitionState: s``{off, accessory, running}`**канон** (B §1; E зеркалит, не дублирует)
- `Uptime: t` — секунды **монотонных** часов (`CLOCK_MONOTONIC`, B §8)
- `PowerSource: s``{vehicle_12v, holdup_cap, sleep_rail, low_battery}`
- **Стаб-поведение (v0):** старт в `running`/`IgnitionState=running`/`PowerSource=vehicle_12v`;
`Uptime` растёт монотонно. **Никаких** методов записи/actuator (#2). Реальная FSM/секвенсинг — v0.3.
- **Dev-mock (feature `dev-mocks`):** доп.интерфейс `ru.shturman.dev.PowerMock1` на том же объекте —
«**fake-ACC**» для тестов и будущего v0.3:
- `SetAcc(b on)` → меняет state/IgnitionState + эмитит `AccChanged`
- `SetIgnition(s state)`
- `TriggerShutdown(u seconds, s reason)` → эмитит `ShutdownImminent`
- `AbortShutdown()` → эмитит `ShutdownAborted`
- **Гейтинг прод:** прод-сборка идёт `--no-default-features` (фича `dev-mocks` выкл.) → `PowerMock1` не
регистрируется; CI имеет job, проверяющий это (§8.3); policy `ru.shturman.dev.*` — dev-only drop-in (§5.1),
не в прод-образе. (Прод-сборки в v0 нет — A01 v4; методы — мок-мутации, не actuator, #2 не нарушают; гейт
заведён заранее, чтобы фича не протекла.)
- **Идентичность/жизненный цикл (ipc §2):** идентичность вызывающего — по соединению (`sender`), не из
аргумента; в v0 (нет песочниц) — без enforcement, **зарезервировано**. Серверное клиент-состояние
(подписки) снимается по `NameOwnerChanged` — даёт `zbus` (тест — §9.2).
### 5.3 `ru.shturman.Settings` — стаб (core-инфра)
- **Имя/путь/интерфейс:** `ru.shturman.Settings` · `/ru/shturman/Settings` · `ru.shturman.Settings1`.
- **Методы:**
- `Get(s key) → v value``value` как D-Bus variant; неизвестный ключ → `ru.shturman.Error.InvalidArgument`.
- `Set(s key, v value)` — валидировать ключ, записать (атомарно в `/data`), эмитить `Changed`.
- `List(s prefix) → as keys`
- `Reset(s key)` — вернуть дефолт (или удалить), эмитить `Changed`.
- **Сигналы:** `Changed(s key, v value)`.
- **Стор:** `/data/settings/settings.json`; запись — **durable-write** (a-base §3):
`settings.json.tmp``fsync(file)``rename``fsync(dir)`. Загрузка на старте; дефолты сеются
first-boot (§7.2). **Settings — единственный писатель этого поддерева** (architecture §3).
- **Дефолты v0 (seed):** `ui.theme = "auto"` (∈ `{auto, day, night}`), `ui.units = "metric"` (канон
единиц — data-model §2; конвертация на презентации).
- **Namespace-изоляция по `sender→app-id`** (ipc §3) — **отложена** (нет песочных апов в v0); стаб =
плоские ключи. Указатель: enforce появится с App-Host/прокси (v3).
### 5.4 `ru.shturman.Error` (контракт ошибок, ipc §2)
Enum в `shturman-ipc` (zbus `DBusError`): `PermissionDenied`, `NotAvailable`, `Stale`, `Timeout`,
`ReadOnly`, `InvalidArgument`, `Unsupported`. В v0 реально используются `InvalidArgument` (Settings) и
зарезервированы остальные (полная семантика — по мере сервисов).
---
## 6. Shell — первый Slint-кадр (срезы C03/C04/C05/C07/C02)
**Скоуп:** минимальный **первый кадр**, доказывающий сквозной поток, **не** полный v0.5-shell.
**Каталожные срезы (минимум каждого; полные версии — v0.5):** C03 (первый кадр), C04 (Slint-shell),
**C05** (декларативный рендер тайлов — плейсхолдеры), **C07** (статус-бар время + сеть-`«unknown»`),
**C02** (тема день/ночь по локальному времени).
- **UI (Slint):** окно со **статус-баром** (часы из системного времени; индикатор сети — `«unknown»`,
C §3 деградированный контракт) + **home-грид** из нескольких крупных тайлов-плейсхолдеров
(декларативно) + (dev) индикатор `IgnitionState`.
- **Поток данных (через `shturman-sdk`, не напрямую):**
1. старт → `sdk::connect()``SettingsClient::get("ui.theme")` → применить тему;
2. подписка `Settings.Changed` → живое обновление темы/единиц;
3. `PowerClient::power_state()` + подписка `AccChanged`/`PropertiesChanged(IgnitionState)` → отразить.
- **Тема день/ночь:** v0 — **по локальному времени** (RTC/NTP/fake-hwclock, C §6); GPS-восход — v1;
до синка времени — пометка неопределённости (C §3). Визуальные **токены design-system** — каркасом
(нейтральные плейсхолдеры); полный визуальный язык — гейт v0.5.
- **Рендер-бэкенды:**
- *dev интерактивно:* Slint под **weston** (вложенный Wayland) в VM, либо нативно на macOS-хосте.
- *CI/E2E без дисплея (автоматический ассерт кадра):* (a) **Slint software-renderer → RGBA/PNG-буфер**
(без дисплей-сервера и композитора) → ассерт «кадр не пустой» + (b) `slint::testing` — дерево элементов
(root + тайлы + часы) и дата-байндинг темы из Settings.
- ⚠️ **`grim` НЕ годится** (требует `wlr-screencopy`, которого у `weston` нет). Если нужен скриншот именно
под weston — `weston-screenshooter`; но **основной автотест кадра — software-renderer**, композитор не нужен.
- **Границы:** Shell **не** реализует логику апов, **не** трогает CAN/safety (C §1). Композитор (smithay),
слот-поверхности, App-Host-хостинг — **не здесь** (v0.5/v3).
---
## 7. База v0.1 + v0.6 (day-1)
### 7.1 Образ/layout в VM (A01/A02/A17)
- **dev-база = Lima Ubuntu ARM64** (нативно к RK3588; A01-прод 🟡 Armbian/Debian vs Yocto — v4).
- **Layout `/data` + overlay (моделируем, не boot-select):**
- `/data`**отдельный журналируемый носитель**: loopback-образ + **постоянный mount** (`data.mount`/
fstab, см. §7.6), монтируется **на каждом boot до сервисов** с power-safe-опциями. ФС dev = **ext4**;
**барьеры — дефолт ядра** (legacy `barrier=1` НЕ задаём — он не отображается в `findmnt`), ассертим реально
отображаемые non-default опции (`errors=remount-ro`, явный `commit=N`). **Главная power-safe-гарантия —
durable-write** (§5.3/§9.1), не mount-опция. **f2fs (`fsync_mode=strict`) — на HW** (a-base §3, 🟡 A02;
формулировку a-base §3 про `barrier=1` синхронизируем — §13).
- **overlay upper/work — на tmpfs (volatile)** (a-base §3); персист — только в `/data`.
- RO-rootfs — **дисциплина** (rootfs трактуем read-only; запись только в `/data`/tmpfs).
- **A/B boot-select (U-Boot env) — НЕ в VM** (нет U-Boot): layout документируем, **переключение
слотов — на HW/v4**. Честная граница VM↔HW (a-base §2 — два разных «образа»).
- **Локаль (A17):** `ru_RU.UTF-8` (LANG/LC_*), `tzdata`, **кириллические шрифты до Shell** (консоль/splash),
console keymap — в provisioning.
### 7.2 First-boot provisioning (A06) — `shturman-firstboot`
Oneshot-бинарь под `shturman-firstboot.service` (`ConditionPathExists=!/data/.shturman-provisioned`,
`After=`/`Requires=data.mount`), **идемпотентно:**
1. создать структуру `/data/{apps,settings,state,log}`;
2. **сгенерировать** persistent `machine-id` в `/data/state/machine-id` (one-shot; гейт маркером ок).
**Привязку** к `/etc/machine-id` делает **отдельный every-boot юнит** `shturman-machineid.service` (§7.6) —
bind волатилен (overlay tmpfs / RO-rootfs) и не переживает ребут, а firstboot после маркера скипается
(a-base §11: «генерим один раз, биндим каждый boot»);
3. посеять дефолт-настройки (`ui.theme=auto`, `ui.units=metric`) — формат совместим с `Settings`-стором;
4. (плейсхолдер) активировать BSP/vehicle-профиль (в VM — no-op);
5. **последним** шагом — durable-write маркера `/data/.shturman-provisioned` (частичный сбой mid-run → повтор
на next boot довосстанавливает, т.к. маркер пишется атомарно в самом конце).
Идемпотентность, factory-reset (маркер удалён + `/data` очищен → пересоздание), прерывание mid-run — тесты §9.1.
### 7.3 Время (A07)
- `systemd-timesyncd` (NTP) + **`fake-hwclock`** с **override пути на `/data`** (`FILE=/data/state/fake-hwclock.data`
через `/etc/default/fake-hwclock` или drop-in) — стоковый путь `/etc/fake-hwclock.data` бьёт по RO-rootfs и
нарушает «персист только в `/data`» (A11). Персист last-known-time (период + on-shutdown) — OS-уровень в
provisioning. GPS-источник UTC — v1 (домен K).
- **Дисциплина монотонности (B §8):** все lifecycle-таймеры/`Uptime` — на `CLOCK_MONOTONIC` (helper в
`shturman-common`), не wall-clock.
- **B-owned save-on-shutdown** (периодика + on-sync + on-shutdown, B §8) — интегрируется в **v0.3**
(graceful shutdown). В v0.6 — базовый OS-fake-hwclock.
### 7.4 Память (A09)
- **zram** (lz4/zstd) через **zram-generator**`/etc/systemd/zram-generator.conf` (секция `[zram0]`);
**только один механизм** (НЕ `zram-tools` — конфликт за `/dev/zram0`). **Swap-на-flash запрещён** (a-base §8).
- **`systemd-oomd`** — `systemd/oomd-shturman.conf`: защищаем Stage-1 critical set (в v0: `power`/`settings`/
`shell`); первые жертвы — фон (в v0 их нет — задел иерархии, performance §5/§3).
- **cgroup-лимиты** (`MemoryMax`/`MemoryHigh`) — drop-ins юнитов (числа 🟡, профиль на железе).
### 7.5 Логи (A10) и eMMC write-min (A11)
- **journald `Storage=volatile`** (журнал в RAM/tmpfs, не на flash) + `RuntimeMaxUse` + `RateLimitIntervalSec`/
`RateLimitBurst``systemd/journald-shturman.conf`. Rust-`tracing` → journald (`tracing-journald`), та же политика.
- **Критичные события переживают power-cut:** size-capped append с `fsync` в `/data/log/` (kernel panic,
итог ACC-off, причина recovery) + `pstore/ramoops` — каркас в `shturman-common` (тело крит-логов —
по мере событий).
- **eMMC write-min (A11):** дисциплина (volatile-логи, tmpfs-транзиент, zram, без спама в `/data`).
**Измеримая проверка (детерминированный VM-прокси):** дельта записанных секторов на loop-устройстве `/data`
(`/proc/diskstats`) за фиксированное окно простоя (напр. 60 c после boot-settle) **ниже порога T** (🟡
калибруется) + **нет периодических флашей вне allow-list писателей** (`fake-hwclock`, `Settings` on-Set);
всё прочее → fail. Абсолютный байт-бюджет — вердикт на RK3588 (performance §2).
### 7.6 systemd-оркестрация (A15)
- **`data.mount`** — постоянный mount loop-`/data` с power-safe-опциями; зависимые юниты несут `RequiresMountsFor=/data`.
- **`shturman-machineid.service`** — every-boot oneshot: bind `/data/state/machine-id``/etc/machine-id`
(`After=data.mount`, `Before=dbus.service`/`systemd-machine-id-commit`) — стабильный machine-id до старта шины
(нужен системной шине, §5.1).
- **`shturman.target`** — v0 critical set (Stage-1 срез), порядок:
`data.mount``shturman-firstboot.service``shturman-machineid.service``dbus`
`shturman-power` + `shturman-settings``shturman-shell`.
- **`Requires=` + `After=shturman-firstboot.service`** у power/settings/shell — при провале firstboot сервисы
**не стартуют** (не работают против полу-провиженного `/data`; `Wants=` лишь упорядочивает — недостаточно).
- Юниты: `Restart=on-failure` (изоляция #4); `RuntimeWatchdogSec` — задел (полный watchdog — v0.3, B §6).
- D-Bus policy `ru.shturman.conf``own` на имена сервисов; `ru.shturman.dev.*`**dev-only drop-in** (§5.1).
---
## 8. Dev-харнесс (v0.6)
### 8.1 Lima-VM (`lima/shturman.yaml`) и как поднимаем
- **Параметры:** `vmType: vz` (Apple Virtualization, нативно ARM64); `images:` Ubuntu ARM64 LTS;
ресурсы (зафиксировано): `cpus: 4`, `memory: 6GiB`, `disk: 20GiB` (под 16 ГБ хоста, dev-environment
«VM лёгкая»; правится локально); `mounts:` репозиторий **writable** (правим на хосте — собираем в VM).
- **provision (system):** установить пакеты (`systemd`, `dbus`, `pipewire` + WirePlumber [задел v1],
`weston` + `weston-screenshooter`, `can-utils`, `rustup`/toolchain, `python3`+venv, **`systemd-zram-generator`**,
`fake-hwclock`, кириллические шрифты); `modprobe vcan` + `ip link add vcan0`;
создать loopback-`/data` (ext4 + power-safe-опции) и завести **постоянный** `data.mount`/fstab + tmpfs-overlay;
override `fake-hwclock` пути на `/data`; разложить `systemd/`-юниты + journald/zram-generator/oomd drop-ins +
dbus policy (прод `ru.shturman.conf` + dev-only `ru.shturman.dev.conf`); включить `shturman.target`.
*(screenshot кадра в CI — через Slint software-renderer, без пакета grim; см. §6.)*
- **reference-«BSP» (A16):** в dev это **Lima-профиль** (дев-таргет). Реальный reference-BSP (DT overlay +
HAL + DBC) — на HW (a-base §13), вне VM.
- **Подъём:** `just vm-up``limactl start --name=shturman lima/shturman.yaml` (создание+provision);
`just vm-shell``limactl shell shturman`; `just vm-reset` (сброс «ничего не сломать», dev-environment).
### 8.2 `justfile` — единые команды
| Цель | Делает |
|------|--------|
| `vm-up` / `vm-down` / `vm-reset` / `vm-shell` | жизненный цикл Lima-VM |
| `build` | `cargo build --workspace` (в VM/нативно на Linux/CI) |
| `test` | `cargo test --workspace` (unit + integration, §9) |
| `lint` | `cargo fmt --check` + `cargo clippy --workspace -- -D warnings` |
| `deny` | `cargo deny check` (лицензии #12 + advisories) |
| `up` / `down` | старт/стоп сервисов Штурмана на шине (dev) |
| `run` | поднять `shturman.target` локально (Power+Settings+Shell) для ручной проверки |
| `shell-frame` | запустить Shell один раз, отрендерить кадр (software-renderer) → PNG для инспекции |
| `e2e` | сквозной VM-тест приёмки (§9.3): boot → имена на шине → fake-ACC → кадр |
| `new-plugin <name>` | scaffolding плагина из `templates/plugin/` (F02) |
| `validate-manifest <path>` | запустить `shturman-manifest-validator` (F02) |
| `plugin-dev-run <path>` | **каркас** (F03): сообщает «нужен App-Host (v3)» |
| `sim` | **плейсхолдер** Vehicle Simulator (v2, домен E) |
| `ci` | локальный прогон гейта: `lint` + `test` + `deny` |
### 8.3 CI (локальный гейт; авто-CI — позже)
> ⚠️ **`.github/workflows/ci.yml` удалён** (2026-06-24): self-hosted **Gitea** триггерится на GitHub-Actions-формат,
> что не нужно. Активный гейт — **локальный `just ci`**. Авто-CI на Gitea (Gitea Actions/runner) — решение позже.
> Описание ниже — задел на тот момент.
- (задел) jobs **lint** (fmt+clippy), **build**, **test** (unit+integration —
раннер **уже Linux**, шину/сервисы/headless-рендер гоним напрямую, **без Lima**), **license**
(`cargo-deny`), **prod-build-gate** (`cargo build --workspace --no-default-features` + ассерт, что
`PowerMock1` не экспортируется без фичи `dev-mocks` — §5.2). Позже — **integration** (vcan+симулятор, v2)
и **build образа** (v4).
- *Фолбэк (dev-environment):* если ARM64-раннеры вне бесплатного тира — x86 + кросс / self-hosted.
- **seed perf-gate (performance §9 ◻️):** замер **time-to-first-frame** в VM/раннере — **функционально**
(smoke), с явной пометкой «**не перф-вердикт**» (вердикт — RK3588, performance §2). Состав авто-гейта
(boot/кадр/ввод/голос) расширяем по фазам.
### 8.4 SDK (F01) + dev-tools (F02) + харнесс (F04)
- **`shturman-sdk` (F01):** `connect()`; `SettingsClient`/`PowerClient` (обёртки proxy из `ipc`);
`manifest::Manifest` (serde-схема YAML, plugin-sdk §2) — **single source** схемы для валидатора и
будущего рантайма. semver.
- **`shturman-manifest-validator` (F02):** schema-валидация + правила (capabilities из таксономии;
`len(tiles) ≤ ui_tiles`; `vehicle_read` только из каталога data-model; `ru` обязателен; манифестный
`id` `ru.shturman.*` **закрыт** для сторонних — F §3). Коды ошибок — человекочитаемые.
- **scaffolding (F02):** `templates/plugin/` (валидный `manifest.yaml` + минимальный бинарь-каркас на
`shturman-sdk` + `locales/ru.yaml`); `just new-plugin foo` генерит плагин, **проходящий** валидатор.
- **F04 (частично):** `fixtures/manifests/` — библиотека **плохих** манифестов (коллизия id,
неподдерж. `shturman_api`, `tiles>ui_tiles`, capability вне таксономии, `vehicle_read` вне каталога,
попытка `ru.shturman.*`) + один валидный → фикстуры тестов валидатора. Рантайм-fault-injection
(crash-loop/mem-hog) — **позже** (нужен App-Host).
---
## 9. План тестирования и приёмка
Пирамида (dev-environment): **unit → integration → E2E → HW-in-the-loop**. HW (RK3588) — **отложено**
(нет железа); перф-**вердикт** там (performance §2).
### 9.1 Unit (хост/VM/CI, по крейтам)
- `shturman-ipc`: сериализация enum↔строка (`PowerState`/`IgnitionState`/`PowerSource`/`ShutdownReason`);
маппинг `Error` ↔ D-Bus-имя.
- `shturman-common`: **тест power-safe #5** — атомарная запись (temp→fsync→rename→fsync(dir)) + **симуляция
сбоя между `rename` и `fsync(dir)` / прерывания**: читается прошлая ИЛИ новая версия, нет частично
записанного/битого файла (это и есть доказательство #5, не reboot); монотонные часы.
- `shturman-settings`: load/seed/round-trip Set→Get; `Reset` к дефолту; неизвестный ключ → `InvalidArgument`;
персист переживает перезапуск процесса (тест на tmp-`/data`).
- `shturman-firstboot`: **идемпотентность** — (а) маркер на месте → no-op; (б) **factory-reset**: маркер
удалён + `/data` очищен → пересоздаёт `/data/{apps,settings,state,log}`, повторно сеет дефолты, ставит маркер,
machine-id перегенерён; (в) **прерывание mid-run** (kill до маркера) → повторный прогон довосстанавливает.
- `shturman-sdk`: парс манифеста (валидный/битый).
- `shturman-manifest-validator`: каждый bad-фикстур → ожидаемый код; good → pass.
- `shturman-shell`: `slint::testing` — дерево элементов строится, тема байндится из мок-Settings.
- Команда: `just test` (или `cargo test -p <crate>`).
### 9.2 Integration (сервисы на **приватной** шине)
- Харнесс поднимает `dbus-run-session` → стартует `shturman-settings` + `shturman-power` → тест-клиент
**через `shturman-sdk`**:
- `Settings.Set("ui.theme","night")``Get` = `night`; приходит `Changed`;
- `Power.GetPowerState()` = `running`; `IgnitionState` читается;
- `dev-mocks`: `PowerMock1.SetAcc(false)` → подписчик получил `AccChanged(false)`;
- **снятие клиент-состояния:** подписчик исчезает → серверная подписка снимается по `NameOwnerChanged` (ipc §2).
- Команда: `just test` (integration-тесты в `crates/*/tests/`, фича `dev-mocks`).
### 9.3 E2E (полный поток в Lima-VM) — `just e2e`
В VM: `shturman.target` поднят → раннер `tests/e2e/`:
1. **boot/init + оркестрация:** `findmnt /data` смонтирован **до сервисов** и показывает реально отображаемые
non-default опции (напр. `errors=remount-ro`); overlay-tmpfs присутствует; **per-unit** `systemctl is-active`
= active для `shturman-power`/`-settings`/`-shell` и inactive(dead, Result=success) для firstboot/machineid —
**не** довольствуемся `is-system-running ∈ {running,degraded}` (degraded маскирует упавший юнит).
2. **first-boot (A06):** маркер есть; повторный запуск `shturman-firstboot` — no-op.
3. **шина:** `busctl --system list` содержит `ru.shturman.Power` и `ru.shturman.Settings` (own).
4. **персист через перезапуск (функц., НЕ power-safe):** `Settings.Set`**reboot VM** → значение сохранилось;
**`machine-id` стабилен после reboot** (every-boot bind работает). *(Настоящий #5 — unit-тест атомарности §9.1;
реальный power-cut — v0.3.)*
5. **fake-ACC:** `PowerMock1.SetAcc(...)``AccChanged` наблюдаем.
6. **первый кадр:** Shell → **software-renderer → PNG не пустой** + (`slint::testing`) root+тайлы+часы;
тема соответствует `ui.theme`.
7. **base-бюджеты (функц.):** journald `Storage=volatile` активен; `zramctl` показывает **одно** zram-устройство;
`fake-hwclock`-данные лежат в `/data` (не в `/etc`); eMMC-прокси (§7.5) — дельта секторов loop-`/data` ниже
порога за окно простоя, вне allow-list писателей записей нет.
8. **seed perf:** замер time-to-first-frame (логируется, не блокирует — не вердикт).
### 9.4 Критерии приёмки (acceptance)
**v0.1** (roadmap: «init поднялся; RO-rootfs A/B + overlay + `/data` ок»):
- [ ] VM грузится; per-unit critical set active; `/data` смонтирован **до сервисов** с реальными power-safe-опциями; overlay(tmpfs) есть.
- [ ] First-boot создал структуру `/data` **идемпотентно**; `machine-id` стабилен **после reboot** (every-boot bind).
- [ ] RO-rootfs-дисциплина соблюдена (запись только в `/data`/tmpfs). *(A/B boot-select — HW/v4.)*
**v0.6** (roadmap: «dev-итерация без железа работает; память/лог/eMMC в бюджете»):
- [ ] `just vm-up && just build && just test && just lint && just deny` — зелено **без железа**.
- [ ] journald `Storage=volatile`; одно zram-устройство; oomd-политика загружена; eMMC-прокси в пороге (§7.5).
- [ ] `shturman-sdk` собирается; `just validate-manifest` корректно принимает good / отвергает bad;
`just new-plugin foo` генерит плагин, **проходящий** валидатор.
- [ ] Governance: `LICENSE`, `CONTRIBUTING.md`, `DCO`, `README.md` (мини-спека §11) присутствуют; prod-build-gate (§8.3) зелёный.
**Шагающий скелет** (CLAUDE.md):
- [ ] `ru.shturman.Power` + `ru.shturman.Settings` владеют именами на шине.
- [ ] Settings round-trip + персист через reboot; fake-ACC `AccChanged` наблюдаем.
- [ ] **Первый Slint-кадр** рендерится (PNG не пустой), отражает `ui.theme` из Settings и состояние `Power`.
---
## 10. LICENSE (MIT) — содержимое файла `LICENSE`
```
MIT License
Copyright (c) 2026 K9 Shturman
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
> **Примечание (лицензионный слой UI):** наш код — MIT. Но `slint` для embedded доступен бесплатно только под
> **GPL-3.0**, поэтому **шипимый UI-бинарь** (Shell и будущие first-party-апы, линкующие Slint) при распространении
> прод-образа (v4) будет под GPL-3.0. Это **отложенное решение (вариант A, §12 п.8)**; до v4 dev-использование
> обязательств не создаёт. MIT GPL-совместима (односторонне). Если копилеф­т неудобен — миграция на Makepad/Iced (MIT)
> или commercial-лицензия Slint.
---
## 11. `CONTRIBUTING.md` — предлагаемое содержимое
Закрывает **governance-пробел**. Структура:
1. **Добро пожаловать + философия:** open-source companion-ОС для авто (RK3588), MIT; язык общения —
русский, код-идентификаторы — как есть.
2. **Красные линии (нерушимы, principles #1#2):** PR, добавляющий CAN-write/actuator/Mode-04/UDS-write
или safety-critical-интеграцию (двигатель/тормоза/ABS/ESP/руль/подушки), **отклоняется**
допустимы только OBD-read (Mode 01/03/07/09/0A). Граница — `docs/contracts/safety.md`.
3. **Источник правды — `docs/`:** не противоречить дизайн-докам; при расхождении реальности и дока —
**синхронизировать док** (двунаправленный шов), не «молча обойти».
4. **Лицензионная гигиена (#12):** зависимости — MIT/Apache-2.0/BSD/ISC-совместимые; GPL/AGPL —
избегать или изолировать отдельным процессом; **LGPL — гранулярно** (динамическая/системная линковка
допустима, не blanket-запрет); **`cargo deny check` — обязателен** (CI-гейт). Исключения (напр. `slint`
GPL-3.0, §3) — явные и задокументированные.
5. **Рабочий цикл (фаза реализации):** roadmap ведёт; цикл на веху — **спека → TDD → реализация →
verify в Lima-VM → коммит**; **код не пишем до утверждённой спеки**. Скиллы: TDD,
writing/executing-plans, verification-before-completion, systematic-debugging, requesting-code-review.
6. **Стиль кода:** `rustfmt` + `clippy -D warnings` (через `just lint`); Rust везде в проде
(Python — только dev/симуляторы, tech-stack).
7. **Тесты:** пирамида unit/integration/E2E; «тестируемо без машины» (#13) — каждая фича со своим
симулятором/моком; `just test` зелёный до PR.
8. **Коммиты/ветки:** `feat/fix/chore(<area>): …`; ветка от `main``main` без явного «ок» не коммитим);
в конце коммита — `Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>`.
9. **PR-чеклист:** прошёл `just ci`; не нарушает красные линии; доки синхронизированы; добавлены тесты.
10. **DCO sign-off (обязателен):** `git commit -s` (Developer Certificate of Origin) — без CLA.
Файл `DCO` (текст DCO 1.1) — в корне.
11. **Поведение/споры:** Code of Conduct (указатель; добавить `CODE_OF_CONDUCT.md` — рек. Contributor Covenant).
12. **Где обсуждать:** GitHub Issues/Discussions; 🟡-решения — в roadmap «Риск-реестр».
13. **`README.md` (мини-спека, корень репо):** название + абзац-описание; красные линии (CAN read-only / не
safety-critical); ссылки на `CLAUDE.md` и `docs/`; быстрый старт (`just vm-up` / `just run`); лицензия
MIT + DCO + примечание о GPL-слое UI (§10). Галочка приёмки — §9.4.
---
## 12. Решения (зафиксированы) и отложенное (не блокеры)
**✅ Зафиксировано (ревью 2026-06-23):**
1. **Контрибьюция — DCO** (`git commit -s`, Developer Certificate of Origin); CLA не вводим. Файл `DCO` (1.1) в корень.
2. **Правообладатель в LICENSE — `K9 Shturman`** (§10).
3. **Топология шины в dev — системная шина устройства + policy** (`systemd/dbus/ru.shturman.conf`),
зеркало прода (совпадает с рек. ipc §1); decision-журнал ipc.md синхронизируем при реализации (§13).
4. **ФС `/data` в dev-VM — ext4** (барьеры — дефолт ядра, `barrier=1` не задаём); **f2fs (`fsync_mode=strict`) — на HW**
(a-base §3; прод-ФС финализируется на прод-образе, 🟡 A02).
5. **Ресурсы Lima-VM — 4 CPU / 6 GiB / 20 GiB** (под 16 ГБ-хост; правится локально).
6. **Место спек — `docs/specs/`.**
7. **Глубина «первого кадра» — минимум:** статус-бар (часы + сеть «unknown») + грид-плейсхолдеры +
тема из Settings; полный home/тема/анимации — v0.5.
8. **UI-тулкит / лицензия Slint — вариант A: отложено к v4.** На v0 берём Slint; dev в VM не триггерит
копилеф­т-обязательств распространения. Royalty-free неприменим (embedded исключён) → бесплатный путь =
GPL-3.0; `deny.toml` — exception `slint = GPL-3.0, pending v4`. Makepad (primary) / Iced (fallback) — пермиссивные
запасные на случай HW-спайка. Финал (GPL-карв-аут #12 vs миграция vs commercial) — до прод-образа v4. Подробно: §3, §10-примечание.
**Отложенное (CLAUDE.md «всплывут по ходу — НЕ блокеры старта»):**
- `A01` Armbian/Debian vs Yocto — прод-образ v4 (dev = Lima Ubuntu).
- `A02` f2fs vs ext4 — финал на прод-образе (dev = ext4, п.4).
- `B08/B09` MCU vs supercap-only — v0.4, вероятно нужна аппаратная проверка.
- **UI-тулкит/лицензия** (Slint GPL vs пермиссивный Makepad/Iced vs commercial) — финал к v4; HW-спайк запасных при случае.
---
## 13. Двунаправленные швы (синхронизировать при реализации)
По правилу проекта (реальность ↔ док). Сейчас не правим (ещё спека) — фиксируем точечные правки на момент кода:
- **`ipc.md`** decision-журнал: топология шины 🟡 → ✅ «системная шина устройства» (§5.1).
- **`a-base §3`**: `barrier=1` — дефолт ядра (legacy-алиас, не отображается); привести формулировку к «барьеры по
умолчанию; durable-write — главная гарантия power-safe» (§7.1).
- **`architecture §6`**: ремарка «Stage-1-минимум для v0-скелета: Perm-Broker/App-Host включаются с первым
песочным клиентом (плагин/surface-ап), Shell в v0 — обычный сервис на шине» (§2.2).
- **`tech-stack.md`**: лицензия Slint — `GPL-3.0` для embedded (royalty-free неприменим); решение по тулкиту
отложено к v4 (§12 п.8); Makepad/Iced — пермиссивные запасные.
- **`principles #12`**: уточнить LGPL — гранулярно (динамическая/системная линковка допустима), а не blanket;
согласовать с `deny.toml`/§3.
---
## 14. Дальше по ритму
1. ✅ Спека → правки → **adversarial-ревью пройдено** (17 находок применены в этой v1) → ждём твоё финальное «ок».
2. По «ок» — **TDD-план** (бите-сайз по writing-plans): порядок крейтов
(`common``ipc``sdk``firstboot`/`settings`/`power``shell``tools`) + dev-харнесс + governance-файлы.
3. **Реализация (TDD)****verify в Lima-VM** (§9) → синхронизация швов §13 → **коммит**
(ветка от `main`, после твоего «ок»).
```