Skip to main content

aion_package/
version.rs

1//! Canonical workflow version record produced from a loaded `Package`.
2
3use serde::{Deserialize, Serialize};
4
5use crate::{ContentHash, DeclaredActivity};
6
7/// Cross-system record describing a validated workflow package version.
8///
9/// The record is produced from a loaded [`crate::Package`] after archive
10/// integrity has been verified. It carries the logical entry module and
11/// recomputed content hash alongside the manifest-declared activities and
12/// schemas. Stores that cannot depend on `aion-package` can persist the textual
13/// content-hash form, but this is the canonical typed record for consumers that
14/// do depend on this crate.
15#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
16pub struct WorkflowVersion {
17    /// Logical workflow entry module before deployed-name namespacing.
18    pub entry_module: String,
19    /// Recomputed SHA-256 content hash of the canonical beam set.
20    pub content_hash: ContentHash,
21    /// Activity types declared by the workflow manifest.
22    pub activities: Vec<DeclaredActivity>,
23    /// JSON schema accepted by the workflow entry point.
24    pub input_schema: serde_json::Value,
25    /// JSON schema produced by the workflow entry point.
26    pub output_schema: serde_json::Value,
27}
28
29#[cfg(test)]
30mod tests {
31    use std::{collections::BTreeMap, time::Duration};
32
33    use serde_json::json;
34
35    use crate::{
36        BeamModule, BeamSet, CURRENT_FORMAT_VERSION, DeclaredActivity, Manifest, ManifestVersion,
37        Package, PackageBuilder, PackageError,
38    };
39
40    fn sample_manifest() -> Manifest {
41        Manifest {
42            entry_module: "workflow/order".to_owned(),
43            entry_function: "run".to_owned(),
44            input_schema: json!({
45                "type": "object",
46                "required": ["order_id"],
47                "properties": {
48                    "order_id": { "type": "string" }
49                }
50            }),
51            output_schema: json!({
52                "type": "object",
53                "required": ["status"],
54                "properties": {
55                    "status": { "type": "string" }
56                }
57            }),
58            timeout: Duration::from_secs(30),
59            activities: vec![
60                DeclaredActivity {
61                    activity_type: "charge_card".to_owned(),
62                },
63                DeclaredActivity {
64                    activity_type: "send_receipt".to_owned(),
65                },
66            ],
67            version: ManifestVersion::new("placeholder"),
68            format_version: CURRENT_FORMAT_VERSION,
69        }
70    }
71
72    fn sample_beams() -> Result<BeamSet, PackageError> {
73        BeamSet::new(vec![
74            BeamModule::new("workflow/support", vec![4, 5, 6]),
75            BeamModule::new("workflow/order", vec![1, 2, 3]),
76        ])
77    }
78
79    #[test]
80    fn loaded_package_produces_matching_version_record() -> Result<(), PackageError> {
81        let bytes = PackageBuilder::with_source(
82            sample_manifest(),
83            sample_beams()?,
84            BTreeMap::from([(
85                "workflow/order".to_owned(),
86                b"pub fn run() { Nil }".to_vec(),
87            )]),
88        )
89        .write_to_bytes()?;
90        let package = Package::load_from_bytes(bytes)?;
91
92        let record = package.version_record();
93
94        assert_eq!(record.entry_module, package.manifest().entry_module);
95        assert_eq!(record.content_hash, package.content_hash().clone());
96        assert_eq!(record.activities, package.manifest().activities);
97        assert_eq!(record.input_schema, package.manifest().input_schema);
98        assert_eq!(record.output_schema, package.manifest().output_schema);
99        Ok(())
100    }
101}