Radish alpha
r
rad:z2UcCU1LgMshWvXj6hXSDDrwB8q8M
Radicle Job Collaborative Object
Radicle
Git
radicle-job
Fintan Halpenny committed 1 year ago
commit d5685cb7557203878fc9f5ba8e71e2b617ba1d2d
9 files changed +3073 -0
added .envrc
@@ -0,0 +1 @@
+
use flake
added .gitignore
@@ -0,0 +1 @@
+
/target
added Cargo.lock
@@ -0,0 +1,2005 @@
+
# This file is automatically @generated by Cargo.
+
# It is not intended for manual editing.
+
version = 4
+

+
[[package]]
+
name = "aead"
+
version = "0.5.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
+
dependencies = [
+
 "crypto-common",
+
 "generic-array",
+
]
+

+
[[package]]
+
name = "aes"
+
version = "0.8.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
+
dependencies = [
+
 "cfg-if",
+
 "cipher",
+
 "cpufeatures",
+
]
+

+
[[package]]
+
name = "aes-gcm"
+
version = "0.10.3"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
+
dependencies = [
+
 "aead",
+
 "aes",
+
 "cipher",
+
 "ctr",
+
 "ghash",
+
 "subtle",
+
]
+

+
[[package]]
+
name = "amplify"
+
version = "4.8.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "448cf0c3afc71439b5f837aac5399a1ef2b223f5f38324dbfb4343deec3b80cc"
+
dependencies = [
+
 "amplify_derive",
+
 "amplify_num",
+
 "ascii",
+
 "wasm-bindgen",
+
]
+

+
[[package]]
+
name = "amplify_derive"
+
version = "4.0.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "2a6309e6b8d89b36b9f959b7a8fa093583b94922a0f6438a24fb08936de4d428"
+
dependencies = [
+
 "amplify_syn",
+
 "proc-macro2",
+
 "quote",
+
 "syn 1.0.109",
+
]
+

+
[[package]]
+
name = "amplify_num"
+
version = "0.5.3"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "99bcb75a2982047f733547042fc3968c0f460dfcf7d90b90dea3b2744580e9ad"
+
dependencies = [
+
 "wasm-bindgen",
+
]
+

+
[[package]]
+
name = "amplify_syn"
+
version = "2.0.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "7736fb8d473c0d83098b5bac44df6a561e20470375cd8bcae30516dc889fd62a"
+
dependencies = [
+
 "proc-macro2",
+
 "quote",
+
 "syn 1.0.109",
+
]
+

+
[[package]]
+
name = "ascii"
+
version = "1.1.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16"
+

+
[[package]]
+
name = "autocfg"
+
version = "1.4.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
+

+
[[package]]
+
name = "base-x"
+
version = "0.2.11"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270"
+

+
[[package]]
+
name = "base16ct"
+
version = "0.2.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
+

+
[[package]]
+
name = "base32"
+
version = "0.4.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa"
+

+
[[package]]
+
name = "base64"
+
version = "0.21.7"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
+

+
[[package]]
+
name = "base64ct"
+
version = "1.7.3"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3"
+

+
[[package]]
+
name = "bcrypt-pbkdf"
+
version = "0.10.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "6aeac2e1fe888769f34f05ac343bbef98b14d1ffb292ab69d4608b3abc86f2a2"
+
dependencies = [
+
 "blowfish",
+
 "pbkdf2",
+
 "sha2",
+
]
+

+
[[package]]
+
name = "bitflags"
+
version = "2.9.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
+

+
[[package]]
+
name = "block-buffer"
+
version = "0.10.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+
dependencies = [
+
 "generic-array",
+
]
+

+
[[package]]
+
name = "block-padding"
+
version = "0.3.3"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93"
+
dependencies = [
+
 "generic-array",
+
]
+

+
[[package]]
+
name = "blowfish"
+
version = "0.9.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7"
+
dependencies = [
+
 "byteorder",
+
 "cipher",
+
]
+

+
[[package]]
+
name = "bumpalo"
+
version = "3.17.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
+

+
[[package]]
+
name = "byteorder"
+
version = "1.5.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+

+
[[package]]
+
name = "cbc"
+
version = "0.1.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6"
+
dependencies = [
+
 "cipher",
+
]
+

+
[[package]]
+
name = "cc"
+
version = "1.2.17"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a"
+
dependencies = [
+
 "jobserver",
+
 "libc",
+
 "shlex",
+
]
+

+
[[package]]
+
name = "cfg-if"
+
version = "1.0.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+

+
[[package]]
+
name = "chacha20"
+
version = "0.9.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
+
dependencies = [
+
 "cfg-if",
+
 "cipher",
+
 "cpufeatures",
+
]
+

+
[[package]]
+
name = "cipher"
+
version = "0.4.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
+
dependencies = [
+
 "crypto-common",
+
 "inout",
+
]
+

+
[[package]]
+
name = "const-oid"
+
version = "0.9.6"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
+

+
[[package]]
+
name = "cpufeatures"
+
version = "0.2.17"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
+
dependencies = [
+
 "libc",
+
]
+

+
[[package]]
+
name = "crossbeam-channel"
+
version = "0.5.14"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471"
+
dependencies = [
+
 "crossbeam-utils",
+
]
+

+
[[package]]
+
name = "crossbeam-utils"
+
version = "0.8.21"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
+

+
[[package]]
+
name = "crypto-bigint"
+
version = "0.5.5"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
+
dependencies = [
+
 "generic-array",
+
 "rand_core",
+
 "subtle",
+
 "zeroize",
+
]
+

+
[[package]]
+
name = "crypto-common"
+
version = "0.1.6"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+
dependencies = [
+
 "generic-array",
+
 "typenum",
+
]
+

+
[[package]]
+
name = "ct-codecs"
+
version = "1.1.3"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b916ba8ce9e4182696896f015e8a5ae6081b305f74690baa8465e35f5a142ea4"
+

+
[[package]]
+
name = "ctr"
+
version = "0.9.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
+
dependencies = [
+
 "cipher",
+
]
+

+
[[package]]
+
name = "cypheraddr"
+
version = "0.4.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "ba5c54d2ad4ab9941383519471b75d12abc1a7b4779265e233168f2703a730d9"
+
dependencies = [
+
 "amplify",
+
 "base32",
+
 "cyphergraphy",
+
 "sha3",
+
]
+

+
[[package]]
+
name = "cyphergraphy"
+
version = "0.3.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b67c16c8ef5ddcdab57aab83fd8e770540ea3682ccdae09642c63575b0da2184"
+
dependencies = [
+
 "amplify",
+
 "ec25519",
+
]
+

+
[[package]]
+
name = "cyphernet"
+
version = "0.5.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "ac949369884a7a1d802cc669821269c707be8cec4d65043382e253733d2e62e1"
+
dependencies = [
+
 "cypheraddr",
+
 "cyphergraphy",
+
 "socks5-client",
+
]
+

+
[[package]]
+
name = "data-encoding"
+
version = "2.8.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010"
+

+
[[package]]
+
name = "data-encoding-macro"
+
version = "0.1.17"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "9f9724adfcf41f45bf652b3995837669d73c4d49a1b5ac1ff82905ac7d9b5558"
+
dependencies = [
+
 "data-encoding",
+
 "data-encoding-macro-internal",
+
]
+

+
[[package]]
+
name = "data-encoding-macro-internal"
+
version = "0.1.15"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "18e4fdb82bd54a12e42fb58a800dcae6b9e13982238ce2296dc3570b92148e1f"
+
dependencies = [
+
 "data-encoding",
+
 "syn 2.0.100",
+
]
+

+
[[package]]
+
name = "der"
+
version = "0.7.9"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0"
+
dependencies = [
+
 "const-oid",
+
 "zeroize",
+
]
+

+
[[package]]
+
name = "digest"
+
version = "0.10.7"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+
dependencies = [
+
 "block-buffer",
+
 "const-oid",
+
 "crypto-common",
+
 "subtle",
+
]
+

+
[[package]]
+
name = "displaydoc"
+
version = "0.2.5"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
+
dependencies = [
+
 "proc-macro2",
+
 "quote",
+
 "syn 2.0.100",
+
]
+

+
[[package]]
+
name = "ec25519"
+
version = "0.1.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "bdfd533a2fc01178c738c99412ae1f7e1ad2cb37c2e14bfd87e9d4618171c825"
+
dependencies = [
+
 "ct-codecs",
+
 "ed25519",
+
 "getrandom 0.2.15",
+
]
+

+
[[package]]
+
name = "ecdsa"
+
version = "0.16.9"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
+
dependencies = [
+
 "der",
+
 "digest",
+
 "elliptic-curve",
+
 "rfc6979",
+
 "signature 2.2.0",
+
 "spki",
+
]
+

+
[[package]]
+
name = "ed25519"
+
version = "1.5.3"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7"
+
dependencies = [
+
 "signature 1.6.4",
+
]
+

+
[[package]]
+
name = "elliptic-curve"
+
version = "0.13.8"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
+
dependencies = [
+
 "base16ct",
+
 "crypto-bigint",
+
 "digest",
+
 "ff",
+
 "generic-array",
+
 "group",
+
 "pkcs8",
+
 "rand_core",
+
 "sec1",
+
 "subtle",
+
 "zeroize",
+
]
+

+
[[package]]
+
name = "equivalent"
+
version = "1.0.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+

+
[[package]]
+
name = "errno"
+
version = "0.3.10"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
+
dependencies = [
+
 "libc",
+
 "windows-sys",
+
]
+

+
[[package]]
+
name = "fastrand"
+
version = "2.3.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
+

