Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
cli: prevent missing delegate being added
Fintan Halpenny committed 2 years ago
commit 0af233cceb28c8049d6909c974f8145644347750
parent bb5fb5e9d9de66731cb1d717a51005fc8c64c9f5
4 files changed +195 -65
modified radicle-cli/examples/rad-id-multi-delegate.md
@@ -7,12 +7,12 @@ $ rad id update --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --title "Add Bob" --des
$ rad sync --fetch rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi..
✓ Fetched repository from 1 seed(s)
-
$ rad id update --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --title "Add Eve" --description "" --delegate did:key:z6MkedTZGJGqgQ2py2b8kGecfxdt2yRdHWF6JpaZC47fovFn --no-confirm
-
✓ Identity revision 9e5aceb50f9307ddcb29923dbaeb5ccbfd07766c created
+
$ rad id update --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --title "Add Eve" --description "" --delegate did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z --no-confirm
+
✓ Identity revision 3cd3c7f9900de0fcb19705856a7cc339a38fb0b3 created
╭────────────────────────────────────────────────────────────────────────╮
│ Title    Add Eve                                                       │
-
│ Revision 9e5aceb50f9307ddcb29923dbaeb5ccbfd07766c                      │
-
│ Blob     4c7fd4c7b7d7fd5d7088a7c952556fab99a034e9                      │
+
│ Revision 3cd3c7f9900de0fcb19705856a7cc339a38fb0b3                      │
+
│ Blob     74581605d1f75396c331487a10ca61c4815ed685                      │
│ Author   did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk      │
│ State    active                                                        │
│ Quorum   no                                                            │
@@ -34,7 +34,7 @@ $ rad id update --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --title "Add Eve" --des
     "did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
-    "did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk"
+    "did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk",
-
+    "did:key:z6MkedTZGJGqgQ2py2b8kGecfxdt2yRdHWF6JpaZC47fovFn"
+
+    "did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z"
   ],
   "threshold": 2
 }
@@ -43,16 +43,17 @@ $ rad id update --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --title "Add Eve" --des
``` ~alice
$ rad sync --fetch rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkt67…v4N1tRk..
-
✓ Fetched repository from 1 seed(s)
+
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkux1…nVhib7Z..
+
✓ Fetched repository from 2 seed(s)
$ rad inspect rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --delegates
did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (alice)
did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (bob)
-
$ rad id accept 9e5aceb50f9307ddcb29923dbaeb5ccbfd07766c --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --no-confirm
-
✓ Revision 9e5aceb50f9307ddcb29923dbaeb5ccbfd07766c accepted
+
$ rad id accept 3cd3c7f9900de0fcb19705856a7cc339a38fb0b3 --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --no-confirm
+
✓ Revision 3cd3c7f9900de0fcb19705856a7cc339a38fb0b3 accepted
╭────────────────────────────────────────────────────────────────────────╮
│ Title    Add Eve                                                       │
-
│ Revision 9e5aceb50f9307ddcb29923dbaeb5ccbfd07766c                      │
-
│ Blob     4c7fd4c7b7d7fd5d7088a7c952556fab99a034e9                      │
+
│ Revision 3cd3c7f9900de0fcb19705856a7cc339a38fb0b3                      │
+
│ Blob     74581605d1f75396c331487a10ca61c4815ed685                      │
│ Author   did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk      │
│ State    accepted                                                      │
│ Quorum   yes                                                           │
@@ -63,12 +64,12 @@ $ rad id accept 9e5aceb50f9307ddcb29923dbaeb5ccbfd07766c --repo rad:z42hL2jL4XNk
$ rad inspect rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --delegates
did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (alice)
did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (bob)
-
did:key:z6MkedTZGJGqgQ2py2b8kGecfxdt2yRdHWF6JpaZC47fovFn
+
did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z (eve)
```

``` ~alice
$ rad id update --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --title "Make private" --description "" --visibility private --no-confirm -q
-
efb8cdd368b9745396f832386f9c7d46988f6bd5
+
e6bf10593b78384eb2b281cbb18a605668a6d1f7
```

