blob: 69aef84ccadc63f44ede94620d187acc883fa93e [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.
*/
use crate::{
StoreError,
context::{self, selection::Selection},
};
use maka_agent_facts::continuation::ContinuationClaim;
use maka_agent_facts::event::Fact;
use maka_agent_facts::event::InvocationInput;
use maka_agent_facts::event::InvocationOutcome;
use maka_agent_facts::event::RuntimeEvent;
use maka_agent_facts::handoff::HandoffPause;
use sqlx::SqliteConnection;
/// Relocation concerns root positions only; Run-local ordinals and provider
/// replay identities belong to different contracts and are never translated.
pub(super) struct Positions {
source: Vec<u64>,
offset: u64,
}
impl Positions {
pub fn high_water(&self) -> u64 {
self.offset + self.source.len() as u64
}
pub async fn read(staged: &mut SqliteConnection, offset: u64) -> Result<Self, StoreError> {
let rows: Vec<i64> = sqlx::query_scalar(
"SELECT source_sequence FROM frames WHERE kind='event' ORDER BY source_sequence",
)
.fetch_all(staged)
.await?;
if offset
.checked_add(rows.len() as u64)
.is_none_or(|n| n >= i64::MAX as u64)
{
return Err(StoreError::PrefixTooLarge);
}
Ok(Self {
source: rows
.into_iter()
.map(crate::sequence_number)
.collect::<Result<_, _>>()?,
offset,
})
}
pub fn event(&self, source: u64) -> Result<u64, StoreError> {
self.source
.binary_search(&source)
.map(|index| self.offset + index as u64 + 1)
.map_err(|_| invalid("relocation lacks a referenced event"))
}
pub fn fence(&self, source: u64) -> u64 {
let rank = self.source.partition_point(|sequence| *sequence <= source);
if rank == 0 {
0
} else {
self.offset + rank as u64
}
}
/// Called in chronological order against already relocated predecessors.
/// Unaffected facts retain their exact source bytes.
pub async fn json(
&self,
db: &mut SqliteConnection,
sequence: u64,
json: String,
) -> Result<String, StoreError> {
let mut event: RuntimeEvent = serde_json::from_str(&json)?;
match &mut event.fact {
Fact::InvocationOpened { input, .. } => match input {
InvocationInput::Continuation { claim, .. } => self.claim(db, claim).await?,
InvocationInput::Handoff { claim, pause } => {
self.claim(db, claim).await?;
self.pause(pause);
}
_ => return Ok(json),
},
Fact::InvocationEnded {
outcome: InvocationOutcome::HandoffPaused { pause },
} => {
self.pause(pause);
}
Fact::ModelRequested {
source_scope,
source_high_water,
source_digest,
effective_source_digest,
..
} => {
*source_high_water = self.event_or_zero(*source_high_water)?;
let selection = Selection::resolve(db, source_scope).await?;
let evidence =
context::evidence::selected(db, &selection, *source_high_water).await?;
*source_digest = evidence.digest.clone();
*effective_source_digest = Some(
crate::archive::digest_selected(db, &selection, &evidence, sequence).await?,
);
}
Fact::ContextCheckpointRecorded { checkpoint } => {
checkpoint.covered_through = self.event_or_zero(checkpoint.covered_through)?;
let selection = Selection::for_invocation(db, &event.invocation).await?;
checkpoint.source_digest =
context::evidence::selected(db, &selection, checkpoint.covered_through)
.await?
.digest;
}
_ => return Ok(json),
}
Ok(serde_json::to_string(&event)?)
}
fn event_or_zero(&self, source: u64) -> Result<u64, StoreError> {
if source == 0 {
Ok(0)
} else {
self.event(source)
}
}
fn pause(&self, pause: &mut HandoffPause) {
pause.execution.replay_base = pause.execution.replay_base.map(|n| self.fence(n));
}
async fn claim(
&self,
db: &mut SqliteConnection,
claim: &mut ContinuationClaim,
) -> Result<(), StoreError> {
claim.base.high_water = self.event_or_zero(claim.base.high_water)?;
claim.base.digest = context::evidence::selected(
db,
&Selection::session(&claim.source.invocation.session_id),
claim.base.high_water,
)
.await?
.digest;
let source = &claim.source.invocation;
let through: i64 = sqlx::query_scalar(
"SELECT sequence FROM runtime_events WHERE invocation_id=? ORDER BY sequence LIMIT 1 OFFSET ?",
).bind(&source.invocation_id).bind((claim.source.high_water - 1) as i64)
.fetch_one(&mut *db).await?;
claim.source.digest = crate::run_source::read(
db,
&source.session_id,
&source.run_id,
&source.invocation_id,
through,
)
.await?
.boundary
.digest;
Ok(())
}
}
fn invalid(message: &str) -> StoreError {
StoreError::InvalidTransition(message.into())
}