# Контракт: 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/.yaml`. **`ru` обязателен** (дефолт + фолбэк); матчинг интентов — по активной локали (d-assistant §6). На старте заполняем только `ru` (i18n-ready, не i18n-now). ## 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 в едином стиле — [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//`). - **Квота:** у `/data/apps//` дефолтный размерный лимит (+ опц. больший в манифесте), enforce на записи (ENOSPC плагину, **не** системе) — `/data` журналируемый, от его места зависит graceful-flush (a-base §3/§9); неограниченный плагин не должен его исчерпать. Механизм (project-quota / учёт App-Host) — домен A/F. - **Remove:** при удалении плагина `/data/apps//` удаляется (опц. экспорт до удаления) — не оставляем сирот (отдельно от 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/` (ENOSPC плагину); remove чистит; миграция — плагин | 2026-06-16 | | i18n | строки манифеста локализуемы (per-locale/ключи); `ru` обязателен (i18n-ready) | 2026-06-16 | | UI-capabilities | `ui_tiles` (квота тайлов) + `ui_screens` (гейт `RegisterScreen`) | 2026-06-16 |