+
[[package]]
+
name = "ff"
+
version = "0.13.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393"
+
dependencies = [
+
 "rand_core",
+
 "subtle",
+
]
+

+
[[package]]
+
name = "form_urlencoded"
+
version = "1.2.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
+
dependencies = [
+
 "percent-encoding",
+
]
+

+
[[package]]
+
name = "generic-array"
+
version = "0.14.7"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+
dependencies = [
+
 "typenum",
+
 "version_check",
+
 "zeroize",
+
]
+

+
[[package]]
+
name = "getrandom"
+
version = "0.2.15"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
+
dependencies = [
+
 "cfg-if",
+
 "libc",
+
 "wasi 0.11.0+wasi-snapshot-preview1",
+
]
+

+
[[package]]
+
name = "getrandom"
+
version = "0.3.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0"
+
dependencies = [
+
 "cfg-if",
+
 "libc",
+
 "r-efi",
+
 "wasi 0.14.2+wasi-0.2.4",
+
]
+

+
[[package]]
+
name = "ghash"
+
version = "0.5.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1"
+
dependencies = [
+
 "opaque-debug",
+
 "polyval",
+
]
+

+
[[package]]
+
name = "git-ref-format"
+
version = "0.3.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "7428e0d6e549a9a613d6f019b839a0f5142c331295b79e119ca8f4faac145da1"
+
dependencies = [
+
 "git-ref-format-core",
+
 "git-ref-format-macro",
+
]
+

+
[[package]]
+
name = "git-ref-format-core"
+
version = "0.3.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "bbaeb9672a55e9e32cb6d3ef781e7526b25ab97d499fae71615649340b143424"
+
dependencies = [
+
 "serde",
+
 "thiserror 1.0.69",
+
]
+

+
[[package]]
+
name = "git-ref-format-macro"
+
version = "0.3.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "3b6ca5353accc201f6324dff744ba4660099546d4daf187ba868f07562e36ca4"
+
dependencies = [
+
 "git-ref-format-core",
+
 "proc-macro-error",
+
 "quote",
+
 "syn 2.0.100",
+
]
+

+
[[package]]
+
name = "git2"
+
version = "0.19.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724"
+
dependencies = [
+
 "bitflags",
+
 "libc",
+
 "libgit2-sys",
+
 "log",
+
 "url",
+
]
+

+
[[package]]
+
name = "group"
+
version = "0.13.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
+
dependencies = [
+
 "ff",
+
 "rand_core",
+
 "subtle",
+
]
+

+
[[package]]
+
name = "hashbrown"
+
version = "0.15.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
+

+
[[package]]
+
name = "hmac"
+
version = "0.12.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
+
dependencies = [
+
 "digest",
+
]
+

+
[[package]]
+
name = "icu_collections"
+
version = "1.5.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"
+
dependencies = [
+
 "displaydoc",
+
 "yoke",
+
 "zerofrom",
+
 "zerovec",
+
]
+

+
[[package]]
+
name = "icu_locid"
+
version = "1.5.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"
+
dependencies = [
+
 "displaydoc",
+
 "litemap",
+
 "tinystr",
+
 "writeable",
+
 "zerovec",
+
]
+

+
[[package]]
+
name = "icu_locid_transform"
+
version = "1.5.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e"
+
dependencies = [
+
 "displaydoc",
+
 "icu_locid",
+
 "icu_locid_transform_data",
+
 "icu_provider",
+
 "tinystr",
+
 "zerovec",
+
]
+

+
[[package]]
+
name = "icu_locid_transform_data"
+
version = "1.5.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e"
+

+
[[package]]
+
name = "icu_normalizer"
+
version = "1.5.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f"
+
dependencies = [
+
 "displaydoc",
+
 "icu_collections",
+
 "icu_normalizer_data",
+
 "icu_properties",
+
 "icu_provider",
+
 "smallvec",
+
 "utf16_iter",
+
 "utf8_iter",
+
 "write16",
+
 "zerovec",
+
]
+

+
[[package]]
+
name = "icu_normalizer_data"
+
version = "1.5.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516"
+

+
[[package]]
+
name = "icu_properties"
+
version = "1.5.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5"
+
dependencies = [
+
 "displaydoc",
+
 "icu_collections",
+
 "icu_locid_transform",
+
 "icu_properties_data",
+
 "icu_provider",
+
 "tinystr",
+
 "zerovec",
+
]
+

+
[[package]]
+
name = "icu_properties_data"
+
version = "1.5.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569"
+

+
[[package]]
+
name = "icu_provider"
+
version = "1.5.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"
+
dependencies = [
+
 "displaydoc",
+
 "icu_locid",
+
 "icu_provider_macros",
+
 "stable_deref_trait",
+
 "tinystr",
+
 "writeable",
+
 "yoke",
+
 "zerofrom",
+
 "zerovec",
+
]
+

+
[[package]]
+
name = "icu_provider_macros"
+
version = "1.5.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
+
dependencies = [
+
 "proc-macro2",
+
 "quote",
+
 "syn 2.0.100",
+
]
+

+
[[package]]
+
name = "idna"
+
version = "1.0.3"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
+
dependencies = [
+
 "idna_adapter",
+
 "smallvec",
+
 "utf8_iter",
+
]
+

+
[[package]]
+
name = "idna_adapter"
+
version = "1.2.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71"
+
dependencies = [
+
 "icu_normalizer",
+
 "icu_properties",
+
]
+

+
[[package]]
+
name = "indexmap"
+
version = "2.8.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058"
+
dependencies = [
+
 "equivalent",
+
 "hashbrown",
+
 "serde",
+
]
+

+
[[package]]
+
name = "inout"
+
version = "0.1.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
+
dependencies = [
+
 "block-padding",
+
 "generic-array",
+
]
+

+
[[package]]
+
name = "itoa"
+
version = "1.0.15"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
+

+
[[package]]
+
name = "jobserver"
+
version = "0.1.32"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
+
dependencies = [
+
 "libc",
+
]
+

+
[[package]]
+
name = "keccak"
+
version = "0.1.5"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654"
+
dependencies = [
+
 "cpufeatures",
+
]
+

+
[[package]]
+
name = "lazy_static"
+
version = "1.5.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
dependencies = [
+
 "spin",
+
]
+

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

+
[[package]]
+
name = "libgit2-sys"
+
version = "0.17.0+1.8.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "10472326a8a6477c3c20a64547b0059e4b0d086869eee31e6d7da728a8eb7224"
+
dependencies = [
+
 "cc",
+
 "libc",
+
 "libz-sys",
+
 "pkg-config",
+
]
+

+
[[package]]
+
name = "libm"
+
version = "0.2.11"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
+

+
[[package]]
+
name = "libz-sys"
+
version = "1.1.22"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d"
+
dependencies = [
+
 "cc",
+
 "libc",
+
 "pkg-config",
+
 "vcpkg",
+
]
+

+
[[package]]
+
name = "linux-raw-sys"
+
version = "0.9.3"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413"
+

+
[[package]]
+
name = "litemap"
+
version = "0.7.5"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856"
+

+
[[package]]
+
name = "localtime"
+
version = "1.3.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "016a009e0bb8ba6e3229fb74bf11a8fe6ef24542cc6ef35ef38863ac13f96d87"
+
dependencies = [
+
 "serde",
+
]
+

+
[[package]]
+
name = "log"
+
version = "0.4.27"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
+

+
[[package]]
+
name = "memchr"
+
version = "2.7.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
+

+
[[package]]
+
name = "multibase"
+
version = "0.9.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "9b3539ec3c1f04ac9748a260728e855f261b4977f5c3406612c884564f329404"
+
dependencies = [
+
 "base-x",
+
 "data-encoding",
+
 "data-encoding-macro",
+
]
+

+
[[package]]
+
name = "nonempty"
+
version = "0.9.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "995defdca0a589acfdd1bd2e8e3b896b4d4f7675a31fd14c32611440c7f608e6"
+
dependencies = [
+
 "serde",
+
]
+

+
[[package]]
+
name = "nonempty"
+
version = "0.11.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "549e471b99ccaf2f89101bec68f4d244457d5a95a9c3d0672e9564124397741d"
+

+
[[package]]
+
name = "num-bigint-dig"
+
version = "0.8.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151"
+
dependencies = [
+
 "byteorder",
+
 "lazy_static",
+
 "libm",
+
 "num-integer",
+
 "num-iter",
+
 "num-traits",
+
 "rand",
+
 "smallvec",
+
 "zeroize",
+
]
+

+
[[package]]
+
name = "num-integer"
+
version = "0.1.46"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+
dependencies = [
+
 "num-traits",
+
]
+

+
[[package]]
+
name = "num-iter"
+
version = "0.1.45"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
+
dependencies = [
+
 "autocfg",
+
 "num-integer",
+
 "num-traits",
+
]
+

+
[[package]]
+
name = "num-traits"
+
version = "0.2.19"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+
dependencies = [
+
 "autocfg",
+
 "libm",
+
]
+

+
[[package]]
+
name = "once_cell"
+
version = "1.21.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc"
+

+
[[package]]
+
name = "opaque-debug"
+
version = "0.3.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
+

+
[[package]]
+
name = "p256"
+
version = "0.13.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b"
+
dependencies = [
+
 "ecdsa",
+
 "elliptic-curve",
+
 "primeorder",
+
 "sha2",
+
]
+

+
[[package]]
+
name = "p384"
+
version = "0.13.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6"
+
dependencies = [
+
 "ecdsa",
+
 "elliptic-curve",
+
 "primeorder",
+
 "sha2",
+
]
+

