brokerd: consult decisions record the body

Co-Authored-By: ornith-1.5-35b-a3b <agent@wntrmute.dev>
This commit is contained in:
2026-10-03 22:28:32 -07:00
co-authored by ornith-1.5-35b-a3b
parent d488910351
commit 2c939ea310
9 changed files with 280 additions and 31 deletions
+24 -1
View File
@@ -252,7 +252,30 @@ pub fn handle(mut stream: UnixStream, broker: &Broker) {
let now = Timestamp::now();
let grants = broker.grants();
let answer = match broker.ledger().decide(request, &grants, now) {
let decided = if request.tool == crate::consult::CONSULT {
let body = crate::consult::wire_body(broker.cfg().consult.as_ref(), &request.arguments);
if broker.cfg().consult.is_none() {
broker.ledger().decide_forced(
request,
&grants,
now,
Some(body),
crate::policy::Denial {
reason: bxcore::DenyReason::InvalidArguments,
grant: None,
grant_sha256: None,
detail: Some(crate::consult::NOT_CONFIGURED.to_string()),
},
)
} else {
broker
.ledger()
.decide_with_body(request, &grants, now, Some(body))
}
} else {
broker.ledger().decide(request, &grants, now)
};
let answer = match decided {
Decided::Denied(denial) => Some(ToolResponse::Denied {
reason: denial.reason,
detail: denial.detail,
+3
View File
@@ -18,6 +18,9 @@ pub const DEFAULT_TIMEOUT_MS: u64 = 180_000;
pub const DEFAULT_TEMPERATURE_MILLI: u16 = 600;
/// Start failure when the named secret cannot be read.
pub const SECRET_POINTER: &str = "see docs/runbook.md#consult-secret-unreadable";
/// Denied when `brokerd.toml` has no `[consult]` table.
pub const NOT_CONFIGURED: &str =
"consult is not configured; see docs/runbook.md#consult-not-configured";
/// `[consult]` in `brokerd.toml`. Unknown keys are errors.
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
+76 -30
View File
@@ -203,6 +203,40 @@ impl Ledger {
/// Decide and record a call.
pub fn decide(&self, request: ToolRequest, grants: &Grants, now: Timestamp) -> Decided {
self.decide_inner(request, grants, now, None, None)
}
/// Decide and record a call, storing `body` on the decision (consult).
pub fn decide_with_body(
&self,
request: ToolRequest,
grants: &Grants,
now: Timestamp,
body: Option<String>,
) -> Decided {
self.decide_inner(request, grants, now, body, None)
}
/// Record `forced` as the denial, with `body`, without matching grants.
pub fn decide_forced(
&self,
request: ToolRequest,
grants: &Grants,
now: Timestamp,
body: Option<String>,
forced: crate::policy::Denial,
) -> Decided {
self.decide_inner(request, grants, now, body, Some(forced))
}
fn decide_inner(
&self,
request: ToolRequest,
grants: &Grants,
now: Timestamp,
body: Option<String>,
forced: Option<crate::policy::Denial>,
) -> Decided {
let session = request.session.clone();
let call = request.call;
let tool = request.tool.clone();
@@ -225,47 +259,58 @@ impl Ledger {
};
// 3. The outcome, first that applies.
let (outcome, grant, grant_sha256, payload) = match grants {
Err(_) => (
let (outcome, grant, grant_sha256, payload) = if let Some(denial) = forced {
(
DecisionRecord::Denied {
reason: DenyReason::GrantsInvalid,
reason: denial.reason,
},
None,
None,
RecordPayload::Simple(DenyReason::GrantsInvalid),
),
Ok(set) => match state {
Err(()) => (
denial.grant.clone(),
denial.grant_sha256,
RecordPayload::Denial(denial),
)
} else {
match grants {
Err(_) => (
DecisionRecord::Denied {
reason: DenyReason::StateUnreadable,
reason: DenyReason::GrantsInvalid,
},
None,
None,
RecordPayload::Simple(DenyReason::StateUnreadable),
RecordPayload::Simple(DenyReason::GrantsInvalid),
),
Ok(state) => match crate::policy::decide(request, set, state, now) {
crate::policy::Outcome::Allowed(decision) => (
DecisionRecord::Allowed {},
Some(decision.grant().to_string()),
Some(decision.grant_sha256()),
RecordPayload::Allowed(decision),
),
crate::policy::Outcome::Ask(ask) => (
DecisionRecord::Ask {},
Some(ask.grant().to_string()),
Some(ask.grant_sha256()),
RecordPayload::Ask(ask),
),
crate::policy::Outcome::Denied(denial) => (
Ok(set) => match state {
Err(()) => (
DecisionRecord::Denied {
reason: denial.reason,
reason: DenyReason::StateUnreadable,
},
denial.grant.clone(),
denial.grant_sha256,
RecordPayload::Denial(denial),
None,
None,
RecordPayload::Simple(DenyReason::StateUnreadable),
),
Ok(state) => match crate::policy::decide(request, set, state, now) {
crate::policy::Outcome::Allowed(decision) => (
DecisionRecord::Allowed {},
Some(decision.grant().to_string()),
Some(decision.grant_sha256()),
RecordPayload::Allowed(decision),
),
crate::policy::Outcome::Ask(ask) => (
DecisionRecord::Ask {},
Some(ask.grant().to_string()),
Some(ask.grant_sha256()),
RecordPayload::Ask(ask),
),
crate::policy::Outcome::Denied(denial) => (
DecisionRecord::Denied {
reason: denial.reason,
},
denial.grant.clone(),
denial.grant_sha256,
RecordPayload::Denial(denial),
),
},
},
},
}
};
// 4. Write the record, then return the verdict.
@@ -279,6 +324,7 @@ impl Ledger {
grant_sha256,
taint: recorded.taint,
untrusted: recorded.untrusted,
body,
};
match self.append(&mut guard, now, event) {
Ok(seq) => match payload {