Skip to main content

runmat_execution_artifact/bundle/
manifest.rs

1use std::collections::BTreeSet;
2
3use runmat_execution::resource::Capability;
4use runmat_execution::{Digest, ProgramRevision};
5use serde::{Deserialize, Serialize};
6
7use crate::{
8    ArtifactError, ArtifactResult, BundleCodeClosure, LogicalObject, ObjectDescriptor,
9    ProgramArtifact, ProgramBuildRecipe,
10};
11
12pub const EXECUTION_BUNDLE_SCHEMA_VERSION: u16 = 3;
13
14#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
15#[serde(deny_unknown_fields)]
16pub struct ProjectRevisionRecord {
17    pub graph_digest: Digest,
18    pub source_digest: Digest,
19}
20
21#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
22#[serde(deny_unknown_fields)]
23pub struct BundleCallable {
24    pub owner_identity: String,
25    pub qualified_name: String,
26    pub source_digest: Digest,
27}
28
29#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
30#[serde(deny_unknown_fields)]
31pub struct BuildResourceDeclaration {
32    pub cpu_millicores: u32,
33    pub memory_bytes: u64,
34    pub scratch_bytes: u64,
35}
36
37#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
38#[serde(deny_unknown_fields)]
39pub struct BundleManifest {
40    pub schema_version: u16,
41    pub program_revision: ProgramRevision,
42    pub project_revision: ProjectRevisionRecord,
43    pub code_closure: BundleCodeClosure,
44    pub sources: Vec<ObjectDescriptor>,
45    pub callables: Vec<BundleCallable>,
46    pub recipes: Vec<ProgramBuildRecipe>,
47    pub artifacts: Vec<ProgramArtifact>,
48    pub requested_capabilities: BTreeSet<Capability>,
49    pub resources: BuildResourceDeclaration,
50    pub portable_environment: Vec<(String, String)>,
51}
52
53#[derive(Clone, Debug, Eq, PartialEq)]
54pub struct ExecutionBundle {
55    pub manifest: BundleManifest,
56    pub objects: Vec<LogicalObject>,
57}
58
59impl ExecutionBundle {
60    pub fn validate(&self) -> ArtifactResult<()> {
61        super::validator::validate(self)
62    }
63
64    pub fn identity(&self) -> ArtifactResult<Digest> {
65        self.validate()?;
66        super::validator::identity(&self.manifest)
67    }
68
69    /// Rebind the bundle's logical source paths to one host-owned storage root.
70    ///
71    /// The returned handoff is the same frozen package graph and source catalog
72    /// that the submitter used. Only the physical access paths change.
73    pub fn project_handoff_at(
74        &self,
75        root: &std::path::Path,
76    ) -> ArtifactResult<runmat_package::FrozenProjectHandoff> {
77        self.validate()?;
78        let BundleCodeClosure::SourceProject { handoff } = &self.manifest.code_closure else {
79            return Err(ArtifactError::Invalid(
80                "compiled execution bundle has no source project to materialize".into(),
81            ));
82        };
83        let mut handoff = handoff.clone();
84        handoff.project.workspace_root = root.to_path_buf();
85        handoff.project.manifest_path = root.join("runmat.toml");
86        for path in handoff.project.access_paths.values_mut() {
87            let relative = runmat_package::NormalizedRelativePath::new(path.as_path())
88                .map_err(|error| crate::ArtifactError::Invalid(error.to_string()))?;
89            *path = root.join(relative.as_str());
90        }
91        handoff
92            .validate()
93            .map_err(|error| crate::ArtifactError::Invalid(error.to_string()))?;
94        Ok(handoff)
95    }
96
97    pub fn requires_source_project(&self) -> bool {
98        matches!(
99            self.manifest.code_closure,
100            BundleCodeClosure::SourceProject { .. }
101        )
102    }
103}