use super::{DeliveryEvidence, DeliveryOutcome};
const LONG_MESSAGE: &str = "A final report with enough detail. A final report with enough detail. A final report with enough detail. A final report with enough detail. A final report with enough detail. A final report with enough detail.";
fn observe(evidence: &mut DeliveryEvidence, event: serde_json::Value) {
evidence.observe_codex_jsonl(&event.to_string());
}
#[test]
fn accepts_substantive_message_after_work() {
let mut evidence = DeliveryEvidence::default();
observe(&mut evidence, serde_json::json!({"type":"item.completed","item":{"type":"command_execution"}}));
observe(&mut evidence, serde_json::json!({"type":"item.completed","item":{"type":"agent_message","text":LONG_MESSAGE}}));
assert_eq!(evidence.validate(), DeliveryOutcome::Delivered);
}
#[test]
fn rejects_progress_message_followed_by_work() {
let mut evidence = DeliveryEvidence::default();
observe(&mut evidence, serde_json::json!({"type":"item.completed","item":{"type":"agent_message","text":LONG_MESSAGE}}));
observe(&mut evidence, serde_json::json!({"type":"item.completed","item":{"type":"command_execution"}}));
assert!(matches!(
evidence.validate(),
DeliveryOutcome::MissingFinalDelivery { .. }
));
}
#[test]
fn rejects_short_trailing_fragment() {
let mut evidence = DeliveryEvidence::default();
observe(&mut evidence, serde_json::json!({"type":"item.completed","item":{"type":"command_execution"}}));
observe(&mut evidence, serde_json::json!({"type":"item.completed","item":{"type":"agent_message","text":"done"}}));
assert!(matches!(
evidence.validate(),
DeliveryOutcome::MissingFinalDelivery { last_message_chars: 4, .. }
));
}
#[test]
fn rejects_138_char_final_message() {
let mut evidence = DeliveryEvidence::default();
let short_message = "x".repeat(138);
observe(&mut evidence, serde_json::json!({"type":"item.completed","item":{"type":"command_execution"}}));
observe(&mut evidence, serde_json::json!({"type":"item.completed","item":{"type":"agent_message","text":short_message}}));
assert!(matches!(
evidence.validate(),
DeliveryOutcome::MissingFinalDelivery { last_message_chars: 138, .. }
));
}
#[test]
fn accepts_tool_free_answer() {
let mut evidence = DeliveryEvidence::default();
observe(&mut evidence, serde_json::json!({"type":"item.completed","item":{"type":"agent_message","text":LONG_MESSAGE}}));
assert_eq!(evidence.validate(), DeliveryOutcome::Delivered);
}
#[test]
fn accepts_final_message_followed_by_todo_list_update() {
let mut evidence = DeliveryEvidence::default();
observe(&mut evidence, serde_json::json!({"type":"item.completed","item":{"type":"command_execution"}}));
observe(&mut evidence, serde_json::json!({"type":"item.completed","item":{"type":"agent_message","text":LONG_MESSAGE}}));
observe(&mut evidence, serde_json::json!({"type":"item.started","item":{"type":"todo_list","items":[]}}));
observe(&mut evidence, serde_json::json!({"type":"item.updated","item":{"type":"todo_list","items":[]}}));
observe(&mut evidence, serde_json::json!({"type":"item.completed","item":{"type":"todo_list","items":[]}}));
assert_eq!(evidence.validate(), DeliveryOutcome::Delivered);
}
const NARRATION_CAPTURE: &str = "I will start by checking the list of permissions to see what actions and directories are available to us.\n\
I will run `pwd` to identify the current working directory of our workspace.\n\
I will run `git diff main..HEAD` to get the list of changes made in the branch.\n\
I will output the full git diff to a file in the scratch folder so we can read it without truncation.\n\
I will inspect the indexer snapshot file `crates/sr-indexer/src/snapshot.rs` to answer Q2.\n\
[MILESTONE] Analyzed Q2 regarding serialization determinism across indexer restarts.\n";
#[test]
fn rejects_pre_tool_narration_capture() {
assert!(!super::looks_like_delivered_report(NARRATION_CAPTURE));
}
#[test]
fn accepts_markdown_report() {
let report = format!("## Findings\n\nQ1 PASS. {LONG_MESSAGE}");
assert!(super::looks_like_delivered_report(&report));
}
#[test]
fn accepts_prose_report_without_headings() {
assert!(super::looks_like_delivered_report(LONG_MESSAGE));
}
#[test]
fn accepts_the_shortest_valid_report() {
assert!(super::looks_like_delivered_report("## Findings\nNo findings."));
}
#[test]
fn rejects_short_text_without_a_heading() {
assert!(!super::looks_like_delivered_report("done"));
}
#[test]
fn narration_followed_by_a_real_report_still_counts_as_delivered() {
let mixed = format!("{NARRATION_CAPTURE}\n## Findings\n\n{LONG_MESSAGE}");
assert!(super::looks_like_delivered_report(&mixed));
}
#[test]
fn rejects_narration_under_a_heading() {
let planning = format!("# Investigation Plan\n{NARRATION_CAPTURE}{NARRATION_CAPTURE}");
assert!(!super::looks_like_delivered_report(&planning));
}
#[test]
fn accepts_report_ending_on_a_forward_looking_sentence() {
let report = "## Findings\n\
No vulnerabilities were found in the codebase.\n\
We will monitor the application logs for any errors.\n";
assert!(super::looks_like_delivered_report(report));
}
#[test]
fn rejects_raw_tool_output_trailing_a_narration_line() {
let capture = "I'll list the directory to see the files.\n\
total 0\n\
-rw-r--r-- 1 user group 0 Jul 31 22:00 Cargo.toml\n\
-rw-r--r-- 1 user group 0 Jul 31 22:00 src/lib.rs\n\
-rw-r--r-- 1 user group 0 Jul 31 22:00 src/main.rs\n\
-rw-r--r-- 1 user group 0 Jul 31 22:00 tests/integration.rs\n\
-rw-r--r-- 1 user group 0 Jul 31 22:00 docs/readme.md\n";
assert!(!super::looks_like_delivered_report(capture));
}
#[test]
fn accepts_a_report_in_chinese() {
let report = "本次代码审计工作已经全部完成。我们对所有的边界条件进行了详细的检查,并确认了1680个单元测试全部顺利通过。\
我们建议可以立即合并当前的分支并部署上线。为了确保系统的长期稳定性,我们还建议在后续的开发中继续保持单元测试的完整覆盖率,\
并定期进行自动化的安全审计工作。同时,我们也已经将所有的审计日志和详细的测试报告保存到了指定的归档目录中,\
方便团队其他成员随时查阅和核对。审计过程中重点复核了快照摘要跳过逻辑在冷启动、部分失败以及进程重启这三种情形下的行为,\
均未发现会导致快照被错误跳过的路径。此外我们还确认了丢弃计数所使用的互斥锁只出现在丢弃分支上,\
不会影响正常的发布路径,因此不存在额外的锁竞争风险。";
assert!(report.chars().count() >= super::MIN_FINAL_MESSAGE_CHARS);
assert!(super::looks_like_delivered_report(report));
}
#[test]
fn accepts_a_bullet_report_without_sentence_punctuation() {
let report = "- Digest skip is safe on cold start, no recorded digest means no skip\n\
- RecentlyUpdated scope keeps its own digest key, so a full snapshot still applies\n\
- Two replicas share one process-global map, which is per-process and therefore fine\n\
- Drop accounting takes the mutex only on the drop path, not the publish path\n";
assert!(super::looks_like_delivered_report(report));
}
#[test]
fn accepts_a_single_line_report_that_opens_like_narration() {
let report = "I will present the final report: we fixed every bug in the codebase, cleaned up the \
compiler warnings, ran the full test suite, and verified that all 1680 unit tests pass without \
any errors or ignored cases.";
assert!(report.chars().count() >= super::MIN_FINAL_MESSAGE_CHARS);
assert!(super::looks_like_delivered_report(report));
}
#[test]
fn rejects_narration_behind_discourse_markers() {
let text = "First, I'll search the repository for the affected call sites.\n\
Next, I will read the snapshot module to confirm the encoding order.\n\
Then I'll check whether the digest guard agrees with the content hash.\n\
Let's inspect the drop accounting path afterwards.\n\
We will finish by summarizing the three answers.\n";
assert!(!super::looks_like_delivered_report(text));
}