Files
shturman/docs/contracts/plugin-sdk.md

177 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Контракт: Plugin / Extension SDK
> **Главный API платформы.** Как расширять Штурман, не трогая ядро. Синтез
> [ipc.md](ipc.md) + [data-model.md](data-model.md) + [security-privacy.md](security-privacy.md).
> Приоритет №1: даже наши first-party апы строятся на этом SDK.
Статус: **v2 (на ревью).** v2 — после ретро-ревью (6 находок).
Связано с: [architecture.md](../architecture.md) (#1 тонкое ядро) · [ipc.md](ipc.md) · [data-model.md](data-model.md) · [security-privacy.md](security-privacy.md) · домены C, F
---
## 1. Принцип
- Плагин = **отдельный процесс**, говорит по D-Bus через **фильтрующий прокси**, в
**bubblewrap-песочнице**. Видит только заявленное в манифесте.
- Тот же SDK — у first-party апов (с расширенными capabilities). Если для фичи
«нужно лезть в ядро» — это баг API, чиним API (принцип #9).
## 2. Манифест (полная схема)
Формат — **YAML**. Единая модель: всё, что нужно плагину, — это **capabilities**
(объединили `capabilities`+`permissions` из исходной спеки в один список).
```yaml
plugin:
id: dev.example.fuel-tracker # reverse-DNS своего домена; ru.shturman.* зарезервирован за first-party (security-privacy §5)
name: "Учёт расхода"
version: 0.1.0 # semver
description: "Пробег и средний расход"
author: "Имя / организация"
shturman_api: "1" # мажорная версия API (совместимость)
capabilities:
vehicle_read: [speed, maf, fuel_level, fuel_rate] # только из каталога data-model
assistant_intents:
- "сколько я проехал"
- "средний расход"
ui_tiles: 1
ui_screens: 1
storage: true
# network: { hosts: ["api.example.ru"] } # опц.: host-allowlist (см. §3)
extension_points:
tiles:
- id: consumption
title: "Расход"
intents:
handler: /dev/example/fuel_tracker/intents # путь IntentHandler
```
**Источник истины и согласованность:** `extension_points.tiles` — авторитетный список
конкретных тайлов; `capabilities.ui_tiles: N`**квота** (Perm-Broker enforce-ит
`len(tiles) ≤ ui_tiles`, иначе install отклоняется `InvalidArgument`). Все фразы
`assistant_intents` маршрутизируются в единственный `extension_points.intents.handler`.
**i18n (принцип #10):** пользовательские строки (`name`/`description`/`tiles[].title`/
`assistant_intents`) — локализуемы: per-locale секции (`assistant_intents: { ru: […], en: […] }`)
или ключи + `locales/<lang>.yaml`. **`ru` обязателен** (дефолт + фолбэк); матчинг интентов — по
активной локали (d-assistant §6). На старте заполняем только `ru` (i18n-ready, не i18n-now).
## 3. Capability-модель
- Таксономия и **гейтинг по каналам** — в [security-privacy.md](security-privacy.md) §34
(не дублируем). Манифест **декларирует** → enforce (статически — прокси+sandbox от App-Host;
рантайм-гранты — Perm-Broker; security-privacy §5).
- `vehicle_read` принимает **только сигналы из каталога** [data-model.md](data-model.md).
- **`network` — host-allowlist:** `network: { hosts: [...] }` — намерение ограничить egress.
⚠️ **Строгая гарантия требует forced-proxy/egress-firewall по SNI/Host** (DNS-allowlist
обходится прямым IP); иначе allowlist — **best-effort**, строго работает только бинарный
`network` on/off (security-privacy §3/§7). Голый `network: true` + `vehicle_read`/`location`
**повышенный риск** при установке.
## 4. Точки расширения
### 4.1 UI — тайлы и экраны (слот-модель, решение №6)
- `Shell.RegisterTile(decl)`**декларативный** элемент (рендерит shell в едином стиле — [design-system.md](design-system.md)).
- `Shell.RegisterScreen(decl) → slot_token` (требует capability `ui_screens`) — декларативный экран **или** Wayland-поверхность (slot-token + slot-протокол, c-shell §4).
- Декларативный словарь (констрейнт): `icon`, `title`, `value`, `state`, привязки к
данным, `action`. Сложнее этого → поверхность. *(🟡 глубину декларативного DSL
уточним при домене C/Shell.)*
### 4.2 Интенты ассистента
- Манифест: `assistant_intents: [фразы]`. Плагин реализует
`ru.shturman.IntentHandler1.HandleIntent(intent_id, slots) → result` и
регистрируется через `Assistant.RegisterIntents` (ipc §6).
- Латентно-критичные встроенные интенты (громче, домой) ассистент держит сам, без плагинов.
- **Контракт отказа/таймаута:** `HandleIntent` ограничен **обязательным dispatch-таймаутом**
(из бюджета латентности d-assistant §10). На таймаут / крэш плагина (`AppHost.AppCrashed`) /
`NotAvailable` — ассистент даёт **деградированный ответ** («не получилось спросить X», #4),
голосовой пайплайн **не виснет** (#11). Долгие интенты — опц. streaming/progress, не блокирующий вызов.
### 4.3 Данные машины (read-only)
- `vehicle_read``VehicleData.Subscribe(names, desired_max_hz)` / `GetSignal` (ipc §3).
Только чтение, только заявленные сигналы.
- **Per-vehicle Unavailable:** заявленный сигнал может быть не поддержан конкретной машиной
(`availability`/`NotAvailable`, data-model §3) — `Subscribe` успешен, но `SignalChanged` не
идёт, пока сигнал недоступен; плагин **деградирует** (#4), не падает. На install-ревью (§6)
заявленные `vehicle_read`, которых нет у текущего авто, показываются предупреждением (best-effort).
### 4.4 Настройки и хранилище
- Свой namespace в `Settings` + приватное хранилище (`storage``/data/apps/<id>/`).
- **Квота:** у `/data/apps/<id>/` дефолтный размерный лимит (+ опц. больший в манифесте),
enforce на записи (ENOSPC плагину, **не** системе) — `/data` журналируемый, от его места
зависит graceful-flush (a-base §3/§9); неограниченный плагин не должен его исчерпать.
Механизм (project-quota / учёт App-Host) — домен A/F.
- **Remove:** при удалении плагина `/data/apps/<id>/` удаляется (опц. экспорт до удаления) —
не оставляем сирот (отдельно от factory-reset, a-base §12).
- **Миграция:** плагин может объявить версию формата хранилища; на мажорный update мигрирует
данные сам (платформа лишь сигналит «есть старые данные»).
## 5. Разрешение коллизии интентов *(резолв открытого вопроса)*
Когда два плагина регистрируют пересекающиеся фразы — слоистая политика:
1. **Встроенные латентно-критичные** интенты — всегда выигрывают, минуя плагины.
2. **Специфичность:** более точное/длинное совпадение бьёт более общее.
3. **Тай-брейк:** first-party > сторонний.
4. **Всё ещё неоднозначно → диздамбигуация:** ассистент уточняет у пользователя
(«кого спросить — X или Y?») либо LLM-роутер выбирает по контексту.
5. **Явная адресация:** «спроси у *учёта расхода*, сколько я проехал» — направляет в конкретный плагин.
6. **На регистрации** пересечения детектируются и подсвечиваются (автору/при установке).
## 6. Биндинги и структура плагина
- **Rust SDK-крейт `shturman-sdk`** — first-class: типобезопасные обёртки над D-Bus,
манифест, helpers (чтение сигналов, регистрация интентов/UI). Другие языки —
через D-Bus (язык-агностично), биндинги по мере нужды экосистемы.
- **Структура:** каталог с `manifest.yaml` + исполняемый бинарь + ассеты.
- **Жизненный цикл (сторонние):** discover → install (ревью capabilities) → App-Host
поднимает в песочнице с прокси → run → update/remove. Дистрибуция/«магазин»/подпись — **домен F**.
- **First-party** апы доставляются в составе **подписанного read-only base-образа**, НЕ проходят
discover/install/ревью (auto-grant); якорь доверия — сам образ, не флажок (security-privacy §5).
## 7. Версионирование и совместимость
- `shturman_api: "N"` — мажорная версия API; **связана с набором D-Bus-мажоров интерфейсов**
(напр. `"1"``VehicleData1`, `IntentHandler1`, `Shell1`…); связь фиксируется таблицей.
- **Параллельный рантайм:** хост экспортирует на шине **все поддерживаемые мажоры одновременно**
весь срок поддержки `shturman_api` — иначе «старые работают на своей» не подкреплено (ipc §2, аддитивно).
- **Депрекация:** сколько мажоров хост держит одновременно и как анонсируется EOL старого.
- **Несовместимая версия:** установка плагина с неподдерживаемым `shturman_api` **явно падает**
`ru.shturman.Error.Unsupported` (не тихий запуск/деградация), всплывает в install-ревью.
- SDK-крейт — semver.
## 8. Пример: `fuel-tracker`
Манифест выше. Что делает: подписывается на `speed/maf/fuel_level/fuel_rate`,
считает пробег и средний расход (свой trip-стейт в `storage`), показывает тайл
«Расход», отвечает на интенты «сколько я проехал»/«средний расход». Никакой сети,
никакой записи — типичный безопасный плагин.
---
## Открытые вопросы (найдено на self-review → роутинг)
- 🟡 **Глубина декларативного UI-DSL** (что именно можно выразить тайлом/экраном без
поверхности). → домен C (Shell).
- ◻️ **Дистрибуция, «магазин», подпись** плагинов. → домен F.
---
## Журнал решений (plugin-sdk)
| Решение | Выбор | Дата |
|---------|-------|------|
| Манифест | YAML; единый список capabilities (объединили с permissions) | 2026-06-16 |
| Точки расширения | UI (тайлы/экраны, слот-модель) · интенты · vehicle_read · settings | 2026-06-16 |
| Коллизия интентов | слоистая: built-in > специфичность > first-party > диздамбигуация; + явная адресация | 2026-06-16 |
| Сеть | host-allowlist в манифесте; голый `network: true` — повышенный риск | 2026-06-16 |
| Биндинги | Rust `shturman-sdk` first-class; др. языки через D-Bus | 2026-06-16 |
| Версии | shturman_api ↔ набор D-Bus-мажоров; параллельный рантайм; неподдерж. → `Unsupported` | 2026-06-16 |
| Интенты | `HandleIntent` с обязательным таймаутом; крэш/таймаут → деградированный ответ, не виснет | 2026-06-16 |
| Манифест-авторитет | `extension_points.tiles` авторитетны; `ui_tiles` — квота (`len ≤ N`) | 2026-06-16 |
| Хранилище | квота на `/data/apps/<id>` (ENOSPC плагину); remove чистит; миграция — плагин | 2026-06-16 |
| i18n | строки манифеста локализуемы (per-locale/ключи); `ru` обязателен (i18n-ready) | 2026-06-16 |
| UI-capabilities | `ui_tiles` (квота тайлов) + `ui_screens` (гейт `RegisterScreen`) | 2026-06-16 |