Skip to main content

borg/harness/
sim.rs

1//! The simulated harness, for the conformance suites (tests/conformance_cli.rs, docs/harnesses.md):
2//! command hooks in `~/.sim/hooks.json`, under native event names of its own, and capabilities from
3//! `BORG_SIM_CAPS`, so every capability branch of the core is reachable from one binary without any
4//! third-party agent. Hidden: never in help, completions, `all`, status or doctor, and known by name
5//! only to a process with `BORG_TEST_HARNESS` set (`TEST_ENV`), as the suites run it.
6
7use std::path::{Path, PathBuf};
8
9use super::{CONTEXT_GUARD, Caps, DroneWake, Harness, Probe, STOP_TIMEOUT_S, Transcript, Wiring};
10
11/// Native names unlike any real harness's: the event table, not a name, decides the canonical event.
12pub const EVENTS: [(&str, &str); 6] = [
13    ("SimPrompt", "prompt"),
14    ("SimPreTool", "pre_tool"),
15    ("SimPostTool", "post_tool"),
16    ("SimPostToolFailure", "post_tool_failure"),
17    ("SimStop", "stop"),
18    ("SimEnd", "end"),
19];
20
21pub const CAPS_ENV: &str = "BORG_SIM_CAPS";
22
23/// Without `BORG_SIM_CAPS`: context at the prompt and after a tool call, denials captured, nothing else.
24const DEFAULT_CAPS: &str = "prompt,tool,deny";
25
26pub struct Sim;
27
28/// The flags in a comma list: prompt, tool, early, dialog, transcript, deny, pid.
29pub fn caps_of(list: &str) -> Caps {
30    let on = |f: &str| list.split(',').any(|x| x.trim() == f);
31    Caps {
32        prompt_context: on("prompt"),
33        tool_context: on("tool"),
34        early_delivery: on("early"),
35        question_dialog: on("dialog"),
36        transcript: if on("transcript") { Transcript::Claude } else { Transcript::None },
37        deny_capture: on("deny"),
38        sends_pid: on("pid"),
39        stop_budget: None,
40        // A harness of no one's: it sets no limit of its own.
41        context_chars: CONTEXT_GUARD,
42        session_env: None,
43        pid_env: None,
44        drone_wake: DroneWake::Model,
45    }
46}
47
48impl Harness for Sim {
49    fn name(&self) -> &'static str {
50        "sim"
51    }
52
53    fn caps(&self) -> Caps {
54        caps_of(&std::env::var(CAPS_ENV).unwrap_or_else(|_| DEFAULT_CAPS.into()))
55    }
56
57    fn wiring(&self) -> Wiring {
58        Wiring::CommandHooks { events: &EVENTS, timeouts: &[("stop", STOP_TIMEOUT_S)], retired: &[], through_links: false }
59    }
60
61    fn target(&self, home: &Path, _var: &dyn Fn(&str) -> Option<PathBuf>) -> PathBuf {
62        home.join(".sim/hooks.json")
63    }
64
65    fn probe(&self) -> Probe {
66        Probe { cli: "sim", tested: "" }
67    }
68
69    fn hidden(&self) -> bool {
70        true
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[test]
79    fn caps_come_from_the_list() {
80        let c = caps_of("early,transcript");
81        assert!(c.early_delivery && !c.question_dialog && !c.prompt_context);
82        assert_eq!(c.transcript, Transcript::Claude);
83        let d = caps_of(DEFAULT_CAPS);
84        assert!(d.prompt_context && d.tool_context && d.deny_capture && !d.early_delivery && !d.sends_pid);
85    }
86}