Skip to main content

qualia_core_db/render/
derivation.rs

1//! Decoupled Derivation Job (P2)
2//!
3//! Converts an original source asset (e.g., an OBJ or GLB file) into a `.10d` hypermedia
4//! container carrying the provenance sidecar (the source bytes + metadata) bound within it.
5//! This fulfills the "context is the asset" mandate, ensuring native geometry and provenance
6//! are inseparable.
7
8use crate::container_10d::provenance_section::ProvenanceSidecar;
9use crate::render::assets::import_asset;
10use crate::render::compile_10d::{compile_mesh_to_10d_with_provenance, Compile10dError};
11
12/// Failure modes for the derivation job.
13#[derive(Debug, PartialEq, Eq)]
14pub enum DerivationError {
15    /// Failed to parse or process the source geometry.
16    ImportFailed(String),
17    /// Failed to compile the `.10d` container or sidecar.
18    CompilationFailed(Compile10dError),
19}
20
21impl std::fmt::Display for DerivationError {
22    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23        match self {
24            Self::ImportFailed(e) => write!(f, "Derivation import failed: {e}"),
25            Self::CompilationFailed(e) => write!(f, "Derivation compilation failed: {e}"),
26        }
27    }
28}
29impl std::error::Error for DerivationError {}
30
31/// Run the decoupled derivation job on an original source asset.
32///
33/// Takes the raw source bytes, imports them into a mesh, wraps the source bytes and
34/// metadata into a `ProvenanceSidecar`, and compiles it all into a sealed `.10d` container.
35pub fn run_derivation_job(
36    source_bytes: &[u8],
37    format_hint: Option<&str>,
38    media_type: &str,
39    licence: &str,
40    vc_payload: Option<&[u8]>,
41) -> Result<Vec<u8>, DerivationError> {
42    let mesh = import_asset(source_bytes, format_hint)
43        .map_err(|e| DerivationError::ImportFailed(e.to_string()))?;
44
45    let mut sidecar = ProvenanceSidecar::new(source_bytes, media_type, licence);
46    if let Some(vc_bytes) = vc_payload {
47        sidecar = sidecar.with_vc(vc_bytes);
48    }
49
50    compile_mesh_to_10d_with_provenance(&mesh, Some(&sidecar))
51        .map_err(DerivationError::CompilationFailed)
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57    use crate::container_10d::header::Container10dHeader;
58    use crate::container_10d::provenance_section::{
59        decode_provenance_section, validate_provenance,
60    };
61    use crate::container_10d::section::{parse_section_table, SectionType};
62
63    const TRI_OBJ: &[u8] = b"v 0 0 0\nv 1 0 0\nv 0 1 0\nf 1 2 3\n";
64
65    #[test]
66    fn derivation_job_produces_valid_container() {
67        let out = run_derivation_job(TRI_OBJ, Some("obj"), "text/plain", "CC0", None).unwrap();
68
69        let header = Container10dHeader::parse(&out).unwrap();
70        let descs = parse_section_table(&out, &header).unwrap();
71
72        // Find provenance sidecar
73        let prov = descs
74            .iter()
75            .find(|d| d.section_type == SectionType::ProvenanceSidecar as u8)
76            .expect("provenance section generated");
77
78        let payload = &out[prov.byte_offset as usize..][..prov.byte_length as usize];
79        let view = decode_provenance_section(payload).unwrap();
80
81        validate_provenance(&view).unwrap();
82        assert_eq!(view.licence(), "CC0");
83        assert_eq!(view.source_bytes(), TRI_OBJ);
84    }
85}