Skip to main content

systemprompt_models/managed/
content_identity.rs

1//! Source-independent content identity retains dependency names, exact file
2//! digests, media types and executable modes while excluding revision history.
3//!
4//! Copyright (c) systemprompt.io — Business Source License 1.1.
5//! See <https://systemprompt.io> for licensing details.
6
7use serde::Serialize;
8use std::collections::BTreeMap;
9use systemprompt_identifiers::ResourceRevisionId;
10
11use super::assets::AssetDigest;
12use super::bundle::RevisionBundle;
13use super::error::RevisionBundleError;
14use super::manifest::FileEntry;
15
16type Result<T> = std::result::Result<T, RevisionBundleError>;
17
18#[derive(Serialize)]
19struct ContentNode<'a> {
20    files: &'a BTreeMap<String, FileEntry>,
21    dependencies: BTreeMap<String, AssetDigest>,
22}
23
24impl RevisionBundle {
25    pub fn content_digest(&self) -> Result<AssetDigest> {
26        self.verify()?;
27        self.node_content_digest(&self.root, 0, &mut BTreeMap::new())
28    }
29
30    fn node_content_digest(
31        &self,
32        id: &ResourceRevisionId,
33        depth: usize,
34        memo: &mut BTreeMap<ResourceRevisionId, AssetDigest>,
35    ) -> Result<AssetDigest> {
36        if depth > 64 {
37            return Err(RevisionBundleError::Integrity);
38        }
39        if let Some(digest) = memo.get(id) {
40            return Ok(digest.clone());
41        }
42        let manifest = self
43            .revisions
44            .get(id)
45            .ok_or(RevisionBundleError::Integrity)?;
46        let dependencies = manifest
47            .dependencies
48            .iter()
49            .map(|(key, dependency)| {
50                Ok((
51                    key.clone(),
52                    self.node_content_digest(&dependency.revision_id, depth + 1, memo)?,
53                ))
54            })
55            .collect::<Result<BTreeMap<_, _>>>()?;
56        let digest = AssetDigest::of(&serde_jcs::to_vec(&ContentNode {
57            files: &manifest.files,
58            dependencies,
59        })?);
60        memo.insert(id.clone(), digest.clone());
61        Ok(digest)
62    }
63}