+
[[package]]
+
name = "p521"
+
version = "0.13.3"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2"
+
dependencies = [
+
 "base16ct",
+
 "ecdsa",
+
 "elliptic-curve",
+
 "primeorder",
+
 "rand_core",
+
 "sha2",
+
]
+

+
[[package]]
+
name = "pbkdf2"
+
version = "0.12.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
+
dependencies = [
+
 "digest",
+
]
+

+
[[package]]
+
name = "pem-rfc7468"
+
version = "0.7.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
+
dependencies = [
+
 "base64ct",
+
]
+

+
[[package]]
+
name = "percent-encoding"
+
version = "2.3.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
+

+
[[package]]
+
name = "pkcs1"
+
version = "0.7.5"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
+
dependencies = [
+
 "der",
+
 "pkcs8",
+
 "spki",
+
]
+

+
[[package]]
+
name = "pkcs8"
+
version = "0.10.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
+
dependencies = [
+
 "der",
+
 "spki",
+
]
+

+
[[package]]
+
name = "pkg-config"
+
version = "0.3.32"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
+

+
[[package]]
+
name = "poly1305"
+
version = "0.8.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
+
dependencies = [
+
 "cpufeatures",
+
 "opaque-debug",
+
 "universal-hash",
+
]
+

+
[[package]]
+
name = "polyval"
+
version = "0.6.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
+
dependencies = [
+
 "cfg-if",
+
 "cpufeatures",
+
 "opaque-debug",
+
 "universal-hash",
+
]
+

+
[[package]]
+
name = "ppv-lite86"
+
version = "0.2.21"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
+
dependencies = [
+
 "zerocopy",
+
]
+

+
[[package]]
+
name = "primeorder"
+
version = "0.13.6"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6"
+
dependencies = [
+
 "elliptic-curve",
+
]
+

+
[[package]]
+
name = "proc-macro-error"
+
version = "1.0.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+
dependencies = [
+
 "proc-macro-error-attr",
+
 "proc-macro2",
+
 "quote",
+
 "syn 1.0.109",
+
 "version_check",
+
]
+

+
[[package]]
+
name = "proc-macro-error-attr"
+
version = "1.0.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+
dependencies = [
+
 "proc-macro2",
+
 "quote",
+
 "version_check",
+
]
+

+
[[package]]
+
name = "proc-macro2"
+
version = "1.0.94"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
+
dependencies = [
+
 "unicode-ident",
+
]
+

+
[[package]]
+
name = "qcheck"
+
version = "1.0.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b439bd4242da51d62d18c95e6a6add749346756b0d1a587dfd0cc22fa6b5f3f0"
+
dependencies = [
+
 "rand",
+
]
+

+
[[package]]
+
name = "quote"
+
version = "1.0.40"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
+
dependencies = [
+
 "proc-macro2",
+
]
+

+
[[package]]
+
name = "r-efi"
+
version = "5.2.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
+

+
[[package]]
+
name = "radicle"
+
version = "0.14.0"
+
source = "git+https://seed.radicle.xyz/z3gqcJUoA1n9HaHKufZs5FCSGazv5.git?rev=refs%2Fnamespaces%2Fz6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM%2Frefs%2Fheads%2Fcob-transaction#c5c668642b4c8f5af8f23a59679fbb9260956ad7"
+
dependencies = [
+
 "amplify",
+
 "base64",
+
 "crossbeam-channel",
+
 "cyphernet",
+
 "fastrand",
+
 "git2",
+
 "libc",
+
 "localtime",
+
 "log",
+
 "multibase",
+
 "nonempty 0.9.0",
+
 "once_cell",
+
 "qcheck",
+
 "radicle-cob",
+
 "radicle-crypto",
+
 "radicle-git-ext",
+
 "radicle-ssh",
+
 "serde",
+
 "serde_json",
+
 "siphasher",
+
 "sqlite",
+
 "tempfile",
+
 "thiserror 1.0.69",
+
 "unicode-normalization",
+
]
+

+
[[package]]
+
name = "radicle-cob"
+
version = "0.13.0"
+
source = "git+https://seed.radicle.xyz/z3gqcJUoA1n9HaHKufZs5FCSGazv5.git?rev=refs%2Fnamespaces%2Fz6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM%2Frefs%2Fheads%2Fcob-transaction#c5c668642b4c8f5af8f23a59679fbb9260956ad7"
+
dependencies = [
+
 "fastrand",
+
 "git2",
+
 "log",
+
 "nonempty 0.9.0",
+
 "once_cell",
+
 "radicle-crypto",
+
 "radicle-dag",
+
 "radicle-git-ext",
+
 "serde",
+
 "serde_json",
+
 "thiserror 1.0.69",
+
]
+

+
[[package]]
+
name = "radicle-crypto"
+
version = "0.11.0"
+
source = "git+https://seed.radicle.xyz/z3gqcJUoA1n9HaHKufZs5FCSGazv5.git?rev=refs%2Fnamespaces%2Fz6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM%2Frefs%2Fheads%2Fcob-transaction#c5c668642b4c8f5af8f23a59679fbb9260956ad7"
+
dependencies = [
+
 "amplify",
+
 "cyphernet",
+
 "ec25519",
+
 "fastrand",
+
 "multibase",
+
 "qcheck",
+
 "radicle-git-ext",
+
 "radicle-ssh",
+
 "serde",
+
 "sqlite",
+
 "ssh-key",
+
 "thiserror 1.0.69",
+
 "zeroize",
+
]
+

+
[[package]]
+
name = "radicle-dag"
+
version = "0.10.0"
+
source = "git+https://seed.radicle.xyz/z3gqcJUoA1n9HaHKufZs5FCSGazv5.git?rev=refs%2Fnamespaces%2Fz6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM%2Frefs%2Fheads%2Fcob-transaction#c5c668642b4c8f5af8f23a59679fbb9260956ad7"
+
dependencies = [
+
 "fastrand",
+
]
+

+
[[package]]
+
name = "radicle-git-ext"
+
version = "0.8.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "4b78c26e67d1712ad5a0c602ae3b236609461372ac04e200bda359fe4a1c6650"
+
dependencies = [
+
 "git-ref-format",
+
 "git2",
+
 "percent-encoding",
+
 "radicle-std-ext",
+
 "serde",
+
 "thiserror 1.0.69",
+
]
+

+
[[package]]
+
name = "radicle-job"
+
version = "0.1.0"
+
dependencies = [
+
 "indexmap",
+
 "nonempty 0.11.0",
+
 "once_cell",
+
 "qcheck",
+
 "radicle",
+
 "serde",
+
 "thiserror 2.0.12",
+
 "url",
+
 "uuid",
+
]
+

+
[[package]]
+
name = "radicle-ssh"
+
version = "0.9.0"
+
source = "git+https://seed.radicle.xyz/z3gqcJUoA1n9HaHKufZs5FCSGazv5.git?rev=refs%2Fnamespaces%2Fz6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM%2Frefs%2Fheads%2Fcob-transaction#c5c668642b4c8f5af8f23a59679fbb9260956ad7"
+
dependencies = [
+
 "byteorder",
+
 "log",
+
 "thiserror 1.0.69",
+
 "zeroize",
+
]
+

+
[[package]]
+
name = "radicle-std-ext"
+
version = "0.1.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "5310e7a04506b6ce92dc9c47b26bd24c1c680937a3dcd13cd20847f89dbda32a"
+

+
[[package]]
+
name = "rand"
+
version = "0.8.5"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+
dependencies = [
+
 "rand_chacha",
+
 "rand_core",
+
]
+

+
[[package]]
+
name = "rand_chacha"
+
version = "0.3.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+
dependencies = [
+
 "ppv-lite86",
+
 "rand_core",
+
]
+

+
[[package]]
+
name = "rand_core"
+
version = "0.6.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+
dependencies = [
+
 "getrandom 0.2.15",
+
]
+

+
[[package]]
+
name = "rfc6979"
+
version = "0.4.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2"
+
dependencies = [
+
 "hmac",
+
 "subtle",
+
]
+

+
[[package]]
+
name = "rsa"
+
version = "0.9.8"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b"
+
dependencies = [
+
 "const-oid",
+
 "digest",
+
 "num-bigint-dig",
+
 "num-integer",
+
 "num-traits",
+
 "pkcs1",
+
 "pkcs8",
+
 "rand_core",
+
 "sha2",
+
 "signature 2.2.0",
+
 "spki",
+
 "subtle",
+
 "zeroize",
+
]
+

+
[[package]]
+
name = "rustix"
+
version = "1.0.3"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "e56a18552996ac8d29ecc3b190b4fdbb2d91ca4ec396de7bbffaf43f3d637e96"
+
dependencies = [
+
 "bitflags",
+
 "errno",
+
 "libc",
+
 "linux-raw-sys",
+
 "windows-sys",
+
]
+

+
[[package]]
+
name = "rustversion"
+
version = "1.0.20"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
+

+
[[package]]
+
name = "ryu"
+
version = "1.0.20"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
+

+
[[package]]
+
name = "sec1"
+
version = "0.7.3"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
+
dependencies = [
+
 "base16ct",
+
 "der",
+
 "generic-array",
+
 "pkcs8",
+
 "subtle",
+
 "zeroize",
+
]
+

+
[[package]]
+
name = "serde"
+
version = "1.0.219"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
+
dependencies = [
+
 "serde_derive",
+
]
+

+
[[package]]
+
name = "serde_derive"
+
version = "1.0.219"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
+
dependencies = [
+
 "proc-macro2",
+
 "quote",
+
 "syn 2.0.100",
+
]
+

