diff --git a/crates/radicle-node/src/tests/e2e.rs b/crates/radicle-node/src/tests/e2e.rs index 850c86846..54fc6e447 100644 --- a/crates/radicle-node/src/tests/e2e.rs +++ b/crates/radicle-node/src/tests/e2e.rs @@ -271,11 +271,9 @@ fn public_to_private_to_public_replay() { // Recompute and set the identity head, just like `rad id cache` would do. repo.set_identity_head().unwrap(); - // Even though Alice made the repository private, it still appears - // as public! assert_eq!( Identity::load(&repo).unwrap().doc().visibility(), - &Visibility::Public + &Visibility::private([]) ); } diff --git a/crates/radicle/src/storage/git.rs b/crates/radicle/src/storage/git.rs index 956a013fc..3331bd52a 100644 --- a/crates/radicle/src/storage/git.rs +++ b/crates/radicle/src/storage/git.rs @@ -907,23 +907,57 @@ impl ReadRepository for Repository { } fn canonical_identity_head(&self) -> Result { - for remote in self.remote_ids()? { - let remote = remote?; - // Nb. A remote may not have an identity document if the user has not contributed - // any changes to the identity COB. - let Ok(root) = self.identity_root_of(&remote) else { - continue; - }; - let blob = Doc::blob_at(root, self)?; + let blob = match self.backend.find_blob(self.id.deref().into()) { + Ok(blob) => blob, + Err(err) if err.is_not_found() => return Err(RepositoryError::Doc(DocError::Missing)), + Err(err) => return Err(err.into()), + }; - // We've got an identity that goes back to the correct root. - if *self.id == blob.id() { - let identity = Identity::get(&root.into(), self)?; + let doc = Doc::from_blob(&blob)?; + let delegates = doc.delegates(); - return Ok(identity.head()); - } + if delegates.len() != 1 { + log::debug!(target: "storage", "Root identity document (blob '{}') has {} delegates, expected exactly 1.", self.id, delegates.len()); + return Err(RepositoryError::Doc(DocError::Missing)); } - Err(DocError::Missing.into()) + + let founder = delegates.first(); + + let root = match self.reference_oid(founder, &git::refs::storage::IDENTITY_ROOT) { + Ok(root) => { + log::debug!(target: "storage", "Obtained identity COB root commit '{root}' via reference '{}' in namespace of founder '{founder}' of identity document '{}'.", git::refs::storage::IDENTITY_ROOT.as_str(), self.id); + root + } + Err(err) if err.is_not_found() => { + // In case the namespace of the founder does not exist, + // or does not have `rad/root`, attempt to find the root + // by walking backwards from `rad/id`, which tracks the + // head of the identity COB. + let root = self.identity_root().map_err(|err| { + if err.is_not_found() { + RepositoryError::Doc(DocError::Missing) + } else { + err + } + })?; + + log::debug!(target: "storage", "Obtained identity COB root commit '{root}' via identity root of the repository."); + root + } + Err(err) => { + log::debug!(target: "storage", "Failed to find identity root for founder '{founder}' of identity document '{}': {err}", self.id); + return Err(RepositoryError::Doc(DocError::Missing)); + } + }; + + let doc_at_root = Doc::load_at(root, self)?; + + if doc_at_root.blob != *self.id { + log::debug!(target: "storage", "Root identity document (blob '{}') resolved via '{}' does not match the expected identity document (blob '{}').", doc_at_root.blob, CANONICAL_IDENTITY.as_str(), self.id); + return Err(RepositoryError::Doc(DocError::Missing)); + } + + Ok(Identity::get(&root.into(), self)?.head()) } fn merge_base(&self, left: &Oid, right: &Oid) -> Result {