Skip to main content

rustev_contract/
optimization.rs

1//! Neutral runtime optimization policy and evidence vocabulary (spec 017).
2//!
3//! These types describe behavior that is part of a plan and observations that
4//! belong to a run. They do not perform I/O or grant authority. Spec 017
5//! covers runtime-planned batching and bounded memory caching; spec 044 adds
6//! shared calls and the persistent tier.
7
8use serde::{Deserialize, Serialize};
9
10/// Most diagnostics one request's optimization record keeps. Later ones are
11/// dropped and the last kept entry says so.
12pub const MAX_OPTIMIZATION_DIAGNOSTICS: usize = 8;
13
14/// Plan-identified controls for reusable semantic work.
15#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
16#[serde(deny_unknown_fields)]
17pub struct OptimizationPolicy {
18    pub key_schema_version: u32,
19    pub cache_namespace_version: u64,
20    pub admission: OptimizationAdmission,
21    pub expiry: ExpiryBasis,
22    #[serde(default, skip_serializing_if = "Option::is_none")]
23    pub batch: Option<BatchPolicy>,
24    #[serde(default, skip_serializing_if = "Option::is_none")]
25    pub memory_cache: Option<MemoryCachePolicy>,
26    /// Concurrent duplicate suppression (spec 044, 3.2).
27    #[serde(default, skip_serializing_if = "Option::is_none")]
28    pub shared_calls: Option<SharedCallPolicy>,
29    /// The persistent request-level tier behind the host's store (spec 044,
30    /// 3.4).
31    #[serde(default, skip_serializing_if = "Option::is_none")]
32    pub persistent_cache: Option<PersistentCachePolicy>,
33}
34
35/// Bounds of shared calls (spec 044, 3.1 and 3.2).
36#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
37#[serde(deny_unknown_fields)]
38pub struct SharedCallPolicy {
39    /// Shared calls in flight on one runtime.
40    pub max_shared_calls: u32,
41    /// Waiters on one shared call, its leader included.
42    pub max_waiters_per_call: u32,
43    pub allocation: SharedChargeAllocation,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
47#[serde(rename_all = "snake_case")]
48pub enum SharedChargeAllocation {
49    /// The waiter that owns the attempt when it ends settles the whole
50    /// observed charge; every other waiter's share is 0 (spec 044, 3.2.4).
51    FinalOwner,
52}
53
54/// The persistent tier's bounds and the host store's declared terms (spec
55/// 044, 3.1 and 3.4).
56#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
57#[serde(deny_unknown_fields)]
58pub struct PersistentCachePolicy {
59    pub store_contract: StoreContract,
60    pub max_entry_bytes: u64,
61    pub max_scope_bytes: u64,
62    pub max_namespace_bytes: u64,
63    pub ttl_ms: u64,
64    pub on_store_failure: StoreFailurePolicy,
65}
66
67/// The host store's terms, each a declared string recorded as given. Rustev
68/// checks only that each is stated and that the store reports the same
69/// contract; it owns none of them (spec 044, 2.1).
70#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
71#[serde(deny_unknown_fields)]
72pub struct StoreContract {
73    pub schema: String,
74    pub namespace: String,
75    pub durability: String,
76    pub encryption: String,
77    pub atomicity: String,
78    pub conflict: String,
79    pub expiry: String,
80    pub invalidation: String,
81    pub erasure: String,
82}
83
84impl StoreContract {
85    /// Every term in declaration order, with its name.
86    pub fn terms(&self) -> [(&'static str, &str); 9] {
87        [
88            ("schema", &self.schema),
89            ("namespace", &self.namespace),
90            ("durability", &self.durability),
91            ("encryption", &self.encryption),
92            ("atomicity", &self.atomicity),
93            ("conflict", &self.conflict),
94            ("expiry", &self.expiry),
95            ("invalidation", &self.invalidation),
96            ("erasure", &self.erasure),
97        ]
98    }
99}
100
101#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
102#[serde(rename_all = "snake_case")]
103pub enum StoreFailurePolicy {
104    /// A read failure refuses under the policy's `admission`; a write
105    /// failure is recorded and the output kept (spec 044, 3.4.2).
106    FailClosed,
107    /// A failure is a miss and a diagnostic.
108    ContinueWithoutCache,
109}
110
111#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
112#[serde(rename_all = "snake_case")]
113pub enum OptimizationAdmission {
114    /// Refuse when an enabled optimization cannot safely admit the request.
115    Refuse,
116    /// Execute independently without reuse and record the reason.
117    ExecuteIndependent,
118}
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
121#[serde(rename_all = "snake_case")]
122pub enum ExpiryBasis {
123    /// The monotonic clock injected into the runtime. It is process-relative,
124    /// which is sound for a memory cache that dies with the process.
125    InjectedRuntimeTime,
126}
127
128#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
129#[serde(deny_unknown_fields)]
130pub struct BatchPolicy {
131    pub max_members: u32,
132    pub max_canonical_bytes: u64,
133    pub max_members_per_scope: u32,
134    /// Upper bound on time a member may wait for peers. Batches are formed
135    /// within one decision's admission wave, so the runtime waits zero.
136    pub max_queue_delay_ms: u64,
137    pub max_backend_work: u64,
138    pub allocation: BatchChargeAllocation,
139}
140
141/// Deterministic settlement of one observed batch charge across its logical
142/// members. Member order is the runtime-recorded batch index order.
143#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
144#[serde(rename_all = "snake_case")]
145pub enum BatchChargeAllocation {
146    /// Divide integer units evenly. Members with the lowest indices receive
147    /// one extra unit until the remainder is exhausted. The shares are
148    /// allocation evidence; the batch total is settled once, by the charge
149    /// owner, against the summed member reservations.
150    EvenRemainderByMemberIndex,
151}
152
153/// Bounds apply to one plan's cache namespace; no plan's policy evicts
154/// another plan's entries.
155#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
156#[serde(deny_unknown_fields)]
157pub struct MemoryCachePolicy {
158    /// Opt in to eligible acquisition pages (spec 036, 3.13.7).
159    /// Absence disables reuse; true is the only explicit wire value.
160    #[serde(
161        default,
162        skip_serializing_if = "std::ops::Not::not",
163        deserialize_with = "acquisition_pages"
164    )]
165    // Despite its name, constructed_schema emits only a literal-true boolean
166    // schema, matching this opt-in field's wire contract too.
167    #[schemars(schema_with = "crate::acquisition_record::constructed_schema")]
168    pub acquisition_pages: bool,
169    pub max_bytes: u64,
170    pub max_entries: u64,
171    pub max_entry_bytes: u64,
172    pub max_scope_bytes: u64,
173    pub ttl_ms: u64,
174    pub eviction: EvictionOrder,
175}
176
177#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
178#[serde(rename_all = "snake_case")]
179pub enum EvictionOrder {
180    /// First admitted, first evicted. Cache hits do not reorder entries. A
181    /// per-scope bound evicts only that scope's oldest entries.
182    Fifo,
183}
184
185#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
186#[serde(rename_all = "snake_case")]
187pub enum OptimizationMechanism {
188    Independent,
189    Batch,
190    MemoryCache,
191    /// The output of an attempt shared with concurrent equivalent requests
192    /// (spec 044, 3.2.5).
193    SharedCall,
194}
195
196#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
197#[serde(rename_all = "snake_case")]
198pub enum OptimizationOutcome {
199    Bypassed,
200    Miss,
201    Hit,
202    Batched,
203    Refused,
204}
205
206/// Per-logical-request runtime observations. Secret key material and cached
207/// content are deliberately absent.
208#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
209#[serde(deny_unknown_fields)]
210pub struct OptimizationRecord {
211    pub mechanism: OptimizationMechanism,
212    pub key_schema_version: u32,
213    pub outcome: OptimizationOutcome,
214    #[serde(default, skip_serializing_if = "Option::is_none")]
215    pub entry_id: Option<String>,
216    #[serde(default, skip_serializing_if = "Option::is_none")]
217    pub batch_id: Option<String>,
218    #[serde(default, skip_serializing_if = "Option::is_none")]
219    pub batch_member: Option<u32>,
220    /// The attempt that produced the supplied output. For a cache hit it is
221    /// an attempt of an earlier decision, and the record has no attempt of
222    /// its own; replay takes it as the output's origin.
223    #[serde(default, skip_serializing_if = "Option::is_none")]
224    pub source_attempt_id: Option<String>,
225    pub age_ms: u64,
226    pub invalidation_generation: u64,
227    pub charge_owner: bool,
228    pub allocated_charge_units: u64,
229    pub shared_liability_units: u64,
230    /// Every refusal or diagnostic, in the order observed, at most
231    /// [`MAX_OPTIMIZATION_DIAGNOSTICS`].
232    #[serde(default, skip_serializing_if = "Vec::is_empty")]
233    pub diagnostics: Vec<String>,
234    /// This request's part in a shared call (spec 044, 3.2.5).
235    #[serde(default, skip_serializing_if = "Option::is_none")]
236    pub shared: Option<SharedRecord>,
237}
238
239/// One waiter's view of a shared call. Waiter ids are opaque and
240/// runtime-generated, never decision ids, so a tenant-only scope exposes no
241/// principal's decisions to another (spec 044, decision 5).
242#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
243#[serde(deny_unknown_fields)]
244pub struct SharedRecord {
245    pub shared_call_id: String,
246    pub attempt_id: String,
247    pub waiter_id: String,
248    pub role: SharedRole,
249    #[serde(default, skip_serializing_if = "Option::is_none")]
250    pub left: Option<SharedLeave>,
251    #[serde(default, skip_serializing_if = "Option::is_none")]
252    pub handed_off_to: Option<String>,
253    #[serde(default, skip_serializing_if = "Option::is_none")]
254    pub received_from: Option<String>,
255    pub final_owner: bool,
256}
257
258#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
259#[serde(rename_all = "snake_case")]
260pub enum SharedRole {
261    Led,
262    Joined,
263}
264
265/// Why a waiter left before the shared attempt ended.
266#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
267#[serde(rename_all = "snake_case")]
268pub enum SharedLeave {
269    Cancelled,
270    Deadline,
271}
272
273impl OptimizationRecord {
274    /// Append a diagnostic without exceeding the bound.
275    pub fn diagnose(&mut self, diagnostic: impl Into<String>) {
276        match self.diagnostics.len() {
277            n if n + 1 < MAX_OPTIMIZATION_DIAGNOSTICS => self.diagnostics.push(diagnostic.into()),
278            n if n + 1 == MAX_OPTIMIZATION_DIAGNOSTICS => {
279                self.diagnostics.push("further diagnostics dropped".into());
280            }
281            _ => {}
282        }
283    }
284}
285
286fn acquisition_pages<'de, D: serde::Deserializer<'de>>(d: D) -> Result<bool, D::Error> {
287    if !bool::deserialize(d)? {
288        return Err(serde::de::Error::custom(
289            "omit acquisition_pages when disabled",
290        ));
291    }
292    Ok(true)
293}