+
[[package]]
+
name = "serde_json"
+
version = "1.0.140"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
+
dependencies = [
+
 "indexmap",
+
 "itoa",
+
 "memchr",
+
 "ryu",
+
 "serde",
+
]
+

+
[[package]]
+
name = "sha2"
+
version = "0.10.8"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
+
dependencies = [
+
 "cfg-if",
+
 "cpufeatures",
+
 "digest",
+
]
+

+
[[package]]
+
name = "sha3"
+
version = "0.10.8"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60"
+
dependencies = [
+
 "digest",
+
 "keccak",
+
]
+

+
[[package]]
+
name = "shlex"
+
version = "1.3.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+

+
[[package]]
+
name = "signature"
+
version = "1.6.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c"
+

+
[[package]]
+
name = "signature"
+
version = "2.2.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
+
dependencies = [
+
 "digest",
+
 "rand_core",
+
]
+

+
[[package]]
+
name = "siphasher"
+
version = "1.0.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
+

+
[[package]]
+
name = "smallvec"
+
version = "1.14.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
+

+
[[package]]
+
name = "socks5-client"
+
version = "0.4.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "ffc7dcf6fab1d65d82d633006a4cc658d76ce436e01cf1a7c71873c0eeba324c"
+
dependencies = [
+
 "amplify",
+
 "cypheraddr",
+
]
+

+
[[package]]
+
name = "spin"
+
version = "0.9.8"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+

+
[[package]]
+
name = "spki"
+
version = "0.7.3"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
+
dependencies = [
+
 "base64ct",
+
 "der",
+
]
+

+
[[package]]
+
name = "sqlite"
+
version = "0.32.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "03801c10193857d6a4a71ec46cee198a15cbc659622aabe1db0d0bdbefbcf8e6"
+
dependencies = [
+
 "libc",
+
 "sqlite3-sys",
+
]
+

+
[[package]]
+
name = "sqlite3-src"
+
version = "0.5.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "bfc95a51a1ee38839599371685b9d4a926abb51791f0bc3bf8c3bb7867e6e454"
+
dependencies = [
+
 "cc",
+
 "pkg-config",
+
]
+

+
[[package]]
+
name = "sqlite3-sys"
+
version = "0.15.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "f2752c669433e40ebb08fde824146f50d9628aa0b66a3b7fc6be34db82a8063b"
+
dependencies = [
+
 "libc",
+
 "sqlite3-src",
+
]
+

+
[[package]]
+
name = "ssh-cipher"
+
version = "0.2.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "caac132742f0d33c3af65bfcde7f6aa8f62f0e991d80db99149eb9d44708784f"
+
dependencies = [
+
 "aes",
+
 "aes-gcm",
+
 "cbc",
+
 "chacha20",
+
 "cipher",
+
 "ctr",
+
 "poly1305",
+
 "ssh-encoding",
+
 "subtle",
+
]
+

+
[[package]]
+
name = "ssh-encoding"
+
version = "0.2.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "eb9242b9ef4108a78e8cd1a2c98e193ef372437f8c22be363075233321dd4a15"
+
dependencies = [
+
 "base64ct",
+
 "pem-rfc7468",
+
 "sha2",
+
]
+

+
[[package]]
+
name = "ssh-key"
+
version = "0.6.7"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "3b86f5297f0f04d08cabaa0f6bff7cb6aec4d9c3b49d87990d63da9d9156a8c3"
+
dependencies = [
+
 "bcrypt-pbkdf",
+
 "p256",
+
 "p384",
+
 "p521",
+
 "rand_core",
+
 "rsa",
+
 "sec1",
+
 "sha2",
+
 "signature 2.2.0",
+
 "ssh-cipher",
+
 "ssh-encoding",
+
 "subtle",
+
 "zeroize",
+
]
+

+
[[package]]
+
name = "stable_deref_trait"
+
version = "1.2.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+

+
[[package]]
+
name = "subtle"
+
version = "2.6.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
+

+
[[package]]
+
name = "syn"
+
version = "1.0.109"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+
dependencies = [
+
 "proc-macro2",
+
 "quote",
+
 "unicode-ident",
+
]
+

+
[[package]]
+
name = "syn"
+
version = "2.0.100"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
+
dependencies = [
+
 "proc-macro2",
+
 "quote",
+
 "unicode-ident",
+
]
+

+
[[package]]
+
name = "synstructure"
+
version = "0.13.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
+
dependencies = [
+
 "proc-macro2",
+
 "quote",
+
 "syn 2.0.100",
+
]
+

+
[[package]]
+
name = "tempfile"
+
version = "3.19.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf"
+
dependencies = [
+
 "fastrand",
+
 "getrandom 0.3.2",
+
 "once_cell",
+
 "rustix",
+
 "windows-sys",
+
]
+

+
[[package]]
+
name = "thiserror"
+
version = "1.0.69"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+
dependencies = [
+
 "thiserror-impl 1.0.69",
+
]
+

+
[[package]]
+
name = "thiserror"
+
version = "2.0.12"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
+
dependencies = [
+
 "thiserror-impl 2.0.12",
+
]
+

+
[[package]]
+
name = "thiserror-impl"
+
version = "1.0.69"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+
dependencies = [
+
 "proc-macro2",
+
 "quote",
+
 "syn 2.0.100",
+
]
+

+
[[package]]
+
name = "thiserror-impl"
+
version = "2.0.12"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
+
dependencies = [
+
 "proc-macro2",
+
 "quote",
+
 "syn 2.0.100",
+
]
+

+
[[package]]
+
name = "tinystr"
+
version = "0.7.6"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
+
dependencies = [
+
 "displaydoc",
+
 "zerovec",
+
]
+

+
[[package]]
+
name = "tinyvec"
+
version = "1.9.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71"
+
dependencies = [
+
 "tinyvec_macros",
+
]
+

+
[[package]]
+
name = "tinyvec_macros"
+
version = "0.1.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+

+
[[package]]
+
name = "typenum"
+
version = "1.18.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
+

+
[[package]]
+
name = "unicode-ident"
+
version = "1.0.18"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
+

+
[[package]]
+
name = "unicode-normalization"
+
version = "0.1.24"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956"
+
dependencies = [
+
 "tinyvec",
+
]
+

+
[[package]]
+
name = "universal-hash"
+
version = "0.5.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
+
dependencies = [
+
 "crypto-common",
+
 "subtle",
+
]
+

+
[[package]]
+
name = "url"
+
version = "2.5.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
+
dependencies = [
+
 "form_urlencoded",
+
 "idna",
+
 "percent-encoding",
+
 "serde",
+
]
+

+
[[package]]
+
name = "utf16_iter"
+
version = "1.0.5"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
+

+
[[package]]
+
name = "utf8_iter"
+
version = "1.0.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
+

+
[[package]]
+
name = "uuid"
+
version = "1.16.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9"
+
dependencies = [
+
 "getrandom 0.3.2",
+
 "serde",
+
]
+

+
[[package]]
+
name = "vcpkg"
+
version = "0.2.15"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+

+
[[package]]
+
name = "version_check"
+
version = "0.9.5"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+

+
[[package]]
+
name = "wasi"
+
version = "0.11.0+wasi-snapshot-preview1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+

+
[[package]]
+
name = "wasi"
+
version = "0.14.2+wasi-0.2.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
+
dependencies = [
+
 "wit-bindgen-rt",
+
]
+

+
[[package]]
+
name = "wasm-bindgen"
+
version = "0.2.100"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
+
dependencies = [
+
 "cfg-if",
+
 "once_cell",
+
 "rustversion",
+
 "wasm-bindgen-macro",
+
]
+

+
[[package]]
+
name = "wasm-bindgen-backend"
+
version = "0.2.100"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
+
dependencies = [
+
 "bumpalo",
+
 "log",
+
 "proc-macro2",
+
 "quote",
+
 "syn 2.0.100",
+
 "wasm-bindgen-shared",
+
]
+

+
[[package]]
+
name = "wasm-bindgen-macro"
+
version = "0.2.100"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
+
dependencies = [
+
 "quote",
+
 "wasm-bindgen-macro-support",
+
]
+

+
[[package]]
+
name = "wasm-bindgen-macro-support"
+
version = "0.2.100"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
+
dependencies = [
+
 "proc-macro2",
+
 "quote",
+
 "syn 2.0.100",
+
 "wasm-bindgen-backend",
+
 "wasm-bindgen-shared",
+
]
+

+
[[package]]
+
name = "wasm-bindgen-shared"
+
version = "0.2.100"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
+
dependencies = [
+
 "unicode-ident",
+
]
+

+
[[package]]
+
name = "windows-sys"
+
version = "0.59.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+
dependencies = [
+
 "windows-targets",
+
]
+

+
[[package]]
+
name = "windows-targets"
+
version = "0.52.6"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+
dependencies = [
+
 "windows_aarch64_gnullvm",
+
 "windows_aarch64_msvc",
+
 "windows_i686_gnu",
+
 "windows_i686_gnullvm",
+
 "windows_i686_msvc",
+
 "windows_x86_64_gnu",
+
 "windows_x86_64_gnullvm",
+
 "windows_x86_64_msvc",
+
]
+

+
[[package]]
+
name = "windows_aarch64_gnullvm"
+
version = "0.52.6"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+

+
[[package]]
+
name = "windows_aarch64_msvc"
+
version = "0.52.6"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+

+
[[package]]
+
name = "windows_i686_gnu"
+
version = "0.52.6"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+

+
[[package]]
+
name = "windows_i686_gnullvm"
+
version = "0.52.6"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+

+
[[package]]
+
name = "windows_i686_msvc"
+
version = "0.52.6"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+

