Skip to main content

plugin_host/
verify.rs

1//! Wasm-бекенд поверхні `verify` (`wit/verify.wit`, world `verify-plugin`)
2//! — друга реалізація [`harness::registry::VerifyFn`].
3//!
4//! `wit/verify.wit` document-ує, що ЦЕ покриває лише 56/58 виміряних
5//! native-концернів `rules-core` — 2 (`kubeconform`/`kubescape`) спавнять
6//! зовнішній бінарник і wasm-компонентом узагалі не можуть бути (guest не
7//! спавнить процесів, доккоментар `crate` "жодного спавну процесів"); для
8//! них `verify` лишається host-інʼєкцією через in-process бекенд
9//! `harness::registry`, не через цей файл.
10
11use std::path::{Path, PathBuf};
12use std::sync::Arc;
13
14use wasmtime::component::{Component, HasSelf, Linker, ResourceTable};
15use wasmtime::{Engine, Store};
16
17use harness::registry::VerifyFn;
18use llm_lib::attempt::VerifyReport;
19
20use crate::engine::new_engine;
21use crate::host_state::HostState;
22use crate::wasi_ctx::read_only_repo_ctx;
23
24mod bindings {
25    wasmtime::component::bindgen!({
26        path: "wit",
27        world: "verify-plugin",
28    });
29}
30
31/// Дзеркало `record verify-report` → [`VerifyReport`] — доккоментар
32/// `detector.rs::From<...Violation> for Violation` пояснює, чому це
33/// РЕАЛЬНИЙ конвертер `bindgen!`-типу, не ручне дзеркало.
34impl From<bindings::exports::n7n::fix_deps::verify::VerifyReport> for VerifyReport {
35    fn from(r: bindings::exports::n7n::fix_deps::verify::VerifyReport) -> Self {
36        VerifyReport {
37            ok: r.ok,
38            output: r.output,
39            infra_error: r.infra_error,
40        }
41    }
42}
43
44impl bindings::n7n::fix_deps::host_fs::Host for HostState {
45    fn read_file(&mut self, path: String) -> Result<String, String> {
46        std::fs::read_to_string(self.repo_root.join(path)).map_err(|e| e.to_string())
47    }
48}
49
50/// Wasm-бекенд одного `verify`-компонента — доккоментар [`crate::detector::WasmDetector`]
51/// пояснює кешування `Component`/`Engine`/`Linker` проти "інстанс на
52/// спробу" для `Instance`.
53pub struct WasmVerify {
54    engine: Engine,
55    component: Component,
56    linker: Linker<HostState>,
57    repo_root: PathBuf,
58}
59
60impl WasmVerify {
61    /// Доккоментар [`crate::detector::WasmDetector::from_file`] — та сама
62    /// побудова `Engine`/`Component`/`Linker`, лише інший `world`.
63    pub fn from_file(
64        repo_root: impl Into<PathBuf>,
65        wasm_path: impl AsRef<Path>,
66    ) -> wasmtime::Result<Self> {
67        let engine = new_engine()?;
68        let component = Component::from_file(&engine, wasm_path.as_ref())?;
69        let mut linker = Linker::<HostState>::new(&engine);
70        wasmtime_wasi::p2::add_to_linker_sync(&mut linker)?;
71        bindings::n7n::fix_deps::host_fs::add_to_linker::<_, HasSelf<HostState>>(
72            &mut linker,
73            |s| s,
74        )?;
75        Ok(Self {
76            engine,
77            component,
78            linker,
79            repo_root: repo_root.into(),
80        })
81    }
82
83    fn call(&self) -> wasmtime::Result<VerifyReport> {
84        let wasi = read_only_repo_ctx(&self.repo_root)?;
85        let mut store = Store::new(
86            &self.engine,
87            HostState {
88                wasi,
89                table: ResourceTable::new(),
90                repo_root: self.repo_root.clone(),
91            },
92        );
93        let instance =
94            bindings::VerifyPlugin::instantiate(&mut store, &self.component, &self.linker)?;
95        let report = instance.n7n_fix_deps_verify().call_run(&mut store)?;
96        Ok(VerifyReport::from(report))
97    }
98
99    /// Перетворює на [`VerifyFn`]. `verify.wit` (`run: func() ->
100    /// verify-report`, без `result`) не має каналу для "сам виклик упав" —
101    /// той самий брак, що вже несе Rust-сигнатура
102    /// (`Arc<dyn Fn() -> BoxFuture<'static, VerifyReport>>`, без `Result`).
103    /// Інфраструктурний збій викликового шляху (інстанціювання, трап)
104    /// мапиться в `infra_error: true` — рівно те поле, яке `attempt.rs`
105    /// документує як "не результат «червоно»": rig списує хід завжди,
106    /// облік такого випадку — наш.
107    #[must_use]
108    pub fn into_verify_fn(self) -> VerifyFn {
109        let this = Arc::new(self);
110        Arc::new(move || {
111            let this = Arc::clone(&this);
112            Box::pin(async move {
113                this.call().unwrap_or_else(|err| VerifyReport {
114                    ok: false,
115                    output: format!("wasm-компонент verify: {err}"),
116                    infra_error: true,
117                })
118            })
119        })
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    //! Доккоментар `detector.rs::tests` — той самий компіляційний доказ
126    //! без `.wasm`, тут для `verify-report`.
127    use super::*;
128
129    #[test]
130    fn bindgen_verify_report_converts_to_llm_lib_verify_report_field_by_field() {
131        let wit_report = bindings::exports::n7n::fix_deps::verify::VerifyReport {
132            ok: false,
133            output: "діагностика".to_string(),
134            infra_error: true,
135        };
136
137        let report = VerifyReport::from(wit_report);
138
139        assert!(!report.ok);
140        assert_eq!(report.output, "діагностика");
141        assert!(report.infra_error);
142    }
143}