1use serde::{Deserialize, Serialize};
2
3use crate::{digest::bytes_digest, FibQuantError, Result};
4
5use super::quality::KvAttentionQualityReportV1;
6
7pub const KV_RECEIPT_SCHEMA: &str = "fib_quant_kv_receipt_v1";
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
11#[non_exhaustive]
12#[serde(rename_all = "snake_case")]
13pub enum KvOperationKindV1 {
14 Compress,
16 Decode,
18 Eval,
20}
21
22#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
24pub struct KvCompressionReceiptV1 {
25 pub schema_version: String,
27 pub operation_kind: KvOperationKindV1,
29 pub source_digest: String,
31 pub profile_digest: String,
33 pub shape_digest: String,
35 pub page_digests: Vec<String>,
37 pub codebook_digest: String,
39 pub rotation_digest: String,
41 pub encoded_pages: u32,
43 pub compressed_blocks: u32,
45 pub raw_fallback_blocks: u32,
47 pub fallback_reasons: Vec<String>,
49 pub recorded_unix_seconds: i64,
51}
52
53#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
55pub struct KvDecodeReceiptV1 {
56 pub schema_version: String,
58 pub operation_kind: KvOperationKindV1,
60 pub decoded_digest: String,
62 pub profile_digest: String,
64 pub shape_digest: String,
66 pub page_digests: Vec<String>,
68 pub codebook_digest: String,
70 pub rotation_digest: String,
72 pub decoded_pages: u32,
74 pub raw_fallback_blocks: u32,
76 pub recorded_unix_seconds: i64,
78}
79
80#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
82pub struct KvEvalReceiptV1 {
83 pub schema_version: String,
85 pub operation_kind: KvOperationKindV1,
87 pub source_digest: String,
89 pub decoded_digest: String,
91 pub profile_digest: String,
93 pub shape_digest: String,
95 pub quality_report: Option<KvAttentionQualityReportV1>,
97 pub recorded_unix_seconds: i64,
99}
100
101impl KvCompressionReceiptV1 {
102 pub(crate) fn validate(&self) -> Result<()> {
103 if self.schema_version != KV_RECEIPT_SCHEMA
104 || self.operation_kind != KvOperationKindV1::Compress
105 {
106 return Err(FibQuantError::CorruptPayload(
107 "invalid kv compression receipt".into(),
108 ));
109 }
110 Ok(())
111 }
112}
113
114pub fn kv_tensor_digest(values: &[f32]) -> Result<String> {
116 if values.iter().any(|value| !value.is_finite()) {
117 return Err(FibQuantError::CorruptPayload(
118 "kv tensor contains non-finite value".into(),
119 ));
120 }
121 let mut bytes = Vec::with_capacity(32 + std::mem::size_of_val(values));
122 bytes.extend_from_slice(b"fib_quant_kv_tensor_f32_v1");
123 bytes.push(0);
124 bytes.extend_from_slice(&(values.len() as u64).to_le_bytes());
125 for value in values {
126 bytes.extend_from_slice(&value.to_le_bytes());
127 }
128 Ok(bytes_digest(&bytes))
129}
130
131pub(crate) fn now_unix_seconds() -> i64 {
132 std::time::SystemTime::now()
133 .duration_since(std::time::UNIX_EPOCH)
134 .map(|duration| duration.as_secs() as i64)
135 .unwrap_or(0)
136}