use polyc_eventlog::Event;
use polyc_proto::kinds;
pub fn verified_refusals<'a>(
events: &'a [Event],
trusted_signers: &'a [Vec<u8>],
) -> impl Iterator<Item = polyc_crypto::approval::VerifiedRefusal> + 'a {
events.iter().filter_map(move |ev| {
let (base, _) = kinds::parse(&ev.kind);
if base != kinds::PAYMENT_REFUSAL {
return None;
}
let refusal = polyc_crypto::approval::verify_signed_refusal(&ev.payload, trusted_signers)?;
if refusal.kind != kinds::PAYMENT_REFUSAL {
return None;
}
Some(refusal)
})
}
#[cfg(test)]
mod tests {
#![allow(clippy::pedantic, clippy::nursery, missing_docs, clippy::unwrap_used)]
use polyc_crypto::approval::{ApprovalSigner, RefusalPayload, refusal_payload};
use super::*;
fn signed_refusal(signer: &ApprovalSigner, tool_call_id: &str) -> Vec<u8> {
let (payload, sig, pk) = refusal_payload(
&RefusalPayload {
kind: kinds::PAYMENT_REFUSAL,
reason: "over_spend_cap",
reason_detail: "payment of 500 base units exceeds the cap of 100",
merchant_host: "merchant.example",
requested_base_units: "500",
permitted_base_units: "100",
tool_call_id,
subject: "persona-1",
timestamp: "1780000000",
},
signer,
);
let _ = (sig, pk);
payload
}
#[test]
fn verified_refusals_drops_unknown_signer() {
let trusted = ApprovalSigner::from_seed(1);
let untrusted = ApprovalSigner::from_seed(2);
let events = vec![
Event::new(
"payment_refusal:trusted".to_owned(),
signed_refusal(&trusted, "call-1"),
),
Event::new(
"payment_refusal:untrusted".to_owned(),
signed_refusal(&untrusted, "call-2"),
),
];
let trusted_signers = vec![trusted.public_key_bytes()];
let refusals: Vec<_> = verified_refusals(&events, &trusted_signers).collect();
assert_eq!(refusals.len(), 1);
assert_eq!(refusals[0].tool_call_id, "call-1");
}
#[test]
fn verified_refusals_filters_by_kind_base() {
let signer = ApprovalSigner::from_seed(3);
let events = vec![
Event::new(
"payment_refusal".to_owned(),
signed_refusal(&signer, "call-1"),
),
Event::new("usage".to_owned(), vec![]),
];
let trusted_signers = vec![signer.public_key_bytes()];
let refusals: Vec<_> = verified_refusals(&events, &trusted_signers).collect();
assert_eq!(refusals.len(), 1);
}
#[test]
fn verified_refusals_drops_malformed_payload() {
let events = vec![Event::new(
kinds::PAYMENT_REFUSAL.to_owned(),
vec![0xFF, 0xFE, 0xFD],
)];
let refusals: Vec<_> = verified_refusals(&events, &[]).collect();
assert_eq!(refusals.len(), 0);
}
}