fix: migrate legacy session history and retained archives
This commit is contained in:
@@ -101,10 +101,8 @@ fn from_logged(entry: &LoggedHistoryEntry) -> HistoryEntry<SessionHistoryMetadat
|
||||
}
|
||||
|
||||
/// Rebuild typed Worker history directly from the append-only Session Log.
|
||||
/// Missing metadata is not inferred from role or arbitrary plaintext. The one adopted legacy
|
||||
/// Host seed is an opening `DerivedSummary` system item in an annotated compacted Segment,
|
||||
/// bearing the exact Host summary marker. It is interpreted as `CompactionSummary` without
|
||||
/// rewriting the durable record; later items and pasted markers never qualify.
|
||||
/// Persisted origins are authoritative. Legacy Host summaries are upgraded by
|
||||
/// the Session schema migration, never reinterpreted during normal restoration.
|
||||
pub(crate) fn restore_history_entries(
|
||||
_session_id: SessionId,
|
||||
_segment_id: SegmentId,
|
||||
@@ -113,32 +111,8 @@ pub(crate) fn restore_history_entries(
|
||||
let mut history = Vec::new();
|
||||
for entry in entries {
|
||||
match entry {
|
||||
LogEntry::AnnotatedSegmentStart {
|
||||
history: seed,
|
||||
compacted_from,
|
||||
..
|
||||
} => {
|
||||
LogEntry::AnnotatedSegmentStart { history: seed, .. } => {
|
||||
history = seed.iter().map(from_logged).collect();
|
||||
// Older Host compactions always put this DerivedSummary system item
|
||||
// first in the replacement seed. Only that trusted construction boundary
|
||||
// may interpret the marker; never infer origin from appended/pasted text.
|
||||
if compacted_from.is_some()
|
||||
&& let Some(first) = history.first_mut()
|
||||
&& first.annotation.origin == WorkerHistoryProvenance::DerivedSummary
|
||||
&& matches!(
|
||||
&first.item,
|
||||
Item::Message {
|
||||
role: agen::Role::System,
|
||||
..
|
||||
}
|
||||
)
|
||||
&& first
|
||||
.item
|
||||
.as_text()
|
||||
.is_some_and(|text| text.starts_with(COMPACTION_SUMMARY_HEADER))
|
||||
{
|
||||
first.annotation.origin = WorkerHistoryProvenance::CompactionSummary;
|
||||
}
|
||||
}
|
||||
LogEntry::AnnotatedUserInput { history: input, .. } => {
|
||||
history.extend(input.iter().map(from_logged))
|
||||
@@ -181,7 +155,7 @@ mod tests {
|
||||
use session_store::LogEntry;
|
||||
|
||||
#[test]
|
||||
fn restore_recognizes_only_legacy_host_compaction_seed_summary() {
|
||||
fn restore_preserves_persisted_origin_without_legacy_reinterpretation() {
|
||||
let summary = history_entry(
|
||||
Item::system_message(format!("{COMPACTION_SUMMARY_HEADER}host summary")),
|
||||
WorkerHistoryProvenance::DerivedSummary,
|
||||
@@ -213,11 +187,7 @@ mod tests {
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
restored[0].annotation.origin,
|
||||
if compacted {
|
||||
WorkerHistoryProvenance::CompactionSummary
|
||||
} else {
|
||||
WorkerHistoryProvenance::DerivedSummary
|
||||
}
|
||||
WorkerHistoryProvenance::DerivedSummary
|
||||
);
|
||||
assert_eq!(
|
||||
restored[1].annotation.origin,
|
||||
|
||||
@@ -5576,6 +5576,7 @@ impl<C: LlmClient + 'static, St: Store> Worker<C, St> {
|
||||
self.apply_interrupt_prep()?;
|
||||
self.last_run_interrupted = false;
|
||||
self.commit_entry(LogEntry::PausedTurnAbandoned {
|
||||
entry_id: None,
|
||||
ts: segment_log::now_millis(),
|
||||
})?;
|
||||
Ok(())
|
||||
@@ -6636,6 +6637,7 @@ impl<C: LlmClient + 'static, St: Store> Worker<C, St> {
|
||||
EngineRunExit::Yielded | EngineRunExit::Interrupted(_) => unreachable!(),
|
||||
};
|
||||
self.commit_entry(LogEntry::RunCompleted {
|
||||
entry_id: None,
|
||||
ts: segment_log::now_millis(),
|
||||
interrupted,
|
||||
result,
|
||||
@@ -6653,6 +6655,7 @@ impl<C: LlmClient + 'static, St: Store> Worker<C, St> {
|
||||
}
|
||||
EngineRunExit::Interrupted(RunInterruptionReason::LimitReached) => {
|
||||
self.commit_entry(LogEntry::RunCompleted {
|
||||
entry_id: None,
|
||||
ts: segment_log::now_millis(),
|
||||
interrupted: false,
|
||||
result: EngineResult::LimitReached,
|
||||
@@ -14512,6 +14515,7 @@ permission = "write"
|
||||
);
|
||||
worker
|
||||
.commit_entry(LogEntry::RunCompleted {
|
||||
entry_id: None,
|
||||
ts: segment_log::now_millis(),
|
||||
interrupted: false,
|
||||
result: EngineResult::Finished,
|
||||
|
||||
@@ -2468,8 +2468,7 @@ async fn pasted_markers_and_other_derived_context_remain_ordinary_overview_mater
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn restored_legacy_summary_requires_compacted_lineage_first_position_origin_role_and_marker()
|
||||
{
|
||||
async fn restore_does_not_reinterpret_legacy_summary_markers() {
|
||||
use session_store::LoggedSessionHistoryOrigin::{
|
||||
CompactionSummary, DerivedSummary, LegacyUnknown,
|
||||
};
|
||||
@@ -2525,13 +2524,9 @@ async fn restored_legacy_summary_requires_compacted_lineage_first_position_origi
|
||||
.iter()
|
||||
.find(|entry| entry.annotation.entry_id == candidate_id)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
restored.annotation.origin == CompactionSummary,
|
||||
case == "trusted",
|
||||
"{case}"
|
||||
);
|
||||
// A restore-time upgrade is an in-memory interpretation of the trusted
|
||||
// seed; it must not silently rewrite the historical durable record.
|
||||
assert_ne!(restored.annotation.origin, CompactionSummary, "{case}");
|
||||
// Migration belongs to the persisted store, not this restore path.
|
||||
// Even a legacy Host seed must not be silently reinterpreted here.
|
||||
assert!(
|
||||
replacement_seed(&fixture.worker)
|
||||
.iter()
|
||||
@@ -2541,11 +2536,7 @@ async fn restored_legacy_summary_requires_compacted_lineage_first_position_origi
|
||||
fixture.worker.compact(0).await.unwrap();
|
||||
|
||||
let input = first_compactor_input(&observed.requests()[1]);
|
||||
if case == "trusted" {
|
||||
assert_full_initial_summary(&input, &inherited);
|
||||
} else {
|
||||
assert!(!input.contains(INITIAL_SUMMARY_HEADING), "{case}");
|
||||
}
|
||||
assert!(!input.contains(INITIAL_SUMMARY_HEADING), "{case}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user