use std::cell::Cell;
use super::authoring::default_levels;
use super::facet::Profile;
use super::level::{FacetMismatch, Level};
use super::resolve;
use crate::parse::Token;
use crate::verdict::{SafetyLevel, Verdict};
thread_local! {
static EVAL_LEVEL: Cell<Option<&'static Level>> = const { Cell::new(None) };
}
pub fn enter_eval_level(level: &'static Level) -> EvalLevelGuard {
EvalLevelGuard(EVAL_LEVEL.with(|c| c.replace(Some(level))))
}
pub struct EvalLevelGuard(Option<&'static Level>);
impl Drop for EvalLevelGuard {
fn drop(&mut self) {
EVAL_LEVEL.with(|c| c.set(self.0));
}
}
pub fn engine_verdict(tokens: &[Token]) -> Option<Verdict> {
resolve::resolve(tokens).map(|p| project(&p))
}
pub fn project(profile: &Profile) -> Verdict {
if profile.capabilities.is_empty() {
return Verdict::Denied;
}
if let Some(level) = EVAL_LEVEL.with(Cell::get) {
return if level.admits(profile) {
Verdict::Allowed(SafetyLevel::SafeWrite)
} else {
Verdict::Denied
};
}
for level in default_levels() {
if let Some(sl) = to_legacy(&level.name)
&& level.admits(profile)
{
return Verdict::Allowed(sl);
}
}
Verdict::Denied
}
fn to_legacy(level_name: &str) -> Option<SafetyLevel> {
match level_name {
"paranoid" => Some(SafetyLevel::Inert),
"reader" => Some(SafetyLevel::SafeRead),
"editor" | "developer" => Some(SafetyLevel::SafeWrite),
_ => None, }
}
pub struct ProfileExplanation {
pub capabilities: Vec<(String, Vec<(&'static str, &'static str)>)>,
pub blocked_by: Option<(String, FacetMismatch)>,
}
pub fn explain_profile(tokens: &[Token]) -> Option<ProfileExplanation> {
let profile = resolve::resolve(tokens)?;
let capabilities = profile
.capabilities
.iter()
.map(|c| (c.because.clone(), c.set_facets()))
.collect();
let blocked_by = default_levels()
.iter()
.rfind(|l| to_legacy(&l.name).is_some())
.and_then(|top| {
profile
.capabilities
.iter()
.find_map(|c| top.nearest_miss(c).map(|m| (top.name.clone(), m)))
});
Some(ProfileExplanation { capabilities, blocked_by })
}
#[cfg(test)]
mod tests {
use super::*;
use crate::engine::facet::*;
fn toks(parts: &[&str]) -> Vec<Token> {
parts.iter().map(|p| Token::from_test(p)).collect()
}
#[test]
fn project_maps_profiles_to_the_lowest_admitting_level() {
let echo = Profile::of(vec![{
let mut c = Capability::new(Operation::Observe);
c.disclosure.audience = DisclosureAudience::LocalProcess;
c
}]);
assert_eq!(project(&echo), Verdict::Allowed(SafetyLevel::Inert));
let read = Profile::of(vec![{
let mut c = Capability::new(Operation::Observe);
c.locus.local = LocalLocus::Worktree;
c.disclosure.audience = DisclosureAudience::LocalProcess;
c
}]);
assert_eq!(project(&read), Verdict::Allowed(SafetyLevel::SafeRead));
let home = Profile::of(vec![{
let mut c = Capability::new(Operation::Observe);
c.locus.local = LocalLocus::User;
c.disclosure.audience = DisclosureAudience::LocalProcess;
c
}]);
assert_eq!(project(&home), Verdict::Denied);
let write = Profile::of(vec![{
let mut c = Capability::new(Operation::Create);
c.locus.local = LocalLocus::Worktree;
c.scale = Scale::Bounded;
c.reversibility = Reversibility::Recoverable;
c.persistence.level = PersistenceLevel::Data;
c
}]);
assert_eq!(project(&write), Verdict::Allowed(SafetyLevel::SafeWrite));
assert_eq!(project(&Profile::of(vec![])), Verdict::Denied);
}
#[test]
fn profiles_needing_an_upper_level_project_to_denied_not_safewrite() {
let sudo = Profile::of(vec![{
let mut c = Capability::new(Operation::Control);
c.locus.local = LocalLocus::Machine;
c.authority = Authority::Root;
c
}]);
assert_eq!(project(&sudo), Verdict::Denied, "sudo must not auto-approve");
let remote = Profile::of(vec![{
let mut c = Capability::new(Operation::Mutate);
c.locus.remote = RemoteReach::Fixed;
c.network.direction = NetDirection::Outbound;
c
}]);
assert_eq!(project(&remote), Verdict::Denied, "remote infra must not auto-approve");
let catastrophe = Profile::of(vec![{
let mut c = Capability::new(Operation::Destroy);
c.locus.remote = RemoteReach::Fixed;
c.reversibility = Reversibility::Irreversible;
c
}]);
assert_eq!(project(&catastrophe), Verdict::Denied, "irreversible destroy must not auto-approve");
}
fn legacy(cmd: &str) -> Verdict {
crate::handlers::dispatch(&toks(&cmd.split_whitespace().collect::<Vec<_>>()))
}
#[test]
fn the_engine_is_authoritative_with_legacy_fallback() {
assert_eq!(
crate::command_verdict("cat ./notes.md"),
Verdict::Allowed(SafetyLevel::SafeRead),
"cat resolves → engine tightens inert to read-local",
);
let unresolved = resolve::UNRESOLVED_CMD.join(" ");
assert_eq!(
crate::command_verdict(&unresolved),
legacy(&unresolved),
"no resolver → legacy verdict",
);
}
#[test]
fn engine_verdict_is_none_for_unresearched_commands() {
assert!(engine_verdict(&toks(resolve::UNRESOLVED_CMD)).is_none());
assert_eq!(engine_verdict(&toks(&["echo", "hi"])), Some(Verdict::Allowed(SafetyLevel::Inert)));
assert_eq!(
engine_verdict(&toks(&["cat", "./notes.md"])),
Some(Verdict::Allowed(SafetyLevel::SafeRead)),
);
assert_eq!(engine_verdict(&toks(&["cat", "~/.ssh/id_rsa"])), Some(Verdict::Denied));
}
fn not_looser(legacy: Verdict, engine: Verdict) -> bool {
match (legacy, engine) {
(_, Verdict::Denied) => true,
(Verdict::Denied, Verdict::Allowed(_)) => false,
(Verdict::Allowed(l), Verdict::Allowed(e)) => e >= l,
}
}
#[test]
fn the_engine_is_never_looser_than_legacy() {
let cases = [
"echo hi", "echo", "cat ./notes.md", "cat -n ./notes.md", "cat ~/.ssh/id_rsa",
"cat /etc/hosts", "cat a.txt b.txt", "grep foo src/main.rs", "grep -r foo src/",
"grep -r foo ~", "grep foo bar.txt",
"grep -P foo file", "grep -oP foo file", "grep --perl-regexp foo file",
"cat --unknownflag ./x", "cat -Z ./x", "grep --wat foo file",
"grep", "grep -r", "grep -i", "grep -e foo", "grep -f patterns.txt",
];
for cmd in cases {
let base = legacy(cmd);
let t = toks(&cmd.split_whitespace().collect::<Vec<_>>());
let Some(engine) = engine_verdict(&t) else { continue };
assert!(
not_looser(base, engine),
"engine LOOSER than legacy for `{cmd}`: legacy {base}, engine {engine}",
);
}
}
#[test]
fn developer_intentionally_admits_worktree_destroy_that_legacy_denies() {
let rm = "rm -rf ./node_modules";
assert_eq!(legacy(rm), Verdict::Denied, "legacy allowlist denies rm deletion");
assert_eq!(crate::command_verdict(rm), Verdict::Allowed(SafetyLevel::SafeWrite), "engine (developer) admits it — intended");
assert!(!not_looser(Verdict::Denied, Verdict::Allowed(SafetyLevel::SafeWrite)), "and it IS looser than legacy, by design");
}
#[test]
fn engine_intentionally_admits_worktree_in_place_edit_that_legacy_sed_handler_denies() {
let sed = "sed -i s/a/b/ ./file.txt";
assert_eq!(legacy(sed), Verdict::Denied, "legacy sed handler denies in-place edit");
assert_eq!(crate::command_verdict(sed), Verdict::Allowed(SafetyLevel::SafeWrite), "engine admits worktree -i — intended");
assert!(!not_looser(Verdict::Denied, Verdict::Allowed(SafetyLevel::SafeWrite)), "and it IS looser than the legacy sed handler, by design");
}
#[test]
fn the_engine_corpus_gate() {
let mut exercised = 0usize;
for (name, safe, denied) in crate::registry::corpus_examples() {
for ex in safe.iter().chain(denied.iter()) {
if ex.contains(['|', '>', '<', '&', ';', '$', '`', '(', '\n']) {
continue; }
let t = toks(&ex.split_whitespace().collect::<Vec<_>>());
let Some(profile) = crate::engine::resolve::resolve(&t) else { continue };
exercised += 1;
for c in &profile.capabilities {
assert!(!c.because.is_empty(), "unjustified capability for `{ex}` ({name})");
}
if crate::registry::sub_archetypes(&t).is_some() {
continue;
}
let engine = project(&profile);
let base = legacy(ex);
assert!(
not_looser(base, engine),
"engine LOOSER than legacy for `{ex}` ({name}): legacy {base}, engine {engine}",
);
}
}
assert!(exercised >= 5, "corpus gate exercised only {exercised} engine resolutions — vacuous?");
}
#[test]
fn upper_band_levels_admit_via_the_engine_end_to_end() {
let net = crate::upper_level_by_name("network-admin").expect("network-admin exists");
let yolo = crate::upper_level_by_name("yolo").expect("yolo exists");
assert_eq!(crate::command_verdict("git push origin main"), Verdict::Denied, "developer denies push");
assert!(crate::command_verdict_at_level("git push origin main", net).is_allowed(), "network-admin admits push");
assert!(crate::command_verdict_at_level("git push origin main", yolo).is_allowed(), "yolo admits push");
assert_eq!(crate::command_verdict_at_level("rm -rf /", yolo), Verdict::Denied, "yolo denies rm -rf /");
assert!(crate::command_verdict_at_level("cat ./README.md", net).is_allowed(), "reads pass at network-admin");
assert_eq!(crate::command_verdict_at_level("frobnicate --wombat", yolo), Verdict::Denied, "unmodeled denied at yolo");
assert_eq!(crate::command_verdict_at_level("git push && rm -rf /", yolo), Verdict::Denied, "one bad segment sinks the chain");
assert!(crate::upper_level_by_name("developer").is_none());
assert!(crate::upper_level_by_name("reader").is_none());
assert!(crate::upper_level_by_name("nonsense").is_none());
assert_eq!(crate::command_verdict("cat ./README.md"), Verdict::Allowed(SafetyLevel::SafeRead), "lower band untouched");
}
}