a9aad21636
- удалён .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>
677 lines
62 KiB
Markdown
677 lines
62 KiB
Markdown
# Спека реализации: 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 — отложены туда (v2–v4); 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`, после твоего «ок»).
|
||
```
|
||
|