blob: eec8e7c00b7eff20d324531e251790033561f211 [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::{PendingMessageAdmission, invalid, read};
use crate::StoreError;
use maka_agent_facts::event::Invocation;
use maka_agent_facts::message::MessageDisposition;
use sqlx::SqliteConnection;
pub(crate) enum Owner {
Unsealed,
/// Host still owns this latest Run's cleanup/handoff under its admission gate.
Current,
}
pub(crate) async fn insert(
tx: &mut SqliteConnection,
admission: &PendingMessageAdmission,
owner: Owner,
) -> Result<Option<u64>, StoreError> {
admission.validate()?;
crate::recovery::require_local(tx, &admission.invocation.invocation_id).await?;
if admission.steering_invocation.is_some() {
return Err(invalid("new admission cannot override steering ownership"));
}
let encoded = serde_json::to_string(&admission)?;
let session = &admission.invocation.session_id;
let message = &admission.source.message.message_id;
if let Some(previous) = read(&mut *tx, session, message).await? {
if previous != *admission {
return Err(invalid("message admission identity changed"));
}
return Ok(None);
}
let archived: Option<bool> =
sqlx::query_scalar("SELECT archived FROM session_control WHERE id = ?")
.bind(session)
.fetch_optional(&mut *tx)
.await?;
match archived {
None => return Err(StoreError::SessionNotFound),
Some(true) => return Err(invalid("cannot admit a message to an archived Session")),
Some(false) => {}
}
let delivered: bool = sqlx::query_scalar(
"SELECT EXISTS(SELECT 1 FROM message_sources WHERE session_id = ? AND message_id = ?)",
)
.bind(session)
.bind(message)
.fetch_one(&mut *tx)
.await?;
if delivered {
return Err(invalid("message already has an immutable delivery"));
}
if crate::message_queue::cancelled(&mut *tx, session, message).await? {
return Err(invalid("message has a durable cancellation"));
}
crate::message_identity::validate_source_id(&mut *tx, session, message).await?;
if crate::message_interrupts::fenced(&mut *tx, session, &admission.invocation.run_id).await? {
return Err(StoreError::SessionBusy);
}
let active: Option<String> = sqlx::query_scalar(
"SELECT json_extract(o.event_json, '$.invocation') FROM (
SELECT invocation_id, event_json FROM event_log INDEXED BY catalog_message_facts
WHERE invocation_id IS NOT NULL AND event_json IS NOT NULL
AND kind IN ('invocation_opened', 'model_completed') AND kind = 'invocation_opened'
AND json_extract(event_json, '$.invocation.session_id') = ?1
ORDER BY sequence DESC LIMIT 1
) AS o
WHERE NOT EXISTS(SELECT 1 FROM imported_invocations i
WHERE i.invocation_id = o.invocation_id)
AND (?2 OR NOT EXISTS(SELECT 1 FROM runtime_events t
WHERE t.invocation_id = o.invocation_id AND t.kind = 'invocation_ended'))",
)
.bind(session)
.bind(matches!(owner, Owner::Current))
.fetch_optional(&mut *tx)
.await?;
match (admission.source.disposition, active) {
(MessageDisposition::TurnStarted, None) => {}
(MessageDisposition::Steering | MessageDisposition::Followup, Some(active))
if serde_json::from_str::<Invocation>(&active)? == admission.invocation => {}
_ => {
return Err(invalid(
"message admission no longer matches the active Run",
));
}
}
let other_owner: bool = sqlx::query_scalar(
"SELECT EXISTS(SELECT 1 FROM message_admissions WHERE session_id = ?
AND json_extract(record_json, '$.execution.owner') IS NOT ?)",
)
.bind(session)
.bind(&admission.execution.owner)
.fetch_one(&mut *tx)
.await?;
if other_owner {
return Err(invalid("pending Session belongs to another dispatcher"));
}
let (pending, position): (bool, i64) = sqlx::query_as(
"SELECT EXISTS(SELECT 1 FROM message_admissions WHERE session_id = ?1),
COALESCE(MAX(position),0)
FROM message_admissions WHERE session_id = ?1",
)
.bind(session)
.fetch_one(&mut *tx)
.await?;
let position = position
.checked_add(1)
.ok_or_else(|| invalid("message queue position exhausted"))?;
if admission.source.disposition == MessageDisposition::TurnStarted && pending {
return Err(StoreError::SessionBusy);
}
sqlx::query("INSERT INTO message_admissions (session_id, message_id, position, record_json) VALUES (?, ?, ?, ?)")
.bind(session).bind(message).bind(position).bind(encoded).execute(&mut *tx).await?;
crate::sessions::retain(tx, session).await?;
crate::message_queue::bump(tx, session).await.map(Some)
}