Add Decision, decide and the runner stub to brokerd
Implemented-By: Laguna S 2.1 (OpenCode)
This commit is contained in:
@@ -1 +1,4 @@
|
||||
//! The broker: the only role that holds authority.
|
||||
|
||||
pub mod policy;
|
||||
pub mod runner;
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
//! Policy decisions. `Decision` can only be constructed in this module.
|
||||
//!
|
||||
//! Code outside this module cannot build a `Decision` with a struct literal, because its fields
|
||||
//! are private:
|
||||
//!
|
||||
//! ```compile_fail
|
||||
//! let request = proto::ToolRequest {
|
||||
//! session: proto::SessionId::new("s1").unwrap(),
|
||||
//! call: proto::CallId(1),
|
||||
//! tool: "read_file".to_string(),
|
||||
//! arguments: "{}".to_string(),
|
||||
//! };
|
||||
//! let _ = brokerd::policy::Decision { request, grant: "g".to_string() };
|
||||
//! ```
|
||||
//!
|
||||
//! Nor with the constructor, because it is private to this module:
|
||||
//!
|
||||
//! ```compile_fail
|
||||
//! let request = proto::ToolRequest {
|
||||
//! session: proto::SessionId::new("s1").unwrap(),
|
||||
//! call: proto::CallId(1),
|
||||
//! tool: "read_file".to_string(),
|
||||
//! arguments: "{}".to_string(),
|
||||
//! };
|
||||
//! let _ = brokerd::policy::Decision::new(request, "g".to_string());
|
||||
//! ```
|
||||
//!
|
||||
//! The same setup compiles when it goes through `decide`, which proves the two examples above
|
||||
//! fail because of `Decision` and not because of a mistake in the setup:
|
||||
//!
|
||||
//! ```
|
||||
//! let request = proto::ToolRequest {
|
||||
//! session: proto::SessionId::new("s1").unwrap(),
|
||||
//! call: proto::CallId(1),
|
||||
//! tool: "read_file".to_string(),
|
||||
//! arguments: "{}".to_string(),
|
||||
//! };
|
||||
//! assert_eq!(brokerd::policy::decide(request).unwrap_err(), proto::DenyReason::NoGrant);
|
||||
//! ```
|
||||
|
||||
use proto::{DenyReason, ToolRequest};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Decision {
|
||||
request: ToolRequest,
|
||||
grant: String,
|
||||
}
|
||||
|
||||
impl Decision {
|
||||
#[cfg_attr(not(test), expect(dead_code, reason = "grant matching arrives in M3"))]
|
||||
fn new(request: ToolRequest, grant: String) -> Self {
|
||||
Decision { request, grant }
|
||||
}
|
||||
|
||||
pub fn request(&self) -> &ToolRequest {
|
||||
&self.request
|
||||
}
|
||||
|
||||
pub fn grant(&self) -> &str {
|
||||
&self.grant
|
||||
}
|
||||
}
|
||||
|
||||
/// Until M3 there are no grants, so every request is denied with DenyReason::NoGrant.
|
||||
pub fn decide(request: ToolRequest) -> Result<Decision, DenyReason> {
|
||||
let _ = request;
|
||||
Err(DenyReason::NoGrant)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use proto::{CallId, SessionId};
|
||||
|
||||
fn request() -> ToolRequest {
|
||||
ToolRequest {
|
||||
session: SessionId::new("s1").unwrap(),
|
||||
call: CallId(1),
|
||||
tool: "read_file".to_string(),
|
||||
arguments: "{}".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_grants_means_deny() {
|
||||
assert_eq!(decide(request()).unwrap_err(), DenyReason::NoGrant);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decision_exposes_request_and_grant() {
|
||||
let d = Decision::new(request(), "g1".to_string());
|
||||
assert_eq!(d.request().tool, "read_file");
|
||||
assert_eq!(d.grant(), "g1");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
//! Tool runner stub. Real tool execution arrives in M3.
|
||||
|
||||
use crate::policy::Decision;
|
||||
use proto::ToolResponse;
|
||||
|
||||
/// Takes the Decision by value, so one decision cannot run a tool twice.
|
||||
pub fn run(decision: Decision) -> ToolResponse {
|
||||
let _ = decision;
|
||||
ToolResponse::Failed {
|
||||
message: "no tool runner until M3".to_string(),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user