1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
//! 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, і мережу (перевірено на старті),
//! тож тут наскрізний прогін ДІЙСНО виконується, а не лише компілюється
//! бекенд.
pub use WasmAstFacts;
pub use WasmDetector;
pub use WasmT0;
pub use WasmVerify;