Skip to main content

heddle_thread_api/fetch/
native.rs

1//! Install a verified hosted download through the existing local store and
2//! replica admission paths. This disk operation never advances a checkout.
3use crypto::thread_operation::SignedGenesis;
4use heddle_object_model::object::{
5    ContentHash, StateId,
6    thread_replication::integration::{SPOOL_GENESIS_TRUST_FORMAT, TrustedHostedExecutor},
7};
8use objects::store::ObjectStore;
9use prost::Message;
10use repo::{Repository, thread_replication::ThreadReplica};
11
12use super::{Error, StagedSource};
13use crate::contract::EndpointKind;
14
15/// Current endpoint possession under independently retained account authority.
16/// Retain the original credential privately; it never joins source proof packs.
17pub struct OwnedDeviceBinding<'a> {
18    pub attachment: &'a crate::contract::RootAttachment,
19    pub credential: &'a [u8],
20}
21
22impl StagedSource {
23    /// Call on the application's disk worker. Owner history is independently
24    /// verified and monotonically pinned; the authenticated hosted endpoint is
25    /// pinned before accepting any original Integration operations.
26    pub fn install(self, repository: &Repository, now_unix_seconds: i64) -> Result<StateId, Error> {
27        let endpoint = self
28            .ready
29            .endpoint
30            .as_ref()
31            .ok_or(Error::Invalid("endpoint absent"))?;
32        if endpoint.kind != EndpointKind::Weft as i32 {
33            return Err(Error::Invalid(
34                "hosted installation requires the selected Weft endpoint",
35            ));
36        }
37        let executor = endpoint
38            .public_key
39            .as_slice()
40            .try_into()
41            .map_err(|_| Error::Invalid("invalid hosted endpoint key"))?;
42        let spool = uuid::Uuid::parse_str(
43            &self
44                .ready
45                .thread
46                .as_ref()
47                .and_then(|t| t.spool.as_ref())
48                .ok_or(Error::Invalid("Spool absent"))?
49                .id,
50        )
51        .map_err(preparation)?;
52        let genesis = self
53            .ready
54            .owner_genesis
55            .as_ref()
56            .ok_or(Error::Invalid("owner genesis absent"))?;
57        let owner = self
58            .ready
59            .ownership
60            .as_ref()
61            .ok_or(Error::Invalid("owner history absent"))?;
62        let verified =
63            repo::verify_spool_owner_observation(genesis, owner, spool, now_unix_seconds)
64                .map_err(preparation)?;
65        let body = genesis
66            .genesis
67            .as_ref()
68            .ok_or(Error::Invalid("owner genesis body absent"))?;
69        let trust = TrustedHostedExecutor {
70            spool,
71            spool_genesis: ContentHash::compute_typed(
72                SPOOL_GENESIS_TRUST_FORMAT,
73                &body.encode_to_vec(),
74            ),
75            executor,
76        };
77        for signed in &self.operations {
78            let operation = signed.verify().map_err(preparation)?;
79            require_source_operation(&operation).map_err(preparation)?;
80            if operation
81                .hosted_execution_binding()
82                .map_err(preparation)?
83                .is_some()
84            {
85                trust.authorize(&operation).map_err(preparation)?;
86            }
87        }
88        repository
89            .verify_and_pin_owner_observation(
90                genesis,
91                owner,
92                spool,
93                &verified.wire().canonical_spool_path_segments,
94                now_unix_seconds,
95            )
96            .map_err(preparation)?;
97        self.install_replicas(repository, Some(&trust), None, "", now_unix_seconds)?;
98        Ok(self.state.id())
99    }
100    /// Device-only source uses independently admitted local account authority;
101    /// incoming material cannot enroll its endpoint or replace a Spool owner.
102    /// The destination must be an unseeded `Repository::init` skeleton or
103    /// already belong to this exact Spool; importing never replaces local work.
104    pub fn install_owned_device(
105        self,
106        repository: &Repository,
107        authority: &repo::device_authority::DeviceAuthority,
108        binding: OwnedDeviceBinding<'_>,
109        spool_path: &str,
110        now_unix_seconds: i64,
111    ) -> Result<StateId, Error> {
112        let endpoint = self
113            .ready
114            .endpoint
115            .as_ref()
116            .ok_or(Error::Invalid("endpoint absent"))?;
117        if endpoint.kind != EndpointKind::Device as i32 {
118            return Err(Error::Invalid(
119                "owned-device installation requires device endpoint",
120            ));
121        }
122        let owner = repo::verify_account_owner_observation(&authority.owner, now_unix_seconds)
123            .map_err(preparation)?;
124        let account = owner
125            .signed_root()
126            .root
127            .as_ref()
128            .ok_or(Error::Invalid("account owner root absent"))?;
129        let account_id = uuid::Uuid::from_slice(&account.account_uuid)
130            .map_err(preparation)?
131            .to_string();
132        authority
133            .verify_mint_root(&binding.attachment.root_public_key, now_unix_seconds)
134            .map_err(preparation)?;
135        authority
136            .verify_publisher(&binding.attachment.subject_public_key)
137            .map_err(preparation)?;
138        authority
139            .verify_publisher(&endpoint.public_key)
140            .map_err(preparation)?;
141        let roots = biscuit_verifier::parse_ed25519_public_keys_hex(
142            &hex::encode(&binding.attachment.root_public_key),
143            1,
144        )
145        .map_err(preparation)?;
146        let verified = crate::root_attachment::verify(
147            binding.attachment,
148            binding.credential,
149            &roots,
150            &account_id,
151            endpoint,
152            chrono::DateTime::from_timestamp(now_unix_seconds, 0)
153                .ok_or(Error::Invalid("invalid endpoint verification time"))?,
154        )?;
155        if verified
156            .credential_revocation_ids()
157            .iter()
158            .any(|id| authority.revoked_ids.contains(id))
159        {
160            return Err(Error::Invalid(
161                "endpoint binding credential is explicitly revoked",
162            ));
163        }
164        let spool = self
165            .ready
166            .thread
167            .as_ref()
168            .and_then(|thread| thread.spool.as_ref())
169            .ok_or(Error::Invalid("Spool absent"))?;
170        repository
171            .install_native_spool_id(spool.id.parse().map_err(preparation)?)
172            .map_err(preparation)?;
173        self.install_replicas(
174            repository,
175            None,
176            Some(authority),
177            spool_path,
178            now_unix_seconds,
179        )?;
180        Ok(self.state.id())
181    }
182    fn install_replicas(
183        &self,
184        repository: &Repository,
185        trust: Option<&TrustedHostedExecutor>,
186        authority: Option<&repo::device_authority::DeviceAuthority>,
187        spool_path: &str,
188        now: i64,
189    ) -> Result<(), Error> {
190        let main = self
191            .ready
192            .thread_genesis
193            .as_ref()
194            .ok_or(Error::Invalid("Thread genesis absent"))?;
195        let mut replicas = std::collections::BTreeMap::new();
196        let mut claims = std::collections::BTreeMap::<ContentHash, Vec<PendingClaim>>::new();
197        let mut resolutions = std::collections::BTreeMap::<
198            ContentHash,
199            crate::replication::ownership::OriginalResolution,
200        >::new();
201        for wrapper in std::iter::once(main).chain(&self.dependencies) {
202            let original = wrapper
203                .genesis
204                .as_ref()
205                .ok_or(Error::Invalid("original signed genesis absent"))?;
206            let [signature] = original.signatures.as_slice() else {
207                return Err(Error::Invalid("one original creator signature required"));
208            };
209            let signed = SignedGenesis {
210                canonical: original.canonical_record.clone(),
211                signature: signature.signature.clone(),
212            };
213            let genesis = signed.verify().map_err(preparation)?;
214            let replica = match &genesis.owner {
215                heddle_object_model::object::thread_replication::GenesisOwner::LocalKey(_) => {
216                    if !wrapper.creator_authority.is_empty() || wrapper.admission.is_some() {
217                        return Err(Error::Invalid(
218                            "local ownership cannot carry implicit account admission",
219                        ));
220                    }
221                    ThreadReplica::create(repository.heddle_dir(), &signed).map_err(preparation)?
222                }
223                heddle_object_model::object::thread_replication::GenesisOwner::Account(_) => {
224                    if let Some(receipt) = &wrapper.admission {
225                        if receipt.format
226                            != heddle_object_model::object::thread_genesis_admission::FORMAT
227                        {
228                            return Err(Error::Invalid("unknown genesis admission format"));
229                        }
230                        let [_signature] = receipt.signatures.as_slice() else {
231                            return Err(Error::Invalid(
232                                "one hosted genesis admission signature required",
233                            ));
234                        };
235                        let admission = crate::boundary_acceptance::genesis_admission(wrapper)?
236                            .ok_or(Error::Invalid("genesis admission absent"))?;
237                        match trust {
238                            Some(trust) => ThreadReplica::create_from_genesis_admission(
239                                repository.heddle_dir(),
240                                &signed,
241                                &wrapper.creator_authority,
242                                &admission,
243                                trust,
244                            ),
245                            None => ThreadReplica::create_from_pinned_genesis_admission(
246                                repository.heddle_dir(),
247                                &signed,
248                                &wrapper.creator_authority,
249                                &admission,
250                            ),
251                        }
252                        .map_err(preparation)?
253                    } else {
254                        let authority = authority.ok_or(Error::Invalid(
255                            "original account genesis admission required",
256                        ))?;
257                        ThreadReplica::create_authorized(
258                            repository.heddle_dir(),
259                            &signed,
260                            &wrapper.creator_authority,
261                            authority,
262                            spool_path,
263                            "/heddle.api.v1alpha2.ThreadService/StartThread",
264                            now,
265                        )
266                        .map_err(preparation)?
267                    }
268                }
269            };
270            if let Some(trust) = trust {
271                let executor = trust.executor;
272                repository
273                    .pin_thread_hosted_executor(&replica, executor)
274                    .map_err(preparation)?;
275            }
276            let mut pending = Vec::new();
277            let retained = replica.ownership_claims().map_err(preparation)?;
278            for claim in crate::replication::ownership::verify_claims(wrapper, &genesis)? {
279                let value = claim.original.verify().map_err(preparation)?;
280                if let Some(receipt) = &claim.authority_admission {
281                    let trust = replica
282                        .authority_admission_trust(receipt)
283                        .map_err(preparation)?;
284                    receipt
285                        .verify_claim(&claim.original, &genesis, &trust)
286                        .map_err(preparation)?;
287                } else if !retained.contains(&claim.original) {
288                    let authority = authority.ok_or(Error::Invalid("new claim requires current original acceptance or independently pinned admission"))?;
289                    repo::thread_replication::ownership_claim::verify_claim_authority(
290                        &claim.original,
291                        &genesis,
292                        authority,
293                        spool_path,
294                        now,
295                    )
296                    .map_err(preparation)?;
297                }
298                pending.push(PendingClaim {
299                    original: claim,
300                    remaining: value.source_frontier,
301                });
302            }
303            for resolution in crate::replication::ownership::verify_resolutions(wrapper, &genesis)?
304            {
305                if resolutions
306                    .insert(replica.thread_id(), resolution)
307                    .is_some()
308                {
309                    return Err(Error::Invalid("duplicate ownership resolution"));
310                }
311            }
312            claims.insert(replica.thread_id(), pending);
313            replicas.insert(replica.thread_id(), replica);
314        }
315        // Verify portable original testimony before installing immutable bytes.
316        // Fresh capabilities are evaluated in dependency order below, after
317        // explicit claims, while availability remains unpublished on failure.
318        for signed in &self.operations {
319            let operation = signed.verify().map_err(preparation)?;
320            let replica = replicas
321                .get(&operation.thread)
322                .ok_or(Error::Invalid("source dependency replica absent"))?;
323            if let Some(receipt) = self
324                .authority_admissions
325                .get(&operation.id().map_err(preparation)?)
326            {
327                replica
328                    .require_authority_admission(signed, receipt)
329                    .map_err(preparation)?;
330            }
331        }
332        self.install_source_objects(repository)?;
333        for (thread, pending) in &mut claims {
334            install_ready_claims(
335                replicas
336                    .get(thread)
337                    .ok_or(Error::Invalid("claim replica absent"))?,
338                pending,
339                authority,
340                spool_path,
341                now,
342            )?;
343        }
344        for (thread, resolution) in &resolutions {
345            install_ready_resolution(
346                replicas
347                    .get(thread)
348                    .ok_or(Error::Invalid("resolution replica absent"))?,
349                resolution,
350                authority,
351                spool_path,
352                now,
353            )?;
354        }
355        for signed in &self.operations {
356            let operation = signed.verify().map_err(preparation)?;
357            let replica = replicas
358                .get(&operation.thread)
359                .ok_or(Error::Invalid("source dependency replica absent"))?;
360            let id = operation.id().map_err(preparation)?;
361            if !self.authority_admissions.contains_key(&id) {
362                let prior = replica
363                    .operation_with_authority_admission(&id)
364                    .map_err(preparation)?;
365                if !prior.is_some_and(|prior| {
366                    prior.original == *signed
367                        && prior.status == objects::object::thread_replication::Admission::Accepted
368                }) && let Some(author) = operation.source_author().map_err(preparation)?
369                {
370                    match author {
371                        objects::object::thread_replication::SourceAuthor::LocalKey => replica
372                            .verify_local_source_owner(&operation)
373                            .map_err(preparation)?,
374                        objects::object::thread_replication::SourceAuthor::Account { .. } => {
375                            replica.verify_source_authority(&operation, authority.ok_or(Error::Invalid("fresh source requires original authority or retained admission"))?, spool_path, now).map_err(preparation)?;
376                        }
377                    }
378                }
379            }
380            let admission = if !self.is_complete() {
381                replica.receive_source_metadata(
382                    signed,
383                    repository.store(),
384                    self.authority_admissions.get(&id),
385                    require_source_operation,
386                )
387            } else if let Some(receipt) = self
388                .authority_admissions
389                .get(&operation.id().map_err(preparation)?)
390            {
391                replica.receive_with_authority_admission(
392                    signed,
393                    receipt,
394                    repository.store(),
395                    require_source_operation,
396                )
397            } else {
398                replica.receive(signed, repository.store(), require_source_operation)
399            }
400            .map_err(preparation)?;
401            if admission != objects::object::thread_replication::Admission::Accepted {
402                return Err(Error::Invalid(
403                    "source proof did not settle in dependency order",
404                ));
405            }
406            if let Some(pending) = claims.get_mut(&operation.thread) {
407                for claim in pending.iter_mut() {
408                    claim.remaining.remove(&id);
409                }
410                install_ready_claims(replica, pending, authority, spool_path, now)?;
411            }
412            if let Some(resolution) = resolutions.get(&operation.thread) {
413                install_ready_resolution(replica, resolution, authority, spool_path, now)?;
414            }
415        }
416        if claims.values().any(|claims| !claims.is_empty()) {
417            return Err(Error::Invalid(
418                "ownership cutoff did not settle before source completion",
419            ));
420        }
421        for (thread, resolution) in &resolutions {
422            let replica = replicas
423                .get(thread)
424                .ok_or(Error::Invalid("resolution replica absent"))?;
425            install_ready_resolution(replica, resolution, authority, spool_path, now)?;
426            if replica
427                .ownership_resolution()
428                .map_err(preparation)?
429                .is_none()
430            {
431                return Err(Error::Invalid(
432                    "ownership resolution frontier did not settle",
433                ));
434            }
435        }
436        for thread in claims
437            .keys()
438            .filter(|thread| !resolutions.contains_key(*thread))
439        {
440            replicas
441                .get(thread)
442                .ok_or(Error::Invalid("claim replica absent"))?
443                .effective_owner()
444                .map_err(preparation)?;
445        }
446        let main_id = crate::replication::opening::verify_genesis(
447            main.genesis
448                .as_ref()
449                .ok_or(Error::Invalid("signed genesis absent"))?,
450            self.ready
451                .thread
452                .as_ref()
453                .ok_or(Error::Invalid("Thread absent"))?,
454        )?
455        .id()
456        .map_err(preparation)?;
457        let selected = replicas
458            .get(&main_id)
459            .ok_or(Error::Invalid("selected replica absent"))?;
460        if self.operations.is_empty() {
461            let genesis = selected.genesis().map_err(preparation)?;
462            let canonical = self.state.encode_current_msgpack().map_err(preparation)?;
463            objects::object::thread_replication::hosted_import::initial_base_state(
464                &genesis, &canonical,
465            )
466            .map_err(preparation)?;
467        } else {
468            let mut source = selected;
469            let mut proved = false;
470            let mut possession = Vec::new();
471            for _ in 0..128 {
472                if source
473                    .accepted_source_revision(self.state.id())
474                    .map_err(preparation)?
475                    .is_some()
476                {
477                    possession.push(source);
478                    proved = true;
479                    break;
480                }
481                let genesis = source.genesis().map_err(preparation)?;
482                if genesis.base != self.state.id() {
483                    break;
484                }
485                possession.push(source);
486                let Some(parent) = genesis.parent else { break };
487                let Some(next) = replicas.get(&parent) else {
488                    break;
489                };
490                if next.genesis().map_err(preparation)?.spool != genesis.spool {
491                    break;
492                }
493                source = next;
494            }
495            if !proved {
496                return Err(Error::Invalid("selected source proof did not settle"));
497            }
498            if self.is_complete() {
499                for replica in possession {
500                    replica
501                        .record_source_possession(self.state.id())
502                        .map_err(preparation)?;
503                }
504            }
505        }
506        if self.is_complete() {
507            selected
508                .record_source_possession(self.state.id())
509                .map_err(preparation)?;
510        }
511        Ok(())
512    }
513
514    pub(super) fn install_source_objects(&self, repository: &Repository) -> Result<(), Error> {
515        let pack = self.directory.path().join("source.pack");
516        let index = self.directory.path().join("source.idx");
517        if self.is_complete() {
518            return repository
519                .store()
520                .install_pack_streaming(&pack, &index)
521                .map(|_| ())
522                .map_err(preparation);
523        }
524        // HRT1 is a disclosure proof, not a full tree object. Split it out before
525        // registering the visible immutable records in the shared object store.
526        let visible_pack = self.directory.path().join("visible.pack");
527        let visible_index = self.directory.path().join("visible.idx");
528        let output = std::fs::OpenOptions::new()
529            .read(true)
530            .write(true)
531            .create_new(true)
532            .open(&visible_pack)?;
533        let mut builder = heddle_pack::store::pack::StreamingPackBuilder::new(
534            output,
535            visible_index.clone(),
536            Default::default(),
537            self.directory.path().join("visible-buckets"),
538        )
539        .map_err(preparation)?;
540        let reader =
541            heddle_pack::store::pack::PackReader::open(&pack, &index).map_err(preparation)?;
542        reader
543            .visit_objects(|id, kind, bytes| {
544                if kind != heddle_pack::store::pack::ObjectType::Tree
545                    || !objects::object::is_redacted_tree(bytes)
546                {
547                    builder.add_id(id, kind, bytes)?;
548                }
549                Ok(())
550            })
551            .map_err(preparation)?;
552        let (output, _) = builder.finalize().map_err(preparation)?;
553        drop(output);
554        for partial in &self.partial_trees {
555            let bytes =
556                objects::object::encode_redacted_projection(partial).map_err(preparation)?;
557            repository
558                .store()
559                .put_partial_tree(&partial.declared_root(), &bytes)
560                .map_err(preparation)?;
561        }
562        repository
563            .store()
564            .install_pack_streaming(&visible_pack, &visible_index)
565            .map(|_| ())
566            .map_err(preparation)
567    }
568}
569struct PendingClaim {
570    original: crate::replication::ownership::OriginalClaim,
571    remaining: std::collections::BTreeSet<ContentHash>,
572}
573fn install_ready_resolution(
574    replica: &ThreadReplica,
575    resolution: &crate::replication::ownership::OriginalResolution,
576    authority: Option<&repo::device_authority::DeviceAuthority>,
577    spool_path: &str,
578    now: i64,
579) -> Result<(), Error> {
580    if let Some(existing) = replica.ownership_resolution().map_err(preparation)? {
581        if existing == resolution.original {
582            return Ok(());
583        }
584        return Err(Error::Invalid(
585            "incoming ownership resolution conflicts with retained history",
586        ));
587    }
588    let value = heddle_object_model::object::thread_replication::ownership_resolution::ThreadOwnershipResolution::decode(&resolution.original.canonical)
589        .map_err(preparation)?;
590    if replica.ownership_claims().map_err(preparation)?.len() != value.conflicting_claims.len() {
591        return Ok(());
592    }
593    for head in &value.frontier {
594        if replica
595            .operation(head)
596            .map_err(preparation)?
597            .is_none_or(|(_, status)| {
598                status != objects::object::thread_replication::Admission::Accepted
599            })
600        {
601            return Ok(());
602        }
603    }
604    if let Some(receipt) = &resolution.authority_admission {
605        replica
606            .resolve_ownership_with_admission(&resolution.original, receipt)
607            .map_err(preparation)?;
608    } else {
609        replica
610            .resolve_ownership(
611                &resolution.original,
612                authority.ok_or(Error::Invalid(
613                    "new ownership resolution requires current recipient authority",
614                ))?,
615                spool_path,
616                now,
617            )
618            .map_err(preparation)?;
619    }
620    Ok(())
621}
622fn install_ready_claims(
623    replica: &ThreadReplica,
624    pending: &mut Vec<PendingClaim>,
625    authority: Option<&repo::device_authority::DeviceAuthority>,
626    spool_path: &str,
627    now: i64,
628) -> Result<(), Error> {
629    let mut index = 0;
630    while index < pending.len() {
631        if !pending[index].remaining.is_empty() {
632            index += 1;
633            continue;
634        }
635        let claim = pending.remove(index).original;
636        if let Some(receipt) = &claim.authority_admission {
637            replica
638                .claim_ownership_with_admission(&claim.original, receipt)
639                .map_err(preparation)?;
640        } else if !replica
641            .ownership_claims()
642            .map_err(preparation)?
643            .contains(&claim.original)
644        {
645            replica
646                .claim_ownership_for_import(
647                    &claim.original,
648                    authority.ok_or(Error::Invalid("new claim authority absent"))?,
649                    spool_path,
650                    now,
651                )
652                .map_err(preparation)?;
653        }
654    }
655    Ok(())
656}
657fn preparation(error: impl std::fmt::Display) -> Error {
658    Error::Preparation(error.to_string())
659}
660
661// Stage already negotiates Source alone. Keep that trust boundary explicit at
662// installation too: source verification is never original Metadata authority.
663fn require_source_operation(
664    operation: &heddle_object_model::object::thread_replication::ThreadOperation,
665) -> repo::thread_replication::Result<()> {
666    if operation.facet() != heddle_object_model::object::thread_replication::ThreadFacet::Source {
667        return Err(repo::thread_replication::Error::Invalid(
668            "source installation cannot admit non-source authority".into(),
669        ));
670    }
671    Ok(())
672}
673
674#[cfg(test)]
675mod tests {
676    use crypto::{Ed25519Signer, Signer, thread_operation::SignedOperation};
677    use heddle_object_model::object::{
678        CollaborationActor,
679        thread_replication::{
680            ThreadGenesis, ThreadOperation, ThreadOperationBody,
681            metadata::{AUTHORITY_FORMAT, Control, ThreadControl},
682        },
683    };
684
685    use super::*;
686    #[test]
687    fn source_install_gate_cannot_create_metadata_original_authority() {
688        let directory = tempfile::tempdir().expect("repository");
689        let repository = Repository::init_default(directory.path()).expect("repo");
690        let signer = Ed25519Signer::from_seed(&[56; 32]).expect("signer");
691        let spool = uuid::Uuid::from_u128(11);
692        let genesis = ThreadGenesis {
693            owner: objects::object::thread_replication::GenesisOwner::LocalKey(
694                signer.public_key().try_into().expect("key"),
695            ),
696            version: 1,
697            spool: spool.to_string(),
698            parent: None,
699            base: repository.head().expect("head").expect("base"),
700            name: "source import".into(),
701            intent: "original authority".into(),
702            creator: signer.public_key().try_into().expect("key"),
703            nonce: vec![5],
704        };
705        let replica = ThreadReplica::create(
706            repository.heddle_dir(),
707            &SignedGenesis::sign(&genesis, &signer).expect("original genesis"),
708        )
709        .expect("replica");
710        let proof = b"unverified author evidence must not establish admission".to_vec();
711        let control = ThreadControl {
712            version: 1,
713            spool,
714            actor: CollaborationActor {
715                principal_id: uuid::Uuid::from_u128(22),
716                agent_id: None,
717            },
718            authority_digest: ContentHash::compute_typed(AUTHORITY_FORMAT, &proof),
719            authority_envelope: proof,
720            client_operation_id: uuid::Uuid::now_v7(),
721            occurred_at_ms: 0,
722            control: Control::Name("unproved author".into()),
723        };
724        let operation = ThreadOperation {
725            version: 1,
726            thread: replica.thread_id(),
727            parents: Default::default(),
728            publisher: signer.public_key().try_into().expect("key"),
729            body: ThreadOperationBody::Metadata(control.encode().expect("valid canonical control")),
730        };
731        let signed = SignedOperation::sign(&operation, &signer).expect("valid original signature");
732        signed.verify().expect("signature itself is valid");
733        let failure = replica
734            .receive(&signed, repository.store(), require_source_operation)
735            .expect_err("source-only gate denies unproved Metadata");
736        assert!(failure.to_string().contains("non-source authority"));
737        assert!(
738            replica
739                .operation(&operation.id().expect("ID"))
740                .expect("stored operation")
741                .is_none(),
742            "denial precedes immutable persistence"
743        );
744        assert!(
745            !replica
746                .original_authority_admitted(&signed)
747                .expect("admission marker"),
748            "source trust cannot manufacture author admission"
749        );
750    }
751}