blob: 27dd4d4e58f65d3495e0275f053602032e55f64f [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 maka_agent_facts::event::EventWrite;
use maka_agent_facts::event::Fact;
use maka_agent_facts::event::Invocation;
use maka_agent_facts::event::InvocationInput;
use maka_agent_facts::event::InvocationOutcome;
use maka_agent_facts::event::RuntimeEvent;
use maka_agent_facts::execution::BehaviorId;
use maka_agent_facts::input::DeliveredMessage;
use maka_agent_facts::input::MessageInput;
use maka_agent_facts::message;
use maka_agent_facts::message::MessageDisposition;
use maka_agent_facts::message::Placement;
use maka_agent_facts::message::RootSourceMessage;
use maka_agent_facts::message::SubmittedTurnIntent;
use maka_agent_facts::message::TurnOrchestration;
use maka_agent_facts::message::TurnOrchestrationSource;
use maka_event_log::{EventLog, message_resolution::MessageExecution};
use serde_json::json;
#[path = "support/transcript.rs"]
mod support;
fn source(id: &str, text: &str) -> RootSourceMessage {
RootSourceMessage {
unprepared_content: text.into(),
message: DeliveredMessage {
origin: None,
message_id: id.into(),
content: text.into(),
submitted_content_digest: format!("sha256:{}", "b".repeat(64)),
},
submitted_placement: Placement::CurrentTurn,
disposition: MessageDisposition::Steering,
submitted_intent: None,
}
}
fn opening(session: &str, turn: &str, sources: Vec<RootSourceMessage>) -> RuntimeEvent {
RuntimeEvent::new(
Invocation {
session_id: session.into(),
turn_id: turn.into(),
run_id: format!("run-{turn}"),
invocation_id: format!("invocation-{turn}"),
},
Fact::InvocationOpened {
configuration: None,
input: InvocationInput::Message {
content: message::aggregate(sources.iter().map(|source| &source.message.content)),
request_fingerprint: None,
source_messages: sources,
},
},
)
}
async fn end(log: &EventLog, event: &RuntimeEvent) {
log.append(
&EventWrite::plain(RuntimeEvent::new(
event.invocation.clone(),
Fact::InvocationEnded {
outcome: InvocationOutcome::Completed,
},
))
.unwrap(),
)
.await
.unwrap();
}
#[tokio::test]
async fn root_sources_are_atomic_exclusive_delivery_proofs_and_rebuild_exact_visible_identity() {
let temp = tempfile::tempdir().unwrap();
let path = temp.path().join("root-sources.sqlite");
let log = EventLog::open(&path).await.unwrap();
for session in ["a", "b"] {
log.create_session(session, "create", &json!({}), 1)
.await
.unwrap();
}
let mut first = source("client-source", "prepared");
first.unprepared_content = "original input".into();
first.message.content.display_text = Some("visible".into());
first.disposition = MessageDisposition::TurnStarted;
first.submitted_intent = Some(SubmittedTurnIntent {
input_selections: [("reviewer".into(), vec!["project:review".into()])].into(),
input_selection_sources: Default::default(),
turn_orchestration: Some(TurnOrchestration {
mode: BehaviorId::try_from("graph".to_owned()).unwrap(),
source: TurnOrchestrationSource::SlashCommand,
}),
});
let event = opening("a", "first", vec![first.clone()]);
let mut duplicate_receipt = event.clone();
let Fact::InvocationOpened {
input: InvocationInput::Message { content, .. },
..
} = &mut duplicate_receipt.fact
else {
unreachable!()
};
content.text.push_str("not in source");
assert!(
EventWrite::plain(duplicate_receipt).is_err(),
"opening must preserve its admitted source content"
);
let write = EventWrite::plain(event.clone()).unwrap();
let pending = maka_event_log::message_admissions::PendingMessageAdmission {
execution: maka_event_log::message_admissions::PendingExecution {
owner: "maka-runtime-host".into(),
basis: serde_json::Value::Null,
},
steering_invocation: None,
required_tools: Default::default(),
invocation: event.invocation.clone(),
source: first.clone(),
admitted_at: 1,
};
assert_eq!(log.admit_message(pending.clone()).await.unwrap(), pending);
assert_eq!(log.admit_message(pending.clone()).await.unwrap(), pending);
assert!(matches!(
log.message_execution("a", "client-source").await.unwrap(),
MessageExecution::Pending
));
let mut changed = pending.clone();
changed.source.message.content.text = "different intent".into();
assert!(log.admit_message(changed).await.is_err());
let db = rusqlite::Connection::open(&path).unwrap();
db.execute_batch("CREATE TRIGGER reject_source BEFORE INSERT ON message_sources BEGIN SELECT RAISE(ABORT, 'source fault'); END;").unwrap();
let before = serde_json::to_vec(&log.prefix(100, 1024 * 1024).await.unwrap()).unwrap();
assert!(log.append(&write).await.is_err());
assert_eq!(
serde_json::to_vec(&log.prefix(100, 1024 * 1024).await.unwrap()).unwrap(),
before
);
assert!(
log.root_message("a", "client-source")
.await
.unwrap()
.is_none()
);
assert_eq!(log.pending_messages("a").await.unwrap(), vec![pending]);
db.execute_batch("DROP TRIGGER reject_source").unwrap();
let sequence = log.append(&write).await.unwrap();
assert!(log.pending_messages("a").await.unwrap().is_empty());
let mut queued_exact = maka_event_log::message_admissions::PendingMessageAdmission {
execution: maka_event_log::message_admissions::PendingExecution {
owner: "maka-runtime-host".into(),
basis: serde_json::Value::Null,
},
steering_invocation: None,
required_tools: Default::default(),
invocation: event.invocation.clone(),
source: first.clone(),
admitted_at: 2,
};
queued_exact.source.message.message_id = "queued-exact".into();
queued_exact.source.disposition = MessageDisposition::Steering;
assert!(log.admit_message(queued_exact).await.is_err());
assert_eq!(log.append(&write).await.unwrap(), sequence);
let proof = log
.root_message("a", "client-source")
.await
.unwrap()
.unwrap();
assert_eq!(proof.opening().event, event);
assert_eq!(proof.source(), &first);
let editable = log
.editable_message("a", "first", "client-source")
.await
.unwrap()
.unwrap();
assert_eq!(editable.content.text, "original input");
assert_eq!(editable.intent, first.submitted_intent);
assert!(
matches!(log.message_execution("a", "client-source").await.unwrap(), MessageExecution::Owned(owner) if owner.invocation == event.invocation)
);
assert!(
log.steering_message("a", "client-source")
.await
.unwrap()
.is_none()
);
let rows = support::transcript(&log, "a").await;
assert_eq!(rows[0]["id"], "client-source");
assert_eq!(rows[0]["text"], "prepared");
assert_eq!(rows[0]["displayText"], "visible");
// Root and steering deliveries share one identity domain, in both directions.
let colliding = RuntimeEvent::new(
event.invocation.clone(),
Fact::MessageSteered {
source: None,
message: Box::new(first.message.clone()),
},
);
assert!(
log.append(&EventWrite::plain(colliding).unwrap())
.await
.is_err()
);
let delivered = source("already-steered", "correction").message;
log.append(
&EventWrite::plain(RuntimeEvent::new(
event.invocation.clone(),
Fact::MessageSteered {
source: None,
message: Box::new(delivered),
},
))
.unwrap(),
)
.await
.unwrap();
end(&log, &event).await;
assert!(
matches!(log.message_execution("a", "already-steered").await.unwrap(), MessageExecution::Shared(owner) if owner.invocation == event.invocation)
);
assert_eq!(log.append(&write).await.unwrap(), sequence);
for id in ["client-source", "already-steered", event.id.as_str()] {
assert!(
log.append(
&EventWrite::plain(opening("a", "conflict", vec![source(id, "new")])).unwrap()
)
.await
.is_err()
);
}
let other = opening("b", "other", vec![first.clone()]);
log.append(&EventWrite::plain(other.clone()).unwrap())
.await
.unwrap();
assert_eq!(
support::transcript(&log, "b").await[0]["id"],
"client-source"
);
let mut batch = vec![
source("batch-z-left", "left"),
source("batch-a-right", "right"),
];
batch[0].unprepared_content.text = "first original".into();
batch[1].unprepared_content.text = "second original".into();
let combined = opening("a", "successor", batch.clone());
let mut mismatch = combined.clone();
if let Fact::InvocationOpened {
input: InvocationInput::Message { content, .. },
..
} = &mut mismatch.fact
{
*content = MessageInput::from("not their ordered aggregate");
}
assert!(EventWrite::plain(mismatch).is_err());
let mixed = vec![first, source("another", "right")];
assert!(
EventWrite::plain(opening("a", "mixed", mixed)).is_err(),
"exact intent cannot join a multi-source Turn"
);
log.append(&EventWrite::plain(combined.clone()).unwrap())
.await
.unwrap();
let rows = support::transcript(&log, "a").await;
let row = rows.iter().find(|row| row["id"] == combined.id).unwrap();
assert_eq!(row["text"], "left\n\nright");
let mut editable = Vec::new();
for (index, source) in batch.iter().enumerate() {
let proof = log
.root_message("a", &source.message.message_id)
.await
.unwrap()
.unwrap();
assert_eq!(proof.opening().event, combined);
if index == 0 {
let Fact::InvocationOpened {
input:
InvocationInput::Message {
source_messages, ..
},
..
} = &proof.opening().event.fact
else {
unreachable!()
};
assert_eq!(
source_messages
.iter()
.map(|source| (
source.message.message_id.as_str(),
source.unprepared_content.text.as_str()
))
.collect::<Vec<_>>(),
[
("batch-z-left", "first original"),
("batch-a-right", "second original")
]
);
}
assert_eq!(proof.source(), source);
let message = log
.editable_message(
"a",
&combined.invocation.turn_id,
&source.message.message_id,
)
.await
.unwrap()
.unwrap();
assert_eq!(
message.content, source.unprepared_content,
"editing selects the exact source, not the batch aggregate"
);
assert!(
log.editable_message("b", "successor", &source.message.message_id)
.await
.unwrap()
.is_none()
);
editable.push(message);
assert!(
matches!(log.message_execution("a", &source.message.message_id).await.unwrap(), MessageExecution::Shared(owner) if owner.invocation == combined.invocation)
);
}
let mut collision = RuntimeEvent::new(
combined.invocation.clone(),
Fact::InvocationEnded {
outcome: InvocationOutcome::Completed,
},
);
collision.id = "batch-a-right".into();
assert!(
log.append(&EventWrite::plain(collision).unwrap())
.await
.is_err()
);
end(&log, &combined).await;
let rows = support::transcript(&log, "a").await;
let canonical = serde_json::to_vec(&log.prefix(100, 1024 * 1024).await.unwrap()).unwrap();
log.close().await.unwrap();
db.execute_batch("DELETE FROM message_sources; DELETE FROM catalog_messages;")
.unwrap();
drop(db);
let log = EventLog::open(&path).await.unwrap();
assert_eq!(
serde_json::to_vec(&log.prefix(100, 1024 * 1024).await.unwrap()).unwrap(),
canonical
);
assert_eq!(support::transcript(&log, "a").await, rows);
for message in editable {
assert_eq!(
log.editable_message("a", "successor", &message.message_id)
.await
.unwrap()
.unwrap(),
message
);
}
assert!(
matches!(log.message_execution("a", "client-source").await.unwrap(), MessageExecution::Owned(owner) if owner.invocation == event.invocation),
"a later independent root cannot acquire an earlier Message"
);
assert_eq!(
log.root_message("a", "client-source")
.await
.unwrap()
.unwrap()
.opening()
.event,
event
);
assert_eq!(
log.root_message("b", "client-source")
.await
.unwrap()
.unwrap()
.opening()
.event,
other
);
assert!(
log.steering_message("a", "already-steered")
.await
.unwrap()
.is_some()
);
for source in &batch {
assert_eq!(
log.root_message("a", &source.message.message_id)
.await
.unwrap()
.unwrap()
.source(),
source
);
}
log.close().await.unwrap();
}