OXYGENHUB
# e0918b887850 oxygenflow / crates / flow-core / src / receipt.rs
3.5 KiB · Rust · last changed OxygenFlow M0: pipeline parser and canonical job graph, receipts, the runner API types, flow-init, the Firecracker tier, flow check, run, rootfs import and runner 1 month ago source blame history raw
e0918b88 Andy McCall 1
//! Execution receipts (spec §6.1), written by the host after a job ends,
2
//! never by the job itself.
3
4
use crate::canonical::{object_hash, Hash};
5
use oxy_core::cbor::{encode, Value};
6
use serde::{Deserialize, Serialize};
7
8
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
9
pub struct Receipt {
10
    pub job_name: String,
11
    #[serde(with = "hex32")]
12
    pub pipeline_hash: Hash,
13
    #[serde(with = "hex32")]
14
    pub scope_tree_hash: Hash,
15
    #[serde(with = "hex32s")]
16
    pub inputs: Vec<Hash>,
17
    #[serde(with = "hex32")]
18
    pub environment: Hash,
19
    #[serde(with = "hex32s")]
20
    pub outputs: Vec<Hash>,
21
    pub exit_code: u32,
22
    pub duration_ms: u64,
23
}
24
25
impl Receipt {
26
    pub fn encode(&self) -> Vec<u8> {
27
        let refs = |v: &[Hash]| Value::Array(v.iter().map(|h| Value::Bytes(h.to_vec())).collect());
28
        encode(&Value::map([
29
            (0, Value::Uint(1)),
30
            (1, Value::Text(self.job_name.clone())),
31
            (2, Value::Bytes(self.pipeline_hash.to_vec())),
32
            (3, Value::Bytes(self.scope_tree_hash.to_vec())),
33
            (4, refs(&self.inputs)),
34
            (5, Value::Bytes(self.environment.to_vec())),
35
            (6, refs(&self.outputs)),
36
            (7, Value::Uint(self.exit_code as u64)),
37
            (8, Value::Uint(self.duration_ms)),
38
        ]))
39
    }
40
41
    /// `BLAKE3("oxy1 receipt " || len || 0x00 || body)`.
42
    pub fn hash(&self) -> Hash {
43
        object_hash("receipt", &self.encode())
44
    }
45
}
46
47
/// What a job ran in (the receipt's `environment`): the pinned pieces of
48
/// the isolation tier, the rootfs, argv and env.
49
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
50
pub struct Environment {
51
    pub runtime: String,
52
    /// Hashes of the tier's pinned parts (hypervisor, kernel, init), by name.
53
    pub parts: Vec<(String, String)>,
54
    pub rootfs: String,
55
    pub argv: Vec<String>,
56
    pub env: Vec<(String, String)>,
57
}
58
59
impl Environment {
60
    pub fn hash(&self) -> Hash {
61
        let t = |s: &str| Value::Text(s.to_string());
62
        let pairs = |v: &[(String, String)]| Value::Array(v.iter().map(|(a, b)| Value::Array(vec![t(a), t(b)])).collect());
63
        let body = encode(&Value::map([
64
            (0, t(&self.runtime)),
65
            (1, pairs(&self.parts)),
66
            (2, t(&self.rootfs)),
67
            (3, Value::Array(self.argv.iter().map(|a| t(a)).collect())),
68
            (4, pairs(&self.env)),
69
        ]));
70
        object_hash("environment", &body)
71
    }
72
}
73
74
mod hex32 {
75
    use serde::{Deserialize, Deserializer, Serializer};
76
    pub fn serialize<S: Serializer>(h: &[u8; 32], s: S) -> Result<S::Ok, S::Error> {
77
        s.serialize_str(&hex::encode(h))
78
    }
79
    pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<[u8; 32], D::Error> {
80
        let s = String::deserialize(d)?;
81
        let v = hex::decode(&s).map_err(serde::de::Error::custom)?;
82
        v.try_into().map_err(|_| serde::de::Error::custom("expected 32 bytes"))
83
    }
84
}
85
86
mod hex32s {
87
    use serde::{Deserialize, Deserializer, Serializer};
88
    pub fn serialize<S: Serializer>(v: &[[u8; 32]], s: S) -> Result<S::Ok, S::Error> {
89
        s.collect_seq(v.iter().map(hex::encode))
90
    }
91
    pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<[u8; 32]>, D::Error> {
92
        let v = Vec::<String>::deserialize(d)?;
93
        v.into_iter()
94
            .map(|s| {
95
                let b = hex::decode(&s).map_err(serde::de::Error::custom)?;
96
                b.try_into().map_err(|_| serde::de::Error::custom("expected 32 bytes"))
97
            })
98
            .collect()
99
    }
100
}