use crate::contracts::{ActionEnvelope, ControlLease, ExecutionReceipt, TargetReservation};
use crate::error::ComputerUseError;
pub fn validate_envelope_freshness(envelope: &ActionEnvelope) -> Result<(), ComputerUseError> {
let proposed_at =
chrono::DateTime::parse_from_rfc3339(&envelope.proposed_at).map_err(|error| {
ComputerUseError::IdentityMismatch(format!(
"action envelope {} has an unreadable proposed_at {:?}: {error}",
envelope.action_id, envelope.proposed_at
))
})?;
let expires_at =
chrono::DateTime::parse_from_rfc3339(&envelope.expires_at).map_err(|error| {
ComputerUseError::IdentityMismatch(format!(
"action envelope {} has an unreadable expires_at {:?}: {error}",
envelope.action_id, envelope.expires_at
))
})?;
if expires_at <= proposed_at {
return Err(ComputerUseError::IdentityMismatch(format!(
"action envelope {} has a non-positive validity window: proposed_at is {} and \
expires_at is {}",
envelope.action_id, envelope.proposed_at, envelope.expires_at
)));
}
if expires_at <= chrono::Utc::now() {
return Err(ComputerUseError::IdentityMismatch(format!(
"action envelope {} expired at {}",
envelope.action_id, envelope.expires_at
)));
}
Ok(())
}
fn mismatch(object: &str, field: &str, expected: &str, actual: &str) -> ComputerUseError {
ComputerUseError::IdentityMismatch(format!(
"{object} returned by the computer-use runtime is not bound to this request: {field} \
is {actual:?}, expected {expected:?}. The response was rejected rather than stored."
))
}
fn check_optional(
object: &str,
field: &str,
expected: Option<&str>,
actual: Option<&str>,
) -> Result<(), ComputerUseError> {
match (expected, actual) {
(Some(expected), Some(actual)) if expected != actual => {
Err(mismatch(object, field, expected, actual))
}
_ => Ok(()),
}
}
pub fn validate_lease(
lease: &ControlLease,
envelope: &ActionEnvelope,
) -> Result<(), ComputerUseError> {
const OBJECT: &str = "control lease";
if lease.session_id != envelope.session_id {
return Err(mismatch(OBJECT, "session_id", &envelope.session_id, &lease.session_id));
}
if lease.principal_id != envelope.principal_id {
return Err(mismatch(OBJECT, "principal_id", &envelope.principal_id, &lease.principal_id));
}
check_optional(OBJECT, "agent_id", envelope.agent_id.as_deref(), lease.agent_id.as_deref())?;
if lease.execution_mode != envelope.requested_mode {
return Err(mismatch(
OBJECT,
"execution_mode",
&format!("{:?}", envelope.requested_mode),
&format!("{:?}", lease.execution_mode),
));
}
if !lease.state.eq_ignore_ascii_case("active") {
return Err(ComputerUseError::IdentityMismatch(format!(
"control lease {} is in state {:?}, not active, so it authorizes nothing",
lease.lease_id, lease.state
)));
}
if lease.actions_used >= lease.action_budget {
return Err(ComputerUseError::IdentityMismatch(format!(
"control lease {} has no remaining action budget: {} of {} used",
lease.lease_id, lease.actions_used, lease.action_budget
)));
}
match chrono::DateTime::parse_from_rfc3339(&lease.expires_at) {
Ok(expires_at) => {
if expires_at <= chrono::Utc::now() {
return Err(ComputerUseError::IdentityMismatch(format!(
"control lease {} expired at {}",
lease.lease_id, lease.expires_at
)));
}
}
Err(e) => {
return Err(ComputerUseError::IdentityMismatch(format!(
"control lease {} has an unreadable expiry {:?}: {e}",
lease.lease_id, lease.expires_at
)));
}
}
if let Some(target) = &envelope.target {
if !lease.boundaries.app_ids.is_empty()
&& !lease.boundaries.app_ids.contains(&target.app_id)
{
return Err(mismatch(
OBJECT,
"boundaries.app_ids",
&target.app_id,
&format!("{:?}", lease.boundaries.app_ids),
));
}
if let Some(window_id) = &target.window_id
&& !lease.boundaries.window_ids.is_empty()
&& !lease.boundaries.window_ids.contains(window_id)
{
return Err(mismatch(
OBJECT,
"boundaries.window_ids",
&format!("{window_id}"),
&format!("{:?}", lease.boundaries.window_ids),
));
}
}
Ok(())
}
pub fn validate_reservation(
reservation: &TargetReservation,
envelope: &ActionEnvelope,
) -> Result<(), ComputerUseError> {
const OBJECT: &str = "target reservation";
if reservation.session_id != envelope.session_id {
return Err(mismatch(OBJECT, "session_id", &envelope.session_id, &reservation.session_id));
}
if reservation.principal_id != envelope.principal_id {
return Err(mismatch(
OBJECT,
"principal_id",
&envelope.principal_id,
&reservation.principal_id,
));
}
check_optional(
OBJECT,
"agent_id",
envelope.agent_id.as_deref(),
reservation.agent_id.as_deref(),
)?;
check_optional(
OBJECT,
"execution_group_id",
envelope.execution_group_id.as_deref(),
reservation.execution_group_id.as_deref(),
)?;
if reservation.intent_id != envelope.action_id {
return Err(mismatch(OBJECT, "intent_id", &envelope.action_id, &reservation.intent_id));
}
if !reservation.state.eq_ignore_ascii_case("active") {
return Err(ComputerUseError::IdentityMismatch(format!(
"target reservation {} is in state {:?}, not active",
reservation.reservation_id, reservation.state
)));
}
match chrono::DateTime::parse_from_rfc3339(&reservation.expires_at) {
Ok(expires_at) => {
if expires_at <= chrono::Utc::now() {
return Err(ComputerUseError::IdentityMismatch(format!(
"target reservation {} expired at {}",
reservation.reservation_id, reservation.expires_at
)));
}
}
Err(error) => {
return Err(ComputerUseError::IdentityMismatch(format!(
"target reservation {} has an unreadable expiry {:?}: {error}",
reservation.reservation_id, reservation.expires_at
)));
}
}
let target = envelope.target.as_ref().ok_or_else(|| {
ComputerUseError::IdentityMismatch(format!(
"target reservation {} was returned for action {} without target evidence",
reservation.reservation_id, envelope.action_id
))
})?;
if reservation.scope.app_id != target.app_id {
return Err(mismatch(OBJECT, "scope.app_id", &target.app_id, &reservation.scope.app_id));
}
if reservation.scope.window_id != target.window_id {
return Err(mismatch(
OBJECT,
"scope.window_id",
&format!("{:?}", target.window_id),
&format!("{:?}", reservation.scope.window_id),
));
}
Ok(())
}
pub fn validate_receipt(
receipt: &ExecutionReceipt,
envelope: &ActionEnvelope,
expected_digest: &str,
) -> Result<(), ComputerUseError> {
const OBJECT: &str = "execution receipt";
if receipt.session_id != envelope.session_id {
return Err(mismatch(OBJECT, "session_id", &envelope.session_id, &receipt.session_id));
}
if receipt.action_id != envelope.action_id {
return Err(mismatch(OBJECT, "action_id", &envelope.action_id, &receipt.action_id));
}
if !expected_digest.is_empty() && receipt.action_digest != expected_digest {
return Err(mismatch(OBJECT, "action_digest", expected_digest, &receipt.action_digest));
}
Ok(())
}