Skip to main content

ferrum_interfaces/vnext/execution/
provider_resource.rs

1use super::{
2    canonical_fingerprint, invalid_plan, is_canonical_sha256, ContractVersion,
3    OperationProviderDescriptor, OperationResourceEstimate, ProviderId,
4    ProviderWorkspaceRequirement, ProviderWorkspaceReusePolicy, ProviderWorkspaceScope, Serialize,
5    VNextError,
6};
7
8/// Trusted output from the selected provider's shape/attribute-specific
9/// resource estimator. The core binds it to the exact estimator input and
10/// selected provider before the values can enter an executable plan.
11#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
12#[serde(deny_unknown_fields)]
13pub struct ProviderResourcePlan {
14    pub(super) provider_id: ProviderId,
15    pub(super) estimator_id: String,
16    pub(super) estimator_version: ContractVersion,
17    pub(super) estimator_implementation_fingerprint: String,
18    pub(super) estimator_input_fingerprint: String,
19    pub(super) estimate_fingerprint: String,
20    pub(super) value_alignment_bytes: u64,
21    pub(super) scratch: Option<ProviderWorkspaceRequirement>,
22    pub(super) binding: Option<ProviderWorkspaceRequirement>,
23    pub(super) persistent: Option<ProviderWorkspaceRequirement>,
24}
25
26impl ProviderResourcePlan {
27    #[allow(clippy::too_many_arguments)]
28    pub(super) fn from_provider_output(
29        descriptor: &OperationProviderDescriptor,
30        estimator_input_fingerprint: &str,
31        estimate: OperationResourceEstimate,
32    ) -> Result<Self, VNextError> {
33        if estimate.estimator_id() != descriptor.resource_estimator_id()
34            || estimate.estimator_version() != descriptor.resource_estimator_version()
35            || estimate.estimator_implementation_fingerprint()
36                != descriptor.resource_estimator_implementation_fingerprint()
37            || estimate.claimed_input_fingerprint() != estimator_input_fingerprint
38        {
39            return Err(invalid_plan(
40                "provider raw resource estimate identity or input claim differs from the selected registered implementation",
41            ));
42        }
43        let mut plan = Self {
44            provider_id: descriptor.provider_id().clone(),
45            estimator_id: descriptor.resource_estimator_id().to_owned(),
46            estimator_version: descriptor.resource_estimator_version(),
47            estimator_implementation_fingerprint: descriptor
48                .resource_estimator_implementation_fingerprint()
49                .to_owned(),
50            estimator_input_fingerprint: estimator_input_fingerprint.to_owned(),
51            estimate_fingerprint: String::new(),
52            value_alignment_bytes: estimate.value_alignment_bytes(),
53            scratch: estimate.scratch().cloned(),
54            binding: estimate.binding().cloned(),
55            persistent: estimate.persistent().cloned(),
56        };
57        plan.validate_fields()?;
58        plan.estimate_fingerprint = plan.compute_estimate_fingerprint()?;
59        plan.validate_shape()?;
60        Ok(plan)
61    }
62
63    pub(super) fn validate_fields(&self) -> Result<(), VNextError> {
64        if self.estimator_id.is_empty()
65            || self.estimator_id.len() > 160
66            || !self.estimator_id.bytes().all(|byte| {
67                byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'-' | b':' | b'/')
68            })
69            || self.estimator_version.major == 0
70            || !is_canonical_sha256(&self.estimator_implementation_fingerprint)
71            || !is_canonical_sha256(&self.estimator_input_fingerprint)
72            || self.value_alignment_bytes == 0
73            || !self.value_alignment_bytes.is_power_of_two()
74            || self.scratch.as_ref().is_some_and(|workspace| {
75                workspace.scope != ProviderWorkspaceScope::Invocation
76                    || workspace.reuse_policy == ProviderWorkspaceReusePolicy::Preserve
77                    || ProviderWorkspaceRequirement::from_formula(
78                        workspace.size_formula.clone(),
79                        workspace.alignment_bytes,
80                        workspace.scope,
81                        workspace.reuse_policy,
82                        workspace.storage.clone(),
83                    )
84                    .is_err()
85            })
86            || self.binding.as_ref().is_some_and(|workspace| {
87                workspace.scope != ProviderWorkspaceScope::Invocation
88                    || workspace.reuse_policy != ProviderWorkspaceReusePolicy::OverwriteBeforeRead
89                    || ProviderWorkspaceRequirement::from_formula(
90                        workspace.size_formula.clone(),
91                        workspace.alignment_bytes,
92                        workspace.scope,
93                        workspace.reuse_policy,
94                        workspace.storage.clone(),
95                    )
96                    .is_err()
97            })
98            || self.persistent.as_ref().is_some_and(|workspace| {
99                workspace.scope == ProviderWorkspaceScope::Invocation
100                    || workspace.reuse_policy != ProviderWorkspaceReusePolicy::Preserve
101                    || ProviderWorkspaceRequirement::from_formula(
102                        workspace.size_formula.clone(),
103                        workspace.alignment_bytes,
104                        workspace.scope,
105                        workspace.reuse_policy,
106                        workspace.storage.clone(),
107                    )
108                    .is_err()
109            })
110        {
111            return Err(invalid_plan(
112                "provider resource estimate identity, alignment, scope, or reuse policy is invalid",
113            ));
114        }
115        Ok(())
116    }
117
118    pub(super) fn validate_shape(&self) -> Result<(), VNextError> {
119        self.validate_fields()?;
120        if !is_canonical_sha256(&self.estimate_fingerprint)
121            || self.estimate_fingerprint != self.compute_estimate_fingerprint()?
122        {
123            return Err(invalid_plan(
124                "provider resource estimate fingerprint does not match its typed fields",
125            ));
126        }
127        Ok(())
128    }
129
130    pub(super) fn compute_estimate_fingerprint(&self) -> Result<String, VNextError> {
131        canonical_fingerprint(
132            &ProviderEstimateFingerprintMaterial {
133                provider_id: &self.provider_id,
134                estimator_id: &self.estimator_id,
135                estimator_version: self.estimator_version,
136                estimator_implementation_fingerprint: &self.estimator_implementation_fingerprint,
137                estimator_input_fingerprint: &self.estimator_input_fingerprint,
138                value_alignment_bytes: self.value_alignment_bytes,
139                scratch: &self.scratch,
140                binding: &self.binding,
141                persistent: &self.persistent,
142            },
143            "fingerprint provider resource estimate",
144        )
145    }
146
147    pub fn provider_id(&self) -> &ProviderId {
148        &self.provider_id
149    }
150
151    pub fn estimator_id(&self) -> &str {
152        &self.estimator_id
153    }
154
155    pub const fn estimator_version(&self) -> ContractVersion {
156        self.estimator_version
157    }
158
159    pub fn estimator_implementation_fingerprint(&self) -> &str {
160        &self.estimator_implementation_fingerprint
161    }
162
163    pub fn estimator_input_fingerprint(&self) -> &str {
164        &self.estimator_input_fingerprint
165    }
166
167    pub fn estimate_fingerprint(&self) -> &str {
168        &self.estimate_fingerprint
169    }
170
171    pub const fn value_alignment_bytes(&self) -> u64 {
172        self.value_alignment_bytes
173    }
174
175    pub fn scratch(&self) -> Option<&ProviderWorkspaceRequirement> {
176        self.scratch.as_ref()
177    }
178
179    pub fn binding(&self) -> Option<&ProviderWorkspaceRequirement> {
180        self.binding.as_ref()
181    }
182
183    pub fn persistent(&self) -> Option<&ProviderWorkspaceRequirement> {
184        self.persistent.as_ref()
185    }
186}
187
188#[derive(Serialize)]
189pub(super) struct ProviderEstimateFingerprintMaterial<'a> {
190    pub(super) provider_id: &'a ProviderId,
191    pub(super) estimator_id: &'a str,
192    pub(super) estimator_version: ContractVersion,
193    pub(super) estimator_implementation_fingerprint: &'a str,
194    pub(super) estimator_input_fingerprint: &'a str,
195    pub(super) value_alignment_bytes: u64,
196    pub(super) scratch: &'a Option<ProviderWorkspaceRequirement>,
197    pub(super) binding: &'a Option<ProviderWorkspaceRequirement>,
198    pub(super) persistent: &'a Option<ProviderWorkspaceRequirement>,
199}