#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
mod support;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use serde_json::json;
use support::{Harness, narrating, narration, token_for};
use turnframe_core::ids::TurnId;
use turnframe_core::prompt::{LoadedPrompt, PromptError, PromptName, PromptSelector, PromptSource};
use turnframe_core::replay::ReplayRecord;
use turnframe_core::response::AssistantTurn;
use turnframe_tasks::TASK_LABEL;
use turnframe_tasks::instructions::BUILT_IN_VERSION;
use turnframe_tasks::testing::ScriptedTasks;
use turnframe_test::providers::UnderstandingBuilder;
use turnframe_test::workflows::trip::{incomplete_case, operations};
const TURN_TEXT: &str = "change the name to Lisbon";
const SEGMENT_TEXT: &str = "Split the message the way this repository says to.";
const NARRATE_TEXT: &str = "Acknowledge in one line. Claim nothing.";
#[derive(Debug)]
struct TableSource {
fail: bool,
asked: AtomicUsize,
}
impl TableSource {
fn answering() -> Arc<Self> {
Arc::new(Self {
fail: false,
asked: AtomicUsize::new(0),
})
}
fn failing() -> Arc<Self> {
Arc::new(Self {
fail: true,
asked: AtomicUsize::new(0),
})
}
fn asked(&self) -> usize {
self.asked.load(Ordering::Relaxed)
}
}
#[async_trait::async_trait]
impl PromptSource for TableSource {
async fn load(
&self,
name: &PromptName,
_selector: &PromptSelector,
) -> Result<LoadedPrompt, PromptError> {
self.asked.fetch_add(1, Ordering::Relaxed);
if self.fail {
return Err(PromptError::Transport { code: "connect" });
}
let text = match name.as_str() {
"understand.segment" => SEGMENT_TEXT,
"narrate.acknowledge" => NARRATE_TEXT,
_ => {
return Err(PromptError::NotFound { name: name.clone() });
}
};
Ok(LoadedPrompt::new(name.clone(), "r7", text))
}
fn describe(&self) -> &'static str {
"table"
}
}
fn turn_id() -> TurnId {
TurnId::from(uuid::Uuid::from_u128(41))
}
fn understanding_tasks() -> Arc<ScriptedTasks> {
let pointer = |from: usize, to: usize| json!({"from": from, "to": to});
let unit = json!({"kind": "request", "words": pointer(1, 5), "workflow": "trip"});
let value =
json!({"kind": "words", "text": "Lisbon", "message": "current", "from": 5, "to": 5});
Arc::new(
ScriptedTasks::new("tasks", "small")
.answer(
"turn/segment",
json!({"analysis": "One request.", "units": [unit]}),
)
.answer("turn/coverage", json!({"missed": []}))
.answer("u1/route", json!({"operations": [operations::SET_NAME]}))
.answer("u1/extract", json!({"arguments": {"value": value}}))
.answer(
"u1/verify",
json!({"reason": "Stated.", "arguments": {"value": "stated"}, "overall": "confirmed"}),
),
)
}
async fn understood(source: Option<Arc<TableSource>>, tasks: Arc<ScriptedTasks>) -> ReplayRecord {
let mut builder = Harness::builder()
.trip("trip-1", "Trip 2026-1", 3, incomplete_case())
.understanding_tasks(tasks)
.without_narration();
if let Some(source) = source {
builder = builder.prompts(source as Arc<dyn PromptSource>);
}
let harness = builder.build().await;
harness
.handle(harness.turn(turn_id(), TURN_TEXT))
.await
.expect("a prompt source is a convenience, not a dependency");
assert_eq!(
harness.events("trip", "trip-1").await,
vec!["trip.name_set"],
"the turn was understood and committed"
);
harness.replay(turn_id()).await
}
async fn narrated(source: Option<Arc<TableSource>>) -> (ReplayRecord, AssistantTurn) {
let understanding = UnderstandingBuilder::of(TURN_TEXT)
.apply(
operations::SET_NAME,
token_for(turn_id(), "trip", "trip-1"),
json!({"value": "Lisbon"}),
TURN_TEXT,
)
.build()
.unwrap();
let mut builder = Harness::builder()
.trip("trip-1", "Trip 2026-1", 3, incomplete_case())
.understands(understanding)
.provider(narrating().build_shared());
if let Some(source) = source {
builder = builder.prompts(source as Arc<dyn PromptSource>);
}
let harness = builder.build().await;
let turn = harness
.handle(harness.turn(turn_id(), TURN_TEXT))
.await
.expect("a prompt source is a convenience, not a dependency");
(harness.replay(turn_id()).await, turn)
}
fn every_task_ran_built_in(record: &ReplayRecord) -> bool {
!record.tasks.is_empty()
&& record.tasks.iter().all(|task| {
task.prompt_ref
.as_ref()
.is_some_and(|reference| reference.version.as_str() == BUILT_IN_VERSION)
})
}
#[tokio::test]
async fn the_prompt_reference_reaches_the_replay_record_and_the_task_that_used_it() {
let source = TableSource::answering();
let (record, turn) = narrated(Some(Arc::clone(&source))).await;
assert!(!narration(&turn).is_empty(), "the narrator ran");
let names: Vec<&str> = record
.prompt_refs
.iter()
.map(|reference| reference.name.as_str())
.collect();
assert_eq!(names, vec!["narrate.acknowledge"]);
assert_eq!(record.prompt_refs[0].version.as_str(), "r7");
assert!(
record.prompt_refs[0].matches(NARRATE_TEXT),
"{} does not name its text",
record.prompt_refs[0]
);
let acknowledged = record
.tasks
.iter()
.find(|task| task.task_id == "reply/acknowledge")
.expect("the acknowledgement is on the record");
let reference = acknowledged
.prompt_ref
.as_ref()
.expect("a task cites its prompt");
assert_eq!(reference.name.as_str(), "narrate.acknowledge");
assert_eq!(reference.version.as_str(), "r7");
assert!(source.asked() >= 1, "narration asked the source");
}
#[tokio::test]
async fn the_prompt_reference_reaches_the_task_that_used_it_and_the_turn() {
let source = TableSource::answering();
let tasks = understanding_tasks();
let record = understood(Some(Arc::clone(&source)), Arc::clone(&tasks)).await;
let segment = record
.tasks
.iter()
.find(|task| task.task_id == "turn/segment")
.expect("the segmentation is on the record");
let reference = segment
.prompt_ref
.as_ref()
.expect("a task cites its prompt");
assert_eq!(reference.name.as_str(), "understand.segment");
assert_eq!(reference.version.as_str(), "r7");
assert!(
reference.matches(SEGMENT_TEXT),
"{reference} does not name its text"
);
for task in record
.tasks
.iter()
.filter(|task| task.task_id != "turn/segment")
{
let reference = task.prompt_ref.as_ref().expect("a task cites its prompt");
assert_eq!(
reference.version.as_str(),
BUILT_IN_VERSION,
"{} ran on the built-in text the source did not replace",
task.task_id
);
}
let sent = tasks
.calls()
.into_iter()
.find(|call| call.metadata.get(TASK_LABEL) == Some("turn/segment"))
.expect("the segmentation was sent");
assert_eq!(
sent.system.as_deref(),
Some(SEGMENT_TEXT),
"the model saw the text the reference names"
);
assert!(source.asked() >= 1, "understanding asked the source");
let names: Vec<&str> = record
.prompt_refs
.iter()
.map(|reference| reference.name.as_str())
.collect();
assert_eq!(names, vec!["understand.segment"]);
}
#[tokio::test]
async fn with_no_source_configured_the_record_carries_no_prompt_reference() {
let (narrated, _) = narrated(None).await;
assert!(
narrated.prompt_refs.is_empty(),
"the default configuration reaches no prompt source"
);
assert!(
every_task_ran_built_in(&narrated),
"every narration task cites the built-in text it ran on"
);
let understood = understood(None, understanding_tasks()).await;
assert!(understood.prompt_refs.is_empty());
assert!(
every_task_ran_built_in(&understood),
"every task cites the built-in text it ran on"
);
}
#[tokio::test]
async fn a_source_that_cannot_answer_leaves_the_turn_standing_and_the_record_honest() {
let source = TableSource::failing();
let (narrated, turn) = narrated(Some(Arc::clone(&source))).await;
assert!(!turn.blocks.is_empty());
assert!(source.asked() >= 1, "the source was tried");
assert!(
narrated.prompt_refs.is_empty(),
"no reference is the audit signal that the built-in text was used"
);
assert!(every_task_ran_built_in(&narrated));
let understood = understood(Some(source), understanding_tasks()).await;
assert!(understood.prompt_refs.is_empty());
assert!(
every_task_ran_built_in(&understood),
"every task says it ran on the built-in text"
);
}