n7n-plugin-host 0.2.0

wasmtime-бекенд для crate::registry::PluginRegistry (n7n-harness) — друга (wasm-компонентна) реалізація слотів detector/verify/ast_facts/t0, поруч з in-process-бекендом §3.12 пункту 16а
Documentation
# 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, який завжди новіший.