We can list all revisions:
@@ -78,8 +79,8 @@ $ rad id list
╭────────────────────────────────────────────────────────────────────────────────╮
│ ●   ID        Title              Author                     Status     Created │
├────────────────────────────────────────────────────────────────────────────────┤
-
│ ●   efb8cdd   Make private       alice    (you)             active     now     │
-
│ ●   9e5aceb   Add Eve            bob      z6Mkt67…v4N1tRk   accepted   now     │
+
│ ●   e6bf105   Make private       alice    (you)             active     now     │
+
│ ●   3cd3c7f   Add Eve            bob      z6Mkt67…v4N1tRk   accepted   now     │
│ ●   069e7d5   Add Bob            alice    (you)             accepted   now     │
│ ●   0656c21   Initial revision   alice    (you)             accepted   now     │
╰────────────────────────────────────────────────────────────────────────────────╯
@@ -88,24 +89,24 @@ $ rad id list
Despite being a delegate, Bob can't edit or redact Alice's revision:

``` ~bob (fail)
-
$ rad id redact efb8cdd368b9745396f832386f9c7d46988f6bd5
+
$ rad id redact e6bf10593b78384eb2b281cbb18a605668a6d1f7
[..]
```
``` ~bob (fail)
-
$ rad id edit --title "Boo!" --description "Boo!" efb8cdd368b9745396f832386f9c7d46988f6bd5
+
$ rad id edit --title "Boo!" --description "Boo!" e6bf10593b78384eb2b281cbb18a605668a6d1f7
[..]
```

Alice can edit:

``` ~alice
-
$ rad id edit --title "Make private" --description "Privacy is cool." efb8cdd368b9745396f832386f9c7d46988f6bd5
-
✓ Revision efb8cdd368b9745396f832386f9c7d46988f6bd5 edited
-
$ rad id show efb8cdd368b9745396f832386f9c7d46988f6bd5
+
$ rad id edit --title "Make private" --description "Privacy is cool." e6bf10593b78384eb2b281cbb18a605668a6d1f7
+
✓ Revision e6bf10593b78384eb2b281cbb18a605668a6d1f7 edited
+
$ rad id show e6bf10593b78384eb2b281cbb18a605668a6d1f7
╭────────────────────────────────────────────────────────────────────────╮
│ Title    Make private                                                  │
-
│ Revision efb8cdd368b9745396f832386f9c7d46988f6bd5                      │
-
│ Blob     79bc5c39103e811a3c9f11744f9a4029f063a5de                      │
+
│ Revision e6bf10593b78384eb2b281cbb18a605668a6d1f7                      │
+
│ Blob     c533865b2846ca6c5b4436ec6872257293380c3b                      │
│ Author   did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi      │
│ State    active                                                        │
│ Quorum   no                                                            │
@@ -114,7 +115,7 @@ $ rad id show efb8cdd368b9745396f832386f9c7d46988f6bd5
├────────────────────────────────────────────────────────────────────────┤
│ ✓ did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi alice (you) │
│ ? did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk bob         │
-
│ ? did:key:z6MkedTZGJGqgQ2py2b8kGecfxdt2yRdHWF6JpaZC47fovFn             │
+
│ ? did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z eve         │
╰────────────────────────────────────────────────────────────────────────╯

@@ -1,15 +1,18 @@
@@ -129,7 +130,7 @@ $ rad id show efb8cdd368b9745396f832386f9c7d46988f6bd5
   "delegates": [
     "did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
     "did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk",
-
     "did:key:z6MkedTZGJGqgQ2py2b8kGecfxdt2yRdHWF6JpaZC47fovFn"
+
     "did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z"
   ],
-  "threshold": 2
+  "threshold": 2,
@@ -142,29 +143,29 @@ $ rad id show efb8cdd368b9745396f832386f9c7d46988f6bd5
And she can redact her revision:

``` ~alice
-
$ rad id redact efb8cdd368b9745396f832386f9c7d46988f6bd5
-
✓ Revision efb8cdd368b9745396f832386f9c7d46988f6bd5 redacted
+
$ rad id redact e6bf10593b78384eb2b281cbb18a605668a6d1f7
+
✓ Revision e6bf10593b78384eb2b281cbb18a605668a6d1f7 redacted
```
``` ~alice (fail)
-
$ rad id show efb8cdd368b9745396f832386f9c7d46988f6bd5
-
✗ Error: revision `efb8cdd368b9745396f832386f9c7d46988f6bd5` not found
+
$ rad id show e6bf10593b78384eb2b281cbb18a605668a6d1f7
+
✗ Error: revision `e6bf10593b78384eb2b281cbb18a605668a6d1f7` not found
```

Finally, Alice can also propose to remove Bob:
``` ~alice
$ rad id update --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --title "Remove Bob" --description "" --rescind did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --no-confirm
-
✓ Identity revision faf5fb018803d2883e9906bb9c08b6ec83aa55dd created
+
✓ Identity revision 8ba242a80bc1181f41f9ea7a19286038c7948994 created
╭────────────────────────────────────────────────────────────────────────╮
│ Title    Remove Bob                                                    │
-
│ Revision faf5fb018803d2883e9906bb9c08b6ec83aa55dd                      │
-
│ Blob     7109c1c201c223dd4e9fdb10f7330dc6f0310258                      │
+
│ Revision 8ba242a80bc1181f41f9ea7a19286038c7948994                      │
+
│ Blob     254d62de237117e7d7b9ceff85c47f5e3b610c1e                      │
│ Author   did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi      │
│ State    active                                                        │
│ Quorum   no                                                            │
├────────────────────────────────────────────────────────────────────────┤
│ ✓ did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi alice (you) │
│ ? did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk bob         │
-
│ ? did:key:z6MkedTZGJGqgQ2py2b8kGecfxdt2yRdHWF6JpaZC47fovFn             │
+
│ ? did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z eve         │
╰────────────────────────────────────────────────────────────────────────╯

