blob: fe5d080054f961fc1f162884adb18d2978c01748 [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 futures_util::TryStreamExt;
use maka_agent_facts::event::LogPrefix;
use maka_agent_facts::event::LogScope;
use maka_agent_facts::event::StoredEvent;
use sha2::{Digest, Sha256};
use sqlx::{Connection, Row};
use crate::{EventLog, StoreError, context::selection::Selection, sequence_number};
impl EventLog {
/// Read one complete bounded execution prefix, or fail without returning a partial
/// history that a caller could accidentally treat as complete. Session-owned
/// control facts share the ledger order but are not execution history.
pub async fn prefix(
&self,
max_events: usize,
max_bytes: usize,
) -> Result<LogPrefix, StoreError> {
self.scoped_prefix(LogScope::Root, max_events, max_bytes)
.await
}
/// Bounds apply only to events in scope; exceeding either bound is an error.
/// Membership, budget and contents are read from the same SQLite snapshot.
pub async fn scoped_prefix(
&self,
scope: LogScope,
max_events: usize,
max_bytes: usize,
) -> Result<LogPrefix, StoreError> {
self.validate_root()?;
self.connection.run(move |connection| Box::pin(async move {
let mut transaction = connection.begin().await?;
let selection = Selection::resolve(&mut transaction, &scope).await?;
let collected: bool = sqlx::query_scalar(
"SELECT EXISTS(SELECT 1 FROM event_log WHERE invocation_id IS NOT NULL
AND event_json IS NULL AND (?1 IS NULL OR event_session=?1))"
).bind(&selection.session).fetch_one(&mut *transaction).await?;
if collected { return Err(StoreError::MaterialCollected); }
let lineage = Selection::predicate("runtime_events", "?2");
let source = if selection.session.is_some() { "session_history_events" } else { "runtime_events" };
let session_filter = if selection.session.is_some() {
"owner_session_id=?1"
} else { "?1 IS NULL" };
let filter = format!("{session_filter} AND {lineage}");
// Only internal SQL clauses are formatted; all identities remain bound.
let budget = sqlx::query_as::<_, (i64, i64, i64)>(sqlx::AssertSqlSafe(format!(
"SELECT COALESCE(MAX(sequence), 0), COUNT(*), COALESCE(SUM(length(CAST(event_json AS BLOB))), 0) FROM {source} runtime_events WHERE {filter}"
))).bind(&selection.session).bind(&selection.lineage);
let (high_water, count, bytes) = budget.fetch_one(&mut *transaction).await?;
if sequence_number(count)? > max_events as u64 || sequence_number(bytes)? > max_bytes as u64
{
return Err(StoreError::PrefixTooLarge);
}
let mut digest = Sha256::new();
digest.update(b"maka.log-prefix.v1\0");
let scope_bytes = serde_json::to_vec(&scope)?;
digest.update((scope_bytes.len() as u64).to_be_bytes());
digest.update(scope_bytes);
let mut events = Vec::new();
{
let statement = sqlx::query(sqlx::AssertSqlSafe(format!(
"SELECT sequence, event_json, (SELECT length(payload) FROM tool_result_payloads WHERE event_id = runtime_events.event_id) FROM {source} runtime_events WHERE {filter} ORDER BY sequence"
))).bind(&selection.session).bind(&selection.lineage);
let mut rows = statement.fetch(&mut *transaction);
while let Some(row) = rows.try_next().await? {
let sequence: i64 = row.try_get(0)?;
let json: &str = row.try_get(1)?;
let sequence = sequence_number(sequence)?;
digest.update(sequence.to_be_bytes());
digest.update((json.len() as u64).to_be_bytes());
digest.update(json.as_bytes());
let event = serde_json::from_str(json)?;
crate::tool_payloads::verify_binding(&event, row.try_get(2)?)?;
events.push(StoredEvent {
sequence,
event,
});
}
}
digest.update(sequence_number(high_water)?.to_be_bytes());
transaction.commit().await?;
Ok(LogPrefix {
scope,
high_water: sequence_number(high_water)?,
digest: format!("sha256:{}", hex::encode(digest.finalize())),
events,
})
})).await
}
}