Skip to main content

hara_native/
native_cli.rs

1#![cfg(not(target_arch = "wasm32"))]
2
3use std::cell::RefCell;
4use std::path::{Path, PathBuf};
5use std::rc::Rc;
6use std::sync::{mpsc, Arc};
7
8use crate::core::{map_entries, ExceptionInfo, Promise, TraceFrame, Value};
9use crate::invoke_hta::InvokeHtaError;
10use crate::lang::data::Symbol;
11use crate::lang::protocol::INamespaced;
12use crate::{
13    EvaluationId, InProcessSandboxProvider, Runtime, SandboxId, SandboxSpec, SandboxStatus,
14    SessionId, SessionKernel,
15};
16
17mod arguments;
18mod documentation;
19mod kernel;
20use arguments::{
21    keyword, optional_string as optional_string_argument, string as string_argument,
22    strings as strings_argument, strings_value, tap_value,
23};
24pub use documentation::{Documentation, DocumentationValue};
25use kernel::kernel_call;
26
27// Optimized brokers stay within the production 8 MiB ceiling. Debug evaluator
28// frames are much larger and need the same development allowance as the CLI
29// and portable test runner while loading the full language library.
30const RUNTIME_BROKER_STACK_SIZE: usize = if cfg!(debug_assertions) {
31    64 * 1024 * 1024
32} else {
33    8 * 1024 * 1024
34};
35const MAX_DIAGNOSTIC_DATA_BYTES: usize = 16 * 1024;
36
37#[derive(Clone, Copy)]
38enum RuntimeBootstrap {
39    Full,
40    Core,
41    Source,
42}
43
44enum Request {
45    Eval {
46        session: String,
47        source: String,
48        reply: mpsc::Sender<Result<String, String>>,
49    },
50    EvalDiagnostic {
51        session: String,
52        source: String,
53        reply: mpsc::Sender<Result<String, RuntimeDiagnostic>>,
54    },
55    Namespace {
56        session: String,
57        reply: mpsc::Sender<Result<String, String>>,
58    },
59    Complete {
60        session: String,
61        prefix: String,
62        reply: mpsc::Sender<Result<Vec<String>, String>>,
63    },
64    Doc {
65        session: String,
66        symbol: String,
67        reply: mpsc::Sender<Result<Documentation, String>>,
68    },
69    Create {
70        session: String,
71        reply: mpsc::Sender<Result<String, String>>,
72    },
73    Close {
74        session: String,
75        reply: mpsc::Sender<Result<String, String>>,
76    },
77    List {
78        reply: mpsc::Sender<Result<Vec<String>, String>>,
79    },
80    Info {
81        session: String,
82        reply: mpsc::Sender<Result<String, String>>,
83    },
84    RegisterResource {
85        name: String,
86        source: String,
87        reply: mpsc::Sender<Result<(), String>>,
88    },
89    RemoveResource {
90        name: String,
91        reply: mpsc::Sender<Result<(), String>>,
92    },
93    ListResources {
94        reply: mpsc::Sender<Result<Vec<String>, String>>,
95    },
96    #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
97    ConfigureNativeSourceCache {
98        root: PathBuf,
99        source_index_fingerprint: [u8; 32],
100        reply: mpsc::Sender<Result<(), String>>,
101    },
102    #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
103    InstallSourceFoundationImage {
104        image: Vec<u8>,
105        reply: mpsc::Sender<Result<(), String>>,
106    },
107    #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
108    InstallSourceLanguageSpecImage {
109        image: Vec<u8>,
110        reply: mpsc::Sender<Result<(), String>>,
111    },
112    InstallModule {
113        session: String,
114        manifest: String,
115        module: crate::wasmtime_provider::CompiledWasmModule,
116        reply: mpsc::Sender<Result<String, String>>,
117    },
118    InvokeModule {
119        session: String,
120        namespace: String,
121        export: String,
122        arguments: Vec<u8>,
123        reply: mpsc::Sender<Result<Vec<u8>, String>>,
124    },
125    InvokeHta {
126        session: String,
127        qualified_var: String,
128        arguments: Vec<u8>,
129        reply: mpsc::Sender<Result<Vec<u8>, InvokeHtaError>>,
130    },
131    SandboxOpen {
132        spec: SandboxSpec,
133        reply: mpsc::Sender<Result<SandboxId, String>>,
134    },
135    SandboxEval {
136        sandbox: SandboxId,
137        source: String,
138        started: mpsc::Sender<Result<EvaluationId, String>>,
139        reply: mpsc::Sender<Result<String, String>>,
140    },
141    SandboxCall {
142        sandbox: SandboxId,
143        callable: String,
144        arguments: Vec<u8>,
145        started: mpsc::Sender<Result<EvaluationId, String>>,
146        reply: mpsc::Sender<Result<Vec<u8>, String>>,
147    },
148    SandboxCancel {
149        sandbox: SandboxId,
150        evaluation: Option<EvaluationId>,
151        reply: mpsc::Sender<Result<bool, String>>,
152    },
153    SandboxStatus {
154        sandbox: SandboxId,
155        reply: mpsc::Sender<Result<SandboxStatus, String>>,
156    },
157    SandboxClose {
158        sandbox: SandboxId,
159        reply: mpsc::Sender<Result<(), String>>,
160    },
161    Shutdown,
162}
163
164struct BrokerHandle {
165    sender: mpsc::Sender<Request>,
166}
167
168impl Drop for BrokerHandle {
169    fn drop(&mut self) {
170        let _ = self.sender.send(Request::Shutdown);
171    }
172}
173
174#[derive(Clone)]
175pub struct RuntimeBroker {
176    handle: Arc<BrokerHandle>,
177    root: Option<PathBuf>,
178}
179
180/// Structured state retained at an embedding boundary when an evaluation
181/// fails.  It deliberately complements `RuntimeBroker::eval` rather than
182/// changing that established string-error API.
183#[derive(Clone, Debug)]
184pub(crate) struct RuntimeDiagnostic {
185    pub message: String,
186    pub exception: Option<RuntimeException>,
187    pub frames: Vec<TraceFrame>,
188}
189
190/// Send-safe exception information retained for diagnostics. Runtime Values
191/// use single-threaded reference types, so this snapshot is made on the broker
192/// thread before the reply crosses its channel boundary.
193#[derive(Clone, Debug)]
194pub(crate) struct RuntimeException {
195    pub message: String,
196    pub class: Option<String>,
197    pub code: Option<String>,
198    pub data: String,
199    pub cause: Option<Box<RuntimeException>>,
200    pub throws: Vec<crate::core::ExceptionSite>,
201}
202
203impl RuntimeDiagnostic {
204    fn message(message: String) -> Self {
205        Self {
206            message,
207            exception: None,
208            frames: Vec::new(),
209        }
210    }
211}
212
213fn exception_attribute(exception: &ExceptionInfo, name: &str) -> Option<Value> {
214    let key = Value::Keyword(name.into());
215    map_entries(exception.data.as_ref())?
216        .into_iter()
217        .find_map(|(candidate, value)| (candidate == key).then_some(value))
218}
219
220fn bounded_diagnostic_data(value: String) -> String {
221    if value.len() <= MAX_DIAGNOSTIC_DATA_BYTES {
222        return value;
223    }
224    let mut end = MAX_DIAGNOSTIC_DATA_BYTES.saturating_sub(3);
225    while end > 0 && !value.is_char_boundary(end) {
226        end -= 1;
227    }
228    format!("{}...", &value[..end])
229}
230
231fn exception_snapshot(exception: &ExceptionInfo) -> RuntimeException {
232    let provenance = exception.provenance.borrow();
233    RuntimeException {
234        message: exception.message.clone(),
235        class: exception_attribute(exception, "ex/class").map(|value| value.display()),
236        code: exception_attribute(exception, "ex/code").map(|value| value.display()),
237        data: bounded_diagnostic_data(exception.data.display()),
238        cause: exception.cause.as_deref().and_then(|value| match value {
239            Value::ExceptionInfo(cause) => Some(Box::new(exception_snapshot(cause))),
240            _ => None,
241        }),
242        throws: provenance.throws.clone(),
243    }
244}
245
246fn captured_exception(value: Option<Value>) -> Option<RuntimeException> {
247    match value {
248        Some(Value::ExceptionInfo(exception)) => Some(exception_snapshot(&exception)),
249        _ => None,
250    }
251}
252
253impl RuntimeBroker {
254    pub fn start() -> Result<Self, String> {
255        Self::start_with_bootstrap(None, false, false, false, RuntimeBootstrap::Full)
256    }
257
258    /// Starts an isolated broker with the portable core-language runtime.
259    ///
260    /// This is intended for small embedding surfaces and focused tests
261    /// that do not require the language-level Foundation bundle.
262    pub fn start_core() -> Result<Self, String> {
263        Self::start_with_bootstrap(None, false, false, false, RuntimeBootstrap::Core)
264    }
265
266    pub fn start_with(
267        root: Option<PathBuf>,
268        native_sockets: bool,
269        allow_process: bool,
270        allow_postgres: bool,
271    ) -> Result<Self, String> {
272        Self::start_with_bootstrap(
273            root,
274            native_sockets,
275            allow_process,
276            allow_postgres,
277            RuntimeBootstrap::Full,
278        )
279    }
280
281    /// Starts a full broker with the requested ordinary evaluation backend.
282    /// Library callers retain the interpreter-default `start_with` entrypoint;
283    /// command-line frontends use this method to make native execution explicit
284    /// while preserving an interpreter escape hatch.
285    pub fn start_with_backend(
286        root: Option<PathBuf>,
287        native_sockets: bool,
288        allow_process: bool,
289        allow_postgres: bool,
290        execution_backend: &str,
291    ) -> Result<Self, String> {
292        Self::start_with_bootstrap_and_backend(
293            root,
294            native_sockets,
295            allow_process,
296            allow_postgres,
297            RuntimeBootstrap::Full,
298            execution_backend,
299        )
300    }
301
302    /// Starts a full Foundation-backed broker with project namespaces loaded
303    /// lazily from a native source catalog.
304    pub fn start_with_backend_and_source_catalog(
305        root: Option<PathBuf>,
306        native_sockets: bool,
307        allow_process: bool,
308        allow_postgres: bool,
309        execution_backend: &str,
310        source_catalog: crate::project::SourceCatalog,
311    ) -> Result<Self, String> {
312        Self::start_with_bootstrap_and_backend_and_catalog(
313            root,
314            native_sockets,
315            allow_process,
316            allow_postgres,
317            RuntimeBootstrap::Full,
318            execution_backend,
319            Some(source_catalog),
320        )
321    }
322
323    /// Starts a broker whose language libraries are resolved from a native
324    /// project source catalog. Foundation is bootstrapped from source before
325    /// the requested ordinary backend is enabled.
326    pub fn start_with_source_catalog(
327        root: Option<PathBuf>,
328        native_sockets: bool,
329        allow_process: bool,
330        allow_postgres: bool,
331        execution_backend: &str,
332        source_catalog: crate::project::SourceCatalog,
333    ) -> Result<Self, String> {
334        Self::start_with_bootstrap_and_backend_and_catalog(
335            root,
336            native_sockets,
337            allow_process,
338            allow_postgres,
339            RuntimeBootstrap::Source,
340            execution_backend,
341            Some(source_catalog),
342        )
343    }
344
345    /// Starts a source-catalog broker without interpreter Foundation
346    /// bootstrap. Callers install a verified source Foundation image before
347    /// evaluating user code, so each session reconstructs its compiler
348    /// environment from HBC without sharing mutable runtime state.
349    pub fn start_core_with_backend_and_source_catalog(
350        root: Option<PathBuf>,
351        native_sockets: bool,
352        allow_process: bool,
353        allow_postgres: bool,
354        execution_backend: &str,
355        source_catalog: crate::project::SourceCatalog,
356    ) -> Result<Self, String> {
357        Self::start_with_bootstrap_and_backend_and_catalog(
358            root,
359            native_sockets,
360            allow_process,
361            allow_postgres,
362            RuntimeBootstrap::Core,
363            execution_backend,
364            Some(source_catalog),
365        )
366    }
367
368    fn start_with_bootstrap(
369        root: Option<PathBuf>,
370        native_sockets: bool,
371        allow_process: bool,
372        allow_postgres: bool,
373        bootstrap: RuntimeBootstrap,
374    ) -> Result<Self, String> {
375        Self::start_with_bootstrap_and_backend(
376            root,
377            native_sockets,
378            allow_process,
379            allow_postgres,
380            bootstrap,
381            "interpreter",
382        )
383    }
384
385    fn start_with_bootstrap_and_backend(
386        root: Option<PathBuf>,
387        native_sockets: bool,
388        allow_process: bool,
389        allow_postgres: bool,
390        bootstrap: RuntimeBootstrap,
391        execution_backend: &str,
392    ) -> Result<Self, String> {
393        Self::start_with_bootstrap_and_backend_and_catalog(
394            root,
395            native_sockets,
396            allow_process,
397            allow_postgres,
398            bootstrap,
399            execution_backend,
400            None,
401        )
402    }
403
404    fn start_with_bootstrap_and_backend_and_catalog(
405        root: Option<PathBuf>,
406        native_sockets: bool,
407        allow_process: bool,
408        allow_postgres: bool,
409        bootstrap: RuntimeBootstrap,
410        execution_backend: &str,
411        source_catalog: Option<crate::project::SourceCatalog>,
412    ) -> Result<Self, String> {
413        crate::validate_execution_backend(execution_backend)?;
414        if allow_postgres {
415            return Err(
416                "PostgreSQL support is not included in the core hara-native crate".to_owned(),
417            );
418        }
419        let execution_backend = execution_backend.to_owned();
420        let (sender, receiver) = mpsc::channel();
421        let runtime_root = root.clone();
422        std::thread::Builder::new()
423            .name("hara-runtime-broker".into())
424            .stack_size(RUNTIME_BROKER_STACK_SIZE)
425            .spawn(move || {
426                run(
427                    receiver,
428                    runtime_root,
429                    native_sockets,
430                    allow_process,
431                    allow_postgres,
432                    bootstrap,
433                    execution_backend,
434                    source_catalog,
435                )
436            })
437            .map_err(|error| format!("runtime broker failed: {error}"))?;
438        Ok(Self {
439            handle: Arc::new(BrokerHandle { sender }),
440            root,
441        })
442    }
443
444    pub(super) fn root(&self) -> Option<&Path> {
445        self.root.as_deref()
446    }
447
448    pub fn eval(&self, session: &str, source: &str) -> Result<String, String> {
449        self.call(|reply| Request::Eval {
450            session: session.into(),
451            source: source.into(),
452            reply,
453        })
454    }
455
456    pub(crate) fn eval_diagnostic(
457        &self,
458        session: &str,
459        source: &str,
460    ) -> Result<String, RuntimeDiagnostic> {
461        let (reply, response) = mpsc::channel();
462        self.handle
463            .sender
464            .send(Request::EvalDiagnostic {
465                session: session.into(),
466                source: source.into(),
467                reply,
468            })
469            .map_err(|_| RuntimeDiagnostic::message("runtime broker is closed".into()))?;
470        response.recv().map_err(|_| {
471            RuntimeDiagnostic::message("runtime broker stopped without a response".into())
472        })?
473    }
474
475    pub fn namespace(&self, session: &str) -> Result<String, String> {
476        self.call(|reply| Request::Namespace {
477            session: session.into(),
478            reply,
479        })
480    }
481
482    pub fn complete(&self, session: &str, prefix: &str) -> Result<Vec<String>, String> {
483        self.call(|reply| Request::Complete {
484            session: session.into(),
485            prefix: prefix.into(),
486            reply,
487        })
488    }
489
490    pub fn documentation(&self, session: &str, symbol: &str) -> Result<Documentation, String> {
491        self.call(|reply| Request::Doc {
492            session: session.into(),
493            symbol: symbol.into(),
494            reply,
495        })
496    }
497
498    pub fn create(&self, session: &str) -> Result<String, String> {
499        self.call(|reply| Request::Create {
500            session: session.into(),
501            reply,
502        })
503    }
504
505    pub fn close(&self, session: &str) -> Result<String, String> {
506        self.call(|reply| Request::Close {
507            session: session.into(),
508            reply,
509        })
510    }
511
512    pub fn list(&self) -> Result<Vec<String>, String> {
513        self.call(|reply| Request::List { reply })
514    }
515
516    pub fn info(&self, session: &str) -> Result<String, String> {
517        self.call(|reply| Request::Info {
518            session: session.into(),
519            reply,
520        })
521    }
522
523    pub fn register_resource(&self, name: &str, source: &str) -> Result<(), String> {
524        self.call(|reply| Request::RegisterResource {
525            name: name.into(),
526            source: source.into(),
527            reply,
528        })
529    }
530
531    pub fn remove_resource(&self, name: &str) -> Result<(), String> {
532        self.call(|reply| Request::RemoveResource {
533            name: name.into(),
534            reply,
535        })
536    }
537
538    pub fn resources(&self) -> Result<Vec<String>, String> {
539        self.call(|reply| Request::ListResources { reply })
540    }
541
542    /// Configures the project-local HBC cache for the root session and every
543    /// child session subsequently created by this broker. Cache artifacts hold
544    /// only compiled source programs; every session still constructs its own
545    /// namespace and runtime state.
546    #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
547    pub fn configure_native_source_cache(
548        &self,
549        root: &Path,
550        source_index_fingerprint: [u8; 32],
551    ) -> Result<(), String> {
552        self.call(|reply| Request::ConfigureNativeSourceCache {
553            root: root.to_path_buf(),
554            source_index_fingerprint,
555            reply,
556        })
557    }
558
559    #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
560    pub fn install_source_foundation_image(&self, image: &[u8]) -> Result<(), String> {
561        self.call(|reply| Request::InstallSourceFoundationImage {
562            image: image.to_vec(),
563            reply,
564        })
565    }
566
567    #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
568    pub fn install_source_language_spec_image(&self, image: &[u8]) -> Result<(), String> {
569        self.call(|reply| Request::InstallSourceLanguageSpecImage {
570            image: image.to_vec(),
571            reply,
572        })
573    }
574
575    pub fn install_module(
576        &self,
577        session: &str,
578        manifest: &str,
579        module: &crate::wasmtime_provider::CompiledWasmModule,
580    ) -> Result<String, String> {
581        self.call(|reply| Request::InstallModule {
582            session: session.into(),
583            manifest: manifest.into(),
584            module: module.clone(),
585            reply,
586        })
587    }
588
589    pub fn invoke_hta(
590        &self,
591        session: &str,
592        qualified_var: &str,
593        arguments: &[u8],
594    ) -> Result<Vec<u8>, InvokeHtaError> {
595        let (reply, response) = mpsc::channel();
596        self.handle
597            .sender
598            .send(Request::InvokeHta {
599                session: session.into(),
600                qualified_var: qualified_var.into(),
601                arguments: arguments.into(),
602                reply,
603            })
604            .map_err(|_| InvokeHtaError::BrokerClosed)?;
605        response.recv().map_err(|_| InvokeHtaError::BrokerStopped)?
606    }
607
608    pub fn invoke_module(
609        &self,
610        session: &str,
611        namespace: &str,
612        export: &str,
613        arguments: &[u8],
614    ) -> Result<Vec<u8>, String> {
615        self.call(|reply| Request::InvokeModule {
616            session: session.into(),
617            namespace: namespace.into(),
618            export: export.into(),
619            arguments: arguments.into(),
620            reply,
621        })
622    }
623
624    fn sandbox_open(&self, spec: SandboxSpec) -> Result<SandboxId, String> {
625        self.call(|reply| Request::SandboxOpen { spec, reply })
626    }
627
628    fn sandbox_eval_receiver(
629        &self,
630        sandbox: SandboxId,
631        source: &str,
632    ) -> Result<(EvaluationId, mpsc::Receiver<Result<String, String>>), String> {
633        let (reply, response) = mpsc::channel();
634        let (started_reply, started_response) = mpsc::channel();
635        self.handle
636            .sender
637            .send(Request::SandboxEval {
638                sandbox,
639                source: source.into(),
640                started: started_reply,
641                reply,
642            })
643            .map_err(|_| "runtime broker is closed".to_owned())?;
644        let evaluation = started_response.recv().map_err(|_| {
645            "runtime broker stopped before starting sandbox evaluation".to_owned()
646        })??;
647        Ok((evaluation, response))
648    }
649
650    fn sandbox_call_receiver(
651        &self,
652        sandbox: SandboxId,
653        callable: &str,
654        arguments: &[u8],
655    ) -> Result<(EvaluationId, mpsc::Receiver<Result<Vec<u8>, String>>), String> {
656        let (reply, response) = mpsc::channel();
657        let (started_reply, started_response) = mpsc::channel();
658        self.handle
659            .sender
660            .send(Request::SandboxCall {
661                sandbox,
662                callable: callable.into(),
663                arguments: arguments.into(),
664                started: started_reply,
665                reply,
666            })
667            .map_err(|_| "runtime broker is closed".to_owned())?;
668        let evaluation = started_response
669            .recv()
670            .map_err(|_| "runtime broker stopped before starting sandbox call".to_owned())??;
671        Ok((evaluation, response))
672    }
673
674    fn sandbox_cancel(&self, sandbox: SandboxId) -> Result<bool, String> {
675        self.call(|reply| Request::SandboxCancel {
676            sandbox,
677            evaluation: None,
678            reply,
679        })
680    }
681
682    fn sandbox_cancel_evaluation(
683        &self,
684        sandbox: SandboxId,
685        evaluation: EvaluationId,
686    ) -> Result<bool, String> {
687        self.call(|reply| Request::SandboxCancel {
688            sandbox,
689            evaluation: Some(evaluation),
690            reply,
691        })
692    }
693
694    fn sandbox_status(&self, sandbox: SandboxId) -> Result<SandboxStatus, String> {
695        self.call(|reply| Request::SandboxStatus { sandbox, reply })
696    }
697
698    fn sandbox_close(&self, sandbox: SandboxId) -> Result<(), String> {
699        self.call(|reply| Request::SandboxClose { sandbox, reply })
700    }
701
702    fn call<T>(
703        &self,
704        request: impl FnOnce(mpsc::Sender<Result<T, String>>) -> Request,
705    ) -> Result<T, String> {
706        let (reply, response) = mpsc::channel();
707        self.handle
708            .sender
709            .send(request(reply))
710            .map_err(|_| "runtime broker is closed".to_owned())?;
711        response
712            .recv()
713            .map_err(|_| "runtime broker stopped without a response".to_owned())?
714    }
715}
716
717fn runtime(
718    root: Option<&PathBuf>,
719    native_sockets: bool,
720    allow_process: bool,
721    allow_postgres: bool,
722    bootstrap: RuntimeBootstrap,
723    execution_backend: &str,
724    source_catalog: Option<&crate::project::SourceCatalog>,
725) -> Runtime {
726    let mut runtime = match bootstrap {
727        RuntimeBootstrap::Full => Runtime::new(),
728        RuntimeBootstrap::Core | RuntimeBootstrap::Source => Runtime::core(),
729    };
730    if let Some(source_catalog) = source_catalog {
731        runtime.register_source_catalog(source_catalog);
732    }
733    if matches!(bootstrap, RuntimeBootstrap::Source) {
734        runtime
735            .bootstrap_source_foundation()
736            .expect("source Foundation bootstrap must be valid");
737    }
738    if let Some(root) = root {
739        runtime.install_native_file_provider(root.to_string_lossy().as_ref());
740    }
741    if native_sockets {
742        runtime.install_native_socket_provider();
743    }
744    if allow_process {
745        runtime.install_native_process_provider();
746    }
747    // Native bootstrap recompiles evaluator-created protocol closures. Install
748    // the host providers first so those closures capture the runtime's actual
749    // authority instead of the zero-provider bootstrap context.
750    runtime
751        .configure_execution_backend(execution_backend)
752        .expect("validated execution backend must configure");
753    let _ = allow_postgres;
754    runtime
755}
756
757fn run(
758    receiver: mpsc::Receiver<Request>,
759    root: Option<PathBuf>,
760    native_sockets: bool,
761    allow_process: bool,
762    allow_postgres: bool,
763    bootstrap: RuntimeBootstrap,
764    execution_backend: String,
765    source_catalog: Option<crate::project::SourceCatalog>,
766) {
767    let runtime_root = root.clone();
768    let runtime_backend = execution_backend.clone();
769    let runtime_catalog = source_catalog;
770    let runtime_factory: Rc<dyn Fn() -> Runtime> = Rc::new(move || {
771        runtime(
772            runtime_root.as_ref(),
773            native_sockets,
774            allow_process,
775            allow_postgres,
776            bootstrap,
777            &runtime_backend,
778            runtime_catalog.as_ref(),
779        )
780    });
781    let root_runtime = runtime_factory();
782    let mut kernel = SessionKernel::with_runtime_factory(root_runtime, runtime_factory);
783    kernel.register_sandbox_provider(Rc::new(InProcessSandboxProvider));
784    while let Ok(request) = receiver.recv() {
785        match request {
786            Request::Eval {
787                session,
788                source,
789                reply,
790            } => {
791                let result = broker_session_id(&session).and_then(|id| {
792                    let runtime = kernel.session_mut(&id)?.runtime_mut()?;
793                    if execution_backend == "direct-native" {
794                        runtime.eval_native(&source)
795                    } else {
796                        runtime.eval_native_traced(&source)
797                    }
798                });
799                let _ = reply.send(result);
800            }
801            Request::EvalDiagnostic {
802                session,
803                source,
804                reply,
805            } => {
806                let result = broker_session_id(&session)
807                    .map_err(RuntimeDiagnostic::message)
808                    .and_then(|id| {
809                        let runtime = kernel
810                            .session_mut(&id)
811                            .and_then(|session| session.runtime_mut())
812                            .map_err(RuntimeDiagnostic::message)?;
813                        let ((result, frames), exception) = runtime.eval_native_diagnostic(&source);
814                        result.map_err(|message| RuntimeDiagnostic {
815                            message,
816                            exception: captured_exception(exception),
817                            frames,
818                        })
819                    });
820                let _ = reply.send(result);
821            }
822            Request::Namespace { session, reply } => {
823                let result =
824                    broker_session_id(&session).and_then(|id| kernel.session_namespace(&id));
825                let _ = reply.send(result);
826            }
827            Request::Complete {
828                session,
829                prefix,
830                reply,
831            } => {
832                let result = broker_session_id(&session).and_then(|id| {
833                    kernel.session(&id)?.runtime().map(|runtime| {
834                        let mut symbols = runtime
835                            .visible_symbols()
836                            .into_iter()
837                            .filter(|symbol| symbol.starts_with(&prefix))
838                            .collect::<Vec<_>>();
839                        symbols.dedup();
840                        symbols
841                    })
842                });
843                let _ = reply.send(result);
844            }
845            Request::Doc {
846                session,
847                symbol,
848                reply,
849            } => {
850                let result = broker_session_id(&session)
851                    .and_then(|id| documentation(kernel.session(&id)?.runtime()?, &symbol));
852                let _ = reply.send(result);
853            }
854            Request::Create { session, reply } => {
855                let result = SessionId::parse(&session)
856                    .map_err(|_| format!("Session already exists or is invalid: {session}"))
857                    .and_then(|id| {
858                        kernel
859                            .create_session(id)
860                            .map_err(|_| format!("Session already exists or is invalid: {session}"))
861                    })
862                    .map(|_| session);
863                let _ = reply.send(result);
864            }
865            Request::Close { session, reply } => {
866                let result = broker_session_id(&session)
867                    .and_then(|id| kernel.close_session(&id))
868                    .map(|_| session)
869                    .map_err(|error| match error.as_str() {
870                        "ROOT_CANNOT_CLOSE" => "ROOT cannot be closed".into(),
871                        _ if error.starts_with("NO_SESSION ") => {
872                            format!("No session: {}", error.trim_start_matches("NO_SESSION "))
873                        }
874                        _ => error,
875                    });
876                let _ = reply.send(result);
877            }
878            Request::List { reply } => {
879                let names = kernel
880                    .session_names()
881                    .into_iter()
882                    .map(|id| id.to_string())
883                    .collect();
884                let _ = reply.send(Ok(names));
885            }
886            Request::Info { session, reply } => {
887                let result = broker_session_id(&session)
888                    .and_then(|id| kernel.session_namespace(&id))
889                    .map(|namespace| format!("{session} {namespace}"));
890                let _ = reply.send(result);
891            }
892            Request::RegisterResource {
893                name,
894                source,
895                reply,
896            } => {
897                kernel.register_resource(&name, &source);
898                let _ = reply.send(Ok(()));
899            }
900            Request::RemoveResource { name, reply } => {
901                kernel.remove_resource(&name);
902                let _ = reply.send(Ok(()));
903            }
904            Request::ListResources { reply } => {
905                let _ = reply.send(Ok(kernel.resource_names()));
906            }
907            #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
908            Request::ConfigureNativeSourceCache {
909                root,
910                source_index_fingerprint,
911                reply,
912            } => {
913                kernel.configure_native_source_cache(&root, source_index_fingerprint);
914                let _ = reply.send(Ok(()));
915            }
916            #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
917            Request::InstallSourceFoundationImage { image, reply } => {
918                let _ = reply.send(kernel.install_source_foundation_image(&image));
919            }
920            #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
921            Request::InstallSourceLanguageSpecImage { image, reply } => {
922                let _ = reply.send(kernel.install_source_language_spec_image(&image));
923            }
924            Request::InstallModule {
925                session,
926                manifest,
927                module,
928                reply,
929            } => {
930                let result = broker_session_id(&session).and_then(|id| {
931                    let runtime = kernel.session_mut(&id)?.runtime_mut()?;
932                    let provider = module.provider();
933                    let parsed =
934                        crate::extension::ExtensionManifest::parse(&manifest, "MODULE PUT")?;
935                    let namespace = parsed.namespace.clone();
936                    runtime.install_wasm_extension(&manifest, "MODULE PUT", provider)?;
937                    Ok(namespace)
938                });
939                let _ = reply.send(result);
940            }
941            Request::InvokeModule {
942                session,
943                namespace,
944                export,
945                arguments,
946                reply,
947            } => {
948                let result = broker_session_id(&session).and_then(|id| {
949                    let runtime = kernel.session_mut(&id)?.runtime_mut()?;
950                    let arguments = crate::hta::decode(&arguments)?;
951                    let arguments: Vec<crate::extension::Value> = match arguments {
952                        crate::extension::Value::Vector(values) => values.iter().cloned().collect(),
953                        crate::extension::Value::Tuple(values) => values.iter().cloned().collect(),
954                        other => {
955                            return Err(format!(
956                                "hta/arguments: expected vector, got {}",
957                                other.display()
958                            ))
959                        }
960                    };
961                    let result = runtime.invoke_wasm_extension(&namespace, &export, &arguments)?;
962                    crate::hta::encode(&result)
963                });
964                let _ = reply.send(result);
965            }
966            Request::InvokeHta {
967                session,
968                qualified_var,
969                arguments,
970                reply,
971            } => {
972                let result = SessionId::parse(&session)
973                    .map_err(|_| InvokeHtaError::SessionMissing(session.clone()))
974                    .and_then(|id| {
975                        kernel
976                            .session_mut(&id)
977                            .map_err(|_| InvokeHtaError::SessionMissing(session.clone()))?
978                            .runtime_mut()
979                            .map_err(InvokeHtaError::Execution)?
980                            .invoke_hta(&qualified_var, &arguments)
981                    });
982                let _ = reply.send(result);
983            }
984            Request::SandboxOpen { spec, reply } => {
985                let _ = reply.send(kernel.open_sandbox(spec).map_err(|error| error.to_string()));
986            }
987            Request::SandboxEval {
988                sandbox,
989                source,
990                started,
991                reply,
992            } => match kernel.sandbox_eval(sandbox, &source) {
993                Ok(pending) => {
994                    let _ = started.send(Ok(pending.evaluation()));
995                    std::thread::spawn(move || {
996                        let _ = reply.send(pending.wait().map_err(|error| error.to_string()));
997                    });
998                }
999                Err(error) => {
1000                    let error = error.to_string();
1001                    let _ = started.send(Err(error.clone()));
1002                    let _ = reply.send(Err(error));
1003                }
1004            },
1005            Request::SandboxCall {
1006                sandbox,
1007                callable,
1008                arguments,
1009                started,
1010                reply,
1011            } => match kernel.sandbox_call(sandbox, &callable, &arguments) {
1012                Ok(pending) => {
1013                    let _ = started.send(Ok(pending.evaluation()));
1014                    std::thread::spawn(move || {
1015                        let _ = reply.send(pending.wait().map_err(|error| error.to_string()));
1016                    });
1017                }
1018                Err(error) => {
1019                    let error = error.to_string();
1020                    let _ = started.send(Err(error.clone()));
1021                    let _ = reply.send(Err(error));
1022                }
1023            },
1024            Request::SandboxCancel {
1025                sandbox,
1026                evaluation,
1027                reply,
1028            } => {
1029                let result = match evaluation {
1030                    Some(evaluation) => kernel.cancel_sandbox_evaluation(sandbox, evaluation),
1031                    None => kernel.cancel_sandbox(sandbox),
1032                };
1033                let _ = reply.send(result.map_err(|error| error.to_string()));
1034            }
1035            Request::SandboxStatus { sandbox, reply } => {
1036                let _ = reply.send(
1037                    kernel
1038                        .sandbox_status(sandbox)
1039                        .map_err(|error| error.to_string()),
1040                );
1041            }
1042            Request::SandboxClose { sandbox, reply } => {
1043                let _ = reply.send(
1044                    kernel
1045                        .close_sandbox(sandbox)
1046                        .map_err(|error| error.to_string()),
1047                );
1048            }
1049            Request::Shutdown => break,
1050        }
1051    }
1052}
1053
1054fn broker_session_id(session: &str) -> Result<SessionId, String> {
1055    SessionId::parse(session).map_err(|_| format!("No session: {session}"))
1056}
1057
1058fn documentation(runtime: &Runtime, symbol: &str) -> Result<Documentation, String> {
1059    documentation::lookup(runtime, symbol)
1060}
1061
1062/// Installs the generic native driver behind `std.native.Kernel/*`.
1063/// Command policy remains in Hara; this adapter only multiplexes isolated
1064/// evaluator sessions and transfers portable values across the boundary.
1065pub fn install_native_kernel(runtime: &mut Runtime, broker: RuntimeBroker) {
1066    runtime.install_native_kernel_provider(Rc::new(move |operation, arguments| {
1067        kernel_call(&broker, &operation, &arguments)
1068    }));
1069}
1070
1071#[cfg(test)]
1072mod tests;