# План 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.