cageforge_policy_compose/context.rs
1// SPDX-License-Identifier: Apache-2.0
2
3//! Narrowed runtime path context produced by [`crate::EffectiveSandbox`].
4//!
5//! The private constructor is intentional: callers should obtain this context
6//! from the effective result rather than rebuilding a broader context by hand.
7
8use std::path::{Path, PathBuf};
9use std::sync::Arc;
10
11use cageforge_policy::{PathPattern, PathResolutionContext, PathSelector};
12
13/// Identity shared by one effective sandbox and the contexts it creates.
14///
15/// The value is intentionally opaque. Pointer identity, rather than the
16/// value stored in the token, binds a context to its originating composition.
17#[derive(Debug, Clone)]
18pub(crate) struct ContextIdentity(Arc<()>);
19
20/// A runtime path context created by [`crate::EffectiveSandbox::path_context`].
21///
22/// The constructor is intentionally private so a filesystem decision cannot
23/// accidentally use a context with workspace roots broader than the composed
24/// result.
25#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct EffectivePathContext {
27 context: PathResolutionContext,
28 identity: ContextIdentity,
29}
30
31impl ContextIdentity {
32 pub(crate) fn new() -> Self {
33 Self(Arc::new(()))
34 }
35
36 fn matches(&self, other: &Self) -> bool {
37 Arc::ptr_eq(&self.0, &other.0)
38 }
39}
40
41impl PartialEq for ContextIdentity {
42 fn eq(&self, other: &Self) -> bool {
43 self.matches(other)
44 }
45}
46
47impl Eq for ContextIdentity {}
48
49impl EffectivePathContext {
50 pub(crate) fn new(context: PathResolutionContext, identity: ContextIdentity) -> Self {
51 Self { context, identity }
52 }
53
54 /// Returns the workspace roots permitted by the composed result.
55 pub fn workspace_roots(&self) -> &[PathBuf] {
56 self.context.workspace_roots()
57 }
58
59 /// Returns the system roots retained by the composed runtime context.
60 pub fn root_paths(&self) -> &[PathBuf] {
61 self.context.root_paths()
62 }
63
64 /// Returns the platform-minimal paths retained by the composed context.
65 pub fn minimal_paths(&self) -> &[PathBuf] {
66 self.context.minimal_paths()
67 }
68
69 /// Returns runtime roots retained for native executable mapping.
70 pub fn executable_roots(&self) -> &[PathBuf] {
71 self.context.executable_roots()
72 }
73
74 /// Returns the platform temporary directory, if one was supplied.
75 pub fn tmpdir(&self) -> Option<&std::path::Path> {
76 self.context.tmpdir()
77 }
78
79 /// Returns the conventional `/tmp` directory, if one was supplied.
80 pub fn slash_tmp(&self) -> Option<&std::path::Path> {
81 self.context.slash_tmp()
82 }
83
84 /// Returns the absolute runtime directory used to resolve or inherit a
85 /// command working directory.
86 pub fn current_directory(&self) -> Option<&Path> {
87 self.context.current_directory()
88 }
89
90 /// Resolves a symbolic selector through this bound effective context.
91 pub fn resolve(&self, selector: &PathSelector) -> Vec<PathBuf> {
92 selector.resolve(&self.context)
93 }
94
95 /// Returns the concrete scan roots for one validated filesystem pattern.
96 ///
97 /// Workspace patterns are resolved only through the roots retained by this
98 /// effective context. Absolute patterns produce their own static prefix.
99 /// The roots are lexical scan anchors, not filesystem authorization.
100 pub fn glob_search_roots(&self, pattern: &PathPattern) -> Vec<PathBuf> {
101 let prefix = pattern.literal_prefix();
102 if pattern.is_absolute() {
103 vec![prefix]
104 } else {
105 self.context
106 .workspace_roots()
107 .iter()
108 .map(|root| root.join(&prefix))
109 .collect()
110 }
111 }
112
113 /// Tests a path pattern through this composition-bound runtime context.
114 ///
115 /// This method preserves workspace-root narrowing. It is a matcher used by
116 /// native lowering and is not an authorization result; the final path must
117 /// still be checked against the complete effective filesystem policy.
118 pub fn pattern_matches(&self, pattern: &PathPattern, path: &Path) -> bool {
119 pattern.matches_path(path, &self.context)
120 }
121
122 pub(crate) fn belongs_to(&self, identity: &ContextIdentity) -> bool {
123 self.identity.matches(identity)
124 }
125
126 pub(crate) fn raw(&self) -> &PathResolutionContext {
127 &self.context
128 }
129}