@@ -1,15 +1,14 @@
@@ -179,7 +180,7 @@ $ rad id update --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --title "Remove Bob" --
   "delegates": [
     "did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
-    "did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk",
-
     "did:key:z6MkedTZGJGqgQ2py2b8kGecfxdt2yRdHWF6JpaZC47fovFn"
+
     "did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z"
   ],
   "threshold": 2
 }
modified radicle-cli/examples/rad-id.md
@@ -10,24 +10,24 @@ command. For now, since we are the only delegate, and `treshold` is `1`, we
can update the identity ourselves.

Let's add Bob as a delegate using their DID,
-
`did:key:z6MkedTZGJGqgQ2py2b8kGecfxdt2yRdHWF6JpaZC47fovFn`, and update the
+
`did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk`, and update the
threshold to `2`.

```
-
$ rad id update --title "Add Bob" --description "Add Bob as a delegate" --delegate did:key:z6MkedTZGJGqgQ2py2b8kGecfxdt2yRdHWF6JpaZC47fovFn --threshold 2
-
✓ Identity revision 07829cdd1993295cd6be18de6219fead428b4a5e created
-
╭───────────────────────────────────────────────────────────────────╮
-
│ Title    Add Bob                                                  │
-
│ Revision 07829cdd1993295cd6be18de6219fead428b4a5e                 │
-
│ Blob     7109c1c201c223dd4e9fdb10f7330dc6f0310258                 │
-
│ Author   did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi │
-
│ State    accepted                                                 │
-
│ Quorum   yes                                                      │
-
│                                                                   │
-
│ Add Bob as a delegate                                             │
-
├───────────────────────────────────────────────────────────────────┤
-
│ ✓ did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi  (you) │
-
╰───────────────────────────────────────────────────────────────────╯
+
$ rad id update --title "Add Bob" --description "Add Bob as a delegate" --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --threshold 2
+
✓ Identity revision 0ca42d376bd566631083c8913cf86bec722da392 created
+
╭────────────────────────────────────────────────────────────────────────╮
+
│ Title    Add Bob                                                       │
+
│ Revision 0ca42d376bd566631083c8913cf86bec722da392                      │
+
│ Blob     053541ba7b90534b35dd8718e0ceaa408979b02b                      │
+
│ Author   did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi      │
+
│ State    accepted                                                      │
+
│ Quorum   yes                                                           │
+
│                                                                        │
+
│ Add Bob as a delegate                                                  │
+
├────────────────────────────────────────────────────────────────────────┤
+
│ ✓ did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi alice (you) │
+
╰────────────────────────────────────────────────────────────────────────╯

@@ -1,13 +1,14 @@
 {
@@ -41,7 +41,7 @@ $ rad id update --title "Add Bob" --description "Add Bob as a delegate" --delega
   "delegates": [
-    "did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"
+    "did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
-
+    "did:key:z6MkedTZGJGqgQ2py2b8kGecfxdt2yRdHWF6JpaZC47fovFn"
+
+    "did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk"
   ],
-  "threshold": 1
+  "threshold": 2
@@ -56,7 +56,7 @@ the delegates and threshold:
      "delegates": [
    -   "did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"
    +   "did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
-
    +   "did:key:z6MkedTZGJGqgQ2py2b8kGecfxdt2yRdHWF6JpaZC47fovFn"
+
    +   "did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk"
      ],
    ...
    -  "threshold": 1
@@ -85,7 +85,7 @@ $ rad inspect --identity
  },
  "delegates": [
    "did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
-
    "did:key:z6MkedTZGJGqgQ2py2b8kGecfxdt2yRdHWF6JpaZC47fovFn"
+
    "did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk"
  ],
  "threshold": 2
}
@@ -94,16 +94,16 @@ $ rad inspect --identity
We can also look at the document's COB directly:
```
$ rad cob show --object 0656c217f917c3e06234771e9ecae53aba5e173e --type xyz.radicle.id --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
-
commit 07829cdd1993295cd6be18de6219fead428b4a5e
+
commit 0ca42d376bd566631083c8913cf86bec722da392
parent 0656c217f917c3e06234771e9ecae53aba5e173e
author z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
date   Thu, 15 Dec 2022 17:28:04 +0000

    {
-
      "blob": "7109c1c201c223dd4e9fdb10f7330dc6f0310258",
+
      "blob": "053541ba7b90534b35dd8718e0ceaa408979b02b",
      "description": "Add Bob as a delegate",
      "parent": "0656c217f917c3e06234771e9ecae53aba5e173e",
-
      "signature": "z3sne3sdReZ4AtgxQmn7R1pQnz7E9ZEUoRfCJDJ8ytgnBMFW4DJqRHuBz2h1NK4QdGEy3QCpyVoJKfE95tNoivXwz",
+
      "signature": "z3AyzixN2eWLtRfQWowtBXwWyRH3iJ8oJ25W6KFYFw5ANLntbzfavge15muNU6AVAUkxSxQvgg9yh2gupbUecavQY",
      "title": "Add Bob",
      "type": "revision"
    }
