bxcore, brokerd, loopd: an invalid-arguments denial says why

Co-Authored-By: ornith-1.5-35b-a3b <agent@wntrmute.dev>
This commit is contained in:
2026-09-27 19:08:42 -07:00
co-authored by ornith-1.5-35b-a3b
parent 89a50bc3c6
commit de60c9e13a
21 changed files with 520 additions and 33 deletions
+16 -3
View File
@@ -120,6 +120,9 @@ pub struct Denial {
pub grant: Option<String>,
/// `Some` only for `DeniedByGrant`: the deny grant's file hash.
pub grant_sha256: Option<Hash32>,
/// `Some` only for `InvalidArguments`: why, from `ArgsError::detail`. It goes to the model in
/// the `ToolResponse`, never into the audit record, which holds the arguments already.
pub detail: Option<String>,
}
impl Denial {
@@ -128,6 +131,7 @@ impl Denial {
reason,
grant: None,
grant_sha256: None,
detail: None,
}
}
}
@@ -244,9 +248,16 @@ pub fn decide(
let Some(tool) = ToolName::parse(&request.tool) else {
return Outcome::Denied(Denial::new(DenyReason::NoGrant));
};
// 2. Malformed arguments are refused before matching, whatever the grants say.
let Ok(args) = args::parse(tool, &request.arguments) else {
return Outcome::Denied(Denial::new(DenyReason::InvalidArguments));
// 2. Malformed arguments are refused before matching, whatever the grants say, and the
// model is told why, so that it can correct the call.
let args = match args::parse(tool, &request.arguments) {
Ok(args) => args,
Err(error) => {
return Outcome::Denied(Denial {
detail: Some(error.detail()),
..Denial::new(DenyReason::InvalidArguments)
});
}
};
let MatchResult {
@@ -273,6 +284,7 @@ pub fn decide(
reason: DenyReason::DeniedByGrant,
grant: Some(winner.id.clone()),
grant_sha256: Some(winner.grant_sha256),
detail: None,
}),
Mode::Ask => Outcome::Ask(Ask {
request,
@@ -316,6 +328,7 @@ pub fn redecide(
reason: DenyReason::DeniedByGrant,
grant: Some(winner.id.clone()),
grant_sha256: Some(winner.grant_sha256),
detail: None,
}),
Mode::Ask | Mode::Auto => Ok(Decision {
request: ask.request().clone(),