use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use crate::opslog;
use crate::xdg::Env;
pub mod author;
pub mod bash;
pub mod classify;
pub mod confine;
pub mod hold;
pub mod judge;
pub mod lex;
pub mod policy;
pub mod reason;
pub mod root;
pub mod rules;
pub mod wire;
use judge::Answers;
use policy::Policy;
use root::Root;
use wire::{Request, Verdict};
pub const SUBCMD: &str = "tool-control";
const UNREADABLE: i32 = 2;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Consult {
pub workspace: PathBuf,
pub world: crate::xdg::Env,
pub state_root: PathBuf,
pub home: PathBuf,
pub cwd: Option<PathBuf>,
pub policy: Policy,
}
impl Consult {
pub fn new(env: &Env, workspace: &Path, cwd: Option<PathBuf>, policy: Policy) -> Consult {
Consult {
workspace: workspace.to_path_buf(),
world: env.clone(),
state_root: env.yog_state_root(),
home: env.home_dir(),
cwd,
policy,
}
}
fn root(&self, agent_id: &str, entries: &[opslog::OpEntry]) -> Root {
let agent = root::agent_worktree(&self.workspace, agent_id);
let claimant = self
.workspace
.file_name()
.map(|s| s.to_string_lossy().into_owned())
.unwrap_or_default();
let mut writable = vec![agent.clone()];
writable.extend(root::bound_worktrees(entries, &self.world, &claimant));
writable.extend(root::candidate_worktrees(
entries,
&self.world,
&self.workspace,
&claimant,
));
Root {
cwd: self.cwd.clone().unwrap_or(agent),
writable,
home: self.home.clone(),
}
}
}
pub fn adjudicate(consult: &Consult, request: &Request) -> Verdict {
let entries = opslog::tail(&consult.state_root, usize::MAX);
let root = consult.root(&request.agent_id, &entries);
let classified = classify::classify(request, &root, &consult.policy);
let standing = Answers::fold(&entries).ruling(
request,
&crate::nav::ws_key(&consult.workspace),
classified.effect,
&consult.policy,
);
reason::verdict(standing, request, &classified)
}
pub fn run(stdin: &mut dyn Read, stdout: &mut dyn Write, env: &Env, workspace: &Path) -> i32 {
let mut raw = String::new();
if stdin.read_to_string(&mut raw).is_err() {
return UNREADABLE;
}
let Some(request) = Request::parse(&raw) else {
return UNREADABLE;
};
let cwd = root::agent_cwd(workspace, &request.agent_id);
let policy = Policy::read(workspace);
let verdict = adjudicate(&Consult::new(env, workspace, cwd, policy), &request);
if writeln!(stdout, "{}", verdict.json()).is_err() {
return UNREADABLE;
}
0
}
pub fn workspace_of(env: &Env) -> PathBuf {
env.litany_conv_repo()
.unwrap_or_else(|| std::env::current_dir().unwrap_or_default())
}
#[cfg(test)]
mod tests;