OXYGENHUB
◆ v0.5.3 oxygenflow / crates / flow-core / src / receipt.rs
4.2 KiB · Rust · last changed Runners sign their receipts 1 month ago source blame history raw
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
    /// The same receipt as an Oxygen object (Oxygen spec §14.6), which a
47
    /// runner signs and a repository keeps.
48
    pub fn to_oxy(&self) -> oxy_core::receipt::Receipt {
49
        use oxy_core::hash::ObjectRef;
50
        oxy_core::receipt::Receipt {
51
            job_name: self.job_name.clone(),
52
            pipeline_hash: ObjectRef(self.pipeline_hash),
53
            scope_tree_hash: ObjectRef(self.scope_tree_hash),
54
            inputs: self.inputs.iter().map(|h| ObjectRef(*h)).collect(),
55
            environment: ObjectRef(self.environment),
56
            outputs: self.outputs.iter().map(|h| ObjectRef(*h)).collect(),
57
            exit_code: self.exit_code,
58
            duration_ms: self.duration_ms,
59
        }
60
    }
61
}
62
63
/// What a job ran in (the receipt's `environment`): the pinned pieces of
64
/// the isolation tier, the rootfs, argv and env.
65
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
66
pub struct Environment {
67
    pub runtime: String,
68
    /// Hashes of the tier's pinned parts (hypervisor, kernel, init), by name.
69
    pub parts: Vec<(String, String)>,
70
    pub rootfs: String,
71
    pub argv: Vec<String>,
72
    pub env: Vec<(String, String)>,
73
}
74
75
impl Environment {
76
    pub fn hash(&self) -> Hash {
77
        let t = |s: &str| Value::Text(s.to_string());
78
        let pairs = |v: &[(String, String)]| Value::Array(v.iter().map(|(a, b)| Value::Array(vec![t(a), t(b)])).collect());
79
        let body = encode(&Value::map([
80
            (0, t(&self.runtime)),
81
            (1, pairs(&self.parts)),
82
            (2, t(&self.rootfs)),
83
            (3, Value::Array(self.argv.iter().map(|a| t(a)).collect())),
84
            (4, pairs(&self.env)),
85
        ]));
86
        object_hash("environment", &body)
87
    }
88
}
89
90
mod hex32 {
91
    use serde::{Deserialize, Deserializer, Serializer};
92
    pub fn serialize<S: Serializer>(h: &[u8; 32], s: S) -> Result<S::Ok, S::Error> {
93
        s.serialize_str(&hex::encode(h))
94
    }
95
    pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<[u8; 32], D::Error> {
96
        let s = String::deserialize(d)?;
97
        let v = hex::decode(&s).map_err(serde::de::Error::custom)?;
98
        v.try_into().map_err(|_| serde::de::Error::custom("expected 32 bytes"))
99
    }
100
}
101
102
mod hex32s {
103
    use serde::{Deserialize, Deserializer, Serializer};
104
    pub fn serialize<S: Serializer>(v: &[[u8; 32]], s: S) -> Result<S::Ok, S::Error> {
105
        s.collect_seq(v.iter().map(hex::encode))
106
    }
107
    pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<[u8; 32]>, D::Error> {
108
        let v = Vec::<String>::deserialize(d)?;
109
        v.into_iter()
110
            .map(|s| {
111
                let b = hex::decode(&s).map_err(serde::de::Error::custom)?;
112
                b.try_into().map_err(|_| serde::de::Error::custom("expected 32 bytes"))
113
            })
114
            .collect()
115
    }
116
}