diff --git a/README.md b/README.md index e38fc93..2c07837 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,8 @@ -# Job +# Hero Job -Job model and client for supervisor \ No newline at end of file +Shared job types and utilities for the Hero ecosystem. + +## Structure + +- `rust/` - Rust implementation of job types +- `vlang/` - V language implementation (future) diff --git a/Cargo.lock b/rust/Cargo.lock similarity index 67% rename from Cargo.lock rename to rust/Cargo.lock index fccda9d..a5cf153 100644 --- a/Cargo.lock +++ b/rust/Cargo.lock @@ -2,27 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "android_system_properties" version = "0.1.5" @@ -50,26 +29,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] -name = "backtrace" -version = "0.3.75" +name = "block-buffer" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets", + "generic-array", ] -[[package]] -name = "bitflags" -version = "2.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d" - [[package]] name = "bumpalo" version = "3.19.0" @@ -84,9 +51,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.35" +version = "1.2.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "590f9024a68a8c40351881787f1934dc11afd69090f5edb6831464694d836ea3" +checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3" dependencies = [ "find-msvc-tools", "shlex", @@ -94,17 +61,16 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chrono" -version = "0.4.41" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" dependencies = [ - "android-tzdata", "iana-time-zone", "js-sys", "num-traits", @@ -133,6 +99,35 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[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 = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -146,9 +141,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.0" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e178e4fba8a2726903f6ba98a6d221e76f9c12c650d5dc0e6afdc50677b49650" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" [[package]] name = "form_urlencoded" @@ -191,41 +186,55 @@ dependencies = [ ] [[package]] -name = "getrandom" -version = "0.3.3" +name = "generic-array" +version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", "r-efi", - "wasi 0.14.3+wasi-0.2.4", + "wasip2", ] -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - [[package]] name = "hero-job" version = "0.1.0" dependencies = [ "chrono", + "hex", "log", "redis", + "secp256k1", "serde", "serde_json", + "sha2", "thiserror", + "tokio", "uuid", ] [[package]] -name = "iana-time-zone" -version = "0.1.63" +name = "hex" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -247,9 +256,9 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" dependencies = [ "displaydoc", "potential_utf", @@ -260,9 +269,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" dependencies = [ "displaydoc", "litemap", @@ -273,11 +282,10 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" dependencies = [ - "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", @@ -288,42 +296,38 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" dependencies = [ - "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", - "potential_utf", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" +checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" [[package]] name = "icu_provider" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" dependencies = [ "displaydoc", "icu_locale_core", - "stable_deref_trait", - "tinystr", "writeable", "yoke", "zerofrom", @@ -352,17 +356,6 @@ dependencies = [ "icu_properties", ] -[[package]] -name = "io-uring" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" -dependencies = [ - "bitflags", - "cfg-if", - "libc", -] - [[package]] name = "itoa" version = "1.0.15" @@ -371,9 +364,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" dependencies = [ "once_cell", "wasm-bindgen", @@ -381,46 +374,37 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.175" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "litemap" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" [[package]] name = "log" -version = "0.4.27" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" - -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", -] +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "mio" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" dependencies = [ "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.59.0", + "wasi", + "windows-sys 0.61.2", ] [[package]] @@ -432,15 +416,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.21.3" @@ -467,27 +442,27 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "potential_utf" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" dependencies = [ "zerovec", ] [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] @@ -519,12 +494,6 @@ dependencies = [ "url", ] -[[package]] -name = "rustc-demangle" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" - [[package]] name = "rustversion" version = "1.0.22" @@ -538,19 +507,47 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] -name = "serde" -version = "1.0.219" +name = "secp256k1" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10" +dependencies = [ + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d1746aae42c19d583c3c1a8c646bfad910498e2051c551a7f2e3c0c9fbb7eb" +dependencies = [ + "cc", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -559,14 +556,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.143" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", + "serde_core", ] [[package]] @@ -575,18 +573,23 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" -[[package]] -name = "slab" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" - [[package]] name = "smallvec" version = "1.15.1" @@ -605,25 +608,25 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "syn" -version = "2.0.106" +version = "2.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" dependencies = [ "proc-macro2", "quote", @@ -663,9 +666,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" dependencies = [ "displaydoc", "zerovec", @@ -673,26 +676,23 @@ dependencies = [ [[package]] name = "tokio" -version = "1.47.1" +version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" dependencies = [ - "backtrace", "bytes", - "io-uring", "libc", "mio", "pin-project-lite", - "slab", - "socket2 0.6.0", - "windows-sys 0.59.0", + "socket2 0.6.1", + "windows-sys 0.61.2", ] [[package]] name = "tokio-util" -version = "0.7.16" +version = "0.7.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" dependencies = [ "bytes", "futures-core", @@ -702,10 +702,16 @@ dependencies = [ ] [[package]] -name = "unicode-ident" -version = "1.0.18" +name = "typenum" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "url" @@ -727,15 +733,21 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.18.0" +version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" dependencies = [ "getrandom", "js-sys", "wasm-bindgen", ] +[[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.1+wasi-snapshot-preview1" @@ -743,45 +755,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasi" -version = "0.14.3+wasi-0.2.4" +name = "wasip2" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a51ae83037bdd272a9e28ce236db8c07016dd0d50c27038b3f407533c030c95" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" 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", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -789,31 +788,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" dependencies = [ "unicode-ident", ] [[package]] name = "windows-core" -version = "0.61.2" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", @@ -824,9 +823,9 @@ dependencies = [ [[package]] name = "windows-implement" -version = "0.60.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", @@ -835,9 +834,9 @@ dependencies = [ [[package]] name = "windows-interface" -version = "0.59.1" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", @@ -846,24 +845,24 @@ dependencies = [ [[package]] name = "windows-link" -version = "0.1.3" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-result" -version = "0.3.4" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" -version = "0.4.2" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ "windows-link", ] @@ -874,16 +873,25 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] name = "windows-sys" -version = "0.59.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets", + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", ] [[package]] @@ -892,14 +900,31 @@ 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", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] @@ -908,42 +933,84 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + [[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_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + [[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_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + [[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_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + [[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_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -951,24 +1018,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "wit-bindgen" -version = "0.45.0" +name = "windows_x86_64_msvc" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052283831dbae3d879dc7f51f3d92703a316ca49f91540417d38591826127814" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "writeable" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] name = "yoke" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" dependencies = [ - "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -976,9 +1048,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", @@ -1009,9 +1081,9 @@ dependencies = [ [[package]] name = "zerotrie" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" dependencies = [ "displaydoc", "yoke", @@ -1020,9 +1092,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.4" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" dependencies = [ "yoke", "zerofrom", @@ -1031,9 +1103,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/rust/Cargo.toml similarity index 76% rename from Cargo.toml rename to rust/Cargo.toml index 38e67d2..083f0bf 100644 --- a/Cargo.toml +++ b/rust/Cargo.toml @@ -11,7 +11,11 @@ serde_json = "1.0" uuid = { version = "1.0", features = ["v4"] } thiserror = "1.0" redis = { version = "0.25", features = ["aio", "tokio-comp"] } +tokio = { version = "1.0", features = ["rt", "time"] } log = "0.4" +secp256k1 = { version = "0.28", features = ["recovery"] } +sha2 = "0.10" +hex = "0.4" [lib] name = "hero_job" diff --git a/src/client.rs b/rust/src/client.rs similarity index 65% rename from src/client.rs rename to rust/src/client.rs index 6e0fe36..fa9c6a5 100644 --- a/src/client.rs +++ b/rust/src/client.rs @@ -138,7 +138,7 @@ impl Client { .await .map_err(|e| JobError::Redis(e))?; - conn.hset_multiple(&job_key, &[ + let _: () = conn.hset_multiple(&job_key, &[ ("error", error), ("status", JobStatus::Error.as_str()), ("updated_at", &now.to_rfc3339()), @@ -161,7 +161,7 @@ impl Client { .await .map_err(|e| JobError::Redis(e))?; - conn.hset_multiple(&job_key, &[ + let _: () = conn.hset_multiple(&job_key, &[ ("status", status.as_str()), ("updated_at", &now.to_rfc3339()), ]).await @@ -204,8 +204,8 @@ impl Client { Ok(()) } - /// Store this job in Redis - pub async fn store_job_in_redis(&self, job: &Job) -> Result<(), JobError> { + /// Store this job in Redis with the specified status + pub async fn store_job_in_redis_with_status(&self, job: &Job, status: JobStatus) -> Result<(), JobError> { let mut conn = self.redis_client .get_multiplexed_async_connection() .await @@ -215,12 +215,12 @@ impl Client { // Serialize the job data let job_data = serde_json::to_string(job) - .map_err(|e| JobError::Serialization(e.to_string()))?; + .map_err(|e| JobError::Serialization(e))?; // Store job data in Redis hash let _: () = conn.hset_multiple(&job_key, &[ ("data", job_data), - ("status", JobStatus::Dispatched.as_str().to_string()), + ("status", status.as_str().to_string()), ("created_at", job.created_at.to_rfc3339()), ("updated_at", job.updated_at.to_rfc3339()), ]).await @@ -232,6 +232,11 @@ impl Client { Ok(()) } + + /// Store this job in Redis (defaults to Dispatched status for backwards compatibility) + pub async fn store_job_in_redis(&self, job: &Job) -> Result<(), JobError> { + self.store_job_in_redis_with_status(job, JobStatus::Dispatched).await + } /// Load a job from Redis by ID pub async fn load_job_from_redis( @@ -252,7 +257,7 @@ impl Client { match job_data { Some(data) => { let job: Job = serde_json::from_str(&data) - .map_err(|e| JobError::Serialization(e.to_string()))?; + .map_err(|e| JobError::Serialization(e))?; Ok(job) } None => Err(JobError::NotFound(job_id.to_string())), @@ -312,6 +317,21 @@ impl Client { Ok(result) } + /// Get job result from Redis + pub async fn get_error( + &self, + job_id: &str, + ) -> Result, JobError> { + let job_key = self.job_key(job_id); + let mut conn = self.redis_client + .get_multiplexed_async_connection() + .await + .map_err(|e| JobError::Redis(e))?; + let result: Option = conn.hget(&job_key, "error").await + .map_err(|e| JobError::Redis(e))?; + Ok(result) + } + /// Get a job ID from the work queue (blocking pop) pub async fn get_job_id(&self, queue_key: &str) -> Result, JobError> { let mut conn = self.redis_client @@ -330,4 +350,113 @@ impl Client { pub async fn get_job(&self, job_id: &str) -> Result { self.load_job_from_redis(job_id).await } + + /// Dispatch a job to a runner's queue + pub async fn dispatch_job(&self, job_id: &str, runner_name: &str) -> Result<(), JobError> { + let mut conn = self.redis_client + .get_multiplexed_async_connection() + .await + .map_err(|e| JobError::Redis(e))?; + + let queue_key = self.runner_key(runner_name); + + // Push job ID to the runner's queue (LPUSH for FIFO with BRPOP) + let _: () = conn.lpush(&queue_key, job_id).await + .map_err(|e| JobError::Redis(e))?; + + Ok(()) + } + + /// Run a job: dispatch it, wait for completion, and return the result + /// + /// This is a convenience method that: + /// 1. Stores the job in Redis + /// 2. Dispatches it to the runner's queue + /// 3. Waits for the job to complete (polls status) + /// 4. Returns the result or error + /// + /// # Arguments + /// * `job` - The job to run + /// * `runner_name` - The name of the runner to dispatch to + /// * `timeout_secs` - Maximum time to wait for job completion (in seconds) + /// + /// # Returns + /// * `Ok(String)` - The job result if successful + /// * `Err(JobError)` - If the job fails, times out, or encounters an error + pub async fn run_job( + &self, + job: &Job, + runner_name: &str, + timeout_secs: u64, + ) -> Result { + use tokio::time::{Duration, timeout}; + + // Store the job in Redis + self.store_job_in_redis(job).await?; + + // Dispatch to runner queue + self.dispatch_job(&job.id, runner_name).await?; + + // Wait for job to complete with timeout + let result = timeout( + Duration::from_secs(timeout_secs), + self.wait_for_job_completion(&job.id) + ).await; + + match result { + Ok(Ok(job_result)) => Ok(job_result), + Ok(Err(e)) => Err(e), + Err(_) => Err(JobError::Timeout(format!( + "Job {} did not complete within {} seconds", + job.id, timeout_secs + ))), + } + } + + /// Wait for a job to complete by polling its status + /// + /// This polls the job status every 500ms until it reaches a terminal state + /// (Finished or Error), then returns the result or error. + async fn wait_for_job_completion(&self, job_id: &str) -> Result { + use tokio::time::{sleep, Duration}; + + loop { + // Check job status + let status = self.get_status(job_id).await?; + + match status { + JobStatus::Finished => { + // Job completed successfully, get the result + let result = self.get_result(job_id).await?; + return result.ok_or_else(|| { + JobError::InvalidData(format!("Job {} finished but has no result", job_id)) + }); + } + JobStatus::Error => { + // Job failed, get the error message + let mut conn = self.redis_client + .get_multiplexed_async_connection() + .await + .map_err(|e| JobError::Redis(e))?; + + let error_msg: Option = conn + .hget(&self.job_key(job_id), "error") + .await + .map_err(|e| JobError::Redis(e))?; + + return Err(JobError::InvalidData( + error_msg.unwrap_or_else(|| format!("Job {} failed with unknown error", job_id)) + )); + } + JobStatus::Stopping => { + return Err(JobError::InvalidData(format!("Job {} was stopped", job_id))); + } + // Job is still running (Dispatched, WaitingForPrerequisites, Started) + _ => { + // Wait before polling again + sleep(Duration::from_millis(500)).await; + } + } + } + } } diff --git a/src/lib.rs b/rust/src/lib.rs similarity index 58% rename from src/lib.rs rename to rust/src/lib.rs index 4261364..a18eb22 100644 --- a/src/lib.rs +++ b/rust/src/lib.rs @@ -1,17 +1,26 @@ -use chrono::{DateTime, Utc}; -use redis::AsyncCommands; +use chrono::{Utc}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use thiserror::Error; use uuid::Uuid; -use log::{debug, error}; +use log::{error}; pub mod client; -pub use client::Client; +pub use client::{Client, ClientBuilder}; + +/// Signature for a job - contains the signatory's public key and their signature +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct JobSignature { + /// Public key of the signatory (hex-encoded secp256k1 public key) + pub public_key: String, + /// Signature (hex-encoded secp256k1 signature) + pub signature: String, +} /// Job status enumeration #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum JobStatus { + Created, Dispatched, WaitingForPrerequisites, Started, @@ -23,6 +32,7 @@ pub enum JobStatus { impl JobStatus { pub fn as_str(&self) -> &'static str { match self { + JobStatus::Created => "created", JobStatus::Dispatched => "dispatched", JobStatus::WaitingForPrerequisites => "waiting_for_prerequisites", JobStatus::Started => "started", @@ -34,6 +44,7 @@ impl JobStatus { pub fn from_str(s: &str) -> Option { match s { + "created" => Some(JobStatus::Created), "dispatched" => Some(JobStatus::Dispatched), "waiting_for_prerequisites" => Some(JobStatus::WaitingForPrerequisites), "started" => Some(JobStatus::Started), @@ -61,6 +72,9 @@ pub struct Job { pub env_vars: HashMap, // environment variables for script execution pub created_at: chrono::DateTime, pub updated_at: chrono::DateTime, + + /// Signatures from authorized signatories (public keys are included in each signature) + pub signatures: Vec, } /// Error types for job operations @@ -69,7 +83,7 @@ pub enum JobError { #[error("Redis error: {0}")] Redis(#[from] redis::RedisError), #[error("Serialization error: {0}")] - Serialization(String), + Serialization(#[from] serde_json::Error), #[error("Job not found: {0}")] NotFound(String), #[error("Invalid job status: {0}")] @@ -78,6 +92,10 @@ pub enum JobError { Timeout(String), #[error("Invalid job data: {0}")] InvalidData(String), + #[error("Signature verification failed: {0}")] + SignatureVerificationFailed(String), + #[error("Unauthorized: {0}")] + Unauthorized(String), } impl Job { @@ -101,73 +119,77 @@ impl Job { env_vars: HashMap::new(), created_at: now, updated_at: now, + signatures: Vec::new(), } } - - /// Update job status in Redis using default client - pub async fn update_status( - redis_conn: &mut redis::aio::MultiplexedConnection, - job_id: &str, - status: JobStatus, - ) -> Result<(), JobError> { - let now = Utc::now(); - let job_key = format!("hero:job:{}", job_id); + + /// Get the canonical representation of the job for signing + /// This creates a deterministic string representation that can be hashed and signed + /// Note: Signatures are excluded from the canonical representation + pub fn canonical_representation(&self) -> String { + // Create a deterministic representation excluding signatures + // Sort env_vars keys for deterministic ordering + let mut env_vars_sorted: Vec<_> = self.env_vars.iter().collect(); + env_vars_sorted.sort_by_key(|&(k, _)| k); - let _: () = redis_conn.hset_multiple(&job_key, &[ - ("status", status.as_str()), - ("updated_at", &now.to_rfc3339()), - ]).await - .map_err(|e| JobError::Redis(e))?; - Ok(()) + format!( + "{}:{}:{}:{}:{}:{}:{}:{:?}", + self.id, + self.caller_id, + self.context_id, + self.payload, + self.runner, + self.executor, + self.timeout, + env_vars_sorted + ) } - - /// Set job result in Redis using default client - pub async fn set_result( - redis_conn: &mut redis::aio::MultiplexedConnection, - job_id: &str, - result: &str, - ) -> Result<(), JobError> { - let job_key = format!("hero:job:{}", job_id); - let now = Utc::now(); + + /// Get list of signatory public keys from signatures + pub fn signatories(&self) -> Vec { + self.signatures.iter() + .map(|sig| sig.public_key.clone()) + .collect() + } + + /// Verify that all signatures are valid + /// Returns Ok(()) if verification passes, Err otherwise + /// Empty signatures list is allowed - loop simply won't execute + pub fn verify_signatures(&self) -> Result<(), JobError> { + use secp256k1::{Message, PublicKey, Secp256k1, ecdsa::Signature}; + use sha2::{Sha256, Digest}; - let _: () = redis_conn.hset_multiple(&job_key, &[ - ("result", result), - ("status", JobStatus::Finished.as_str()), - ("updated_at", &now.to_rfc3339()), - ]).await - .map_err(|e| JobError::Redis(e))?; - Ok(()) - } - - /// Set job error in Redis using default client - pub async fn set_error( - redis_conn: &mut redis::aio::MultiplexedConnection, - job_id: &str, - error: &str, - ) -> Result<(), JobError> { - let job_key = format!("hero:job:{}", job_id); - let now = Utc::now(); + // Get the canonical representation and hash it + let canonical = self.canonical_representation(); + let mut hasher = Sha256::new(); + hasher.update(canonical.as_bytes()); + let hash = hasher.finalize(); + + let secp = Secp256k1::verification_only(); + let message = Message::from_digest_slice(&hash) + .map_err(|e| JobError::SignatureVerificationFailed(format!("Invalid message: {}", e)))?; + + // Verify each signature (if any) + for sig_data in &self.signatures { + // Decode public key + let pubkey_bytes = hex::decode(&sig_data.public_key) + .map_err(|e| JobError::SignatureVerificationFailed(format!("Invalid public key hex: {}", e)))?; + let pubkey = PublicKey::from_slice(&pubkey_bytes) + .map_err(|e| JobError::SignatureVerificationFailed(format!("Invalid public key: {}", e)))?; + + // Decode signature + let sig_bytes = hex::decode(&sig_data.signature) + .map_err(|e| JobError::SignatureVerificationFailed(format!("Invalid signature hex: {}", e)))?; + let signature = Signature::from_compact(&sig_bytes) + .map_err(|e| JobError::SignatureVerificationFailed(format!("Invalid signature: {}", e)))?; + + // Verify signature + secp.verify_ecdsa(&message, &signature, &pubkey) + .map_err(|e| JobError::SignatureVerificationFailed(format!("Signature verification failed: {}", e)))?; + } - let _: () = redis_conn.hset_multiple(&job_key, &[ - ("error", error), - ("status", JobStatus::Error.as_str()), - ("updated_at", &now.to_rfc3339()), - ]).await - .map_err(|e| JobError::Redis(e))?; Ok(()) } - - /// Delete job from Redis using default client - pub async fn delete( - redis_conn: &mut redis::aio::MultiplexedConnection, - job_id: &str, - ) -> Result<(), JobError> { - let job_key = format!("hero:job:{}", job_id); - let _: () = redis_conn.del(&job_key).await - .map_err(|e| JobError::Redis(e))?; - Ok(()) - } - } /// Builder for constructing job execution requests. @@ -179,6 +201,7 @@ pub struct JobBuilder { executor: String, timeout: u64, // timeout in seconds env_vars: HashMap, + signatures: Vec, } impl JobBuilder { @@ -191,6 +214,7 @@ impl JobBuilder { executor: "".to_string(), timeout: 300, // 5 minutes default env_vars: HashMap::new(), + signatures: Vec::new(), } } @@ -248,6 +272,27 @@ impl JobBuilder { self } + /// Add a signature (public key and signature) + pub fn signature(mut self, public_key: &str, signature: &str) -> Self { + self.signatures.push(JobSignature { + public_key: public_key.to_string(), + signature: signature.to_string(), + }); + self + } + + /// Set multiple signatures + pub fn signatures(mut self, signatures: Vec) -> Self { + self.signatures = signatures; + self + } + + /// Clear all signatures + pub fn clear_signatures(mut self) -> Self { + self.signatures.clear(); + self + } + /// Build the job pub fn build(self) -> Result { if self.caller_id.is_empty() { @@ -276,6 +321,7 @@ impl JobBuilder { job.timeout = self.timeout; job.env_vars = self.env_vars; + job.signatures = self.signatures; Ok(job) }