use chrono::Utc;
use serde_json::json;
use uuid::Uuid;
use crate::multi_agent::agent_invoke::{
no_cycles_in_chain, record_invocation, validate_target_agent, REGISTERED_CFA_AGENTS,
};
use crate::multi_agent::budget::{check_budget, BudgetVerdict, PlanBudget};
use crate::multi_agent::entity_graph::{extract_entities_from_text, EntityGraph};
use crate::multi_agent::goap_adapter::{load_action_catalogue, validate_action};
use crate::multi_agent::planner::{detect_cycles, plan, plan_hash, replan, DEFAULT_MAX_REPLANS};
use crate::multi_agent::types::{
AgentInvocation, EntityKind, EntityRef, InvocationStatus, PlanAction, RelationKind, StepStatus,
};
fn mk_invocation(slug: &str, parent: Option<Uuid>) -> AgentInvocation {
AgentInvocation {
invocation_id: Uuid::now_v7(),
target_agent: slug.into(),
parent_invocation_id: parent,
input_summary: "x".into(),
tenant_id: Some("local".into()),
ts: Utc::now(),
status: InvocationStatus::Pending,
}
}
#[test]
fn ruf_orc_001_agent_invoked_event_recorded() {
let inv = mk_invocation("cfa-equity-analyst", None);
assert!(record_invocation(&inv).is_ok());
}
#[test]
fn ruf_orc_002_target_agent_validated_against_registry() {
let bad = mk_invocation("not-a-real-specialist", None);
assert!(record_invocation(&bad).is_err());
assert!(!validate_target_agent("not-a-real-specialist"));
assert!(validate_target_agent("cfa-equity-analyst"));
}
#[test]
fn ruf_orc_003_multidomain_plan_emitted_before_specialists_run() {
let cat = load_action_catalogue();
let p = plan("initiate coverage on PFE including credit and macro", &cat).unwrap();
let serialised = serde_json::to_string(&p).unwrap();
assert!(!serialised.is_empty());
assert!(p.steps.len() >= 3);
}
#[test]
fn ruf_orc_004_entity_extraction_yields_entities_for_typical_outputs() {
let outputs = [
"AAPL beat MSFT on revenue",
"CUSIP 037833100 traded today",
"Acme Industrial Corp issued new debt",
"Healthcare and Financials lagged the index",
];
let with_entities = outputs
.iter()
.filter(|t| !extract_entities_from_text(t).is_empty())
.count();
assert_eq!(with_entities, outputs.len());
}
#[test]
fn ruf_orc_005_cross_specialist_co_occurrence_emits_pattern_detected() {
let mut g = EntityGraph::new();
let aapl = EntityRef {
kind: EntityKind::Ticker,
value: "AAPL".into(),
source_invocation: None,
};
for other in ["MSFT", "GOOG", "META"] {
let other_ref = EntityRef {
kind: EntityKind::Ticker,
value: other.into(),
source_invocation: None,
};
g.add_relation(aapl.clone(), other_ref, RelationKind::MentionedTogether);
}
let hits = g.pattern_detected(3);
assert!(hits.iter().any(|e| e.value == "AAPL"));
}
#[test]
fn ruf_orc_006_plan_depth_default_ceiling_not_exceeded() {
let cat = load_action_catalogue();
let p = plan("initiate coverage on AAPL", &cat).unwrap();
let budget = PlanBudget::new(Some("local".into()), "cfa-equity-analyst");
assert_eq!(check_budget(&p, &budget, &[]), BudgetVerdict::Ok);
}
#[test]
fn ruf_orc_007_plan_cycle_detection_rejects_self_cyclic_plan() {
let cat = load_action_catalogue();
let mut p = plan("dcf for AAPL", &cat).unwrap();
let last = p.steps.len() - 1;
let last_id = p.steps[last].step_id;
p.steps[0].depends_on.push(last_id);
assert!(detect_cycles(&p));
}
#[test]
fn ruf_orc_008_budget_reservation_precedes_dispatch() {
let cat = load_action_catalogue();
let p = plan("initiate coverage on PFE", &cat).unwrap();
let mut b = PlanBudget::new(Some("local".into()), "cfa-equity-analyst");
b.max_steps_per_invocation = 2;
assert!(matches!(
check_budget(&p, &b, &[]),
BudgetVerdict::WouldExceedSteps { .. }
));
}
#[test]
fn ruf_orc_009_at_most_one_in_flight_per_specialist() {
let cat = load_action_catalogue();
let p = plan("dcf for AAPL", &cat).unwrap();
let b = PlanBudget::new(Some("local".into()), "cfa-equity-analyst");
let in_flight = vec![Uuid::now_v7()];
assert!(matches!(
check_budget(&p, &b, &in_flight),
BudgetVerdict::WouldExceedConcurrency { current: 1, max: 1 }
));
}
#[test]
fn ruf_orc_010_every_invocation_reaches_one_terminal_state() {
let terminals = [InvocationStatus::Completed, InvocationStatus::Failed];
let non_terminals = [InvocationStatus::Pending, InvocationStatus::Running];
assert_eq!(terminals.len() + non_terminals.len(), 4);
for s in [
InvocationStatus::Pending,
InvocationStatus::Running,
InvocationStatus::Completed,
InvocationStatus::Failed,
] {
let _ = serde_json::to_string(&s).unwrap();
}
}
#[test]
fn mac_inv_001_invocation_carries_tenant_id() {
let inv = mk_invocation("cfa-credit-analyst", None);
assert!(inv.tenant_id.is_some());
}
#[test]
fn mac_inv_002_target_in_registered_set() {
for slug in REGISTERED_CFA_AGENTS {
assert!(validate_target_agent(slug));
}
assert!(!validate_target_agent("anonymous"));
}
#[test]
fn mac_inv_003_no_cycles_in_parent_chain() {
let root = mk_invocation("cfa-equity-analyst", None);
let child = AgentInvocation {
parent_invocation_id: Some(root.invocation_id),
..mk_invocation("cfa-credit-analyst", Some(root.invocation_id))
};
let grand = AgentInvocation {
parent_invocation_id: Some(child.invocation_id),
..mk_invocation("cfa-equity-analyst", Some(child.invocation_id))
};
let history = vec![root, child];
assert!(!no_cycles_in_chain(&grand, &history));
}
#[test]
fn mac_inv_004_entity_kind_has_exactly_five_variants() {
let all = [
EntityKind::Ticker,
EntityKind::Issuer,
EntityKind::Sector,
EntityKind::Fund,
EntityKind::Cusip,
];
assert_eq!(all.len(), 5);
}
#[test]
fn mac_inv_005_entities_tenant_scoped_unless_federation_bridges() {
let mut g = EntityGraph::new();
let aapl = EntityRef {
kind: EntityKind::Ticker,
value: "AAPL".into(),
source_invocation: Some(Uuid::now_v7()),
};
let idx = g.add_entity(aapl.clone());
assert_eq!(g.node_count(), 1);
let _ = idx;
}
#[test]
fn mac_inv_006_plan_replans_bounded() {
let cat = load_action_catalogue();
let mut p = plan("ic memo for Acme", &cat).unwrap();
assert_eq!(p.max_replans, DEFAULT_MAX_REPLANS);
let target = p.steps[1].step_id;
for _ in 0..DEFAULT_MAX_REPLANS {
replan(&mut p, target, "x").unwrap();
}
let res = replan(&mut p, target, "again");
assert!(res.is_err());
}
#[test]
fn mac_inv_007_plan_hash_deterministic_for_same_goal_and_registry() {
let cat = load_action_catalogue();
let a = plan("initiate coverage on PFE", &cat).unwrap();
let b = plan("initiate coverage on PFE", &cat).unwrap();
assert_eq!(plan_hash(&a), plan_hash(&b));
assert_eq!(a.plan_hash, b.plan_hash);
}
#[test]
fn mac_inv_008_plan_emitted_before_execution() {
let cat = load_action_catalogue();
let p = plan("ic memo for Acme", &cat).unwrap();
assert!(!p.steps.is_empty());
assert!(p.steps.iter().all(|s| s.status == StepStatus::Pending));
let _ = serde_json::to_string(&p).unwrap();
}
#[test]
fn mac_inv_009_output_hash_set_iff_completed() {
fn requires_output_hash(s: InvocationStatus) -> bool {
matches!(s, InvocationStatus::Completed)
}
assert!(requires_output_hash(InvocationStatus::Completed));
assert!(!requires_output_hash(InvocationStatus::Failed));
assert!(!requires_output_hash(InvocationStatus::Running));
assert!(!requires_output_hash(InvocationStatus::Pending));
}
#[test]
fn mac_inv_010_surface_events_translated_through_acl() {
let cat = load_action_catalogue();
let action = PlanAction::McpTool {
name: "dcf_model".into(),
input_hint: json!({}),
};
assert!(validate_action(&cat, &action));
let foreign = PlanAction::McpTool {
name: "an_external_unregistered_tool".into(),
input_hint: json!({}),
};
assert!(!validate_action(&cat, &foreign));
}