ferrum_interfaces/vnext/execution/
provider_resource.rs1use super::{
2 canonical_fingerprint, invalid_plan, is_canonical_sha256, ContractVersion,
3 OperationProviderDescriptor, OperationResourceEstimate, ProviderId,
4 ProviderWorkspaceRequirement, ProviderWorkspaceReusePolicy, ProviderWorkspaceScope, Serialize,
5 VNextError,
6};
7
8#[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}