From debe7ce9e7160c5c63832f88d67f9add6f97b1aa Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Tue, 29 Sep 2026 23:30:21 +0200 Subject: [PATCH] Move the operation envelope to huginn-mind: one spelling for client and daemon envelope.rs (encode/decode request and response, OperationFailure, FAILURE_SCHEMA) leaves huginn-daemon for huginn-mind, next to the wire types it wraps. The daemon imports it and no longer re-exports it. base64 moves with it; the daemon keeps base64 because serve.rs measures and decodes payloads. schemars leaves the daemon, whose only user was the envelope's OperationFailure derive. The two envelope tests run unchanged from their new home on huginn-mind fixtures. Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 2 +- crates/huginn-daemon/Cargo.toml | 1 - crates/huginn-daemon/src/lib.rs | 10 +++------- crates/huginn-daemon/src/semantic_tests.rs | 2 +- crates/huginn-daemon/src/serve.rs | 8 ++++---- crates/huginn-mind/Cargo.toml | 1 + .../src/envelope.rs | 16 ++++++++++------ crates/huginn-mind/src/lib.rs | 4 +++- 8 files changed, 23 insertions(+), 21 deletions(-) rename crates/{huginn-daemon => huginn-mind}/src/envelope.rs (93%) diff --git a/Cargo.lock b/Cargo.lock index 3ecdc74..1e30f52 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -477,7 +477,6 @@ dependencies = [ "cultnet-rs", "huginn-mind", "rmp-serde", - "schemars", "serde", "serde_json", "sha2", @@ -491,6 +490,7 @@ name = "huginn-mind" version = "0.1.0" dependencies = [ "anyhow", + "base64", "chrono", "cultcache-rs", "cultnet-rs", diff --git a/crates/huginn-daemon/Cargo.toml b/crates/huginn-daemon/Cargo.toml index 428588e..5890c67 100644 --- a/crates/huginn-daemon/Cargo.toml +++ b/crates/huginn-daemon/Cargo.toml @@ -24,7 +24,6 @@ chrono = "0.4.44" cultnet-rs = { git = "https://github.com/GameCult/CultLib.git", rev = "6251cbcd37c12ef3f0c1cc50063a03369225b7ac", package = "cultnet-rs" } huginn-mind = { path = "../huginn-mind" } rmp-serde = "1" -schemars = "1" serde = { version = "1", features = ["derive"] } # The vector store's and the embedder's HTTP APIs are the xenos boundary, and # JSON exists in `index/ollama.rs` and `index/qdrant.rs` only. `ureq` 3 with diff --git a/crates/huginn-daemon/src/lib.rs b/crates/huginn-daemon/src/lib.rs index a9fbb9f..90ae5e8 100644 --- a/crates/huginn-daemon/src/lib.rs +++ b/crates/huginn-daemon/src/lib.rs @@ -1,14 +1,12 @@ //! `huginn-daemon`: one process that serves one mind over CultNet. //! -//! The crate owns five things and no rule. `startup` owns "may this process +//! The crate owns four things and no rule. `startup` owns "may this process //! serve this mind", by delegating to `Mind::open` and refusing before //! anything listens. `index` owns when the mind's documents are embedded and //! written to the vector store, off the serving path. `Daemon::handle` owns "which `Mind` method answers this //! request": it dispatches, it asks `Mind::require_instance` for every read -//! that names an instance, and it compares nothing itself. `envelope` owns "is -//! this a request at all": a message it cannot decode is answered with a typed -//! failure and touches no mind. `serve` owns "which session gets which reply" -//! and whether an answer is delivered whole, deferred or refused; `bodies` +//! that names an instance, and it compares nothing itself. `serve` owns "which +//! session gets which reply" and whether an answer is delivered whole, deferred or refused; `bodies` //! owns how long and how much of a deferred answer is held. //! //! Every admission rule, the instance check, the receipt, the derived status @@ -17,7 +15,6 @@ pub mod bodies; pub mod daemon; -pub mod envelope; pub mod index; pub mod serve; @@ -25,5 +22,4 @@ pub mod serve; mod semantic_tests; pub use daemon::{Daemon, Handled, Hits, IndexSink, Search, SearchTicket, runtime_id}; -pub use envelope::{FAILURE_SCHEMA, OperationFailure}; pub use serve::{Options, ServeOptions, parse_options, run, startup}; diff --git a/crates/huginn-daemon/src/semantic_tests.rs b/crates/huginn-daemon/src/semantic_tests.rs index 513f0e9..f72c5b4 100644 --- a/crates/huginn-daemon/src/semantic_tests.rs +++ b/crates/huginn-daemon/src/semantic_tests.rs @@ -20,7 +20,7 @@ use tempfile::TempDir; use crate::daemon::Daemon; use crate::daemon::tests::{CAMPAIGN, INSTANCE, batch, campaign_seed, now, slug}; -use crate::envelope::{decode_response, encode_request}; +use huginn_mind::envelope::{decode_response, encode_request}; use crate::index::fakes::{FakeEmbedder, FakeIndex, Gate}; use crate::index::{Backoff, Described, Hit, WorkerSink, collection_name}; use crate::serve::{ServeOptions, bind, run, schema_registry}; diff --git a/crates/huginn-daemon/src/serve.rs b/crates/huginn-daemon/src/serve.rs index a5efc02..bfc54b8 100644 --- a/crates/huginn-daemon/src/serve.rs +++ b/crates/huginn-daemon/src/serve.rs @@ -79,7 +79,7 @@ use crate::bodies::{ DEFERRED_BUDGET_BYTES, DEFERRED_CHUNK_BYTES, DeferredBodies, MAX_DEFERRED_BODY_BYTES, }; use crate::daemon::{Daemon, Handled, IndexSink, Search, SearchTicket, runtime_id}; -use crate::envelope::{OperationFailure, decode_request, encode_failure, encode_response}; +use huginn_mind::envelope::{OperationFailure, decode_request, encode_failure, encode_response}; use crate::index::{Backoff, Embedder, VectorIndex, WorkerSink}; /// What the loop does when nothing is waiting. A deferred body untouched for @@ -345,7 +345,7 @@ fn encode_or_fail( Err(error) => encode_failure( message_id, operation, - &crate::envelope::OperationFailure { + &OperationFailure { code: "response-not-encodable".into(), message: format!("{error:#}"), }, @@ -548,7 +548,7 @@ mod tests { open_unindexed, seeded_wide, slug, }; use huginn_mind::epiphany_pipeline::{PipelineKind, PipelineRef}; - use crate::envelope::{FAILURE_SCHEMA, decode_response, encode_request}; + use huginn_mind::envelope::{FAILURE_SCHEMA, decode_response, encode_request}; use crate::index::Backoff; use base64::Engine; use base64::engine::general_purpose::STANDARD; @@ -632,7 +632,7 @@ mod tests { assert_eq!(rejected(&reply).1, code); } // The sixth code, `not-an-operation-request`, is the codec's and is - // pinned in `envelope::tests`: `answer` never produces it, because a + // pinned in `huginn_mind::envelope::tests`: `answer` never produces it, because a // message of another family is answered with `Error` instead. let reply = answer(&mut daemon, ®istry, &mut fresh_bodies(), CultNetMessage::Error { error: "hello".into(), code: None, details: None }, now()).reply(); let CultNetMessage::Error { error, .. } = &reply else { panic!("expected an error, got {reply:?}") }; diff --git a/crates/huginn-mind/Cargo.toml b/crates/huginn-mind/Cargo.toml index a2e60d4..ccf4e94 100644 --- a/crates/huginn-mind/Cargo.toml +++ b/crates/huginn-mind/Cargo.toml @@ -15,6 +15,7 @@ publish.workspace = true # daemon's (Cut 10, Cut 11). [dependencies] anyhow = "1" +base64 = "0.22" chrono = "0.4.44" cultcache-rs = { git = "https://github.com/GameCult/CultLib.git", rev = "6251cbcd37c12ef3f0c1cc50063a03369225b7ac", package = "cultcache-rs" } cultnet-rs = { git = "https://github.com/GameCult/CultLib.git", rev = "6251cbcd37c12ef3f0c1cc50063a03369225b7ac", package = "cultnet-rs" } diff --git a/crates/huginn-daemon/src/envelope.rs b/crates/huginn-mind/src/envelope.rs similarity index 93% rename from crates/huginn-daemon/src/envelope.rs rename to crates/huginn-mind/src/envelope.rs index fecbde2..8152a54 100644 --- a/crates/huginn-daemon/src/envelope.rs +++ b/crates/huginn-mind/src/envelope.rs @@ -14,7 +14,7 @@ use anyhow::Result; use base64::Engine; use base64::engine::general_purpose::STANDARD; use cultnet_rs::CultNetMessage; -use huginn_mind::wire::{HuginnMindRequest, HuginnMindResponse, MIND_REQUEST_SCHEMA, MIND_RESPONSE_SCHEMA, MIND_SERVICE_ID}; +use crate::wire::{HuginnMindRequest, HuginnMindResponse, MIND_REQUEST_SCHEMA, MIND_RESPONSE_SCHEMA, MIND_SERVICE_ID}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -151,9 +151,9 @@ pub fn decode_response(message: &CultNetMessage) -> Result<(String, Result MindRefusal { MindRefusal::ForeignInstance { declared: OTHER.into(), mind: INSTANCE.into() } @@ -183,7 +183,7 @@ mod tests { schema_epoch: "epiphany.pipeline.epoch.v2".into(), documents: 0, receipts: 0, - index: huginn_mind::wire::IndexStatus::Current, + index: crate::wire::IndexStatus::Current, }), "accepted", ), @@ -214,7 +214,11 @@ mod tests { /// `Error` instead. #[test] fn a_request_round_trips_and_a_foreign_message_is_not_a_request() { - let request = HuginnMindRequest::Admit(batch(INSTANCE, vec![identity(INSTANCE)])); + let request = HuginnMindRequest::Admit(PipelineAdmissionBatch { + instance: slug(INSTANCE), + provenance: provenance(Faculty::Hands), + documents: vec![instance(INSTANCE)], + }); let message = encode_request("m-3", &request, Some("eureka-state".into())).unwrap(); assert_eq!(decode_request(&message).unwrap(), ("m-3".into(), request)); diff --git a/crates/huginn-mind/src/lib.rs b/crates/huginn-mind/src/lib.rs index 0fd85e7..b791aac 100644 --- a/crates/huginn-mind/src/lib.rs +++ b/crates/huginn-mind/src/lib.rs @@ -14,7 +14,8 @@ //! selection (CultNet's, over rows this crate supplies) through the same //! `docs` the rules use; nothing is stored for it. //! -//! The wire vocabulary is here too, so the daemon and the client share one set +//! The wire vocabulary and its operation envelope (`envelope`) are here too, +//! so the daemon and the client share one set //! of types. //! //! Document shape, bounds, formats and keys are `epiphany-pipeline`'s; the @@ -26,6 +27,7 @@ pub mod admission; mod docs; +pub mod envelope; pub mod index; pub mod mind; mod rows;