use polyc_crypto::approval;
use polyc_eventlog::Event;
use polyc_proto::kinds;
use serde_json::Value;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GrantReplaySignatureStatus {
Verified,
Invalid,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GrantReplayFact {
pub tool: String,
pub grant_ref: String,
pub covered_capabilities: Vec<String>,
pub coverage_hash: String,
pub signature_status: GrantReplaySignatureStatus,
pub signer_public_key: Option<Vec<u8>>,
}
#[must_use]
pub fn fold_grant_replay_event(
event: &Event,
trusted_signers: &[Vec<u8>],
) -> Option<GrantReplayFact> {
let (base, _turn_id) = kinds::parse(&event.kind);
if base != kinds::GRANT_REPLAY {
return None;
}
fold_grant_replay_payload(&event.payload, trusted_signers)
}
fn fold_grant_replay_payload(
payload: &[u8],
trusted_signers: &[Vec<u8>],
) -> Option<GrantReplayFact> {
let v: Value = serde_json::from_slice(payload).ok()?;
let tool = v.get("tool")?.as_str()?.to_owned();
let grant_ref = v.get("grant_ref")?.as_str()?.to_owned();
let covered_capabilities: Vec<String> = v
.get("covered_capabilities")?
.as_array()?
.iter()
.filter_map(|c| c.as_str().map(str::to_owned))
.collect();
let coverage_hash = v.get("coverage_hash")?.as_str()?.to_owned();
let signature_status = if approval::verify_grant_replay_pinned(payload, trusted_signers) {
GrantReplaySignatureStatus::Verified
} else {
GrantReplaySignatureStatus::Invalid
};
let signer_public_key = v
.get("signed_by")
.and_then(Value::as_str)
.and_then(polyc_crypto::hex::decode);
Some(GrantReplayFact {
tool,
grant_ref,
covered_capabilities,
coverage_hash,
signature_status,
signer_public_key,
})
}
#[cfg(test)]
mod tests {
#![allow(clippy::pedantic, clippy::nursery, missing_docs, clippy::unwrap_used)]
use polyc_crypto::approval::{ApprovalSigner, test_util::grant_replay_payload};
use super::*;
fn grant_replay_event(
conversation_id: &str,
turn_id: &str,
tool: &str,
grant_ref: &str,
covered_capabilities: &[String],
coverage_hash: &str,
signer: &ApprovalSigner,
) -> Event {
let (payload, _sig, _pk) = grant_replay_payload(
conversation_id,
turn_id,
tool,
grant_ref,
covered_capabilities,
coverage_hash,
signer,
);
Event::new(kinds::GRANT_REPLAY.to_owned(), payload)
}
#[test]
fn fold_grant_replay_event_verifies_a_trusted_signer_record() {
let signer = ApprovalSigner::from_seed(11);
let event = grant_replay_event(
"conv-1",
"turn-1",
"read_file",
"grant-ref-1",
&["fs.read".to_owned()],
"hash-1",
&signer,
);
let fact = fold_grant_replay_event(&event, &[signer.public_key_bytes()])
.expect("a structurally valid record always folds to a fact");
assert_eq!(fact.tool, "read_file");
assert_eq!(fact.grant_ref, "grant-ref-1");
assert_eq!(fact.covered_capabilities, vec!["fs.read".to_owned()]);
assert_eq!(fact.coverage_hash, "hash-1");
assert_eq!(fact.signature_status, GrantReplaySignatureStatus::Verified);
assert_eq!(
fact.signer_public_key.as_deref(),
Some(signer.public_key_bytes().as_slice())
);
}
#[test]
fn fold_grant_replay_event_keeps_an_untrusted_signer_record_tagged_invalid() {
let trusted = ApprovalSigner::from_seed(12);
let untrusted = ApprovalSigner::from_seed(13);
let event = grant_replay_event(
"conv-1",
"turn-1",
"read_file",
"grant-ref-2",
&["fs.read".to_owned()],
"hash-2",
&untrusted,
);
let fact = fold_grant_replay_event(&event, &[trusted.public_key_bytes()])
.expect("a structurally valid record always folds to a fact");
assert_eq!(fact.signature_status, GrantReplaySignatureStatus::Invalid);
assert_eq!(
fact.tool, "read_file",
"the row is still present with its claimed fields"
);
assert_eq!(
fact.signer_public_key.as_deref(),
Some(untrusted.public_key_bytes().as_slice()),
"the untrusted signer's own key is still surfaced, for audit"
);
}
#[test]
fn fold_grant_replay_event_drops_a_structurally_malformed_record() {
let event = Event::new(kinds::GRANT_REPLAY.to_owned(), vec![0xFF, 0xFE]);
assert_eq!(fold_grant_replay_event(&event, &[]), None);
}
#[test]
fn fold_grant_replay_event_drops_a_record_missing_a_required_field() {
let event = Event::new(
kinds::GRANT_REPLAY.to_owned(),
br#"{"tool":"read_file","grant_ref":"g1"}"#.to_vec(),
);
assert_eq!(fold_grant_replay_event(&event, &[]), None);
}
#[test]
fn fold_grant_replay_event_ignores_unrelated_kinds() {
let event = Event::new(kinds::USAGE.to_owned(), Vec::new());
assert_eq!(fold_grant_replay_event(&event, &[]), None);
}
}