Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
Make `Doc` type payload generic
Alexis Sellier committed 3 years ago
commit 98c156610905e04a2d3ee862b64e7974ea21383f
parent e7f26371262c0bfee25a3ee2ebd1585f701f3c7a
20 files changed +332 -220
modified Cargo.lock
@@ -30,9 +30,9 @@ dependencies = [

[[package]]
name = "amplify"
-
version = "4.0.0-beta.4"
+
version = "4.0.0-beta.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "62c3b7fe483c11f7a434ec6923a68749b3415c1328857f1a8e37bb2c792a25c0"
+
checksum = "4ff4d466b0cb5124991d3a2085c28f2aed6d86264c3c0bdbd7d1b64a49b4726b"
dependencies = [
 "amplify_derive",
 "amplify_num",
@@ -543,9 +543,9 @@ dependencies = [

[[package]]
name = "cxx"
-
version = "1.0.82"
+
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "d4a41a86530d0fe7f5d9ea779916b7cadd2d4f9add748b99c2c029cbbdfaf453"
+
checksum = "bdf07d07d6531bfcdbe9b8b739b104610c6508dcc4d63b410585faf338241daf"
dependencies = [
 "cc",
 "cxxbridge-flags",
@@ -555,9 +555,9 @@ dependencies = [

[[package]]
name = "cxx-build"
-
version = "1.0.82"
+
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "06416d667ff3e3ad2df1cd8cd8afae5da26cf9cec4d0825040f88b5ca659a2f0"
+
checksum = "d2eb5b96ecdc99f72657332953d4d9c50135af1bac34277801cc3937906ebd39"
dependencies = [
 "cc",
 "codespan-reporting",
@@ -570,15 +570,15 @@ dependencies = [

[[package]]
name = "cxxbridge-flags"
-
version = "1.0.82"
+
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "820a9a2af1669deeef27cb271f476ffd196a2c4b6731336011e0ba63e2c7cf71"
+
checksum = "ac040a39517fd1674e0f32177648334b0f4074625b5588a64519804ba0553b12"

[[package]]
name = "cxxbridge-macro"
-
version = "1.0.82"
+
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "a08a6e2fcc370a089ad3b4aaf54db3b1b4cee38ddabce5896b33eb693275f470"
+
checksum = "1362b0ddcfc4eb0a1f57b68bd77dd99f0e826958a96abd0ae9bd092e114ffed6"
dependencies = [
 "proc-macro2",
 "quote",
@@ -597,9 +597,9 @@ dependencies = [

[[package]]
name = "data-encoding"
-
version = "2.3.2"
+
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57"
+
checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb"

[[package]]
name = "data-encoding-macro"
@@ -623,9 +623,9 @@ dependencies = [

[[package]]
name = "der"
-
version = "0.6.0"
+
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "13dd2ae565c0a381dde7fade45fce95984c568bdcb4700a4fdbe3175e0380b2f"
+
checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de"
dependencies = [
 "const-oid",
 "pem-rfc7468",
@@ -704,9 +704,9 @@ dependencies = [

[[package]]
name = "ed25519-compact"
-
version = "2.0.2"
+
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "1f2d21333b679bbbac680b3eb45c86937e42f69277028f4e97b599b80b86c253"
+
checksum = "6a3d382e8464107391c8706b4c14b087808ecb909f6c15c34114bc42e53a9e4c"
dependencies = [
 "ct-codecs",
 "getrandom 0.2.8",
@@ -873,9 +873,9 @@ dependencies = [

[[package]]
name = "filetime"
-
version = "0.2.18"
+
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "4b9663d381d07ae25dc88dbdf27df458faa83a9b25336bcac83d5e452b5fc9d3"
+
checksum = "4e884668cd0c7480504233e951174ddc3b382f7c2666e3b7310b5c4e7b0c37f9"
dependencies = [
 "cfg-if",
 "libc",
@@ -1023,10 +1023,28 @@ dependencies = [
[[package]]
name = "git-ref-format"
version = "0.1.0"
+
source = "git+https://github.com/radicle-dev/radicle-git?rev=39ce2f934915a563f9420ac9c85480df8a591481#39ce2f934915a563f9420ac9c85480df8a591481"
+
dependencies = [
+
 "git-ref-format-core 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=39ce2f934915a563f9420ac9c85480df8a591481)",
+
 "git-ref-format-macro 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=39ce2f934915a563f9420ac9c85480df8a591481)",
+
]
+

+
[[package]]
+
name = "git-ref-format"
+
version = "0.1.0"
source = "git+https://github.com/radicle-dev/radicle-git?rev=d3115a22158c8395705babefdc89049f7510d32d#d3115a22158c8395705babefdc89049f7510d32d"
dependencies = [
-
 "git-ref-format-core",
-
 "git-ref-format-macro",
+
 "git-ref-format-core 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=d3115a22158c8395705babefdc89049f7510d32d)",
+
 "git-ref-format-macro 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=d3115a22158c8395705babefdc89049f7510d32d)",
+
]
+

+
[[package]]
+
name = "git-ref-format-core"
+
version = "0.1.0"
+
source = "git+https://github.com/radicle-dev/radicle-git?rev=39ce2f934915a563f9420ac9c85480df8a591481#39ce2f934915a563f9420ac9c85480df8a591481"
+
dependencies = [
+
 "serde",
+
 "thiserror",
]

[[package]]
@@ -1041,9 +1059,20 @@ dependencies = [
[[package]]
name = "git-ref-format-macro"
version = "0.1.0"
+
source = "git+https://github.com/radicle-dev/radicle-git?rev=39ce2f934915a563f9420ac9c85480df8a591481#39ce2f934915a563f9420ac9c85480df8a591481"
+
dependencies = [
+
 "git-ref-format-core 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=39ce2f934915a563f9420ac9c85480df8a591481)",
+
 "proc-macro-error",
+
 "quote",
+
 "syn",
+
]
+

+
[[package]]
+
name = "git-ref-format-macro"
+
version = "0.1.0"
source = "git+https://github.com/radicle-dev/radicle-git?rev=d3115a22158c8395705babefdc89049f7510d32d#d3115a22158c8395705babefdc89049f7510d32d"
dependencies = [
-
 "git-ref-format-core",
+
 "git-ref-format-core 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=d3115a22158c8395705babefdc89049f7510d32d)",
 "proc-macro-error",
 "quote",
 "syn",
@@ -1467,9 +1496,9 @@ checksum = "478ee9e62aaeaf5b140bd4138753d1f109765488581444218d3ddda43234f3e8"

[[package]]
name = "libc"
-
version = "0.2.137"
+
version = "0.2.138"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
+
checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8"

[[package]]
name = "libgit2-sys"
@@ -2067,7 +2096,7 @@ dependencies = [
 "cyphernet",
 "ed25519-compact",
 "fastrand",
-
 "git-ref-format",
+
 "git-ref-format 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=39ce2f934915a563f9420ac9c85480df8a591481)",
 "git2",
 "log",
 "multibase",
@@ -2126,7 +2155,7 @@ dependencies = [
 "ed25519-compact",
 "fastrand",
 "git-commit",
-
 "git-ref-format",
+
 "git-ref-format 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=39ce2f934915a563f9420ac9c85480df8a591481)",
 "git-trailers",
 "git2",
 "log",
@@ -2164,7 +2193,7 @@ dependencies = [
 "cyphernet",
 "ed25519-compact",
 "fastrand",
-
 "git-ref-format",
+
 "git-ref-format 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=39ce2f934915a563f9420ac9c85480df8a591481)",
 "multibase",
 "qcheck",
 "qcheck-macros",
@@ -2183,7 +2212,7 @@ name = "radicle-git-ext"
version = "0.2.0"
source = "git+https://github.com/radicle-dev/radicle-git?rev=d3115a22158c8395705babefdc89049f7510d32d#d3115a22158c8395705babefdc89049f7510d32d"
dependencies = [
-
 "git-ref-format",
+
 "git-ref-format 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=d3115a22158c8395705babefdc89049f7510d32d)",
 "git2",
 "percent-encoding",
 "radicle-std-ext",
@@ -2229,7 +2258,7 @@ dependencies = [
 "colored",
 "crossbeam-channel",
 "fastrand",
-
 "git-ref-format",
+
 "git-ref-format 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=39ce2f934915a563f9420ac9c85480df8a591481)",
 "lexopt",
 "log",
 "nakamoto-net",
@@ -2281,7 +2310,7 @@ dependencies = [
 "base64",
 "either",
 "flate2",
-
 "git-ref-format",
+
 "git-ref-format 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=d3115a22158c8395705babefdc89049f7510d32d)",
 "git2",
 "nom 6.1.2",
 "nonempty 0.5.0",
@@ -2297,7 +2326,7 @@ name = "radicle-tools"
version = "0.2.0"
dependencies = [
 "anyhow",
-
 "git-ref-format",
+
 "git-ref-format 0.1.0 (git+https://github.com/radicle-dev/radicle-git?rev=39ce2f934915a563f9420ac9c85480df8a591481)",
 "radicle",
]

@@ -2489,9 +2518,9 @@ checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6"

[[package]]
name = "rustix"
-
version = "0.36.4"
+
version = "0.36.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "cb93e85278e08bb5788653183213d3a60fc242b10cb9be96586f5a73dcb67c23"
+
checksum = "a3807b5d10909833d3e9acd1eb5fb988f79376ff10fce42937de71a449c4c588"
dependencies = [
 "bitflags",
 "errno",
@@ -2524,9 +2553,9 @@ dependencies = [

[[package]]
name = "scale-info"
-
version = "2.3.0"
+
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "88d8a765117b237ef233705cc2cc4c6a27fccd46eea6ef0c8c6dae5f3ef407f8"
+
checksum = "001cf62ece89779fd16105b5f515ad0e5cedcd5440d3dd806bb067978e7c3608"
dependencies = [
 "cfg-if",
 "derive_more",
@@ -2536,9 +2565,9 @@ dependencies = [

[[package]]
name = "scale-info-derive"
-
version = "2.3.0"
+
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "cdcd47b380d8c4541044e341dcd9475f55ba37ddc50c908d945fc036a8642496"
+
checksum = "303959cf613a6f6efd19ed4b4ad5bf79966a13352716299ad532cfb115f4205c"
dependencies = [
 "proc-macro-crate",
 "proc-macro2",
@@ -2580,18 +2609,18 @@ dependencies = [

[[package]]
name = "serde"
-
version = "1.0.148"
+
version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "e53f64bb4ba0191d6d0676e1b141ca55047d83b74f5607e6d8eb88126c52c2dc"
+
checksum = "256b9932320c590e707b94576e3cc1f7c9024d0ee6612dfbcf1cb106cbe8e055"
dependencies = [
 "serde_derive",
]

[[package]]
name = "serde_derive"
-
version = "1.0.148"
+
version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "a55492425aa53521babf6137309e7d34c20bbfbbfcfe2c7f3a047fd1f6b92c0c"
+
checksum = "b4eae9b04cbffdfd550eb462ed33bc6a1b68c935127d008b27444d08380f94e4"
dependencies = [
 "proc-macro2",
 "quote",
@@ -2901,9 +2930,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"

[[package]]
name = "syn"
-
version = "1.0.104"
+
version = "1.0.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "4ae548ec36cf198c0ef7710d3c230987c2d6d7bd98ad6edc0274462724c585ce"
+
checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908"
dependencies = [
 "proc-macro2",
 "quote",
@@ -3077,9 +3106,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"

[[package]]
name = "tokio"
-
version = "1.22.0"
+
version = "1.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "d76ce4a75fb488c605c54bf610f221cea8b0dafb53333c1a67e8ee199dcd2ae3"
+
checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46"
dependencies = [
 "autocfg",
 "bytes",
@@ -3090,7 +3119,7 @@ dependencies = [
 "pin-project-lite",
 "socket2",
 "tokio-macros",
-
 "winapi",
+
 "windows-sys",
]

[[package]]
@@ -3145,9 +3174,9 @@ dependencies = [

[[package]]
name = "tower-http"
-
version = "0.3.4"
+
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "3c530c8675c1dbf98facee631536fa116b5fb6382d7dd6dc1b118d970eafe3ba"
+
checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858"
dependencies = [
 "bitflags",
 "bytes",
@@ -3258,9 +3287,9 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"

[[package]]
name = "typenum"
-
version = "1.15.0"
+
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
+
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"

[[package]]
name = "uint"
@@ -3556,9 +3585,9 @@ dependencies = [

[[package]]
name = "zeroize_derive"
-
version = "1.3.2"
+
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17"
+
checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c"
dependencies = [
 "proc-macro2",
 "quote",
modified Cargo.toml
@@ -42,4 +42,4 @@ rev = "d3115a22158c8395705babefdc89049f7510d32d"

[patch.crates-io.git-ref-format]
git = "https://github.com/radicle-dev/radicle-git"
-
rev = "d3115a22158c8395705babefdc89049f7510d32d"
+
rev = "39ce2f934915a563f9420ac9c85480df8a591481"
modified radicle-cli/examples/rad-init.md
@@ -15,7 +15,7 @@ ok Project heartwood created
}


-
Your project id is rad:z2TBtGrJKGsremYAPec6vN4n77Ba7. You can show it any time by running:
+
Your project id is rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji. You can show it any time by running:
   rad .

To publish your project to the network, run:
@@ -27,5 +27,5 @@ Projects can be listed with the `ls` command:

```
$ rad ls
-
heartwood rad:z2TBtGrJKGsremYAPec6vN4n77Ba7 cdf76ce Radicle Heartwood Protocol & Stack
+
heartwood rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji cdf76ce Radicle Heartwood Protocol & Stack
```
modified radicle-cli/src/commands/checkout.rs
@@ -79,12 +79,11 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
pub fn execute(options: Options, profile: &Profile) -> anyhow::Result<PathBuf> {
    let id = options.id;
    let storage = &profile.storage;
-
    let Doc {
-
        payload, delegates, ..
-
    } = storage
+
    let doc = storage
        .repository(id)?
-
        .project_of(profile.id())
+
        .identity_of(profile.id())
        .context("project could not be found in local storage")?;
+
    let payload = doc.project()?;
    let path = PathBuf::from(payload.name.clone());

    if path.exists() {
@@ -109,7 +108,8 @@ pub fn execute(options: Options, profile: &Profile) -> anyhow::Result<PathBuf> {
    };
    spinner.finish();

-
    let remotes = delegates
+
    let remotes = doc
+
        .delegates
        .into_iter()
        .map(|did| *did)
        .filter(|id| id != profile.id())
modified radicle-cli/src/commands/clone.rs
@@ -93,10 +93,11 @@ pub fn clone(id: Id, _interactive: Interactive, ctx: impl term::Context) -> anyh
    let doc = profile
        .storage
        .repository(id)?
-
        .project_of(profile.id())
+
        .identity_of(profile.id())
        .map_err(|_e| anyhow!("couldn't load project {} from local state", id))?;
+
    let proj = doc.project()?;

-
    let path = Path::new(&doc.name);
+
    let path = Path::new(&proj.name);
    let repo = rad::checkout(id, profile.id(), path, &profile.storage)?;
    let delegates = doc
        .delegates
@@ -104,7 +105,7 @@ pub fn clone(id: Id, _interactive: Interactive, ctx: impl term::Context) -> anyh
        .map(|d| **d)
        .filter(|id| id != profile.id())
        .collect::<Vec<_>>();
-
    let default_branch = doc.payload.default_branch.clone();
+
    let default_branch = proj.default_branch.clone();

    // Setup tracking for project delegates.
    setup_remotes(
modified radicle-cli/src/commands/init.rs
@@ -201,24 +201,26 @@ pub fn init(options: Options, profile: &profile::Profile) -> anyhow::Result<()>
        &profile.storage,
    ) {
        Ok((id, doc, _)) => {
+
            let proj = doc.project()?;
+

            spinner.message(format!(
                "Project {} created",
-
                term::format::highlight(&doc.name)
+
                term::format::highlight(&proj.name)
            ));
            spinner.finish();

            if interactive.no() {
-
                term::blob(json::to_string_pretty(&doc.payload)?);
+
                term::blob(json::to_string_pretty(&proj)?);
                term::blank();
            }

-
            if options.set_upstream || git::branch_remote(&repo, &doc.default_branch).is_err() {
+
            if options.set_upstream || git::branch_remote(&repo, &proj.default_branch).is_err() {
                // Setup eg. `master` -> `rad/master`
                radicle::git::set_upstream(
                    &repo,
                    &radicle::rad::REMOTE_NAME,
-
                    &doc.default_branch,
-
                    &radicle::git::refs::workdir::branch(&doc.default_branch),
+
                    &proj.default_branch,
+
                    &radicle::git::refs::workdir::branch(&proj.default_branch),
                )?;
            }

modified radicle-cli/src/commands/ls.rs
@@ -50,13 +50,13 @@ pub fn run(_options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
    storage.projects()?.into_iter().for_each(|id| {
        let Ok(repo) = storage.repository(id) else { return };
        let Ok((_, head)) = repo.head() else { return };
-
        let Ok(Doc { payload, .. }) = repo.project_of(profile.id()) else { return };
+
        let Ok(Project { name, description, .. }) = repo.project_of(profile.id()) else { return };
        let head = term::format::oid(head);
        table.push([
-
            term::format::bold(payload.name),
+
            term::format::bold(name),
            term::format::tertiary(id),
            term::format::secondary(head),
-
            term::format::italic(payload.description),
+
            term::format::italic(description),
        ]);
    });
    table.render();
modified radicle-cli/src/commands/merge.rs
@@ -137,7 +137,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
    let signer = term::signer(&profile)?;
    let repository = profile.storage.repository(id)?;
    let _project = repository
-
        .project_of(profile.id())
+
        .identity_of(profile.id())
        .context(format!("couldn't load project {} from local state", id))?;
    let repository = profile.storage.repository(id)?;
    let mut patches = Patches::open(*profile.id(), &repository)?;
modified radicle-cli/src/commands/review.rs
@@ -137,7 +137,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
    let signer = term::signer(&profile)?;
    let repository = profile.storage.repository(id)?;
    let _project = repository
-
        .project_of(profile.id())
+
        .identity_of(profile.id())
        .context(format!("couldn't load project {} from local state", id))?;
    let mut patches = Patches::open(*profile.id(), &repository)?;

modified radicle-cli/src/commands/track.rs
@@ -3,8 +3,7 @@ use std::str::FromStr;

use anyhow::{anyhow, Context as _};

-
use radicle::node::Handle;
-
use radicle::prelude::*;
+
use radicle::node::{Handle, NodeId};
use radicle::storage::WriteStorage;

use crate::terminal as term;
@@ -94,12 +93,12 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
    let profile = ctx.profile()?;
    let storage = &profile.storage;
    let (_, rid) = radicle::rad::cwd().context("this command must be run within a project")?;
-
    let Doc { payload, .. } = storage.repository(rid)?.project_of(profile.id())?;
+
    let project = storage.repository(rid)?.project_of(profile.id())?;
    let mut node = radicle::node::connect(&profile.node())?;

    term::info!(
        "Establishing 🌱 tracking relationship for {}",
-
        term::format::highlight(&payload.name)
+
        term::format::highlight(&project.name)
    );
    term::blank();

modified radicle-cli/src/commands/untrack.rs
@@ -68,18 +68,18 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
        .context("current directory is not a git repository; please supply an `<id>`")?;
    let profile = ctx.profile()?;
    let storage = &profile.storage;
-
    let Doc { payload, .. } = storage.repository(id)?.project_of(profile.id())?;
+
    let project = storage.repository(id)?.project_of(profile.id())?;

    if untrack(id, &profile)? {
        term::success!(
            "Tracking relationships for {} ({}) removed",
-
            term::format::highlight(payload.name),
+
            term::format::highlight(project.name),
            &id.to_human()
        );
    } else {
        term::info!(
            "Tracking relationships for {} ({}) doesn't exist",
-
            term::format::highlight(payload.name),
+
            term::format::highlight(project.name),
            &id.to_human()
        );
    }
modified radicle-cob/Cargo.toml
@@ -19,7 +19,7 @@ git-trailers = { version = "0.1" }
log = { version = "0.4.17" }
nonempty = { version = "0.8.1", features = ["serialize"] }
petgraph = { version = "0.5" }
-
radicle-git-ext = { version = "0.2" }
+
radicle-git-ext = { version = "0" }
serde_json = { version = "1.0" }
thiserror = { version = "1.0" }

modified radicle-httpd/src/api.rs
@@ -17,7 +17,7 @@ use tower_http::trace::TraceLayer;
use tracing::Span;

use radicle::cob::issue::Issues;
-
use radicle::identity::{Doc, Id};
+
use radicle::identity::Id;
use radicle::storage::{ReadRepository, WriteStorage};
use radicle::Profile;

@@ -49,7 +49,7 @@ impl Context {
        let storage = &self.profile.storage;
        let repo = storage.repository(id)?;
        let (_, head) = repo.head()?;
-
        let Doc { payload, .. } = repo.project_of(self.profile.id())?;
+
        let payload = repo.project_of(self.profile.id())?;
        let issues = (Issues::open(self.profile.public_key, &repo)?).count()?;

        Ok(project::Info {
@@ -142,7 +142,7 @@ pub struct PaginationQuery {

mod project {
    use radicle::git::Oid;
-
    use radicle::identity::project::Payload;
+
    use radicle::identity::project::Project;
    use radicle::identity::Id;
    use serde::Serialize;

@@ -152,7 +152,7 @@ mod project {
    pub struct Info {
        /// Project metadata.
        #[serde(flatten)]
-
        pub payload: Payload,
+
        pub payload: Project,
        pub head: Oid,
        pub patches: usize,
        pub issues: usize,
modified radicle-httpd/src/api/v1/delegates.rs
@@ -3,7 +3,7 @@ use axum::routing::get;
use axum::{Extension, Json, Router};

use radicle::cob::issue::Issues;
-
use radicle::identity::{Did, Doc};
+
use radicle::identity::Did;
use radicle::storage::{ReadRepository, WriteStorage};

use crate::api::axum_extra::{Path, Query};
@@ -38,9 +38,10 @@ async fn delegates_projects_handler(
        .filter_map(|id| {
            let Ok(repo) = storage.repository(id) else { return None };
            let Ok((_, head)) = repo.head() else { return None };
-
            let Ok(Doc { payload, delegates, .. }) = repo.project_of(ctx.profile.id()) else { return None };
+
            let Ok(doc) = repo.identity_of(ctx.profile.id()) else { return None };
+
            let Ok(payload) = doc.project() else { return None };

-
            if !delegates.iter().any(|d| *d == delegate) {
+
            if !doc.delegates.iter().any(|d| *d == delegate) {
                return None;
            }

modified radicle-httpd/src/api/v1/projects.rs
@@ -14,7 +14,7 @@ use radicle::cob::issue::Issues;
use radicle::cob::thread::{self, CommentId};
use radicle::cob::Timestamp;
use radicle::git::raw::BranchType;
-
use radicle::identity::{Doc, Id, PublicKey};
+
use radicle::identity::{Id, PublicKey};
use radicle::node::NodeId;
use radicle::storage::{Oid, ReadRepository, WriteRepository, WriteStorage};
use radicle_surf::git::History;
@@ -68,7 +68,7 @@ async fn project_root_handler(
        .filter_map(|id| {
            let Ok(repo) = storage.repository(id) else { return None };
            let Ok((_, head)) = repo.head() else { return None };
-
            let Ok(Doc { payload, .. }) = repo.project_of(ctx.profile.id()) else { return None };
+
            let Ok(payload) = repo.project_of(ctx.profile.id()) else { return None };
            let Ok(issues) = Issues::open(ctx.profile.public_key, &repo) else { return None };
            let Ok(issues) = (*issues).count() else { return None };

modified radicle/src/identity/project.rs
@@ -1,7 +1,7 @@
mod id;

use std::collections::{BTreeMap, HashMap};
-
use std::fmt::Write as _;
+
use std::fmt::{self, Write as _};
use std::io;
use std::marker::PhantomData;
use std::ops::Deref;
@@ -46,6 +46,8 @@ pub enum DocError {
    Io(#[from] io::Error),
    #[error("verification: {0}")]
    Verification(#[from] VerificationError),
+
    #[error("payload: {0}")]
+
    Payload(#[from] PayloadError),
    #[error("git: {0}")]
    Git(#[from] git::Error),
    #[error("git: {0}")]
@@ -68,26 +70,120 @@ impl DocError {
    }
}

+
#[derive(Debug, Error)]
+
pub enum ProjectError {
+
    #[error("invalid name: {0}")]
+
    Name(&'static str),
+
    #[error("invalid description: {0}")]
+
    Description(&'static str),
+
    #[error("invalid default branch: {0}")]
+
    DefaultBranch(&'static str),
+
    #[error("json: {0}")]
+
    Json(#[from] serde_json::Error),
+
    #[error("project payload not found in identity document")]
+
    NotFound,
+
}
+

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
-
pub struct Payload {
+
pub struct Project {
    pub name: String,
    pub description: String,
-
    pub default_branch: git::RefString,
+
    pub default_branch: BranchName,
+
}
+

+
impl From<Project> for Payload {
+
    fn from(proj: Project) -> Self {
+
        let value = serde_json::to_value(proj)
+
            .expect("Payload::from: could not convert project into value");
+

+
        Self { value }
+
    }
}

+
impl Project {
+
    /// Validate the project data.
+
    pub fn validate(&self) -> Result<(), ProjectError> {
+
        if self.name.is_empty() {
+
            return Err(ProjectError::Name("name cannot be empty"));
+
        }
+
        if self.name.len() > MAX_STRING_LENGTH {
+
            return Err(ProjectError::Name("name cannot exceed 255 bytes"));
+
        }
+
        if self.description.len() > MAX_STRING_LENGTH {
+
            return Err(ProjectError::Description(
+
                "description cannot exceed 255 bytes",
+
            ));
+
        }
+
        if self.default_branch.is_empty() {
+
            return Err(ProjectError::DefaultBranch(
+
                "default branch cannot be empty",
+
            ));
+
        }
+
        if self.default_branch.len() > MAX_STRING_LENGTH {
+
            return Err(ProjectError::DefaultBranch(
+
                "default branch cannot exceed 255 bytes",
+
            ));
+
        }
+
        Ok(())
+
    }
+
}
+

+
#[derive(Debug, Error)]
+
pub enum PayloadError {
+
    #[error("json: {0}")]
+
    Json(#[from] serde_json::Error),
+
    #[error("payload '{0}' not found in identity document")]
+
    NotFound(PayloadId),
+
}
+

+
/// Identifies an identity document payload type.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(transparent)]
// TODO: Restrict values.
-
pub struct Namespace(String);
+
pub struct PayloadId(String);
+

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

+
impl PayloadId
+
where
+
    PayloadId: Clone,
+
{
+
    /// Project payload type.
+
    pub fn project() -> Self {
+
        Self(String::from("xyz.radicle.project"))
+
    }
+
}
+

+
/// Payload value.
+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+
#[serde(transparent)]
+
pub struct Payload {
+
    value: serde_json::Value,
+
}
+

+
impl From<serde_json::Value> for Payload {
+
    fn from(value: serde_json::Value) -> Self {
+
        Self { value }
+
    }
+
}
+

+
impl Deref for Payload {
+
    type Target = serde_json::Value;
+

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

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Doc<V> {
-
    #[serde(rename = "xyz.radicle.project")]
-
    pub payload: Payload,
-
    #[serde(flatten)]
-
    pub extensions: BTreeMap<Namespace, serde_json::Value>,
+
    pub payload: BTreeMap<PayloadId, Payload>,
    pub delegates: NonEmpty<Did>,
    pub threshold: usize,

@@ -117,6 +213,17 @@ impl Doc<Verified> {
        false
    }

+
    /// Get the project payload, if it exists and is valid, out of this document.
+
    pub fn project(&self) -> Result<Project, PayloadError> {
+
        let value = self
+
            .payload
+
            .get(&PayloadId::project())
+
            .ok_or_else(|| PayloadError::NotFound(PayloadId::project()))?;
+
        let proj: Project = serde_json::from_value((**value).clone())?;
+

+
        Ok(proj)
+
    }
+

    pub fn sign<G: crypto::Signer>(&self, signer: &G) -> Result<(git::Oid, Signature), DocError> {
        let (oid, bytes) = self.encode()?;
        let sig = signer.sign(&bytes);
@@ -181,66 +288,27 @@ impl Doc<Verified> {
    }
}

-
impl<V> Deref for Doc<V> {
-
    type Target = Payload;
-

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

#[derive(Error, Debug)]
pub enum VerificationError {
-
    #[error("invalid name: {0}")]
-
    Name(&'static str),
-
    #[error("invalid description: {0}")]
-
    Description(&'static str),
-
    #[error("invalid default branch: {0}")]
-
    DefaultBranch(&'static str),
    #[error("invalid delegates: {0}")]
    Delegates(&'static str),
    #[error("invalid version `{0}`")]
    Version(u32),
-
    #[error("invalid parent: {0}")]
-
    Parent(&'static str),
    #[error("invalid threshold `{0}`: {1}")]
    Threshold(usize, &'static str),
}

impl Doc<Unverified> {
-
    pub fn initial(
-
        name: String,
-
        description: String,
-
        default_branch: BranchName,
-
        delegate: Did,
-
    ) -> Self {
-
        Self {
-
            payload: Payload {
-
                name,
-
                description,
-
                default_branch,
-
            },
-
            extensions: BTreeMap::new(),
-
            delegates: NonEmpty::new(delegate),
-
            threshold: 1,
-
            verified: PhantomData,
-
        }
+
    pub fn initial(project: Project, delegate: Did) -> Self {
+
        Self::new(project, NonEmpty::new(delegate), 1)
    }

-
    pub fn new(
-
        name: String,
-
        description: String,
-
        default_branch: BranchName,
-
        delegates: NonEmpty<Did>,
-
        threshold: usize,
-
    ) -> Self {
+
    pub fn new(project: Project, delegates: NonEmpty<Did>, threshold: usize) -> Self {
+
        let project =
+
            serde_json::to_value(project).expect("Doc::initial: payload must be serializable");
+

        Self {
-
            payload: Payload {
-
                name,
-
                description,
-
                default_branch,
-
            },
-
            extensions: BTreeMap::new(),
+
            payload: BTreeMap::from_iter([(PayloadId::project(), Payload::from(project))]),
            delegates,
            threshold,
            verified: PhantomData,
@@ -252,17 +320,6 @@ impl Doc<Unverified> {
    }

    pub fn verified(self) -> Result<Doc<Verified>, VerificationError> {
-
        if self.name.is_empty() {
-
            return Err(VerificationError::Name("name cannot be empty"));
-
        }
-
        if self.name.len() > MAX_STRING_LENGTH {
-
            return Err(VerificationError::Name("name cannot exceed 255 bytes"));
-
        }
-
        if self.description.len() > MAX_STRING_LENGTH {
-
            return Err(VerificationError::Description(
-
                "description cannot exceed 255 bytes",
-
            ));
-
        }
        if self.delegates.len() > MAX_DELEGATES {
            return Err(VerificationError::Delegates(
                "number of delegates cannot exceed 255",
@@ -273,16 +330,6 @@ impl Doc<Unverified> {
                "delegate list cannot be empty",
            ));
        }
-
        if self.default_branch.is_empty() {
-
            return Err(VerificationError::DefaultBranch(
-
                "default branch cannot be empty",
-
            ));
-
        }
-
        if self.default_branch.len() > MAX_STRING_LENGTH {
-
            return Err(VerificationError::DefaultBranch(
-
                "default branch cannot exceed 255 bytes",
-
            ));
-
        }
        if self.threshold > self.delegates.len() {
            return Err(VerificationError::Threshold(
                self.threshold,
@@ -298,7 +345,6 @@ impl Doc<Unverified> {

        Ok(Doc {
            payload: self.payload,
-
            extensions: self.extensions,
            delegates: self.delegates,
            threshold: self.threshold,
            verified: PhantomData,
@@ -501,8 +547,12 @@ mod test {
            String::from("z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi")
        );
        assert_eq!(
+
            (*id).to_string(),
+
            "d96f425412c9f8ad5d9a9a05c9831d0728e2338d"
+
        );
+
        assert_eq!(
            id.to_human(),
-
            String::from("rad:z2TBtGrJKGsremYAPec6vN4n77Ba7")
+
            String::from("rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji")
        );
    }

@@ -542,14 +592,16 @@ mod test {
        // TODO: In some cases we want to get the repo and the project, but don't
        // want to have to create a repository object twice. Perhaps there should
        // be a way of getting a project from a repo.
-
        let mut proj = storage.get(alice.public_key(), id).unwrap().unwrap();
+
        let mut doc = storage.get(alice.public_key(), id).unwrap().unwrap();
+
        let mut prj = doc.project().unwrap();
        let repo = storage.repository(id).unwrap();

        // Make a change to the description and sign it.
-
        proj.payload.description += "!";
-
        proj.sign(&alice)
+
        prj.description += "!";
+
        doc.payload.insert(PayloadId::project(), prj.clone().into());
+
        doc.sign(&alice)
            .and_then(|(_, sig)| {
-
                proj.update(
+
                doc.update(
                    alice.public_key(),
                    "Update description",
                    &[(alice.public_key(), sig)],
@@ -559,11 +611,11 @@ mod test {
            .unwrap();

        // Add Bob as a delegate, and sign it.
-
        proj.delegate(*bob.public_key());
-
        proj.threshold = 2;
-
        proj.sign(&alice)
+
        doc.delegate(*bob.public_key());
+
        doc.threshold = 2;
+
        doc.sign(&alice)
            .and_then(|(_, sig)| {
-
                proj.update(
+
                doc.update(
                    alice.public_key(),
                    "Add bob",
                    &[(alice.public_key(), sig)],
@@ -573,11 +625,11 @@ mod test {
            .unwrap();

        // Add Eve as a delegate, and sign it.
-
        proj.delegate(*eve.public_key());
-
        proj.sign(&alice)
+
        doc.delegate(*eve.public_key());
+
        doc.sign(&alice)
            .and_then(|(_, alice_sig)| {
-
                proj.sign(&bob).and_then(|(_, bob_sig)| {
-
                    proj.update(
+
                doc.sign(&bob).and_then(|(_, bob_sig)| {
+
                    doc.update(
                        alice.public_key(),
                        "Add eve",
                        &[(alice.public_key(), alice_sig), (bob.public_key(), bob_sig)],
@@ -588,12 +640,13 @@ mod test {
            .unwrap();

        // Update description again with signatures by Eve and Bob.
-
        proj.payload.description += "?";
-
        let (current, head) = proj
+
        prj.description += "?";
+
        doc.payload.insert(PayloadId::project(), prj.into());
+
        let (current, head) = doc
            .sign(&bob)
            .and_then(|(_, bob_sig)| {
-
                proj.sign(&eve).and_then(|(blob_id, eve_sig)| {
-
                    proj.update(
+
                doc.sign(&eve).and_then(|(blob_id, eve_sig)| {
+
                    doc.update(
                        alice.public_key(),
                        "Update description",
                        &[(bob.public_key(), bob_sig), (eve.public_key(), eve_sig)],
@@ -614,10 +667,10 @@ mod test {
        assert_eq!(identity.root, id);
        assert_eq!(identity.current, current);
        assert_eq!(identity.head, head);
-
        assert_eq!(identity.doc, proj);
+
        assert_eq!(identity.doc, doc);

-
        let proj = storage.get(alice.public_key(), id).unwrap().unwrap();
-
        assert_eq!(proj.description, "Acme's repository!?");
+
        let doc = storage.get(alice.public_key(), id).unwrap().unwrap();
+
        assert_eq!(doc.project().unwrap().description, "Acme's repository!?");
    }

    #[quickcheck]
modified radicle/src/lib.rs
@@ -26,7 +26,7 @@ pub mod prelude {
    use super::*;

    pub use crypto::{PublicKey, Signer, Verified};
-
    pub use identity::{Did, Doc, Id};
+
    pub use identity::{project::Project, Did, Doc, Id};
    pub use node::NodeId;
    pub use profile::Profile;
    pub use storage::{BranchName, ReadRepository, ReadStorage, WriteRepository, WriteStorage};
modified radicle/src/rad.rs
@@ -8,7 +8,7 @@ use thiserror::Error;

use crate::crypto::{Signer, Verified};
use crate::git;
-
use crate::identity::project::DocError;
+
use crate::identity::project::{self, DocError, Project};
use crate::identity::Id;
use crate::node;
use crate::node::NodeId;
@@ -60,13 +60,12 @@ pub fn init<G: Signer>(
    // TODO: Better error when project id already exists in storage, but remote doesn't.
    let pk = signer.public_key();
    let delegate = identity::Did::from(*pk);
-
    let doc = identity::Doc::initial(
-
        name.to_owned(),
-
        description.to_owned(),
-
        default_branch.clone(),
-
        delegate,
-
    )
-
    .verified()?;
+
    let proj = Project {
+
        name: name.to_owned(),
+
        description: description.to_owned(),
+
        default_branch: default_branch.clone(),
+
    };
+
    let doc = identity::Doc::initial(proj, delegate).verified()?;

    let (id, _, project) = doc.create(pk, "Initialize Radicle\n", storage)?;
    let url = git::Url::from(id).with_namespace(*pk);
@@ -96,6 +95,8 @@ pub enum ForkError {
    Git(#[from] git::Error),
    #[error("storage: {0}")]
    Storage(#[from] storage::Error),
+
    #[error("payload: {0}")]
+
    Payload(#[from] project::PayloadError),
    #[error("project `{0}` was not found in storage")]
    NotFound(Id),
    #[error("project identity error: {0}")]
@@ -123,9 +124,10 @@ pub fn fork_remote<G: Signer, S: storage::WriteStorage>(
    // refs/namespaces/<pk>/refs/tags/*

    let me = signer.public_key();
-
    let project = storage
+
    let doc = storage
        .get(remote, proj)?
        .ok_or(ForkError::NotFound(proj))?;
+
    let project = doc.project()?;
    let repository = storage.repository(proj)?;

    let raw = repository.raw();
@@ -247,6 +249,8 @@ pub enum CheckoutError {
    Git(#[from] git2::Error),
    #[error("storage: {0}")]
    Storage(#[from] storage::Error),
+
    #[error("payload: {0}")]
+
    Payload(#[from] project::PayloadError),
    #[error("project `{0}` was not found in storage")]
    NotFound(Id),
    #[error("project error: {0}")]
@@ -263,9 +267,10 @@ pub fn checkout<P: AsRef<Path>, S: storage::ReadStorage>(
) -> Result<git2::Repository, CheckoutError> {
    // TODO: Decide on whether we can use `clone_local`
    // TODO: Look into sharing object databases.
-
    let project = storage
+
    let doc = storage
        .get(remote, proj)?
        .ok_or(CheckoutError::NotFound(proj))?;
+
    let project = doc.project()?;

    let mut opts = git2::RepositoryInitOptions::new();
    opts.no_reinit(true).description(&project.description);
@@ -375,7 +380,8 @@ mod tests {
        )
        .unwrap();

-
        let project = storage.get(&public_key, proj).unwrap().unwrap();
+
        let doc = storage.get(&public_key, proj).unwrap().unwrap();
+
        let project = doc.project().unwrap();
        let remotes: HashMap<_, _> = storage
            .repository(proj)
            .unwrap()
@@ -402,7 +408,7 @@ mod tests {
        assert_eq!(project.name, "acme");
        assert_eq!(project.description, "Acme's repo");
        assert_eq!(project.default_branch, git::refname!("master"));
-
        assert_eq!(project.delegates.first(), &Did::from(public_key));
+
        assert_eq!(doc.delegates.first(), &Did::from(public_key));
    }

    #[test]
modified radicle/src/storage/git.rs
@@ -11,7 +11,7 @@ use radicle_cob::{self as cob, change};

use crate::git;
use crate::identity;
-
use crate::identity::project::{Identity, IdentityError};
+
use crate::identity::project::{Identity, IdentityError, Project};
use crate::identity::{Doc, Id};
use crate::storage::refs;
use crate::storage::refs::{Refs, SignedRefs};
@@ -41,6 +41,8 @@ pub enum ProjectError {
    Storage(#[from] Error),
    #[error("identity document error: {0}")]
    Doc(#[from] identity::project::DocError),
+
    #[error("payload error: {0}")]
+
    Payload(#[from] identity::project::PayloadError),
    #[error("identity verification error: {0}")]
    Verify(#[from] identity::project::VerificationError),
    #[error("git: {0}")]
@@ -96,7 +98,7 @@ impl ReadStorage for Storage {
    fn get(&self, remote: &RemoteId, proj: Id) -> Result<Option<Doc<Verified>>, ProjectError> {
        // TODO: Don't create a repo here if it doesn't exist?
        // Perhaps for checking we could have a `contains` method?
-
        match self.repository(proj)?.project_of(remote) {
+
        match self.repository(proj)?.identity_of(remote) {
            Ok(doc) => Ok(Some(doc)),

            Err(err) if err.is_not_found() => Ok(None),
@@ -271,7 +273,14 @@ impl Repository {
        Identity::load(remote, self)
    }

-
    pub fn project_of(&self, remote: &RemoteId) -> Result<identity::Doc<Verified>, ProjectError> {
+
    pub fn project_of(&self, remote: &RemoteId) -> Result<Project, ProjectError> {
+
        let doc = self.identity_of(remote)?;
+
        let proj = doc.project()?;
+

+
        Ok(proj)
+
    }
+

+
    pub fn identity_of(&self, remote: &RemoteId) -> Result<Doc<Verified>, ProjectError> {
        let (doc, _) = identity::Doc::load(remote, self)?;
        let verified = doc.verified()?;

@@ -279,7 +288,7 @@ impl Repository {
    }

    /// Return the canonical identity [`git::Oid`] and document.
-
    pub fn project(&self) -> Result<(Oid, identity::Doc<Unverified>), ProjectError> {
+
    pub fn identity_doc(&self) -> Result<(Oid, identity::Doc<Unverified>), ProjectError> {
        let mut heads = Vec::new();
        for remote in self.remote_ids()? {
            let remote = remote?;
@@ -478,7 +487,7 @@ impl ReadRepository for Repository {
    }

    fn project_identity(&self) -> Result<(Oid, identity::Doc<Unverified>), ProjectError> {
-
        Repository::project(self)
+
        Repository::identity_doc(self)
    }

    fn head(&self) -> Result<(Qualified, Oid), ProjectError> {
@@ -494,12 +503,14 @@ impl ReadRepository for Repository {
    fn canonical_head(&self) -> Result<(Qualified, Oid), ProjectError> {
        // TODO: In the `fork` function for example, we call Repository::project_identity again,
        // This should only be necessary once.
-
        let (_, project) = self.project_identity()?;
+
        let (_, doc) = self.project_identity()?;
+
        let doc = doc.verified()?;
+
        let project = doc.project()?;
        let branch_ref = Qualified::from(lit::refs_heads(&project.default_branch));
        let raw = self.raw();

        let mut heads = Vec::new();
-
        for delegate in project.delegates.iter() {
+
        for delegate in doc.delegates.iter() {
            let r = self.reference_oid(delegate, &branch_ref)?.into();

            heads.push(r);
modified radicle/src/test/arbitrary.rs
@@ -10,7 +10,7 @@ use qcheck::Arbitrary;

use crate::collections::HashMap;
use crate::git;
-
use crate::identity::{project::Doc, Did, Id};
+
use crate::identity::{project::Doc, project::Project, Did, Id};
use crate::storage;
use crate::storage::refs::{Refs, SignedRefs};
use crate::test::storage::MockStorage;
@@ -74,18 +74,7 @@ impl Arbitrary for Did {
    }
}

-
impl Arbitrary for Doc<Unverified> {
-
    fn arbitrary(g: &mut qcheck::Gen) -> Self {
-
        let name = String::arbitrary(g);
-
        let description = String::arbitrary(g);
-
        let default_branch = git::RefString::try_from(String::arbitrary(g)).unwrap();
-
        let delegate = Did::arbitrary(g);
-

-
        Self::initial(name, description, default_branch, delegate)
-
    }
-
}
-

-
impl Arbitrary for Doc<Verified> {
+
impl Arbitrary for Project {
    fn arbitrary(g: &mut qcheck::Gen) -> Self {
        let rng = fastrand::Rng::with_seed(u64::arbitrary(g));
        let name = iter::repeat_with(|| rng.alphanumeric())
@@ -99,14 +88,35 @@ impl Arbitrary for Doc<Verified> {
            .collect::<String>()
            .try_into()
            .unwrap();
+

+
        Project {
+
            name,
+
            description,
+
            default_branch,
+
        }
+
    }
+
}
+

+
impl Arbitrary for Doc<Unverified> {
+
    fn arbitrary(g: &mut qcheck::Gen) -> Self {
+
        let proj = Project::arbitrary(g);
+
        let delegate = Did::arbitrary(g);
+

+
        Self::initial(proj, delegate)
+
    }
+
}
+

+
impl Arbitrary for Doc<Verified> {
+
    fn arbitrary(g: &mut qcheck::Gen) -> Self {
+
        let rng = fastrand::Rng::with_seed(u64::arbitrary(g));
+
        let project = Project::arbitrary(g);
        let delegates: NonEmpty<_> = iter::repeat_with(|| Did::arbitrary(g))
            .take(rng.usize(1..6))
            .collect::<Vec<_>>()
            .try_into()
            .unwrap();
        let threshold = delegates.len() / 2 + 1;
-
        let doc: Doc<Unverified> =
-
            Doc::new(name, description, default_branch, delegates, threshold);
+
        let doc: Doc<Unverified> = Doc::new(project, delegates, threshold);

        doc.verified().unwrap()
    }