blob: 19a15e8e03969d2260ae8283bffacb4900f96d58 [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.
*/
//! Owner-local certificates for exact committed history. No permissions or
//! rendered data are retained here.
use super::selection::Selection;
use crate::{StoreError, connection::WriterIngress};
use sqlx::{Decode, Sqlite, SqliteConnection, sqlite::SqliteOperation};
use std::collections::HashMap;
use std::sync::{
Arc, Mutex,
atomic::{AtomicI64, AtomicU64, Ordering},
};
#[derive(Default)]
pub(crate) struct Cache {
#[cfg(test)]
anchor_hits: std::sync::atomic::AtomicUsize,
#[cfg(test)]
validated_links: std::sync::atomic::AtomicUsize,
#[cfg(test)]
stamps: std::sync::atomic::AtomicUsize,
#[cfg(test)]
raw_reuses: std::sync::atomic::AtomicUsize,
generation: AtomicU64,
frontier: AtomicI64,
certificates: Mutex<HashMap<String, Certificate>>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct Stamp {
version: i64,
main_schema: i64,
temp_schema: i64,
failures: u64,
generation: u64,
}
#[derive(Clone)]
pub(crate) struct Certificate {
pub selection: Selection,
pub stamp: Stamp,
pub raw: Option<Raw>,
pub checkpoint: Option<Checkpoint>,
}
#[derive(Clone, serde::Serialize, serde::Deserialize)]
pub(crate) struct Raw {
pub through: u64,
pub state: Vec<u8>,
}
#[derive(Clone)]
pub(crate) struct Checkpoint {
pub id: String,
pub sequence: u64,
pub covered_through: u64,
}
impl Cache {
/// Presence only selects an optimization attempt; fresh stamps and the
/// transaction's real Selection still authenticate every consumed proof.
pub(crate) fn has_proof(&self, session: &str) -> Option<bool> {
self.certificates.lock().ok().map(|entries| {
entries
.get(session)
.is_some_and(|entry| entry.raw.is_some() || entry.checkpoint.is_some())
})
}
pub(crate) fn has_anchor(&self, session: &str) -> bool {
self.certificates.lock().ok().is_some_and(|entries| {
entries
.get(session)
.is_some_and(|entry| entry.checkpoint.is_some())
})
}
pub(crate) fn has_raw(&self, session: &str) -> bool {
self.certificates.lock().ok().is_some_and(|entries| {
entries
.get(session)
.is_some_and(|entry| entry.raw.is_some())
})
}
pub(crate) async fn install(
self: &Arc<Self>,
connection: &mut SqliteConnection,
) -> Result<(), StoreError> {
let observed = self.clone();
let mut handle = connection.lock_handle().await?;
handle.set_preupdate_hook(move |change| {
if change.database != "main" {
return;
}
match change.table {
"session_history_members" => observed.invalidate(),
"event_log" => {
if change.operation != SqliteOperation::Insert {
observed.invalidate();
return;
}
let Ok(row) = change.get_new_row_id() else {
observed.invalidate();
return;
};
// Advance on every observed INSERT, even without a certificate.
// An out-of-order insertion can change an already pinned cut.
if row <= observed.frontier.fetch_max(row, Ordering::AcqRel) {
observed.invalidate();
}
let kind = change
.get_new_column_value(3)
.ok()
.and_then(|value| <&str as Decode<Sqlite>>::decode(value).ok());
if kind.is_none_or(|kind| kind == "tool_result_archived") {
observed.invalidate();
}
}
_ => {}
}
});
Ok(())
}
pub(crate) fn invalidate(&self) {
self.generation.fetch_add(1, Ordering::AcqRel);
}
pub(crate) async fn stamp(
&self,
connection: &mut SqliteConnection,
failures: u64,
) -> Result<Stamp, StoreError> {
#[cfg(test)]
self.stamps.fetch_add(1, Ordering::Relaxed);
// SAFETY: the live SQLx handle remains locked during this synchronous
// SQLite transaction-state observation.
let autocommit = unsafe {
rusqlite::ffi::sqlite3_get_autocommit(
connection.lock_handle().await?.as_raw_handle().as_ptr(),
)
};
if autocommit == 0 {
self.invalidate();
return Err(StoreError::OperationUnknown);
}
let stamp = self.current_stamp(connection, failures).await?;
let maximum: i64 = sqlx::query_scalar("SELECT COALESCE(MAX(sequence),0) FROM event_log")
.fetch_one(connection)
.await?;
// Only fresh writer boundaries rebase after rollback/external commits.
self.frontier.store(maximum, Ordering::Release);
Ok(stamp)
}
async fn current_stamp(
&self,
connection: &mut SqliteConnection,
failures: u64,
) -> Result<Stamp, StoreError> {
let version = sqlx::query_scalar("PRAGMA main.data_version")
.fetch_one(&mut *connection)
.await?;
let main_schema = sqlx::query_scalar("PRAGMA main.schema_version")
.fetch_one(&mut *connection)
.await?;
let temp_schema = sqlx::query_scalar("PRAGMA temp.schema_version")
.fetch_one(connection)
.await?;
Ok(Stamp {
version,
main_schema,
temp_schema,
failures,
generation: self.generation.load(Ordering::Acquire),
})
}
/// BEGIN IMMEDIATE has pinned and locked the append snapshot. Confirm the
/// original autocommit observation; never re-label its certificate here.
pub(crate) async fn confirm_append(
&self,
connection: &mut SqliteConnection,
origin: &Stamp,
failures: u64,
) -> Result<bool, StoreError> {
Ok(origin == &self.current_stamp(connection, failures).await?)
}
pub(crate) fn get(
&self,
session: &str,
stamp: &Stamp,
) -> Result<Option<Certificate>, StoreError> {
let mut certificates = self
.certificates
.lock()
.map_err(|_| StoreError::OperationUnknown)?;
if certificates
.get(session)
.is_some_and(|certificate| &certificate.stamp != stamp)
{
certificates.remove(session);
}
Ok(certificates.get(session).cloned())
}
pub(crate) fn publish(&self, candidate: Certificate) -> Result<(), StoreError> {
let Some(session) = candidate.selection.session.clone() else {
return Ok(());
};
let mut certificates = self
.certificates
.lock()
.map_err(|_| StoreError::OperationUnknown)?;
if let Some(current) = certificates.get(&session)
&& current.stamp == candidate.stamp
&& current.selection == candidate.selection
&& (current
.checkpoint
.as_ref()
.map_or(0, |value| value.sequence)
> candidate
.checkpoint
.as_ref()
.map_or(0, |value| value.sequence)
|| current.raw.as_ref().map_or(0, |value| value.through)
> candidate.raw.as_ref().map_or(0, |value| value.through))
{
return Ok(());
}
certificates.insert(session, candidate);
Ok(())
}
}
pub(crate) async fn observe(
cache: Arc<Cache>,
writer: &WriterIngress,
session: String,
) -> Result<(Stamp, Option<Certificate>), StoreError> {
let ingress = writer.clone();
writer
.run(move |connection| {
Box::pin(async move {
let stamp = cache.stamp(connection, ingress.failures()).await?;
let certificate = cache.get(&session, &stamp)?;
Ok((stamp, certificate))
})
})
.await
}
/// Transaction-local scratch. Only an authenticated reader handshake or the
/// audited append entry constructs this; ordinary run validators use Default.
#[derive(Default)]
pub(crate) struct Verification {
cache: Option<Arc<Cache>>,
origin: Option<Stamp>,
candidate: Option<Certificate>,
}
impl Verification {
pub(crate) fn new(cache: Arc<Cache>, origin: Stamp, certificate: Option<Certificate>) -> Self {
Self {
cache: Some(cache),
origin: Some(origin),
candidate: certificate,
}
}
fn check(&mut self) {
if let (Some(cache), Some(origin), Some(candidate)) =
(&self.cache, &self.origin, &mut self.candidate)
&& cache.generation.load(Ordering::Acquire) != origin.generation
{
candidate.raw = None;
candidate.checkpoint = None;
}
}
pub(crate) fn select(&mut self, selection: &Selection) {
self.check();
if let Some(origin) = &self.origin
&& self
.candidate
.as_ref()
.is_none_or(|candidate| candidate.selection != *selection)
{
self.candidate = Some(Certificate {
selection: selection.clone(),
stamp: origin.clone(),
raw: None,
checkpoint: None,
});
}
}
pub(crate) fn anchor(&mut self, id: &str, sequence: u64, covered_through: u64) -> bool {
self.check();
let matched = self
.candidate
.as_ref()
.and_then(|candidate| candidate.checkpoint.as_ref())
.is_some_and(|anchor| {
anchor.id == id
&& anchor.sequence == sequence
&& anchor.covered_through == covered_through
});
#[cfg(test)]
if matched && let Some(cache) = &self.cache {
cache.anchor_hits.fetch_add(1, Ordering::Relaxed);
}
matched
}
#[cfg(test)]
pub(super) fn validated_link(&self) {
if let Some(cache) = &self.cache {
cache.validated_links.fetch_add(1, Ordering::Relaxed);
}
}
pub(crate) fn adopt(&mut self, event: &maka_agent_facts::event::RuntimeEvent, sequence: u64) {
self.check();
if let Some(candidate) = &mut self.candidate
&& let maka_agent_facts::event::Fact::ContextCheckpointRecorded { checkpoint } =
&event.fact
{
candidate.checkpoint = Some(Checkpoint {
id: event.id.clone(),
sequence,
covered_through: checkpoint.covered_through,
});
}
}
pub(crate) async fn digests(
&mut self,
connection: &mut SqliteConnection,
selection: &Selection,
throughs: &[u64],
) -> Result<Vec<String>, StoreError> {
self.check();
let candidate = self
.candidate
.as_mut()
.filter(|candidate| candidate.selection == *selection);
let raw = candidate
.as_ref()
.and_then(|candidate| candidate.raw.as_ref());
#[cfg(test)]
if raw.is_some_and(|raw| throughs.first().is_some_and(|cut| *cut >= raw.through))
&& let Some(cache) = &self.cache
{
cache.raw_reuses.fetch_add(1, Ordering::Relaxed);
}
let (digests, raw) = super::evidence::scan(connection, selection, throughs, raw).await?;
if let Some(candidate) = candidate
&& raw.as_ref().is_some_and(|raw| {
candidate
.raw
.as_ref()
.is_none_or(|old| raw.through >= old.through)
})
{
candidate.raw = raw;
}
Ok(digests)
}
pub(crate) fn candidate(self) -> Option<Certificate> {
self.candidate
.filter(|candidate| candidate.raw.is_some() || candidate.checkpoint.is_some())
}
}
/// A disposable autocommit hint, never evidence about the work snapshot.
pub(crate) async fn history_hint(connection: &mut SqliteConnection, session: &str) -> bool {
sqlx::query_scalar(
"SELECT EXISTS(SELECT 1 FROM runtime_events e
WHERE (e.event_json IS NOT NULL AND e.event_session=?1)
AND e.kind <> 'invocation_opened')
OR EXISTS(SELECT 1 FROM session_history_members WHERE session_id=?1)",
)
.bind(session)
.fetch_one(connection)
.await
.unwrap_or(false)
}
pub(crate) async fn promote(
cache: Arc<Cache>,
writer: &WriterIngress,
origin: Stamp,
candidate: Option<Certificate>,
) -> Result<(), StoreError> {
let Some(candidate) = candidate else {
return Ok(());
};
let ingress = writer.clone();
writer
.run(move |connection| {
Box::pin(async move {
if cache.stamp(connection, ingress.failures()).await? == origin {
cache.publish(candidate)?;
}
Ok(())
})
})
.await
}
#[cfg(test)]
mod tests;