Skip to main content

graphforge_knowledge/research/
canonical.rs

1//! Exact immutable record commitments use the shared canonical envelope.
2use super::{
3    ClaimRelationRecord, RESEARCH_RECORD_VERSION, ResearchClaimRecord, ResearchDecisionRecord,
4};
5use crate::KnowledgeError;
6use graphforge_core::canonical::{
7    CANONICAL_CONTRACT_VERSION, CanonicalDomain, CanonicalWriter, fingerprint,
8};
9use uuid::Uuid;
10
11impl ResearchClaimRecord {
12    /// Commit all immutable metadata, including original producer and origin.
13    pub fn fingerprint(&self) -> Result<[u8; 32], KnowledgeError> {
14        let mut w = CanonicalWriter::new();
15        w.u32(RESEARCH_RECORD_VERSION)?;
16        w.raw(self.assertion_uuid.as_bytes())?;
17        w.raw(self.conceptual_uuid.as_bytes())?;
18        w.text(self.category.as_str())?;
19        w.raw(self.creator_uuid.as_bytes())?;
20        optional_uuid(&mut w, self.run_uuid)?;
21        optional_uuid(&mut w, self.origin_branch_uuid)?;
22        optional_uuid(&mut w, self.origin_version_uuid)?;
23        w.raw(self.provenance_uuid.as_bytes())?;
24        w.i64(self.recorded_at)?;
25        finish(CanonicalDomain::ResearchClaim, w)
26    }
27}
28impl ClaimRelationRecord {
29    /// Commit the direction, meaning and original producer of a claim relation.
30    pub fn fingerprint(&self) -> Result<[u8; 32], KnowledgeError> {
31        let mut w = CanonicalWriter::new();
32        w.u32(RESEARCH_RECORD_VERSION)?;
33        w.raw(self.relation_uuid.as_bytes())?;
34        w.raw(self.source_assertion_uuid.as_bytes())?;
35        w.raw(self.target_assertion_uuid.as_bytes())?;
36        w.text(self.kind.as_str())?;
37        w.raw(self.creator_uuid.as_bytes())?;
38        w.raw(self.provenance_uuid.as_bytes())?;
39        w.i64(self.recorded_at)?;
40        finish(CanonicalDomain::ResearchClaimRelation, w)
41    }
42}
43impl ResearchDecisionRecord {
44    /// Commit exact publication order, destination authority and explicit decision.
45    pub fn fingerprint(&self) -> Result<[u8; 32], KnowledgeError> {
46        let mut w = CanonicalWriter::new();
47        w.u32(RESEARCH_RECORD_VERSION)?;
48        w.u64(self.sequence)?;
49        w.raw(self.decision_uuid.as_bytes())?;
50        w.raw(self.operation_uuid.as_bytes())?;
51        w.raw(&self.request_sha256)?;
52        w.raw(self.authority.project_uuid.as_bytes())?;
53        optional_uuid(&mut w, self.authority.community_uuid)?;
54        w.raw(self.authority.context_uuid.as_bytes())?;
55        w.text(self.subject_kind.as_str())?;
56        w.raw(self.subject_uuid.as_bytes())?;
57        w.text(self.kind.as_str())?;
58        w.raw(self.creator_uuid.as_bytes())?;
59        optional_uuid(&mut w, self.source_version_uuid)?;
60        w.i64(self.recorded_at)?;
61        finish(CanonicalDomain::ResearchDecision, w)
62    }
63}
64fn optional_uuid(w: &mut CanonicalWriter, value: Option<Uuid>) -> Result<(), KnowledgeError> {
65    if let Some(id) = value {
66        w.u8(1)?;
67        w.raw(id.as_bytes())?;
68    } else {
69        w.u8(0)?;
70    }
71    Ok(())
72}
73fn finish(domain: CanonicalDomain, w: CanonicalWriter) -> Result<[u8; 32], KnowledgeError> {
74    Ok(fingerprint(
75        domain,
76        CANONICAL_CONTRACT_VERSION,
77        &w.finish(),
78    )?)
79}
80
81impl super::ResearchSuppressionRecord {
82    /// Commit exact scoped suppression without changing assertion or graph bytes.
83    pub fn fingerprint(&self) -> Result<[u8; 32], KnowledgeError> {
84        let mut w = CanonicalWriter::new();
85        w.u32(RESEARCH_RECORD_VERSION)?;
86        for id in [
87            self.suppression_uuid,
88            self.assertion_uuid,
89            self.context_uuid,
90            self.creator_uuid,
91            self.provenance_uuid,
92        ] {
93            w.raw(id.as_bytes())?;
94        }
95        w.i64(self.recorded_at)?;
96        finish(CanonicalDomain::ResearchClaimSuppression, w)
97    }
98}