luft_runtime/lib.rs
1//! # luft-runtime
2//!
3//! **Sandboxed Lua orchestration VM.**
4//!
5//! The runtime executes Luft orchestration scripts in a sandboxed
6//! [`mlua`] VM. Scripts call SDK primitives that bridge to the scheduler
7//! — the script is pure orchestration and cannot touch the filesystem, shell,
8//! or network directly.
9//!
10//! ## SDK Primitives
11//!
12//! Lua scripts call these global functions:
13//!
14//! | Primitive | Description |
15//! |-----------|-------------|
16//! | `agent(opts)` | Run a single agent task; returns `AgentResult` |
17//! | `parallel(items, map_fn)` | Fan-out: run `map_fn(item)` for each item concurrently, barrier-sync results |
18//! | `pipeline(items, stages)` | Streaming pipeline: items flow through stages without barrier sync |
19//! | `workflow(path, args?)` | Invoke a nested sub-workflow |
20//! | `converge(opts)` | Multi-round consensus: agents iterate until convergence or round limit |
21//! | `phase_begin(name)` / `phase_end(span)` | Structural progress spans for observability |
22//! | `report(value)` | Emit the final workflow output (required) |
23//! | `log(msg, level?)` | Structured log event |
24//! | `budget(time_ms?, rounds?)` | Set runtime limits hint |
25//! | `json.encode(value)` / `json.decode(str)` | JSON helpers (pure Lua, no `io`) |
26//!
27//! ## Sandbox Security Model
28//!
29//! The following Lua standard libraries are **removed** from the VM:
30//!
31//! - `io.*` — no file I/O
32//! - `os.*` — no process / environment access
33//! - `package.*` — no module loading
34//! - `require`, `dofile`, `loadfile` — no external code execution
35//!
36//! Only orchestration primitives and `math`, `string`, `table`, `json` are available.
37//! This guarantees that scripts — which may be **LLM-generated** — cannot escape
38//! the orchestration layer.
39//!
40//! ## Validation
41//!
42//! Before execution, scripts are validated via [`validate`] (syntax + forbidden
43//! globals) and [`validate_workflow`] (structural checks: `report()` presence,
44//! span pairing, meta consistency).
45
46mod converge;
47mod error;
48mod pipeline;
49mod sandbox;
50mod sdk;
51
52pub use converge::{ConvergeConfig, ConvergeResult, RoundStats};
53pub use error::{ExecLimits, ScriptError};
54pub use pipeline::{
55 PipelineConfig, PipelineError, PipelineExecutor, PipelineItem, PipelineItemResult,
56 PipelineResult, PipelineStage, PipelineStats, StageResult, StageStatus,
57};
58pub use sandbox::{validate_script, validate_workflow, Runtime, WorkflowMeta, WorkflowValidation};
59
60/// Validate a script without executing it (syntax + forbidden globals).
61pub fn validate(script: &str) -> Result<(), ScriptError> {
62 validate_script(script)
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68
69 #[test]
70 fn validate_accepts_syntax() {
71 assert!(validate("return 1 + 2").is_ok());
72 }
73
74 #[test]
75 fn validate_rejects_bad_syntax() {
76 let err = validate("if true then").unwrap_err();
77 assert!(matches!(err, ScriptError::Syntax(_)));
78 }
79
80 #[test]
81 fn validate_empty_string() {
82 assert!(validate("").is_ok());
83 }
84
85 #[test]
86 fn validate_rejects_garbage() {
87 let err = validate("~~ not lua ~~").unwrap_err();
88 assert!(matches!(err, ScriptError::Syntax(_)));
89 }
90
91 #[test]
92 fn validate_accepts_function_call() {
93 assert!(validate("print('hello')").is_ok());
94 }
95
96 #[test]
97 fn validate_rejects_mismatched_brackets() {
98 let err = validate("local x = {1, 2, 3").unwrap_err();
99 assert!(matches!(err, ScriptError::Syntax(_)));
100 }
101
102 #[test]
103 fn validate_accepts_multi_line_script() {
104 let script = r#"
105 local x = 10
106 local y = 20
107 return x + y
108 "#;
109 assert!(validate(script).is_ok());
110 }
111
112 #[test]
113 fn validate_rejects_operator_error() {
114 let err = validate("local x = 1 +++ 2").unwrap_err();
115 assert!(matches!(err, ScriptError::Syntax(_)));
116 }
117
118 #[test]
119 fn validate_accepts_table_construction() {
120 assert!(validate("local t = {a = 1, b = 2}").is_ok());
121 }
122
123 #[test]
124 fn validate_accepts_function_def() {
125 assert!(validate("function add(a, b) return a + b end").is_ok());
126 }
127}