n7n-plugin-host 0.1.0

wasmtime-бекенд для crate::registry::PluginRegistry (n7n-harness) — друга (wasm-компонентна) реалізація слотів detector/verify/ast_facts/t0, поруч з in-process-бекендом §3.12 пункту 16а
docs.rs failed to build n7n-plugin-host-0.1.0
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
Visit the last successful build: n7n-plugin-host-0.2.0

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]: DirPerms::READ/FilePerms::READ, нуль MUTATE в усьому крейті. Кожен із чотирьох 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, і мережу (перевірено на старті), тож тут наскрізний прогін ДІЙСНО виконується, а не лише компілюється бекенд.