Skip to main content

aperion_shield/
context.rs

1//! Workspace context probe -- runs once at startup and reports whether
2//! the current working directory looks like a production-managed repo.
3//! When it does, the engine bumps every matched rule's severity by one
4//! tier. The signals are deliberately conservative: only files that
5//! strongly imply "this codebase manages production".
6//!
7//! Cheap by design -- does not recurse, does not stat outside the cwd
8//! root, returns in a millisecond. Designed to run on every Shield
9//! launch with zero perceptible cost.
10
11use std::path::{Path, PathBuf};
12
13use crate::engine::Policy;
14
15#[derive(Debug, Clone)]
16pub struct WorkspaceContext {
17    pub root: PathBuf,
18    pub is_prod: bool,
19    pub matched_signals: Vec<String>,
20}
21
22impl WorkspaceContext {
23    /// Probe the cwd against the policy's prod_signals.
24    pub fn probe(policy: &Policy) -> Self {
25        let root = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
26        Self::probe_at(policy, &root)
27    }
28
29    /// Probe an arbitrary root directory. Exists primarily for tests --
30    /// the production code path goes through `probe()`, which is the
31    /// single-arg convenience wrapper.
32    pub fn probe_at(policy: &Policy, root: &Path) -> Self {
33        let root = root.to_path_buf();
34        if !policy.workspace_probe.enabled {
35            return Self { root, is_prod: false, matched_signals: vec![] };
36        }
37        let mut matched = Vec::new();
38        for sig in &policy.workspace_probe.prod_signals {
39            if signal_present(&root, sig) {
40                matched.push(sig.clone());
41            }
42        }
43        let is_prod = !matched.is_empty();
44        Self { root, is_prod, matched_signals: matched }
45    }
46}
47
48fn signal_present(root: &Path, signal: &str) -> bool {
49    if let Some(dir) = signal.strip_suffix('/') {
50        let p = root.join(dir);
51        return p.is_dir();
52    }
53    // Bare filename -- checked at cwd and one level under common
54    // configuration dirs (config/, deploy/, ops/). One level is enough
55    // to catch nested production manifests without unbounded recursion.
56    if root.join(signal).exists() {
57        return true;
58    }
59    for sub in ["config", "deploy", "ops", "infra"] {
60        if root.join(sub).join(signal).exists() {
61            return true;
62        }
63    }
64    false
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70    use crate::engine::WorkspaceProbeCfg;
71    use std::fs;
72    use tempfile::TempDir;
73
74    fn policy_with_signals(signals: &[&str]) -> Policy {
75        let mut p = Policy::default();
76        p.workspace_probe = WorkspaceProbeCfg {
77            enabled: true,
78            prod_signals: signals.iter().map(|s| s.to_string()).collect(),
79            severity_bump: 1,
80        };
81        p
82    }
83
84    #[test]
85    fn no_signals_means_not_prod() {
86        let tmp = TempDir::new().unwrap();
87        let ctx = WorkspaceContext::probe_at(
88            &policy_with_signals(&[".env.production", "prod/"]),
89            tmp.path(),
90        );
91        assert!(!ctx.is_prod);
92        assert!(ctx.matched_signals.is_empty());
93    }
94
95    #[test]
96    fn file_signal_at_cwd_root() {
97        let tmp = TempDir::new().unwrap();
98        fs::write(tmp.path().join(".env.production"), "DB=prod").unwrap();
99        let ctx = WorkspaceContext::probe_at(
100            &policy_with_signals(&[".env.production"]),
101            tmp.path(),
102        );
103        assert!(ctx.is_prod);
104        assert_eq!(ctx.matched_signals, vec![".env.production".to_string()]);
105    }
106
107    #[test]
108    fn dir_signal() {
109        let tmp = TempDir::new().unwrap();
110        fs::create_dir(tmp.path().join("prod")).unwrap();
111        let ctx = WorkspaceContext::probe_at(&policy_with_signals(&["prod/"]), tmp.path());
112        assert!(ctx.is_prod);
113    }
114
115    #[test]
116    fn nested_config_dir() {
117        let tmp = TempDir::new().unwrap();
118        fs::create_dir(tmp.path().join("config")).unwrap();
119        fs::write(tmp.path().join("config").join("production.yml"), "x: 1").unwrap();
120        let ctx = WorkspaceContext::probe_at(&policy_with_signals(&["production.yml"]), tmp.path());
121        assert!(ctx.is_prod);
122    }
123
124    #[test]
125    fn disabled_probe_short_circuits() {
126        let tmp = TempDir::new().unwrap();
127        fs::write(tmp.path().join(".env.production"), "x").unwrap();
128        let mut p = policy_with_signals(&[".env.production"]);
129        p.workspace_probe.enabled = false;
130        let ctx = WorkspaceContext::probe_at(&p, tmp.path());
131        assert!(!ctx.is_prod);
132    }
133}