diff --git a/docs/specs/plans/03-stub-services.md b/docs/specs/plans/03-stub-services.md new file mode 100644 index 0000000..b45682a --- /dev/null +++ b/docs/specs/plans/03-stub-services.md @@ -0,0 +1,93 @@ +# План 3 — стаб-сервисы `firstboot` / `settings` / `power` + интеграция на шине + +> REQUIRED SUB-SKILL: `executing-plans` + TDD. Полный код — в `src` по ходу; здесь дизайн/задачи/acceptance. + +**Goal:** поднять привилегированные стаб-сервисы ядра и доказать «шагающий скелет» на **живой шине**: +`Power`/`Settings` владеют именами, round-trip настроек, fake-ACC сигнал. First-boot идемпотентно готовит `/data`. + +**Architecture:** сервисы — отдельные процессы (architecture §3/§4), зависят от `ipc` (контракт), **не** от `sdk`. +Каждый — **lib + bin** (логика в lib для тестов; `main` тонкий). Bus-bootstrap (`connect`) — общий, в `ipc::conn`. +Интеграция — на **session-шине** (`dbus-run-session`), тесты `#[ignore]` (не требуют шины в `just test`). + +**Tech:** `zbus` `#[interface]` (server), `tokio`, `shturman-common` (Layout/atomic), `dbus` (session-bus для тестов). + +**Решения v0:** Settings хранит **строковые** значения (variant на проводе; типы расширим позже) и **сам сеет +дефолты** на первом старте; firstboot создаёт каталоги + `machine-id` (`/dev/urandom`) + маркер, **дефолты не +сеет** (single source формата — Settings; шов §7.2 синхронизировать). `dev-mocks` — **default-фича** power +(прод: `--no-default-features`). + +--- + +### P3.1: `ipc::conn::connect` (рефактор из sdk) + +- `crates/shturman-ipc/src/conn.rs`: `pub async fn connect() -> zbus::Result` — система по + умолчанию, `SHTURMAN_BUS=session` → сессия (перенести тело из `sdk::connect`). +- `ipc/lib.rs`: `pub mod conn; pub use conn::connect;`. `sdk/connect.rs` → `pub use shturman_ipc::connect;`. +- Build + `just test` (без регрессий). commit `refactor(ipc): connect() в ipc::conn; sdk ре-экспортит`. + +### P3.2: `shturman-firstboot` (A06) — lib provision + bin (TDD) + +- `crates/core/shturman-firstboot` (members). Deps: `shturman-common`, `anyhow`. +- `lib.rs`: `pub fn provision(layout: &Layout) -> std::io::Result` — idempotent: + маркер есть → `Ok(false)`; иначе создать `/data/{apps,settings,state,log}`, сгенерить `state/machine-id` + (32 hex из `/dev/urandom`, если нет), **durable-write маркер последним** → `Ok(true)`. + *(Привязка machine-id→`/etc/machine-id` — отдельный every-boot юнит, План 5. Дефолты настроек — Settings.)* +- `main.rs`: `init_tracing("shturman-firstboot")`; `provision(&Layout::from_env())`; лог; exit. +- **TDD-тесты** (tmp-`/data` через `Layout::new`): (а) идемпотентность (2-й прогон → `false`, структура/`machine-id` + стабильны); (б) factory-reset (снести маркер+wipe → пересоздание, новый `machine-id`); (в) mid-run (каталоги без + маркера → довосстановление, маркер появился). +- Red → impl → green → commit `feat(firstboot): идемпотентный provision /data + machine-id (A06)`. + +### P3.3: `shturman-settings` — Settings1 server-стаб (lib + bin) + +- `crates/core/shturman-settings` (members). Deps: `shturman-ipc`, `shturman-common`, `zbus`, `tokio`, `anyhow`; + dev-deps: `shturman-sdk`, `tempfile`. +- `store.rs` (lib): `Store{ layout, map: BTreeMap }` — `load_or_seed` (если `settings.json` нет → + дефолты `ui.theme=auto`, `ui.units=metric`, durable-write); `get/set/reset/list`; persist через `common::write_atomic`. + **Unit-тесты:** seed при пустом, round-trip set→get, reset к дефолту, list по префиксу, неизвестный ключ. +- `service.rs` (lib): `SettingsService` + `#[interface(name="ru.shturman.Settings1")]`: + `get(key)->Result` (неизвестный → `InvalidArgument`); `set(key,Value)` (только строка в + v0, иначе `InvalidArgument`; persist + emit `changed`); `list(prefix)`; `reset(key)` (emit `changed`); + `#[zbus(signal)] changed(ctx, key, value)`. +- `main.rs`: `connect()` → `object_server().at(settings::PATH, svc)` → `request_name(settings::NAME)` → park. +- Build + unit-тесты store зелёные → commit `feat(settings): Settings1 стаб + атомарный стор + seed`. + +### P3.4: `shturman-power` — Power1 server-стаб + dev-mock (lib + bin) + +- `crates/core/shturman-power` (members). Deps: `shturman-ipc`, `shturman-common`, `zbus`, `tokio`, `anyhow`; + dev-deps: `shturman-sdk`, `tempfile`. **Features:** `default=["dev-mocks"]`, `dev-mocks=[]`. +- `service.rs` (lib): `PowerService{ state, ignition, source, epoch }` + `#[interface(name="ru.shturman.Power1")]`: + `get_power_state()->String` (= `running` стаб); `request_sleep()` (no-op); `#[zbus(property)]` + `ignition_state`/`uptime`(монотон. `common::monotonic_secs`)/`power_source`; `#[zbus(signal)]` + `acc_changed`/`shutdown_imminent`/`shutdown_aborted`/`sleep`/`wake`. + `#[cfg(feature="dev-mocks")]` второй интерфейс `ru.shturman.dev.PowerMock1` на том же объекте: + `set_acc(bool)` (меняет ignition/state + emit `acc_changed`), `set_ignition(String)`, + `trigger_shutdown(u32,String)` (emit), `abort_shutdown()` (emit). +- `main.rs`: `connect()` → `at(power::PATH, svc)` → `request_name(power::NAME)` → park. +- **Unit-тест:** дефолты (`running`/running/`vehicle_12v`), `uptime` монотонен. Build → commit + `feat(power): Power1 стаб + dev-mock fake-ACC (feature)`. + +### P3.5: интеграция на session-шине + `just test-integration` + +- `shturman-settings/tests/integration.rs` (`#[ignore]`): in-process server на session-conn + `sdk::SettingsClient` + на втором conn → `set("ui.theme","night")` → `get`==night; приходит `changed`; неизвестный ключ → ошибка. +- `shturman-power/tests/integration.rs` (`#[ignore]`, `--features dev-mocks`): `PowerClient.power_state()==Running`; + `PowerMock1.set_acc(false)` → подписчик получил `acc_changed(false)`; снятие подписки по выходу владельца. +- `justfile`: `test-integration: dbus-run-session -- cargo test --workspace -- --ignored` (+ note: нужен `dbus`). +- Прогон под `dbus-run-session` зелёный (вручную; CI/Lima-E2E — План 5). `just ci` (unit) не затронут (тесты `#[ignore]`). +- commit `test(core): интеграция Settings/Power на session-шине (#[ignore])`. + +--- + +## Acceptance (Плана 3) + +- [ ] `cargo build --workspace` зелёный (3 сервиса; default-фича power включает dev-mock). +- [ ] `just test` (unit) зелёный: firstboot (идемпотентность/factory-reset/mid-run), settings-store, power-defaults; common/ipc/sdk не сломаны. +- [ ] `just test-integration` (под `dbus-run-session`): Settings round-trip + `changed`; Power state + fake-ACC `acc_changed`. +- [ ] `prod-build-gate`: `cargo build -p shturman-power --no-default-features` — `PowerMock1` не регистрируется (нет dev-mock-символов). +- [ ] `just ci` зелёный (fmt/clippy/test/deny). Границы: сервисы зависят от `ipc`, не от `sdk`. + +## Дальше + +План 4 — `shturman-shell` (Slint первый кадр на `sdk`) + `slint::testing` + slint GPL-3.0 exception в `deny.toml`. +План 5 — dev-tools (валидатор/scaffolding/fixtures) + systemd-юниты + Lima + сквозной E2E.