Skip to main content

ic_backup/model/operation_plan/context/
mod.rs

1//! Canonical declared network, caller and release context; no fresh permission.
2
3use super::OperationPlanError;
4use crate::model::artifacts::canonical_hash;
5use crate::model::attempt_journal::OperationBindingRecord;
6use serde::{Deserialize, Serialize};
7
8/// Passive integration-owned exact execution context declarations.
9#[derive(Clone, Debug)]
10pub struct PlanContextRequest {
11    /// Qualified network fingerprint digest, not an endpoint label.
12    pub network: String,
13    /// Exact selected caller principal; credentials are excluded.
14    pub caller: String,
15    /// Exact opaque release evidence digest supplied by its owner.
16    pub release: String,
17}
18/// Immutable canonical declared context shared by every operation in one plan.
19#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
20#[serde(try_from = "ContextFields")]
21pub struct PlanContextRecord {
22    network: String,
23    caller: String,
24    release: String,
25}
26#[derive(Deserialize)]
27#[serde(deny_unknown_fields)]
28struct ContextFields {
29    network: String,
30    caller: String,
31    release: String,
32}
33impl TryFrom<ContextFields> for PlanContextRecord {
34    type Error = OperationPlanError;
35    fn try_from(fields: ContextFields) -> Result<Self, Self::Error> {
36        Self::new(&PlanContextRequest {
37            network: fields.network,
38            caller: fields.caller,
39            release: fields.release,
40        })
41    }
42}
43impl PlanContextRecord {
44    // Complete context equality has one ordered field comparison owner. Source
45    // network/release-only checks intentionally retain their separate contract.
46    pub(crate) fn binding_mismatch(
47        &self,
48        binding: &OperationBindingRecord,
49    ) -> Option<&'static str> {
50        if self.network() != binding.network() {
51            Some("network")
52        } else if self.caller() != binding.caller() {
53            Some("caller")
54        } else if self.release() != binding.release() {
55            Some("release")
56        } else {
57            None
58        }
59    }
60    /// Canonicalize exact declarations without IO or fresh authorization.
61    ///
62    /// # Errors
63    /// Rejects malformed principal or SHA-256 digest fields.
64    pub fn new(request: &PlanContextRequest) -> Result<Self, OperationPlanError> {
65        Ok(Self {
66            network: canonical_hash(&request.network)?,
67            caller: crate::model::principal::canonical_text(&request.caller)
68                .ok_or(OperationPlanError::InvalidPrincipal("caller"))?,
69            release: canonical_hash(&request.release)?,
70        })
71    }
72    /// Read the declared canonical network fingerprint.
73    #[must_use]
74    pub fn network(&self) -> &str {
75        &self.network
76    }
77    /// Read the exact canonical selected caller.
78    #[must_use]
79    pub fn caller(&self) -> &str {
80        &self.caller
81    }
82    /// Read the integration-owned release evidence digest.
83    #[must_use]
84    pub fn release(&self) -> &str {
85        &self.release
86    }
87}