basis/shell.rs
1//! Whether a run may execute commands.
2//!
3//! Per [ADR-0013] the host owns the boundary. Commands are enabled by default:
4//! a harness that cannot run `cargo test` does very little real work, and the
5//! flag that used to gate them was theater once a process spawned — an
6//! in-process path check cannot confine a command that is already running.
7//!
8//! So basis claims nothing. The process holds whatever authority the user who
9//! started it holds, and confinement, where it is wanted, comes from the OS —
10//! `docs/containerization.md` has the patterns. Denying is one line for a run
11//! that is meant to read and report.
12//!
13//! [ADR-0013]: https://github.com/oops-rs/basis/blob/main/docs/adr/0013-the-host-owns-the-boundary.md
14
15/// Whether the agent may run commands.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
17pub enum ShellAccess {
18 /// No command execution.
19 ///
20 /// For a run that inspects a workspace and reports on it. Worth being
21 /// precise about what this is: one route closed, not a boundary. The file
22 /// tools still write, and the process still runs as its user — see
23 /// [`DenyAll`](crate::DenyAll) for a run that changes nothing at all.
24 Denied,
25 /// Commands allowed.
26 ///
27 /// The default. A command reaches whatever the user account running basis
28 /// can reach, which is the honest description of a process on a host and
29 /// is why basis neither warns about it per run nor pretends otherwise.
30 #[default]
31 Granted,
32}
33
34impl ShellAccess {
35 pub fn is_granted(self) -> bool {
36 matches!(self, Self::Granted)
37 }
38
39 /// Maps a yes-or-no answer onto the enum, for a caller holding a bool —
40 /// a CLI flag, a config field, an environment variable of its own.
41 pub fn from_flag(granted: bool) -> Self {
42 if granted { Self::Granted } else { Self::Denied }
43 }
44}
45
46#[cfg(test)]
47mod tests {
48 use super::*;
49
50 #[test]
51 fn commands_are_the_default() {
52 // ADR-0013: the first `basis "run the tests"` has to work, and a default
53 // of denied made the tool's purpose opt-in.
54 assert_eq!(ShellAccess::default(), ShellAccess::Granted);
55 assert!(ShellAccess::default().is_granted());
56 }
57
58 #[test]
59 fn a_flag_maps_both_ways() {
60 assert_eq!(ShellAccess::from_flag(true), ShellAccess::Granted);
61 assert_eq!(ShellAccess::from_flag(false), ShellAccess::Denied);
62 }
63}