use trusty_mpm_core::overseer::{Overseer, OverseerContext, OverseerDecision};
#[derive(Debug)]
pub struct CompositeOverseer {
primary: Box<dyn Overseer>,
secondary: Box<dyn Overseer>,
}
impl CompositeOverseer {
pub fn new(primary: Box<dyn Overseer>, secondary: Box<dyn Overseer>) -> Self {
Self { primary, secondary }
}
}
impl Overseer for CompositeOverseer {
fn pre_tool_use(&self, ctx: &OverseerContext) -> OverseerDecision {
match self.primary.pre_tool_use(ctx) {
decision @ (OverseerDecision::Block { .. } | OverseerDecision::Respond { .. }) => {
decision
}
_ if self.secondary.is_enabled() => self.secondary.pre_tool_use(ctx),
other => other,
}
}
fn post_tool_use(&self, ctx: &OverseerContext, output: &str) -> OverseerDecision {
self.primary.post_tool_use(ctx, output)
}
fn session_question(&self, ctx: &OverseerContext, question: &str) -> OverseerDecision {
match self.primary.session_question(ctx, question) {
OverseerDecision::Respond { text } => OverseerDecision::Respond { text },
_ if self.secondary.is_enabled() => self.secondary.session_question(ctx, question),
other => other,
}
}
fn is_enabled(&self) -> bool {
self.primary.is_enabled() || self.secondary.is_enabled()
}
}
#[cfg(test)]
mod tests {
use super::*;
use trusty_mpm_core::session::SessionId;
#[derive(Debug)]
struct StubOverseer {
decision: OverseerDecision,
enabled: bool,
}
impl Overseer for StubOverseer {
fn pre_tool_use(&self, _ctx: &OverseerContext) -> OverseerDecision {
self.decision.clone()
}
fn post_tool_use(&self, _ctx: &OverseerContext, _output: &str) -> OverseerDecision {
self.decision.clone()
}
fn session_question(&self, _ctx: &OverseerContext, _q: &str) -> OverseerDecision {
self.decision.clone()
}
fn is_enabled(&self) -> bool {
self.enabled
}
}
fn ctx() -> OverseerContext {
OverseerContext::new(SessionId::new(), "tmpm-test", Some("Bash".into()), None)
}
#[test]
fn block_short_circuits() {
let primary = Box::new(StubOverseer {
decision: OverseerDecision::Block {
reason: "rule".into(),
},
enabled: true,
});
let secondary = Box::new(StubOverseer {
decision: OverseerDecision::Allow,
enabled: true,
});
let composite = CompositeOverseer::new(primary, secondary);
assert!(matches!(
composite.pre_tool_use(&ctx()),
OverseerDecision::Block { .. }
));
}
#[test]
fn allow_escalates_to_llm() {
let primary = Box::new(StubOverseer {
decision: OverseerDecision::Allow,
enabled: true,
});
let secondary = Box::new(StubOverseer {
decision: OverseerDecision::Block {
reason: "llm".into(),
},
enabled: true,
});
let composite = CompositeOverseer::new(primary, secondary);
assert!(matches!(
composite.pre_tool_use(&ctx()),
OverseerDecision::Block { .. }
));
}
#[test]
fn disabled_llm_is_not_consulted() {
let primary = Box::new(StubOverseer {
decision: OverseerDecision::Allow,
enabled: true,
});
let secondary = Box::new(StubOverseer {
decision: OverseerDecision::Block {
reason: "llm".into(),
},
enabled: false,
});
let composite = CompositeOverseer::new(primary, secondary);
assert_eq!(composite.pre_tool_use(&ctx()), OverseerDecision::Allow);
}
#[test]
fn enabled_when_either_layer_active() {
let primary = Box::new(StubOverseer {
decision: OverseerDecision::Allow,
enabled: false,
});
let secondary = Box::new(StubOverseer {
decision: OverseerDecision::Allow,
enabled: true,
});
assert!(CompositeOverseer::new(primary, secondary).is_enabled());
}
}