# n7n-plugin-host
wasmtime-бекенд для `harness::registry::PluginRegistry` ([`n7n-harness`](../harness)) —
друга (wasm-компонентна) реалізація чотирьох слотів плагінів (`detector`/`verify`/
`ast_facts`/`t0`), поруч з in-process-бекендом. Модульний шлях — `plugin_host::...`
(`[lib] name = "plugin_host"`). Контракти — WIT-світи [`wit/*.wit`](wit) (`n7n:fix-deps@0.1.0`),
вендоровані всередині цього крейта (`wasmtime::component::bindgen!` читає
`.wit`-файли на своєму боці компіляції, тож шлях мусить лишатись у межах
опублікованого пакета — інакше build падає і на docs.rs, і для будь-кого,
хто бере крейт з crates.io поза цим workspace).
## Встановлення
У межах цього workspace крейт додається як звичайна path-залежність:
```toml
[dependencies]
plugin-host = { package = "n7n-plugin-host", version = "0.1", path = "../plugin-host" }
```
Модульний шлях лишається `plugin_host::...` незалежно від того, як названо `package`.
## Приклад
`WasmDetector::from_file` компілює `.wasm`-компонент; `into_detect_fn` віддає той самий
callable-тип (`DetectFn`), що `harness::registry::PluginRegistry` уже приймає від
in-process-бекенда:
```rust
use plugin_host::WasmDetector;
let detector = WasmDetector::from_file(repo_root, "detector.wasm")?;
registry.replace_detector(detector.into_detect_fn());
```
## Навіщо окремий крейт
`wasmtime` — найбільша залежність усього workspace-у. `n7n-harness` тримає інваріант «нуль
важких залежностей» (CI перевіряє `cargo tree -p n7n-harness --all-features` — там немає
`wasmtime`), а `n7n-llm-lib` (featureless-ядро) свідомо звужений. Жоден з них цей крейт не
чіпає — він лише споживає обидва (`n7n-harness` за типами `DetectFn`/`T0Fn`/`Violation`/
`VerifyFn`/`AstFactsFn`, `n7n-llm-lib` з `default-features = false` за `EditPlan`/
`VerifyReport`). Підключає його лише той споживач, що реально вантажить wasm-плагіни —
`harness`/`llm-lib` лишаються придатними для решти без жодного зайвого байта в графі.
## Що всередині
Чотири типи — [`WasmDetector`], [`WasmVerify`], [`WasmAstFacts`], [`WasmT0`] — кожен
виробляє рівно той самий callable-тип, що in-process-бекенд уже приймає в
`PluginRegistry::new`/`replace_*`: реєстр не відрізняє, звідки прийшло замикання, з
`Arc::new(|| ...)` напряму чи з `WasmDetector::into_detect_fn()`.
## Безпекова межа
- **Guest-и не отримують WASI-права запису.** Єдине місце, де guest отримує (чи не
отримує) права на диск — `wasi_ctx::no_filesystem_ctx`: **жодного preopen**,
тобто файлової capability немає взагалі. Читає guest вужчим `host-fs.read-file`
(§3.9 передбачає цей шлях явно).
- **T0 лише рахує план, не застосовує.** `WasmT0::into_t0_fn` виробляє лише `T0Fn`
(порахувати `edit-plan`) — фаза застосування (`write_guard`) сюди не входить за
побудовою: `n7n-llm-lib` тут узятий з `default-features = false`.
- **Жодного спавну процесів у guest.** Жоден із чотирьох WIT-світів не імпортує нічого, що
дало б guest-у спавнити процес — WASI Preview 2 в принципі не має такого інтерфейсу.
- **Інстанс на спробу, без кешу.** Кожен `Wasm*::call*` будує свіжий `Store`+`WasiCtx`+
`Instance` на кожен виклик; кешується лише скомпільований `Component` (і `Engine`/
`Linker`, які самі по собі не несуть стану guest-а).
## Фічі wasmtime
Свідомо мінімальний набір, без дефолтних (`wasmtime` за замовчуванням тягне ще ~20 фіч —
`cache`, GC-варіанти, `profiling`, `pooling-allocator`, `threads` тощо, з яких жодна тут не
потрібна): `runtime`, `component-model`, `cranelift` (єдиний доступний компілятор без
альтернативи `winch`). `wasmtime-wasi` — лише фіча `p2` (Component Model WASI); `p1`
(preview1-адаптер) не потрібна, жоден guest не цілить `wasm32-wasip1`.
## Тести
`tests/guest/detector_fixture` і `tests/guest/t0_fixture` — мінімальні `wasm32-wasip3`-
компоненти для наскрізного тесту (`tests/wasmtime_backend.rs`). Компіляція гейтована
наявністю таргета в `rustup target list --installed` — де його немає, тест друкує причину й
виходить рано, не падаючи.
## MSRV
`rust-version = "1.93"` у `Cargo.toml` (workspace-успадкований). CI компілює на
поточному stable, який завжди новіший.