diff --git a/AGENTS.md b/AGENTS.md index 3182f10..be16c02 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,7 +20,7 @@ Studio's job, not Huginn's. derived status included, over `cultcache-rs` and `eureka-pipeline`. `huginn-daemon` serves one mind over CultNet RUDP through `cultnet-rs`, and reaches the leaf and the store type through `huginn-mind` so the workspace's one CultLib revision (root `[workspace.dependencies]`) decides both. `eureka-state` is the client core: one call - to a daemon over CultNet, holding no state. + to a daemon over CultNet, holding no state; its `huginn` binary is the CLI agents reach the mind through, and it holds no state either. - Owned: an instance's memory documents, their admission and the read side that derives their status, in `huginn-mind`, over a redb CultCache store at `/minds//mind.redb`; diff --git a/Cargo.lock b/Cargo.lock index a46de8b..ad75baa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -48,6 +48,35 @@ dependencies = [ "version_check", ] +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "serde", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "android_system_properties" version = "0.1.6" @@ -57,6 +86,56 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + [[package]] name = "anyhow" version = "1.0.104" @@ -64,10 +143,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] -name = "arrayvec" -version = "0.7.8" +name = "ar_archive_writer" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" +checksum = "73cd58deff2140a0a8eae87e417bd01db68a33e148aa93d1e8cd837e55e312b6" +dependencies = [ + "object", +] [[package]] name = "async-trait" @@ -98,6 +180,21 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bitflags" version = "1.3.2" @@ -120,28 +217,10 @@ dependencies = [ ] [[package]] -name = "borsh" -version = "1.8.1" +name = "borrow-or-share" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "553c5d846a6ba5150c65e3b1b8ec073bcf1abc20f9b7220de384a4443ea4e20a" -dependencies = [ - "borsh-derive", - "bytes", - "cfg_aliases", -] - -[[package]] -name = "borsh-derive" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12cdfe656708a01f89b451a7d36466e6fe6c414de0aa18fc54f864f6f9ca9f56" -dependencies = [ - "once_cell", - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 3.0.5", -] +checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c" [[package]] name = "bumpalo" @@ -149,6 +228,12 @@ version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" +[[package]] +name = "bytecount" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" + [[package]] name = "bytes" version = "1.12.1" @@ -171,12 +256,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" -[[package]] -name = "cfg_aliases" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" - [[package]] name = "chrono" version = "0.4.45" @@ -201,6 +280,52 @@ dependencies = [ "inout", ] +[[package]] +name = "clap" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + [[package]] name = "const-oid" version = "0.9.6" @@ -249,7 +374,7 @@ source = "git+https://github.com/GameCult/CultLib.git?rev=8fc74c709444b884a2bd33 dependencies = [ "anyhow", "chrono", - "cultcache-rs-derive", + "cultcache-rs-derive 0.1.0", "fs2", "redb", "rmp-serde", @@ -260,6 +385,26 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "cultcache-rs" +version = "0.3.1" +source = "git+https://github.com/GameCult/CultLib.git?rev=a4eae9056152335662f1115f97bf35fb00ab6d23#a4eae9056152335662f1115f97bf35fb00ab6d23" +dependencies = [ + "anyhow", + "chrono", + "cultcache-rs-derive 0.2.0", + "fs2", + "redb", + "rmp-serde", + "serde", + "serde_bytes", + "serde_json", + "serde_stacker", + "stacker", + "uuid", + "windows-sys 0.59.0", +] + [[package]] name = "cultcache-rs-derive" version = "0.1.0" @@ -270,6 +415,16 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "cultcache-rs-derive" +version = "0.2.0" +source = "git+https://github.com/GameCult/CultLib.git?rev=a4eae9056152335662f1115f97bf35fb00ab6d23#a4eae9056152335662f1115f97bf35fb00ab6d23" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "cultnet-rs" version = "0.1.0" @@ -279,10 +434,10 @@ dependencies = [ "anyhow", "base64", "chrono", - "cultcache-rs", + "cultcache-rs 0.2.0", "ed25519-dalek", "hmac", - "rand 0.9.5", + "rand", "reed-solomon-erasure", "rmp-serde", "rmpv", @@ -296,6 +451,31 @@ dependencies = [ "zeroize", ] +[[package]] +name = "cultnet-rs" +version = "0.2.0" +source = "git+https://github.com/GameCult/CultLib.git?rev=a4eae9056152335662f1115f97bf35fb00ab6d23#a4eae9056152335662f1115f97bf35fb00ab6d23" +dependencies = [ + "aes-gcm", + "anyhow", + "base64", + "chrono", + "cultcache-rs 0.3.1", + "ed25519-dalek", + "hmac", + "rand", + "rmp-serde", + "rmpv", + "serde", + "serde_bytes", + "serde_json", + "sha2", + "socket2", + "uuid", + "windows-sys 0.61.2", + "zeroize", +] + [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -357,6 +537,12 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "data-encoding" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" + [[package]] name = "der" version = "0.7.10" @@ -408,6 +594,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "email_address" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" +dependencies = [ + "serde", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -430,7 +625,7 @@ version = "0.1.0" dependencies = [ "anyhow", "chrono", - "cultcache-rs", + "cultcache-rs 0.2.0", "rmp-serde", "schemars", "serde", @@ -443,7 +638,8 @@ version = "0.1.0" dependencies = [ "anyhow", "chrono", - "cultnet-rs", + "clap", + "cultnet-rs 0.1.0", "huginn-daemon", "huginn-mind", "rmcp", @@ -451,8 +647,22 @@ dependencies = [ "schemars", "serde", "serde_json", + "serde_path_to_error", + "sha2", "tempfile", "tokio", + "unicode-general-category", +] + +[[package]] +name = "fancy-regex" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d301f5bf187b3c295fce6468d3875037a0bccc5f6b151c63cac2f85babf21912" +dependencies = [ + "bit-set", + "regex-automata", + "regex-syntax", ] [[package]] @@ -473,6 +683,33 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d" +[[package]] +name = "fluent-uri" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc74ac4d8359ae70623506d512209619e5cf8f347124910440dbc221714b328e" +dependencies = [ + "borrow-or-share", + "ref-cast", + "serde", +] + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "fraction" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e246562084dde8ebbcc943b261c406ce4f68e5032ec28029a251a47d6a295500" +dependencies = [ + "num", + "num-bigint", +] + [[package]] name = "fs2" version = "0.4.3" @@ -599,9 +836,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi 5.3.0", "wasip2", + "wasm-bindgen", ] [[package]] @@ -625,13 +864,23 @@ dependencies = [ "polyval", ] +[[package]] +name = "ghostlight-persona-projection" +version = "0.1.0" +source = "git+https://github.com/GameCult/Ghostlight.git?rev=c43eb54962ed4a09cc737ecac73a16b0d7ce62b4#c43eb54962ed4a09cc737ecac73a16b0d7ce62b4" +dependencies = [ + "serde", + "sha2", + "unicode-segmentation", +] + [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash", + "ahash 0.7.8", ] [[package]] @@ -639,6 +888,17 @@ name = "hashbrown" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hmac" @@ -672,9 +932,10 @@ dependencies = [ "anyhow", "base64", "chrono", - "cultnet-rs", + "cultnet-rs 0.1.0", "huginn-mind", "rmp-serde", + "rustix", "serde", "serde_json", "sha2", @@ -690,15 +951,18 @@ dependencies = [ "anyhow", "base64", "chrono", - "cultcache-rs", - "cultnet-rs", + "cultcache-rs 0.2.0", + "cultnet-rs 0.1.0", "eureka-pipeline", + "jsonschema", "rmp-serde", "schemars", "serde", "serde_json", + "serde_path_to_error", "sha2", "tempfile", + "unicode-normalization", ] [[package]] @@ -731,16 +995,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" -[[package]] -name = "indexmap" -version = "2.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" -dependencies = [ - "equivalent", - "hashbrown 0.17.1", -] - [[package]] name = "inout" version = "0.1.4" @@ -759,6 +1013,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itoa" version = "1.0.18" @@ -776,6 +1036,59 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jsonschema" +version = "0.58.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f622c574a29fb3034a0a90645afee194dfedc75fc124228801b6e77041075bd1" +dependencies = [ + "ahash 0.8.12", + "bytecount", + "data-encoding", + "email_address", + "fancy-regex", + "fraction", + "getrandom 0.3.4", + "itoa", + "jsonschema-regex", + "jsonschema-value", + "num-cmp", + "num-traits", + "percent-encoding", + "referencing", + "regex", + "serde", + "serde_json", + "strum", + "unicode-general-category", + "uuid-simd", +] + +[[package]] +name = "jsonschema-regex" +version = "0.58.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a968fffd387b2ab005bf00c1851f5d26e90df05b6f7ad609ed0d620033cca5e7" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "jsonschema-value" +version = "0.58.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39b7670345e5ce87371339d13705dbb9276b7e3e934bbe1efa2c6734a1f65a96" +dependencies = [ + "ahash 0.8.12", + "bytecount", + "fraction", + "getrandom 0.3.4", + "num-cmp", + "num-traits", + "serde_json", + "zmij", +] + [[package]] name = "libc" version = "0.2.189" @@ -824,18 +1137,129 @@ version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" +[[package]] +name = "micromap" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a86d3146ed3995b5913c414f6664344b9617457320782e64f0bb44afd49d74" + [[package]] name = "mind-body" version = "0.1.0" dependencies = [ "anyhow", + "base64", "chrono", - "cultcache-rs", + "cultcache-rs 0.2.0", + "cultnet-rs 0.1.0", "eureka-pipeline", + "eureka-state", + "ghostlight-persona-projection", + "huginn-mind", "libc", - "rust_decimal", + "model-connector", + "proc-macro2", + "quote", + "rmp-serde", + "rustix", "serde", + "serde_json", + "sha2", + "syn 2.0.119", "tempfile", + "unicode-general-category", + "ureq", + "uuid", +] + +[[package]] +name = "model-connector" +version = "0.1.0" +source = "git+https://eureka.gamecult.org/forge/GameCult/ModelConnector.git?rev=c1802d85bee3ae04b0c5737563c8343c1fef515d#c1802d85bee3ae04b0c5737563c8343c1fef515d" +dependencies = [ + "aes-gcm", + "base64", + "cultcache-rs 0.3.1", + "cultnet-rs 0.2.0", + "rand_core 0.6.4", + "rmp-serde", + "rmpv", + "schemars", + "serde", + "serde_bytes", + "serde_json", + "sha2", + "thiserror", + "zeroize", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-cmp" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63335b2e2c34fae2fb0aa2cecfd9f0832a1e24b3b32ecec612c3426d46dc8aaa" + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", ] [[package]] @@ -847,18 +1271,39 @@ dependencies = [ "autocfg", ] +[[package]] +name = "object" +version = "0.39.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e5a6c098c7a3b6547378093f5cc30bc54fd361ce711e05293a5cc589562739b" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "opaque-debug" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "outref" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + [[package]] name = "parking_lot" version = "0.11.2" @@ -867,7 +1312,17 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", - "parking_lot_core", + "parking_lot_core 0.8.6", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.12", ] [[package]] @@ -879,11 +1334,24 @@ dependencies = [ "cfg-if", "instant", "libc", - "redox_syscall", + "redox_syscall 0.2.16", "smallvec", "winapi", ] +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link", +] + [[package]] name = "pastey" version = "0.2.3" @@ -933,15 +1401,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "proc-macro-crate" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" -dependencies = [ - "toml_edit", -] - [[package]] name = "proc-macro2" version = "1.0.107" @@ -951,6 +1410,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "psm" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd034599e63b970727f70d79e02d62390a4a84f7c6b827c27c46d5ac3fa622" +dependencies = [ + "ar_archive_writer", + "cc", +] + [[package]] name = "quote" version = "1.0.47" @@ -972,37 +1441,16 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" -[[package]] -name = "rand" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - [[package]] name = "rand" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" dependencies = [ - "rand_chacha 0.9.0", + "rand_chacha", "rand_core 0.9.5", ] -[[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 0.6.4", -] - [[package]] name = "rand_chacha" version = "0.9.0" @@ -1049,6 +1497,15 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.13.2", +] + [[package]] name = "reed-solomon-erasure" version = "6.0.0" @@ -1057,7 +1514,7 @@ checksum = "7263373d500d4d4f505d43a2a662d475a894aa94503a1ee28e9188b5f3960d4f" dependencies = [ "libm", "lru", - "parking_lot", + "parking_lot 0.11.2", "smallvec", "spin", ] @@ -1082,6 +1539,52 @@ dependencies = [ "syn 3.0.5", ] +[[package]] +name = "referencing" +version = "0.58.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "558f3855529c0a7e78a48e409fa653456e55f1b167eab4109c16a255b7727e3f" +dependencies = [ + "ahash 0.8.12", + "fluent-uri", + "getrandom 0.3.4", + "hashbrown 0.17.1", + "itoa", + "micromap", + "parking_lot 0.12.5", + "percent-encoding", + "serde_json", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + [[package]] name = "rmcp" version = "2.2.0" @@ -1146,23 +1649,6 @@ dependencies = [ "serde_bytes", ] -[[package]] -name = "rust_decimal" -version = "1.43.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7653272e75dcac41dc199fbea6f5797633994fafd339943c06c9af16bf29cd3a" -dependencies = [ - "arrayvec", - "borsh", - "bytes", - "num-traits", - "rand 0.8.8", - "rand 0.9.5", - "serde", - "serde_json", - "wasm-bindgen", -] - [[package]] name = "rustc_version" version = "0.4.1" @@ -1293,6 +1779,34 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_stacker" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4936375d50c4be7eff22293a9344f8e46f323ed2b3c243e52f89138d9bb0f4a" +dependencies = [ + "serde", + "serde_core", + "stacker", +] + +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + [[package]] name = "sha2" version = "0.10.9" @@ -1377,12 +1891,46 @@ dependencies = [ "der", ] +[[package]] +name = "stacker" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707f49d46706bacf8a2b00d51dace3f9de527c13eec3778f570c411f89e69967" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "windows-sys 0.61.2", +] + [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "subtle" version = "2.6.1" @@ -1444,6 +1992,12 @@ dependencies = [ "syn 3.0.5", ] +[[package]] +name = "tinyvec" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" + [[package]] name = "tokio" version = "1.53.1" @@ -1480,36 +2034,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "toml_datetime" -version = "1.1.2+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b86d767906c6c42421dcba507eb9d203e779497710a47782a224bb871653053" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_edit" -version = "0.25.17+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3641d5bbb5349a79e1020a242d251efbc546ad8048d133958323ce9c40a9c9c" -dependencies = [ - "indexmap", - "toml_datetime", - "toml_parser", - "winnow", -] - -[[package]] -name = "toml_parser" -version = "1.1.5+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baa693a8032d7e1cada7d0041e96126df243179ff061456783ac7f12bda4744c" -dependencies = [ - "winnow", -] - [[package]] name = "tracing" version = "0.1.44" @@ -1547,12 +2071,33 @@ version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" +[[package]] +name = "unicode-general-category" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b993bddc193ae5bd0d623b49ec06ac3e9312875fdae725a975c51db1cc1677f" + [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unicode-normalization" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75cd3fb03d5288b3ba9a1ee103ac7e9cbb59f0b03d64f8ddfcc04dd42758a85d" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + [[package]] name = "universal-hash" version = "0.5.1" @@ -1594,6 +2139,12 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "1.26.1" @@ -1602,15 +2153,32 @@ checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce" dependencies = [ "getrandom 0.4.3", "js-sys", + "sha1_smol", "wasm-bindgen", ] +[[package]] +name = "uuid-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b082222b4f6619906941c17eb2297fff4c2fb96cb60164170522942a200bd8" +dependencies = [ + "outref", + "vsimd", +] + [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -1635,7 +2203,6 @@ dependencies = [ "cfg-if", "once_cell", "rustversion", - "serde", "wasm-bindgen-macro", "wasm-bindgen-shared", ] @@ -1844,15 +2411,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "winnow" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" -dependencies = [ - "memchr", -] - [[package]] name = "wit-bindgen" version = "0.57.1" diff --git a/README.md b/README.md index 41a6815..9fea14c 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ steer its next action, and the provenance of every admitted claim. Upstream: `https://github.com/GameCult/Huginn.git` +**Status.** Live: `huginn-mind` (admission with receipts, typed queries, derived status), `huginn-daemon` (CultNet RUDP with a Qdrant semantic index), and the `huginn` CLI from `crates/eureka-state` (whoami, view, query, admit, schema). The `eureka` instance runs on Yggdrasil. Entry points: `cargo check --workspace`, `crates/huginn-mind/src/admission.rs`, `crates/huginn-daemon/src/serve.rs`, `schemas/cultnet/`. Planned: what each crate does next is owned by the `eureka-body` cut specs in the mind and `docs/eureka-body-map.md`; the legacy `.voidbot/` Persona migration belongs to the portable-Persona work. + ## Authority - An instance owns its mind; Huginn owns the state. Huginn is the single @@ -24,7 +26,9 @@ Upstream: `https://github.com/GameCult/Huginn.git` restart compares the mind with the collection and writes what is missing. An unreachable Qdrant or Ollama shows in `whoami` as the index's status and is retried; admission never waits on it. The daemon requires `--qdrant-url`, - `--ollama-url` and `--embedding-model`. A `query` carrying `semantic` is + `--ollama-url` and `--embedding-model`; `--ollama-url` repeats for each GPU + host and `--reserve-ollama-url` for each CPU host, and the GPU hosts embed + together while the CPU hosts serve only when none can. A `query` carrying `semantic` is answered through the same selection as any other: the index supplies candidate ids and scores, and the mind keeps only the documents it holds and the selection admits (ask for `in_force` to exclude resolved ones), ordered by @@ -38,7 +42,7 @@ edits `.cc` state. Huginn reads and writes minds. ## Layout -A Cargo workspace of three crates: +A Cargo workspace of five crates (`crates/eureka-pipeline` holds the document shapes and keys, `crates/mind-body` the control store, launch primitive and `mind-heartbeat` timeline; the three below are the mind, the daemon and the client): - `crates/huginn-mind`: storage, identity, admission, and queries and derived status over memory documents. @@ -46,7 +50,9 @@ A Cargo workspace of three crates: the process, and no rule. The operation envelope both sides speak is `huginn-mind`'s. - `crates/eureka-state`: the client core, `HuginnClient::call`: one request to a - daemon over CultNet RUDP, a deferred answer resolved, and no state. + daemon over CultNet RUDP, a deferred answer resolved, and no state. Its + `huginn` binary is the agents' CLI: whoami, view, query and named recipes, + admit with read-back, schema. `huginn-mind` is live: it opens one instance's store (an owned redb CultCache at `/minds//mind.redb`, locked for the mind's lifetime), diff --git a/crates/eureka-pipeline/README.md b/crates/eureka-pipeline/README.md index 5d9c633..25fa726 100644 --- a/crates/eureka-pipeline/README.md +++ b/crates/eureka-pipeline/README.md @@ -18,13 +18,6 @@ and every encoding-relevant variant against `golden/envelopes.txt`. A diff there is a wire change; do not regenerate it to make the test pass. An additive field adds golden lines and never changes an existing one. -Opt-in, against a real store: `stored_documents_read_back` decodes, validates -and keys every stored pipeline document and asserts that it re-encodes to the -stored key, type and payload bytes. Take a copy of the store (the test copies -the file again before opening it, so the copy may be read-only), never open the -live one: - -```sh -sudo cp /var/lib/gamecult/huginn/minds/eureka/mind.redb "$SNAP/minds/eureka/mind.redb" -HUGINN_MIND_SNAPSHOT="$SNAP" cargo test -p eureka-pipeline -- --ignored --exact tests::stored_documents_read_back --nocapture -``` +The real-store gate is huginn-mind's `#[ignore]` `query::tests::stored_documents_read_back`, +which checks these stored bytes and the read path together (the leaf cannot see the read +path); the Huginn Upgrade runbook in gamecult-ops runs it. diff --git a/crates/eureka-pipeline/golden/envelopes.txt b/crates/eureka-pipeline/golden/envelopes.txt index a5e6fc8..d1b1489 100644 --- a/crates/eureka-pipeline/golden/envelopes.txt +++ b/crates/eureka-pipeline/golden/envelopes.txt @@ -10,6 +10,8 @@ # Epiphany ef956865 modulo that rename (verdict ...s2). Never regenerate to make # a failing test pass: a diff here is a wire change and needs its own ruling. # cut_spec.reads-and-symbol and finding.location-symbol were appended after 44c010c added CodeLocation.symbol and PipelineCutSpec.reads (ruling context-pack-read-anchors); no earlier line changed. +# target.repos was appended after 9298371 added PipelineTarget.repos (ruling campaign-repo-set-home); no earlier line changed. +# sample.desk_answer was inserted after the sample corpus when the desk_answer kind was added (ruling eureka-substrate:ruling:desk-answer-kind-tagged-outcome); no earlier line changed. sample.campaign eureka-state:campaign:self epiphany.pipeline.campaign.v2 EpiphanyPipelineCampaignDocument 9185a4736c7567ac657572656b612d7374617465a57469746c65b5457572656b6120706970656c696e65207374617465a57265706f7391b147616d6543756c742f4570697068616e79ae776f726b696e675f6272616e6368bb636f6465782f657572656b612d706970656c696e652d7374617465aa7461726765745f646f6384a470617468d9256e6f7465732f657572656b612d706970656c696e652d73746174652d7461726765742e6d64aa73746172745f6c696e6501a8656e645f6c696e6509a6636f6d6d6974a83566393832323864 sample.target eureka-state:target:r2 epiphany.pipeline.target.v2 EpiphanyPipelineTargetDocument 9186a863616d706169676eac657572656b612d7374617465a87265766973696f6e02aa696e76617269616e74739182a56c6162656cab6d696e642d61646d697473a973746174656d656e74b64f6e6c792061646d697373696f6e207772697465732eac6e6f745f696e5f73636f706591ac4576652062726f7773696e67b963616e6f6e6963616c5f696d706c656d656e746174696f6e7391a743756c744c6962a3646f6384a470617468d9256e6f7465732f657572656b612d706970656c696e652d73746174652d7461726765742e6d64aa73746172745f6c696e6501a8656e645f6c696e6509a6636f6d6d6974a83566393832323864 sample.question eureka-state:question:Q1 epiphany.pipeline.question.v2 EpiphanyPipelineQuestionDocument 9189a863616d706169676eac657572656b612d7374617465a56c6162656ca25131a57469746c65b357686f206f776e73207468652073746174653fa87175657374696f6eb357686f206f776e73207468652073746174653fa76f7074696f6e739282a56c6162656ca141a474657874ab616e20696e7374616e636582a56c6162656ca142a474657874a661207265706fab7265636f6d6d656e646564a141a7646570656e647391a6437574203361a97261697365645f696e82a46b696e64a743757453706563a26964bf657572656b612d73746174653a6375745f737065633a6375742d33612e7231a861736b65645f6f6eaa323032362d30392d3135 @@ -24,12 +26,20 @@ sample.instance yggdrasil:instance:self epiphany.pipeline.instance.v2 EpiphanyPi sample.stewardship yggdrasil:stewardship:gamecult_-epiphany.n1 epiphany.pipeline.stewardship.v2 EpiphanyPipelineStewardshipDocument 9185a8696e7374616e6365a979676764726173696ca47265706fb147616d6543756c742f4570697068616e79a873657175656e636501ab61737369676e65645f6f6eaa323032362d30392d3135a46e6f7465b954686520457572656b612063616d706169676e207265706f2e sample.hand_off yggdrasil:hand_off:thought-cage.gamecult_-epiphany.2026-09-15 epiphany.pipeline.hand_off.v2 EpiphanyPipelineHandOffDocument 9186ad66726f6d5f696e7374616e6365a979676764726173696cab746f5f696e7374616e6365ac74686f756768742d63616765a47265706fb147616d6543756c742f4570697068616e79a9646f63756d656e747392ac657572656b612d7374617465b6657572656b612d73746174653a72756c696e673a5238a6726561736f6ed92854686520776f726b73746174696f6e206d696e642074616b6573207468652063616d706169676e2ea968616e6465645f6f6eaa323032362d30392d3135 sample.run yggdrasil:run:mind-2026-10-09-1 epiphany.pipeline.run.v2 EpiphanyPipelineRunDocument 9189a8696e7374616e6365a979676764726173696ca56c6162656cb16d696e642d323032362d31302d30392d31ab6f706572617465645f6279a44d696e64a47475726ea753656c6652756ea4686f7374a979676764726173696caa737461727465645f6f6eaa323032362d30392d3135aa6275646765745f757364a3372e35a6636c61696d739182a46b696e64a743757453706563a26964d925657572656b612d73746174653a6375745f737065633a6375742d72756e2d6b696e642e7232a963616d706169676e7391ac657572656b612d7374617465 +sample.pulse yggdrasil:pulse:2026-10-31t041200-t0 epiphany.pipeline.pulse.v2 EpiphanyPipelinePulseDocument 9185a8696e7374616e6365a979676764726173696ca56c6162656cb4323032362d31302d3331743034313230302d7430b174656d706f5f766f74655f626561745f73cd0258b0757267656e63795f7065726d696c6c65ccfaa46e6f7465d92254686520737761726d2069732069646c653b20612073686f7274657220626561742e +sample.memory yggdrasil:memory:2026-10-31t041200-t0-m0 epiphany.pipeline.memory.v2 EpiphanyPipelineMemoryDocument 9189a8696e7374616e6365a979676764726173696ca56c6162656cb7323032362d31302d3331743034313230302d74302d6d30a4736f7274a9436f6d6d6974746564a474657874d936492074616b6520746865206261636b67726f756e6420736861646572733b204c6962627920686f6c64732074686520736f756e64732ea561626f757491ab406c696262793a63686174a66f726967696e81a4526f6f6d82a5726f6f6d73a5737761726daa68656172645f66726f6d91ab406c696262793a63686174a7736f757263657391a3246531b173616c69656e63655f7065726d696c6c65cd02bcac636f6e736f6c69646174657390 +sample.research eureka-state:research:p1.scope epiphany.pipeline.research.v2 EpiphanyPipelineResearchDocument 9189a863616d706169676eac657572656b612d7374617465a470617373a27031a56c6162656ca573636f7065a5636c61696dd93654686520656d626564206c696d6974206973203430393620746f6b656e7320776974682073696c656e74207472756e636174696f6e2ea664657461696cd934526561642066726f6d20746865206461656d6f6e277320696e646578206d6f64756c6520616e642070726f626564206c6976652ea8696e666572726564c2a7736f75726365739481a45265706f82a47265706fb147616d6543756c742f4570697068616e79a2617484a470617468d9256372617465732f687567696e6e2d6461656d6f6e2f7372632f696e6465782f6d6f642e7273aa73746172745f6c696e6532a8656e645f6c696e6538a6636f6d6d6974a8356639383232386481a357656283a375726cd92268747470733a2f2f7a657474656c6b617374656e2e64652f61746f6d69636974792fa4776f726bb65a657474656c6b617374656e2061746f6d6963697479ac7265747269657665645f6f6eaa323032362d30392d313581a84f6273657276656481a865766964656e636583a46b696e64a454657374a76c6f6361746f72aa636172676f2074657374a6726573756c74a26f6b81a8446f63756d656e7481a97265666572656e636582a46b696e64a652756c696e67a26964b6657572656b612d73746174653a72756c696e673a5238a67365727665739182a46b696e64a85175657374696f6ea26964b8657572656b612d73746174653a7175657374696f6e3a5131ab6f627365727665645f6f6eaa323032362d30392d3135 +sample.idea eureka-state:idea:p1.i1 epiphany.pipeline.idea.v2 EpiphanyPipelineIdeaDocument 9189a863616d706169676eac657572656b612d7374617465a470617373a27031a56c6162656ca26931a57469746c65b24f6e65206e6f74652070657220636c61696da469646561d9204b65657020612070617373206173206d616e7920736d616c6c206e6f7465732ea974726164656f666673b74d6f726520646f63756d656e747320746f206c6973742ea872657374735f6f6e9182a46b696e64a85265736561726368a26964be657572656b612d73746174653a72657365617263683a70312e73636f7065a6746f776172649182a46b696e64a85175657374696f6ea26964b8657572656b612d73746174653a7175657374696f6e3a5131ab70726f706f7365645f6f6eaa323032362d30392d3135 +sample.reply yggdrasil:reply:mind-2026-10-09-1-r1 epiphany.pipeline.reply.v2 EpiphanyPipelineReplyDocument 9185a8696e7374616e6365a979676764726173696ca56c6162656cb46d696e642d323032362d31302d30392d312d7231a372756e82a46b696e64a352756ea26964bf79676764726173696c3a72756e3a6d696e642d323032362d31302d30392d31a7616e73776572739182a46b696e64a8466f6c6c6f775570a26964d923657572656b612d73746174653a666f6c6c6f775f75703a61736b2d7468652d73656c66a474657874d93054686520736861646572207061737320697320646f6e653b2074686520736f756e647320617265204c6962627927732e +sample.thought yggdrasil:thought:t1793000000 epiphany.pipeline.thought.v2 EpiphanyPipelineThoughtDocument 91de0014a8696e7374616e6365a979676764726173696ca56c6162656cab7431373933303030303030a474657874d93d54686520736f756e647320617265204c6962627927733b2074686520736861646572732077657265206e6576657220746865206861726420706172742ea572656164739182a46b696e64a64d656d6f7279a26964d92879676764726173696c3a6d656d6f72793a323032362d31302d3331743034313230302d74302d6d30af706572736f6e615f72656365697074b06d696e642d636f6d6d69742d30613162a86964656e74697479be457572656b612c207368652f6865722c20706c61696e2d73706f6b656e2ea7636f6e74657874d92056616c7565733a20636f686572656e6365206f7665722076656c6f636974792ea87374696d756c7573bd4c696262792061736b65642061626f75742074686520736f756e64732eaf70726f6a6563746f725f6d6f64656cb070726f6a6563746f723a6c6174657374aa70726f6a656374696f6ed92949206b656570207475726e696e67206f7665722077686f20686f6c64732074686520736f756e64732eaa626173655f6d6f64656cab626173653a6c6174657374b474656d70657261747572655f7065726d696c6c65cd03e8ae6d696e5f705f7065726d696c6c6532aa6d61785f746f6b656e73cca0aa63616e6469646174657392d93d54686520736f756e647320617265204c6962627927733b2074686520736861646572732077657265206e6576657220746865206861726420706172742eaf41207365636f6e642064726966742eb06e6561726573745f7065726d696c6c6592cd0136cd0366a663686f73656e00ac70726f6a6563746f725f6d73cd1068ac636f6e74696e7565725f6d73cd238cae70726f6d70745f76657273696f6ebd67686f73746c696768743d633433656235343b20776f7264733d313830 +sample.desk_answer eureka-state:desk_answer:Q1.n1 epiphany.pipeline.desk_answer.v2 EpiphanyPipelineDeskAnswerDocument 918ea863616d706169676eac657572656b612d7374617465a4666f726bb8657572656b612d73746174653a7175657374696f6e3a5131a873657175656e636501ac6f7074696f6e735f68617368d94063636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363a663686f696365a161a46e6f7465b853686172652069742c207769746820746865206d61726b2eab616e7377657265645f6174b4323032362d31312d31305431353a30303a30305aab72656365697665645f6174b4323032362d31312d31305431353a30303a30335aaf636c61696d5f696e5f667574757265c2aa726571756573745f6964d92436663163306135322d336237652d346431612d396335382d306532643761346239663133ab626f64795f736861323536d94064646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464a8616e737765726572a6757365722d37a9617574686f72697479a64d656d626572a76f7574636f6d6581a85265766973696f6e81a26f66be657572656b612d73746174653a6465736b5f616e737765723a51312e6e31 resolution.superseded eureka-state:resolution:question.Q1.n1 epiphany.pipeline.resolution.v2 EpiphanyPipelineResolutionDocument 9185a77375626a65637482a46b696e64a85175657374696f6ea26964b8657572656b612d73746174653a7175657374696f6e3a5131a873657175656e636501a76f7574636f6d6581aa5375706572736564656481a262799282a46b696e64a652756c696e67a26964b6657572656b612d73746174653a72756c696e673a523882a46b696e64a85175657374696f6ea26964b8657572656b612d73746174653a7175657374696f6e3a5131a9726174696f6e616c65a852756c656420412eab7265736f6c7665645f6f6eaa323032362d30392d3135 resolution.fixed-by-none eureka-state:resolution:question.Q1.n1 epiphany.pipeline.resolution.v2 EpiphanyPipelineResolutionDocument 9185a77375626a65637482a46b696e64a85175657374696f6ea26964b8657572656b612d73746174653a7175657374696f6e3a5131a873657175656e636501a76f7574636f6d6581a5466978656482a6636f6d6d6974a83566393832323864a26279c0a9726174696f6e616c65a852756c656420412eab7265736f6c7665645f6f6eaa323032362d30392d3135 resolution.fixed-by-some eureka-state:resolution:question.Q1.n1 epiphany.pipeline.resolution.v2 EpiphanyPipelineResolutionDocument 9185a77375626a65637482a46b696e64a85175657374696f6ea26964b8657572656b612d73746174653a7175657374696f6e3a5131a873657175656e636501a76f7574636f6d6581a5466978656482a6636f6d6d6974a83566393832323864a2627982a46b696e64a94375745265706f7274a26964d921657572656b612d73746174653a6375745f7265706f72743a6375742d33612e6831a9726174696f6e616c65a852756c656420412eab7265736f6c7665645f6f6eaa323032362d30392d3135 resolution.deferred eureka-state:resolution:question.Q1.n1 epiphany.pipeline.resolution.v2 EpiphanyPipelineResolutionDocument 9185a77375626a65637482a46b696e64a85175657374696f6ea26964b8657572656b612d73746174653a7175657374696f6e3a5131a873657175656e636501a76f7574636f6d6581a8446566657272656481a2746f82a46b696e64a8466f6c6c6f775570a26964bb657572656b612d73746174653a666f6c6c6f775f75703a46552d34a9726174696f6e616c65a852756c656420412eab7265736f6c7665645f6f6eaa323032362d30392d3135 resolution.recorded eureka-state:resolution:question.Q1.n1 epiphany.pipeline.resolution.v2 EpiphanyPipelineResolutionDocument 9185a77375626a65637482a46b696e64a85175657374696f6ea26964b8657572656b612d73746174653a7175657374696f6e3a5131a873657175656e636501a76f7574636f6d6581a85265636f7264656481a6726561736f6ea64e6f7465642ea9726174696f6e616c65a852756c656420412eab7265736f6c7665645f6f6eaa323032362d30392d3135 resolution.withdrawn eureka-state:resolution:question.Q1.n1 epiphany.pipeline.resolution.v2 EpiphanyPipelineResolutionDocument 9185a77375626a65637482a46b696e64a85175657374696f6ea26964b8657572656b612d73746174653a7175657374696f6e3a5131a873657175656e636501a76f7574636f6d6581a957697468647261776e81a6726561736f6ea94d697374616b656e2ea9726174696f6e616c65a852756c656420412eab7265736f6c7665645f6f6eaa323032362d30392d3135 +resolution.slept eureka-state:resolution:question.Q1.n1 epiphany.pipeline.resolution.v2 EpiphanyPipelineResolutionDocument 9185a77375626a65637482a46b696e64a85175657374696f6ea26964b8657572656b612d73746174653a7175657374696f6e3a5131a873657175656e636501a76f7574636f6d6581a5536c65707482a46c696e65b04c657420746865207265737420676f2eab6461795f7468726f75676882a46b696e64a64d656d6f7279a26964b7657572656b612d73746174653a6d656d6f72793a6d2d31a9726174696f6e616c65a852756c656420412eab7265736f6c7665645f6f6eaa323032362d30392d3135 ruling.authority-standing eureka-state:ruling:R8 epiphany.pipeline.ruling.v2 EpiphanyPipelineRulingDocument 918aa863616d706169676eac657572656b612d7374617465a56c6162656ca25238a57469746c65b9416e20696e7374616e6365206f776e7320697473206d696e64a7616e7377657273b8657572656b612d73746174653a7175657374696f6e3a5131a663686f696365a141a672756c696e67ba416e20696e7374616e6365206f776e7320697473206d696e642eae6f70657261746f725f71756f7465bd616c6c207265636f6d6d656e646174696f6e732c20676f206168656164a872756c65645f6f6eaa323032362d30392d3135aa707265636564656e74739185a47265706fb147616d6543756c742f4165746865726961a6636f6d6d6974d92861616161616161616161616161616161616161616161616161616161616161616161616161616161a46b696e64a652756c696e67a26964b363756c7463616368653a72756c696e673a5231ae7061796c6f61645f736861323536d94062626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262a9617574686f72697479a85374616e64696e67 ruling.authority-defaulted eureka-state:ruling:R8 epiphany.pipeline.ruling.v2 EpiphanyPipelineRulingDocument 918aa863616d706169676eac657572656b612d7374617465a56c6162656ca25238a57469746c65b9416e20696e7374616e6365206f776e7320697473206d696e64a7616e7377657273b8657572656b612d73746174653a7175657374696f6e3a5131a663686f696365a141a672756c696e67ba416e20696e7374616e6365206f776e7320697473206d696e642eae6f70657261746f725f71756f7465bd616c6c207265636f6d6d656e646174696f6e732c20676f206168656164a872756c65645f6f6eaa323032362d30392d3135aa707265636564656e74739185a47265706fb147616d6543756c742f4165746865726961a6636f6d6d6974d92861616161616161616161616161616161616161616161616161616161616161616161616161616161a46b696e64a652756c696e67a26964b363756c7463616368653a72756c696e673a5231ae7061796c6f61645f736861323536d94062626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262a9617574686f72697479a944656661756c746564 ruling.options-none eureka-state:ruling:R8 epiphany.pipeline.ruling.v2 EpiphanyPipelineRulingDocument 918aa863616d706169676eac657572656b612d7374617465a56c6162656ca25238a57469746c65b9416e20696e7374616e6365206f776e7320697473206d696e64a7616e7377657273c0a663686f696365c0a672756c696e67ba416e20696e7374616e6365206f776e7320697473206d696e642eae6f70657261746f725f71756f7465c0a872756c65645f6f6eaa323032362d30392d3135aa707265636564656e747390a9617574686f72697479a84f70657261746f72 @@ -41,5 +51,9 @@ finding.plausible-blocker-preexisting-kinds eureka-state:finding:cut-3a.s2.F4 ep finding.severity-medium eureka-state:finding:cut-3a.s2.F4 epiphany.pipeline.finding.v2 EpiphanyPipelineFindingDocument 918da863616d706169676eac657572656b612d7374617465a776657264696374be657572656b612d73746174653a766572646963743a6375742d33612e7332a56c6162656ca24634a572616e676582a462617365a83566393832323864a468656164a83566393832323864aa636f6e666964656e6365a9436f6e6669726d6564a87365766572697479a64d656469756da5636c61696dd9214120646f74746564206c6162656c20636f6d706f7365732074776f206b6579732eaa696e76617269616e747391ab6d696e642d61646d697473a96c6f636174696f6e739183a470617468d9216372617465732f657572656b612d706970656c696e652f7372632f6c69622e7273a46c696e6501a8656e645f6c696e6509b06661696c7572655f7363656e6172696fbc54776f20646f63756d656e747320636c61696d206f6e65206b65792ea865766964656e63659183a46b696e64a454657374a76c6f6361746f72aa636172676f2074657374a6726573756c74a26f6baa707265636564656e747390a66f726967696eaa496e74726f6475636564 finding.severity-low eureka-state:finding:cut-3a.s2.F4 epiphany.pipeline.finding.v2 EpiphanyPipelineFindingDocument 918da863616d706169676eac657572656b612d7374617465a776657264696374be657572656b612d73746174653a766572646963743a6375742d33612e7332a56c6162656ca24634a572616e676582a462617365a83566393832323864a468656164a83566393832323864aa636f6e666964656e6365a9436f6e6669726d6564a87365766572697479a34c6f77a5636c61696dd9214120646f74746564206c6162656c20636f6d706f7365732074776f206b6579732eaa696e76617269616e747391ab6d696e642d61646d697473a96c6f636174696f6e739183a470617468d9216372617465732f657572656b612d706970656c696e652f7372632f6c69622e7273a46c696e6501a8656e645f6c696e6509b06661696c7572655f7363656e6172696fbc54776f20646f63756d656e747320636c61696d206f6e65206b65792ea865766964656e63659183a46b696e64a454657374a76c6f6361746f72aa636172676f2074657374a6726573756c74a26f6baa707265636564656e747390a66f726967696eaa496e74726f6475636564 ruling.authority-mind eureka-state:ruling:R8 epiphany.pipeline.ruling.v2 EpiphanyPipelineRulingDocument 918aa863616d706169676eac657572656b612d7374617465a56c6162656ca25238a57469746c65b9416e20696e7374616e6365206f776e7320697473206d696e64a7616e7377657273c0a663686f696365c0a672756c696e67ba416e20696e7374616e6365206f776e7320697473206d696e642eae6f70657261746f725f71756f7465c0a872756c65645f6f6eaa323032362d30392d3135aa707265636564656e74739185a47265706fb147616d6543756c742f4165746865726961a6636f6d6d6974d92861616161616161616161616161616161616161616161616161616161616161616161616161616161a46b696e64a652756c696e67a26964b363756c7463616368653a72756c696e673a5231ae7061796c6f61645f736861323536d94062626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262a9617574686f72697479a44d696e64 +ruling.authority-steward eureka-state:ruling:R8 epiphany.pipeline.ruling.v2 EpiphanyPipelineRulingDocument 918aa863616d706169676eac657572656b612d7374617465a56c6162656ca25238a57469746c65b9416e20696e7374616e6365206f776e7320697473206d696e64a7616e7377657273c0a663686f696365c0a672756c696e67ba416e20696e7374616e6365206f776e7320697473206d696e642eae6f70657261746f725f71756f7465c0a872756c65645f6f6eaa323032362d30392d3135aa707265636564656e74739185a47265706fb147616d6543756c742f4165746865726961a6636f6d6d6974d92861616161616161616161616161616161616161616161616161616161616161616161616161616161a46b696e64a652756c696e67a26964b363756c7463616368653a72756c696e673a5231ae7061796c6f61645f736861323536d94062626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262a9617574686f7269747981a75374657761726484a773746577617264ae406865696d64616c6c3a63686174a47265706fb047616d6543756c742f426966726f7374a5776f726473b4726566757365206c656164696e67207a65726f73a56576656e74a424657631 cut_spec.reads-and-symbol eureka-state:cut_spec:cut-3a.r1 epiphany.pipeline.cut_spec.v2 EpiphanyPipelineCutSpecDocument 91de0013a863616d706169676eac657572656b612d7374617465a3637574a23361a87265766973696f6e01a57469746c65b2506970656c696e6520646f63756d656e7473a47265706fb147616d6543756c742f4570697068616e79a66272616e6368bb636f6465782f657572656b612d706970656c696e652d7374617465a462617365a83566393832323864aa646570656e64735f6f6e91a132a5666972737491ae526561642074686520737065632ea764656c657465739183a470617468a66f6c642e7273a56c696e657303a46e6f7465a464656164ab6b656570735f6d6f76657391ac636f6d6d6974206f776e6572a46164647391b5706970656c696e655f646f63756d656e74732e7273ac66696c655f6368616e6765739182a86c6f636174696f6e84a470617468d9216372617465732f657572656b612d706970656c696e652f7372632f6c69622e7273a46c696e6501a8656e645f6c696e6509a673796d626f6cab76616c75655f7479706573a66368616e6765a3616464a572656164739282a86c6f636174696f6e84a470617468d9216372617465732f657572656b612d706970656c696e652f7372632f6c69622e7273a46c696e650aa8656e645f6c696e6514a673796d626f6caf506970656c696e6543757453706563a3776879b274686520646f63756d656e7420736861706582a86c6f636174696f6e83a470617468d9216372617465732f657572656b612d706970656c696e652f7372632f6c69622e7273a46c696e651ea8656e645f6c696e65c0a3776879a86f6e65206c696e65ad617574686f726974795f6d617087a56f776e6572a4636f7265a6696e7075747391af747970656420646f63756d656e7473a76f75747075747391a9656e76656c6f706573ad646572697665645f737461746591ac64657269766564206b657973b1666f7262696464656e5f7772697465727391a34d4350ac7368617265645f706174687391a961646d697373696f6ead64656c6574696f6e5f6c696e65a36e2f61ac766572696669636174696f6e84a66275696c647391ab636172676f20636865636ba574657374739182a46e616d65a46b657973a470696e73af6f6e652064657269766564206b6579a86e656761746976659182a77061747465726ea75665633c75383ea573636f7065a9646f63756d656e7473a86f70657261746f7291a46e6f6e65a8657374696d61746588ab6c696e65735f6164646564cd0384ad6c696e65735f72656d6f76656400b2646570656e64656e636965735f616464656491a8736368656d617273b4646570656e64656e636965735f72656d6f76656490ad666f726d6174735f616464656491b66570697068616e792e706970656c696e652e2a2e7631af666f726d6174735f72656d6f76656490ad746172676574735f616464656490af746172676574735f72656d6f76656490a772756c696e677391b6657572656b612d73746174653a72756c696e673a5238a97175657374696f6e7391b8657572656b612d73746174653a7175657374696f6e3a5131 finding.location-symbol eureka-state:finding:cut-3a.s2.F4 epiphany.pipeline.finding.v2 EpiphanyPipelineFindingDocument 918da863616d706169676eac657572656b612d7374617465a776657264696374be657572656b612d73746174653a766572646963743a6375742d33612e7332a56c6162656ca24634a572616e676582a462617365a83566393832323864a468656164a83566393832323864aa636f6e666964656e6365a9436f6e6669726d6564a87365766572697479a448696768a5636c61696dd9214120646f74746564206c6162656c20636f6d706f7365732074776f206b6579732eaa696e76617269616e747391ab6d696e642d61646d697473a96c6f636174696f6e739184a470617468d9216372617465732f657572656b612d706970656c696e652f7372632f6c69622e7273a46c696e6501a8656e645f6c696e6509a673796d626f6cab2631313533303930343938b06661696c7572655f7363656e6172696fbc54776f20646f63756d656e747320636c61696d206f6e65206b65792ea865766964656e63659183a46b696e64a454657374a76c6f6361746f72aa636172676f2074657374a6726573756c74a26f6baa707265636564656e747390a66f726967696eaa496e74726f6475636564 +target.repos eureka-state:target:r2 epiphany.pipeline.target.v2 EpiphanyPipelineTargetDocument 9187a863616d706169676eac657572656b612d7374617465a87265766973696f6e02aa696e76617269616e74739182a56c6162656cab6d696e642d61646d697473a973746174656d656e74b64f6e6c792061646d697373696f6e207772697465732eac6e6f745f696e5f73636f706591ac4576652062726f7773696e67b963616e6f6e6963616c5f696d706c656d656e746174696f6e7391a743756c744c6962a57265706f7392b147616d6543756c742f4570697068616e79af47616d6543756c742f487567696e6ea3646f6384a470617468d9256e6f7465732f657572656b612d706970656c696e652d73746174652d7461726765742e6d64aa73746172745f6c696e6501a8656e645f6c696e6509a6636f6d6d6974a83566393832323864 +stewardship.names yggdrasil:stewardship:gamecult_-epiphany.n1 epiphany.pipeline.stewardship.v2 EpiphanyPipelineStewardshipDocument 9186a8696e7374616e6365a979676764726173696ca47265706fb147616d6543756c742f4570697068616e79a873657175656e636501ab61737369676e65645f6f6eaa323032362d30392d3135a46e6f7465b954686520457572656b612063616d706169676e207265706f2ea56e616d657392a3424453b1426164204461792053696d756c61746f72 +resolution.moved eureka-state:resolution:question.Q1.n1 epiphany.pipeline.resolution.v2 EpiphanyPipelineResolutionDocument 9185a77375626a65637482a46b696e64a85175657374696f6ea26964b8657572656b612d73746174653a7175657374696f6e3a5131a873657175656e636501a76f7574636f6d6581a54d6f76656481a2746fa86865696d64616c6ca9726174696f6e616c65a852756c656420412eab7265736f6c7665645f6f6eaa323032362d30392d3135 diff --git a/crates/eureka-pipeline/src/lib.rs b/crates/eureka-pipeline/src/lib.rs index 07c6857..08b0bf2 100644 --- a/crates/eureka-pipeline/src/lib.rs +++ b/crates/eureka-pipeline/src/lib.rs @@ -96,6 +96,12 @@ impl Absent for Option { } } +impl Absent for bool { + fn absent(&self) -> bool { + !*self + } +} + impl Absent for Vec { fn absent(&self) -> bool { self.is_empty() @@ -112,7 +118,7 @@ macro_rules! unbounded_scalars { )*}; } -unbounded_scalars!(u32, u64, bool); +unbounded_scalars!(u8, u16, u32, u64, bool); fn bound(field: &str, limit: usize, actual: usize) -> Result<(), PipelineRefusal> { if actual > limit { @@ -266,6 +272,27 @@ fn symbol_text(field: &str, value: &str) -> Result<(), PipelineRefusal> { within(128)(field, value) } +/// A lowercase D-format UUID: 8-4-4-4-12 lowercase hex digits, any version. +fn uuid_text(field: &str, value: &str) -> Result<(), PipelineRefusal> { + let shaped = value.len() == 36 + && value.bytes().enumerate().all(|(at, byte)| match at { + 8 | 13 | 18 | 23 => byte == b'-', + _ => matches!(byte, b'0'..=b'9' | b'a'..=b'f'), + }); + if !shaped { + return Err(format_error(field, value)); + } + Ok(()) +} + +/// A person's words: 1 to 4000 UTF-8 bytes and not blank (`str::trim` leaves something). +fn words_text(field: &str, value: &str) -> Result<(), PipelineRefusal> { + if value.trim().is_empty() { + return Err(format_error(field, value)); + } + within(4000)(field, value) +} + bounded_text! { /// Text of at most 200 UTF-8 bytes. Short = 200, |field, value| within(200)(field, value); @@ -284,6 +311,11 @@ bounded_text! { Symbol = 128, symbol_text; /// Text of at most 4,000 UTF-8 bytes; longer narrative is cited by `DocRef`. Para = 4000, |field, value| within(4000)(field, value); + /// A person's words, verbatim: 1 to 4,000 UTF-8 bytes, not blank (CultLib PR #72, `answer.note`). + #[schemars(extend("minLength" = 1))] + Words = 4000, words_text; + /// A client's retry token: a lowercase D-format UUID. + RequestId = 36, uuid_text; /// A key label: `[A-Za-z0-9_-]{1,64}`. Label = 64, label_text; /// A campaign slug or key segment: label parts joined by `.`. @@ -491,16 +523,17 @@ macro_rules! unit_enums { )*}; } -/// RS-L closing fix 4: `campaign.repos` refuses two entries naming the same +/// RS-L closing fix 4: a repo list refuses two entries naming the same /// repository by identity, not by raw spelling, so `GameCult/Epiphany` and /// `gamecult/epiphany` in one list refuse exactly as one entry twice would. -/// This is a cross-element invariant on `PipelineCampaign` alone, not a -/// per-type format rule any other `value_types!` struct shares, so it is -/// wired in as that struct's one `=> extra` hook rather than folded into -/// `Bounded for OrgRepo` or the list bound in `bounded_field!`. -fn campaign_repos_are_distinct(campaign: &PipelineCampaign, at: &str) -> Result<(), PipelineRefusal> { +/// This is a cross-element invariant on the lists alone, not a per-type format +/// rule any other `value_types!` struct shares, so it is wired in as the +/// `=> extra` hook of the two structs that carry a repo list +/// (`PipelineCampaign.repos` and `PipelineTarget.repos`) rather than folded +/// into `Bounded for OrgRepo` or the list bound in `bounded_field!`. +fn repos_are_distinct(repos: &[OrgRepo], at: &str) -> Result<(), PipelineRefusal> { let mut seen = std::collections::HashSet::new(); - for repo in &campaign.repos { + for repo in repos { if !seen.insert(repo.identity()) { return Err(PipelineRefusal::DuplicateRepo { field: format!("{at}.repos"), @@ -511,6 +544,14 @@ fn campaign_repos_are_distinct(campaign: &PipelineCampaign, at: &str) -> Result< Ok(()) } +fn campaign_repos_are_distinct(campaign: &PipelineCampaign, at: &str) -> Result<(), PipelineRefusal> { + repos_are_distinct(&campaign.repos, at) +} + +fn target_repos_are_distinct(target: &PipelineTarget, at: &str) -> Result<(), PipelineRefusal> { + repos_are_distinct(&target.repos, at) +} + /// The most lines one read anchor may span. const READ_WINDOW_MAX_LINES: u32 = 100; @@ -563,15 +604,189 @@ fn run_is_well_formed(run: &PipelineRun, at: &str) -> Result<(), PipelineRefusal } } +/// A chat name holds a letter or a digit and has no space at either end, so it can match a whole word. +fn stewardship_is_well_formed(stewardship: &PipelineStewardship, at: &str) -> Result<(), PipelineRefusal> { + for (index, name) in stewardship.names.iter().enumerate() { + if name.0.trim() != name.0 || !name.0.chars().any(char::is_alphanumeric) { + return Err(format_error(&format!("{at}.names[{index}]"), &name.0)); + } + } + Ok(()) +} + +/// The longest and shortest beat a Mind may vote for, in seconds. +pub const TEMPO_VOTE_MIN_S: u32 = 60; +pub const TEMPO_VOTE_MAX_S: u32 = 86_400; +/// Urgency is in thousandths: 0 declares none, 1000 the most. +pub const URGENCY_MAX_PERMILLE: u16 = 1000; + +/// A pulse votes a tempo or declares an urgency, in bounds, or says nothing it +/// could be read for: an empty pulse is refused. +fn pulse_is_well_formed(pulse: &PipelinePulse, at: &str) -> Result<(), PipelineRefusal> { + if let Some(beat) = pulse.tempo_vote_beat_s + && !(TEMPO_VOTE_MIN_S..=TEMPO_VOTE_MAX_S).contains(&beat) + { + return Err(format_error(&format!("{at}.tempo_vote_beat_s"), &beat.to_string())); + } + if let Some(urgency) = pulse.urgency_permille + && urgency > URGENCY_MAX_PERMILLE + { + return Err(format_error(&format!("{at}.urgency_permille"), &urgency.to_string())); + } + if pulse.tempo_vote_withdrawn && pulse.tempo_vote_beat_s.is_some() { + return Err(format_error(&format!("{at}.tempo_vote_withdrawn"), "a pulse that votes a beat and withdraws the vote")); + } + if pulse.tempo_vote_beat_s.is_none() && !pulse.tempo_vote_withdrawn && pulse.urgency_permille.is_none() { + return Err(format_error(&format!("{at}.tempo_vote_beat_s"), "a pulse that votes and declares nothing")); + } + Ok(()) +} + +/// A reply says something and answers only follow-ups, from a run. +fn reply_is_well_formed(reply: &PipelineReply, at: &str) -> Result<(), PipelineRefusal> { + if reply.text.0.trim().is_empty() { + return Err(format_error(&format!("{at}.text"), "a reply with no words")); + } + if reply.run.kind != PipelineKind::Run { + return Err(format_error(&format!("{at}.run.kind"), reply.run.kind.name())); + } + for (index, answer) in reply.answers.iter().enumerate() { + if answer.kind != PipelineKind::FollowUp { + return Err(format_error(&format!("{at}.answers[{index}].kind"), answer.kind.name())); + } + } + Ok(()) +} + +/// The most candidates a continuation may offer, and the nearness that means +/// she had no earlier thought to be near. +pub const THOUGHT_CANDIDATES_MAX: usize = 4; +pub const THOUGHT_NEAREST_NONE_PERMILLE: u16 = 1000; + +/// A thought says something it was chosen to say: its text is not blank and is +/// the chosen candidate, every candidate has its nearness, and no nearness is +/// over 1000 thousandths. +fn thought_is_well_formed(thought: &PipelineThought, at: &str) -> Result<(), PipelineRefusal> { + if thought.text.0.trim().is_empty() { + return Err(format_error(&format!("{at}.text"), "a thought with no words")); + } + if thought.prompt_version.0.trim().is_empty() { + return Err(format_error(&format!("{at}.prompt_version"), "a thought with no prompt version")); + } + if thought.candidates.is_empty() || thought.candidates.len() > THOUGHT_CANDIDATES_MAX { + return Err(format_error(&format!("{at}.candidates"), &format!("{} candidates", thought.candidates.len()))); + } + if thought.nearest_permille.len() != thought.candidates.len() { + return Err(format_error(&format!("{at}.nearest_permille"), "a nearness list that is not one per candidate")); + } + if let Some(over) = thought.nearest_permille.iter().position(|near| *near > THOUGHT_NEAREST_NONE_PERMILLE) { + return Err(format_error(&format!("{at}.nearest_permille[{over}]"), &thought.nearest_permille[over].to_string())); + } + match thought.candidates.get(usize::from(thought.chosen)) { + None => Err(format_error(&format!("{at}.chosen"), &thought.chosen.to_string())), + Some(chosen) if chosen.0 != thought.text.0 => Err(format_error(&format!("{at}.text"), "a thought that is not its chosen candidate")), + Some(_) => Ok(()), + } +} + +/// The most thousandths a memory's salience may carry. +pub const SALIENCE_MAX_PERMILLE: u16 = 1000; + +/// A memory says something (its text is not empty), its salience is in +/// thousandths, and it consolidates only memories. +fn memory_is_well_formed(memory: &PipelineMemory, at: &str) -> Result<(), PipelineRefusal> { + if memory.text.0.trim().is_empty() { + return Err(format_error(&format!("{at}.text"), "an empty memory")); + } + if let MemoryOrigin::Direct { with, .. } = &memory.origin + && with.is_empty() + { + return Err(format_error(&format!("{at}.origin.with"), "a direct memory that names no one she heard it from")); + } + if memory.salience_permille > SALIENCE_MAX_PERMILLE { + return Err(format_error(&format!("{at}.salience_permille"), &memory.salience_permille.to_string())); + } + for (index, entry) in memory.consolidates.iter().enumerate() { + if entry.kind != PipelineKind::Memory { + return Err(format_error(&format!("{at}.consolidates[{index}].kind"), entry.kind.name())); + } + } + Ok(()) +} + +/// A research note states a claim and stands on at least one source that +/// names something: a Repo source a file and a range that does not end before +/// it starts, a Web source its address and work, an Observed one its +/// locator and result, a Document one a note other than this one. A claim +/// with no source is not research. An inferred note names what it inferred +/// from in those sources. Two notes citing each other cannot be seen from one +/// document; that refusal would need the image, so it is not made here. +fn research_is_well_formed(research: &PipelineResearch, at: &str) -> Result<(), PipelineRefusal> { + if research.claim.0.trim().is_empty() { + return Err(format_error(&format!("{at}.claim"), "an empty claim")); + } + if research.sources.is_empty() { + return Err(format_error(&format!("{at}.sources"), "a research note that names no source")); + } + let own_key = format!("{}:research:{}.{}", research.campaign.0, research.pass.0, research.label.0); + let blank = |text: &str| text.trim().is_empty(); + for (index, source) in research.sources.iter().enumerate() { + let (field, empty) = match source { + ResearchSource::Repo { at, .. } if blank(&at.path.0) => ("at.path", "a repo source pinning no file"), + ResearchSource::Repo { at, .. } if at.end_line < at.start_line => ("at.end_line", "a repo source whose range ends before it starts"), + ResearchSource::Web { url, .. } if blank(&url.0) => ("url", "a web source with no address"), + ResearchSource::Web { work, .. } if blank(&work.0) => ("work", "a web source naming no work"), + ResearchSource::Observed { evidence } if blank(&evidence.locator.0) => ("evidence.locator", "an observation naming nothing observed"), + ResearchSource::Observed { evidence } if blank(&evidence.result.0) => ("evidence.result", "an observation with no result"), + ResearchSource::Document { reference } if reference.kind == PipelineKind::Research && reference.id.0 == own_key => ("reference", "a note that is its own source"), + _ => continue, + }; + return Err(format_error(&format!("{at}.sources[{index}].{field}"), empty)); + } + Ok(()) +} + +/// An idea says something. It may serve no fork and rest on nothing. +fn idea_is_well_formed(idea: &PipelineIdea, at: &str) -> Result<(), PipelineRefusal> { + if idea.idea.0.trim().is_empty() { + return Err(format_error(&format!("{at}.idea"), "an empty idea")); + } + Ok(()) +} + unit_enums! { EvidenceKind { Command, Test, Mutation, Probe, SourceRead, Capture } ClaimOutcome { Holds, Falsified, Unproven } FindingConfidence { Confirmed, Plausible } FindingSeverity { Blocker, High, Medium, Low } - RulingAuthority { Operator, Standing, Defaulted, Mind } FindingOrigin { Introduced, PreExisting } RunOperator { Operator, Mind } - RunTurn { PersonaTurn, SelfRun } + RunTurn { PersonaTurn, SelfRun, Sleep } + MemorySort { Asked, Decided, Committed, Learned, Did, Idea, Rapport } + // The role the verified account holds, an attribution copied from Bifrost's state (CultLib PR #72, + // fork-card.md). It confers nothing: whether an answerer may decide is the decider list's, never this. + DeskAuthority { Operator, Steward, Member } + // Why a desk answer was refused and stored: binding spec 1.3 steps 4-7 and R1. + DeskRefusal { StaleFork, NotADecider, ForkClosed, ChoiceInvalid, BadRetraction, AlreadyRetracted } +} + +/// The slots of a Mind's runs: one live run of hers per instance and slot. A +/// Persona session and her Sleep run share the Persona slot (unit +/// `mind-persona@`); a Self run holds the other. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum RunSlot { + Persona, + SelfRun, +} + +impl RunTurn { + /// The slot this turn holds while live; admission and launch both read it here. + pub fn slot(self) -> RunSlot { + match self { + Self::PersonaTurn | Self::Sleep => RunSlot::Persona, + Self::SelfRun => RunSlot::SelfRun, + } + } } value_types! { @@ -625,10 +840,12 @@ value_types! { } pub struct PipelineCampaign { slug: Slug, title: Title, repos: Vec[8], working_branch: Short, target_doc: DocRef } => campaign_repos_are_distinct + /// `repos`, when non-empty, is the campaign's whole repo set from this + /// revision on; empty keeps the previous set (ruling campaign-repo-set-home). pub struct PipelineTarget { campaign: Slug, revision: u32, invariants: Vec[32], not_in_scope: Vec[32], - canonical_implementations: Vec[16], doc: DocRef, - } + canonical_implementations: Vec[16], repos: Vec[16] = absent, doc: DocRef, + } => target_repos_are_distinct pub struct PipelineQuestion { campaign: Slug, label: Label, title: Title, question: Para, options: Vec[8], recommended: Label, depends: Vec[8], raised_in: Option, asked_on: Date, @@ -651,15 +868,38 @@ value_types! { structural_delta: StructuralDelta, landed_names: Vec[128], undone: Vec[32], promises: Vec[64], } + /// One answer the fork desk's receiver passed to the Mind, refused ones included. + /// `answerer` and `authority` (an attribution, never a decision) are stamped by the + /// Mind from the verified caller and are absent from the request type; `answered_at` + /// is the client's claim kept as written, display only: order is `sequence`, the + /// receiver's order. `received_at` is the Mind's admission time (UTC) and + /// `claim_in_future` is set when `answered_at` is more than five minutes after it. + /// `request_id` is the client's retry token and `body_sha256` the digest of the + /// request it came with: the pair that makes a retry the same answer, never a new + /// one; the key is `(answerer, request_id)`. A record holds no card: a replayed + /// `stale_fork` is shown the current card. `sequence` is per question and is the + /// last part of the key (the answer id). `choice`, `note` and (for a retraction) the + /// target are kept as sent whether the request was counted or refused, so a refused + /// `choice_invalid` keeps a choice that is no option label; which answer counts now is + /// derived by the reader, never stored here. + pub struct PipelineDeskAnswer { + campaign: Slug, fork: Short, sequence: u32, options_hash: Sha256Hex, choice: Option, note: Option, + answered_at: Short, received_at: Short, claim_in_future: bool, request_id: RequestId, body_sha256: Sha256Hex, + answerer: Short, authority: DeskAuthority, outcome: DeskOutcome, + } pub struct PipelineVerdict { campaign: Slug, cut_report: Short, pass: u32, range: CommitRange, claims: Vec[64] } pub struct PipelineFinding { campaign: Slug, verdict: Short, label: Label, range: CommitRange, confidence: FindingConfidence, severity: FindingSeverity, claim: Line, invariants: Vec