@@ -126,14 +126,24 @@ Note that once a revision is accepted, it can't be edited, redacted or otherwise
acted upon:

``` (fail)
-
$ rad id redact 07829cdd1993295cd6be18de6219fead428b4a5e
+
$ rad id redact 0ca42d376bd566631083c8913cf86bec722da392
✗ Error: [..]
```
``` (fail)
-
$ rad id reject 07829cdd1993295cd6be18de6219fead428b4a5e
+
$ rad id reject 0ca42d376bd566631083c8913cf86bec722da392
✗ Error: [..]
```
``` (fail)
-
$ rad id accept 07829cdd1993295cd6be18de6219fead428b4a5e
+
$ rad id accept 0ca42d376bd566631083c8913cf86bec722da392
✗ Error: [..]
```
+

+
If we attempt to add a delegate that we do not have locally, then we
+
will be told that they are missing in our repository:
+

+
``` (fail)
+
$ rad id update --title "Add Eve" --description "Add Eve as a delegate" --delegate did:key:z6MkedTZGJGqgQ2py2b8kGecfxdt2yRdHWF6JpaZC47fovFn
+
✗ Error: failed to verify delegates for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
+
✗ Error: missing delegate z6MkedT…47fovFn in local storage
+
✗ Error: fatal: refusing to update identity document
+
```
modified radicle-cli/src/commands/id.rs
@@ -6,7 +6,8 @@ use nonempty::NonEmpty;
use radicle::cob::identity::{self, IdentityMut, Revision, RevisionId};
use radicle::identity::{doc, Identity, Visibility};
use radicle::prelude::{Did, Doc, Id, Signer};
-
use radicle::storage::{ReadStorage as _, WriteRepository};
+
use radicle::storage::refs;
+
use radicle::storage::{ReadRepository, ReadStorage as _, WriteRepository};
use radicle::{cob, Profile};
use radicle_crypto::Verified;
use radicle_surf::diff::Diff;
@@ -356,6 +357,14 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
                    "at lease one delegate must be present for the identity to be valid"
                ))?;