+
[[package]]
+
name = "windows_x86_64_gnu"
+
version = "0.52.6"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+

+
[[package]]
+
name = "windows_x86_64_gnullvm"
+
version = "0.52.6"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+

+
[[package]]
+
name = "windows_x86_64_msvc"
+
version = "0.52.6"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+

+
[[package]]
+
name = "wit-bindgen-rt"
+
version = "0.39.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
+
dependencies = [
+
 "bitflags",
+
]
+

+
[[package]]
+
name = "write16"
+
version = "1.0.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936"
+

+
[[package]]
+
name = "writeable"
+
version = "0.5.5"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
+

+
[[package]]
+
name = "yoke"
+
version = "0.7.5"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40"
+
dependencies = [
+
 "serde",
+
 "stable_deref_trait",
+
 "yoke-derive",
+
 "zerofrom",
+
]
+

+
[[package]]
+
name = "yoke-derive"
+
version = "0.7.5"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
+
dependencies = [
+
 "proc-macro2",
+
 "quote",
+
 "syn 2.0.100",
+
 "synstructure",
+
]
+

+
[[package]]
+
name = "zerocopy"
+
version = "0.8.24"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879"
+
dependencies = [
+
 "zerocopy-derive",
+
]
+

+
[[package]]
+
name = "zerocopy-derive"
+
version = "0.8.24"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be"
+
dependencies = [
+
 "proc-macro2",
+
 "quote",
+
 "syn 2.0.100",
+
]
+

+
[[package]]
+
name = "zerofrom"
+
version = "0.1.6"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
+
dependencies = [
+
 "zerofrom-derive",
+
]
+

+
[[package]]
+
name = "zerofrom-derive"
+
version = "0.1.6"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
+
dependencies = [
+
 "proc-macro2",
+
 "quote",
+
 "syn 2.0.100",
+
 "synstructure",
+
]
+

+
[[package]]
+
name = "zeroize"
+
version = "1.8.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
+

+
[[package]]
+
name = "zerovec"
+
version = "0.10.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"
+
dependencies = [
+
 "yoke",
+
 "zerofrom",
+
 "zerovec-derive",
+
]
+

+
[[package]]
+
name = "zerovec-derive"
+
version = "0.10.3"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
+
dependencies = [
+
 "proc-macro2",
+
 "quote",
+
 "syn 2.0.100",
+
]
added Cargo.toml
@@ -0,0 +1,27 @@
+
[package]
+
name = "radicle-job"
+
description = "Radicle Job Collaborative Object"
+
license = "MIT OR Apache-2.0"
+
version = "0.1.0"
+
authors = ["Fintan Halpenny <fintan.halpenny@gmail.com>"]
+
edition = "2021"
+

+
[dependencies]
+
indexmap = { version = "2.7.1", features = ["serde"] }
+
nonempty = "0.11.0"
+
once_cell = "1.20.3"
+
qcheck = "1.0.0"
+
serde = { version = "1.0", features = ["derive"] }
+
thiserror = "2.0.11"
+
url = { version = "2.5.4", features = ["serde"] }
+
uuid = { version = "1.13.1", features = ["serde", "v4"] }
+

+
# TODO: we depend on a commit that improves the `Transaction::initial` method
+
[dependencies.radicle]
+
git = "https://seed.radicle.xyz/z3gqcJUoA1n9HaHKufZs5FCSGazv5.git"
+
rev= "refs/namespaces/z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM/refs/heads/cob-transaction"
+

+
[dev-dependencies.radicle]
+
git = "https://seed.radicle.xyz/z3gqcJUoA1n9HaHKufZs5FCSGazv5.git"
+
rev= "refs/namespaces/z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM/refs/heads/cob-transaction"
+
features = ["test"]
added flake.lock
@@ -0,0 +1,98 @@
+
{
+
  "nodes": {
+
    "crane": {
+
      "locked": {
+
        "lastModified": 1742394900,
+
        "narHash": "sha256-vVOAp9ahvnU+fQoKd4SEXB2JG2wbENkpqcwlkIXgUC0=",
+
        "owner": "ipetkov",
+
        "repo": "crane",
+
        "rev": "70947c1908108c0c551ddfd73d4f750ff2ea67cd",
+
        "type": "github"
+
      },
+
      "original": {
+
        "owner": "ipetkov",
+
        "repo": "crane",
+
        "type": "github"
+
      }
+
    },
+
    "flake-utils": {
+
      "inputs": {
+
        "systems": "systems"
+
      },
+
      "locked": {
+
        "lastModified": 1731533236,
+
        "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
+
        "owner": "numtide",
+
        "repo": "flake-utils",
+
        "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
+
        "type": "github"
+
      },
+
      "original": {
+
        "owner": "numtide",
+
        "repo": "flake-utils",
+
        "type": "github"
+
      }
+
    },
+
    "nixpkgs": {
+
      "locked": {
+
        "lastModified": 1742998488,
+
        "narHash": "sha256-r7dqSLNkzg1rOPsq4Tuc6p+UtAGqNRMma4riupsuZv4=",
+
        "owner": "NixOS",
+
        "repo": "nixpkgs",
+
        "rev": "8e0b1e9c8c4dfaef0ff85e09c91294ba4afa4168",
+
        "type": "github"
+
      },
+
      "original": {
+
        "owner": "NixOS",
+
        "ref": "release-24.11",
+
        "repo": "nixpkgs",
+
        "type": "github"
+
      }
+
    },
+
    "root": {
+
      "inputs": {
+
        "crane": "crane",
+
        "flake-utils": "flake-utils",
+
        "nixpkgs": "nixpkgs",
+
        "rust-overlay": "rust-overlay"
+
      }
+
    },
+
    "rust-overlay": {
+
      "inputs": {
+
        "nixpkgs": [
+
          "nixpkgs"
+
        ]
+
      },
+
      "locked": {
+
        "lastModified": 1742956365,
+
        "narHash": "sha256-Slrqmt6kJ/M7Z/ce4ebQWsz2aeEodrX56CsupOEPoz0=",
+
        "owner": "oxalica",
+
        "repo": "rust-overlay",
+
        "rev": "a0e3395c63cdbc9c1ec17915f8328c077c79c4a1",
+
        "type": "github"
+
      },
+
      "original": {
+
        "owner": "oxalica",
+
        "repo": "rust-overlay",
+
        "type": "github"
+
      }
+
    },
+
    "systems": {
+
      "locked": {
+
        "lastModified": 1681028828,
+
        "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+
        "owner": "nix-systems",
+
        "repo": "default",
+
        "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+
        "type": "github"
+
      },
+
      "original": {
+
        "owner": "nix-systems",
+
        "repo": "default",
+
        "type": "github"
+
      }
+
    }
+
  },
+
  "root": "root",
+
  "version": 7
+
}
added flake.nix
@@ -0,0 +1,108 @@
+
{
+
  description = "Radicle Job Collaborative Object";
+

+
  inputs = {
+
    nixpkgs.url = "github:NixOS/nixpkgs/release-24.11";
+

+
    crane.url = "github:ipetkov/crane";
+

+
    flake-utils.url = "github:numtide/flake-utils";
+

+
    rust-overlay = {
+
      url = "github:oxalica/rust-overlay";
+
      inputs.nixpkgs.follows = "nixpkgs";
+
    };
+
  };
+

+
  outputs = {
+
    self,
+
    nixpkgs,
+
    crane,
+
    flake-utils,
+
    rust-overlay,
+
    ...
+
  }:
+
    flake-utils.lib.eachDefaultSystem (system: let
+
      lib = nixpkgs.lib;
+
      pkgs = import nixpkgs {
+
        inherit system;
+
        overlays = [(import rust-overlay)];
+
      };
+

+
      rustToolChain = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
+
      craneLib = (crane.mkLib pkgs).overrideToolchain rustToolChain;
+

+
      src = lib.cleanSourceWith {
+
        src = ./.;
+
      };
+

+
      basicArgs = {
+
        inherit src;
+
        pname = "radicle-job";
+
        strictDeps = true;
+
      };
+

+
      # Build *just* the cargo dependencies, so we can reuse
+
      # all of that work (e.g. via cachix) when running in CI
+
      cargoArtifacts = craneLib.buildDepsOnly basicArgs;
+

+
      # Common arguments can be set here to avoid repeating them later
+
      commonArgs =
+
        basicArgs
+
        // {
+
          inherit cargoArtifacts;
+

+
          nativeBuildInputs = with pkgs; [
+
            # Add additional build inputs here
+
          ];
+
          buildInputs = lib.optionals pkgs.stdenv.buildPlatform.isDarwin (with pkgs; [
+
            darwin.apple_sdk.frameworks.Security
+
            pkgs.libiconv
+
          ]);
+
        };
+
    in {
+
      formatter = pkgs.alejandra;
+

+
      checks = {
+
        inherit (self.packages.${system}) radicle-job;
+

+
        clippy = craneLib.cargoClippy (commonArgs
+
          // {
+
            cargoClippyExtraArgs = "--all-targets -- --deny warnings";
+
          });
+

+
        doc = craneLib.cargoDoc commonArgs;
+
        fmt = craneLib.cargoFmt basicArgs;
+
        # Run tests with cargo-nextest
+
        nextest = craneLib.cargoNextest (commonArgs
+
          // {
+
            partitions = 1;
+
            partitionType = "count";
+
            # Ensure dev is used since we rely on env variables being
+
            # set in tests.
+
            env.CARGO_PROFILE = "dev";
+
          });
+
      };
+

