use super::*;
use a3s::research::{
replay, EvidenceRef, InquiryEvent, InquiryLimits, InquiryPhase, Question, ResearchMethod,
};
#[test]
fn completed_event_snapshot_is_authoritative_when_tool_output_is_diagnostic_text() {
let final_output = serde_json::json!({
"mode": "hybrid_direct_web_parallel",
"plan": {
"report_title": "Evidence-backed comparison",
"answer_shape": "investigation",
"execution_route": "direct_then_maker",
"tracks": ["Primary evidence"],
"stop_conditions": ["The finding has a traceable source"],
"budget": {}
},
"checker": {
"decision": "finalize",
"coverage_summary": "The planned evidence obligation is supported."
},
"research": {
"results": [{
"success": true,
"structured": {
"summary": "The event journal retained the completed source-backed result.",
"sources": [{
"title": "Official evidence",
"url_or_path": "https://example.com/official",
"quote_or_fact": "The official source supports the retained finding.",
"reliability": "Official source"
}],
"key_evidence": ["The retained finding is source-backed."],
"contradictions": [],
"confidence": "high",
"gaps": []
}
}]
}
});
let metadata = serde_json::json!({
"dynamic_workflow": {
"status": "Completed",
"snapshot": {
"status": "completed",
"output": final_output
}
}
});
let display_output = "Program script completed. Internal workflow events were captured.";
let canonical = deep_research_canonical_workflow_output(display_output, Some(&metadata));
assert_eq!(
serde_json::from_str::<serde_json::Value>(&canonical).unwrap(),
metadata["dynamic_workflow"]["snapshot"]["output"]
);
assert!(
!deep_research_workflow_needs_recovery_report_with_metadata(
display_output,
Some(&metadata),
),
"a completed event snapshot must not be demoted because display output contains diagnostics"
);
assert_eq!(
deep_research_report_outcome_for_workflow(
"Compare the documented mechanisms",
DeepResearchEvidenceScope::WebAndWorkspace,
display_output,
Some(&metadata),
),
DeepResearchRunOutcome::Completed,
);
assert!(
deep_research_cli_report_is_qualified(
"Compare the documented mechanisms",
display_output,
Some(&metadata),
)
.is_ok(),
"CLI synthesis must consume the event-sourced result instead of generic recovery"
);
}
#[test]
fn bounded_supporting_obligation_produces_a_qualified_reportable_inquiry() {
let mut supporting = Question::queued(
"question:supporting",
None,
"Which supporting context can be established?",
);
supporting.material = false;
let events = vec![
InquiryEvent::StrategySelected {
method: ResearchMethod::Focused,
},
InquiryEvent::QuestionsQueued {
questions: vec![
Question::queued(
"question:core",
None,
"What evidence determines the core conclusion?",
),
supporting,
],
},
InquiryEvent::EvidenceAccepted {
evidence: EvidenceRef::new(
"evidence:core",
vec!["claim:core".to_string()],
vec!["source:core".to_string()],
),
},
InquiryEvent::QuestionAnswered {
question_id: "question:core".to_string(),
answer: "The retained source supports the core conclusion.".to_string(),
evidence_ids: vec!["evidence:core".to_string()],
},
InquiryEvent::QuestionBounded {
question_id: "question:supporting".to_string(),
reason: "The supporting context remains unavailable.".to_string(),
},
];
let state = replay(&events, &InquiryLimits::default()).expect("qualified inquiry projection");
assert_eq!(state.phase, InquiryPhase::Outlining);
let output = serde_json::json!({
"mode": "direct_web",
"checker": {
"decision": "finalize",
"coverage_summary": "The core conclusion is supported. Supporting context remains bounded."
},
"research": {
"status": "success",
"results": [{
"success": true,
"structured": {
"summary": "The accepted evidence establishes the core conclusion.",
"sources": [{
"title": "Core source",
"url_or_path": "https://example.com/core",
"quote_or_fact": "The source supports the core conclusion.",
"reliability": "Authoritative source"
}],
"key_evidence": ["The core conclusion is source-backed."],
"contradictions": [],
"confidence": "high",
"gaps": ["Supporting context remains unavailable."]
}
}]
},
"inquiry": {
"events": events,
"state": state
}
});
assert_eq!(
deep_research_report_outcome_for_workflow(
"Determine the core conclusion with useful supporting context",
DeepResearchEvidenceScope::WebAndWorkspace,
&output.to_string(),
None,
),
DeepResearchRunOutcome::Qualified,
);
}
#[test]
fn collect_only_inquiry_projection_owns_completion_and_fails_closed() {
let mut question = Question::queued(
"question:core",
None,
"What evidence determines the conclusion?",
);
question.obligation_ids = vec!["obligation:core".to_string()];
let events = vec![
InquiryEvent::StrategySelected {
method: ResearchMethod::Focused,
},
InquiryEvent::ResearchObligationsCommitted {
obligations: vec![a3s::research::ResearchObligation::new(
"obligation:core",
"Core conclusion",
"Establish the core conclusion",
true,
vec!["The conclusion is supported by traceable evidence".to_string()],
)],
stop_conditions: vec!["The core conclusion is traceable".to_string()],
},
InquiryEvent::QuestionsQueued {
questions: vec![question],
},
InquiryEvent::EvidenceAccepted {
evidence: EvidenceRef::new(
"evidence:core",
vec!["claim:core".to_string()],
vec!["source:core".to_string()],
),
},
InquiryEvent::QuestionAnswered {
question_id: "question:core".to_string(),
answer: "The retained source supports the conclusion.".to_string(),
evidence_ids: vec!["evidence:core".to_string()],
},
InquiryEvent::ResearchContractAssessed {
assessment: a3s::research::ResearchContractAssessment {
obligations: vec![a3s::research::ResearchObligationAssessment {
obligation_id: "obligation:core".to_string(),
criteria: vec![a3s::research::CompletionCriterionAssessment {
criterion_index: 0,
status: a3s::research::ContractAssessmentStatus::Satisfied,
rationale: "The accepted evidence satisfies the criterion.".to_string(),
evidence_ids: vec!["evidence:core".to_string()],
}],
primary_source: None,
independent_corroboration: None,
}],
stop_conditions: vec![a3s::research::StopConditionAssessment {
condition_index: 0,
status: a3s::research::ContractAssessmentStatus::Satisfied,
rationale: "The conclusion is traceable.".to_string(),
evidence_ids: vec!["evidence:core".to_string()],
}],
diagnostics: Vec::new(),
},
},
];
let state = replay(&events, &InquiryLimits::default()).expect("completed inquiry projection");
assert_eq!(state.phase, InquiryPhase::Outlining);
let mut output = serde_json::json!({
"mode": "inquiry_collection_wave",
"execution": {
"mode": "collect_only",
"terminal_authority": "host_inquiry_reducer"
},
"research": {
"status": "success",
"results": [{
"success": true,
"structured": {
"summary": "The accepted evidence establishes the conclusion.",
"sources": [{
"title": "Core source",
"url_or_path": "https://example.com/core",
"quote_or_fact": "The source supports the conclusion.",
"reliability": "Authoritative source"
}],
"key_evidence": ["The conclusion is source-backed."],
"contradictions": [],
"confidence": "high",
"gaps": []
}
}]
},
"inquiry": {
"events": events,
"state": state
}
});
assert_eq!(deep_research_collection_status(&output), "completed");
assert!(!deep_research_workflow_needs_recovery_report(
&output.to_string()
));
assert!(deep_research_evidence_package_is_complete_for_query(
"Determine the conclusion",
DeepResearchEvidenceScope::WebAndWorkspace,
&output.to_string(),
None,
));
assert_eq!(
deep_research_report_outcome_for_workflow(
"Determine the conclusion",
DeepResearchEvidenceScope::WebAndWorkspace,
&output.to_string(),
None,
),
DeepResearchRunOutcome::Completed,
);
let nonterminal_events = output["inquiry"]["events"]
.as_array()
.expect("inquiry events")
.iter()
.take(2)
.cloned()
.collect::<Vec<_>>();
let decoded_nonterminal_events: Vec<InquiryEvent> =
serde_json::from_value(serde_json::Value::Array(nonterminal_events.clone()))
.expect("decode nonterminal inquiry events");
let nonterminal_state = replay(&decoded_nonterminal_events, &InquiryLimits::default())
.expect("replay nonterminal inquiry");
let mut nonterminal = output.clone();
nonterminal["inquiry"]["events"] = serde_json::Value::Array(nonterminal_events);
nonterminal["inquiry"]["state"] = serde_json::to_value(nonterminal_state).unwrap();
assert_eq!(deep_research_collection_status(&nonterminal), "degraded");
assert_eq!(
deep_research_report_outcome_for_workflow(
"Determine the conclusion",
DeepResearchEvidenceScope::WebAndWorkspace,
&nonterminal.to_string(),
None,
),
DeepResearchRunOutcome::Degraded,
);
let mut missing_projection = output.clone();
missing_projection
.as_object_mut()
.unwrap()
.remove("inquiry");
assert_eq!(
deep_research_collection_status(&missing_projection),
"degraded"
);
assert_eq!(
deep_research_report_outcome_for_workflow(
"Determine the conclusion",
DeepResearchEvidenceScope::WebAndWorkspace,
&missing_projection.to_string(),
None,
),
DeepResearchRunOutcome::Degraded,
);
output["inquiry"]["state"]["events_applied"] = serde_json::json!(999);
assert_eq!(deep_research_collection_status(&output), "degraded");
assert!(deep_research_workflow_needs_recovery_report(
&output.to_string()
));
assert_eq!(
deep_research_report_outcome_for_workflow(
"Determine the conclusion",
DeepResearchEvidenceScope::WebAndWorkspace,
&output.to_string(),
None,
),
DeepResearchRunOutcome::Degraded,
);
}