n7n-plugin-host 0.1.6

wasmtime-бекенд для crate::registry::PluginRegistry (n7n-harness) — друга (wasm-компонентна) реалізація слотів detector/verify/ast_facts/t0, поруч з in-process-бекендом §3.12 пункту 16а
Documentation
//! wasmtime-бекенд `wit/*.wit`-контрактів (`n7n:fix-deps@0.1.0`) — спека
//! `2026-08-17-n7n-harness-local-models.md`, §3.12 пункт 16, ДРУГА половина
//! (пункт 16а — контракти й in-process-бекенд, `harness::registry` — уже
//! зроблений, без wasmtime; цей крейт додає ЄДИНУ нову залежність, яку
//! той крок свідомо не чіпав).
//!
//! # Чому окремий крейт, а не `harness`/`llm-lib`
//!
//! `wasmtime` — найбільша залежність усього плану `FixDeps`-плагінів.
//! `harness` тримає інваріант «нуль важких залежностей» (CI перевіряє
//! `cargo tree -p n7n-harness --all-features` — там немає `wasmtime`); і
//! `n7n-llm-lib` (featureless-ядро) щойно спеціально звужували (§3.1).
//! Обидва інваріанти цей крейт **не чіпає** — він лише СПОЖИВАЄ обидва
//! (`n7n-harness` за типами `DetectFn`/`T0Fn`/`Violation`/`VerifyFn`/
//! `AstFactsFn` з `crate::registry`/`crate::pipeline`, `n7n-llm-lib` з
//! `default-features = false` за `EditPlan`/`FileEditPlan`/`VerifyReport`).
//! Підключає його лише той споживач, що реально вантажить wasm-плагіни
//! (`rules-fix`) — `harness`/`llm-lib` лишаються придатними для
//! споживачів, яким wasm не потрібен, без жодного зайвого байта в графі.
//!
//! # Що тут головне: чотири `Wasm*` — друга реалізація тих самих слотів
//!
//! [`harness::registry::PluginRegistry`] уже спроєктований так, що бекенд
//! компонента — друга реалізація `T` усередині `Slot<T>`, не окремий
//! трейт-об'єкт (доккоментар `registry.rs`, "чому `T`, а не трейт"). Чотири
//! типи нижче — [`WasmDetector`], [`WasmVerify`], [`WasmAstFacts`],
//! [`WasmT0`] — виробляють РІВНО ті самі callable-типи, що in-process
//! бекенд уже приймає ([`harness::pipeline::DetectFn`],
//! [`harness::registry::VerifyFn`], [`harness::registry::AstFactsFn`],
//! [`harness::pipeline::T0Fn`]) — `PluginRegistry::new`/`replace_*` не
//! відрізняє, звідки прийшло замикання: з `Arc::new(|| ...)` напряму чи
//! з `WasmDetector::into_detect_fn()` нижче. Володіння (реєстр, `Slot`,
//! `generation`, LIFO-аквайр) лишається рівно тим самим кодом
//! (`crates/harness/src/registry.rs`) — цей крейт лише постачає ЩО саме
//! кладеться в слот.
//!
//! # Безпекова межа (§3.9) — де вона виражена в коді
//!
//! - **Guest-и не отримують WASI-права запису взагалі.** Єдине місце, де
//!   guest отримує (чи не отримує) права на диск — [`wasi_ctx::read_only_repo_ctx`]:
//!   `FsPerms::ReadOnly`, нуль `ReadWrite` в усьому крейті.
//!   Кожен із чотирьох `Wasm*::call*` нижче будує `WasiCtx` РІВНО через цю
//!   функцію — підміни в обхід неї немає, бо `Store` для guest-виклику
//!   ніде більше в крейті не конструюється.
//! - **T0 повертає `edit-plan`, хост застосовує.** [`WasmT0::into_t0_fn`]
//!   виробляє лише [`harness::pipeline::T0Fn`] (порахувати план) — фаза
//!   застосування (`PreparePlanFn`/`CommitPlanFn`, `write_guard`) сюди НЕ
//!   входить: `n7n-llm-lib` тут узятий з `default-features = false`, тож
//!   `write_guard` (живе під фічею `agents`) цьому крейту недоступний за
//!   побудовою — той самий принцип, що вже застосовує `harness::registry`
//!   (доккоментар `T0Step`: "`prepare`/`commit` — цілком хостові… не
//!   частина контракту «порахувати план»"). Викликач збирає повний
//!   `T0Step { plan: wasm_t0.into_t0_fn(), prepare: ..., commit: ... }`
//!   сам, як і сьогодні для in-process T0.
//! - **Жодного спавну процесів у guest.** Жоден із чотирьох `wit/*.wit`
//!   world-ів не імпортує нічого, що дало б guest-у спавнити процес
//!   (жодного `wasi:cli/exec`-подібного імпорту в жодному з
//!   `detector-plugin`/`verify-plugin`/`ast-facts-plugin`/`t0-plugin`), і
//!   WASI Preview 2 в принципі не має такого стандартного інтерфейсу —
//!   немає wasi-фічі, яку можна було б увімкнути, щоб це з'явилось.
//! - **Інстанс на спробу, без кешу.** Кожен `Wasm*::call*` будує СВІЖИЙ
//!   `Store`+`WasiCtx`+`Instance` на КОЖЕН виклик (не лише на кожен
//!   `checkout()` реєстру) — суворіша гарантія, ніж вимагає §3.16 правило
//!   3 ("інстанс на спробу"): жодного способу випадково закешувати
//!   `Instance` між викликами немає, бо для нього просто немає поля, яке
//!   пережило б виклик. Кешується лише СКОМПІЛЬОВАНИЙ [`wasmtime::component::Component`]
//!   (і `Engine`/`Linker`, які самі по собі не несуть стану guest-а) —
//!   те саме розрізнення "Component ≠ Instance", яке задача явно вимагає.
//!
//! # Тестовий guest
//!
//! `tests/guest/detector_fixture` і `tests/guest/t0_fixture` — мінімальні
//! `wasm32-wasip2`-компоненти для наскрізного тесту
//! (`tests/wasmtime_backend.rs`). Компіляція гейтована наявністю
//! `wasm32-wasip2` в `rustup target list --installed` — де його немає
//! (чи `cargo build` для guest-крейта впаде з іншої причини, напр. немає
//! мережі до crates.io), тест друкує причину й виходить РАНО, не падаючи:
//! середовище цієї задачі мало і target, і мережу (перевірено на старті),
//! тож тут наскрізний прогін ДІЙСНО виконується, а не лише компілюється
//! бекенд.

mod ast_facts;
mod detector;
mod engine;
mod host_state;
mod t0;
mod verify;
mod wasi_ctx;

pub use ast_facts::WasmAstFacts;
pub use detector::WasmDetector;
pub use t0::WasmT0;
pub use verify::WasmVerify;