Huginn/crates/eureka-state/tests/cli.rs
Meta Vens 5874717c88 huginn-sources: embed before deleting; the reserve gets a CPU's bounds
Source refresh now upserts a repo's new chunks first and deletes what is
left in scope under another commit afterwards, so a failed embed never
removes indexed content (embed-outage-empties-repo). The cursor is still
dropped before the first write.

The pool's reserve endpoints use EmbedBounds::RESERVE (30 s a text, 60 s
floor) instead of the GPU default (reserve-bounded-like-a-gpu).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 21:54:54 +01:00

1345 lines
56 KiB
Rust

//! The `huginn` binary as an agent drives it: a real process, its stdout, its
//! stderr and its exit code, against a real daemon in-process or a scripted
//! server where the daemon cannot be made to misbehave.
use std::io::Write;
use std::net::{SocketAddr, UdpSocket};
use std::path::Path;
use std::process::{Command, Stdio};
use std::sync::{Arc, Mutex};
use cultnet_rs::Selection;
use huginn_mind::envelope::{OperationFailure, decode_request, encode_failure, encode_response};
use huginn_mind::eureka_pipeline::{
CodeLocation, CommitRange, CutVerification, Date, Evidence, EvidenceKind, MutationRecord,
OrgRepo, PipelineCutReport, PipelineCutSpec, PipelineDocument, PipelineKind, PipelineQuestion,
PipelineRef, PipelineResolution, PipelineRuling, PipelineRun, Promise, QuestionOption,
ReportCommit, ResolutionOutcome, RulingAuthority, RunOperator, RunTurn, Short, StructuralDelta,
pipeline_key,
};
use huginn_mind::{
AdmissionFacts, HuginnMindRequest, HuginnMindResponse, IndexStatus, MindRefusal, MindStatus, PersonaStatus, PipelineAdmissionOutcome, PipelineDocumentView,
PipelinePageItems, PipelineProvenance, PipelineSelectionPage, PipelineStatus,
};
use huginn_daemon::{Daemon, Hits, IndexSink, SearchTicket};
use huginn_mind::MindStore;
use serde_json::{Value, json};
use std::time::Duration;
use tempfile::TempDir;
mod common;
use common::*;
struct Run {
code: i32,
out: String,
err: String,
}
fn run(env: &[(&str, &str)], args: &[&str], stdin: Option<&str>) -> Run {
let mut command = Command::new(env!("CARGO_BIN_EXE_huginn"));
command
.env_remove("EUREKA_INSTANCE")
.env_remove("HUGINN_ENDPOINT")
.envs(env.iter().copied())
.args(args)
.stdin(if stdin.is_some() { Stdio::piped() } else { Stdio::null() })
.stdout(Stdio::piped())
.stderr(Stdio::piped());
let mut child = command.spawn().expect("the binary starts");
if let Some(text) = stdin {
child.stdin.take().unwrap().write_all(text.as_bytes()).unwrap();
}
let output = child.wait_with_output().unwrap();
Run {
code: output.status.code().expect("exited, not killed"),
out: String::from_utf8(output.stdout).unwrap(),
err: String::from_utf8(output.stderr).unwrap(),
}
}
/// Configured for the test instance against a daemon at `addr`.
fn at(addr: SocketAddr, args: &[&str], stdin: Option<&str>) -> Run {
run(&[("EUREKA_INSTANCE", INSTANCE), ("HUGINN_ENDPOINT", &format!("rudp://{addr}"))], args, stdin)
}
/// A loopback UDP port nothing listens on.
fn closed_port() -> SocketAddr {
UdpSocket::bind("127.0.0.1:0").unwrap().local_addr().unwrap()
}
fn write(dir: &TempDir, name: &str, value: &Value) -> String {
let path = dir.path().join(name);
std::fs::write(&path, value.to_string()).unwrap();
path.to_str().unwrap().to_string()
}
fn campaign_json() -> Value {
serde_json::to_value(campaign()).unwrap()
}
fn live() -> Server {
let (root, daemon) = mind(vec![vec![stewardship()]]);
serve(root, daemon)
}
fn admit_args(file: &str) -> Vec<&str> {
vec!["admit", "--faculty", "Hands", "--agent", "claude", "--session", "session-7", file]
}
fn delta() -> StructuralDelta {
StructuralDelta {
lines_added: 0,
lines_removed: 0,
dependencies_added: vec![],
dependencies_removed: vec![],
formats_added: vec![],
formats_removed: vec![],
targets_added: vec![],
targets_removed: vec![],
}
}
fn spec(cut: &str) -> Value {
serde_json::to_value(PipelineDocument::CutSpec(PipelineCutSpec {
campaign: slug(CAMPAIGN),
cut: cut.into(),
revision: 1,
title: "A cut".into(),
repo: OrgRepo(REPO.into()),
branch: "codex/eureka-pipeline-state".into(),
base: sha(),
depends_on: vec![],
first: vec![],
deletes: vec![],
keeps_moves: vec![],
adds: vec![],
file_changes: vec![],
reads: vec![],
authority_map: None,
verification: CutVerification { builds: vec![], tests: vec![], negative: vec![], operator: vec![] },
estimate: delta(),
rulings: vec![],
questions: vec![],
}))
.unwrap()
}
fn report(cut: &str) -> Value {
serde_json::to_value(PipelineDocument::CutReport(PipelineCutReport {
campaign: slug(CAMPAIGN),
cut_spec: Short(format!("{CAMPAIGN}:cut_spec:cut-{cut}.r1")),
attempt: 1,
repo: OrgRepo(REPO.into()),
branch: "codex/eureka-pipeline-state".into(),
commits: vec![ReportCommit { sha: sha(), subject: "Land the cut".into(), builds: true }],
range: CommitRange { base: sha(), head: sha() },
verification: vec![Evidence { kind: EvidenceKind::Test, locator: "cargo test".into(), result: "ok".into() }],
mutations: vec![MutationRecord {
label: "M1".into(),
rule: "the rule".into(),
location: CodeLocation { path: Short("crates/x.rs".into()), line: 1, end_line: None, symbol: None },
before: "before".into(),
after: "after".into(),
commit: sha(),
failed_as_expected: true,
}],
deviations: vec![],
forks: vec![],
structural_delta: delta(),
landed_names: vec![],
undone: vec![],
promises: vec![Promise { label: "P1".into(), text: "One derived key per document.".into() }],
}))
.unwrap()
}
fn ruling(quote: Option<&str>) -> PipelineDocument {
PipelineDocument::Ruling(PipelineRuling {
campaign: slug(CAMPAIGN),
label: "R1".into(),
title: "An instance owns its mind".into(),
answers: None,
choice: None,
ruling: "An instance owns its mind.".into(),
operator_quote: quote.map(Into::into),
ruled_on: Date("2026-10-10".into()),
precedents: vec![],
authority: RulingAuthority::Standing,
})
}
fn empty_page() -> PipelineSelectionPage {
PipelineSelectionPage { matched: 0, as_of: 1, next: None, items: PipelinePageItems::Headers(vec![]), edges: None, scores: None }
}
fn answer(operation: &str, response: &HuginnMindResponse) -> cultnet_rs::CultNetMessage {
encode_response("eureka-state-call", operation, response, "scripted").unwrap()
}
/// A scripted server that records every request it is sent and answers a
/// query with an empty page.
fn recording() -> (Server, Arc<Mutex<Vec<HuginnMindRequest>>>) {
let seen = Arc::new(Mutex::new(Vec::new()));
let sink = Arc::clone(&seen);
let server = scripted(move |message| {
let (_, request) = decode_request(message).expect("a mind request");
sink.lock().unwrap().push(request);
answer("query", &HuginnMindResponse::Query(empty_page()))
});
(server, seen)
}
/// The selection a command sent, and its semantic part.
fn sent(args: &[&str]) -> (Selection, Option<(String, u32)>) {
let (server, seen) = recording();
let ran = at(server.addr, args, None);
assert_eq!(ran.code, 0, "{args:?}: {}", ran.err);
assert!(ran.out.contains("matched 0 as_of 1 next -"), "{}", ran.out);
let seen = seen.lock().unwrap();
let [HuginnMindRequest::Query { instance, selection, semantic }] = &seen[..] else {
panic!("one query expected: {seen:?}");
};
assert_eq!(instance.0, INSTANCE);
(selection.clone(), semantic.as_ref().map(|asked| (asked.text.0.clone(), asked.top_k)))
}
/// The page's literal as the selection it spells, with the CLI's default page.
fn page_literal(literal: Value) -> Selection {
let mut selection: Selection = serde_json::from_value(literal).unwrap();
selection.limit = Some(50);
selection
}
fn counting(replies: HuginnMindResponse, operation: &'static str) -> (Server, Arc<Mutex<usize>>) {
let count = Arc::new(Mutex::new(0));
let counter = Arc::clone(&count);
let server = scripted(move |_| {
*counter.lock().unwrap() += 1;
answer(operation, &replies)
});
(server, count)
}
#[test]
fn without_configuration_whoami_reports_it_and_every_other_command_refuses() {
let dir = tempfile::tempdir().unwrap();
let file = write(&dir, "campaign.json", &json!([campaign_json()]));
let whoami = run(&[], &["whoami"], None);
assert_eq!(whoami.code, 2);
assert!(whoami.out.contains("instance unset\nendpoint unset\nclient "), "{}", whoami.out);
assert!(whoami.out.contains("reachable no"), "{}", whoami.out);
assert!(whoami.err.starts_with("Misconfigured: EUREKA_INSTANCE is not set"), "{}", whoami.err);
for args in [
vec!["view", "eureka-state:campaign:self"],
vec!["query", "{}"],
vec!["rulings", "eureka-state"],
admit_args(&file),
] {
let ran = run(&[], &args, None);
assert_eq!(ran.code, 2, "{args:?}");
assert!(ran.err.starts_with("Misconfigured:"), "{args:?}: {}", ran.err);
assert_eq!(ran.out, "", "{args:?}");
}
}
#[test]
fn a_bad_environment_is_misconfigured_with_its_own_reason() {
let cases: [(&[(&str, &str)], &str); 4] = [
(&[("HUGINN_ENDPOINT", "rudp://127.0.0.1:1")], "EUREKA_INSTANCE is not set"),
(&[("EUREKA_INSTANCE", "Not A Slug"), ("HUGINN_ENDPOINT", "rudp://127.0.0.1:1")], "EUREKA_INSTANCE is not a valid"),
(&[("EUREKA_INSTANCE", "eureka")], "HUGINN_ENDPOINT is not set"),
(&[("EUREKA_INSTANCE", "eureka"), ("HUGINN_ENDPOINT", "127.0.0.1:1")], "HUGINN_ENDPOINT is not rudp://"),
];
for (env, expected) in cases {
let ran = run(env, &["whoami"], None);
assert_eq!(ran.code, 2, "{expected}");
assert!(ran.err.starts_with("Misconfigured: "), "{}", ran.err);
assert!(ran.err.contains(expected), "{expected}: {}", ran.err);
}
let named = run(&[("EUREKA_INSTANCE", "eureka"), ("HUGINN_ENDPOINT", "rudp://not-an-address")], &["whoami"], None);
assert_eq!(named.code, 2);
assert!(named.out.contains("endpoint rudp://not-an-address"), "{}", named.out);
assert!(named.out.contains("instance eureka"), "{}", named.out);
}
#[test]
fn whoami_reads_the_live_mind() {
let server = live();
let ran = at(server.addr, &["whoami"], None);
assert_eq!(ran.code, 0, "{}", ran.err);
let lines: Vec<&str> = ran.out.lines().collect();
assert_eq!(lines[0], format!("instance {INSTANCE}"));
assert_eq!(lines[1], format!("endpoint rudp://{}", server.addr));
assert_eq!(lines[2], format!("client {}", option_env!("HUGINN_BUILD_SHA").unwrap_or("unknown")));
assert_eq!(lines[3], "reachable yes");
assert_eq!(lines[4], "documents 2 receipts 2 index Current epoch epiphany.pipeline.epoch.v2");
assert_eq!(lines[5], "persona absent");
assert_eq!(lines.len(), 6);
assert_eq!(ran.err, "");
}
#[test]
fn an_index_that_is_behind_shows_its_count() {
let status = MindStatus {
instance: slug(INSTANCE),
schema_epoch: "epoch".into(),
documents: 7,
receipts: 9,
index: IndexStatus::Behind { pending: 3 },
persona: PersonaStatus::Absent,
};
let (server, _) = counting(HuginnMindResponse::Whoami(status), "whoami");
let ran = at(server.addr, &["whoami"], None);
assert_eq!(ran.code, 0);
assert!(ran.out.contains("documents 7 receipts 9 index Behind pending=3 epoch epoch"), "{}", ran.out);
}
#[test]
fn whoami_shows_the_persona_door_state_and_nothing_of_the_document() {
for (persona, line) in [
(PersonaStatus::Stored { updated_at: "2026-10-10T10:00:00Z".into() }, "persona stored updatedAt=2026-10-10T10:00:00Z"),
(PersonaStatus::Unreadable, "persona unreadable"),
] {
let status = MindStatus { instance: slug(INSTANCE), schema_epoch: "e".into(), documents: 1, receipts: 1, index: IndexStatus::Current, persona };
let (server, _) = counting(HuginnMindResponse::Whoami(status), "whoami");
let ran = at(server.addr, &["whoami"], None);
assert_eq!(ran.code, 0);
assert_eq!(ran.out.lines().last(), Some(line), "{}", ran.out);
}
}
#[test]
fn admit_round_trips_and_is_idempotent() {
let server = live();
let dir = tempfile::tempdir().unwrap();
let file = write(&dir, "campaign.json", &json!([campaign_json()]));
let first = at(server.addr, &admit_args(&file), None);
assert_eq!(first.code, 0, "{}", first.err);
let lines: Vec<&str> = first.out.lines().collect();
assert!(lines[0].starts_with("Committed mind-commit-"), "{}", first.out);
assert!(lines[0].contains(" at "), "{}", first.out);
assert_eq!(lines[1], format!("wrote {CAMPAIGN}:campaign:self"));
assert_eq!(lines[2], "read back 1 equal");
assert_eq!(lines.len(), 3, "{}", first.out);
let again = at(server.addr, &admit_args(&file), None);
assert_eq!(again.code, 0, "{}", again.err);
let lines: Vec<&str> = again.out.lines().collect();
assert!(lines[0].starts_with("AlreadyAdmitted mind-commit-"), "{}", again.out);
assert_eq!(lines[1], "read back 1 equal");
let id = format!("{CAMPAIGN}:campaign:self");
let viewed = at(server.addr, &["view", &id], None);
assert_eq!(viewed.code, 0, "{}", viewed.err);
let lines: Vec<&str> = viewed.out.lines().collect();
assert_eq!(lines[0], format!("{id} InForce"));
assert!(lines[1].starts_with("admitted "), "{}", viewed.out);
assert!(lines[1].contains(" ordinal 3 Hands/claude session session-7 tool huginn receipt mind-commit-"), "{}", lines[1]);
let shown: Value = serde_json::from_str(&lines[2..].join("\n")).unwrap();
assert_eq!(shown, campaign_json());
let raw = at(server.addr, &["view", &id, "--raw"], None);
assert_eq!(raw.code, 0);
assert_eq!(serde_json::from_str::<Value>(&raw.out).unwrap(), campaign_json());
assert!(raw.out.starts_with('{'), "{}", raw.out);
let fed_back = at(server.addr, &admit_args("-"), Some(&raw.out));
assert_eq!(fed_back.code, 0, "{}", fed_back.err);
assert!(fed_back.out.starts_with("AlreadyAdmitted "), "{}", fed_back.out);
let status = at(server.addr, &["whoami"], None);
assert!(status.out.contains("documents 3 receipts 3"), "{}", status.out);
}
#[test]
fn unknown_keys_are_refused_by_path_and_never_by_value() {
let server = live();
let dir = tempfile::tempdir().unwrap();
let mut nested = campaign_json();
nested["value"]["target_doc"]["pathh"] = json!("CANARY-NESTED");
let mut top = campaign_json();
top["surplus"] = json!("CANARY-TOP");
let file = write(&dir, "bad.json", &json!([nested, top]));
let ran = at(server.addr, &admit_args(&file), None);
assert_eq!(ran.code, 2);
assert_eq!(ran.out, "");
assert!(ran.err.starts_with("InvalidInput: "), "{}", ran.err);
assert!(ran.err.contains("documents[0].value.target_doc.pathh"), "{}", ran.err);
assert!(ran.err.contains("documents[1].surplus"), "{}", ran.err);
assert!(!ran.err.contains("CANARY"), "{}", ran.err);
let status = at(server.addr, &["whoami"], None);
assert!(status.out.contains("receipts 2 "), "nothing was sent: {}", status.out);
let selection = at(server.addr, &["query", r#"{"limt": 5}"#], None);
assert_eq!(selection.code, 2);
assert!(selection.err.contains("selection.limt"), "{}", selection.err);
}
#[test]
fn an_explicitly_empty_unknown_key_is_not_a_loss() {
let server = live();
let dir = tempfile::tempdir().unwrap();
let mut doc = campaign_json();
doc["value"]["ghost"] = Value::Null;
doc["value"]["ghosts"] = json!([]);
doc["value"]["ghost_map"] = json!({});
doc["value"]["ghost_text"] = json!("");
let file = write(&dir, "ghosts.json", &json!([doc]));
let ran = at(server.addr, &admit_args(&file), None);
assert_eq!(ran.code, 0, "{}", ran.err);
assert!(ran.out.contains("read back 1 equal"), "{}", ran.out);
}
#[test]
fn a_falsy_unknown_value_is_still_a_loss() {
let server = live();
let dir = tempfile::tempdir().unwrap();
let mut doc = campaign_json();
doc["value"]["zero"] = json!(0);
doc["value"]["no"] = json!(false);
let file = write(&dir, "falsy.json", &json!([doc]));
let ran = at(server.addr, &admit_args(&file), None);
assert_eq!(ran.code, 2);
assert!(ran.err.contains("documents[0].value.zero") && ran.err.contains("documents[0].value.no"), "{}", ran.err);
}
#[test]
fn a_document_that_does_not_decode_is_invalid_input() {
let dir = tempfile::tempdir().unwrap();
let cases = [
(json!([{ "kind": "nonsense", "value": {} }]), "documents does not decode"),
(json!("text"), "JSON array"),
(json!([[]]), "documents[0] must be a JSON object"),
(json!([]), "1 to 64"),
(json!((0..65).map(|_| campaign_json()).collect::<Vec<_>>()), "1 to 64"),
];
for (n, (value, expected)) in cases.into_iter().enumerate() {
let file = write(&dir, &format!("case{n}.json"), &value);
let ran = at(closed_port(), &admit_args(&file), None);
assert_eq!(ran.code, 2, "{expected}: {}", ran.err);
assert!(ran.err.starts_with("InvalidInput: "), "{}", ran.err);
assert!(ran.err.contains(expected), "{expected}: {}", ran.err);
}
let broken = at(closed_port(), &admit_args("-"), Some("{ not json"));
assert_eq!(broken.code, 2);
assert!(broken.err.contains("not JSON"), "{}", broken.err);
let missing = at(closed_port(), &admit_args("/no/such/file.json"), None);
assert_eq!(missing.code, 2);
assert!(missing.err.contains("cannot read /no/such/file.json"), "{}", missing.err);
}
#[test]
fn a_document_that_does_not_decode_never_echoes_its_values() {
let dir = tempfile::tempdir().unwrap();
let mut string_for_number = report("canary");
string_for_number["value"]["attempt"] = json!("CANARY-NUMBER");
let mut long_string_for_list = campaign_json();
long_string_for_list["value"]["repos"] = json!(format!("CANARY-LIST{}", "x".repeat(60_000)));
let unknown_kind = json!({ "kind": "CANARY-KIND", "value": {} });
let mut unknown_variant = serde_json::to_value(ruling(None)).unwrap();
unknown_variant["value"]["authority"] = json!("CANARY-VARIANT");
for (n, document) in [string_for_number, long_string_for_list, unknown_kind, unknown_variant].into_iter().enumerate() {
let file = write(&dir, &format!("canary{n}.json"), &json!([document]));
let ran = at(closed_port(), &admit_args(&file), None);
assert_eq!(ran.code, 2, "case {n}: {}", ran.err);
assert!(ran.err.starts_with("InvalidInput: documents does not decode: "), "case {n}: {}", ran.err);
assert!(ran.err.contains(" at line 1 column "), "case {n}: {}", ran.err);
assert!(!ran.err.contains("CANARY") && !ran.out.contains("CANARY"), "case {n}: {}", ran.err);
assert!(ran.err.len() < 300, "stderr is bounded whatever the input: {} bytes", ran.err.len());
}
let ran = at(closed_port(), &["query", r#"{"limit": "CANARY-LIMIT"}"#], None);
assert_eq!(ran.code, 2);
assert!(ran.err.starts_with("InvalidInput: selection does not decode: "), "{}", ran.err);
assert!(!ran.err.contains("CANARY") && ran.err.len() < 300, "{}", ran.err);
let ran = at(closed_port(), &admit_args("-"), Some(r#"[{"kind": "campaign", "value": CANARY-SYNTAX}]"#));
assert_eq!(ran.code, 2);
assert!(ran.err.starts_with("InvalidInput: not JSON: syntax at line 1 column "), "{}", ran.err);
assert!(!ran.err.contains("CANARY"), "{}", ran.err);
}
#[test]
fn a_refused_read_back_is_the_minds_no_with_the_receipt_printed() {
let dir = tempfile::tempdir().unwrap();
let file = write(&dir, "ruling.json", &json!([ruling(None)]));
let server = scripted(|message| {
let (_, request) = decode_request(message).expect("a mind request");
match request {
HuginnMindRequest::Admit(_) => answer(
"admit",
&HuginnMindResponse::Admit(PipelineAdmissionOutcome::Committed {
receipt_id: "mind-commit-x".into(),
committed_at: "2026-10-10T00:00:00Z".into(),
writes: vec![],
}),
),
_ => answer("query", &HuginnMindResponse::Refused(MindRefusal::MissingIdentity)),
}
});
let ran = at(server.addr, &admit_args(&file), None);
assert_eq!(ran.code, 1, "{}", ran.err);
assert!(ran.out.starts_with("Committed mind-commit-x at "), "{}", ran.out);
assert!(ran.out.contains("Refused MissingIdentity"), "{}", ran.out);
assert_eq!(ran.err, "");
}
fn view_of(document: &PipelineDocument) -> PipelineDocumentView {
PipelineDocumentView {
id: PipelineRef { kind: document.kind(), id: Short(pipeline_key(document).unwrap()) },
document: document.clone(),
admission: AdmissionFacts {
receipt_id: "mind-commit-x".into(),
admitted_at: "2026-10-10T00:00:00Z".into(),
provenance: PipelineProvenance {
faculty: huginn_mind::Faculty::Hands,
agent: Short("a".into()),
session: Short("s".into()),
tool: Short("huginn".into()),
},
ordinal: 1,
},
status: PipelineStatus::InForce,
}
}
/// Admits `Committed` and answers the keys query with `stored`.
fn committing(stored: Vec<PipelineDocumentView>) -> Server {
scripted(move |message| {
let (_, request) = decode_request(message).expect("a mind request");
match request {
HuginnMindRequest::Admit(_) => answer(
"admit",
&HuginnMindResponse::Admit(PipelineAdmissionOutcome::Committed {
receipt_id: "mind-commit-x".into(),
committed_at: "2026-10-10T00:00:00Z".into(),
writes: vec![],
}),
),
HuginnMindRequest::Query { selection, .. } => {
assert_eq!(selection.projection, "document");
assert_eq!(selection.limit, Some(u32::try_from(selection.keys.as_ref().unwrap().len()).unwrap()));
let page = PipelineSelectionPage {
matched: u32::try_from(stored.len()).unwrap(),
as_of: 2,
next: None,
items: PipelinePageItems::Documents(stored.clone()),
edges: None,
scores: None,
};
answer("query", &HuginnMindResponse::Query(page))
}
other => panic!("unexpected {other:?}"),
}
})
}
#[test]
fn a_field_the_mind_dropped_is_reported_with_the_receipt_printed() {
let dir = tempfile::tempdir().unwrap();
let sent = ruling(Some("CANARY-QUOTE"));
let file = write(&dir, "ruling.json", &json!([sent]));
let key = pipeline_key(&sent).unwrap();
let server = committing(vec![view_of(&ruling(None))]);
let ran = at(server.addr, &admit_args(&file), None);
assert_eq!(ran.code, 5);
assert!(ran.out.starts_with("Committed mind-commit-x at 2026-10-10T00:00:00Z"), "{}", ran.out);
assert!(!ran.out.contains("read back"), "{}", ran.out);
assert!(ran.err.starts_with(&format!("ReadBackDiffers: {key}: value.operator_quote;")), "{}", ran.err);
assert!(ran.err.contains("supersede"), "{}", ran.err);
let missing = committing(vec![]);
let ran = at(missing.addr, &admit_args(&file), None);
assert_eq!(ran.code, 5);
assert!(ran.err.contains(&format!("{key}: missing from the read-back")), "{}", ran.err);
let equal = committing(vec![view_of(&sent)]);
let ran = at(equal.addr, &admit_args(&file), None);
assert_eq!(ran.code, 0, "{}", ran.err);
assert!(ran.out.ends_with("read back 1 equal\n"), "{}", ran.out);
}
#[test]
fn a_difference_deep_in_a_list_names_its_path() {
let dir = tempfile::tempdir().unwrap();
let mut sent = serde_json::from_value::<PipelineDocument>(spec("A")).unwrap();
let rulings = |document: &mut PipelineDocument, ids: &[&str]| {
if let PipelineDocument::CutSpec(value) = document {
value.rulings = ids.iter().map(|id| Short(format!("{CAMPAIGN}:ruling:{id}"))).collect();
}
};
rulings(&mut sent, &["R1"]);
let file = write(&dir, "spec.json", &json!([sent]));
let mut stored = sent.clone();
rulings(&mut stored, &["R2"]);
if let PipelineDocument::CutSpec(value) = &mut stored {
value.title = "Another".into();
}
let server = committing(vec![view_of(&stored)]);
let ran = at(server.addr, &admit_args(&file), None);
assert_eq!(ran.code, 5);
assert!(ran.err.contains("value.rulings[0], value.title;"), "{}", ran.err);
let mut longer = sent.clone();
rulings(&mut longer, &["R1", "R2"]);
let server = committing(vec![view_of(&longer)]);
let ran = at(server.addr, &admit_args(&file), None);
assert_eq!(ran.code, 5);
assert!(ran.err.contains("value.rulings;"), "{}", ran.err);
assert!(!ran.err.contains("rulings["), "{}", ran.err);
}
#[test]
fn a_full_batch_passes_the_door() {
let dir = tempfile::tempdir().unwrap();
let file = write(&dir, "full.json", &json!((0..64).map(|_| campaign_json()).collect::<Vec<_>>()));
let ran = at(closed_port(), &admit_args(&file), None);
assert_eq!(ran.code, 3, "64 documents are a batch; the daemon is what is down: {}", ran.err);
}
#[test]
fn exit_codes_follow_the_answer() {
let dir = tempfile::tempdir().unwrap();
let file = write(&dir, "campaign.json", &json!([campaign_json()]));
// Nothing listening: Unavailable, naming the endpoint, whoami included.
let port = closed_port();
let down = at(port, &["view", "eureka-state:campaign:self"], None);
assert_eq!(down.code, 3);
assert!(down.err.starts_with("Unavailable: "), "{}", down.err);
assert!(down.err.contains(&format!("rudp://{port}")), "{}", down.err);
let whoami = at(port, &["whoami"], None);
assert_eq!(whoami.code, 3);
assert!(whoami.out.contains("reachable no"), "{}", whoami.out);
// The daemon rejecting the envelope.
let rejecting = scripted(|_| {
encode_failure(
"eureka-state-call",
"view",
&OperationFailure { code: "wrong-service".into(), message: "not huginn.mind".into() },
"scripted",
)
});
let rejected = at(rejecting.addr, &["view", "eureka-state:campaign:self"], None);
assert_eq!(rejected.code, 4);
assert!(rejected.err.starts_with("Rejected: "), "{}", rejected.err);
assert!(rejected.err.contains("wrong-service"), "{}", rejected.err);
// An answer of another operation's shape.
let status = MindStatus {
instance: slug(INSTANCE),
schema_epoch: "e".into(),
documents: 0,
receipts: 0,
index: IndexStatus::Current,
persona: PersonaStatus::Absent,
};
let (odd, _) = counting(HuginnMindResponse::Whoami(status), "view");
let internal = at(odd.addr, &["view", "eureka-state:campaign:self"], None);
assert_eq!(internal.code, 4);
assert!(internal.err.starts_with("Internal: "), "{}", internal.err);
// A batch no single send carries, refused before sending.
let mut huge = campaign_json();
huge["value"]["working_branch"] = json!("b".repeat(90_000));
let big = write(&dir, "huge.json", &json!([huge]));
let too_large = at(closed_port(), &admit_args(&big), None);
assert_eq!(too_large.code, 2);
assert!(too_large.err.starts_with("TooLarge: "), "{}", too_large.err);
assert!(too_large.err.contains(&eureka_state::MAX_REQUEST_BYTES.to_string()), "{}", too_large.err);
// The mind saying no: a refusal is an answer on stdout.
let live = live();
let refused = at(live.addr, &["admit", "--faculty", "Hands", "--agent", "a", "--session", "", &file], None);
assert_eq!(refused.code, 1);
assert!(refused.out.starts_with("Refused "), "{}", refused.out);
assert!(refused.out.contains("provenance.session"), "{}", refused.out);
assert_eq!(refused.err, "");
// Absent: the mind answered null.
let absent = at(live.addr, &["view", "eureka-state:cut_spec:cut-99.r1"], None);
assert_eq!(absent.code, 1);
assert_eq!(absent.out, "absent eureka-state:cut_spec:cut-99.r1\n");
// Another instance's mind: refused on the read side too.
let foreign = run(
&[("EUREKA_INSTANCE", "thought-cage"), ("HUGINN_ENDPOINT", &format!("rudp://{}", live.addr))],
&["query", "{}"],
None,
);
assert_eq!(foreign.code, 1);
assert!(foreign.out.starts_with("Refused "), "{}", foreign.out);
}
#[test]
fn a_call_that_is_wrong_exits_two_without_sending() {
let (server, count) = counting(HuginnMindResponse::View(None), "view");
for args in [
vec!["view", "eureka-state:nonsense:x"],
vec!["view", "no-kind-segment"],
vec!["history", "eureka-state:nonsense:x"],
vec!["query", "{ not json"],
vec!["query", "[]"],
vec!["admit", "--faculty", "Nope", "--agent", "a", "--session", "s", "x.json"],
vec!["schema", "no-such-kind"],
vec!["bogus"],
vec!["view"],
vec![],
] {
let ran = at(server.addr, &args, None);
assert_eq!(ran.code, 2, "{args:?}");
assert!(ran.err.starts_with("InvalidInput: "), "{args:?}: {}", ran.err);
assert_eq!(ran.out, "", "{args:?}");
}
assert_eq!(*count.lock().unwrap(), 0);
}
#[test]
fn the_recipes_send_exactly_the_pages_selections() {
let schema = |kind: &str| format!("epiphany.pipeline.{kind}.v2");
let root = |values: &[&str]| json!({ "index": "root", "op": "any_of", "values": values });
let in_force = json!({ "index": "in_force", "op": "any_of", "values": ["true"] });
let c = "eureka-substrate";
let cases = [
(vec!["campaigns"], json!({ "schemas": [schema("campaign")] })),
(
vec!["target", c],
json!({ "schemas": [schema("target")], "fields": [root(&[c]), in_force], "projection": "document" }),
),
(
vec!["rulings", c, "eureka-body"],
json!({ "schemas": [schema("ruling")], "fields": [root(&[c, "eureka-body"]), in_force] }),
),
(
vec!["open", c],
json!({ "schemas": [schema("question"), schema("follow_up")], "fields": [root(&[c]), in_force] }),
),
(
vec!["findings", c],
json!({
"schemas": [schema("finding")],
"fields": [root(&[c]), in_force, { "index": "faculty", "op": "any_of", "values": ["Soul"] }],
}),
),
(
vec!["findings", c, "--blocking"],
json!({
"schemas": [schema("finding")],
"fields": [
root(&[c]),
in_force,
{ "index": "faculty", "op": "any_of", "values": ["Soul"] },
{ "index": "severity", "op": "any_of", "values": ["Blocker", "High"] },
],
}),
),
(
vec!["unreported", c],
json!({
"schemas": [schema("cut_spec")],
"fields": [root(&[c]), in_force],
"cited": { "role": "cut_spec", "exists": false },
}),
),
(
vec!["unjudged", c],
json!({
"schemas": [schema("cut_report")],
"fields": [root(&[c])],
"cited": { "role": "cut_report", "exists": false },
}),
),
(
vec!["history", "eureka-substrate:ruling:R1"],
json!({
"schemas": [schema("resolution")],
"cites": { "target": { "schemaId": schema("ruling"), "recordKey": "eureka-substrate:ruling:R1" }, "role": "subject" },
"descending": true,
}),
),
(
vec!["cut", c, "huginn-cli"],
json!({
"schemas": [schema("cut_spec"), schema("cut_report"), schema("verdict"), schema("finding"), schema("resolution")],
"fields": [root(&[c]), { "index": "cut", "op": "any_of", "values": ["huginn-cli"] }],
}),
),
(
vec!["ledger", c],
json!({ "schemas": [schema("cut_spec"), schema("cut_report")], "fields": [root(&[c])], "projection": "document" }),
),
(
vec!["precedent", c, "the question in plain words"],
json!({ "schemas": [schema("ruling"), schema("resolution"), schema("finding")], "fields": [root(&[c])] }),
),
(
vec!["gaps"],
json!({ "schemas": [schema("follow_up")], "fields": [root(&["eureka-substrate"]), in_force] }),
),
];
for (args, literal) in cases {
let (selection, semantic) = sent(&args);
assert_eq!(selection, page_literal(literal), "{args:?}");
let expected = (args[0] == "precedent").then(|| ("the question in plain words".to_string(), 10));
assert_eq!(semantic, expected, "{args:?}");
}
}
/// `huginn blocked` answers with the hold the heartbeat's gate reads: a question
/// raised in a run holds every claim of that run, and an answer of `retire`
/// keeps holding the claims until they are resolved.
#[test]
fn blocked_names_the_questions_that_hold_a_spec_as_the_gate_reads_them() {
let server = live();
let dir = tempfile::tempdir().unwrap();
let admit = |name: &str, documents: Value| {
let file = write(&dir, name, &documents);
let ran = at(server.addr, &admit_args(&file), None);
assert_eq!(ran.code, 0, "{name}: {}", ran.err);
};
let value = |document: PipelineDocument| serde_json::to_value(document).unwrap();
let spec_id = |cut: &str| format!("{CAMPAIGN}:cut_spec:cut-{cut}.r1");
let held = |cut: &str| at(server.addr, &["blocked", &spec_id(cut)], None);
admit("campaign.json", json!([campaign_json()]));
admit("specs.json", json!([spec("A"), spec("B")]));
let claim = PipelineRef {
kind: PipelineKind::CutSpec,
id: Short(spec_id("A")),
};
admit(
"run.json",
json!([value(PipelineDocument::Run(PipelineRun {
instance: slug(INSTANCE),
label: "failed".into(),
operated_by: RunOperator::Mind,
turn: RunTurn::SelfRun,
host: "test".into(),
started_on: Date("2026-10-10".into()),
budget_usd: "0".into(),
claims: vec![claim.clone()],
campaigns: vec![slug(CAMPAIGN)],
}))]),
);
let run = PipelineRef {
kind: PipelineKind::Run,
id: Short(format!("{INSTANCE}:run:failed")),
};
let resolution = |subject: &PipelineRef, outcome: ResolutionOutcome| {
value(PipelineDocument::Resolution(PipelineResolution {
subject: subject.clone(),
sequence: 1,
outcome,
rationale: "So.".into(),
resolved_on: Date("2026-10-10".into()),
}))
};
admit(
"ended.json",
json!([resolution(
&run,
ResolutionOutcome::Recorded {
reason: "unit not active".into()
}
)]),
);
assert!(
held("A").out.starts_with("matched 0 "),
"no question yet: {}",
held("A").out
);
// A second run claims B, and a second question is raised in it: it holds B and never A.
let other_claim = PipelineRef { kind: PipelineKind::CutSpec, id: Short(spec_id("B")) };
admit(
"other.json",
json!([value(PipelineDocument::Run(PipelineRun {
instance: slug(INSTANCE),
label: "other".into(),
operated_by: RunOperator::Mind,
turn: RunTurn::SelfRun,
host: "test".into(),
started_on: Date("2026-10-10".into()),
budget_usd: "0".into(),
claims: vec![other_claim],
campaigns: vec![slug(CAMPAIGN)],
}))]),
);
let other = PipelineRef { kind: PipelineKind::Run, id: Short(format!("{INSTANCE}:run:other")) };
admit("other-ended.json", json!([resolution(&other, ResolutionOutcome::Recorded { reason: "unit not active".into() })]));
let ask = |label: &str, raised_in: PipelineRef| {
value(PipelineDocument::Question(PipelineQuestion {
campaign: slug(CAMPAIGN),
label: label.into(),
title: "A fork".into(),
question: "Which?".into(),
options: ["reopen", "retire"].iter().map(|name| QuestionOption { label: (*name).into(), text: "an option".into() }).collect(),
recommended: "reopen".into(),
depends: vec![],
raised_in: Some(raised_in),
asked_on: Date("2026-10-10".into()),
}))
};
admit("other-question.json", json!([ask("fork2", other)]));
let other_question = format!("{CAMPAIGN}:question:fork2");
let question = format!("{CAMPAIGN}:question:fork");
admit("question.json", json!([ask("fork", run)]));
let shown = held("A");
assert_eq!(shown.code, 0, "{}", shown.err);
assert!(
shown.out.starts_with(&format!("{question} InForce ")) && shown.out.contains("matched 1 "),
"a question raised in the run holds its claim: {}",
shown.out
);
assert!(!shown.out.contains(&other_question), "a question raised in another run does not hold A: {}", shown.out);
let shown = held("B");
assert!(
shown.out.starts_with(&format!("{other_question} InForce ")) && !shown.out.contains(&format!("{question} ")) && shown.out.contains("matched 1 "),
"B is held by its own run's question only: {}",
shown.out
);
let PipelineDocument::Ruling(mut answer) = ruling(None) else {
unreachable!()
};
(answer.answers, answer.choice) = (Some(Short(question.clone())), Some("retire".into()));
admit(
"answer.json",
json!([value(PipelineDocument::Ruling(answer))]),
);
let shown = held("A");
assert!(
shown.out.starts_with(&format!("{question} Resolved")) && shown.out.contains("matched 1 "),
"retired and not yet resolved, it still holds: {}",
shown.out
);
admit(
"withdrawn.json",
json!([resolution(
&claim,
ResolutionOutcome::Withdrawn {
reason: "retired".into()
}
)]),
);
assert!(
held("A").out.starts_with("matched 0 "),
"the claim resolved, nothing holds it: {}",
held("A").out
);
}
#[test]
fn paging_and_query_flags_shape_the_selection() {
let (selection, semantic) = sent(&["gaps", "--cursor", "abc", "--documents"]);
assert_eq!(selection.cursor.as_deref(), Some("abc"));
assert_eq!(selection.projection, "document");
assert_eq!(selection.limit, Some(50));
assert_eq!(semantic, None);
let (selection, _) = sent(&["gaps"]);
assert_eq!(selection.cursor, None);
assert_eq!(selection.projection, "header");
let (selection, semantic) = sent(&["query", "{}", "--near", "drift", "--top-k", "7"]);
assert_eq!(selection, page_literal(json!({})));
assert_eq!(semantic, Some(("drift".to_string(), 7)));
let (_, semantic) = sent(&["query", "{}", "--near", "drift"]);
assert_eq!(semantic, Some(("drift".to_string(), 10)));
let (selection, _) = sent(&["query", r#"{"limit": 5, "descending": true}"#]);
assert_eq!(selection.limit, Some(5));
assert!(selection.descending);
let (selection, _) = sent(&["query", r#"{"projection": "header"}"#, "--documents"]);
assert_eq!(selection.projection, "document");
let ran = at(closed_port(), &["query", "{}", "--top-k", "3"], None);
assert_eq!(ran.code, 2, "--top-k needs --near");
let dir = tempfile::tempdir().unwrap();
let file = write(&dir, "selection.json", &json!({ "schemas": ["epiphany.pipeline.campaign.v2"] }));
let (selection, _) = sent(&["query", &format!("@{file}")]);
assert_eq!(selection.schemas, Some(vec!["epiphany.pipeline.campaign.v2".to_string()]));
let (server, seen) = recording();
let ran = at(server.addr, &["query", "-"], Some(r#"{"keys": ["a:campaign:self"]}"#));
assert_eq!(ran.code, 0, "{}", ran.err);
let seen = seen.lock().unwrap();
let [HuginnMindRequest::Query { selection, .. }] = &seen[..] else { panic!("{seen:?}") };
assert_eq!(selection.keys, Some(vec!["a:campaign:self".to_string()]));
}
#[test]
fn recipes_list_what_the_mind_holds() {
let server = live();
let dir = tempfile::tempdir().unwrap();
let campaign = write(&dir, "campaign.json", &json!([campaign_json()]));
let ran = at(server.addr, &admit_args(&campaign), None);
assert_eq!(ran.code, 0, "{}", ran.err);
let specs = write(&dir, "specs.json", &json!([spec("A"), spec("B")]));
let ran = at(server.addr, &admit_args(&specs), None);
assert_eq!(ran.code, 0, "{}", ran.err);
let unreported = at(server.addr, &["unreported", CAMPAIGN], None);
assert_eq!(unreported.code, 0, "{}", unreported.err);
let mut lines: Vec<&str> = unreported.out.lines().collect();
assert_eq!(lines.pop().unwrap().split(' ').take(2).collect::<Vec<_>>(), ["matched", "2"]);
assert_eq!(lines.len(), 2);
let reports = write(&dir, "report.json", &json!([report("B")]));
let ran = at(server.addr, &admit_args(&reports), None);
assert_eq!(ran.code, 0, "{}", ran.err);
let unreported = at(server.addr, &["unreported", CAMPAIGN], None);
let lines: Vec<&str> = unreported.out.lines().collect();
assert_eq!(lines.len(), 2, "{}", unreported.out);
let tokens: Vec<&str> = lines[0].split(' ').collect();
let id = format!("{CAMPAIGN}:cut_spec:cut-A.r1");
assert_eq!(tokens[..5], [id.as_str(), "InForce", "Hands", "A", "cut"]);
for fact in ["cut=A", "repo=GameCult/Epiphany", "revision=1", "base=5f98228d", "branch=codex/eureka-pipeline-state"] {
assert!(tokens.contains(&fact), "{fact}: {}", lines[0]);
}
assert!(lines[1].starts_with("matched 1 as_of "), "{}", lines[1]);
assert!(lines[1].ends_with(" next -"), "{}", lines[1]);
assert!(!unreported.out.contains("cut-B.r1"), "{}", unreported.out);
let unjudged = at(server.addr, &["unjudged", CAMPAIGN], None);
assert!(unjudged.out.contains(&format!("{CAMPAIGN}:cut_report:cut-B.h1")), "{}", unjudged.out);
assert!(unjudged.out.contains("matched 1 "), "{}", unjudged.out);
let campaigns = at(server.addr, &["campaigns"], None);
assert!(campaigns.out.starts_with(&format!("{CAMPAIGN}:campaign:self InForce Hands Eureka pipeline state repos={REPO}\n")), "{}", campaigns.out);
let whole = at(server.addr, &["ledger", CAMPAIGN], None);
assert_eq!(whole.code, 0, "{}", whole.err);
assert_eq!(whole.out.matches("admitted ").count(), 3, "{}", whole.out);
assert!(!whole.out.starts_with('\n'), "{}", whole.out);
assert_eq!(whole.out.matches("\n\n").count(), 2, "documents are separated by one blank line: {}", whole.out);
let documents = at(server.addr, &["campaigns", "--documents"], None);
assert!(documents.out.contains("\nadmitted "), "{}", documents.out);
assert!(documents.out.contains("\"kind\": \"campaign\""), "{}", documents.out);
}
#[test]
fn a_long_header_is_cut() {
let server = live();
let dir = tempfile::tempdir().unwrap();
let mut doc = campaign_json();
doc["value"]["title"] = json!("t".repeat(200));
doc["value"]["working_branch"] = json!("b");
let file = write(&dir, "long.json", &json!([doc]));
assert_eq!(at(server.addr, &admit_args(&file), None).code, 0);
let ran = at(server.addr, &["campaigns"], None);
let header = ran.out.lines().next().unwrap();
assert_eq!(header.chars().count(), 240);
}
#[test]
fn a_resolved_document_shows_its_closing_record() {
let server = live();
let dir = tempfile::tempdir().unwrap();
let question = json!({
"kind": "question",
"value": {
"campaign": CAMPAIGN,
"label": "Q1",
"title": "Who owns the state?",
"question": "Who owns the state?",
"options": [{ "label": "A", "text": "an instance" }, { "label": "B", "text": "a repo" }],
"recommended": "A",
"depends": [],
"raised_in": null,
"asked_on": "2026-10-10",
},
});
let setup = write(&dir, "setup.json", &json!([campaign_json(), question]));
let ran = at(server.addr, &admit_args(&setup), None);
assert_eq!(ran.code, 0, "{}{}", ran.out, ran.err);
let resolution = json!({
"kind": "resolution",
"value": {
"subject": { "kind": "Question", "id": format!("{CAMPAIGN}:question:Q1") },
"sequence": 1,
"outcome": { "Withdrawn": { "reason": "moot" } },
"rationale": "Resolved.",
"resolved_on": "2026-10-10",
},
});
let closing = write(&dir, "closing.json", &json!([resolution]));
let ran = at(server.addr, &admit_args(&closing), None);
assert_eq!(ran.code, 0, "{}{}", ran.out, ran.err);
let id = format!("{CAMPAIGN}:question:Q1");
let closer = format!("{CAMPAIGN}:resolution:question.Q1.n1");
let viewed = at(server.addr, &["view", &id], None);
assert_eq!(viewed.out.lines().next().unwrap(), format!("{id} Resolved Withdrawn by {closer}"));
let history = at(server.addr, &["history", &id], None);
assert_eq!(history.code, 0, "{}", history.err);
assert!(history.out.starts_with(&format!("{closer} InForce Hands ")), "{}", history.out);
assert!(history.out.contains("sequence=1"), "{}", history.out);
let open = at(server.addr, &["open", CAMPAIGN], None);
assert!(open.out.contains("matched 0 "), "a resolved question is not open: {}", open.out);
let header = at(server.addr, &["query", r#"{"schemas": ["epiphany.pipeline.question.v2"]}"#], None);
assert!(header.out.starts_with(&format!("{id} Resolved:Withdrawn Hands Who owns the state? ")), "{}", header.out);
assert!(header.out.contains("options=A,B") && header.out.contains("recommended=A"), "{}", header.out);
}
#[test]
fn schema_prints_the_published_files() {
let manifest = Path::new(env!("CARGO_MANIFEST_DIR"));
for kind in ["cut_spec", "campaign", "hand_off", "run"] {
let ran = run(&[], &["schema", kind], None);
assert_eq!(ran.code, 0);
let published = std::fs::read_to_string(manifest.join(format!("../../schemas/cultnet/epiphany.pipeline.{kind}.v2.schema.json"))).unwrap();
assert_eq!(ran.out, published, "{kind}");
}
let selection = run(&[], &["schema", "selection"], None);
assert_eq!(selection.out, std::fs::read_to_string(manifest.join("schemas/cultnet.selection.schema.json")).unwrap());
let names = run(&[], &["schema"], None);
assert_eq!(names.code, 0);
let names: Vec<&str> = names.out.lines().collect();
assert_eq!(names.len(), 17, "{names:?}");
assert_eq!(names[0], "campaign");
assert_eq!(names[14], "pulse");
assert_eq!(names[15], "memory");
assert_eq!(names[16], "selection");
for kind in PipelineKind::ALL {
assert!(names.contains(&kind.name()), "{}", kind.name());
}
let unknown = run(&[], &["schema", "no-such-kind"], None);
assert_eq!(unknown.code, 2);
assert!(unknown.err.starts_with("InvalidInput: "), "{}", unknown.err);
}
#[test]
fn help_is_the_schema_surface() {
let rulings = run(&[], &["rulings", "--help"], None);
assert_eq!(rulings.code, 0);
let (_, printed) = rulings.out.split_once("selection:\n").expect("the selection is in the help");
let selection: Value = serde_json::from_str(printed.trim()).unwrap();
assert_eq!(
selection,
json!({
"schemas": ["epiphany.pipeline.ruling.v2"],
"fields": [
{ "index": "root", "op": "any_of", "values": ["<c>"] },
{ "index": "in_force", "op": "any_of", "values": ["true"] },
],
})
);
let history = run(&[], &["history", "--help"], None);
assert!(history.out.contains("epiphany.pipeline.<kind>.v2"), "{}", history.out);
let findings = run(&[], &["findings", "--help"], None);
assert!(findings.out.contains("\"severity\"") && findings.out.contains("Blocker"), "{}", findings.out);
let precedent = run(&[], &["precedent", "--help"], None);
assert!(precedent.out.contains("\"top_k\": 10"), "{}", precedent.out);
let admit = run(&[], &["admit", "--help"], None);
let admit_text = admit.out.split_whitespace().collect::<Vec<_>>().join(" ");
assert!(admit_text.contains("{\"kind\": <kind>, \"value\": {...}}"), "{admit_text}");
assert!(admit_text.contains("exit 5"), "{admit_text}");
let version = run(&[], &["--version"], None);
assert_eq!(version.code, 0);
let sha = option_env!("HUGINN_BUILD_SHA").unwrap_or("unknown");
assert_eq!(version.out, format!("huginn {} ({sha})\n", env!("CARGO_PKG_VERSION")));
}
#[test]
fn every_recipe_has_help() {
for recipe in [
"campaigns", "target", "rulings", "open", "findings", "unreported", "blocked", "unjudged", "history", "cut", "ledger",
"precedent", "gaps",
] {
let ran = run(&[], &[recipe, "--help"], None);
assert_eq!(ran.code, 0, "{recipe}");
assert!(ran.out.contains("epiphany.pipeline."), "{recipe}: {}", ran.out);
assert!(ran.out.contains("--cursor"), "{recipe}: {}", ran.out);
}
}
#[test]
fn a_conflicting_batch_is_the_minds_no() {
let conflict = scripted(|message| {
let (_, request) = decode_request(message).expect("a mind request");
assert!(matches!(request, HuginnMindRequest::Admit(_)));
answer(
"admit",
&HuginnMindResponse::Admit(PipelineAdmissionOutcome::Conflict {
identities: vec![PipelineRef { kind: PipelineKind::Campaign, id: Short(format!("{CAMPAIGN}:campaign:self")) }],
}),
)
});
let dir = tempfile::tempdir().unwrap();
let file = write(&dir, "campaign.json", &json!([campaign_json()]));
let ran = at(conflict.addr, &admit_args(&file), None);
assert_eq!(ran.code, 1);
assert_eq!(ran.out, format!("Conflict\nconflicts {CAMPAIGN}:campaign:self\n"));
}
/// The chunks a source index holds, served from memory: each text embeds to one
/// vector and each held chunk scores the same, so the search is the daemon's own
/// path (`sources::search`, its identity check and its citation fields) with
/// nothing about ranking in it.
struct Desk {
built_under: &'static str,
embeds_under: &'static str,
held: Vec<huginn_daemon::sources::SourcePayload>,
tickets: u64,
done: Vec<(SearchTicket, Result<huginn_mind::SourceHits, String>)>,
}
/// An embedder that sends every text to one vector, under a digest.
struct Flat(&'static str);
impl huginn_daemon::index::Embedder for Flat {
fn model_identity(&mut self, _within: Duration) -> anyhow::Result<huginn_daemon::index::ModelIdentity> {
Ok(huginn_daemon::index::ModelIdentity { name: "flat".into(), digest: self.0.into(), dimensions: 2 })
}
fn embed(&mut self, texts: &[String], _within: Duration) -> anyhow::Result<Vec<Vec<f32>>> {
Ok(texts.iter().map(|_| vec![1.0, 0.0]).collect())
}
}
impl huginn_daemon::sources::SourceIndex for Desk {
fn describe_sources(&mut self, _within: Duration) -> anyhow::Result<huginn_daemon::sources::DescribedSources> {
Ok(huginn_daemon::sources::DescribedSources::Labelled {
meta: huginn_daemon::sources::SourcesMeta {
managed_by: "huginn".into(),
corpus: huginn_daemon::sources::CORPUS.into(),
model: "flat".into(),
model_digest: self.built_under.into(),
dimensions: 2,
chunker_version: huginn_daemon::sources::chunk::CHUNKER_VERSION,
},
cursors: Default::default(),
})
}
fn recreate_sources(&mut self, _meta: &huginn_daemon::sources::SourcesMeta) -> anyhow::Result<()> {
anyhow::bail!("a daemon never writes the source index")
}
fn set_cursors(&mut self, _cursors: &std::collections::BTreeMap<String, String>) -> anyhow::Result<()> {
anyhow::bail!("a daemon never writes the source index")
}
fn delete_by(&mut self, _repo: &str, _paths: Option<&[String]>, _keeping: Option<&str>) -> anyhow::Result<()> {
anyhow::bail!("a daemon never writes the source index")
}
fn upsert_sources(&mut self, _points: &[huginn_daemon::sources::SourcePoint]) -> anyhow::Result<()> {
anyhow::bail!("a daemon never writes the source index")
}
fn search_sources(
&mut self,
_vector: &[f32],
repo: Option<&str>,
limit: u32,
_within: Duration,
) -> anyhow::Result<Vec<huginn_daemon::sources::SourceRow>> {
Ok(self
.held
.iter()
.filter(|payload| repo.is_none_or(|repo| payload.repo == repo))
.take(limit as usize)
.map(|payload| huginn_daemon::sources::SourceRow { score: 0.9, payload: payload.clone() })
.collect())
}
}
impl<S: MindStore> IndexSink<S> for Desk {
fn committed(&mut self, _mind: &huginn_mind::Mind<S>, _writes: &[PipelineRef]) -> anyhow::Result<()> {
Ok(())
}
fn status(&self) -> IndexStatus {
IndexStatus::Current
}
fn search(&mut self, _text: &str, _top_k: u32, _among: Vec<String>) -> Result<SearchTicket, String> {
Err("this index holds sources only".into())
}
fn search_sources(&mut self, query: &huginn_mind::SourceQuery) -> Result<SearchTicket, String> {
self.tickets += 1;
let ticket = SearchTicket(self.tickets);
let mut embedder = Flat(self.embeds_under);
let found = huginn_daemon::sources::search(&mut embedder, self, query, Duration::from_secs(5)).map_err(|error| format!("{error:#}"));
self.done.push((ticket, found));
Ok(ticket)
}
fn sources_searched(&mut self) -> Vec<(SearchTicket, Result<huginn_mind::SourceHits, String>)> {
std::mem::take(&mut self.done)
}
fn search_deadline(&self) -> Duration {
Duration::from_secs(5)
}
fn searched(&mut self) -> Vec<(SearchTicket, Result<Hits, String>)> {
Vec::new()
}
fn abandon(&mut self, _ticket: SearchTicket) {}
}
/// A daemon over a temporary mind whose source index was built under
/// `built_under` and whose embedder reports `embeds_under`.
fn source_daemon(built_under: &'static str, embeds_under: &'static str) -> Server {
let chunk = |repo: &str, path: &str, line_start: u32, line_end: u32, text: &str| huginn_daemon::sources::SourcePayload {
repo: repo.into(),
path: path.into(),
commit: "abcdef1234567890".into(),
blob: "b".into(),
line_start,
line_end,
chunk_index: 0,
ext: "rs".into(),
text: text.into(),
};
let desk = Desk {
built_under,
embeds_under,
held: vec![
chunk("Demo", "src/lib.rs", 3, 9, "fn cursor() {}"),
chunk("Demo", "README.md", 1, 2, "# Demo"),
chunk("Other", "x.rs", 1, 1, "fn other() {}"),
],
tickets: 0,
done: Vec::new(),
};
let root = tempfile::tempdir().unwrap();
let mut daemon = Daemon::new(huginn_mind::Mind::open(root.path(), &slug(INSTANCE)).unwrap(), desk);
let admitted = answered(daemon.handle(HuginnMindRequest::Admit(batch(INSTANCE, vec![identity(INSTANCE)])), chrono::Utc::now()));
assert!(matches!(admitted, HuginnMindResponse::Admit(PipelineAdmissionOutcome::Committed { .. })), "{admitted:?}");
serve(root, daemon)
}
/// `huginn sources` end to end: the binary asks a daemon over CultNet, the
/// daemon answers from its source index through the worker path, and the
/// citation is printed for the chunk the filter and limit chose.
#[test]
fn sources_prints_a_citation_for_a_search_answered_by_a_daemon() {
let server = source_daemon("d1", "d1");
let found = at(server.addr, &["sources", "where is the cursor kept", "--repo", "Demo", "--top-k", "1"], None);
assert_eq!(found.code, 0, "{}", found.err);
assert!(found.out.starts_with("Demo:src/lib.rs:3-9 @abcdef1 0.900\nfn cursor() {}"), "{}", found.out);
assert!(!found.out.contains("README") && !found.out.contains("Other"), "{}", found.out);
}
/// A daemon whose embedder is not the model the index was built under refuses
/// the search naming both, and the CLI exits 1 with that word.
#[test]
fn sources_refuses_naming_both_models_when_the_daemon_embeds_under_another() {
let server = source_daemon("d1", "d2");
let refused = at(server.addr, &["sources", "anything"], None);
assert_eq!(refused.code, 1, "{}", refused.err);
assert!(refused.out.contains("d1") && refused.out.contains("d2"), "{}", refused.out);
}