blob: 1b8c9f699c1a02390e3c50a87afa5d2419e042b6 [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 super::invalid;
use crate::{
StoreError,
bundle::{
format::{Blob, Record},
stage::payload,
},
};
use maka_agent_facts::composition::RequestComposition;
use maka_agent_facts::event::Fact;
use maka_agent_facts::event::RuntimeEvent;
use maka_agent_facts::event::ToolOutcome;
use maka_agent_facts::tool_output::decode_raw_tool_result;
use sqlx::SqliteConnection;
pub(super) async fn tool(
staged: &mut SqliteConnection,
event: &RuntimeEvent,
) -> Result<Option<Vec<u8>>, StoreError> {
let Some((number, descriptor)) = descriptor(staged, &event.id, "tool_result").await? else {
return Ok(None);
};
let Fact::ToolSettled {
outcome: ToolOutcome::Succeeded { raw, .. },
..
} = &event.fact
else {
return Err(invalid("tool blob belongs to a non-success event"));
};
if raw.bytes != descriptor.bytes() || raw.digest != descriptor.digest() {
return Err(invalid("tool blob differs from its original event"));
}
let payload = payload(staged, number, &descriptor).await?;
decode_raw_tool_result(&payload, raw).map_err(invalid)?;
Ok(Some(payload))
}
pub(super) async fn composition(
staged: &mut SqliteConnection,
db: &mut SqliteConnection,
event: &RuntimeEvent,
) -> Result<(), StoreError> {
let material = descriptor(staged, &event.id, "composition").await?;
crate::composition::validate_retry(
db,
event,
material.as_ref().map(|(_, descriptor)| descriptor.digest()),
)
.await?;
let Some((number, descriptor)) = material else {
return Ok(());
};
if !matches!(event.fact, Fact::ModelRequested { .. }) {
return Err(invalid("composition belongs to a non-request event"));
}
let bytes = payload(staged, number, &descriptor).await?;
let surface: RequestComposition = serde_json::from_slice(&bytes)?;
if surface.freeze().map_err(invalid)?.digest() != descriptor.digest() {
return Err(invalid("composition is not the frozen request surface"));
}
sqlx::query("INSERT INTO request_compositions(digest,surface) VALUES(?,?) ON CONFLICT(digest) DO NOTHING")
.bind(descriptor.digest()).bind(bytes).execute(&mut *db).await?;
sqlx::query("INSERT INTO model_request_compositions(event_id,digest) VALUES(?,?)")
.bind(&event.id)
.bind(descriptor.digest())
.execute(db)
.await?;
Ok(())
}
async fn descriptor(
staged: &mut SqliteConnection,
event: &str,
resource: &str,
) -> Result<Option<(i64, Blob)>, StoreError> {
let row: Option<(i64,String)> = sqlx::query_as(
"SELECT number,record_json FROM frames WHERE kind='blob' AND json_extract(record_json,'$.resource')=?
AND json_extract(record_json,'$.event_id')=?"
).bind(resource).bind(event).fetch_optional(staged).await?;
row.map(|(number, json)| {
let Record::Blob(blob) = serde_json::from_str(&json)? else {
unreachable!()
};
Ok((number, blob))
})
.transpose()
}
pub(super) async fn check_bindings(
staged: &mut SqliteConnection,
db: &mut SqliteConnection,
) -> Result<(), StoreError> {
let expected: i64 = sqlx::query_scalar(
"SELECT COUNT(*) FROM frames WHERE kind='blob' AND json_extract(record_json,'$.resource') IN ('tool_result','composition')"
).fetch_one(&mut *staged).await?;
let installed: i64 = sqlx::query_scalar(
"SELECT (SELECT COUNT(*) FROM tool_result_payloads)+(SELECT COUNT(*) FROM model_request_compositions)"
).fetch_one(&mut *db).await?;
if expected != installed {
return Err(invalid("bundle contains unbound event payloads"));
}
let expected: i64 = sqlx::query_scalar(
"SELECT COUNT(*) FROM frames WHERE kind IN ('member','revision_source')",
)
.fetch_one(staged)
.await?;
let installed: i64 = sqlx::query_scalar(
"SELECT (SELECT COUNT(*) FROM session_history_members)+(SELECT COUNT(*) FROM session_revision_sources)"
).fetch_one(&mut *db).await?;
if expected != installed {
return Err(invalid("bundle contains history without its copy"));
}
let invalid_pin: bool = sqlx::query_scalar(
"SELECT EXISTS(SELECT 1 FROM session_history_members h JOIN runtime_events t ON t.sequence=h.sequence
LEFT JOIN runtime_events a ON a.sequence=h.archive_sequence
WHERE t.event_session=h.session_id OR (h.archive_sequence IS NOT NULL AND
(a.kind IS NOT 'tool_result_archived' OR a.sequence<=t.sequence OR
json_extract(a.event_json,'$.fact.placeholder.identity.runtime_event_id') IS NOT t.event_id)))"
).fetch_one(&mut *db).await?;
if invalid_pin {
return Err(invalid("copy has a self member or mismatched archive pin"));
}
let changed_projection: bool = sqlx::query_scalar(
"SELECT EXISTS(SELECT 1 FROM session_history_members h
JOIN session_history_copies c ON c.session_id=h.session_id
JOIN runtime_events target ON target.sequence=h.sequence
LEFT JOIN session_history_members parent ON parent.session_id=c.source_session_id AND parent.sequence=h.sequence
WHERE h.archive_sequence IS NOT COALESCE(
(SELECT a.sequence FROM runtime_events a WHERE a.kind='tool_result_archived'
AND a.event_session=c.source_session_id AND a.sequence<=c.observed_through
AND json_extract(a.event_json,'$.fact.placeholder.identity.runtime_event_id')=target.event_id),
parent.archive_sequence))"
).fetch_one(&mut *db).await?;
if changed_projection {
return Err(invalid(
"copy archive pin differs from its source projection",
));
}
let premature: bool = sqlx::query_scalar(
"SELECT EXISTS(SELECT 1 FROM session_history_copies c JOIN runtime_events e ON e.event_session=c.session_id
WHERE e.sequence<=c.observed_through OR c.state!='committed')"
).fetch_one(db).await?;
if premature {
return Err(invalid("copy has work before its creation or retention"));
}
Ok(())
}