blob: 2b0eaf2d872071418355eefaa6f605f25d27f325 [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.
*/
//! Durable continuation identity. The opening itself acquires the claim;
//! a query or an in-memory plan has no execution authority.
use crate::{archive::valid_projection_digest, event::Invocation, interaction::entity_id};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RunBoundary {
pub invocation: Invocation,
/// Run-local ordinal, never a root ledger sequence.
pub high_water: u64,
pub digest: String,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SessionBase {
/// Root ledger position in the source's Session, before the first fresh Run.
pub high_water: u64,
pub digest: String,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ContinuationClaim {
pub id: String,
pub source: RunBoundary,
pub base: SessionBase,
/// Engine-owned replay evidence over canonical input and registered tools.
/// ModelRequested records the actual request after live tool binding.
pub replay: ReplayEvidence,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ReplayEvidence {
pub version: u32,
pub digest: String,
pub route_identity: String,
}
pub const REPLAY_VERSION: u32 = 1;
impl ReplayEvidence {
pub(crate) fn validate(&self) -> Result<(), &'static str> {
if self.version != REPLAY_VERSION
|| !valid_projection_digest(&self.digest)
|| !valid_projection_digest(&self.route_identity)
{
return Err("invalid provider replay evidence");
}
Ok(())
}
}
impl ContinuationClaim {
pub fn validate(&self, target: &Invocation) -> Result<(), &'static str> {
self.validate_boundary(target)?;
if self.source.invocation.turn_id == target.turn_id {
return Err("manual continuation requires a fresh Turn");
}
Ok(())
}
pub(crate) fn validate_boundary(&self, target: &Invocation) -> Result<(), &'static str> {
entity_id(&self.id)?;
for invocation in [&self.source.invocation, target] {
for id in [
&invocation.session_id,
&invocation.turn_id,
&invocation.run_id,
&invocation.invocation_id,
] {
entity_id(id)?;
}
}
let source = &self.source.invocation;
if source.session_id != target.session_id
|| source.run_id == target.run_id
|| source.invocation_id == target.invocation_id
{
return Err("continuation requires fresh Run and Invocation in the same Session");
}
if self.source.high_water == 0
|| self.source.high_water > crate::configuration::validation::MAX_SAFE_INTEGER
|| self.base.high_water >= i64::MAX as u64
|| !valid_projection_digest(&self.source.digest)
|| !valid_projection_digest(&self.base.digest)
{
return Err("invalid continuation boundary or replay evidence");
}
self.replay.validate()
}
}