docs(specs): План 2 — shturman-ipc (контракт) + shturman-sdk (клиент)
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,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<String>`, `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<String>`,
|
||||
`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<ExtensionPoints> }`; `Plugin{id,name,version,description,author,shturman_api}`;
|
||||
`Capabilities{ vehicle_read: Vec<String>, 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<PowerState>` (парс строки в enum через `ipc::types`),
|
||||
`ignition_state()->Result<IgnitionState>`, `uptime()->Result<u64>`; подписки на сигналы (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-настройку раньше).
|
||||
Reference in New Issue
Block a user