use std::sync::Arc;
use aion_core::{
AssistantCommand, AssistantCommandInvocation, AssistantSessionEvent, AssistantSessionState,
};
use aion_store::assistant::AssistantSessionStore;
use super::error::AssistantSessionError;
use super::fixture::{
OPERATOR, RefusingTranscriptStore, open_session, opened, registry, registry_over, went_dormant,
};
use super::lifecycle::{CLOSED_RESUMABLE, DELETED, ENDED_IS_TERMINAL, RESUME_REFUSED};
type TestResult = Result<(), String>;
const BOUNDED_WAIT: std::time::Duration = std::time::Duration::from_secs(10);
async fn within_bound<T>(what: &str, future: impl Future<Output = T>) -> Result<T, String> {
tokio::time::timeout(BOUNDED_WAIT, future)
.await
.map_err(|_elapsed| format!("{what} did not answer within {BOUNDED_WAIT:?}; it hung"))
}
#[tokio::test]
async fn a_dormant_session_whose_agent_cannot_reload_refuses_the_next_turn() -> TestResult {
let (sessions, _store) = registry();
let summary = open_session(&sessions).await?;
opened(&sessions, summary.session_id, false).await?;
went_dormant(&sessions, summary.session_id).await?;
let refusal = within_bound(
"a turn on an unresumable dormant session",
sessions.turn(
OPERATOR,
summary.session_id,
"carry on".to_owned(),
None,
None,
),
)
.await?;
match refusal {
Err(AssistantSessionError::Ended { reason, .. }) => {
assert_eq!(
reason, RESUME_REFUSED,
"the refusal must name the capability the agent did not advertise"
);
assert!(
reason.contains("loadSession"),
"the reason names the capability: {reason}"
);
}
other => {
return Err(format!(
"an unresumable dormant session must refuse as Ended, got {other:?}"
));
}
}
let (state, reason) = sessions
.state_of_session(summary.session_id)
.await
.map_err(|error| error.to_string())?;
assert_eq!(state, AssistantSessionState::Ended);
assert_eq!(reason.as_deref(), Some(RESUME_REFUSED));
Ok(())
}
#[tokio::test]
async fn a_dormant_session_whose_agent_can_reload_reaches_a_resume_and_keeps_its_indices()
-> TestResult {
let (sessions, store) = registry();
let summary = open_session(&sessions).await?;
opened(&sessions, summary.session_id, true).await?;
went_dormant(&sessions, summary.session_id).await?;
let before = store
.assistant_transcript_head(&summary.session_id)
.await
.map_err(|error| error.to_string())?;
let outcome = within_bound(
"a turn on a resumable dormant session",
sessions.turn(
OPERATOR,
summary.session_id,
"carry on".to_owned(),
None,
None,
),
)
.await?;
match outcome {
Err(AssistantSessionError::HarnessFailed { .. }) => {}
Err(AssistantSessionError::Ended { reason, .. }) => {
return Err(format!(
"a resumable session must NOT be settled ended before it is tried: {reason}"
));
}
other => {
return Err(format!(
"the fixture's command is not an ACP agent, so the spawn must fail by name, got \
{other:?}"
));
}
}
let (state, _reason) = sessions
.state_of_session(summary.session_id)
.await
.map_err(|error| error.to_string())?;
assert_ne!(
state,
AssistantSessionState::Ended,
"a spawn that failed must not end a conversation the agent can still reload"
);
let frames = store
.assistant_transcript(&summary.session_id, None)
.await
.map_err(|error| error.to_string())?;
let indices: Vec<u64> = frames.iter().map(|frame| frame.index).collect();
let expected: Vec<u64> = (0..indices.len() as u64).collect();
assert_eq!(
indices, expected,
"the transcript's indices must stay dense from zero across a resume"
);
assert!(
indices.len() as u64 >= before,
"the resume must append at the head, never behind it"
);
Ok(())
}
#[tokio::test]
async fn closing_a_reloadable_session_keeps_it_reopenable_but_never_current() -> TestResult {
let (sessions, _store) = registry();
let reloadable = open_session(&sessions).await?;
opened(&sessions, reloadable.session_id, true).await?;
went_dormant(&sessions, reloadable.session_id).await?;
let current = sessions
.current(OPERATOR)
.await
.map_err(|error| error.to_string())?
.map(|summary| summary.session_id);
assert_eq!(
current,
Some(reloadable.session_id),
"before it is put away, the dormant session is the operator's current one"
);
sessions
.delete(OPERATOR, reloadable.session_id)
.await
.map_err(|error| error.to_string())?;
let (state, reason) = sessions
.state_of_session(reloadable.session_id)
.await
.map_err(|error| error.to_string())?;
assert_eq!(
state,
AssistantSessionState::Closed,
"a reloadable session the caller shut is closed, not ended"
);
assert_eq!(reason.as_deref(), Some(CLOSED_RESUMABLE));
let current = sessions
.current(OPERATOR)
.await
.map_err(|error| error.to_string())?;
assert!(
current.is_none(),
"a closed session is never the current one, however new it is: {current:?}"
);
let outcome = within_bound(
"a turn on a closed session",
sessions.turn(
OPERATOR,
reloadable.session_id,
"let us pick this back up".to_owned(),
None,
None,
),
)
.await?;
match outcome {
Err(AssistantSessionError::HarnessFailed { .. }) => {}
other => {
return Err(format!(
"a turn on a closed session must reach a resume (and fail by name against a \
binary that is not an agent), got {other:?}"
));
}
}
let unreloadable = open_session(&sessions).await?;
opened(&sessions, unreloadable.session_id, false).await?;
went_dormant(&sessions, unreloadable.session_id).await?;
sessions
.delete(OPERATOR, unreloadable.session_id)
.await
.map_err(|error| error.to_string())?;
let (state, reason) = sessions
.state_of_session(unreloadable.session_id)
.await
.map_err(|error| error.to_string())?;
assert_eq!(
state,
AssistantSessionState::Ended,
"a session whose agent cannot reload it is ended when the caller shuts it"
);
assert_eq!(reason.as_deref(), Some(DELETED));
match within_bound(
"a turn on an ended session",
sessions.turn(
OPERATOR,
unreloadable.session_id,
"carry on".to_owned(),
None,
None,
),
)
.await?
{
Err(AssistantSessionError::Ended { .. }) => Ok(()),
other => Err(format!(
"a turn on an ended session is refused before any spawn, got {other:?}"
)),
}
}
#[tokio::test]
async fn a_turn_on_a_never_opened_session_the_caller_deleted_is_refused_and_spawns_nothing()
-> TestResult {
let (sessions, store) = registry();
let created = open_session(&sessions).await?;
sessions
.delete(OPERATOR, created.session_id)
.await
.map_err(|error| error.to_string())?;
let (state, reason) = sessions
.state_of_session(created.session_id)
.await
.map_err(|error| error.to_string())?;
assert_eq!(state, AssistantSessionState::Ended);
assert_eq!(reason.as_deref(), Some(DELETED));
let frames_before = store
.assistant_transcript(&created.session_id, None)
.await
.map_err(|error| error.to_string())?
.len();
match within_bound(
"a turn on a never-opened, deleted session",
sessions.turn(
OPERATOR,
created.session_id,
"hello?".to_owned(),
None,
None,
),
)
.await?
{
Err(AssistantSessionError::Ended { reason, .. }) => {
assert_eq!(reason, ENDED_IS_TERMINAL);
}
other => {
return Err(format!(
"a turn on an ended session is refused by name before any spawn, got {other:?}"
));
}
}
let rows_after = store
.assistant_transcript(&created.session_id, None)
.await
.map_err(|error| error.to_string())?
.len();
assert_eq!(
rows_after,
frames_before + 1,
"a refused turn on an ended session records exactly its own failure"
);
let frames_after = sessions
.transcript_from(created.session_id, None)
.await
.map_err(|error| error.to_string())?;
match frames_after.last().map(|frame| &frame.event) {
Some(AssistantSessionEvent::TurnFailed { message, .. }) => {
assert!(
message.contains(ENDED_IS_TERMINAL),
"the failure frame names the reason: {message}"
);
}
other => {
return Err(format!(
"the last frame is the refused turn's failure, got {other:?}"
));
}
}
assert!(
!frames_after
.iter()
.any(|frame| matches!(frame.event, AssistantSessionEvent::SessionOpened { .. })),
"a refused turn on an ended session spawns nothing: {frames_after:?}"
);
match sessions
.settle(created.session_id, AssistantSessionState::Live, "resurrect")
.await
{
Err(AssistantSessionError::Ended { reason, .. }) => assert_eq!(reason, DELETED),
other => {
return Err(format!(
"settling out of ended must be refused, got {other:?}"
));
}
}
let (state, _) = sessions
.state_of_session(created.session_id)
.await
.map_err(|error| error.to_string())?;
assert_eq!(state, AssistantSessionState::Ended, "still ended");
Ok(())
}
#[tokio::test]
async fn a_store_that_refuses_the_append_fails_the_turn_and_broadcasts_nothing() -> TestResult {
let store = Arc::new(RefusingTranscriptStore::new());
let sessions = registry_over(Arc::clone(&store) as Arc<dyn AssistantSessionStore>);
let created = open_session(&sessions).await;
match created {
Err(message) => {
assert!(
message.contains(RefusingTranscriptStore::REFUSAL),
"the refusal must reach the caller verbatim: {message}"
);
}
Ok(summary) => {
return Err(format!(
"a store that refuses every append must fail the opening it cannot record, got \
{summary:?}"
));
}
}
let listing = store
.list_assistant_sessions()
.await
.map_err(|error| error.to_string())?;
let record = listing
.sessions
.first()
.ok_or_else(|| {
"the create wrote its record before the append it could not make".to_owned()
})?
.clone();
let mut watcher = sessions.recorder(record.session_id).subscribe();
let refusal = within_bound(
"a turn on a store that refuses every append",
sessions.turn(OPERATOR, record.session_id, "hello".to_owned(), None, None),
)
.await?;
assert!(
refusal.is_err(),
"a turn whose frames cannot be recorded must fail rather than stream unrecorded frames"
);
assert!(
matches!(
watcher.try_recv(),
Err(tokio::sync::broadcast::error::TryRecvError::Empty)
),
"NOTHING may be broadcast when the append that would have justified it failed"
);
Ok(())
}
#[tokio::test]
async fn a_store_that_accepts_the_append_does_broadcast_what_it_took() -> TestResult {
let (sessions, _store) = registry();
let summary = open_session(&sessions).await?;
let mut watcher = sessions.recorder(summary.session_id).subscribe();
sessions
.recorder(summary.session_id)
.record(AssistantSessionEvent::Delta {
turn_id: "t-1".to_owned(),
text: "hello".to_owned(),
})
.await
.map_err(|error| error.to_string())?;
let frame = watcher
.try_recv()
.map_err(|error| format!("a committed frame must reach a subscriber: {error}"))?;
assert!(matches!(frame.event, AssistantSessionEvent::Delta { .. }));
Ok(())
}
#[tokio::test]
async fn the_operators_request_reaches_the_transcript_before_any_harness_frame() -> TestResult {
let (sessions, store) = registry();
let summary = open_session(&sessions).await?;
drop(
within_bound(
"a turn whose harness cannot start",
sessions.turn(
OPERATOR,
summary.session_id,
"fix the check".to_owned(),
None,
None,
),
)
.await?,
);
let frames = store
.assistant_transcript(&summary.session_id, None)
.await
.map_err(|error| error.to_string())?;
let decoded: Vec<AssistantSessionEvent> = frames
.iter()
.map(|frame| serde_json::from_slice(frame.payload.bytes()))
.collect::<Result<_, _>>()
.map_err(|error| error.to_string())?;
assert!(
decoded.iter().any(|event| matches!(
event,
AssistantSessionEvent::Request { text, .. } if text == "fix the check"
)),
"the operator's own words must be on the transcript: {decoded:?}"
);
Ok(())
}
#[tokio::test]
async fn a_command_the_harness_never_advertised_is_refused_naming_what_it_does_offer() -> TestResult
{
let (sessions, _store) = registry();
let summary = open_session(&sessions).await?;
sessions
.recorder(summary.session_id)
.record(AssistantSessionEvent::AvailableCommands {
commands: vec![AssistantCommand {
name: "compact".to_owned(),
description: "compact the conversation".to_owned(),
input_hint: None,
}],
})
.await
.map_err(|error| error.to_string())?;
let refusal = within_bound(
"a turn invoking an unadvertised command",
sessions.turn(
OPERATOR,
summary.session_id,
String::new(),
None,
Some(AssistantCommandInvocation {
name: "rewrite-everything".to_owned(),
input: None,
}),
),
)
.await?;
match refusal {
Err(AssistantSessionError::UnknownCommand {
requested,
advertised,
..
}) => {
assert_eq!(requested, "rewrite-everything");
assert!(
advertised.contains("compact"),
"the refusal must say what IS offered: {advertised}"
);
}
other => {
return Err(format!(
"an unadvertised command must be refused by name, got {other:?}"
));
}
}
Ok(())
}
#[tokio::test]
async fn an_advertised_command_passes_the_check_and_reaches_the_harness() -> TestResult {
let (sessions, store) = registry();
let summary = open_session(&sessions).await?;
sessions
.recorder(summary.session_id)
.record(AssistantSessionEvent::AvailableCommands {
commands: vec![AssistantCommand {
name: "compact".to_owned(),
description: "compact the conversation".to_owned(),
input_hint: Some("what to keep".to_owned()),
}],
})
.await
.map_err(|error| error.to_string())?;
let outcome = within_bound(
"a turn invoking an advertised command",
sessions.turn(
OPERATOR,
summary.session_id,
String::new(),
None,
Some(AssistantCommandInvocation {
name: "compact".to_owned(),
input: Some("keep the plan".to_owned()),
}),
),
)
.await?;
assert!(
!matches!(outcome, Err(AssistantSessionError::UnknownCommand { .. })),
"an advertised command must not be refused: {outcome:?}"
);
let frames = store
.assistant_transcript(&summary.session_id, None)
.await
.map_err(|error| error.to_string())?;
let decoded: Vec<AssistantSessionEvent> = frames
.iter()
.map(|frame| serde_json::from_slice(frame.payload.bytes()))
.collect::<Result<_, _>>()
.map_err(|error| error.to_string())?;
assert!(
decoded.iter().any(|event| matches!(
event,
AssistantSessionEvent::Request { text, command: Some(command), .. }
if text == "/compact keep the plan" && command.name == "compact"
)),
"the composed line AND the structured invocation must both be recorded: {decoded:?}"
);
Ok(())
}
#[tokio::test]
async fn the_records_command_cache_is_what_the_transcript_projects() -> TestResult {
let (sessions, store) = registry();
let summary = open_session(&sessions).await?;
let advertised = vec![
AssistantCommand {
name: "compact".to_owned(),
description: "compact the conversation".to_owned(),
input_hint: None,
},
AssistantCommand {
name: "plan".to_owned(),
description: "draft a plan".to_owned(),
input_hint: Some("what to plan".to_owned()),
},
];
sessions
.recorder(summary.session_id)
.record(AssistantSessionEvent::AvailableCommands {
commands: advertised.clone(),
})
.await
.map_err(|error| error.to_string())?;
let projected = sessions
.projection(summary.session_id)
.await
.map_err(|error| error.to_string())?
.commands;
let cached = store
.get_assistant_session(&summary.session_id)
.await
.map_err(|error| error.to_string())?
.ok_or_else(|| "the session record is present".to_owned())?
.commands;
assert_eq!(projected, advertised);
assert_eq!(
cached, projected,
"the record's cache and the transcript's projection are ONE answer"
);
Ok(())
}
#[tokio::test]
async fn the_current_session_is_the_newest_continuable_one_or_an_absence() -> TestResult {
let (sessions, _store) = registry();
assert_eq!(
sessions
.current(OPERATOR)
.await
.map_err(|error| error.to_string())?,
None,
"a caller who has started nothing holds no current session"
);
let first = open_session(&sessions).await?;
let second = open_session(&sessions).await?;
let current = sessions
.current(OPERATOR)
.await
.map_err(|error| error.to_string())?
.ok_or_else(|| "a caller with two open sessions holds a current one".to_owned())?;
assert_eq!(
current.session_id, second.session_id,
"the NEWEST continuable session is the current one"
);
sessions
.delete(OPERATOR, second.session_id)
.await
.map_err(|error| error.to_string())?;
let after = sessions
.current(OPERATOR)
.await
.map_err(|error| error.to_string())?
.ok_or_else(|| "the earlier session is still continuable".to_owned())?;
assert_eq!(after.session_id, first.session_id);
assert_eq!(
sessions
.current("somebody-else")
.await
.map_err(|error| error.to_string())?,
None,
"one caller's conversation must never be another's current session"
);
Ok(())
}
#[tokio::test]
async fn creating_a_session_starts_nothing_and_records_no_opening() -> TestResult {
let (sessions, _store) = registry();
let summary = open_session(&sessions).await?;
assert_eq!(
summary.state,
AssistantSessionState::Dormant,
"a conversation nobody has spoken in has no process"
);
assert!(
!sessions.is_live(summary.session_id).await,
"no harness process may exist before the first message"
);
let frames = sessions
.transcript_from(summary.session_id, None)
.await
.map_err(|error| error.to_string())?;
assert!(
!frames
.iter()
.any(|frame| matches!(frame.event, AssistantSessionEvent::SessionOpened { .. })),
"an opening on the transcript of a session nobody has spoken in means a process was \
started for it: {frames:?}"
);
Ok(())
}
#[tokio::test]
async fn a_turn_that_never_reached_an_agent_records_its_failure_and_no_turn_frames() -> TestResult {
let (sessions, store) = registry();
let summary = open_session(&sessions).await?;
let mut record = store
.get_assistant_session(&summary.session_id)
.await
.map_err(|error| error.to_string())?
.ok_or_else(|| "the opened session has a record".to_owned())?;
record.harness = "a-harness-this-build-does-not-ship".to_owned();
store
.put_assistant_session(record)
.await
.map_err(|error| error.to_string())?;
let refusal = within_bound(
"a turn on a session whose harness cannot be planned",
sessions.turn(OPERATOR, summary.session_id, "hello".to_owned(), None, None),
)
.await?;
match refusal {
Err(AssistantSessionError::UnknownHarness { requested, .. }) => {
assert_eq!(requested, "a-harness-this-build-does-not-ship");
}
other => {
return Err(format!(
"a turn that cannot plan a spawn must refuse by name, got {other:?}"
));
}
}
let frames = sessions
.transcript_from(summary.session_id, None)
.await
.map_err(|error| error.to_string())?;
assert!(
!frames.iter().any(|frame| matches!(
frame.event,
AssistantSessionEvent::Request { .. } | AssistantSessionEvent::TurnStarted { .. }
)),
"a turn that never reached an agent must not have written a request or a start: {frames:?}"
);
let failed = frames
.iter()
.find_map(|frame| match &frame.event {
AssistantSessionEvent::TurnFailed { code, message, .. } => {
Some((code.clone(), message.clone()))
}
_ => None,
})
.ok_or_else(|| format!("the failure must reach the transcript: {frames:?}"))?;
assert_eq!(failed.0, "unknown_harness");
assert!(
failed.1.contains("a-harness-this-build-does-not-ship"),
"the recorded failure names what could not be run: {}",
failed.1
);
Ok(())
}
#[tokio::test]
async fn a_store_the_boot_sweep_cannot_read_takes_the_surface_down_by_name() -> TestResult {
let (sessions, _store) = registry();
assert!(
sessions.availability().is_available(),
"a stock server serves the assistant before anything has gone wrong"
);
let refusing = registry_over(Arc::new(RefusingTranscriptStore::new()));
let settled = open_session(&refusing).await;
assert!(
settled.is_err(),
"this instrument's store refuses every append"
);
refusing.report_store_fault(&AssistantSessionError::Store(
aion_store::StoreError::Backend(RefusingTranscriptStore::REFUSAL.to_owned()),
));
let reason = refusing
.availability()
.reason()
.ok_or_else(|| "a server with an unusable store must say so".to_owned())?
.to_owned();
assert!(
reason.contains(RefusingTranscriptStore::REFUSAL),
"the reason carries the store's own error: {reason}"
);
assert!(
!reason.contains("[assistant]"),
"`not configured` is never the reason any more: {reason}"
);
match refusing.create(OPERATOR, None, None, None).await {
Err(AssistantSessionError::NotCommissioned { reason: refused }) => {
assert_eq!(refused, reason);
Ok(())
}
other => Err(format!(
"a server that cannot record a conversation must refuse to open one, got {other:?}"
)),
}
}
#[tokio::test]
async fn the_turn_bookkeeping_never_restores_a_pre_spawn_token_digest() -> TestResult {
let (sessions, store) = registry();
let summary = open_session(&sessions).await?;
let session_id = summary.session_id;
let handed = "the-digest-of-the-secret-the-child-holds".to_owned();
let mut spawned = store
.get_assistant_session(&session_id)
.await
.map_err(|error| error.to_string())?
.ok_or("the opened session's record is missing before the spawn write")?;
spawned.mcp_token_digest = Some(handed.clone());
store
.put_assistant_session(spawned)
.await
.map_err(|error| error.to_string())?;
sessions
.touch(session_id, Some("first words of the conversation"))
.await
.map_err(|error| error.to_string())?;
let held = store
.get_assistant_session(&session_id)
.await
.map_err(|error| error.to_string())?
.ok_or("the session's record is missing after the bookkeeping write")?;
if held.mcp_token_digest.as_deref() != Some(handed.as_str()) {
return Err(format!(
"the bookkeeping write restored a pre-spawn token digest: the child holds a secret \
for {handed:?} and the store now says {:?}",
held.mcp_token_digest
));
}
if held.turns != 1 {
return Err(format!(
"the bookkeeping did not run: expected 1 recorded turn, found {}",
held.turns
));
}
if held.title.as_deref() != Some("first words of the conversation") {
return Err(format!(
"the first turn's words did not become the title: found {:?}",
held.title
));
}
Ok(())
}