Skip to main content

harn_kernel/execution/
types.rs

1use std::collections::{BTreeMap, BTreeSet};
2
3use harn_builtin_meta::Ty;
4use serde::{Deserialize, Serialize};
5
6use super::diagnostic;
7use super::resource::{validate_data_value, validate_json_value};
8use crate::{type_contract::manifest_signature_is_portable, Diagnostic};
9
10const MAX_JSON_SAFE_INTEGER: i64 = 9_007_199_254_740_991;
11
12#[derive(Debug, Clone, PartialEq)]
13pub enum DataValue {
14    Nil,
15    Bool(bool),
16    Int(i64),
17    Float(f64),
18    String(String),
19    Bytes(Vec<u8>),
20    List(Vec<DataValue>),
21    Record(BTreeMap<String, DataValue>),
22}
23
24// DataValue's serde representation is intentionally identical to the public
25// JSON seam. Deriving serde here would turn non-finite floats into null and
26// would create a second, incompatible tagged representation for snapshots and
27// capability requests.
28impl Serialize for DataValue {
29    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
30    where
31        S: serde::Serializer,
32    {
33        self.to_json().serialize(serializer)
34    }
35}
36
37impl<'de> Deserialize<'de> for DataValue {
38    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
39    where
40        D: serde::Deserializer<'de>,
41    {
42        let value = serde_json::Value::deserialize(deserializer)?;
43        Self::from_json(value).map_err(|diagnostic| {
44            serde::de::Error::custom(format!("{}: {}", diagnostic.code, diagnostic.message))
45        })
46    }
47}
48
49impl DataValue {
50    pub fn from_json(value: serde_json::Value) -> Result<Self, Diagnostic> {
51        validate_json_value(&value)?;
52        Self::from_json_validated(value)
53    }
54
55    fn from_json_validated(value: serde_json::Value) -> Result<Self, Diagnostic> {
56        Ok(match value {
57            serde_json::Value::Null => Self::Nil,
58            serde_json::Value::Bool(value) => Self::Bool(value),
59            serde_json::Value::Number(value) => {
60                if let Some(value) = value.as_i64() {
61                    Self::Int(value)
62                } else {
63                    Self::Float(value.as_f64().ok_or_else(|| {
64                        diagnostic(
65                            "input_number",
66                            "JSON number is outside Harn's numeric range",
67                        )
68                    })?)
69                }
70            }
71            serde_json::Value::String(value) => Self::String(value),
72            serde_json::Value::Array(values) => Self::List(
73                values
74                    .into_iter()
75                    .map(Self::from_json_validated)
76                    .collect::<Result<_, _>>()?,
77            ),
78            serde_json::Value::Object(entries) => {
79                if entries.len() == 1 {
80                    let (tag, tagged) = entries.iter().next().expect("single entry");
81                    match (tag.as_str(), tagged) {
82                        ("$int", serde_json::Value::String(value)) => {
83                            return value.parse::<i64>().map(Self::Int).map_err(|_| {
84                                diagnostic(
85                                    "input_integer",
86                                    "tagged integer is outside the i64 range",
87                                )
88                            });
89                        }
90                        ("$int", _) => {
91                            return Err(diagnostic(
92                                "input_integer",
93                                "tagged integer must contain a decimal string",
94                            ));
95                        }
96                        ("$float", serde_json::Value::String(value)) => {
97                            return match value.as_str() {
98                                "nan" => Ok(Self::Float(f64::NAN)),
99                                "infinity" => Ok(Self::Float(f64::INFINITY)),
100                                "-infinity" => Ok(Self::Float(f64::NEG_INFINITY)),
101                                _ => Err(diagnostic(
102                                    "input_float",
103                                    "tagged float must be nan, infinity, or -infinity",
104                                )),
105                            };
106                        }
107                        ("$float", _) => {
108                            return Err(diagnostic(
109                                "input_float",
110                                "tagged float must contain a string",
111                            ));
112                        }
113                        ("$bytes", serde_json::Value::Array(values)) => {
114                            let mut bytes = Vec::with_capacity(values.len());
115                            for value in values {
116                                let Some(value) =
117                                    value.as_u64().and_then(|value| u8::try_from(value).ok())
118                                else {
119                                    return Err(diagnostic(
120                                        "input_bytes",
121                                        "tagged bytes must contain integers from 0 through 255",
122                                    ));
123                                };
124                                bytes.push(value);
125                            }
126                            return Ok(Self::Bytes(bytes));
127                        }
128                        ("$bytes", _) => {
129                            return Err(diagnostic(
130                                "input_bytes",
131                                "tagged bytes must contain an integer array",
132                            ));
133                        }
134                        _ => {}
135                    }
136                }
137                Self::Record(
138                    entries
139                        .into_iter()
140                        .map(|(key, value)| Ok((key, Self::from_json_validated(value)?)))
141                        .collect::<Result<_, Diagnostic>>()?,
142                )
143            }
144        })
145    }
146
147    pub fn to_json(&self) -> serde_json::Value {
148        match self {
149            Self::Nil => serde_json::Value::Null,
150            Self::Bool(value) => (*value).into(),
151            Self::Int(value) if value.unsigned_abs() <= MAX_JSON_SAFE_INTEGER as u64 => {
152                (*value).into()
153            }
154            Self::Int(value) => serde_json::json!({"$int": value.to_string()}),
155            Self::Float(value) if value.is_nan() => serde_json::json!({"$float": "nan"}),
156            Self::Float(value) if *value == f64::INFINITY => {
157                serde_json::json!({"$float": "infinity"})
158            }
159            Self::Float(value) if *value == f64::NEG_INFINITY => {
160                serde_json::json!({"$float": "-infinity"})
161            }
162            Self::Float(value) => serde_json::Number::from_f64(*value)
163                .map(serde_json::Value::Number)
164                .expect("finite floats are representable as JSON numbers"),
165            Self::String(value) => value.clone().into(),
166            Self::Bytes(value) => serde_json::json!({"$bytes": value}),
167            Self::List(values) => {
168                serde_json::Value::Array(values.iter().map(Self::to_json).collect())
169            }
170            Self::Record(entries) => serde_json::Value::Object(
171                entries
172                    .iter()
173                    .map(|(key, value)| (key.clone(), value.to_json()))
174                    .collect(),
175            ),
176        }
177    }
178
179    pub(super) fn validate(&self) -> Result<(), Diagnostic> {
180        validate_data_value(self)
181    }
182}
183
184#[derive(Clone, Default, PartialEq, Eq)]
185pub struct GrantSet {
186    grants: BTreeSet<String>,
187    snapshot_key: Option<[u8; 32]>,
188}
189
190impl GrantSet {
191    pub fn pure() -> Self {
192        Self::default()
193    }
194
195    pub fn from_names(names: impl IntoIterator<Item = String>) -> Result<Self, Diagnostic> {
196        let grants = names.into_iter().collect::<BTreeSet<_>>();
197        for grant in &grants {
198            let Some((capability, operation)) = grant.split_once('.') else {
199                return Err(diagnostic(
200                    "invalid_capability_grant",
201                    format!("grant `{grant}` must name one exact capability operation"),
202                ));
203            };
204            let Some(contract) =
205                harn_capability_contracts::capability_method_entry(capability, operation)
206            else {
207                return Err(diagnostic(
208                    "unknown_capability_grant",
209                    format!("grant `{grant}` is not in the canonical capability registry"),
210                ));
211            };
212            if !manifest_signature_is_portable(contract.signature) {
213                return Err(diagnostic(
214                    "unsupported_portable_capability_type",
215                    format!(
216                        "grant `{grant}` uses a capability type outside the portable value contract"
217                    ),
218                ));
219            }
220        }
221        Ok(Self {
222            grants,
223            snapshot_key: None,
224        })
225    }
226
227    /// Install a host-owned key that authenticates resumable snapshots.
228    ///
229    /// The key is never serialized by the kernel. A host that grants a
230    /// suspendable capability must retain the same key until execution ends.
231    pub fn with_snapshot_key(mut self, key: [u8; 32]) -> Self {
232        self.snapshot_key = Some(key);
233        self
234    }
235
236    pub(super) fn snapshot_key(&self) -> Option<&[u8; 32]> {
237        self.snapshot_key.as_ref()
238    }
239
240    pub(super) fn fingerprint(&self) -> [u8; 32] {
241        let mut hasher = blake3::Hasher::new();
242        for grant in &self.grants {
243            hasher.update(grant.as_bytes());
244            hasher.update(&[0]);
245        }
246        *hasher.finalize().as_bytes()
247    }
248
249    pub fn allows(&self, capability: &str, operation: &str) -> bool {
250        self.grants.contains(&format!("{capability}.{operation}"))
251    }
252}
253
254impl std::fmt::Debug for GrantSet {
255    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
256        formatter
257            .debug_struct("GrantSet")
258            .field("grants", &self.grants)
259            .field(
260                "snapshot_key",
261                &self.snapshot_key.is_some().then_some("<redacted>"),
262            )
263            .finish()
264    }
265}
266
267#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
268pub struct CapabilityRequest {
269    pub id: String,
270    pub capability: String,
271    pub operation: String,
272    pub arguments: DataValue,
273    pub expected: ValueShape,
274}
275
276#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
277#[serde(rename_all = "snake_case")]
278pub enum ValueShape {
279    Any,
280    Nil,
281    Bool,
282    Int,
283    Float,
284    String,
285    Bytes,
286    List,
287    Record,
288}
289
290impl ValueShape {
291    pub(super) fn from_type(ty: Ty) -> Self {
292        match ty {
293            Ty::Named("nil") | Ty::Never => Self::Nil,
294            Ty::Named("bool") => Self::Bool,
295            Ty::Named("int") | Ty::LitInt(_) => Self::Int,
296            Ty::Named("float") => Self::Float,
297            Ty::Named("string") | Ty::LitString(_) => Self::String,
298            Ty::Named("bytes") => Self::Bytes,
299            Ty::Named("list") | Ty::Apply("list" | "List", _) => Self::List,
300            Ty::Named("dict" | "record") | Ty::Shape(_) => Self::Record,
301            Ty::Optional(_)
302            | Ty::Any
303            | Ty::Generic(_)
304            | Ty::Named(_)
305            | Ty::Apply(_, _)
306            | Ty::Union(_)
307            | Ty::Fn(_, _)
308            | Ty::SchemaOf(_) => Self::Any,
309        }
310    }
311}
312
313#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
314#[serde(tag = "status", rename_all = "snake_case")]
315pub enum CapabilityResult {
316    Ok {
317        request_id: String,
318        value: DataValue,
319    },
320    Err {
321        request_id: String,
322        code: String,
323        message: String,
324    },
325}
326
327impl CapabilityResult {
328    pub(super) fn request_id(&self) -> &str {
329        match self {
330            Self::Ok { request_id, .. } | Self::Err { request_id, .. } => request_id,
331        }
332    }
333}
334
335#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
336#[serde(tag = "status", rename_all = "snake_case")]
337pub enum Execution {
338    Completed {
339        value: DataValue,
340    },
341    Suspended {
342        request: CapabilityRequest,
343        snapshot: Vec<u8>,
344    },
345    Failed {
346        diagnostic: Diagnostic,
347    },
348}
349
350pub(super) fn value_kind(value: &DataValue) -> &'static str {
351    match value {
352        DataValue::Nil => "nil",
353        DataValue::Bool(_) => "bool",
354        DataValue::Int(_) => "int",
355        DataValue::Float(_) => "float",
356        DataValue::String(_) => "string",
357        DataValue::Bytes(_) => "bytes",
358        DataValue::List(_) => "list",
359        DataValue::Record(_) => "record",
360    }
361}