+
      packages.radicle-job = craneLib.buildPackage (commonArgs
+
        // {
+
          src = craneLib.cleanCargoSource ./.;
+
          strictDeps = true;
+
          doCheck = false;
+
        });
+
      packages.default = self.packages.${system}.radicle-job;
+

+
      devShells.default = craneLib.devShell {
+
        # Inherit inputs from checks.
+
        checks = self.checks.${system};
+

+
        # Extra inputs can be added here; cargo and rustc are provided by default
+
        # from the toolchain that was specified earlier.
+
        packages = with pkgs; [
+
          cargo-watch
+
          ripgrep
+
          rust-analyzer
+
        ];
+
      };
+
    });
+
}
added rust-toolchain.toml
@@ -0,0 +1,4 @@
+
[toolchain]
+
channel = "1.84"
+
profile = "default"
+
components = [ "rust-src" ]
added src/error.rs
@@ -0,0 +1,22 @@
+
use radicle::{cob, git};
+
use thiserror::Error;
+

+
#[derive(Debug, Error)]
+
pub enum Build {
+
    #[error("initial action of job must request an OID")]
+
    Initial,
+
    #[error("missing commit for job run {oid}: {err}")]
+
    MissingCommit {
+
        oid: git::Oid,
+
        #[source]
+
        err: git::Error,
+
    },
+
}
+

+
#[derive(Debug, Error)]
+
pub enum Apply {
+
    #[error(transparent)]
+
    Build(#[from] Build),
+
    #[error(transparent)]
+
    Op(#[from] cob::op::OpEncodingError),
+
}
added src/lib.rs
@@ -0,0 +1,807 @@
+
use std::collections::HashMap;
+
use std::ops::{Deref, DerefMut};
+
use std::str::FromStr;
+

+
use indexmap::IndexMap;
+
use once_cell::sync::Lazy;
+
use radicle::cob::store::Cob;
+
use radicle::cob::{self, store, EntryId, Evaluate, ObjectId, Op, TypeName};
+
use radicle::crypto::Signer;
+
use radicle::node::NodeId;
+
use radicle::prelude::ReadRepository;
+
use radicle::storage::{RepositoryError, SignRepository, WriteRepository};
+
use radicle::{cob::store::CobAction, git::Oid};
+
use serde::{Deserialize, Serialize};
+
use url::Url;
+
use uuid::Uuid;
+

+
pub mod error;
+

+
/// Type name of a patch.
+
pub static TYPENAME: Lazy<TypeName> =
+
    Lazy::new(|| FromStr::from_str("xyz.radworks.job").expect("type name is valid"));
+

+
/// A `Job` describes a generic task run for a given commit [`Oid`] by a set of
+
/// nodes.
+
///
+
/// A node may run the task many times, which are accumulated in its [`Runs`]
+
/// set. Each [`Run`] is identified by a [`Uuid`].
+
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+
pub struct Job {
+
    oid: Oid,
+
    runs: HashMap<NodeId, Runs>,
+
}
+

+
/// A set of [`Run`]s identified by a [`Uuid`].
+
///
+
/// Iteration over this set is guaranteed to come in insertion order.
+
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
+
pub struct Runs(IndexMap<Uuid, Run>);
+

+
impl Runs {
+
    /// Insert a new [`Run`], identified by the given [`Uuid`].
+
    pub fn insert(&mut self, uuid: Uuid, run: Run) -> Option<Run> {
+
        self.0.insert(uuid, run)
+
    }
+

+
    /// Check that the set of runs contains the given [`Uuid`].
+
    pub fn contains_key(&self, uuid: &Uuid) -> bool {
+
        self.0.contains_key(uuid)
+
    }
+

+
    /// Get the [`Run`] identifier by the given [`Uuid`].
+
    ///
+
    /// Return `None` if the `Uuid` does not exist.`
+
    pub fn get(&self, uuid: &Uuid) -> Option<&Run> {
+
        self.0.get(uuid)
+
    }
+

+
    /// Get the `nth` [`Run`] of the set.
+
    pub fn get_index(&self, nth: usize) -> Option<(&Uuid, &Run)> {
+
        self.0.get_index(nth)
+
    }
+

+
    /// Get the latest [`Run`] and its corresponding [`Uuid`].
+
    pub fn latest(&self) -> Option<(&Uuid, &Run)> {
+
        self.0.iter().next_back()
+
    }
+

+
    /// Get all [`Run`]s that have started [`Status`].
+
    pub fn started(&self) -> Runs {
+
        self.iter()
+
            .filter_map(|(uuid, run)| run.is_started().then_some((*uuid, run.clone())))
+
            .collect()
+
    }
+

+
    /// Get all [`Run`]s that have finished [`Status`].
+
    pub fn finished(&self) -> Runs {
+
        self.iter()
+
            .filter_map(|(uuid, run)| run.is_finished().then_some((*uuid, run.clone())))
+
            .collect()
+
    }
+

+
    /// Get all [`Run`]s that have finished [`Status`] and have the succeeded
+
    /// [`Reason`].
+
    pub fn succeeded(&self) -> Runs {
+
        self.iter()
+
            .filter_map(|(uuid, run)| run.succeeded().then_some((*uuid, run.clone())))
+
            .collect()
+
    }
+

+
    /// Get all [`Run`]s that have finished [`Status`] and have the failed
+
    /// [`Reason`].
+
    pub fn failed(&self) -> Runs {
+
        self.iter()
+
            .filter_map(|(uuid, run)| run.failed().then_some((*uuid, run.clone())))
+
            .collect()
+
    }
+

+
    /// Partition all [`Run`]s into started, succeeded, and failed.
+
    pub fn partition(&self) -> (Runs, Runs, Runs) {
+
        let mut started = IndexMap::new();
+
        let mut succeeded = IndexMap::new();
+
        let mut failed = IndexMap::new();
+

+
        for (uuid, run) in self.0.iter() {
+
            match run.status {
+
                Status::Started => started.insert(*uuid, run.clone()),
+
                Status::Finished(Reason::Succeeded) => succeeded.insert(*uuid, run.clone()),
+
                Status::Finished(Reason::Failed) => failed.insert(*uuid, run.clone()),
+
            };
+
        }
+
        (Runs(started), Runs(succeeded), Runs(failed))
+
    }
+

+
    /// Check is the set of [`Runs`] empty.
+
    pub fn is_empty(&self) -> bool {
+
        self.0.is_empty()
+
    }
+

+
    /// Get the number of [`Runs`] in the set.
+
    pub fn len(&self) -> usize {
+
        self.0.len()
+
    }
+

+
    /// Iterate over the [`Run`]s and their corresponding [`Uuid`]s.
+
    ///
+
    /// The order of the iteration is guaranteed to be insertion order.
+
    pub fn iter(&self) -> impl Iterator<Item = (&Uuid, &Run)> {
+
        self.0.iter()
+
    }
+
}
+

+
impl FromIterator<(Uuid, Run)> for Runs {
+
    fn from_iter<T: IntoIterator<Item = (Uuid, Run)>>(iter: T) -> Self {
+
        Self(iter.into_iter().collect())
+
    }
+
}
+

+
impl<'a> IntoIterator for &'a Runs {
+
    type Item = (&'a Uuid, &'a Run);
+
    type IntoIter = indexmap::map::Iter<'a, Uuid, Run>;
+

+
    fn into_iter(self) -> Self::IntoIter {
+
        self.0.iter()
+
    }
+
}
+

+
impl IntoIterator for Runs {
+
    type Item = (Uuid, Run);
+
    type IntoIter = indexmap::map::IntoIter<Uuid, Run>;
+

+
    fn into_iter(self) -> Self::IntoIter {
+
        self.0.into_iter()
+
    }
+
}
+

+
/// The collaborative object actions that are used for Radicle COB operations.
+
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
+
pub enum Action {
+
    /// Request a [`Job`] be created for the given [`Oid`].
+
    ///
+
    /// Note this is expected only once for initializing the [`Job`]. Every
+
    /// other `Request` for the same `Oid` will be ignored.
+
    Request {
+
        /// The commit the [`Job`] corresponds to.
+
        oid: Oid,
+
    },
+
    /// Notify that the node has started a [`Run`].
+
    Run {
+
        /// The [`Uuid`] that identifies this particular [`Run`] of the node.
+
        uuid: Uuid,
+
        /// The [`Url`] where the node will log any information or data.
+
        log: Url,
+
    },
+
    /// Notify that the node has finished a [`Run`].
+
    Finished {
+
        /// The [`Uuid`] that identifies the [`Run`] that is finished.
+
        uuid: Uuid,
+
        /// The [`Reason`] which the node finished with.
+
        reason: Reason,
+
    },
+
}
+

+
impl CobAction for Action {
+
    fn parents(&self) -> Vec<radicle::git::Oid> {
+
        match self {
+
            Action::Request { oid } => vec![*oid],
+
            _ => Vec::new(),
+
        }
+
    }
+
}
+

+
impl Job {
+
    /// Construct a new [`Job`].
+
    fn new(oid: Oid) -> Self {
+
        Self {
+
            oid,
+
            runs: HashMap::new(),
+
        }
+
    }
+

+
    /// Get the [`Oid`] this [`Job`] is running tasks for.
+
    pub fn oid(&self) -> &Oid {
+
        &self.oid
+
    }
+

+
    /// Get all the nodes that have started, but not finished, [`Runs`].
+
    pub fn started(&self) -> HashMap<NodeId, Runs> {
+
        self.filter_map_by(|runs| runs.started())
+
    }
+

+
    /// Get all the nodes that have started, and finished, [`Runs`].
+
    pub fn finished(&self) -> HashMap<NodeId, Runs> {
+
        self.filter_map_by(|runs| runs.finished())
+
    }
+

+
    /// Get all the nodes that have succeeded [`Runs`].
+
    pub fn succeeded(&self) -> HashMap<NodeId, Runs> {
+
        self.filter_map_by(|runs| runs.succeeded())
+
    }
+

+
    /// Get all the nodes that have failed [`Runs`].
+
    pub fn failed(&self) -> HashMap<NodeId, Runs> {
+
        self.filter_map_by(|runs| runs.failed())
+
    }
+

+
    /// Get all nodes' started, succeeded, and failed runs – respectively.
+
    pub fn partition(&self) -> HashMap<NodeId, (Runs, Runs, Runs)> {
+
        self.runs
+
            .iter()
+
            .map(|(node, runs)| (*node, runs.partition()))
+
            .collect()
+
    }
+

+
    /// Get the latest [`Run`] of the given [`NodeId`].
+
    pub fn latest_of(&self, node: &NodeId) -> Option<(&Uuid, &Run)> {
+
        self.runs
+
            .get(node)
+
            .and_then(|runs| runs.0.iter().next_back())
+
    }
+

+
    /// Get the latest [`Run`]s of all [`NodeId`]s.
+
    pub fn latest(&self) -> impl Iterator<Item = (&NodeId, &Uuid, &Run)> + '_ {
+
        self.runs
+
            .iter()
+
            .filter_map(|(node, runs)| runs.latest().map(|(uuid, run)| (node, uuid, run)))
+
    }
+

+
    /// Get the raw `HashMap` of the node runs.
+
    pub fn runs(&self) -> &HashMap<NodeId, Runs> {
+
        &self.runs
+
    }
+

+
    /// Get the [`Runs`] of a given [`NodeId`].
+
    pub fn runs_of(&self, node: &NodeId) -> Option<&Runs> {
+
        self.runs.get(node)
+
    }
+

+
    fn filter_map_by<P>(&self, p: P) -> HashMap<NodeId, Runs>
+
    where
+
        P: Fn(&Runs) -> Runs,
+
    {
+
        self.runs
+
            .iter()
+
            .filter_map(|(node, runs)| {
+
                let runs = p(runs);
+
                (!runs.is_empty()).then_some((*node, runs))
+
            })
+
            .collect()
+
    }
+

+
    fn insert(&mut self, node: NodeId, uuid: Uuid, run: Run) -> bool {
+
        let runs = self.runs.entry(node).or_default();
+
        if runs.contains_key(&uuid) {
+
            false
+
        } else {
+
            runs.insert(uuid, run);
+
            true
+
        }
+
    }
+

+
    fn update(&mut self, node: NodeId, uuid: Uuid, reason: Reason) -> bool {
+
        let Some(runs) = self.runs.get_mut(&node) else {
+
            return false;
+
        };
+
        let mut updated = false;
+
        runs.0.entry(uuid).and_modify(|run| {
+
            updated = true;
+
            *run = run.clone().finish(reason);
+
        });
+
        updated
+
    }
+

+
    fn action(&mut self, node: NodeId, action: Action) -> Result<(), error::Build> {
+
        match action {
+
            // Cannot request for another `oid`, so we ignore any superfluous
+
            // request actions
+
            Action::Request { .. } => Ok(()),
+
            Action::Run { uuid, log } => {
+
                self.insert(node, uuid, Run::new(log));
+
                Ok(())
+
            }
+
            Action::Finished { uuid, reason } => {
+
                self.update(node, uuid, reason);
+
                Ok(())
+
            }
+
        }
+
    }
+
}
+

+
impl store::Cob for Job {
+
    type Action = Action;
+
    type Error = error::Build;
+

+
    fn type_name() -> &'static TypeName {
+
        &TYPENAME
+
    }
+

+
    fn from_root<R: ReadRepository>(op: Op<Self::Action>, repo: &R) -> Result<Self, Self::Error> {
+
        let mut actions = op.actions.into_iter();
+
        let Some(Action::Request { oid }) = actions.next() else {
+
            return Err(error::Build::Initial);
+
        };
+
        repo.commit(oid)
+
            .map_err(|err| error::Build::MissingCommit { oid, err })?;
+
        let mut runs = Self::new(oid);
+
        for action in actions {
+
            runs.action(op.author, action)?;
+
        }
+
        Ok(runs)
+
    }
+

+
    fn op<'a, R: ReadRepository, I: IntoIterator<Item = &'a radicle::cob::Entry>>(
+
        &mut self,
+
        op: Op<Self::Action>,
+
        _concurrent: I,
+
        _repo: &R,
+
    ) -> Result<(), Self::Error> {
+
        for action in op.actions {
+
            self.action(op.author, action)?;
+
        }
+
        Ok(())
+
    }
+
}
+

