diff --git a/docs/contracts/ipc.md b/docs/contracts/ipc.md index 16cb951..0f3bff2 100644 --- a/docs/contracts/ipc.md +++ b/docs/contracts/ipc.md @@ -4,7 +4,7 @@ > Формализует карту связей из [architecture.md](../architecture.md) §5 в конкретные > контракты. К нему цепляются data-model, plugin-sdk и все домены. -Статус: **v1 (на ревью).** +Статус: **v2 (на ревью).** v2 — после ретро-ревью (10 находок). Связано с: [architecture.md](../architecture.md) (§4–5) · [data-model.md](data-model.md) · [plugin-sdk.md](plugin-sdk.md) · [security-privacy.md](security-privacy.md) Область: **control plane** (D-Bus). Аудио/видео/графика — НЕ здесь (PipeWire/Wayland, см. architecture §4). @@ -31,13 +31,15 @@ | Well-known имя | `ru.shturman.` (напр. `ru.shturman.VehicleData`) | | Путь объекта | `/ru/shturman/` | | Интерфейс | `ru.shturman.N` — `N` = мажорная версия (напр. `…VehicleData1`) | -| Версионирование | аддитивные изменения внутри версии; ломающие → новый интерфейс `…2` | -| Ошибки | `ru.shturman.Error.`: `PermissionDenied`, `NotAvailable`, `ReadOnly`, `InvalidArgument`, `Unsupported` | +| Версионирование | аддитивно внутри версии; ломающее → **добавляется** `…2` рядом со старым `…1`; оба сосуществуют в окне поддержки (старые плагины работают без правок; EOL мажора синхронизируется с `shturman_api` в манифесте) | +| Ошибки | `ru.shturman.Error.`: `PermissionDenied`, `NotAvailable` (PID не поддержан машиной — постоянно), `Stale`/`Timeout` (транзиентно: таймаут/устарело), `ReadOnly`, `InvalidArgument`, `Unsupported` | | Действие | **метод** | | Изменение состояния | **сигнал** | | Текущее состояние | **property** (+ стандартный `PropertiesChanged`) | | Асинхронность | всё async (zbus + tokio); долгие операции не блокируют | | Стандартные интерфейсы | `org.freedesktop.DBus.{Properties,Introspectable,Peer}` | +| Идентичность вызывающего | по **аутентифицированному соединению** (`sender` → PID/cgroup → манифест, привязка App-Host'ом), НЕ по аргументу метода; подменить нельзя | +| Жизненный цикл клиент-состояния | серверное состояние клиента (подписки, регистрации тайлов/интентов) **снимается автоматически** при исчезновении владельца имени (`NameOwnerChanged`) — крэш/рестарт не оставляет мусора | > Полные сигнатуры (XML-интроспекция) фиксируются при реализации; здесь — реестр и ключевые члены. @@ -46,10 +48,12 @@ ## 3. Реестр сервисов — ядро (привилегированные) ### `ru.shturman.VehicleData` — данные машины (**READ-ONLY**) -- **Методы (только чтение):** `ListSignals() → [name]`, `GetSignal(name) → (value, unit, ts)`, `Subscribe(names, max_hz)`/`Unsubscribe(names)`, `GetDtcs() → [(code, status, desc)]`. -- **Сигналы:** `SignalChanged(name, value, ts)` — **только подписчикам и только по подписанным** именам (не широковещательно «всё всем»), `DtcsChanged([...])`. -- **Политика частоты:** на шину сигналы идут **с потолком (~10–20 Гц)** — этого хватает для читаемых датчиков и держит control-plane лёгким. Сырой высокочастотный CAN остаётся внутри VehicleData. Цифровому приборному щитку, если нужна более плавная стрелка, — клиентская интерполяция или выделенный быстрый канал (не D-Bus); см. «Открытые вопросы». -- **Properties (горячие, текущее значение):** `Speed`, `Rpm`, `CoolantTemp`, `BatteryVoltage`, `Online`. +- **Методы (только чтение):** `ListSignals() → [(name, availability)]`, `GetSignal(name) → (value, unit, ts, quality)`, `Subscribe(names, desired_max_hz)`/`Unsubscribe(names)`, `GetDtcs() → [(code, status, desc)]`. +- **Авторизация по имени сигнала — в самом сервисе:** прокси (§5) не видит payload, поэтому `vehicle_read:[…]` enforce-ит **VehicleData по идентичности вызывающего** (`sender` → манифест), отвергая незаявленные имена `PermissionDenied`. Сигналы **адресные**: `SignalChanged(name, value, ts, quality)` шлётся только подписчику и только по `Subscribe ∩ манифест`; `DtcsChanged([...])`. +- **Частота:** `desired_max_hz` — верхняя граница, которую готов принять подписчик, и вход в **агрегатный** опрос: фактический опрос PID = `max()` запрошенных, ограничен потолком шины (~10–20 Гц) и источником. Доставка индивидуально не троттлится — слабый клиент (приборка) прореживает сам. Имя `desired_` подчёркивает: не гарантия «моей частоты». +- **Подписки привязаны к владельцу соединения** — снимаются автоматически при крэше/рестарте; агрегатный опрос пересчитывается, опрос PID останавливается без живых подписчиков (бережёт скудный ELM327). +- **Таймаут/staleness:** `GetSignal` имеет таймаут → `Stale`/`Timeout` (или stale-flag в `quality`), не виснет на медленном ELM327-PID. `NotAvailable` = PID не поддержан машиной (постоянно); `Stale` = транзиентно (двигатель заглушен/таймаут). +- **Properties (горячие):** `Speed`, `Rpm`, `CoolantTemp`, `ModuleVoltage` (питание ЭБУ ≈ бортсеть, не клеммы АКБ), `Online`. - ⛔ **Методов записи нет** — ни `SetSignal`, ни actuator-команд, ни clear-DTC. Архитектурная гарантия read-only (принцип #2). Типы — [data-model.md](data-model.md). ### `ru.shturman.Power` — питание и жизненный цикл @@ -60,12 +64,13 @@ ### `ru.shturman.Settings` — конфигурация и состояние - **Методы:** `Get(key) → value`, `Set(key, value)`, `List(prefix) → [key]`, `Reset(key)`. - **Сигналы:** `Changed(key, value)`. -- Namespace на каждый ап изолирован (брокер не даёт читать чужой). +- **Namespace изолирует сам сервис Settings** по идентичности вызывающего (`sender` → app-id): Get/Set/List/Reset вне своего namespace → `PermissionDenied` (прокси этого не делает — не инспектирует строку `key`). Свой namespace — read/write по умолчанию (ambient, без отдельной capability). -### `ru.shturman.PermBroker` — выдача разрешений (привратник) -- **Методы:** `CheckCapability(app, cap) → bool`, `RequestRuntimeGrant(cap) → granted` (рантайм-промпт пользователю, напр. «ап X хочет сеть»). -- **Сигналы:** `GrantChanged(app, cap, granted)`. -- Основная фильтрация — на уровне прокси (§5); этот API для рантайм-согласий. +### `ru.shturman.PermBroker` — политика разрешений (привратник) +- **Идентичность — только из соединения** (`sender` → манифест), **никогда из аргумента** (иначе спуфинг/проверка за чужой ап). +- **Методы:** `CheckCapability(cap) → bool` (sender-scoped — ап спрашивает про СЕБЯ; кросс-ап форма доступна только привилегированным core по D-Bus-политике), `RequestRuntimeGrant(cap) → granted` (рантайм-промпт; целевой ап = `sender`). +- **Сигналы:** `GrantChanged(cap, granted)` — адресно владельцу гранта (чужие не получают). +- Статическая фильтрация — прокси + sandbox (§5, владеет App-Host); broker — политика и рантайм-согласия. ### `ru.shturman.AppHost` — запуск/супервизия апов и плагинов - **Методы:** `ListApps() → [(id, status)]`, `StartApp(id)`, `StopApp(id)`, `GetStatus(id) → status`. @@ -75,14 +80,19 @@ ### `ru.shturman.Connectivity` — сеть (обёртка NM/MM) - **Методы:** `GetStatus() → status`, `ListNetworks() → [...]`, `Connect(id)`, `Disconnect()`. - **Сигналы:** `ConnectivityChanged(online)`, `NetworkChanged(...)`. -- **Properties:** `Online`, `Type` (wifi/modem), `SignalStrength`. +- **Properties:** `Online` (bool — «сервис ещё не поднят» ≠ «offline»), `Type` (wifi/modem), `SignalStrength`. + +### `ru.shturman.Location` — GPS/положение (владелец — домен K) +- **Properties:** `Latitude`, `Longitude`, `Heading`, `Speed` (GPS-скорость), `FixQuality`, `ts`. +- **Сигналы:** `LocationChanged(...)`, `FixChanged(quality)`. +- Источник — NMEA/gpsd (hardware §4); гейтится capability `location`. **GPS-скорость берётся ОТСЮДА** (не из VehicleData/E — E это CAN/OBD, появляется в v2). До домена K — мок-стаб (dev fake-GPS). --- ## 4. Реестр сервисов — апы (на SDK) ### `ru.shturman.Assistant` -- **Методы:** `SendText(query) → reply`, `StartListening()`, `StopListening()`, `RegisterIntents(handler_path, [pattern])` (для плагинов с capability `assistant_intents`). +- **Методы:** `SendText(query) → reply`, `StartListening()`, `StopListening()`, `RegisterIntents(handler_path)` — привязывает handler к фразам **из манифеста** `assistant_intents` (фразы НЕ передаются в рантайме, иначе обходится install-ревью). - **Сигналы:** `StateChanged(idle|listening|thinking|speaking)`, `TranscriptUpdated(text)`, `ReplyUpdated(text)`. - Контекст машины подмешивается ассистентом из `VehicleData` (см. architecture §5). @@ -100,7 +110,7 @@ 1. Манифест плагина декларирует capabilities (`vehicle_read:[speed]`, `network`, `assistant_intents`…). 2. App-Host поднимает плагин в bubblewrap и даёт ему **фильтрующий D-Bus-прокси**, сконфигурированный под эти capabilities. -3. Прокси пропускает только разрешённые сервисы/интерфейсы/методы/сигналы. Пример: `vehicle_read:[speed]` → виден `VehicleData.GetSignal("speed")` и `SignalChanged` по speed; **не виден** `Power`, чужие апы, любые write-методы. +3. Прокси даёт **грубую** изоляцию — сервис/интерфейс/метод/сигнал (payload/аргументы НЕ инспектирует). Пример: `vehicle_read:[speed]` → `VehicleData` (read) **достижим**, а `Power`, чужие апы и write-методы — **нет**. **Тонкую** авторизацию по конкретному имени сигнала / ключу настроек делает сам сервис по идентичности `sender` (см. VehicleData, Settings). 4. Рантайм-согласия (сеть «сейчас») — через `PermBroker.RequestRuntimeGrant`. Итог: плагин физически не может дотянуться до того, чего нет в манифесте. Детали модели — [security-privacy.md](security-privacy.md). @@ -140,4 +150,7 @@ |---------|-------|------| | Топология шины | системная шина устройства + фильтрующий прокси на ап (🟡 на подтверждение) | 2026-06-16 | | Изоляция доступа апов | портал-паттерн: per-app dbus-proxy из манифеста | 2026-06-16 | -| Соглашения имён/версий | `ru.shturman.*`, версия в имени интерфейса | 2026-06-16 | +| Соглашения имён/версий | `ru.shturman.*`, версия в имени интерфейса; мажоры сосуществуют в окне поддержки (аддитивно) | 2026-06-16 | +| Enforcement | грубо — прокси (сервис/метод); тонко (per-signal/per-key) — сам сервис по `sender` | 2026-06-16 | +| Идентичность | только из аутентиф. соединения, не из аргумента; клиент-состояние снимается по `NameOwnerChanged` | 2026-06-16 | +| Location/GPS | отдельный сервис `ru.shturman.Location` (домен K); GPS-скорость не через E | 2026-06-16 |