docs(specs): План 3 — стаб-сервисы firstboot/settings/power + интеграция
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Alexander <akotenev2003@gmail.com>
This commit is contained in:
@@ -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<zbus::Connection>` — система по
|
||||
умолчанию, `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<bool>` — 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<String,String> }` — `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<OwnedValue,ipc::Error>` (неизвестный → `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.
|
||||
Reference in New Issue
Block a user