+
impl<R: ReadRepository> Evaluate<R> for Job {
+
    type Error = error::Apply;
+

+
    fn init(entry: &radicle::cob::Entry, store: &R) -> Result<Self, Self::Error> {
+
        let op = Op::try_from(entry)?;
+
        let object = Job::from_root(op, store)?;
+
        Ok(object)
+
    }
+

+
    fn apply<'a, I: Iterator<Item = (&'a Oid, &'a radicle::cob::Entry)>>(
+
        &mut self,
+
        entry: &radicle::cob::Entry,
+
        concurrent: I,
+
        store: &R,
+
    ) -> Result<(), Self::Error> {
+
        let op = Op::try_from(entry)?;
+
        self.op(op, concurrent.map(|(_, e)| e), store)
+
            .map_err(error::Apply::from)
+
    }
+
}
+

+
/// A `Run` represents a task run for a [`Job`].
+
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
+
pub struct Run {
+
    /// The status of the run.
+
    ///
+
    /// A run can be in one of three states:
+
    ///   - Started
+
    ///   - Succeeded – implies that it has finished
+
    ///   - Failed – implies that it has finished
+
    status: Status,
+
    /// The [`Url`] of the [`Run`] where information is logged by the node.
+
    log: Url,
+
}
+

+
impl Run {
+
    fn new(log: Url) -> Self {
+
        Self {
+
            status: Status::Started,
+
            log,
+
        }
+
    }
+

+
    fn finish(self, reason: Reason) -> Self {
+
        Self {
+
            status: Status::Finished(reason),
+
            log: self.log,
+
        }
+
    }
+

+
    pub fn status(&self) -> &Status {
+
        &self.status
+
    }
+

+
    pub fn is_started(&self) -> bool {
+
        match self.status {
+
            Status::Started => true,
+
            Status::Finished(_) => false,
+
        }
+
    }
+

+
    pub fn is_finished(&self) -> bool {
+
        !self.is_started()
+
    }
+

+
    pub fn succeeded(&self) -> bool {
+
        match self.status {
+
            Status::Started => false,
+
            Status::Finished(Reason::Failed) => false,
+
            Status::Finished(Reason::Succeeded) => true,
+
        }
+
    }
+

+
    pub fn failed(&self) -> bool {
+
        match self.status {
+
            Status::Started => false,
+
            Status::Finished(Reason::Failed) => true,
+
            Status::Finished(Reason::Succeeded) => false,
+
        }
+
    }
+
}
+

+
/// The status of a [`Run`].
+
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
+
pub enum Status {
+
    /// The [`Run`] has started.
+
    Started,
+
    /// The [`Run`] has finished with the given [`Reason`].
+
    Finished(Reason),
+
}
+

+
/// The reason for a [`Status`] to have finished.
+
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
+
pub enum Reason {
+
    Failed,
+
    Succeeded,
+
}
+

+
pub struct Jobs<'a, R> {
+
    raw: store::Store<'a, Job, R>,
+
}
+

+
impl<'a, R> Deref for Jobs<'a, R> {
+
    type Target = store::Store<'a, Job, R>;
+

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

+
impl<'a, R> Jobs<'a, R>
+
where
+
    R: ReadRepository + cob::Store,
+
{
+
    /// Open a jobs store.
+
    pub fn open(repository: &'a R) -> Result<Self, RepositoryError> {
+
        let identity = repository.identity_head()?;
+
        let raw = store::Store::open(repository)?.identity(identity);
+

+
        Ok(Self { raw })
+
    }
+

+
    /// Return the number of [`Job`]s in the store.
+
    pub fn counts(&self) -> Result<usize, store::Error> {
+
        Ok(self.all()?.count())
+
    }
+

+
    /// Get a [`Job`].
+
    pub fn get(&self, id: &ObjectId) -> Result<Option<Job>, store::Error> {
+
        self.raw.get(id)
+
    }
+
}
+

+
impl<'a, R> Jobs<'a, R>
+
where
+
    R: ReadRepository + SignRepository + cob::Store,
+
{
+
    /// Get a [`JobMut`].
+
    pub fn get_mut<'g, C>(&'g mut self, id: &ObjectId) -> Result<JobMut<'a, 'g, R>, store::Error> {
+
        let job = self
+
            .raw
+
            .get(id)?
+
            .ok_or_else(move || store::Error::NotFound(TYPENAME.clone(), *id))?;
+

+
        Ok(JobMut {
+
            id: *id,
+
            job,
+
            store: self,
+
        })
+
    }
+

+
    pub fn create<'g, G>(
+
        &'g mut self,
+
        oid: Oid,
+
        signer: &G,
+
    ) -> Result<JobMut<'a, 'g, R>, store::Error>
+
    where
+
        G: Signer,