+
                if let Some(errs) = verify_delegates(&proposal.delegates, &repo)? {
+
                    term::error(format!("failed to verify delegates for {rid}"));
+
                    for e in errs {
+
                        e.print();
+
                    }
+
                    anyhow::bail!("fatal: refusing to update identity document");
+
                }
+

                for (id, key, val) in payload {
                    if let Some(ref mut payload) = proposal.payload.get_mut(&id) {
                        if let Some(obj) = payload.as_object_mut() {
@@ -643,3 +652,54 @@ fn print_diff(
    }
    Ok(())
}
+

+
enum VerificationError {
+
    Missing(Did),
+
    MissingDefaultBranch {
+
        branch: radicle::git::RefString,
+
        did: Did,
+
    },
+
}
+

+
impl VerificationError {
+
    fn print(&self) {
+
        match self {
+
            VerificationError::Missing(did) => term::error(format!(
+
                "missing delegate {} in local storage",
+
                term::format::did(did)
+
            )),
+
            VerificationError::MissingDefaultBranch { branch, did } => term::error(format!(
+
                "missing {} for {} in local storage",
+
                term::format::secondary(branch),
+
                term::format::did(did)
+
            )),
+
        }
+
    }
+
}
+

+
fn verify_delegates<S>(
+
    dids: &NonEmpty<Did>,
+
    repo: &S,
+
) -> anyhow::Result<Option<Vec<VerificationError>>>
+
where
+
    S: ReadRepository,
+
{
+
    let (canonical, _) = repo.canonical_head()?;
+
    let mut errors = Vec::with_capacity(dids.len());
+
    for did in dids {
+
        match refs::SignedRefsAt::load((*did).into(), repo)? {
+
            None => {
+
                errors.push(VerificationError::Missing(*did));
+
            }
+
            Some(refs::SignedRefsAt { sigrefs, .. }) => {
+
                if sigrefs.get(&canonical).is_none() {
+
                    errors.push(VerificationError::MissingDefaultBranch {
+
                        branch: canonical.to_ref_string(),
+
                        did: *did,
+
                    })
+
                }
+
            }
+
        }
+
    }
+
    Ok((!errors.is_empty()).then_some(errors))
+
}
modified radicle-cli/tests/commands.rs
@@ -256,15 +256,54 @@ fn rad_checkout() {
#[test]
fn rad_id() {
    let mut environment = Environment::new();
-
    let profile = environment.profile("alice");
+
    let alice = environment.node(Config::test(Alias::new("alice")));
+
    let bob = environment.node(Config::test(Alias::new("bob")));
    let working = tempfile::tempdir().unwrap();
-
    let home = &profile.home;
+
    let working = working.path();
+
    let acme = Id::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap();

    // Setup a test repository.
-
    fixtures::repository(working.path());
+
    fixtures::repository(working.join("alice"));

-
    test("examples/rad-init.md", working.path(), Some(home), []).unwrap();
-
    test("examples/rad-id.md", working.path(), Some(home), []).unwrap();
+
    test(
+
        "examples/rad-init.md",
+
        working.join("alice"),
+
        Some(&alice.home),
+
        [],
+
    )
+
    .unwrap();
+

+
    let mut alice = alice.spawn();
+
    let bob = bob.spawn();
+

+
    alice.handle.track_repo(acme, Scope::All).unwrap();
+
    alice.connect(&bob).converge([&bob]);
+
    let events = alice.handle.events();
+

+
    test(
+
        "examples/rad-clone.md",
+
        working.join("bob"),
+
        Some(&bob.home),
+
        [],
+
    )
+
    .unwrap();
+
    alice.has_inventory_of(&acme, &bob.id);
+

+
    // Alice must have Bob to try add them as a delegate
+
    events
+
        .wait(
+
            |e| matches!(e, Event::RefsFetched { .. }).then_some(()),
+
            time::Duration::from_secs(6),
+
        )
+
        .unwrap();
+

+
    test(
+
        "examples/rad-id.md",
+
        working.join("alice"),
+
        Some(&alice.home),
+
        [],
+
    )
+
    .unwrap();
}

#[test]
@@ -272,6 +311,7 @@ fn rad_id_multi_delegate() {
    let mut environment = Environment::new();
    let alice = environment.node(Config::test(Alias::new("alice")));
    let bob = environment.node(Config::test(Alias::new("bob")));
+
    let eve = environment.node(Config::test(Alias::new("eve")));
    let working = tempfile::tempdir().unwrap();
    let working = working.path();
    let acme = Id::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap();
@@ -289,9 +329,12 @@ fn rad_id_multi_delegate() {

    let mut alice = alice.spawn();
    let mut bob = bob.spawn();
+
    let mut eve = eve.spawn();

-
    bob.handle.track_repo(acme, Scope::All).unwrap();
+
    alice.handle.track_repo(acme, Scope::All).unwrap();
+
    bob.handle.track_node(eve.id, None).unwrap();
    alice.connect(&bob).converge([&bob]);
+
    eve.connect(&alice).converge([&alice]);

    test(
        "examples/rad-clone.md",
@@ -300,6 +343,18 @@ fn rad_id_multi_delegate() {
        [],
    )
    .unwrap();
+
    bob.has_inventory_of(&acme, &alice.id);
+
    alice.has_inventory_of(&acme, &bob.id);
+

+
    test(
+
        "examples/rad-clone-all.md",
+
        working.join("eve"),
+
        Some(&eve.home),
+
        [],
+
    )
+
    .unwrap();
+
    eve.has_inventory_of(&acme, &bob.id);
+
    alice.has_inventory_of(&acme, &eve.id);

    // TODO: Have formula with two connected nodes and a tracked project.

@@ -340,9 +395,8 @@ fn rad_id_conflict() {
    .unwrap();

    let mut alice = alice.spawn();
-
    let mut bob = bob.spawn();
+
    let bob = bob.spawn();

-
    bob.handle.track_repo(acme, Scope::All).unwrap();
    alice.connect(&bob).converge([&bob]);

    test(
@@ -352,6 +406,7 @@ fn rad_id_conflict() {
        [],
    )
    .unwrap();
+
    alice.has_inventory_of(&acme, &bob.id);

    formula(&environment.tmp(), "examples/rad-id-conflict.md")
        .unwrap()
@@ -1553,6 +1608,7 @@ fn git_push_diverge() {
    let alice = environment.node(Config::test(Alias::new("alice")));
    let bob = environment.node(Config::test(Alias::new("bob")));
    let working = environment.tmp().join("working");
+
    let acme = Id::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap();

    fixtures::repository(working.join("alice"));

@@ -1576,6 +1632,7 @@ fn git_push_diverge() {
        [],
    )
    .unwrap();
+
    alice.has_inventory_of(&acme, &bob.id);

    formula(&environment.tmp(), "examples/git/git-push-diverge.md")
        .unwrap()
@@ -1599,6 +1656,7 @@ fn rad_push_and_pull_patches() {
    let alice = environment.node(Config::test(Alias::new("alice")));
    let bob = environment.node(Config::test(Alias::new("bob")));
    let working = environment.tmp().join("working");
+
    let acme = Id::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap();

    fixtures::repository(working.join("alice"));

@@ -1624,6 +1682,7 @@ fn rad_push_and_pull_patches() {
        [],
    )
    .unwrap();
+
    alice.has_inventory_of(&acme, &bob.id);

    formula(&environment.tmp(), "examples/rad-push-and-pull-patches.md")
        .unwrap()