Skip to main content

qualia_client_core/
vision_10d_rights.rs

1//! F4 — rights / attestation barrier for vision `.10d` browse and load.
2//!
3//! Fail-closed for **citable** use: CRC must verify and ProvenanceSidecar must
4//! be present and pass `validate_provenance`. Browse-only may allow unattested
5//! recon for local development when explicitly opted in.
6
7use qualia_core_db::container_10d::{
8    header::Container10dHeader,
9    integrity::verify_whole_file_crc32c,
10    parse_section_table,
11    provenance_section::{decode_provenance_section, validate_provenance},
12    section::SectionType,
13};
14use serde::Serialize;
15
16/// Access mode for vision 10D assets.
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
18pub enum Vision10dAccess {
19    /// Local browse / debug: CRC required; provenance optional.
20    BrowseAllowUnattested,
21    /// Product / citable: CRC + valid ProvenanceSidecar required.
22    CitableRequireProvenance,
23}
24
25/// Barrier outcome (F4).
26#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
27pub enum Vision10dBarrier {
28    Permit,
29    Deny { reason: &'static str },
30}
31
32/// Evaluate rights barrier on sealed container bytes.
33pub fn evaluate_vision_10d_barrier(bytes: &[u8], access: Vision10dAccess) -> Vision10dBarrier {
34    let mut bytes_mut = bytes.to_vec();
35    if verify_whole_file_crc32c(&mut bytes_mut).is_err() {
36        return Vision10dBarrier::Deny {
37            reason: "crc_failed",
38        };
39    }
40    let Ok(header) = Container10dHeader::parse(&bytes_mut) else {
41        return Vision10dBarrier::Deny {
42            reason: "bad_header",
43        };
44    };
45    let Ok(descs) = parse_section_table(&bytes_mut, &header) else {
46        return Vision10dBarrier::Deny {
47            reason: "bad_section_table",
48        };
49    };
50
51    let mut has_mesh = false;
52    let mut prov_payload: Option<&[u8]> = None;
53    for d in descs.iter() {
54        match d.typ() {
55            Some(SectionType::QuantizedMesh) => has_mesh = true,
56            Some(SectionType::ProvenanceSidecar) => {
57                let start = d.byte_offset as usize;
58                let end = start.saturating_add(d.byte_length as usize);
59                prov_payload = bytes_mut.get(start..end);
60            }
61            _ => {}
62        }
63    }
64    if !has_mesh {
65        return Vision10dBarrier::Deny {
66            reason: "no_mesh_section",
67        };
68    }
69
70    match access {
71        Vision10dAccess::BrowseAllowUnattested => Vision10dBarrier::Permit,
72        Vision10dAccess::CitableRequireProvenance => match prov_payload {
73            None => Vision10dBarrier::Deny {
74                reason: "missing_provenance",
75            },
76            Some(payload) => match decode_provenance_section(payload) {
77                Err(_) => Vision10dBarrier::Deny {
78                    reason: "provenance_decode_failed",
79                },
80                Ok(view) => match validate_provenance(&view) {
81                    Ok(()) => Vision10dBarrier::Permit,
82                    Err(_) => Vision10dBarrier::Deny {
83                        reason: "provenance_invalid",
84                    },
85                },
86            },
87        },
88    }
89}
90
91/// Convenience: true only when barrier is Permit.
92pub fn vision_10d_may_load(bytes: &[u8], access: Vision10dAccess) -> bool {
93    matches!(
94        evaluate_vision_10d_barrier(bytes, access),
95        Vision10dBarrier::Permit
96    )
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102    use qualia_core_db::container_10d::provenance_section::ProvenanceSidecar;
103    use qualia_core_db::render::assets::Mesh;
104    use qualia_core_db::render::compile_10d::{
105        compile_mesh_to_10d_vision, compile_mesh_to_10d_vision_with_provenance,
106    };
107    use qualia_core_db::tensor::Tensor10D;
108
109    fn tri_mesh() -> Mesh {
110        Mesh {
111            positions: vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
112            triangles: vec![[0, 1, 2]],
113            min: [0.0, 0.0, 0.0],
114            max: [1.0, 1.0, 0.0],
115        }
116    }
117
118    #[test]
119    fn unattested_denied_for_citable() {
120        let nodes = [Tensor10D::default()];
121        let bytes = compile_mesh_to_10d_vision(&tri_mesh(), &nodes).unwrap();
122        assert!(matches!(
123            evaluate_vision_10d_barrier(&bytes, Vision10dAccess::CitableRequireProvenance),
124            Vision10dBarrier::Deny {
125                reason: "missing_provenance"
126            }
127        ));
128        assert!(matches!(
129            evaluate_vision_10d_barrier(&bytes, Vision10dAccess::BrowseAllowUnattested),
130            Vision10dBarrier::Permit
131        ));
132    }
133
134    #[test]
135    fn attested_permits_citable() {
136        let nodes = [Tensor10D::default()];
137        let prov = ProvenanceSidecar::new(b"source-rgb-stub".to_vec(), "image/rgb8", "CC0");
138        let bytes = compile_mesh_to_10d_vision_with_provenance(&tri_mesh(), &nodes, &prov).unwrap();
139        assert!(matches!(
140            evaluate_vision_10d_barrier(&bytes, Vision10dAccess::CitableRequireProvenance),
141            Vision10dBarrier::Permit
142        ));
143    }
144}