1use serde::{Deserialize, Serialize};
11
12use crate::canonical::{CanonicalError, record_canonical_bytes, tagged_digest};
13use crate::ids::ContentDigest;
14use crate::limits::RECORD_V1;
15use crate::output::RawOutput;
16use crate::schema;
17use crate::time::Timestamp;
18
19#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
20#[serde(deny_unknown_fields)]
21pub struct CacheEntry {
22 pub schema: String,
23 pub entry: EntryBody,
24 pub integrity: ContentDigest,
26}
27
28crate::document::document!(CacheEntry, schema::CACHE_ENTRY, RECORD_V1);
29
30#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
32#[serde(deny_unknown_fields)]
33pub struct EntryBody {
34 pub kind: EntryKind,
35 pub key_material: String,
37 pub key_id: String,
39 pub key_schema_version: u32,
40 pub namespace_version: u64,
41 pub runtime_version: String,
43 pub generations: EntryGenerations,
45 pub created: Timestamp,
48 pub ttl_ms: u64,
49 pub payload: EntryPayload,
50 pub evidence: ContentDigest,
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
57#[serde(rename_all = "snake_case")]
58pub enum EntryKind {
59 Request,
60 Decision,
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
64#[serde(deny_unknown_fields)]
65pub struct EntryGenerations {
66 pub namespace: u64,
67 pub scope: u64,
68}
69
70#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
71#[serde(rename_all = "snake_case", deny_unknown_fields)]
72pub enum EntryPayload {
73 Request(RequestPayload),
74 Decision(DecisionPayload),
75}
76
77#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
81#[serde(deny_unknown_fields)]
82pub struct RequestPayload {
83 pub target: u32,
85 pub output: RawOutput,
86 pub source_attempt_id: String,
87}
88
89#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
92#[serde(deny_unknown_fields)]
93pub struct DecisionPayload {
94 pub requests: Vec<ReusedRequestPayload>,
96 pub volatile: Vec<crate::snapshot::Entry>,
99 pub judgment: ContentDigest,
101 pub run_record: ContentDigest,
103}
104
105#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
110#[serde(deny_unknown_fields)]
111pub struct ReusedRequestPayload {
112 pub step: String,
113 pub instance: Vec<String>,
114 pub key_material: String,
117 pub output: RawOutput,
118 pub source_attempt_id: String,
119}
120
121impl EntryBody {
122 pub fn integrity(&self) -> Result<ContentDigest, CanonicalError> {
124 let bytes = record_canonical_bytes(self)?;
125 Ok(ContentDigest::from_digest(tagged_digest(
126 schema::CACHE_ENTRY,
127 &bytes,
128 )))
129 }
130}
131
132impl CacheEntry {
133 pub fn seal(entry: EntryBody) -> Result<CacheEntry, CanonicalError> {
135 Ok(CacheEntry {
136 schema: schema::CACHE_ENTRY.into(),
137 integrity: entry.integrity()?,
138 entry,
139 })
140 }
141
142 pub fn intact(&self) -> bool {
144 self.entry.integrity().is_ok_and(|d| d == self.integrity)
145 }
146}