Skip to main content

rustev_contract/
cache_entry.rs

1//! `rustev.cache-entry/1`: one entry of the persistent tier (spec 044,
2//! 3.4.3).
3//!
4//! An entry is the record canonical bytes of [`CacheEntry`]. Everything
5//! but the integrity digest is its [`EntryBody`], and the digest is
6//! `tagged_digest("rustev.cache-entry/1", record canonical bytes of the
7//! body)`, so a stored entry is checked in full on every read and a store
8//! never needs to understand it.
9
10use 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    /// Over the record canonical bytes of `entry`, tagged with the schema.
25    pub integrity: ContentDigest,
26}
27
28crate::document::document!(CacheEntry, schema::CACHE_ENTRY, RECORD_V1);
29
30/// Everything an entry holds besides its integrity digest.
31#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
32#[serde(deny_unknown_fields)]
33pub struct EntryBody {
34    pub kind: EntryKind,
35    /// The complete canonical key material, lowercase hexadecimal.
36    pub key_material: String,
37    /// The key's digest, under which the store keeps the entry.
38    pub key_id: String,
39    pub key_schema_version: u32,
40    pub namespace_version: u64,
41    /// The runtime version that wrote the entry.
42    pub runtime_version: String,
43    /// The store's generations captured before the work began.
44    pub generations: EntryGenerations,
45    /// The evaluation time of the decision that wrote the entry; expiry
46    /// is measured from it (3.4.3).
47    pub created: Timestamp,
48    pub ttl_ms: u64,
49    pub payload: EntryPayload,
50    /// The digest of the run record whose attempt produced the payload.
51    pub evidence: ContentDigest,
52}
53
54/// A request-level entry, or a decision-result cache entry (3.6.4); the
55/// payload's variant is the kind's.
56#[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/// A validated raw output and where it came from. Its derivation follows
78/// from the plan, which the key names; its lineage is the source attempt
79/// and the entry's `evidence`.
80#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
81#[serde(deny_unknown_fields)]
82pub struct RequestPayload {
83    /// The index of the target that answered: the primary, or a fallback.
84    pub target: u32,
85    pub output: RawOutput,
86    pub source_attempt_id: String,
87}
88
89/// A judged decision's reusable work (spec 044, 3.6.4). It holds no
90/// judgment to serve: the core recomputes every served judgment (3.6.3).
91#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
92#[serde(deny_unknown_fields)]
93pub struct DecisionPayload {
94    /// Each request outside the refresh closure, by step then instance.
95    pub requests: Vec<ReusedRequestPayload>,
96    /// The source decision's snapshot entries of its volatile inputs, as
97    /// supplied, for a failed refresh (3.6.5).
98    pub volatile: Vec<crate::snapshot::Entry>,
99    /// The record digest of the source decision's judgment.
100    pub judgment: ContentDigest,
101    /// The record digest of the source decision's run record.
102    pub run_record: ContentDigest,
103}
104
105/// A request outside the refresh closure: its 017 key, the validated
106/// output of target 0 and the attempt that produced it. As for a request
107/// payload, its derivation follows from the plan the entry's key names,
108/// and its lineage is the source attempt and the payload's `run_record`.
109#[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    /// The request's complete canonical key material, lowercase
115    /// hexadecimal.
116    pub key_material: String,
117    pub output: RawOutput,
118    pub source_attempt_id: String,
119}
120
121impl EntryBody {
122    /// The integrity digest of this body.
123    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    /// The entry for `entry`, with its integrity digest.
134    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    /// Whether the integrity digest is the body's.
143    pub fn intact(&self) -> bool {
144        self.entry.integrity().is_ok_and(|d| d == self.integrity)
145    }
146}