Skip to main content

heddle_object_model/object/thread_replication/
delegated_import.rs

1//! Typed converted content, separate from host witness testimony.
2//!
3//! The API operation is the job's import-specific signature. Its content and
4//! frontier commitments select an exact native Capture and causal operation.
5//! This module checks native identity/ancestry and transport commitments;
6//! crypto checks signatures, capability-verifier checks owner permission, and
7//! repo rechecks publication trust while committing. A job never becomes a
8//! `TrustedHostedExecutor` or acquires generic capture/metadata/landing powers.
9use api::{
10    heddle::api::v1alpha2::{
11        ImportContentV1, ImportFrontierV1, ImportIdentityV1, SignedDelegatedImportOperationV1,
12    },
13    import_authority::{self, VerifiedImportDelegation},
14};
15
16use super::{GenesisOwner, ThreadGenesis, ThreadOperation, ThreadOperationBody, invalid};
17use crate::{error::Result, object::ContentHash};
18
19/// Native content bound to a verified import certificate, without any claim of
20/// host commit. Original Git attribution remains inside the original State.
21#[derive(Clone, Debug, PartialEq)]
22pub struct DelegatedImport {
23    signed: SignedDelegatedImportOperationV1,
24    converted: ThreadOperation,
25}
26
27impl DelegatedImport {
28    /// Bind exact native converted content, target, frontier and ancestry. The
29    /// caller verifies every native original signature before supplying it.
30    pub fn bind(
31        signed: &SignedDelegatedImportOperationV1,
32        delegation: &VerifiedImportDelegation,
33        genesis: &ThreadGenesis,
34        original_identity: &ImportIdentityV1,
35        converted: &ThreadOperation,
36        parents: &[ThreadOperation],
37    ) -> Result<Self> {
38        import_authority::verify_operation(signed, delegation).map_err(invalid)?;
39        let body = signed
40            .body
41            .as_ref()
42            .ok_or_else(|| invalid("import operation missing"))?;
43        let identity = delegation
44            .body()
45            .identity
46            .as_ref()
47            .ok_or_else(|| invalid("import identity missing"))?;
48        let account =
49            uuid::Uuid::from_slice(&original_identity.owner_account_uuid).map_err(invalid)?;
50        let ThreadOperationBody::Capture(capture) = &converted.body else {
51            return Err(invalid(
52                "delegated import requires converted Capture content",
53            ));
54        };
55        // One native Capture selects the branch tip; Git ancestors are States
56        // in its content closure, not additional native operations or grants.
57        // The API job signature authorizes their exact content/frontier.
58        if !matches!(capture.author, super::SourceAuthor::LocalKey)
59            || genesis.owner != GenesisOwner::Account(account)
60            || identity.spool_uuid != original_identity.spool_uuid
61            || identity.spool_genesis_digest != original_identity.spool_genesis_digest
62            || genesis.spool
63                != uuid::Uuid::from_slice(&body.spool_uuid)
64                    .map_err(invalid)?
65                    .to_string()
66            || genesis.id()?.as_bytes().as_slice() != body.genesis_digest
67            || converted.thread.as_bytes().as_slice() != body.target_thread_id
68        {
69            return Err(invalid(
70                "import content differs from owner/genesis/job/target binding",
71            ));
72        }
73        let expected = ImportFrontierV1 {
74            format_version: 1,
75            thread_id: converted.thread.as_bytes().to_vec(),
76            operation_ids: converted
77                .parents
78                .iter()
79                .map(|id| id.as_bytes().to_vec())
80                .collect(),
81        };
82        let resulting = ImportFrontierV1 {
83            format_version: 1,
84            thread_id: converted.thread.as_bytes().to_vec(),
85            operation_ids: vec![converted.id()?.as_bytes().to_vec()],
86        };
87        let content = ImportContentV1 {
88            format_version: 1,
89            canonical_capture: rmp_serde::to_vec_named(&capture.result)?,
90        };
91        if import_authority::frontier_digest(&expected).map_err(invalid)?
92            != body.expected_frontier_digest
93            || import_authority::frontier_digest(&resulting).map_err(invalid)?
94                != body.resulting_frontier_digest
95            || import_authority::content_digest(&content).map_err(invalid)?
96                != body.resulting_content_digest
97        {
98            return Err(invalid(
99                "import content or complete frontier commitment differs",
100            ));
101        }
102        let bound = Self {
103            signed: signed.clone(),
104            converted: converted.clone(),
105        };
106        bound.validate_parents(genesis, parents)?;
107        Ok(bound)
108    }
109    /// Recheck only this exact authenticated, carrier-bound operation.
110    pub fn validate_parents(
111        &self,
112        genesis: &ThreadGenesis,
113        parents: &[ThreadOperation],
114    ) -> Result<()> {
115        self.converted
116            .validate_parents_inner(genesis, parents, true)
117    }
118    /// Original API certificate signature and typed operation bytes.
119    pub fn signed(&self) -> &SignedDelegatedImportOperationV1 {
120        &self.signed
121    }
122    /// Exact native converted operation; no host commit is implied.
123    pub fn converted(&self) -> &ThreadOperation {
124        &self.converted
125    }
126    /// Existing native operation identity, unchanged by delegation or rotation.
127    pub fn id(&self) -> Result<ContentHash> {
128        self.converted.id()
129    }
130}