blob: a3ca2271a8402d8f61ab28f487927172a27e809d [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
//! A physical Run can seal without ending its logical Turn. Only the sealed
//! facts authorize the named successor; an in-memory reservation does not.
use crate::{event::Invocation, interaction::entity_id};
use serde::{Deserialize, Serialize};
mod execution;
pub use execution::{CompactionBudget, HandoffExecution, HandoffTools};
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct HandoffIntent {
pub handoff_id: String,
pub host_epoch: String,
pub root_run_id: String,
pub successor_run_id: String,
pub successor_invocation_id: String,
pub claim_id: String,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct HandoffPause {
pub intent: HandoffIntent,
pub execution: Box<HandoffExecution>,
}
impl HandoffIntent {
pub fn validate(&self, source: &Invocation) -> Result<(), &'static str> {
for id in [
&self.handoff_id,
&self.host_epoch,
&self.root_run_id,
&self.successor_run_id,
&self.successor_invocation_id,
&self.claim_id,
] {
entity_id(id)?;
}
if self.successor_run_id == self.root_run_id
|| [&source.run_id, &source.invocation_id].contains(&&self.successor_run_id)
|| [&source.run_id, &source.invocation_id].contains(&&self.successor_invocation_id)
|| self.successor_run_id == self.successor_invocation_id
{
return Err("handoff must name a fresh physical Run and invocation");
}
Ok(())
}
pub fn successor(&self, source: &Invocation) -> Invocation {
Invocation {
session_id: source.session_id.clone(),
turn_id: source.turn_id.clone(),
run_id: self.successor_run_id.clone(),
invocation_id: self.successor_invocation_id.clone(),
}
}
}
impl HandoffPause {
pub fn validate_claim(
&self,
claim: &crate::continuation::ContinuationClaim,
target: &Invocation,
) -> Result<(), &'static str> {
claim.validate_boundary(target)?;
self.validate(&claim.source.invocation)?;
if claim.id != self.intent.claim_id
|| claim.replay != self.execution.replay
|| self
.execution
.replay_base
.is_some_and(|base| base != claim.base.high_water)
|| *target != self.intent.successor(&claim.source.invocation)
{
return Err("handoff must acquire its reserved successor in the same Turn");
}
Ok(())
}
pub fn validate(&self, source: &Invocation) -> Result<(), &'static str> {
self.intent.validate(source)?;
self.execution.validate()?;
Ok(())
}
}