blob: a443e16b50f9ece85fe2c459765fa9fd7c7eea23 [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::{EventLog, StoreError, sequence_number};
use maka_agent_facts::event::Fact;
use maka_agent_facts::event::RuntimeEvent;
use maka_agent_facts::event::StoredEvent;
use sqlx::SqliteConnection;
/// Executed inside the event transaction, after exact replay/identity/seal checks.
pub(crate) async fn validate_append(
tx: &mut SqliteConnection,
event: &RuntimeEvent,
) -> Result<(), StoreError> {
let Fact::MessageSteered { message, source } = &event.fact else {
return Ok(());
};
maka_agent_facts::event::validate_steered_source(
message,
source.as_deref(),
&event.invocation.session_id,
)
.map_err(|error| StoreError::InvalidTransition(error.into()))?;
let eligible: bool = sqlx::query_scalar(
"SELECT EXISTS(SELECT 1 FROM runtime_events WHERE invocation_id = ?
AND kind = 'invocation_opened' AND json_extract(event_json, '$.fact.input.kind') IN ('message', 'continuation', 'handoff'))"
).bind(&event.invocation.invocation_id).fetch_one(&mut *tx).await?;
if !eligible {
return Err(StoreError::InvalidTransition(
"steering requires an inline model invocation".into(),
));
}
let pending: bool = sqlx::query_scalar(sqlx::AssertSqlSafe(format!(
"{}, {} SELECT EXISTS(SELECT 1 FROM unresolved WHERE kind != 0
OR id NOT IN (SELECT operation_id FROM private_summaries))",
crate::recovery::unresolved!(""),
crate::context::safety::PRIVATE_SUMMARIES,
)))
.bind(&event.invocation.invocation_id)
.bind(i64::MAX)
.fetch_one(&mut *tx)
.await?;
if pending {
return Err(StoreError::InvalidTransition(
"steering requires settled model and tool boundaries".into(),
));
}
// The unique canonical index rejects a different event claiming this same
// Session/message identity. Exact event replay was already handled by append.
Ok(())
}
impl EventLog {
/// One immutable delivery proof, never a queue revision or guessed retry result.
pub async fn steering_message(
&self,
session_id: &str,
message_id: &str,
) -> Result<Option<StoredEvent>, StoreError> {
self.validate_root()?;
crate::sessions::validate_id(session_id)?;
crate::sessions::validate_id(message_id)?;
let session_id = session_id.to_owned();
let message_id = message_id.to_owned();
self.connection
.run(move |tx| {
Box::pin(async move {
let row: Option<(i64, String)> = sqlx::query_as(
"SELECT e.sequence, e.event_json FROM message_sources s JOIN runtime_events e ON e.event_id = s.event_id
WHERE e.kind = 'message_steered' AND s.session_id = ? AND s.message_id = ?",
)
.bind(&session_id)
.bind(&message_id)
.fetch_optional(tx)
.await?;
row.map(|(sequence, json)| {
let event: RuntimeEvent = serde_json::from_str(&json)?;
let Fact::MessageSteered { message, source } = &event.fact else {
return Err(StoreError::InvalidTransition(
"invalid steering proof".into(),
));
};
maka_agent_facts::event::validate_steered_source(
message, source.as_deref(), &event.invocation.session_id,
)
.map_err(|error| StoreError::InvalidTransition(error.into()))?;
if event.invocation.session_id != session_id
|| message.message_id != message_id
{
return Err(StoreError::InvalidTransition(
"steering proof identity changed".into(),
));
}
Ok(StoredEvent {
sequence: sequence_number(sequence)?,
event,
})
})
.transpose()
})
})
.await
}
}
pub(crate) async fn project_catalog(
tx: &mut SqliteConnection,
sequence: i64,
session: &str,
) -> Result<(), StoreError> {
sqlx::query(
"INSERT OR IGNORE INTO catalog_messages
SELECT sequence, 0, ?2, catalog_time(json_extract(event_json, '$.recorded_at')),
catalog_preview(COALESCE(
json_extract(event_json, '$.fact.message.content.display_text'),
json_extract(event_json, '$.fact.message.content.text'))),
json_extract(event_json, '$.fact.message.message_id')
FROM runtime_events WHERE sequence = ?1 AND kind = 'message_steered'",
)
.bind(sequence)
.bind(session)
.execute(tx)
.await?;
Ok(())
}