use async_trait::async_trait;
use runtime_api_contract::{
CreateExecutionRequest, CreateExecutionResponse, EventPage, EventPayload, ExecutionEvent,
ExecutionFailure, ExecutionOutcome, ExecutionState, ExecutionView, SubmitInputRequest,
};
use runtime_types::{CallerScope, DelegationLeaseRef, ExecutionId, OperationId, RequestAuthority};
use thiserror::Error;
#[async_trait]
pub trait RuntimeLink: Send + Sync + 'static {
async fn create_execution(
&self,
authority: &RequestAuthority,
idempotency_key: &str,
request: CreateExecutionRequest,
) -> Result<CreateExecutionResponse, LinkError>;
async fn execution(
&self,
authority: &RequestAuthority,
id: &ExecutionId,
) -> Result<ExecutionView, LinkError>;
async fn events(
&self,
authority: &RequestAuthority,
id: &ExecutionId,
after: Option<u64>,
limit: usize,
) -> Result<EventPage, LinkError>;
async fn submit_input(
&self,
authority: &RequestAuthority,
id: &ExecutionId,
request: SubmitInputRequest,
) -> Result<ExecutionView, LinkError>;
async fn cancel(
&self,
authority: &RequestAuthority,
id: &ExecutionId,
) -> Result<ExecutionView, LinkError>;
}
#[derive(Debug, Error)]
pub enum LinkError {
#[error("invalid request: {0}")]
Invalid(String),
#[error("execution not found")]
NotFound,
#[error("caller is not allowed to access this execution")]
Forbidden,
#[error("execution state conflict: {0}")]
Conflict(String),
#[error("runtime is overloaded")]
Overloaded,
#[error("runtime dependency unavailable: {0}")]
Unavailable(String),
#[error("runtime internal failure: {0}")]
Internal(String),
}
#[derive(Debug, Clone)]
pub struct NewJournalExecution {
pub idempotency_key: String,
pub caller: CallerScope,
pub request: CreateExecutionRequest,
pub view: ExecutionView,
}
#[derive(Debug, Clone)]
pub struct JournalExecution {
pub idempotency_key: String,
pub caller: CallerScope,
pub request: CreateExecutionRequest,
pub view: ExecutionView,
pub version: u64,
pub pending_interaction: Option<JournalInteraction>,
pub input_receipts: std::collections::BTreeMap<OperationId, JournalInputReceipt>,
pub delegation: Option<JournalDelegation>,
pub claim: Option<JournalClaim>,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct JournalClaim {
pub worker_id: String,
pub expires_at_ms: i64,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct JournalDelegation {
pub lease_ref: DelegationLeaseRef,
pub expires_at_seconds: u64,
pub revision: u64,
pub cleanup_complete: bool,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct JournalInteraction {
pub request_id: String,
pub prompt: String,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct JournalInputReceipt {
pub request_id: String,
pub response_text: String,
pub event_sequence: u64,
}
#[derive(Debug, Clone)]
pub struct JournalMutation {
pub execution: JournalExecution,
pub event: ExecutionEvent,
pub replayed: bool,
}
#[derive(Debug, Clone)]
pub enum JournalReservation {
Created(JournalExecution),
Existing(JournalExecution),
}
#[derive(Debug, Error)]
pub enum JournalError {
#[error("execution not found")]
NotFound,
#[error("journal state conflict")]
Conflict,
#[error("journal unavailable: {0}")]
Unavailable(String),
}
#[async_trait]
pub trait ExecutionJournal: Send + Sync + 'static {
async fn reserve(
&self,
execution: NewJournalExecution,
) -> Result<JournalReservation, JournalError>;
async fn get(&self, id: &ExecutionId) -> Result<JournalExecution, JournalError>;
async fn claim(
&self,
id: &ExecutionId,
worker_id: &str,
now_ms: i64,
expires_at_ms: i64,
) -> Result<JournalExecution, JournalError>;
async fn recoverable(
&self,
now_ms: i64,
limit: usize,
) -> Result<Vec<JournalExecution>, JournalError>;
async fn transition(
&self,
id: &ExecutionId,
expected: &[ExecutionState],
next: ExecutionState,
) -> Result<JournalExecution, JournalError>;
async fn attach_delegation(
&self,
id: &ExecutionId,
delegation: JournalDelegation,
) -> Result<JournalExecution, JournalError>;
async fn complete_delegation_cleanup(
&self,
id: &ExecutionId,
lease_ref: &DelegationLeaseRef,
) -> Result<JournalExecution, JournalError>;
async fn finish(
&self,
id: &ExecutionId,
expected: &[ExecutionState],
next: ExecutionState,
outcome: Option<ExecutionOutcome>,
failure: Option<ExecutionFailure>,
) -> Result<JournalExecution, JournalError>;
async fn transition_with_event(
&self,
id: &ExecutionId,
expected: &[ExecutionState],
next: ExecutionState,
outcome: Option<ExecutionOutcome>,
failure: Option<ExecutionFailure>,
payload: EventPayload,
) -> Result<JournalMutation, JournalError>;
async fn append_event(
&self,
id: &ExecutionId,
payload: EventPayload,
) -> Result<ExecutionEvent, JournalError>;
async fn begin_interaction(
&self,
id: &ExecutionId,
request_id: &str,
prompt: &str,
) -> Result<JournalMutation, JournalError>;
async fn commit_interaction_input(
&self,
id: &ExecutionId,
operation_id: &OperationId,
request_id: &str,
response_text: &str,
) -> Result<JournalMutation, JournalError>;
async fn complete_interaction(
&self,
id: &ExecutionId,
request_id: &str,
) -> Result<JournalExecution, JournalError>;
async fn events(
&self,
id: &ExecutionId,
after: Option<u64>,
limit: usize,
) -> Result<Vec<ExecutionEvent>, JournalError>;
}