fix: migrate legacy session history and retained archives

This commit is contained in:
2026-10-19 13:52:43 +09:00
parent b3189d8e85
commit cea0225a6e
19 changed files with 960 additions and 109 deletions
+5 -35
View File
@@ -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,
+4
View File
@@ -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,
+5 -14
View File
@@ -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}");
}
}