Skip to main content

phoxal_bundle/
lib.rs

1//! The persisted runtime bundle boundary.
2//!
3//! `phoxal-manifest` compiles authored YAML/URDF into canonical model facts;
4//! this crate owns the artifact that remains after that source tree is gone.
5//! A runtime process reads only `runtime.json`, the indexed files below
6//! `assets/`, and the selected binary below `bin/`. It never invokes the source
7//! compiler and never discovers a participant from a catalog.
8//!
9//! ```text
10//! <bundle>/
11//! ├── runtime.json
12//! ├── assets/
13//! └── bin/
14//! ```
15
16pub use phoxal_runtime_contract::identity::ParticipantArtifactId;
17
18mod path;
19pub use path::{BundlePath, BundlePathError, DigestError, Sha256Digest};
20mod asset;
21pub use asset::{AssetIndex, AssetRecord, ParticipantAssets};
22mod reader;
23pub use reader::{ParticipantBundle, ParticipantRuntimeInputs, RuntimeBundle};
24mod error;
25pub use error::{BundleError, DocumentError, SelectionError};
26mod writer;
27pub use writer::BundleWriter;
28mod fs;
29pub(crate) use fs::{
30    BundleRoot, copy_executable_source, create_staging_root, ensure_staging_directory,
31    open_bundle_file, open_executable_source, prepare_publish_parent, publish_staging_root,
32    read_and_verify, read_runtime_document, reject_existing_target, require_layout_directories,
33    validate_layout, write_new_file,
34};
35mod artifact;
36pub use artifact::{BinaryReference, BinarySource};
37mod participant;
38pub use participant::RuntimeParticipant;
39mod document;
40pub use document::{ParticipantClock, Runtime, RuntimeDocument};
41
42/// The only schema tag currently readable by this framework train.
43pub const RUNTIME_SCHEMA: &str = "phoxal/runtime-bundle/v0";
44/// The persisted document filename at the bundle root.
45pub const RUNTIME_FILE: &str = "runtime.json";
46/// The participant-readable asset directory.
47pub const ASSETS_DIR: &str = "assets";
48/// The supervisor-only binary directory.
49pub const BIN_DIR: &str = "bin";
50pub use phoxal_runtime_contract::metadata::MAX_RUNTIME_PARTICIPANTS;
51#[cfg(test)]
52mod bundle_boundary_tests;
53
54/// The contract surface this crate owns: the one persisted runtime document.
55///
56/// Not public API. It exists so compatibility CI can read this crate's declared
57/// process boundary out of the crate itself.
58///
59/// The document body reaches all the way down: the canonical robot it embeds,
60/// that robot's components, capabilities and structure, and the artifact
61/// contracts staged beside it are all part of the one record below, because a
62/// process reading `runtime.json` has to agree with the writer about every one
63/// of them.
64#[doc(hidden)]
65pub mod __compat {
66    use phoxal_runtime_contract::contract_surface::{ContractRecord, ContractSurface};
67    use phoxal_runtime_contract::wire_schema::DescribeWire;
68
69    use crate::{RUNTIME_SCHEMA, RuntimeDocument};
70
71    /// The canonical rendering of this crate's contract surface.
72    #[must_use]
73    pub fn contract_surface() -> String {
74        ContractSurface::new([ContractRecord::document(
75            "RuntimeDocument",
76            RUNTIME_SCHEMA,
77            RuntimeDocument::wire_schema(),
78        )])
79        .canonical_json()
80    }
81
82    #[cfg(test)]
83    mod tests {
84        use super::contract_surface;
85
86        /// The surface is one deterministic JSON document that names the
87        /// bundle's schema tag and reaches into the model the document embeds,
88        /// so an accidentally shallow or empty surface cannot pass.
89        #[test]
90        fn the_surface_names_the_runtime_document_and_the_model_it_embeds() {
91            let rendered = contract_surface();
92            serde_json::from_str::<serde_json::Value>(&rendered).expect("the surface is JSON");
93            assert_eq!(contract_surface(), rendered);
94            for expected in [
95                r#""tag":"phoxal/runtime-bundle/v0""#,
96                r#""name":"RuntimeDocument""#,
97                // The document body, one field from each layer it embeds.
98                r#""name":"participants""#,
99                r#""name":"artifacts""#,
100                r#""name":"config_schema""#,
101                r#""name":"Robot""#,
102                r#""name":"Structure""#,
103                r#""name":"component_instances""#,
104            ] {
105                assert!(
106                    rendered.contains(expected),
107                    "{expected} missing: {rendered}"
108                );
109            }
110        }
111    }
112}