Skip to main content

cageforge_policy/
context.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Runtime inputs for resolving symbolic [`crate::PathSelector`] values.
4//!
5//! [`crate::PathResolutionContext`] is supplied by the harness or backend. It
6//! stores declarations only and never discovers a workspace or follows links;
7//! [`crate::FilesystemPolicy`] consumes it when evaluating a concrete path.
8
9use crate::PathSelector;
10use crate::PolicyError;
11use cageforge_path::NativePathKey;
12use std::collections::HashSet;
13use std::path::Path;
14use std::path::PathBuf;
15
16/// Runtime paths needed to resolve platform-independent policy selectors.
17///
18/// The context is supplied by a harness or a platform backend. Constructing it
19/// never reads the filesystem, follows symlinks, or infers a workspace.
20#[derive(Debug, Clone, Default, PartialEq, Eq)]
21pub struct PathResolutionContext {
22    root_paths: Vec<PathBuf>,
23    root_keys: HashSet<NativePathKey>,
24    workspace_roots: Vec<PathBuf>,
25    workspace_root_keys: HashSet<NativePathKey>,
26    minimal_paths: Vec<PathBuf>,
27    minimal_path_keys: HashSet<NativePathKey>,
28    tmpdir: Option<PathBuf>,
29    slash_tmp: Option<PathBuf>,
30    current_directory: Option<PathBuf>,
31}
32
33impl PathResolutionContext {
34    /// Creates an empty context.
35    pub fn new() -> Self {
36        Self {
37            root_paths: Vec::new(),
38            root_keys: HashSet::new(),
39            workspace_roots: Vec::new(),
40            workspace_root_keys: HashSet::new(),
41            minimal_paths: Vec::new(),
42            minimal_path_keys: HashSet::new(),
43            tmpdir: None,
44            slash_tmp: None,
45            current_directory: None,
46        }
47    }
48
49    /// Adds one absolute system root represented by the runtime environment.
50    ///
51    /// POSIX backends normally provide `/`. Windows backends may provide more
52    /// than one drive or UNC root. The context never discovers these paths on
53    /// its own.
54    pub fn with_root(mut self, path: impl Into<PathBuf>) -> Result<Self, PolicyError> {
55        let path = validated_absolute(path.into())?;
56        if self.root_keys.insert(NativePathKey::new(&path)) {
57            self.root_paths.push(path);
58        }
59        Ok(self)
60    }
61
62    /// Adds one absolute workspace root.
63    pub fn with_workspace_root(mut self, path: impl Into<PathBuf>) -> Result<Self, PolicyError> {
64        let path = validated_absolute(path.into())?;
65        if self.workspace_root_keys.insert(NativePathKey::new(&path)) {
66            self.workspace_roots.push(path);
67        }
68        Ok(self)
69    }
70
71    /// Adds one absolute path required by ordinary process execution.
72    pub fn with_minimal_path(mut self, path: impl Into<PathBuf>) -> Result<Self, PolicyError> {
73        let path = validated_absolute(path.into())?;
74        if self.minimal_path_keys.insert(NativePathKey::new(&path)) {
75            self.minimal_paths.push(path);
76        }
77        Ok(self)
78    }
79
80    /// Sets the platform temporary directory.
81    pub fn with_tmpdir(mut self, path: impl Into<PathBuf>) -> Result<Self, PolicyError> {
82        self.tmpdir = Some(validated_absolute(path.into())?);
83        Ok(self)
84    }
85
86    /// Sets the conventional `/tmp` directory when the platform provides it.
87    pub fn with_slash_tmp(mut self, path: impl Into<PathBuf>) -> Result<Self, PolicyError> {
88        self.slash_tmp = Some(validated_absolute(path.into())?);
89        Ok(self)
90    }
91
92    /// Sets the absolute runtime current directory used for command cwd
93    /// resolution and for commands that otherwise inherit their cwd.
94    ///
95    /// This is runtime input only; the context never reads the directory or
96    /// changes the process cwd.
97    pub fn with_current_directory(mut self, path: impl Into<PathBuf>) -> Result<Self, PolicyError> {
98        self.current_directory = Some(validated_absolute(path.into())?);
99        Ok(self)
100    }
101
102    /// Returns the configured workspace roots.
103    pub fn workspace_roots(&self) -> &[PathBuf] {
104        &self.workspace_roots
105    }
106
107    /// Returns the absolute system roots supplied by the runtime.
108    pub fn root_paths(&self) -> &[PathBuf] {
109        &self.root_paths
110    }
111
112    /// Returns the configured minimal runtime paths.
113    pub fn minimal_paths(&self) -> &[PathBuf] {
114        &self.minimal_paths
115    }
116
117    /// Returns the configured platform temporary directory.
118    pub fn tmpdir(&self) -> Option<&Path> {
119        self.tmpdir.as_deref()
120    }
121
122    /// Returns the configured conventional `/tmp` directory.
123    pub fn slash_tmp(&self) -> Option<&Path> {
124        self.slash_tmp.as_deref()
125    }
126
127    /// Returns the absolute runtime directory used for relative command cwd
128    /// values and for commands that inherit their cwd.
129    pub fn current_directory(&self) -> Option<&Path> {
130        self.current_directory.as_deref()
131    }
132}
133
134fn validated_absolute(path: PathBuf) -> Result<PathBuf, PolicyError> {
135    PathSelector::absolute(path)?
136        .path()
137        .map(Path::to_path_buf)
138        .ok_or_else(|| PolicyError::InvalidContext {
139            message: "absolute path validation returned a non-absolute selector".to_string(),
140        })
141}