From df553790aecfca5c136a996dbed8b3363d9826d1 Mon Sep 17 00:00:00 2001 From: Alexander Date: Wed, 24 Jun 2026 12:05:30 +0300 Subject: [PATCH] =?UTF-8?q?docs(specs):=20=D0=9F=D0=BB=D0=B0=D0=BD=202=20?= =?UTF-8?q?=E2=80=94=20shturman-ipc=20(=D0=BA=D0=BE=D0=BD=D1=82=D1=80?= =?UTF-8?q?=D0=B0=D0=BA=D1=82)=20+=20shturman-sdk=20(=D0=BA=D0=BB=D0=B8?= =?UTF-8?q?=D0=B5=D0=BD=D1=82)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 Signed-off-by: Alexander --- docs/specs/plans/02-ipc-and-sdk.md | 132 +++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 docs/specs/plans/02-ipc-and-sdk.md diff --git a/docs/specs/plans/02-ipc-and-sdk.md b/docs/specs/plans/02-ipc-and-sdk.md new file mode 100644 index 0000000..e30c8c1 --- /dev/null +++ b/docs/specs/plans/02-ipc-and-sdk.md @@ -0,0 +1,132 @@ +# План 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-настройку раньше).