Skip to main content

scrollcase_consumer/
execution.rs

1//! Static execution prerequisites.
2//!
3//! Execution metadata is not a command string: it names either one regular payload file or one
4//! dotted Python module. Checking the file set proves those names can resolve without importing a
5//! package, running an `__init__.py`, or starting the application — so the check itself cannot be
6//! the thing that executes box code before the trust chain has finished.
7
8use std::collections::BTreeSet;
9
10use crate::contract::targets::BoxTargetAdapter;
11use crate::error::{fail, Result};
12use crate::path::safe_relative_path;
13use crate::release::Execution;
14
15/// The `major.minor` prefix used to locate a standard library directory.
16fn python_major_minor(version: &str) -> Result<String> {
17    let mut parts = version.split('.');
18    let (Some(major), Some(minor)) = (parts.next(), parts.next()) else {
19        fail!("Invalid Python version for execution discovery: {version}.");
20    };
21    if major.is_empty()
22        || minor.is_empty()
23        || !major.bytes().all(|byte| byte.is_ascii_digit())
24        || !minor.bytes().all(|byte| byte.is_ascii_digit())
25    {
26        fail!("Invalid Python version for execution discovery: {version}.");
27    }
28    Ok(format!("{major}.{minor}"))
29}
30
31/// Every path a dotted module could legitimately resolve to inside a box.
32fn module_entry_points(
33    adapter: &BoxTargetAdapter,
34    module: &str,
35    python_version: &str,
36) -> Result<Vec<String>> {
37    let module_path = module.replace('.', "/");
38    let relative = [
39        format!("{module_path}.py"),
40        format!("{module_path}/__main__.py"),
41    ];
42    let standard_library = if adapter.platform == "windows" {
43        "venv/Lib".to_string()
44    } else {
45        format!("venv/lib/python{}", python_major_minor(python_version)?)
46    };
47    let roots = [
48        String::new(),
49        standard_library.clone(),
50        format!("{standard_library}/site-packages"),
51    ];
52    Ok(roots
53        .iter()
54        .flat_map(|root| {
55            relative.iter().map(move |candidate| {
56                if root.is_empty() {
57                    candidate.clone()
58                } else {
59                    format!("{root}/{candidate}")
60                }
61            })
62        })
63        .collect())
64}
65
66/// Confirms optional execution metadata names something runnable in a payload or archive.
67///
68/// `files` must hold only regular entries: a link resolves, but the thing that finally runs has to
69/// be a file, and the caller decides which of the two questions it is asking.
70///
71/// # Errors
72///
73/// When the script is missing, or the module resolves to nothing.
74pub fn assert_execution_files(
75    execution: Option<&Execution>,
76    adapter: &BoxTargetAdapter,
77    python_version: &str,
78    files: &BTreeSet<String>,
79) -> Result<()> {
80    let Some(execution) = execution else {
81        return Ok(());
82    };
83    match execution {
84        Execution::PythonScript { script, .. } => {
85            let safe = safe_relative_path(script)?;
86            if !files.contains(&safe) {
87                fail!("Execution script is missing from the box: {safe}.");
88            }
89        }
90        Execution::PythonModule { module, .. } => {
91            let candidates = module_entry_points(adapter, module, python_version)?;
92            if !candidates.iter().any(|path| files.contains(path)) {
93                fail!("Execution module is not discoverable in the box: {module}.");
94            }
95        }
96    }
97    Ok(())
98}
99
100#[cfg(test)]
101mod tests {
102    use super::{assert_execution_files, python_major_minor};
103    use crate::contract::targets::{box_target_adapter, BoxTarget};
104    use crate::release::Execution;
105    use std::collections::BTreeSet;
106
107    fn adapter(platform: &str, arch: &str, accelerator: &str) -> &'static crate::contract::targets::BoxTargetAdapter {
108        box_target_adapter(&BoxTarget {
109            platform: platform.to_string(),
110            arch: arch.to_string(),
111            accelerator: accelerator.to_string(),
112            cuda_version: None,
113        })
114        .unwrap()
115    }
116
117    fn files(paths: &[&str]) -> BTreeSet<String> {
118        paths.iter().map(|path| (*path).to_string()).collect()
119    }
120
121    #[test]
122    fn a_script_must_exist_as_a_regular_entry() {
123        let adapter = adapter("linux", "x86_64", "cpu");
124        let execution = Execution::PythonScript {
125            script: "app/main.py".to_string(),
126            default_args: vec![],
127        };
128        assert!(
129            assert_execution_files(Some(&execution), adapter, "3.11.9", &files(&["app/main.py"]))
130                .is_ok()
131        );
132        let error =
133            assert_execution_files(Some(&execution), adapter, "3.11.9", &files(&["app/other.py"]))
134                .unwrap_err();
135        assert!(error.message().contains("Execution script is missing"), "{error}");
136    }
137
138    #[test]
139    fn a_module_resolves_through_any_of_its_legitimate_locations() {
140        let adapter = adapter("linux", "x86_64", "cpu");
141        let execution = Execution::PythonModule {
142            module: "example_model.main".to_string(),
143            default_args: vec![],
144        };
145        for location in [
146            "example_model/main.py",
147            "example_model/main/__main__.py",
148            "venv/lib/python3.11/example_model/main.py",
149            "venv/lib/python3.11/site-packages/example_model/main.py",
150            "venv/lib/python3.11/site-packages/example_model/main/__main__.py",
151        ] {
152            assert!(
153                assert_execution_files(Some(&execution), adapter, "3.11.9", &files(&[location]))
154                    .is_ok(),
155                "{location} did not resolve"
156            );
157        }
158        let error =
159            assert_execution_files(Some(&execution), adapter, "3.11.9", &files(&["elsewhere.py"]))
160                .unwrap_err();
161        assert!(
162            error.message().contains("Execution module is not discoverable"),
163            "{error}"
164        );
165    }
166
167    #[test]
168    fn windows_looks_in_its_own_standard_library() {
169        let windows = adapter("windows", "x86_64", "cpu");
170        let execution = Execution::PythonModule {
171            module: "pkg".to_string(),
172            default_args: vec![],
173        };
174        assert!(assert_execution_files(
175            Some(&execution),
176            windows,
177            "3.11.9",
178            &files(&["venv/Lib/site-packages/pkg/__main__.py"])
179        )
180        .is_ok());
181        // The POSIX layout must not resolve on a Windows target.
182        assert!(assert_execution_files(
183            Some(&execution),
184            windows,
185            "3.11.9",
186            &files(&["venv/lib/python3.11/site-packages/pkg/__main__.py"])
187        )
188        .is_err());
189    }
190
191    #[test]
192    fn a_python_version_that_cannot_locate_a_standard_library_is_refused() {
193        assert_eq!(python_major_minor("3.11.9").unwrap(), "3.11");
194        assert_eq!(python_major_minor("3.12").unwrap(), "3.12");
195        for invalid in ["", "3", "3.x", "x.1", "3."] {
196            assert!(python_major_minor(invalid).is_err(), "{invalid} was accepted");
197        }
198    }
199
200    #[test]
201    fn a_library_only_box_declares_no_execution() {
202        let adapter = adapter("macos", "aarch64", "metal");
203        assert!(assert_execution_files(None, adapter, "3.11.9", &files(&[])).is_ok());
204    }
205}