diff --git a/crates/eureka-pipeline/src/lib.rs b/crates/eureka-pipeline/src/lib.rs index 3186941..07c6857 100644 --- a/crates/eureka-pipeline/src/lib.rs +++ b/crates/eureka-pipeline/src/lib.rs @@ -805,7 +805,7 @@ macro_rules! pipeline_kinds { /// shape, not a document's. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(tag = "kind", content = "value", rename_all = "snake_case")] - #[allow(clippy::large_enum_variant, reason = "a document is a transient wrapper around one value; boxing a variant would change every construction site")] + #[allow(clippy::large_enum_variant, reason = "one value per request or per batch of at most 64; boxing a variant would buy no memory and change every construction site")] pub enum PipelineDocument { $($variant($value)),* } impl PipelineKind { @@ -1908,7 +1908,7 @@ mod tests { // refuses this one: the digits rule is satisfied either way. refused(&format!("{CAMPAIGN}:cut_spec:cut-3a.1")); refused(&format!("{CAMPAIGN}:CUT_SPEC:cut-3a.r1")); - refused(&format!("EUREKA-STATE:cut_spec:cut-3a.r1")); + refused("EUREKA-STATE:cut_spec:cut-3a.r1"); refused(&format!("{CAMPAIGN}:cut_spec:cut-3a.r1:")); refused(&format!("{CAMPAIGN}::cut-3a.r1")); refused("cut-3a.r1"); diff --git a/crates/huginn-daemon/src/daemon.rs b/crates/huginn-daemon/src/daemon.rs index ef6f8b9..34a54c7 100644 --- a/crates/huginn-daemon/src/daemon.rs +++ b/crates/huginn-daemon/src/daemon.rs @@ -150,7 +150,7 @@ impl> Daemon { HuginnMindResponse::Admit(outcome) } HuginnMindRequest::View { id, .. } => match self.mind.view(&id) { - Ok(view) => HuginnMindResponse::View(view), + Ok(view) => HuginnMindResponse::View(view.map(Box::new)), Err(refusal) => HuginnMindResponse::Refused(refusal), }, HuginnMindRequest::PersonaGet { .. } => match self.mind.persona() { @@ -502,9 +502,11 @@ pub(crate) mod tests { Ok(Daemon::new(Mind::open(state_root, instance)?, NoIndex)) } + type Seen = Vec<(Vec, Vec)>; + #[derive(Clone, Default)] struct RecordingIndex { - seen: Arc, Vec)>>>, + seen: Arc>, } impl IndexSink for RecordingIndex { @@ -615,7 +617,7 @@ pub(crate) mod tests { let PipelinePageItems::Documents(views) = &page.items else { panic!("the document projection is the view") }; assert_eq!(views[0].status, PipelineStatus::InForce); let view = daemon.handle(HuginnMindRequest::View { instance: slug(INSTANCE), id: id.clone() }, now()).answered(); - assert_eq!(view, HuginnMindResponse::View(Some(views[0].clone()))); + assert_eq!(view, HuginnMindResponse::View(Some(Box::new(views[0].clone())))); } /// Ruling 14 across the transport, on both sides: the mind refuses a read diff --git a/crates/huginn-daemon/src/index/mod.rs b/crates/huginn-daemon/src/index/mod.rs index fef9ebb..d52f5b6 100644 --- a/crates/huginn-daemon/src/index/mod.rs +++ b/crates/huginn-daemon/src/index/mod.rs @@ -596,7 +596,7 @@ impl Projector { let found = self.index.search(&self.collection, &vector, limit, budget.left()?)?; let mut hits = Vec::with_capacity(found.len()); for hit in found { - match PipelineKind::ALL.into_iter().find(|kind| kind.name() == hit.kind) { + match PipelineKind::ALL.iter().find(|kind| kind.name() == hit.kind) { Some(kind) => hits.push((PipelineRef { kind: *kind, id: Short(hit.doc_id) }, hit.score)), None => eprintln!("huginn: the index returned {} under the unknown kind {:?}; it is left out", hit.doc_id, hit.kind), } diff --git a/crates/huginn-daemon/src/index/tests.rs b/crates/huginn-daemon/src/index/tests.rs index d18f25b..2e53a16 100644 --- a/crates/huginn-daemon/src/index/tests.rs +++ b/crates/huginn-daemon/src/index/tests.rs @@ -19,10 +19,13 @@ use super::*; use crate::daemon::Daemon; use crate::daemon::tests::{INSTANCE, OTHER, batch, campaign_seed, now, question_and_ruling, slug}; +type AfterAbsorb = Box; +type Edit = (&'static str, Box); + thread_local! { /// Runs once, in the worker's own turn, between the inbox being absorbed /// and a search being taken, holding the guard `serve_search` holds. - static AFTER_ABSORB: std::cell::RefCell>> = const { std::cell::RefCell::new(None) }; + static AFTER_ABSORB: std::cell::RefCell> = const { std::cell::RefCell::new(None) }; } /// The hook `serve_search` calls at the point where a second lock once left a @@ -167,7 +170,7 @@ fn reconcile_indexes_exactly_the_indexable_documents_the_collection_lacks() { fn a_model_digest_change_rebuilds_the_collection() { let (_root, mind) = mind_with_documents(); let entries = mind.index_entries(None).unwrap(); - let changes: [(&str, Box); 3] = [ + let changes: [Edit; 3] = [ ("digest", Box::new(|identity| identity.digest = "d2".into())), ("name", Box::new(|identity| identity.name = "other-model".into())), ("dimensions", Box::new(|identity| identity.dimensions = 8)), @@ -1352,7 +1355,7 @@ fn a_model_changed_refusal_names_the_state_and_the_pending_count() { fn a_search_re_reads_the_collections_label() { let (_root, mind) = mind_with_documents(); let entries = mind.index_entries(None).unwrap(); - let cases: [(&str, Box); 3] = [ + let cases: [Edit; 3] = [ ( "another mind's label", Box::new(|stored| { diff --git a/crates/huginn-daemon/src/serve.rs b/crates/huginn-daemon/src/serve.rs index 561ad1b..60755d5 100644 --- a/crates/huginn-daemon/src/serve.rs +++ b/crates/huginn-daemon/src/serve.rs @@ -1024,7 +1024,7 @@ mod tests { let message_id = format!("{stem}{}", "0".repeat(zeros)); let at_probe = encoded_len(&reply_with_payload(&message_id, operation, runtime_id, "A".repeat(probe))); let payload_len = (probe as i64 + (target as i64 - at_probe as i64)) as usize; - if payload_len % 4 == 0 { + if payload_len.is_multiple_of(4) { let message = reply_with_payload(&message_id, operation, runtime_id, "A".repeat(payload_len)); assert_eq!(encoded_len(&message), target); return (message_id, message); diff --git a/crates/huginn-mind/src/admission.rs b/crates/huginn-mind/src/admission.rs index 961b8b7..a0e9d53 100644 --- a/crates/huginn-mind/src/admission.rs +++ b/crates/huginn-mind/src/admission.rs @@ -1751,7 +1751,7 @@ mod tests { let hand_off_ref = r(K::HandOff, &format!("{INSTANCE}:hand_off:{OTHER_INSTANCE}.gamecult_-huginn.2026-09-16")); // Accepted, one per resolvable kind. - committed(admit(&mut world(), vec![target(2, &[INVARIANT]), resolution(target_ref.clone(), superseded(&[target_r2.clone()]))])); + committed(admit(&mut world(), vec![target(2, &[INVARIANT]), resolution(target_ref.clone(), superseded(std::slice::from_ref(&target_r2)))])); committed(admit(&mut world(), vec![resolution(question_ref.clone(), withdrawn())])); committed(admit(&mut world(), vec![D::Ruling(ruling("R2")), resolution(ruling_ref.clone(), superseded(&[r(K::Ruling, &id("ruling", "R2"))]))])); committed(admit(&mut world(), vec![resolution(spec_ref.clone(), withdrawn())])); @@ -1794,7 +1794,7 @@ mod tests { incompatible(K::CutSpec, "Superseded") ); assert_eq!( - refusal(admit(&mut world(), vec![resolution(finding_ref.clone(), superseded(&[finding_ref.clone()]))])), + refusal(admit(&mut world(), vec![resolution(finding_ref.clone(), superseded(std::slice::from_ref(&finding_ref)))])), incompatible(K::Finding, "Superseded") ); assert_eq!( @@ -2294,7 +2294,7 @@ mod tests { committed(admit(&mut open(), vec![resolution(subject(), withdrawn())])); let other_run = r(K::Run, &run_key("z")); let refused = [ - (superseded(&[other_run.clone()]), "Superseded"), + (superseded(std::slice::from_ref(&other_run)), "Superseded"), (ResolutionOutcome::Answered { by: r(K::Ruling, &id("ruling", "R1")) }, "Answered"), (ResolutionOutcome::Fixed { commit: sha(), by: None }, "Fixed"), (ResolutionOutcome::Deferred { to: other_run }, "Deferred"), diff --git a/crates/huginn-mind/src/wire.rs b/crates/huginn-mind/src/wire.rs index 10bbede..e68805a 100644 --- a/crates/huginn-mind/src/wire.rs +++ b/crates/huginn-mind/src/wire.rs @@ -120,11 +120,10 @@ impl HuginnMindRequest { /// transport failure: the read side carries `Refused`, the write side carries /// the outcome's own `Refused`. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] -#[allow(clippy::large_enum_variant, reason = "one answer per request; a document view is the answer a reader asked for")] pub enum HuginnMindResponse { Whoami(MindStatus), Admit(PipelineAdmissionOutcome), - View(Option), + View(Option>), Query(PipelineSelectionPage), Refused(MindRefusal), Persona(Option), diff --git a/crates/mind-body/tests/control.rs b/crates/mind-body/tests/control.rs index 8ab013e..a11068f 100644 --- a/crates/mind-body/tests/control.rs +++ b/crates/mind-body/tests/control.rs @@ -485,13 +485,15 @@ mod unsafe_installs { error.downcast::().expect("the refusal is the typed UnsafeStore error") } + type Case = (&'static str, u32, Option, Option, &'static str); + #[test] fn a_group_or_world_writable_directory_lock_or_store_is_refused_and_nothing_is_written() { if !running_as_root() { eprintln!("SKIPPED: needs root to own the fixtures"); return; } - let cases: [(&str, u32, Option, Option, &str); 5] = [ + let cases: [Case; 5] = [ ("world-writable directory", 0o777, None, None, ""), ("group-writable directory", 0o775, None, None, ""), ("world-writable lock", 0o755, Some(0o666), None, "control.cc.lock"), diff --git a/docs/eureka-body-map.md b/docs/eureka-body-map.md index d084e6b..b943400 100644 --- a/docs/eureka-body-map.md +++ b/docs/eureka-body-map.md @@ -29,6 +29,13 @@ F21. The run, Repetition breaker, Brake and Units rows changed, and the rational Specs: `cut-run-live-admission.r1`, `cut-mind-launch.r3`, `cut-mind-wake.r3`, `cut-brake-stop.r2`. +Revised again on 2026-10-10 by Imagination (`imagination-mind-units`, session +`self-2026-10-10-eureka`) at Huginn `0c6f6cb` and gamecult-ops `6b4a874`, for cut +`mind-units-install`. New fact F22. New section "The mind units: identity, lifecycle and +authority", written before the cut was mapped. Specs: `cut-mind-unit-gate.r1` (Huginn) and +`cut-mind-units-install.r1` (gamecult-ops). Questions: `mind-unit-names` and +`mind-unit-start-grant`. + ## Pinned heads (fetched 2026-10-08 ~12:10 UTC) | Repo | Ref | Commit | Note | @@ -217,6 +224,39 @@ Each line: what was run, where, result. - `Slug::validate_slug` and `Label::validate_label` are public (crates/eureka-pipeline/src/lib.rs:287, 386). `RunTurn` and `RunOperator` spell their variants through `Debug`, which is also their `NAMES` (lib.rs:436-452). +- **F22 What the mind units would land on, 2026-10-10.** Read-only `ssh yggdrasil` as + `gamecultadmin` and an Eyes pass over Idunn `6667f0f` (Imagination `imagination-mind-units`). + - Still no unit, user or store of hers: `systemctl list-unit-files 'mind-*'` lists 0; + `getent passwd` has no `mind-*` user; `/etc/gamecult/minds` is absent; no `mind-control` + on PATH. `/opt/gamecult/huginn/current` is `releases/107552dc...` and holds only + `huginn-daemon` (0755 root). The Release step builds only `-p huginn-daemon` + (gamecult-ops `runbooks/huginn-yggdrasil.md:57`). + - systemd 257 (257.13-1~deb13u1): `ExecCondition=` (since 243) exists. Per + systemd.service(5), an ExecCondition exit of 1-254 skips the unit (inactive, not failed) + and 255 or a signal fails it; the `+` prefix runs that command with full privileges. + - **polkit is not installed:** `polkitd` "Installed: (none)", candidate 126-2; + `/etc/polkit-1/rules.d` absent (`/usr/share/polkit-1/actions` exists, from systemd). + Without polkitd, systemd refuses an unprivileged caller's `StartUnit`, so no user of hers + can start a unit today. `/etc/sudoers.d` holds only `90-gamecultadmin` and README. + - systemd cannot derive a per-instance user from a `: