commit d5685cb7557203878fc9f5ba8e71e2b617ba1d2d Author: Fintan Halpenny Date: Wed Mar 26 14:15:52 2025 +0000 radicle-job The genesis of the `Job` collaborative object diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..5489941 --- /dev/null +++ b/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", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..2c6ed0d --- /dev/null +++ b/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 "] +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"] diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..45f8812 --- /dev/null +++ b/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 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..72c50bc --- /dev/null +++ b/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 + ]; + }; + }); +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..ba99c25 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "1.84" +profile = "default" +components = [ "rust-src" ] diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..a4574bd --- /dev/null +++ b/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), +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..d743297 --- /dev/null +++ b/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 = + 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, +} + +/// 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); + +impl Runs { + /// Insert a new [`Run`], identified by the given [`Uuid`]. + pub fn insert(&mut self, uuid: Uuid, run: Run) -> Option { + 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 { + self.0.iter() + } +} + +impl FromIterator<(Uuid, Run)> for Runs { + fn from_iter>(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; + + 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 { + 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 { + self.filter_map_by(|runs| runs.started()) + } + + /// Get all the nodes that have started, and finished, [`Runs`]. + pub fn finished(&self) -> HashMap { + self.filter_map_by(|runs| runs.finished()) + } + + /// Get all the nodes that have succeeded [`Runs`]. + pub fn succeeded(&self) -> HashMap { + self.filter_map_by(|runs| runs.succeeded()) + } + + /// Get all the nodes that have failed [`Runs`]. + pub fn failed(&self) -> HashMap { + self.filter_map_by(|runs| runs.failed()) + } + + /// Get all nodes' started, succeeded, and failed runs – respectively. + pub fn partition(&self) -> HashMap { + 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 + '_ { + 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 { + &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

(&self, p: P) -> HashMap + 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(op: Op, repo: &R) -> Result { + 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>( + &mut self, + op: Op, + _concurrent: I, + _repo: &R, + ) -> Result<(), Self::Error> { + for action in op.actions { + self.action(op.author, action)?; + } + Ok(()) + } +} + +impl Evaluate for Job { + type Error = error::Apply; + + fn init(entry: &radicle::cob::Entry, store: &R) -> Result { + let op = Op::try_from(entry)?; + let object = Job::from_root(op, store)?; + Ok(object) + } + + fn apply<'a, I: Iterator>( + &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 { + 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 { + Ok(self.all()?.count()) + } + + /// Get a [`Job`]. + pub fn get(&self, id: &ObjectId) -> Result, 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, 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, store::Error> + where + G: Signer, + { + let (id, job) = store::Transaction::initial::<_, _, Transaction>( + "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 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(&mut self, uuid: Uuid, log: Url, signer: &G) -> Result + 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( + &mut self, + uuid: Uuid, + reason: Reason, + signer: &G, + ) -> Result + where + G: Signer, + { + self.transaction("Finished node job", signer, |tx| tx.finish(uuid, reason)) + } + + pub fn transaction( + &mut self, + message: &str, + signer: &G, + operations: F, + ) -> Result + where + G: Signer, + F: FnOnce(&mut Transaction) -> 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(store::Transaction); + +impl From> for Transaction +where + R: ReadRepository, +{ + fn from(tx: store::Transaction) -> Self { + Self(tx) + } +} + +impl From> for store::Transaction +where + R: ReadRepository, +{ + fn from(Transaction(tx): Transaction) -> Self { + tx + } +} + +impl Default for Transaction +where + R: ReadRepository, +{ + fn default() -> Self { + Self(Default::default()) + } +} + +impl Deref for Transaction +where + R: ReadRepository, +{ + type Target = store::Transaction; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Transaction +where + R: ReadRepository, +{ + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Transaction +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::() + ); + + 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::() + ); + + 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::>(); + 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::>() + ) + } +}