Distinguish rollout IDs from thread IDs (#38127)

## Why

`thread/revert` preserves a thread ID while creating a new immutable rollout. Rollout persistence and filesystem fallback therefore need to distinguish the logical thread from each physical history file.

## What changed

- Add a `RolloutId` alias and canonical filename parsing for both ordinary rollouts and reverted rollouts with a distinct rollout ID.
- Allow the recorder to create a replacement rollout without changing the thread ID, and add lookup by either the stable thread ID or an exact rollout ID.
- Index history references and compression safety checks by rollout ID so multiple rollouts for one thread remain distinct.
- Preserve the rollout path selected in SQLite when filesystem repair encounters another rollout for the same thread.

## Testing

Add coverage for filename round trips, replacement rollout metadata, thread and rollout lookup, reference indexing, compression, and database path preservation.

GitOrigin-RevId: 14b1d0b4a9432b341a1307afc0abadd507e7ae7d
This commit is contained in:
Owen Lin authored and copyberry committed 2026-08-12 05:20:32 +00:00
1 parent 2230d64464
commit 4ef836f883
18 files changed
+513 -178

No files matched your search

+60
View File
@@ -199,6 +199,66 @@ async fn reconcile_rollout_preserves_existing_explicit_title() -> anyhow::Result
Ok(())
}
#[tokio::test]
async fn filesystem_repair_preserves_existing_rollout_path() -> anyhow::Result<()> {
let home = TempDir::new().expect("temp dir");
let thread_id = ThreadId::new();
let initial_rollout_path = write_rollout_with_user_message(
home.path(),
thread_id,
"Old",
ThreadHistoryMode::Paginated,
)?;
let old_rollout_path = initial_rollout_path
.with_file_name(format!("rollout-2026-06-01T14-26-25-{thread_id}_old.jsonl"));
std::fs::rename(initial_rollout_path, old_rollout_path.as_path())?;
let active_rollout_path = write_rollout_with_user_message(
home.path(),
thread_id,
"Current",
ThreadHistoryMode::Paginated,
)?;
let runtime = codex_state::StateRuntime::init(
codex_state::SqliteConfig::new_for_testing(home.path().abs()),
"test-provider".to_string(),
)
.await?;
let active_metadata =
metadata::extract_metadata_from_rollout(active_rollout_path.as_path(), "test-provider")
.await?
.metadata;
runtime.upsert_thread(&active_metadata).await?;
let active_metadata = runtime
.get_thread(thread_id)
.await?
.expect("thread should exist");
read_repair_rollout_path(
Some(runtime.as_ref()),
Some(thread_id),
/*archived_only*/ None,
old_rollout_path.as_path(),
)
.await;
assert_eq!(
runtime.get_thread(thread_id).await?,
Some(active_metadata.clone())
);
reconcile_rollout(
Some(runtime.as_ref()),
old_rollout_path.as_path(),
"test-provider",
/*builder*/ None,
&[],
/*archived_only*/ None,
/*new_thread_memory_mode*/ None,
)
.await;
assert_eq!(runtime.get_thread(thread_id).await?, Some(active_metadata));
Ok(())
}
#[tokio::test]
async fn reconcile_rollout_preserves_existing_paginated_memory_mode() -> anyhow::Result<()> {
let home = TempDir::new().expect("temp dir");