use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::crypto::{PublicKey, Signature, SigningAlgorithm};
use crate::receipt::body::ChioReceipt;
pub const IOU_ENVELOPE_SCHEMA: &str = "chio.credit.iou-envelope.v1";
#[derive(Debug, Error)]
pub enum CreditEvaluatorError {
#[error("receipt signature verification failed for receipt {receipt_id}")]
SignatureInvalid { receipt_id: String },
#[error("receipt {receipt_id} was signed by untrusted kernel key {kernel_key}")]
SignerUntrusted {
receipt_id: String,
kernel_key: String,
},
#[error("canonical encoding failed: {0}")]
Canonical(String),
#[error("iou signing failed: {0}")]
Signing(String),
#[error("invalid pricing context: {0}")]
PricingContext(String),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct IouEnvelopeBody {
pub schema: String,
pub iou_id: String,
pub receipt_id: String,
pub receipt_timestamp: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tenant_id: Option<String>,
pub tool_server: String,
pub tool_name: String,
pub capability_id: String,
pub amount_units: u64,
pub currency: String,
pub issuer_key: PublicKey,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct IouEnvelope {
#[serde(flatten)]
pub body: IouEnvelopeBody,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub algorithm: Option<SigningAlgorithm>,
pub signature: Signature,
}
impl IouEnvelope {
pub fn verify_signature(&self) -> Result<bool, CreditEvaluatorError> {
self.body
.issuer_key
.verify_canonical(&self.body, &self.signature)
.map_err(|err| CreditEvaluatorError::Canonical(err.to_string()))
}
}
pub trait CreditEvaluatorHook: Send + Sync {
fn evaluate(&self, receipt: &ChioReceipt) -> Result<Option<IouEnvelope>, CreditEvaluatorError>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn iou_envelope_schema_is_stable() {
assert_eq!(IOU_ENVELOPE_SCHEMA, "chio.credit.iou-envelope.v1");
}
}