use std::collections::BTreeSet;
use std::error::Error;
use std::path::{Path, PathBuf};
use std::time::Duration;
use serde_json::{Value, json};
use super::args::{self, AgentArgs};
use super::surface;
use crate::settings::Environment;
use crate::worker_surface::{self, ServingSource};
type TestResult = Result<(), Box<dyn Error>>;
const DOCUMENT: &str = r"//! reviewer: the document an agent worker derives its advertised surface from.
workflow review_flow
input brief: String
outcome reviewed: type Review, route success
/// What the agent reported back.
type Review {
verdict: String,
detail: String,
}
/// The queue an agent worker serves.
worker reviewers
action review(brief: String) -> Review
step do_review
brief |> review |> route reviewed
";
fn write_document(label: &str, source: &str) -> Result<PathBuf, Box<dyn Error>> {
let directory =
std::env::temp_dir().join(format!("aion-agent-worker-{}-{label}", std::process::id()));
std::fs::create_dir_all(&directory)?;
let path = directory.join("document.awl");
std::fs::write(&path, source)?;
Ok(path)
}
fn args_for(document: &Path) -> AgentArgs {
AgentArgs {
awl: Some(document.to_path_buf()),
liminal_addresses: Vec::new(),
task_queue: None,
identity: None,
node: None,
check: false,
}
}
#[test]
fn the_advertised_surface_is_the_documents_and_is_not_empty() -> TestResult {
let document = write_document("advertises", DOCUMENT)?;
let surface = surface::derive(&document, None)?;
assert_eq!(surface.task_queue, "reviewers");
assert_eq!(
surface.descriptors.len(),
1,
"the queue's one bodyless action must be advertised, got {:?}",
surface.descriptors
);
assert_eq!(surface.descriptors[0].name, "review");
assert_eq!(
surface.descriptors[0].input_schema["properties"]["brief"]["type"],
json!("string")
);
assert_eq!(
surface.descriptors[0].output_schema["$ref"],
json!("#/$defs/Review")
);
assert_eq!(
surface.descriptors[0].output_schema["$defs"]["Review"]["properties"]["verdict"]["type"],
json!("string")
);
Ok(())
}
#[test]
fn a_single_worker_block_derives_the_queue_without_a_flag() -> TestResult {
let document = write_document("derived", DOCUMENT)?;
assert_eq!(surface::derive(&document, None)?.task_queue, "reviewers");
assert_eq!(
surface::derive(&document, Some("reviewers"))?.task_queue,
"reviewers"
);
Ok(())
}
#[test]
fn a_multi_worker_document_refuses_and_names_every_choice() -> TestResult {
let document = write_document("ambiguous", &two_worker_document())?;
let Err(error) = surface::derive(&document, None) else {
return Err("a document declaring two queues must not pick one".into());
};
let error = error.to_string();
assert!(
error.contains("reviewers") && error.contains("summarisers"),
"the refusal must name every choice, got: {error}"
);
assert!(
error.contains("--task-queue"),
"the refusal must name the flag that selects one, got: {error}"
);
assert_eq!(
surface::derive(&document, Some("summarisers"))?.task_queue,
"summarisers"
);
assert_eq!(
surface::derive(&document, Some("reviewers"))?.task_queue,
"reviewers"
);
Ok(())
}
#[test]
fn a_queue_the_document_does_not_declare_is_refused() -> TestResult {
let document = write_document("wrongqueue", DOCUMENT)?;
let Err(error) = surface::derive(&document, Some("nobody")) else {
return Err("a queue absent from the document must be refused".into());
};
let error = error.to_string();
assert!(
error.contains("nobody") && error.contains("reviewers"),
"the refusal must name the selection and the declaration, got: {error}"
);
Ok(())
}
#[test]
fn a_body_carrying_action_is_excluded_from_the_surface() -> TestResult {
let document = write_document("mixed", MIXED_BODY_DOCUMENT)?;
let surface = surface::derive(&document, None)?;
assert_eq!(
surface.names(),
BTreeSet::from([String::from("review")]),
"only the bodyless action is the worker's to serve"
);
Ok(())
}
#[test]
fn a_queue_of_only_bodied_actions_is_refused_by_name() -> TestResult {
let document = write_document("allbodied", ALL_BODIED_DOCUMENT)?;
let Err(error) = surface::derive(&document, None) else {
return Err("a queue with nothing for a worker to serve must be refused".into());
};
let error = error.to_string();
assert!(
error.contains("declared body") && error.contains("run"),
"the refusal must name the rule and the action, got: {error}"
);
Ok(())
}
#[test]
fn a_partially_served_queue_is_refused_by_name() -> TestResult {
let document = write_document("partial", &two_action_document())?;
let contract = worker_surface::compile_contract(&document)?;
let worker = worker_surface::select_worker(&document, &contract, None, "--task-queue")?;
let whole = worker_surface::serviceable_action_names(worker);
assert_eq!(
whole,
BTreeSet::from([String::from("review"), String::from("summarise")])
);
assert_eq!(
worker_surface::reconcile(&document, worker, &whole, AGENT_SOURCE)?.len(),
2
);
let partial = BTreeSet::from([String::from("review")]);
let Err(error) = worker_surface::reconcile(&document, worker, &partial, AGENT_SOURCE) else {
return Err("a queue must be served whole or not at all".into());
};
let error = error.to_string();
assert!(
error.contains("summarise") && error.contains("whole queue or none"),
"the refusal must name the unserved action and the rule, got: {error}"
);
Ok(())
}
const AGENT_SOURCE: ServingSource = ServingSource {
subject: "the agent worker",
serves: "serves",
omits: "does not serve",
};
#[test]
fn a_document_that_does_not_compile_is_refused_with_its_diagnostics() -> TestResult {
let document = write_document("broken", "//! broken\nworkflow \n")?;
let Err(error) = surface::derive(&document, None) else {
return Err("an uncompilable document must be refused".into());
};
assert!(
error.to_string().contains("does not compile"),
"the refusal must say the document does not compile, got: {error}"
);
Ok(())
}
#[test]
fn missing_settings_are_reported_by_name_and_all_at_once() -> TestResult {
let document = write_document("missing", DOCUMENT)?;
let Err(error) = args::resolve(&args_for(&document), &Environment::default()) else {
return Err("resolution with nothing supplied must be refused".into());
};
let report = error.to_string();
for expected in [
"--liminal-address or $AION_WORKER_LIMINAL_ADDRESSES",
"--identity or $AION_WORKER_IDENTITY",
] {
assert!(
report.contains(expected),
"the report must name `{expected}`, got:\n{report}"
);
}
for deleted in ["--concurrency", "--reconnect-"] {
assert!(
!report.contains(deleted),
"`{deleted}` is declared in the document now, so the connection report must \
not ask for it: {report}"
);
}
Ok(())
}
#[test]
fn the_environment_supplies_settings_and_the_flag_wins() -> TestResult {
let document = write_document("environment", DOCUMENT)?;
let environment = Environment::from_pairs([
(
"AION_WORKER_LIMINAL_ADDRESSES",
"127.0.0.1:50061, 127.0.0.1:50062",
),
("AION_WORKER_IDENTITY", "from-environment"),
("AION_TASK_QUEUE", "reviewers"),
]);
let resolved = args::resolve(&args_for(&document), &environment)?;
assert_eq!(
resolved.addresses,
vec![
String::from("127.0.0.1:50061"),
String::from("127.0.0.1:50062")
],
"a comma-separated list keeps its order and drops blank entries"
);
assert_eq!(resolved.identity, "from-environment");
assert_eq!(resolved.node, None, "node is the SDK's default, not ours");
let mut arguments = args_for(&document);
arguments.identity = Some(String::from("from-flag"));
arguments.liminal_addresses = vec![String::from("127.0.0.1:50099")];
arguments.task_queue = Some(String::from("from-flag-queue"));
let resolved = args::resolve(&arguments, &environment)?;
assert_eq!(resolved.identity, "from-flag");
assert_eq!(resolved.addresses, vec![String::from("127.0.0.1:50099")]);
assert_eq!(
arguments.requested_queue(&environment).as_deref(),
Some("from-flag-queue")
);
Ok(())
}
#[test]
fn a_blank_environment_value_counts_as_missing() -> TestResult {
let document = write_document("blank", DOCUMENT)?;
let environment = Environment::from_pairs([
("AION_WORKER_LIMINAL_ADDRESSES", " , "),
("AION_WORKER_IDENTITY", " "),
]);
let Err(error) = args::resolve(&args_for(&document), &environment) else {
return Err("blank values must be refused as missing".into());
};
let report = error.to_string();
assert!(report.contains("--liminal-address"));
assert!(report.contains("--identity"));
Ok(())
}
#[test]
fn the_launch_settings_come_from_the_documents_harness_section() -> TestResult {
let document = write_document("launch", &harness_document())?;
let Some(launch) = crate::worker_surface::compile_harness(&document, "reviewers")? else {
return Err("the document declares a harness section".into());
};
assert_eq!(launch.concurrency, 7);
assert_eq!(launch.reconnect_initial_backoff, Duration::from_secs(5));
assert_eq!(launch.reconnect_max_backoff, Duration::from_secs(120));
assert_eq!(launch.reconnect_max_attempts, 42);
assert_eq!(
launch.env_pass,
vec![String::from("PATH"), String::from("HOME")],
"the pass-through list is the agent child's WHOLE environment"
);
Ok(())
}
#[test]
fn a_document_without_a_harness_section_declares_no_launch() -> TestResult {
let document = write_document("nolaunch", DOCUMENT)?;
let launch = crate::worker_surface::compile_harness(&document, "reviewers")?;
assert!(
launch.is_none(),
"no section means no launch, not an empty one"
);
Ok(())
}
#[test]
fn serving_a_document_with_no_harness_section_refuses_naming_the_section() -> TestResult {
let document = write_document("refusal", DOCUMENT)?;
let surface = surface::derive(&document, None)?;
let error = super::serve::no_harness_section(&surface, &document);
let message = error.to_string();
for expected in [
"declares no `harness` section",
"There is no flag for any of it",
"aion awl check",
"reviewers",
] {
assert!(
message.contains(expected),
"the refusal must name `{expected}`, got:\n{message}"
);
}
Ok(())
}
const AGENT_SEAM_WITHOUT_A_HARNESS: &str = r"//! An agent seam with no launch: valid as a document, unservable by this verb.
workflow ask_flow
input question: String
outcome answered: type Answer, route success
/// What the flow routes on.
type Answer { reply: String }
/// The canonical agent-outcome record.
type Reply {
text: String,
final_message: String,
stop_reason: String,
session_id: String,
}
/// The queue an agent worker would serve.
worker reviewers
action review(brief: String) -> Reply
agent
step do_review
review(brief: question) -> reply
route answered(reply: reply.text)
";
#[test]
fn the_refusal_does_not_point_at_a_check_that_answers_ok() -> TestResult {
let document = write_document("agent-seam-no-harness", AGENT_SEAM_WITHOUT_A_HARNESS)?;
let parsed = aion_awl::parse(AGENT_SEAM_WITHOUT_A_HARNESS)?;
let findings = aion_awl::check(&parsed);
assert!(
findings.is_empty(),
"the premise of aion#88 is that `aion awl check` PASSES an agent seam carrying no \
harness section; it now reports {findings:?} — the refusal's explanation must be \
revisited before this assertion is relaxed"
);
let surface = surface::derive(&document, None)?;
let message = super::serve::no_harness_section(&surface, &document).to_string();
for expected in [
"kind acp",
"kind norn",
"reports the document valid",
"is a legitimate document",
] {
assert!(
message.contains(expected),
"the remedy must carry `{expected}` so the recommended check is not read as a \
contradiction, got:\n{message}"
);
}
Ok(())
}
#[test]
fn check_prints_the_surface_without_connecting() -> TestResult {
let document = write_document("check", DOCUMENT)?;
let surface = surface::derive(&document, None)?;
let mut rendered = Vec::new();
surface::print(&mut rendered, &surface, None)?;
let report: Value = serde_json::from_slice(&rendered)?;
assert_eq!(report["task_queue"], json!("reviewers"));
let Some(actions) = report["actions"].as_array() else {
return Err("the report must carry an actions array".into());
};
assert_eq!(actions.len(), 1);
assert_eq!(actions[0]["name"], json!("review"));
assert_eq!(
actions[0]["input_schema"]["properties"]["brief"]["type"],
json!("string")
);
assert_eq!(
actions[0]["output_schema"]["$defs"]["Review"]["properties"]["detail"]["type"],
json!("string")
);
assert_eq!(report["harness"], Value::Null);
assert_eq!(
report["intervention_capabilities"]["supported"],
json!([]),
"a build with no harness advertises no controls"
);
for harness in crate::harness::catalogue::compiled() {
let mut rendered = Vec::new();
surface::print(&mut rendered, &surface, Some(harness))?;
let report: Value = serde_json::from_slice(&rendered)?;
assert_eq!(report["harness"], json!(harness.name()));
assert_eq!(
report["intervention_capabilities"],
serde_json::to_value(crate::harness::registration_capabilities(Some(harness)))?
);
let Some(supported) = report["intervention_capabilities"]["supported"].as_array() else {
return Err("the capability set must carry a supported array".into());
};
assert!(
!supported.is_empty(),
"`{}` must advertise the controls its adapter can honour",
harness.name()
);
}
Ok(())
}
#[cfg(feature = "acp")]
#[test]
fn the_acp_check_report_advertises_exactly_cancel() -> TestResult {
let document = write_document("acpcheck", DOCUMENT)?;
let surface = surface::derive(&document, None)?;
let mut rendered = Vec::new();
surface::print(
&mut rendered,
&surface,
Some(crate::harness::HarnessKind::Acp),
)?;
let report: Value = serde_json::from_slice(&rendered)?;
assert_eq!(report["harness"], json!("acp"));
assert_eq!(
report["intervention_capabilities"]["supported"],
json!([{ "primitive": "Cancel" }]),
"ACP can deliver exactly one primitive, so exactly one is advertised"
);
Ok(())
}
#[cfg(all(any(feature = "norn", feature = "acp"), feature = "liminal-transport"))]
#[test]
fn the_composed_config_advertises_exactly_the_documents_surface() -> TestResult {
let document = write_document("composed", &two_action_document())?;
let surface = surface::derive(&document, None)?;
let advertised = surface.names();
let Some(harness) = crate::harness::catalogue::compiled().first().copied() else {
return Err("a build with liminal transport and a harness feature must carry one".into());
};
let config = harness.test_config();
let agent = aion_worker::AgentHarnessConfig::new(
crate::harness::compose::build(&config),
advertised.iter().cloned(),
crate::harness::registration_capabilities(Some(harness)),
)
.with_activity_descriptors(surface.descriptors.clone());
assert_eq!(agent.agent_activity_types(), &advertised);
assert_eq!(
agent
.activity_descriptors()
.iter()
.map(|descriptor| descriptor.name.clone())
.collect::<BTreeSet<_>>(),
advertised,
"a descriptor must exist for every advertised action"
);
assert!(
!agent.activity_descriptors().is_empty(),
"an empty descriptor set is refused by every contract-bearing queue"
);
Ok(())
}
fn two_worker_document() -> String {
DOCUMENT.replace(
"step do_review",
"/// A second queue, so the choice is the operator's.\nworker summarisers\n \
action summarise(brief: String) -> Review\n\nstep do_review",
)
}
fn two_action_document() -> String {
DOCUMENT.replace(
" action review(brief: String) -> Review",
" action review(brief: String) -> Review\n action summarise(brief: String) -> Review",
)
}
const MIXED_BODY_DOCUMENT: &str = r#"//! mixed: one action for a worker, one the server runs from the contract.
workflow mixed_flow
input brief: String
outcome reviewed: type Review, route success
/// What the agent reported back.
type Review {
verdict: String,
detail: String,
}
/// What a declared command reported.
type RunOutcome { exit_code: Int, stdout: String, stderr: String }
/// One action a worker serves, one the server runs.
worker reviewers
action review(brief: String) -> Review
action stamp(brief: String) -> RunOutcome
run "printf %s {{brief}}"
step do_review
brief |> review |> route reviewed
"#;
const ALL_BODIED_DOCUMENT: &str = r#"//! bodied: every action on the queue is the server's to run.
workflow bodied_flow
input brief: String
outcome stamped: type RunOutcome, route success
/// What a declared command reported.
type RunOutcome { exit_code: Int, stdout: String, stderr: String }
/// The server runs every action here; no worker serves any of them.
worker reviewers
action stamp(brief: String) -> RunOutcome
run "printf %s {{brief}}"
step do_stamp
brief |> stamp |> route stamped
"#;
fn harness_document() -> String {
DOCUMENT.replace(
"worker reviewers\n",
"worker reviewers\n harness\n kind norn\n concurrency 7\n \
reconnect_initial_backoff 5s\n reconnect_max_backoff 2m\n \
reconnect_max_attempts 42\n env_pass \"PATH\", \"HOME\"\n cwd \
\"/srv/agents\"\n",
)
}