runmat_execution/executable/
manifest.rs1use std::collections::BTreeSet;
2
3use serde::{Deserialize, Serialize};
4
5use super::{
6 component::validate_component_descriptors, ExecutableComponentDescriptor,
7 ExecutableComponentRevisions, ExecutableIdentity, ExecutableOptionalSection,
8 ExecutableSectionSupport,
9};
10use crate::{ContractError, Digest};
11use runmat_types::{
12 CapabilityRequirement, CapabilitySet, InteropManifest, ParallelManifest, RegionContract,
13};
14
15pub const EXECUTABLE_UNIT_SCHEMA_VERSION: u16 = crate::schema::EXECUTABLE_UNIT_SCHEMA_V3;
16
17#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
18#[serde(deny_unknown_fields)]
19pub struct ExecutableUnitManifest {
20 pub schema_version: u16,
21 pub identity: ExecutableIdentity,
22 pub revisions: ExecutableComponentRevisions,
23 pub components: Vec<ExecutableComponentDescriptor>,
24 pub capabilities: CapabilitySet,
25 pub regions: Vec<RegionContract>,
26 pub interop: InteropManifest,
27 pub parallel: ParallelManifest,
28 pub optional_sections: Vec<ExecutableOptionalSection>,
29}
30
31impl ExecutableUnitManifest {
32 pub fn validate(&self) -> Result<(), ContractError> {
33 if self.schema_version != EXECUTABLE_UNIT_SCHEMA_VERSION {
34 return Err(ContractError::UnsupportedSchema {
35 actual: self.schema_version,
36 supported: EXECUTABLE_UNIT_SCHEMA_VERSION,
37 });
38 }
39 self.identity.validate()?;
40 self.revisions.validate()?;
41 validate_component_descriptors(&self.components, &self.revisions)?;
42 if self.identity.program.catalog_fingerprint() != &self.revisions.catalog_fingerprint {
43 return Err(ContractError::invalid(
44 "executable.revisions.catalog_fingerprint",
45 "must match the immutable program revision",
46 ));
47 }
48 if self.regions.windows(2).any(|pair| pair[0].id >= pair[1].id) {
49 return Err(ContractError::invalid(
50 "executable.regions",
51 "regions must be sorted and unique by identity",
52 ));
53 }
54 for region in &self.regions {
55 region.validate().map_err(|error| {
56 ContractError::invalid(
57 "executable.regions",
58 format!("{}: {}", error.path, error.message),
59 )
60 })?;
61 }
62 self.interop.validate().map_err(|error| {
63 ContractError::invalid(
64 "executable.interop",
65 format!("{}: {}", error.path, error.message),
66 )
67 })?;
68 self.parallel.validate().map_err(|error| {
69 ContractError::invalid(
70 "executable.parallel",
71 format!("{}: {}", error.path, error.message),
72 )
73 })?;
74 let region_ids = self
75 .regions
76 .iter()
77 .map(|region| region.id)
78 .collect::<BTreeSet<_>>();
79 if self
80 .parallel
81 .parfor_regions
82 .iter()
83 .map(|region| region.id.0)
84 .chain(self.parallel.spmd_regions.iter().map(|region| region.id.0))
85 .any(|region| !region_ids.contains(®ion))
86 {
87 return Err(ContractError::invalid(
88 "executable.parallel.regions",
89 "every parallel construct must name a declared region contract",
90 ));
91 }
92 if !self.interop.foreign_types.is_empty()
93 && !self
94 .capabilities
95 .0
96 .contains(&CapabilityRequirement::ForeignRuntime)
97 {
98 return Err(ContractError::invalid(
99 "executable.capabilities",
100 "foreign requirements need the foreign-runtime capability",
101 ));
102 }
103 if (!self.parallel.parfor_regions.is_empty() || !self.parallel.spmd_regions.is_empty())
104 && !self
105 .capabilities
106 .0
107 .contains(&CapabilityRequirement::ParallelRuntime)
108 {
109 return Err(ContractError::invalid(
110 "executable.capabilities",
111 "parallel constructs need the parallel-runtime capability",
112 ));
113 }
114 if (!self.parallel.distributed_values.is_empty() || !self.parallel.collectives.is_empty())
115 && !self
116 .capabilities
117 .0
118 .contains(&CapabilityRequirement::DistributedRuntime)
119 {
120 return Err(ContractError::invalid(
121 "executable.capabilities",
122 "distributed constructs need the distributed-runtime capability",
123 ));
124 }
125 if self
126 .optional_sections
127 .windows(2)
128 .any(|pair| pair[0].name >= pair[1].name)
129 {
130 return Err(ContractError::invalid(
131 "executable.optional_sections",
132 "sections must be sorted and unique by name",
133 ));
134 }
135 for section in &self.optional_sections {
136 section.validate()?;
137 }
138 Ok(())
139 }
140
141 pub fn validate_for(&self, support: &ExecutableSectionSupport) -> Result<(), ContractError> {
142 self.validate()?;
143 for section in &self.optional_sections {
144 support.validate_section(section)?;
145 }
146 Ok(())
147 }
148
149 pub fn canonical_bytes(&self) -> Result<Vec<u8>, ContractError> {
150 self.validate()?;
151 serde_json::to_vec(self)
152 .map_err(|error| ContractError::invalid("executable.manifest", error.to_string()))
153 }
154
155 pub fn from_canonical_bytes(bytes: &[u8]) -> Result<Self, ContractError> {
156 let manifest = serde_json::from_slice::<Self>(bytes)
157 .map_err(|error| ContractError::invalid("executable.manifest", error.to_string()))?;
158 manifest.validate()?;
159 if manifest.canonical_bytes()? != bytes {
160 return Err(ContractError::invalid(
161 "executable.manifest",
162 "encoding is valid JSON but not canonical RunMat JSON",
163 ));
164 }
165 Ok(manifest)
166 }
167
168 pub fn cache_key(&self) -> Result<Digest, ContractError> {
169 self.canonical_bytes().map(Digest::sha256)
170 }
171}