use sha2::{Digest, Sha256};
use crate::policy::maintenance::SealedAccounting;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReceiptStatus {
Complete,
Degraded,
}
impl ReceiptStatus {
pub fn as_str(self) -> &'static str {
match self {
ReceiptStatus::Complete => "complete",
ReceiptStatus::Degraded => "degraded",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Receipt {
pub id: String,
pub model_id: Option<String>,
pub prompt_tokens_total: u64,
pub completion_tokens_total: u64,
pub uptime_seconds: u64,
pub status: ReceiptStatus,
}
impl Receipt {
pub fn from_sealed(identity: &str, sealed: &SealedAccounting) -> Self {
Receipt {
id: receipt_id(identity),
model_id: sealed.model_id.clone(),
prompt_tokens_total: sealed.prompt_tokens_total,
completion_tokens_total: sealed.completion_tokens_total,
uptime_seconds: sealed.uptime_seconds,
status: if sealed.drain_complete {
ReceiptStatus::Complete
} else {
ReceiptStatus::Degraded
},
}
}
}
const RECEIPT_NAMESPACE: &str = "ferrox.accounting.receipt.v1";
pub fn receipt_id(identity: &str) -> String {
let mut hasher = Sha256::new();
hasher.update(RECEIPT_NAMESPACE.as_bytes());
hasher.update([0u8]);
hasher.update(identity.as_bytes());
let digest = hasher.finalize();
let mut bytes = [0u8; 16];
bytes.copy_from_slice(&digest[..16]);
bytes[6] = (bytes[6] & 0x0f) | 0x80;
bytes[8] = (bytes[8] & 0x3f) | 0x80;
let hex: String = bytes.iter().map(|b| format!("{b:02x}")).collect();
format!(
"{}-{}-{}-{}-{}",
&hex[0..8],
&hex[8..12],
&hex[12..16],
&hex[16..20],
&hex[20..32]
)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum StopFailure {
NotPersisted(String),
NotSignalled {
receipt: Box<Receipt>,
error: String,
},
}
impl std::fmt::Display for StopFailure {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
StopFailure::NotPersisted(e) => {
write!(
f,
"accounting receipt was not persisted, nothing signalled: {e}"
)
}
StopFailure::NotSignalled { receipt, error } => write!(
f,
"accounting receipt {} is durable but the stop was not signalled: {error}",
receipt.id
),
}
}
}
impl std::error::Error for StopFailure {}
pub fn finish_stop(
receipt: Receipt,
persist: impl FnOnce(&Receipt) -> Result<(), String>,
signal: impl FnOnce(&Receipt) -> Result<(), String>,
) -> Result<Receipt, StopFailure> {
persist(&receipt).map_err(StopFailure::NotPersisted)?;
signal(&receipt).map_err(|error| StopFailure::NotSignalled {
receipt: Box::new(receipt.clone()),
error,
})?;
Ok(receipt)
}
#[cfg(test)]
mod tests {
use super::*;
fn sealed(drain_complete: bool) -> SealedAccounting {
SealedAccounting {
model_id: Some("glm-5.2".to_string()),
prompt_tokens_total: 1_000,
completion_tokens_total: 250,
uptime_seconds: 3_600,
drain_complete,
}
}
#[test]
fn the_same_generation_always_derives_the_same_receipt_id() {
assert_eq!(receipt_id("instance-7"), receipt_id("instance-7"));
assert_ne!(receipt_id("instance-7"), receipt_id("instance-8"));
let id = receipt_id("instance-7");
assert_eq!(id.len(), 36, "UUID-shaped: {id}");
let groups: Vec<usize> = id.split('-').map(str::len).collect();
assert_eq!(groups, vec![8, 4, 4, 4, 12]);
assert!(
id.chars().all(|c| c == '-' || c.is_ascii_hexdigit()),
"{id}"
);
}
#[test]
fn a_receipt_id_declares_the_version_it_really_is() {
let id = receipt_id("anything");
let version = id.split('-').nth(2).unwrap().chars().next().unwrap();
assert_eq!(version, '8', "RFC 9562 custom version: {id}");
let variant = id.split('-').nth(3).unwrap().chars().next().unwrap();
assert!("89ab".contains(variant), "RFC 4122 variant bits: {id}");
}
#[test]
fn a_drain_that_did_not_finish_is_demoted_rather_than_accepted() {
assert_eq!(
Receipt::from_sealed("i", &sealed(true)).status,
ReceiptStatus::Complete
);
let degraded = Receipt::from_sealed("i", &sealed(false));
assert_eq!(degraded.status, ReceiptStatus::Degraded);
assert_eq!(
degraded.completion_tokens_total, 250,
"the totals are still reported -- as a lower bound, which is \
what `degraded` says"
);
}
#[test]
fn a_receipt_that_could_not_be_persisted_signals_nothing() {
let mut signalled = false;
let result = finish_stop(
Receipt::from_sealed("i", &sealed(true)),
|_| Err("disk full".to_string()),
|_| {
signalled = true;
Ok(())
},
);
assert_eq!(
result,
Err(StopFailure::NotPersisted("disk full".to_string()))
);
assert!(
!signalled,
"the signal must not be reachable past a failed write"
);
}
#[test]
fn a_persisted_receipt_that_could_not_be_signalled_says_it_is_durable() {
let receipt = Receipt::from_sealed("i", &sealed(true));
let err = finish_stop(receipt.clone(), |_| Ok(()), |_| Err("no pipe".to_string()))
.expect_err("the signal failed");
assert!(err.to_string().contains("durable"), "{err}");
match &err {
StopFailure::NotSignalled { receipt: r, .. } => assert_eq!(**r, receipt),
other => panic!("expected NotSignalled, got {other:?}"),
}
}
#[test]
fn a_stop_that_persisted_and_signalled_returns_the_receipt() {
let order = std::cell::RefCell::new(Vec::new());
let receipt = finish_stop(
Receipt::from_sealed("i", &sealed(true)),
|_| {
order.borrow_mut().push("persist");
Ok(())
},
|_| {
order.borrow_mut().push("signal");
Ok(())
},
)
.expect("both steps succeed");
assert_eq!(
order.into_inner(),
vec!["persist", "signal"],
"durable first, always"
);
assert_eq!(receipt.model_id.as_deref(), Some("glm-5.2"));
}
}