# План 2 — `shturman-ipc` (контракт шины) + `shturman-sdk` (клиент) > REQUIRED SUB-SKILL: `superpowers:executing-plans` + TDD. Полный код пишется в `src` по ходу (inline-исполнение); > здесь — дизайн, задачи, ключевые сигнатуры, тест-кейсы, acceptance. **Goal:** зафиксировать контракт D-Bus в коде (`shturman-ipc`: ошибки, имена/пути/версии, enum-типы, `#[proxy]` Power1/Settings1) и публичный клиентский слой (`shturman-sdk`: `connect`, `SettingsClient`/`PowerClient`, схема манифеста). Граница: ядро-сервисы будут зависеть от `ipc`; апы/плагины — от `sdk` (architecture §3, #1). **Architecture:** `ipc` ← `common`; `sdk` ← `ipc` + `common`. Enum-типы сериализуются на провод как **строки** (spec §5.2): D-Bus-сигнатуры используют `String`/`Variant`, Rust-сторона конвертит через `as_str`/`FromStr`. Bus-зависимые обёртки (clients) компилируются здесь, **проверяются на живой шине в Плане 3**. **Tech Stack:** `zbus` 4 (`#[proxy]`, `zbus::DBusError`, `zvariant`), `serde`/`serde_yaml` (манифест), `thiserror`. **Контекст:** [спека §4–§5](../v0.1-v0.6-foundation.md), [ipc.md](../../contracts/ipc.md), [data-model §2](../../contracts/data-model.md), [plugin-sdk §2](../../contracts/plugin-sdk.md). Ветка `feat/v0-foundation`, коммиты `-s` + Co-Authored-By. --- ### Task 1: `shturman-ipc` — крейт + `Error` + `names` **Files:** `crates/shturman-ipc/Cargo.toml`, `src/lib.rs`, `src/error.rs`, `src/names.rs`; `Cargo.toml` (members). - Добавить `crates/shturman-ipc` в `members`. Deps: `zbus`, `serde`, `thiserror`, `shturman-common`. - `error.rs` — `ru.shturman.Error.*` (ipc §2): ```rust #[derive(Debug, thiserror::Error, zbus::DBusError)] #[zbus(prefix = "ru.shturman.Error")] pub enum Error { #[zbus(error)] ZBus(zbus::Error), // catch-all для транспортных ошибок #[error("permission denied: {0}")] PermissionDenied(String), #[error("not available: {0}")] NotAvailable(String), #[error("stale: {0}")] Stale(String), #[error("timeout: {0}")] Timeout(String), #[error("read only: {0}")] ReadOnly(String), #[error("invalid argument: {0}")] InvalidArgument(String), #[error("unsupported: {0}")] Unsupported(String), } ``` - `names.rs` — well-known имена/пути (ipc §2): ```rust pub mod power { pub const NAME: &str = "ru.shturman.Power"; pub const PATH: &str = "/ru/shturman/Power"; pub const IFACE: &str = "ru.shturman.Power1"; } pub mod settings { pub const NAME: &str = "ru.shturman.Settings"; pub const PATH: &str = "/ru/shturman/Settings"; pub const IFACE: &str = "ru.shturman.Settings1"; } ``` - **TDD-тест (error):** `Error::InvalidArgument("x").to_string()` содержит `"invalid argument"`; компиляция `zbus::DBusError` подтверждает маппинг имён. **Тест (names):** консты соответствуют конвенции `ru.shturman.*` / `/ru/shturman/*` (sanity). - Build + `cargo test -p shturman-ipc` зелёный → commit `feat(ipc): Error + well-known имена`. ### Task 2: `ipc::types` — enum-типы (TDD round-trip строк) **Files:** `crates/shturman-ipc/src/types.rs`. - Enums (data-model/spec §5.2): `PowerState {off, accessory, running, shutting_down, sleep, battery_cutoff}`, `IgnitionState {off, accessory, running}`, `PowerSource {vehicle_12v, holdup_cap, sleep_rail, low_battery}`, `ShutdownReason {acc_off, under_voltage, thermal, battery_cutoff}`. Каждому — `as_str(&self) -> &'static str` и `FromStr` (snake_case на проводе). - **TDD-тесты:** для каждого enum — `parse(as_str(v)) == v` по всем вариантам; неизвестная строка → `Err`. - Падающий тест → реализация → зелёно → commit `feat(ipc): enum-типы Power/Ignition/Source/Reason (string round-trip)`. ### Task 3: `ipc::proxy` — `Power1`/`Settings1` (`#[proxy]`) **Files:** `crates/shturman-ipc/src/proxy.rs`, обновить `lib.rs` (re-export). - `#[proxy(interface="ru.shturman.Power1", default_service="ru.shturman.Power", default_path="/ru/shturman/Power")]` трейт `Power1`: метод `get_power_state() -> zbus::Result`, `request_sleep()`; `#[zbus(property)]` `ignition_state()->String`, `uptime()->u64`, `power_source()->String`; `#[zbus(signal)]` `acc_changed(on: bool)`, `shutdown_imminent(seconds: u32, reason: String)`, `shutdown_aborted()`, `sleep()`, `wake()`. - `Settings1` трейт: `get(key)->OwnedValue`, `set(key, value: &Value)`, `list(prefix)->Vec`, `reset(key)`; `#[zbus(signal)]` `changed(key: String, value: OwnedValue)`. - Проверка — **компиляция** (zbus-макро генерит клиент). Build зелёный → commit `feat(ipc): zbus-proxy Power1/Settings1`. ### Task 4: `shturman-sdk` — крейт + `manifest` (TDD) **Files:** `crates/shturman-sdk/Cargo.toml`, `src/lib.rs`, `src/manifest.rs`; `Cargo.toml` (members). - Добавить `crates/shturman-sdk` в `members`. Deps: `zbus`, `serde`, `serde_yaml`, `tokio`, `shturman-ipc`, `shturman-common`. - `manifest.rs` — serde-схема (plugin-sdk §2): `Manifest{ plugin: Plugin, capabilities: Capabilities, extension_points: Option }`; `Plugin{id,name,version,description,author,shturman_api}`; `Capabilities{ vehicle_read: Vec, assistant_intents: …, ui_tiles: u32, ui_screens: u32, storage: bool, network: Option<…> }` (поля `#[serde(default)]` где опц.). - **TDD-тесты:** распарсить валидный YAML (пример `fuel-tracker`, plugin-sdk §8) → поля верны; битый YAML → `Err`; отсутствие обязательного `plugin.id` → `Err`. - Падающий тест → реализация → зелёно → commit `feat(sdk): схема манифеста (plugin-sdk §2)`. ### Task 5: `sdk` — `connect` + `PowerClient` + `SettingsClient` (build; bus — План 3) **Files:** `crates/shturman-sdk/src/{connect.rs,power.rs,settings.rs}`, `lib.rs` (re-export). - `connect()` — `zbus::Connection` к **системной шине** (`Connection::system()`); env `SHTURMAN_BUS=session` для тестов/dev → `Connection::session()`. (Вызов на живой шине — План 3.) - `PowerClient{ proxy: Power1Proxy }`: `power_state()->Result` (парс строки в enum через `ipc::types`), `ignition_state()->Result`, `uptime()->Result`; подписки на сигналы (stream). - `SettingsClient{ proxy: Settings1Proxy }`: `get_string(key)`, `set(key, value)`, `list(prefix)`, `reset(key)`, поток `changed`. - Проверка — **компиляция** + `cargo test --workspace` (unit, без шины) + `just ci`. Re-exports в `lib.rs`. - commit `feat(sdk): connect + Power/Settings клиенты (bus-тест — План 3)`. --- ## Acceptance (Плана 2) - [ ] `cargo build --workspace` зелёный (ipc + sdk компилируются, proxy-макро ок). - [ ] `cargo test --workspace` — round-trip enum (ipc) + парс манифеста (sdk) зелёные; common-тесты не сломаны. - [ ] `just ci` зелёный (fmt + clippy -D + test + deny). Новые транзитивные лицензии (zbus и пр.) — в allow или добавить. - [ ] Границы: `ipc` не зависит от `sdk`; сервисы (План 3) будут импортировать `ipc`; `sdk` — для апов. ## Дальше План 3 — стаб-сервисы `firstboot`/`settings`/`power` + интеграция на **живой шине** (здесь всплывёт вопрос D-Bus на macOS-хосте: `brew install dbus` + session-bus, либо перенести Lima-настройку раньше).