tea-session 0.1.0

Append-only session log, replay, and storage contracts for tea-rs
Documentation
use crate::common;

use common::{TOOL_CALL_ID, approval_flow, creation, envelope};
use serde_json::json;
use std::str::FromStr;
use tea_protocol::{RunId, ToolCallId};
use tea_session::{RunRecoveryState, SessionReducer, ToolExecutionState};

const RUN_ID: &str = "0195a0b1-5e40-7136-8ae0-0aa7aa000006";
const TURN_ID: &str = "0195a0b1-5e42-7b38-af7c-0aa7aa000008";

#[test]
fn checkpoint_and_run_interruption_are_reconstructed() {
    let state = SessionReducer::replay([
        creation(),
        envelope(
            1,
            "turn_checkpointed",
            json!({"runId":RUN_ID,"turnId":TURN_ID,"nextAction":"model_request"}),
        ),
        envelope(
            2,
            "run_interrupted",
            json!({"runId":RUN_ID,"turnId":TURN_ID,"reason":"provider stream ended"}),
        ),
    ])
    .unwrap();
    assert_eq!(state.latest_checkpoint().unwrap().sequence().get(), 1);
    assert!(matches!(
        state.run_recovery()[&RunId::from_str(RUN_ID).unwrap()],
        RunRecoveryState::Interrupted { .. }
    ));
}

#[test]
fn started_then_interrupted_tool_has_uncertain_outcome() {
    let mut records = approval_flow();
    records.extend([
        envelope(
            6,
            "approval_resolved",
            json!({"approvalId":common::APPROVAL_ID,"decision":{"type":"allow_once"}}),
        ),
        envelope(
            7,
            "tool_execution_started",
            json!({
                "toolCallId":TOOL_CALL_ID,
                "executionTarget":"native",
                "idempotency":"non_idempotent"
            }),
        ),
        envelope(
            8,
            "tool_execution_interrupted",
            json!({"toolCallId":TOOL_CALL_ID,"reason":"process terminated"}),
        ),
    ]);
    let state = SessionReducer::replay(records).unwrap();
    assert!(matches!(
        state.tool_calls()[&ToolCallId::from_str(TOOL_CALL_ID).unwrap()].execution(),
        ToolExecutionState::Interrupted { .. }
    ));
}

#[test]
fn duplicate_run_terminal_fails_closed() {
    let error = SessionReducer::replay([
        creation(),
        envelope(1, "run_cancelled", json!({"runId":RUN_ID})),
        envelope(
            2,
            "run_interrupted",
            json!({"runId":RUN_ID,"turnId":TURN_ID,"reason":"late interruption"}),
        ),
    ])
    .unwrap_err();
    assert!(error.to_string().contains("duplicate_run_terminal"));
}