+
    {
+
        let (id, job) = store::Transaction::initial::<_, _, Transaction<R>>(
+
            "Request job",
+
            &mut self.raw,
+
            signer,
+
            |tx, _| {
+
                tx.request(oid)?;
+
                Ok(())
+
            },
+
        )?;
+

+
        Ok(JobMut {
+
            id,
+
            job,
+
            store: self,
+
        })
+
    }
+
}
+

+
pub struct JobMut<'a, 'g, R> {
+
    pub id: ObjectId,
+

+
    job: Job,
+
    store: &'g mut Jobs<'a, R>,
+
}
+

+
impl<R> Deref for JobMut<'_, '_, R> {
+
    type Target = Job;
+

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

+
impl<'a, 'g, R> JobMut<'a, 'g, R>
+
where
+
    R: WriteRepository + cob::Store,
+
{
+
    pub fn new(id: ObjectId, job: Job, store: &'g mut Jobs<'a, R>) -> Self {
+
        Self { id, job, store }
+
    }
+

+
    pub fn id(&self) -> &ObjectId {
+
        &self.id
+
    }
+

+
    /// Reload the patch data from storage.
+
    pub fn reload(&mut self) -> Result<(), store::Error> {
+
        self.job = self
+
            .store
+
            .get(&self.id)?
+
            .ok_or_else(|| store::Error::NotFound(TYPENAME.clone(), self.id))?;
+

+
        Ok(())
+
    }
+

+
    /// Start a new [`Run`] for the node, where the run is identified by the
+
    /// given [`Uuid`].
+
    pub fn run<G>(&mut self, uuid: Uuid, log: Url, signer: &G) -> Result<EntryId, store::Error>
+
    where
+
        G: Signer,
+
    {
+
        self.transaction("Run node job", signer, |tx| tx.run(uuid, log))
+
    }
+

+
    /// Finish a [`Run`], identified by the given [`Uuid`], for the node, with
+
    /// the provided [`Reason`].
+
    pub fn finish<G>(
+
        &mut self,
+
        uuid: Uuid,
+
        reason: Reason,
+
        signer: &G,
+
    ) -> Result<EntryId, store::Error>
+
    where
+
        G: Signer,
+
    {
+
        self.transaction("Finished node job", signer, |tx| tx.finish(uuid, reason))
+
    }
+

+
    pub fn transaction<G, F>(
+
        &mut self,
+
        message: &str,
+
        signer: &G,
+
        operations: F,
+
    ) -> Result<EntryId, store::Error>
+
    where
+
        G: Signer,
+
        F: FnOnce(&mut Transaction<R>) -> Result<(), store::Error>,
+
    {
+
        let mut tx = Transaction::default();
+
        operations(&mut tx)?;
+

+
        let (job, commit) = tx.0.commit(message, self.id, &mut self.store.raw, signer)?;
+
        self.job = job;
+

+
        Ok(commit)
+
    }
+
}
+

+
pub struct Transaction<R: ReadRepository>(store::Transaction<Job, R>);
+

+
impl<R> From<store::Transaction<Job, R>> for Transaction<R>
+
where
+
    R: ReadRepository,
+
{
+
    fn from(tx: store::Transaction<Job, R>) -> Self {
+
        Self(tx)
+
    }
+
}
+

+
impl<R> From<Transaction<R>> for store::Transaction<Job, R>
+
where
+
    R: ReadRepository,
+
{
+
    fn from(Transaction(tx): Transaction<R>) -> Self {
+
        tx
+
    }
+
}
+

+
impl<R> Default for Transaction<R>
+
where
+
    R: ReadRepository,
+
{
+
    fn default() -> Self {
+
        Self(Default::default())
+
    }
+
}
+

+
impl<R> Deref for Transaction<R>
+
where
+
    R: ReadRepository,
+
{
+
    type Target = store::Transaction<Job, R>;
+

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

+
impl<R> DerefMut for Transaction<R>
+
where
+
    R: ReadRepository,
+
{
+
    fn deref_mut(&mut self) -> &mut Self::Target {
+
        &mut self.0
+
    }
+
}
+

+
impl<R> Transaction<R>
+
where
+
    R: ReadRepository,
+
{
+
    pub fn request(&mut self, oid: Oid) -> Result<(), store::Error> {
+
        self.0.push(Action::Request { oid })
+
    }
+

+
    pub fn run(&mut self, uuid: Uuid, log: Url) -> Result<(), store::Error> {
+
        self.0.push(Action::Run { uuid, log })
+
    }
+

+
    pub fn finish(&mut self, uuid: Uuid, reason: Reason) -> Result<(), store::Error> {
+
        self.0.push(Action::Finished { uuid, reason })
+
    }
+
}
+

+
#[cfg(test)]
+
#[allow(clippy::unwrap_used)]
+
mod test {
+
    use radicle::crypto::Signer;
+
    use radicle::git::{raw::Repository, Oid};
+
    use radicle::test;
+
    use url::Url;
+
    use uuid::Uuid;
+

+
    use crate::{Jobs, Reason, Run, Runs, Status};
+

+
    fn node_run() -> (Uuid, Url) {
+
        let uuid = Uuid::new_v4();
+
        let log = Url::parse(&format!("https://example.com/ci/logs?run={}", uuid)).unwrap();
+
        (uuid, log)
+
    }
+

+
    fn commit(repo: &Repository) -> Oid {
+
        let tree = {
+
            let tree = repo.treebuilder(None).unwrap();
+
            let oid = tree.write().unwrap();
+
            repo.find_tree(oid).unwrap()
+
        };
+

+
        let author = repo.signature().unwrap();
+
        repo.commit(None, &author, &author, "Test Commit", &tree, &[])
+
            .unwrap()
+
            .into()
+
    }
+

+
    #[test]
+
    fn e2e() {
+
        let test::setup::NodeWithRepo {
+
            node: alice, repo, ..
+
        } = test::setup::NodeWithRepo::default();
+
        let oid = commit(&repo.backend);
+
        let mut jobs = Jobs::open(&*repo).unwrap();
+

+
        let test::setup::NodeWithRepo { node: bob, .. } = test::setup::NodeWithRepo::default();
+
        let mut job = jobs.create(oid, &alice.signer).unwrap();
+

+
        let (alice_uuid, alice_log) = node_run();
+
        job.run(alice_uuid, alice_log.clone(), &alice.signer)
+
            .unwrap();
+

+
        let (bob_uuid, bob_log) = node_run();
+
        job.run(bob_uuid, bob_log.clone(), &bob.signer).unwrap();
+

+
        let alice_runs = job.runs_of(alice.signer.public_key());
+
        assert!(alice_runs.is_some());
+
        assert_eq!(
+
            *alice_runs.unwrap(),
+
            [(alice_uuid, Run::new(alice_log))]
+
                .into_iter()
+
                .collect::<Runs>()
+
        );
+

+
        let bob_runs = job.runs_of(bob.signer.public_key());
+
        assert!(bob_runs.is_some());
+
        assert_eq!(
+
            *bob_runs.unwrap(),
+
            [(bob_uuid, Run::new(bob_log))]
+
                .into_iter()
+
                .collect::<Runs>()
+
        );
+

+
        job.finish(alice_uuid, Reason::Succeeded, &alice.signer)
+
            .unwrap();
+

+
        let finished = job.finished();
+
        assert!(finished.contains_key(alice.signer.public_key()));
+
        assert!(!finished.contains_key(bob.signer.public_key()));
+

+
        job.finish(bob_uuid, Reason::Failed, &bob.signer).unwrap();
+

+
        let succeeded = job.succeeded();
+
        assert!(succeeded.contains_key(alice.signer.public_key()));
+
        assert!(!succeeded.contains_key(bob.signer.public_key()));
+
        let failed = job.failed();
+
        assert!(!failed.contains_key(alice.signer.public_key()));
+
        assert!(failed.contains_key(bob.signer.public_key()));
+
        let started = job.started();
+
        assert!(started.is_empty());
+
    }
+

+
    #[test]
+
    fn missing_commit() {
+
        let test::setup::NodeWithRepo {
+
            node: alice, repo, ..
+
        } = test::setup::NodeWithRepo::default();
+
        let mut jobs = Jobs::open(&*repo).unwrap();
+
        let oid = test::arbitrary::oid();
+
        let job = jobs.create(oid, &alice.signer);
+
        assert!(job.is_err())
+
    }
+

+
    #[test]
+
    fn idempotent_create() {
+
        let test::setup::NodeWithRepo {
+
            node: alice, repo, ..
+
        } = test::setup::NodeWithRepo::default();
+
        let oid = commit(&repo.backend);
+
        let mut jobs = Jobs::open(&*repo).unwrap();
+
        let job1 = {
+
            let job1 = jobs.create(oid, &alice.signer).unwrap();
+
            job1.id
+
        };
+
        let job2 = {
+
            let job2 = jobs.create(oid, &alice.signer).unwrap();
+
            job2.id
+
        };
+

+
        assert_eq!(job1, job2);
+
        assert_eq!(jobs.get(&job1).unwrap(), jobs.get(&job2).unwrap());
+
    }
+

+
    #[test]
+
    fn runs_insertion_order_iteration() {
+
        let mut runs = Runs::default();
+
        let uuids = (0..10).map(|_| Uuid::new_v4()).collect::<Vec<_>>();
+
        for uuid in &uuids {
+
            runs.insert(
+
                *uuid,
+
                Run {
+
                    status: Status::Started,
+
                    log: Url::parse("https://example.com/ci/logs").unwrap(),
+
                },
+
            );
+
        }
+

+
        assert_eq!(
+
            uuids,
+
            runs.iter()
+
                .map(|(uuid, _)| uuid)
+
                .copied()
+
                .collect::<Vec<_>>()
+
        )
+
    }
+
}