blob: ec89cf0a1170a98404aa1fb306703d25b6734786 [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::*;
#[path = "../bundle/fixtures.rs"]
mod bundle_fixtures;
#[path = "../bundle/import.rs"]
mod bundle_import;
#[tokio::test]
async fn bundle_checkpoint_requires_its_atomic_terminal_even_with_a_valid_transfer_digest() {
let directory = tempfile::tempdir().unwrap();
let log = EventLog::open(&directory.path().join("compact.sqlite"))
.await
.unwrap();
log.create_session("session", "session", &json!({}), 1)
.await
.unwrap();
open(&log, "old", false).await;
end(&log, "old").await;
open(&log, "compact", true).await;
let source = log
.prepare_context_compaction("session", Some("compact"), &CheckpointMode::Standalone)
.await
.unwrap();
let pair = summary_pair(&log, "compact", &source).await;
log.append_batch(&pair).await.unwrap();
let inventory = log.preview_bundle("session").await.unwrap();
let (bytes, _) = log
.export_bundle("session", &inventory.subtree_digest, Vec::new())
.await
.unwrap();
let mut staged = maka_event_log::bundle::StagedBundle::read(bytes.as_slice())
.await
.unwrap();
staged.validate_history().await.unwrap();
staged.close().await.unwrap();
bundle_import::roundtrip(&bytes).await;
let altered = frames::rewrite_events(&bytes, |event| event["id"] != pair[1].event().id);
maka_event_log::bundle::inspect(altered.as_slice())
.await
.unwrap();
let mut staged = maka_event_log::bundle::StagedBundle::read(altered.as_slice())
.await
.unwrap();
assert!(staged.validate_history().await.is_err());
staged.close().await.unwrap();
log.close().await.unwrap();
}
#[path = "../bundle/frames.rs"]
mod frames;
#[tokio::test]
async fn bundle_archive_closure_preserves_lineage_instead_of_exporting_intermediate_runs() {
let directory = tempfile::tempdir().unwrap();
let path = directory.path().join("bundle-lineage.sqlite");
let log = EventLog::open(&path).await.unwrap();
log.create_session("session", "source", &json!({}), 1)
.await
.unwrap();
let source = continuation::opening("source", None);
continuation::append(&log, &source).await;
let original = bundle_fixtures::tool(
&log,
&source.invocation,
"source-tool",
"original".repeat(2000),
)
.await;
continuation::close(&log, &source).await;
let base = log
.context_before_run(&source.invocation, 100, 65536)
.await
.unwrap()
.source_evidence;
let claim = continuation::claim(
&log,
"continue-source",
&source,
SessionBase {
high_water: base.high_water,
digest: base.digest,
},
)
.await;
let mut unrelated = continuation::opening("unrelated", None);
if let Fact::InvocationOpened {
input: InvocationInput::Message { content, .. },
..
} = &mut unrelated.fact
{
*content = "EXCLUDED INTERMEDIATE TURN".into();
}
continuation::append(&log, &unrelated).await;
continuation::close(&log, &unrelated).await;
let child = continuation::opening("child", Some(claim));
continuation::append(&log, &child).await;
let own = bundle_fixtures::tool(
&log,
&child.invocation,
"child-tool",
"child output".repeat(2000),
)
.await;
// The first archive's target follows the excluded Run; using Session scope here leaks it.
log.append(&archive("child", &own)).await.unwrap();
log.append(&archive("child", &original)).await.unwrap();
continuation::close(&log, &child).await;
let revision = log
.get_session::<serde_json::Value>("session")
.await
.unwrap()
.unwrap()
.revision;
log.copy_session(
maka_event_log::sessions::SessionCopy {
source_session_id: "session".into(),
target_session_id: "branch".into(),
expected_source_revision: revision,
purpose: maka_agent_facts::session::CopyPurpose::Branch {
turn_id: Some("source".into()),
side_conversation: false,
},
},
&json!({}),
2,
)
.await
.unwrap();
let inventory = log.preview_bundle("branch").await.unwrap();
let (bytes, report) = log
.export_bundle("branch", &inventory.subtree_digest, Vec::new())
.await
.unwrap();
let exported = frames::records(&bytes, &report.digest);
let mut staged = maka_event_log::bundle::StagedBundle::read(bytes.as_slice())
.await
.unwrap();
staged.validate_history().await.unwrap();
staged.close().await.unwrap();
bundle_import::roundtrip(&bytes).await;
let events: Vec<RuntimeEvent> = exported
.iter()
.filter(|r| r["kind"] == "event")
.map(|r| serde_json::from_str(r["json"].as_str().unwrap()).unwrap())
.collect();
assert!(events.iter().any(|event| event.id == own.event().id));
assert!(events.iter().any(|event| event.id == original.event().id));
assert!(events.iter().any(|event| event.id == child.id));
assert!(
!events
.iter()
.any(|event| event.invocation.run_id == "unrelated")
);
assert!(!String::from_utf8_lossy(&bytes).contains("EXCLUDED INTERMEDIATE TURN"));
log.close().await.unwrap();
// Excluded, collected bodies must not become required by an overbroad collection check.
let db = rusqlite::Connection::open(&path).unwrap();
db.execute(
"UPDATE event_log SET retained_json=event_json,body_digest='fixture',event_json=NULL WHERE invocation_id='unrelated'",
[],
).unwrap();
drop(db);
let log = EventLog::open(&path).await.unwrap();
let (_, repeated) = log
.export_bundle("branch", &inventory.subtree_digest, Vec::new())
.await
.unwrap();
assert_eq!(repeated.digest, report.digest);
log.close().await.unwrap();
}