Skip to main content

gatehouse_runtime_protocol/lifecycle/journal/
resolution.rs

1//! Immutable pre-create plan and image evidence; no execution authority itself.
2use super::*;
3use crate::lifecycle::wire::Entry;
4#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
5#[serde(deny_unknown_fields)]
6pub struct CreatePlan {
7    pub name: String,
8    pub labels: Vec<Entry>,
9    pub environment: Vec<Entry>,
10    pub container_port: u16,
11    /// Podman 5.7 only; omitted from old Docker plans and wire bytes.
12    #[serde(default, skip_serializing_if = "Option::is_none")]
13    pub command: Option<Vec<String>>,
14}
15impl CreatePlan {
16    pub fn validate(&self, resolve: &Request) -> Result<()> {
17        resolve.validate()?;
18        if !matches!(resolve.operation, Operation::ResolveImage { .. })
19            || resolve.binding.generation != 1
20        {
21            return Err(Invalid("reservation requires initial image resolution"));
22        }
23        self.request(
24            resolve,
25            &format!("sha256:{}", "0".repeat(64)),
26            &resolve.binding.invocation,
27        )?
28        .validate()
29    }
30    fn request(&self, resolve: &Request, image_id: &str, invocation: &str) -> Result<Request> {
31        let mut request = resolve.clone();
32        request.operation = Operation::Create {
33            name: self.name.clone(),
34            image_id: image_id.into(),
35            labels: self.labels.clone(),
36            environment: self.environment.clone(),
37            container_port: self.container_port,
38            command: self.command.clone(),
39        };
40        request.binding.generation = 2;
41        request.binding.invocation = invocation.into();
42        request.binding.operation_key = request.operation_key()?;
43        Ok(request)
44    }
45}
46#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
47#[serde(deny_unknown_fields)]
48pub(super) struct ResolutionProof {
49    pub request: Request,
50    pub plan: CreatePlan,
51    pub response: Response,
52}
53impl ResolutionProof {
54    pub fn create(&self, invocation: &str) -> Result<Request> {
55        self.plan.validate(&self.request)?;
56        self.response.validate(&self.request)?;
57        let Outcome::Image { image_id, .. } = &self.response.outcome else {
58            return Err(Invalid("image resolution not proven"));
59        };
60        if invocation == self.request.binding.invocation {
61            return Err(Invalid("create invocation must be fresh"));
62        }
63        let create = self.plan.request(&self.request, image_id, invocation)?;
64        create.validate()?;
65        Ok(create)
66    }
67    pub fn validate_create(&self, create: &Request) -> Result<()> {
68        if self.create(&create.binding.invocation)? != *create {
69            return Err(Invalid("create differs from reserved plan/image evidence"));
70        }
71        Ok(())
72    }
73}