Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
cob: Simplify `Entry` and `Change` types
cloudhead committed 2 years ago
commit 205c15caa692813510f2b0d5f09d6956c75b7eab
parent f6bd7a3dc6176adbd8b55f3a0801b6ecb26b1649
24 files changed +132 -356
modified radicle-cli/src/commands/patch/edit.rs
@@ -36,7 +36,7 @@ pub fn run(
        return Ok(());
    }

-
    let root = patch.id.into();
+
    let root = *patch.id;
    let target = patch.target();

    patch.transaction("Edit", &signer, |tx| {
modified radicle-cli/src/commands/patch/list.rs
@@ -145,7 +145,7 @@ pub fn timeline(

    for (revision_id, revision) in patch.revisions() {
        // Don't show an "update" line for the first revision.
-
        if **revision_id != **patch_id {
+
        if *revision_id != **patch_id {
            timeline.push((
                revision.timestamp(),
                term::Line::spaced(
modified radicle-cob/src/backend/git/change.rs
@@ -12,11 +12,10 @@ use once_cell::sync::Lazy;
use radicle_git_ext::commit::trailers::OwnedTrailer;

use crate::change::store::Version;
-
use crate::history::entry::Timestamp;
use crate::signatures;
use crate::{
-
    change::{self, store, Change},
-
    history::entry,
+
    change,
+
    change::{store, Contents, Entry, Timestamp},
    signatures::{ExtendedSignature, Signatures},
    trailers, Embed,
};
@@ -99,7 +98,7 @@ impl change::Storage for git2::Repository {
        parents: Vec<Self::Parent>,
        signer: &Signer,
        spec: store::Template<Self::ObjectId>,
-
    ) -> Result<Change, Self::StoreError>
+
    ) -> Result<Entry, Self::StoreError>
    where
        Signer: crypto::Signer,
    {
@@ -129,7 +128,7 @@ impl change::Storage for git2::Repository {
            tree,
        )?;

-
        Ok(Change {
+
        Ok(Entry {
            id,
            revision: revision.into(),
            signature,
@@ -149,7 +148,7 @@ impl change::Storage for git2::Repository {
            .collect::<Vec<_>>())
    }

-
    fn load(&self, id: Self::ObjectId) -> Result<Change, Self::LoadError> {
+
    fn load(&self, id: Self::ObjectId) -> Result<Entry, Self::LoadError> {
        let commit = Commit::read(self, id.into())?;
        let timestamp = git2::Time::from(commit.committer().time).seconds() as u64;
        let resource = parse_resource_trailer(commit.trailers())?;
@@ -172,7 +171,7 @@ impl change::Storage for git2::Repository {
        let manifest = load_manifest(self, &tree)?;
        let contents = load_contents(self, &tree)?;

-
        Ok(Change {
+
        Ok(Entry {
            id,
            revision: tree.id().into(),
            signature: ExtendedSignature::new(key, sig),
@@ -220,10 +219,7 @@ fn load_manifest(
    })
}

-
fn load_contents(
-
    repo: &git2::Repository,
-
    tree: &git2::Tree,
-
) -> Result<entry::Contents, error::Load> {
+
fn load_contents(repo: &git2::Repository, tree: &git2::Tree) -> Result<Contents, error::Load> {
    let ops = tree
        .iter()
        .filter_map(|entry| {
modified radicle-cob/src/change.rs
@@ -3,11 +3,11 @@
use git_ext::Oid;

pub mod store;
-
pub use store::{Storage, Template};
+
pub use store::{Contents, EntryId, Storage, Template, Timestamp};

use crate::signatures::ExtendedSignature;

/// A single change in the change graph. The layout of changes in the repository
/// is specified in the RFC (docs/rfc/0662-collaborative-objects.adoc)
/// under "Change Commits".
-
pub type Change = store::Change<Oid, Oid, ExtendedSignature>;
+
pub type Entry = store::Entry<Oid, Oid, ExtendedSignature>;
modified radicle-cob/src/change/store.rs
@@ -6,12 +6,9 @@ use nonempty::NonEmpty;
use radicle_git_ext::Oid;
use serde::{Deserialize, Serialize};

-
use crate::{
-
    history::{Contents, Timestamp},
-
    signatures, TypeName,
-
};
+
use crate::{signatures, TypeName};

-
/// Change storage.
+
/// Change entry storage.
pub trait Storage {
    type StoreError: Error + Send + Sync + 'static;
    type LoadError: Error + Send + Sync + 'static;
@@ -20,7 +17,7 @@ pub trait Storage {
    type Parent;
    type Signatures;

-
    /// Store a new change.
+
    /// Store a new change entry.
    #[allow(clippy::type_complexity)]
    fn store<G>(
        &self,
@@ -28,16 +25,16 @@ pub trait Storage {
        parents: Vec<Self::Parent>,
        signer: &G,
        template: Template<Self::ObjectId>,
-
    ) -> Result<Change<Self::Parent, Self::ObjectId, Self::Signatures>, Self::StoreError>
+
    ) -> Result<Entry<Self::Parent, Self::ObjectId, Self::Signatures>, Self::StoreError>
    where
        G: crypto::Signer;

-
    /// Load a change.
+
    /// Load a change entry.
    #[allow(clippy::type_complexity)]
    fn load(
        &self,
        id: Self::ObjectId,
-
    ) -> Result<Change<Self::Parent, Self::ObjectId, Self::Signatures>, Self::LoadError>;
+
    ) -> Result<Entry<Self::Parent, Self::ObjectId, Self::Signatures>, Self::LoadError>;

    /// Returns the parents of the object with the specified ID.
    fn parents_of(&self, id: &Oid) -> Result<Vec<Oid>, Self::LoadError>;
@@ -52,8 +49,18 @@ pub struct Template<Id> {
    pub contents: NonEmpty<Vec<u8>>,
}

+
/// Entry contents.
+
/// This is the change payload.
+
pub type Contents = NonEmpty<Vec<u8>>;
+

+
/// Local time in seconds since epoch.
+
pub type Timestamp = u64;
+

+
/// A unique identifier for a history entry.
+
pub type EntryId = Oid;
+

#[derive(Clone, Debug)]
-
pub struct Change<Resource, Id, Signature> {
+
pub struct Entry<Resource, Id, Signature> {
    /// The content address of the `Change` itself.
    pub id: Id,
    /// The content address of the tree of the `Change`.
@@ -75,7 +82,7 @@ pub struct Change<Resource, Id, Signature> {
    pub timestamp: Timestamp,
}

-
impl<Resource, Id, S> fmt::Display for Change<Resource, Id, S>
+
impl<Resource, Id, S> fmt::Display for Entry<Resource, Id, S>
where
    Id: fmt::Display,
{
@@ -84,7 +91,7 @@ where
    }
}

-
impl<Resource, Id, Signatures> Change<Resource, Id, Signatures> {
+
impl<Resource, Id, Signatures> Entry<Resource, Id, Signatures> {
    pub fn id(&self) -> &Id {
        &self.id
    }
@@ -102,7 +109,7 @@ impl<Resource, Id, Signatures> Change<Resource, Id, Signatures> {
    }
}

-
impl<R, Id> Change<R, Id, signatures::Signatures>
+
impl<R, Id> Entry<R, Id, signatures::Signatures>
where
    Id: AsRef<[u8]>,
{
@@ -113,13 +120,17 @@ where
    }
}

-
impl<R, Id> Change<R, Id, signatures::ExtendedSignature>
+
impl<R, Id> Entry<R, Id, signatures::ExtendedSignature>
where
    Id: AsRef<[u8]>,
{
    pub fn valid_signatures(&self) -> bool {
        self.signature.verify(self.revision.as_ref())
    }
+

+
    pub fn author(&self) -> &crypto::PublicKey {
+
        &self.signature.key
+
    }
}

#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
modified radicle-cob/src/change_graph.rs
@@ -7,14 +7,14 @@ use git_ext::Oid;
use radicle_dag::Dag;

use crate::{
-
    change, history::EntryId, object, signatures::ExtendedSignature, Change, CollaborativeObject,
-
    Entry, History, ObjectId, TypeName,
+
    change, history::EntryId, object, signatures::ExtendedSignature, CollaborativeObject, Entry,
+
    History, ObjectId, TypeName,
};

/// The graph of changes for a particular collaborative object
pub(super) struct ChangeGraph {
    object_id: ObjectId,
-
    graph: Dag<Oid, Change>,
+
    graph: Dag<Oid, Entry>,
}

impl ChangeGraph {
@@ -96,20 +96,7 @@ impl ChangeGraph {
                if !change.valid_signatures() {
                    return ControlFlow::Break(graph);
                }
-
                let entry = Entry::new(
-
                    *change.id(),
-
                    change.signature.key,
-
                    change.resource,
-
                    change.contents().clone(),
-
                    change
-
                        .parents
-
                        .iter()
-
                        .cloned()
-
                        .map(|oid| oid.into())
-
                        .collect(),
-
                    change.timestamp,
-
                    change.manifest.clone(),
-
                );
+
                let entry = change.value.clone();
                let id = *entry.id();

                graph.node(id, entry);
@@ -141,7 +128,7 @@ impl ChangeGraph {
}

struct GraphBuilder {
-
    graph: Dag<Oid, Change>,
+
    graph: Dag<Oid, Entry>,
}

impl Default for GraphBuilder {
@@ -157,7 +144,7 @@ impl GraphBuilder {
        &mut self,
        storage: &S,
        commit_id: Oid,
-
        change: Change,
+
        change: Entry,
    ) -> Result<Vec<(Oid, Oid)>, S::LoadError>
    where
        S: change::Storage<ObjectId = Oid, Parent = Oid, Signatures = ExtendedSignature>,
modified radicle-cob/src/history.rs
@@ -3,13 +3,9 @@
use std::{cmp::Ordering, collections::BTreeSet, ops::ControlFlow};

use git_ext::Oid;
-
use radicle_crypto::PublicKey;
use radicle_dag::Dag;

-
pub mod entry;
-
pub use entry::{Contents, Entry, EntryId, Timestamp};
-

-
use crate::Manifest;
+
pub use crate::change::{Contents, Entry, EntryId, Timestamp};

/// The DAG of changes making up the history of a collaborative object.
#[derive(Clone, Debug)]
@@ -36,32 +32,11 @@ impl History {
        Self { root, graph }
    }

-
    pub fn new_from_root<Id>(
-
        id: Id,
-
        actor: PublicKey,
-
        resource: Oid,
-
        contents: Contents,
-
        timestamp: Timestamp,
-
        manifest: Manifest,
-
    ) -> Self
-
    where
-
        Id: Into<EntryId>,
-
    {
-
        let id = id.into();
-
        let root = Entry {
-
            id,
-
            actor,
-
            resource,
-
            contents,
-
            parents: vec![],
-
            timestamp,
-
            manifest,
-
        };
-

-
        Self {
-
            root: id,
-
            graph: Dag::root(id, root),
-
        }
+
    /// Create a new history from a root entry.
+
    pub fn new_from_root(root: Entry) -> Self {
+
        let id = *root.id();
+

+
        Self::new(id, Dag::root(id, root))
    }

    /// Get the current history timestamp.
@@ -69,17 +44,14 @@ impl History {
    pub fn timestamp(&self) -> Timestamp {
        self.graph
            .tips()
-
            .map(|(_, n)| n.timestamp())
+
            .map(|(_, n)| n.timestamp)
            .max()
            .unwrap_or_default()
    }

    /// Get all the tips of the graph.
    pub fn tips(&self) -> BTreeSet<Oid> {
-
        self.graph
-
            .tips()
-
            .map(|(_, entry)| (*entry.id()).into())
-
            .collect()
+
        self.graph.tips().map(|(_, entry)| *entry.id()).collect()
    }

    /// A topological (parents before children) traversal of the dependency
@@ -109,34 +81,14 @@ impl History {
    }

    /// Extend this history with a new entry.
-
    pub fn extend<Id>(
-
        &mut self,
-
        new_id: Id,
-
        new_actor: PublicKey,
-
        new_resource: Oid,
-
        new_contents: Contents,
-
        new_parents: Vec<EntryId>,
-
        new_timestamp: Timestamp,
-
        manifest: Manifest,
-
    ) where
-
        Id: Into<EntryId>,
-
    {
+
    pub fn extend(&mut self, change: Entry) {
        let tips = self.tips();
-
        let new_id = new_id.into();
-
        let new_entry = Entry::new(
-
            new_id,
-
            new_actor,
-
            new_resource,
-
            new_contents,
-
            new_parents,
-
            new_timestamp,
-
            manifest,
-
        );
+
        let id = *change.id();

-
        self.graph.node(new_id, new_entry);
+
        self.graph.node(id, change);

        for tip in tips {
-
            self.graph.dependency(new_id, (*tip).into());
+
            self.graph.dependency(id, (*tip).into());
        }
    }

deleted radicle-cob/src/history/entry.rs
@@ -1,162 +0,0 @@
-
// Copyright © 2021 The Radicle Link Contributors
-

-
use std::fmt;
-
use std::ops::Deref;
-
use std::str::FromStr;
-

-
use git_ext::Oid;
-
use nonempty::NonEmpty;
-
use radicle_crypto::PublicKey;
-
use serde::{Deserialize, Serialize};
-

-
use crate::{object, Manifest, ObjectId, Version};
-

-
/// Entry contents.
-
/// This is the change payload.
-
pub type Contents = NonEmpty<Vec<u8>>;
-

-
/// Local time in seconds since epoch.
-
pub type Timestamp = u64;
-

-
/// A unique identifier for a history entry.
-
#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq, PartialOrd, Ord, Serialize, Deserialize)]
-
pub struct EntryId(Oid);
-

-
impl fmt::Display for EntryId {
-
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-
        write!(f, "{}", self.0)
-
    }
-
}
-

-
impl FromStr for EntryId {
-
    type Err = git_ext::Error;
-

-
    fn from_str(s: &str) -> Result<Self, Self::Err> {
-
        let oid = git_ext::Oid::try_from(s)?;
-

-
        Ok(Self(oid))
-
    }
-
}
-

-
impl From<git2::Oid> for EntryId {
-
    fn from(id: git2::Oid) -> Self {
-
        Self(id.into())
-
    }
-
}
-

-
impl From<Oid> for EntryId {
-
    fn from(id: Oid) -> Self {
-
        Self(id)
-
    }
-
}
-

-
impl From<EntryId> for Oid {
-
    fn from(EntryId(id): EntryId) -> Self {
-
        id
-
    }
-
}
-

-
impl From<&EntryId> for object::ObjectId {
-
    fn from(EntryId(id): &EntryId) -> Self {
-
        id.into()
-
    }
-
}
-

-
impl From<ObjectId> for EntryId {
-
    fn from(id: ObjectId) -> Self {
-
        Self(*id)
-
    }
-
}
-

-
impl Deref for EntryId {
-
    type Target = Oid;
-

-
    fn deref(&self) -> &Self::Target {
-
        &self.0
-
    }
-
}
-

-
/// One entry in the dependency graph for a change
-
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
-
pub struct Entry {
-
    /// The identifier for this entry
-
    pub(super) id: EntryId,
-
    /// The actor that authored this entry.
-
    pub(super) actor: PublicKey,
-
    /// The content-address for the resource this entry lives under.
-
    /// If the resource was updated, this should point to its latest version.
-
    pub(super) resource: Oid,
-
    /// Parent entries.
-
    pub(super) parents: Vec<EntryId>,
-
    /// The contents of this entry.
-
    pub(super) contents: Contents,
-
    /// The entry timestamp, as seconds since epoch.
-
    pub(super) timestamp: Timestamp,
-
    /// COB manifest.
-
    pub(super) manifest: Manifest,
-
}
-

-
impl Entry {
-
    pub fn new<Id>(
-
        id: Id,
-
        actor: PublicKey,
-
        resource: Oid,
-
        contents: Contents,
-
        parents: Vec<EntryId>,
-
        timestamp: Timestamp,
-
        manifest: Manifest,
-
    ) -> Self
-
    where
-
        Id: Into<EntryId>,
-
    {
-
        Self {
-
            id: id.into(),
-
            actor,
-
            resource,
-
            contents,
-
            timestamp,
-
            parents,
-
            manifest,
-
        }
-
    }
-

-
    /// The current `Oid` of the resource this change lives under.
-
    pub fn resource(&self) -> Oid {
-
        self.resource
-
    }
-

-
    /// Parent entries.
-
    pub fn parents(&self) -> &[EntryId] {
-
        &self.parents
-
    }
-

-
    /// The public key of the actor.
-
    pub fn actor(&self) -> &PublicKey {
-
        &self.actor
-
    }
-

-
    /// The COB version of this entry.
-
    pub fn version(&self) -> &Version {
-
        &self.manifest.version
-
    }
-

-
    /// The COB manifest.
-
    pub fn manifest(&self) -> &Manifest {
-
        &self.manifest
-
    }
-

-
    /// The entry timestamp.
-
    pub fn timestamp(&self) -> Timestamp {
-
        self.timestamp
-
    }
-

-
    /// The contents of this change
-
    pub fn contents(&self) -> &Contents {
-
        &self.contents
-
    }
-

-
    /// Entry ID.
-
    pub fn id(&self) -> &EntryId {
-
        &self.id
-
    }
-
}
modified radicle-cob/src/lib.rs
@@ -6,9 +6,6 @@
//! Collaborative objects are graphs of CRDTs. The current CRDTs that
//! is intended to be used are specifically [automerge] CRDTs.
//!
-
//! The initial design is proposed at [RFC-0662], and this
-
//! implementation keeps to most of its design principle.
-
//!
//! ## Basic Types
//!
//! The basic types that are found in `radicle-cob` are:
@@ -55,15 +52,14 @@
//! automerge document and deserialize into an application defined
//! object.
//!
-
//! This traversal is also the point at which the [`Entry::actor`]
+
//! This traversal is also the point at which the [`Entry::author`]
//! and [`Entry::resource`] can be retrieved to apply any kind of
-
//! filtering logic. For example, a specific `actor`'s change may be
+
//! filtering logic. For example, a specific `author`'s change may be
//! egregious, spouting terrible libel about Radicle. It is at this
//! point that the `actor`'s change can be filtered out from the
//! final product of the traversal.
//!
//! [automerge]: https://automerge.org
-
//! [RFC-0662]: https://github.com/radicle-dev/radicle-link/blob/master/docs/rfc/0662-collaborative-objects.adoc

#[cfg(test)]
extern crate qcheck;
@@ -81,11 +77,11 @@ mod change_graph;
mod trailers;

pub mod change;
-
pub use change::store::{Embed, Manifest, Version};
-
pub use change::Change;
+
pub use change::store::{Contents, Embed, Manifest, Version};
+
pub use change::Entry;

pub mod history;
-
pub use history::{Contents, Entry, History};
+
pub use history::History;

pub mod signatures;
use signatures::ExtendedSignature;
modified radicle-cob/src/object/collaboration/create.rs
@@ -36,7 +36,7 @@ impl Create {
/// Create a new [`CollaborativeObject`].
///
/// The `storage` is the backing storage for storing
-
/// [`crate::Change`]s at content-addressable locations. Please see
+
/// [`crate::Entry`]s at content-addressable locations. Please see
/// [`Store`] for further information.
///
/// The `signer` is expected to be a cryptographic signing key. This
@@ -74,14 +74,7 @@ where
        .update(identifier, &type_name, &object_id, &init_change)
        .map_err(|err| error::Create::Refs { err: Box::new(err) })?;

-
    let history = History::new_from_root(
-
        *init_change.id(),
-
        init_change.signature.key,
-
        resource,
-
        init_change.contents,
-
        init_change.timestamp,
-
        init_change.manifest,
-
    );
+
    let history = History::new_from_root(init_change);

    Ok(CollaborativeObject {
        manifest: Manifest::new(type_name, version),
modified radicle-cob/src/object/collaboration/get.rs
@@ -7,7 +7,7 @@ use super::error;
/// Get a [`CollaborativeObject`], if it exists.
///
/// The `storage` is the backing storage for storing
-
/// [`crate::Change`]s at content-addressable locations. Please see
+
/// [`crate::Entry`]s at content-addressable locations. Please see
/// [`Store`] for further information.
///
/// The `typename` is the type of object to be found, while the
modified radicle-cob/src/object/collaboration/info.rs
@@ -27,7 +27,7 @@ pub struct ChangeGraphInfo {
/// is mostly useful for debugging and testing
///
/// The `storage` is the backing storage for storing
-
/// [`crate::Change`]s at content-addressable locations. Please see
+
/// [`crate::Entry`]s at content-addressable locations. Please see
/// [`Store`] for further information.
///
/// The `typename` is the type of object to be found, while the `oid`
modified radicle-cob/src/object/collaboration/list.rs
@@ -7,7 +7,7 @@ use super::error;
/// List a set of [`CollaborativeObject`].
///
/// The `storage` is the backing storage for storing
-
/// [`crate::Change`]s at content-addressable locations. Please see
+
/// [`crate::Entry`]s at content-addressable locations. Please see
/// [`Store`] for further information.
///
/// The `typename` is the type of objects to be listed.
modified radicle-cob/src/object/collaboration/remove.rs
@@ -7,7 +7,7 @@ use super::error;
/// Remove a [`crate::CollaborativeObject`].
///
/// The `storage` is the backing storage for storing
-
/// [`crate::Change`]s at content-addressable locations. Please see
+
/// [`crate::Entry`]s at content-addressable locations. Please see
/// [`Store`] for further information.
///
/// The `typename` is the type of object to be found, while the
modified radicle-cob/src/object/collaboration/update.rs
@@ -38,7 +38,7 @@ pub struct Update {
/// Update an existing [`CollaborativeObject`].
///
/// The `storage` is the backing storage for storing
-
/// [`crate::Change`]s at content-addressable locations. Please see
+
/// [`crate::Entry`]s at content-addressable locations. Please see
/// [`Store`] for further information.
///
/// The `signer` is expected to be a cryptographic signing key. This
@@ -99,21 +99,14 @@ where
        .update(identifier, typename, &object_id, &change)
        .map_err(|err| error::Update::Refs { err: Box::new(err) })?;

-
    let parents: Vec<EntryId> = change.parents.into_iter().map(|oid| oid.into()).collect();
+
    let parents = change.parents.to_vec();
+
    let head = change.id;

-
    object.history.extend(
-
        change.id,
-
        change.signature.key,
-
        change.resource,
-
        change.contents,
-
        parents.clone(),
-
        change.timestamp,
-
        change.manifest,
-
    );
+
    object.history.extend(change);

    Ok(Updated {
        object,
-
        head: change.id,
+
        head,
        parents,
    })
}
modified radicle-cob/src/object/storage.rs
@@ -5,7 +5,7 @@ use std::{collections::BTreeMap, error::Error};
use git_ext::ref_format::RefString;
use git_ext::Oid;

-
use crate::change::Change;
+
use crate::change::Entry;
use crate::{ObjectId, TypeName};

/// The [`Reference`]s that refer to the commits that make up a
@@ -78,7 +78,7 @@ pub trait Storage {
        identifier: &Self::Identifier,
        typename: &TypeName,
        object_id: &ObjectId,
-
        change: &Change,
+
        change: &Entry,
    ) -> Result<(), Self::UpdateError>;

    /// Remove a ref to a particular collaborative object
modified radicle-cob/src/test/storage.rs
@@ -73,7 +73,7 @@ impl change::Storage for Storage {
        signer: &Signer,
        spec: change::Template<Self::ObjectId>,
    ) -> Result<
-
        change::store::Change<Self::Parent, Self::ObjectId, Self::Signatures>,
+
        change::store::Entry<Self::Parent, Self::ObjectId, Self::Signatures>,
        Self::StoreError,
    >
    where
@@ -85,10 +85,8 @@ impl change::Storage for Storage {
    fn load(
        &self,
        id: Self::ObjectId,
-
    ) -> Result<
-
        change::store::Change<Self::Parent, Self::ObjectId, Self::Signatures>,
-
        Self::LoadError,
-
    > {
+
    ) -> Result<change::store::Entry<Self::Parent, Self::ObjectId, Self::Signatures>, Self::LoadError>
+
    {
        self.as_raw().load(id)
    }

@@ -156,7 +154,7 @@ impl object::Storage for Storage {
        identifier: &Self::Identifier,
        typename: &crate::TypeName,
        object_id: &ObjectId,
-
        change: &change::Change,
+
        change: &change::Entry,
    ) -> Result<(), Self::UpdateError> {
        let name = format!(
            "refs/rad/{}/cobs/{}/{}",
modified radicle-cob/src/tests.rs
@@ -213,7 +213,7 @@ fn traverse_cobs() {

    // traverse over the history and filter by changes that were only authorized by terry
    let contents = object.history().traverse(Vec::new(), |mut acc, _, entry| {
-
        if entry.actor() == terry_signer.public_key() {
+
        if entry.author() == terry_signer.public_key() {
            acc.push(entry.contents().head.clone());
        }
        ControlFlow::Continue(acc)
modified radicle/src/cob/issue.rs
@@ -129,7 +129,7 @@ impl store::FromHistory for Issue {
        let root = history.root();

        // Deprecated. Remove when we drop legacy support.
-
        if root.manifest().is_legacy() {
+
        if root.manifest.is_legacy() {
            let legacy = super::legacy::issue::Issue::from_history(history, repo)?;
            let issue = legacy.into();

@@ -673,10 +673,10 @@ mod test {
        let mut issue_bob = bob_issues.get_mut(&id).unwrap();

        issue_bob
-
            .comment("Bob's reply", id.into(), vec![], &t.bob.signer)
+
            .comment("Bob's reply", *id, vec![], &t.bob.signer)
            .unwrap();
        issue_alice
-
            .comment("Alice's reply", id.into(), vec![], &t.alice.signer)
+
            .comment("Alice's reply", *id, vec![], &t.alice.signer)
            .unwrap();

        assert_eq!(issue_bob.comments().count(), 2);
@@ -704,7 +704,7 @@ mod test {
        t.eve.repo.fetch(&t.alice);

        let eve_reply = issue_eve
-
            .comment("Eve's reply", id.into(), vec![], &t.eve.signer)
+
            .comment("Eve's reply", *id, vec![], &t.eve.signer)
            .unwrap();

        t.bob.repo.fetch(&t.eve);
@@ -721,7 +721,7 @@ mod test {
        let (first, _) = issue_bob.comments().next().unwrap();
        let (last, _) = issue_bob.comments().last().unwrap();

-
        assert_eq!(*first, issue_alice.id.into());
+
        assert_eq!(*first, *issue_alice.id);
        assert_eq!(*last, eve_reply);
    }

@@ -1212,7 +1212,7 @@ mod test {
        issue
            .comment(
                "Here's a binary file",
-
                issue.id.into(),
+
                *issue.id,
                [embed3.clone()],
                &node.signer,
            )
modified radicle/src/cob/op.rs
@@ -84,11 +84,11 @@ impl From<Entry> for Op<Vec<u8>> {
        Self {
            id: *entry.id(),
            actions: entry.contents().clone(),
-
            author: *entry.actor(),
-
            parents: entry.parents().to_owned(),
-
            timestamp: Timestamp::from_secs(entry.timestamp()),
-
            identity: entry.resource(),
-
            manifest: entry.manifest().clone(),
+
            author: *entry.author(),
+
            parents: entry.parents.to_owned(),
+
            timestamp: Timestamp::from_secs(entry.timestamp),
+
            identity: entry.resource,
+
            manifest: entry.manifest.clone(),
        }
    }
}
@@ -101,13 +101,13 @@ where

    fn try_from(entry: &'a Entry) -> Result<Self, Self::Error> {
        let id = *entry.id();
-
        let identity = entry.resource();
+
        let identity = *entry.resource();
        let actions: Vec<_> = entry
            .contents()
            .iter()
            .map(|blob| serde_json::from_slice(blob.as_slice()))
            .collect::<Result<_, _>>()?;
-
        let manifest = entry.manifest().clone();
+
        let manifest = entry.manifest.clone();

        // SAFETY: Entry is guaranteed to have at least one operation.
        #[allow(clippy::unwrap_used)]
@@ -115,9 +115,9 @@ where
        let op = Op {
            id,
            actions,
-
            author: *entry.actor(),
-
            timestamp: Timestamp::from_secs(entry.timestamp()),
-
            parents: entry.parents().to_owned(),
+
            author: *entry.author(),
+
            timestamp: Timestamp::from_secs(entry.timestamp),
+
            parents: entry.parents.to_owned(),
            identity,
            manifest,
        };
modified radicle/src/cob/patch.rs
@@ -469,7 +469,7 @@ impl store::FromHistory for Patch {
        let root = history.root();

        // Deprecated. Remove when we drop legacy support.
-
        if root.manifest().is_legacy() {
+
        if root.manifest.is_legacy() {
            let legacy = super::legacy::patch::Patch::from_history(history, repo)?;
            let patch = legacy.into();

@@ -1379,7 +1379,7 @@ where
        revision: RevisionId,
        signer: &G,
    ) -> Result<EntryId, Error> {
-
        if revision == RevisionId::from(self.id) {
+
        if revision == RevisionId::from(*self.id) {
            return Err(Error::RootRevision(revision));
        }
        self.transaction("Redact revision", signer, |tx| tx.redact(revision))
@@ -2223,7 +2223,7 @@ mod test {
        assert_eq!(patch.revisions().count(), 2);

        patch.redact(revision_id, &alice.signer).unwrap();
-
        assert_eq!(patch.latest().0, &RevisionId::from(patch_id));
+
        assert_eq!(patch.latest().0, &RevisionId::from(*patch_id));
        assert_eq!(patch.revisions().count(), 1);

        // The patch's root must always exist.
modified radicle/src/cob/test.rs
@@ -2,6 +2,7 @@ use std::marker::PhantomData;
use std::ops::Deref;

use nonempty::NonEmpty;
+
use radicle_crypto::ssh::ExtendedSignature;
use serde::{Deserialize, Serialize};

use crate::cob::op::Op;
@@ -58,18 +59,24 @@ where
{
    pub fn new<G: Signer>(action: &T::Action, time: Timestamp, signer: &G) -> HistoryBuilder<T> {
        let resource = arbitrary::oid();
+
        let revision = arbitrary::oid();
        let (data, root) = encoded::<T, _>(action, time, [], signer);
        let manifest = Manifest::new(T::type_name().clone(), Version::default());
+
        let signature = signer.sign(data.as_slice());
+
        let signature = ExtendedSignature::new(*signer.public_key(), signature);
+
        let change = Entry {
+
            id: root,
+
            signature,
+
            resource,
+
            contents: NonEmpty::new(data),
+
            timestamp: time.as_secs(),
+
            revision,
+
            parents: vec![],
+
            manifest,
+
        };

        Self {
-
            history: History::new_from_root(
-
                root,
-
                *signer.public_key(),
-
                resource,
-
                NonEmpty::new(data),
-
                time.as_secs(),
-
                manifest,
-
            ),
+
            history: History::new_from_root(change),
            time,
            resource,
            witness: PhantomData,
@@ -87,18 +94,23 @@ where
    pub fn commit<G: Signer>(&mut self, action: &T::Action, signer: &G) -> git::ext::Oid {
        let timestamp = self.time;
        let tips = self.tips();
+
        let revision = arbitrary::oid();
        let (data, oid) = encoded::<T, _>(action, timestamp, tips, signer);
        let manifest = Manifest::new(T::type_name().clone(), Version::default());
-

-
        self.history.extend(
-
            oid,
-
            *signer.public_key(),
-
            self.resource,
-
            NonEmpty::new(data),
-
            vec![],
-
            timestamp.as_secs(),
+
        let signature = signer.sign(data.as_slice());
+
        let signature = ExtendedSignature::new(*signer.public_key(), signature);
+
        let change = Entry {
+
            id: oid,
+
            signature,
+
            resource: self.resource,
+
            contents: NonEmpty::new(data),
+
            timestamp: timestamp.as_secs(),
+
            revision,
+
            parents: vec![],
            manifest,
-
        );
+
        };
+
        self.history.extend(change);
+

        oid
    }
}
modified radicle/src/storage/git/cob.rs
@@ -58,14 +58,14 @@ impl change::Storage for Repository {
        parents: Vec<Self::Parent>,
        signer: &Signer,
        spec: change::Template<Self::ObjectId>,
-
    ) -> Result<cob::Change, Self::StoreError>
+
    ) -> Result<cob::Entry, Self::StoreError>
    where
        Signer: crypto::Signer,
    {
        self.backend.store(authority, parents, signer, spec)
    }

-
    fn load(&self, id: Self::ObjectId) -> Result<cob::Change, Self::LoadError> {
+
    fn load(&self, id: Self::ObjectId) -> Result<cob::Entry, Self::LoadError> {
        self.backend.load(id)
    }

@@ -139,7 +139,7 @@ impl cob::object::Storage for Repository {
        identifier: &Self::Identifier,
        typename: &cob::TypeName,
        object_id: &cob::ObjectId,
-
        change: &cob::Change,
+
        change: &cob::Entry,
    ) -> Result<(), Self::UpdateError> {
        self.backend.reference(
            git::refs::storage::cob(identifier, typename, object_id).as_str(),
@@ -202,14 +202,14 @@ impl<'a> change::Storage for DraftStore<'a> {
        parents: Vec<Self::Parent>,
        signer: &Signer,
        spec: change::Template<Self::ObjectId>,
-
    ) -> Result<cob::Change, Self::StoreError>
+
    ) -> Result<cob::Entry, Self::StoreError>
    where
        Signer: crypto::Signer,
    {
        self.repo.backend.store(authority, parents, signer, spec)
    }

-
    fn load(&self, id: Self::ObjectId) -> Result<cob::Change, Self::LoadError> {
+
    fn load(&self, id: Self::ObjectId) -> Result<cob::Entry, Self::LoadError> {
        self.repo.backend.load(id)
    }

@@ -387,7 +387,7 @@ impl<'a> cob::object::Storage for DraftStore<'a> {
        identifier: &Self::Identifier,
        typename: &cob::TypeName,
        object_id: &cob::ObjectId,
-
        change: &cob::Change,
+
        change: &cob::Entry,
    ) -> Result<(), Self::UpdateError> {
        self.repo.backend.reference(
            git::refs::storage::draft::cob(identifier, typename, object_id).as_str(),
modified radicle/src/test/arbitrary.rs
@@ -28,7 +28,7 @@ pub fn oid() -> storage::Oid {
}

pub fn entry_id() -> cob::EntryId {
-
    self::oid().into()
+
    self::oid()
}

pub fn refstring(len: usize) -> git::RefString {