Skip to main content

open_agent_profile/
canonical.rs

1use sha2::{Digest, Sha256};
2use thiserror::Error;
3
4use crate::{AgentProfile, Digests, Document, object};
5
6/// Failure while producing canonical JSON.
7#[derive(Debug, Error)]
8pub enum CanonicalError {
9    /// The value could not be serialized.
10    #[error("canonical JSON error: {0}")]
11    Serialize(#[from] serde_json::Error),
12}
13
14/// Serializes a value using RFC 8785 JSON Canonicalization Scheme rules.
15pub fn canonical_json(value: &impl serde::Serialize) -> Result<Vec<u8>, CanonicalError> {
16    Ok(serde_jcs::to_vec(value)?)
17}
18fn digest(value: &impl serde::Serialize) -> Result<String, CanonicalError> {
19    Ok(format!(
20        "sha256:{:x}",
21        Sha256::digest(canonical_json(value)?)
22    ))
23}
24/// Computes the `sha256:<hex>` identity of the full profile.
25pub fn profile_digest(profile: &AgentProfile) -> Result<String, CanonicalError> {
26    digest(profile)
27}
28/// Computes the `sha256:<hex>` identity of the profile's `spec` member.
29pub fn spec_digest(profile: &AgentProfile) -> Result<String, CanonicalError> {
30    let mut metadata = object(profile.get("metadata")).clone();
31    for key in ["revision", "updated_at", "trust"] {
32        metadata.remove(key);
33    }
34    let mut identity = Document::new();
35    identity.insert("metadata".into(), metadata.into());
36    identity.insert(
37        "spec".into(),
38        profile.get("spec").cloned().unwrap_or_default(),
39    );
40    digest(&identity)
41}
42/// Computes both full-profile and specification identities.
43pub fn profile_digests(profile: &AgentProfile) -> Result<Digests, CanonicalError> {
44    Ok(Digests {
45        profile: profile_digest(profile)?,
46        spec: spec_digest(profile)?,
47    })
48}