n7n-plugin-host 0.1.1

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

## Встановлення

У межах цього 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::read_only_repo_ctx`: `DirPerms::READ`/
  `FilePerms::READ`, нуль `MUTATE` в усьому крейті.
- **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-wasip2`-
компоненти для наскрізного тесту (`tests/wasmtime_backend.rs`). Компіляція гейтована
наявністю таргета в `rustup target list --installed` — де його немає, тест друкує причину й
виходить рано, не падаючи.

## MSRV

Формально не зафіксований (`rust-version` у `Cargo.toml` відсутній) — edition 2021, тож
мінімум мовою Rust 1.56. CI компілює на поточному stable.