64ef300ef4
- версионирование: shturman_api ↔ набор D-Bus-мажоров; хост экспортирует все поддерживаемые мажоры параллельно; неподдерж. версия → Error.Unsupported (не тихо) - IntentHandler: обязательный dispatch-таймаут; крэш/таймаут/NotAvailable → деградированный ответ (#4), пайплайн не виснет (#11) - vehicle_read: per-vehicle Unavailable (Subscribe ок, SignalChanged молчит, плагин деградирует); install-ревью предупреждает о неподдерживаемых сигналах - авторитет манифеста: extension_points.tiles авторитетны, ui_tiles — квота (len ≤ N) - storage: квота (ENOSPC плагину не системе — бережёт /data), remove чистит, миграция - lifecycle: discover/install только для сторонних; first-party — в base-образе, без install - network host-allowlist приведён к best-effort (синхрон с security-privacy) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
169 lines
13 KiB
Markdown
169 lines
13 KiB
Markdown
# Контракт: 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: ru.shturman.plugin.fuel-tracker # reverse-DNS, уникальный
|
||
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
|
||
storage: true
|
||
# network: { hosts: ["api.example.ru"] } # опц.: host-allowlist (см. §3)
|
||
|
||
extension_points:
|
||
tiles:
|
||
- id: consumption
|
||
title: "Расход"
|
||
intents:
|
||
handler: /ru/shturman/plugin/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`.
|
||
|
||
## 3. Capability-модель
|
||
|
||
- Таксономия и **гейтинг по каналам** — в [security-privacy.md](security-privacy.md) §3–4
|
||
(не дублируем). Манифест **декларирует** → 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 в едином стиле).
|
||
- `Shell.RegisterScreen(decl | surface)` — декларативный экран **или** Wayland-поверхность (богатый ап рисует сам).
|
||
- Декларативный словарь (констрейнт): `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 |
|