Skip to main content

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}