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 the platform temporary directory, if one was supplied.
70 pub fn tmpdir(&self) -> Option<&std::path::Path> {
71 self.context.tmpdir()
72 }
73
74 /// Returns the conventional `/tmp` directory, if one was supplied.
75 pub fn slash_tmp(&self) -> Option<&std::path::Path> {
76 self.context.slash_tmp()
77 }
78
79 /// Returns the absolute runtime directory used to resolve or inherit a
80 /// command working directory.
81 pub fn current_directory(&self) -> Option<&Path> {
82 self.context.current_directory()
83 }
84
85 /// Resolves a symbolic selector through this bound effective context.
86 pub fn resolve(&self, selector: &PathSelector) -> Vec<PathBuf> {
87 selector.resolve(&self.context)
88 }
89
90 /// Returns the concrete scan roots for one validated filesystem pattern.
91 ///
92 /// Workspace patterns are resolved only through the roots retained by this
93 /// effective context. Absolute patterns produce their own static prefix.
94 /// The roots are lexical scan anchors, not filesystem authorization.
95 pub fn glob_search_roots(&self, pattern: &PathPattern) -> Vec<PathBuf> {
96 let prefix = pattern.literal_prefix();
97 if pattern.is_absolute() {
98 vec![prefix]
99 } else {
100 self.context
101 .workspace_roots()
102 .iter()
103 .map(|root| root.join(&prefix))
104 .collect()
105 }
106 }
107
108 /// Tests a path pattern through this composition-bound runtime context.
109 ///
110 /// This method preserves workspace-root narrowing. It is a matcher used by
111 /// native lowering and is not an authorization result; the final path must
112 /// still be checked against the complete effective filesystem policy.
113 pub fn pattern_matches(&self, pattern: &PathPattern, path: &Path) -> bool {
114 pattern.matches_path(path, &self.context)
115 }
116
117 pub(crate) fn belongs_to(&self, identity: &ContextIdentity) -> bool {
118 self.identity.matches(identity)
119 }
120
121 pub(crate) fn raw(&self) -> &PathResolutionContext {
122 &self.context
123 }
124}