1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
//! `rustev.cache-entry/1`: one entry of the persistent tier (spec 044,
//! 3.4.3).
//!
//! An entry is the record canonical bytes of [`CacheEntry`]. Everything
//! but the integrity digest is its [`EntryBody`], and the digest is
//! `tagged_digest("rustev.cache-entry/1", record canonical bytes of the
//! body)`, so a stored entry is checked in full on every read and a store
//! never needs to understand it.
use serde::{Deserialize, Serialize};
use crate::canonical::{CanonicalError, record_canonical_bytes, tagged_digest};
use crate::ids::ContentDigest;
use crate::limits::RECORD_V1;
use crate::output::RawOutput;
use crate::schema;
use crate::time::Timestamp;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct CacheEntry {
pub schema: String,
pub entry: EntryBody,
/// Over the record canonical bytes of `entry`, tagged with the schema.
pub integrity: ContentDigest,
}
crate::document::document!(CacheEntry, schema::CACHE_ENTRY, RECORD_V1);
/// Everything an entry holds besides its integrity digest.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct EntryBody {
pub kind: EntryKind,
/// The complete canonical key material, lowercase hexadecimal.
pub key_material: String,
/// The key's digest, under which the store keeps the entry.
pub key_id: String,
pub key_schema_version: u32,
pub namespace_version: u64,
/// The runtime version that wrote the entry.
pub runtime_version: String,
/// The store's generations captured before the work began.
pub generations: EntryGenerations,
/// The evaluation time of the decision that wrote the entry; expiry
/// is measured from it (3.4.3).
pub created: Timestamp,
pub ttl_ms: u64,
pub payload: EntryPayload,
/// The digest of the run record whose attempt produced the payload.
pub evidence: ContentDigest,
}
/// A request-level entry, or a decision-result cache entry (3.6.4); the
/// payload's variant is the kind's.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum EntryKind {
Request,
Decision,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct EntryGenerations {
pub namespace: u64,
pub scope: u64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub enum EntryPayload {
Request(RequestPayload),
Decision(DecisionPayload),
}
/// A validated raw output and where it came from. Its derivation follows
/// from the plan, which the key names; its lineage is the source attempt
/// and the entry's `evidence`.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct RequestPayload {
/// The index of the target that answered: the primary, or a fallback.
pub target: u32,
pub output: RawOutput,
pub source_attempt_id: String,
}
/// A judged decision's reusable work (spec 044, 3.6.4). It holds no
/// judgment to serve: the core recomputes every served judgment (3.6.3).
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct DecisionPayload {
/// Each request outside the refresh closure, by step then instance.
pub requests: Vec<ReusedRequestPayload>,
/// The source decision's snapshot entries of its volatile inputs, as
/// supplied, for a failed refresh (3.6.5).
pub volatile: Vec<crate::snapshot::Entry>,
/// The record digest of the source decision's judgment.
pub judgment: ContentDigest,
/// The record digest of the source decision's run record.
pub run_record: ContentDigest,
}
/// A request outside the refresh closure: its 017 key, the validated
/// output of target 0 and the attempt that produced it. As for a request
/// payload, its derivation follows from the plan the entry's key names,
/// and its lineage is the source attempt and the payload's `run_record`.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ReusedRequestPayload {
pub step: String,
pub instance: Vec<String>,
/// The request's complete canonical key material, lowercase
/// hexadecimal.
pub key_material: String,
pub output: RawOutput,
pub source_attempt_id: String,
}
impl EntryBody {
/// The integrity digest of this body.
pub fn integrity(&self) -> Result<ContentDigest, CanonicalError> {
let bytes = record_canonical_bytes(self)?;
Ok(ContentDigest::from_digest(tagged_digest(
schema::CACHE_ENTRY,
&bytes,
)))
}
}
impl CacheEntry {
/// The entry for `entry`, with its integrity digest.
pub fn seal(entry: EntryBody) -> Result<CacheEntry, CanonicalError> {
Ok(CacheEntry {
schema: schema::CACHE_ENTRY.into(),
integrity: entry.integrity()?,
entry,
})
}
/// Whether the integrity digest is the body's.
pub fn intact(&self) -> bool {
self.entry.integrity().is_ok_and(|d| d == self.integrity)
}
}