Keep latest session selection tied to real session recency

The next repo-local sweep target was ROADMAP #72: the `latest`
managed-session alias could depend on filesystem mtime before the
session's own persisted recency markers, which made the selection
path vulnerable to coarse or misleading file timestamps. The fix
promotes `updated_at_ms` into the summary/order path, keeps CLI
wrappers in sync, and locks the mtime-vs-session-recency case with
regression coverage.

Constraint: Preserve existing managed-session storage layout while changing only the ordering signal
Rejected: Keep sorting by filesystem mtime and just sleep longer in tests | hides the semantic ordering bug instead of fixing it
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Any future managed-session ordering change must keep runtime and CLI summary structs aligned on the same recency fields
Tested: cargo fmt --all --check; cargo clippy --workspace --all-targets -- -D warnings; cargo test --workspace; architect review APPROVE
Not-tested: Cross-filesystem behavior where persisted session JSON cannot be read and fallback ordering uses mtime only
This commit is contained in:
Yeachan-Heo
2026-04-12 07:49:32 +00:00
parent f309ff8642
commit dbc2824a3e
3 changed files with 48 additions and 6 deletions

View File

@@ -144,12 +144,7 @@ impl SessionStore {
if let Some(legacy_root) = self.legacy_sessions_root() {
self.collect_sessions_from_dir(&legacy_root, &mut sessions)?;
}
sessions.sort_by(|left, right| {
right
.modified_epoch_millis
.cmp(&left.modified_epoch_millis)
.then_with(|| right.id.cmp(&left.id))
});
sort_managed_sessions(&mut sessions);
Ok(sessions)
}
@@ -260,6 +255,7 @@ impl SessionStore {
ManagedSessionSummary {
id: session.session_id,
path,
updated_at_ms: session.updated_at_ms,
modified_epoch_millis,
message_count: session.messages.len(),
parent_session_id: session
@@ -279,6 +275,7 @@ impl SessionStore {
.unwrap_or("unknown")
.to_string(),
path,
updated_at_ms: 0,
modified_epoch_millis,
message_count: 0,
parent_session_id: None,
@@ -322,12 +319,23 @@ pub struct SessionHandle {
pub struct ManagedSessionSummary {
pub id: String,
pub path: PathBuf,
pub updated_at_ms: u64,
pub modified_epoch_millis: u128,
pub message_count: usize,
pub parent_session_id: Option<String>,
pub branch_name: Option<String>,
}
fn sort_managed_sessions(sessions: &mut [ManagedSessionSummary]) {
sessions.sort_by(|left, right| {
right
.updated_at_ms
.cmp(&left.updated_at_ms)
.then_with(|| right.modified_epoch_millis.cmp(&left.modified_epoch_millis))
.then_with(|| right.id.cmp(&left.id))
});
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LoadedManagedSession {
pub handle: SessionHandle,
@@ -598,6 +606,35 @@ mod tests {
.expect("session summary should exist")
}
#[test]
fn latest_session_prefers_semantic_updated_at_over_file_mtime() {
let mut sessions = vec![
ManagedSessionSummary {
id: "older-file-newer-session".to_string(),
path: PathBuf::from("/tmp/older"),
updated_at_ms: 200,
modified_epoch_millis: 100,
message_count: 2,
parent_session_id: None,
branch_name: None,
},
ManagedSessionSummary {
id: "newer-file-older-session".to_string(),
path: PathBuf::from("/tmp/newer"),
updated_at_ms: 100,
modified_epoch_millis: 200,
message_count: 1,
parent_session_id: None,
branch_name: None,
},
];
crate::session_control::sort_managed_sessions(&mut sessions);
assert_eq!(sessions[0].id, "older-file-newer-session");
assert_eq!(sessions[1].id, "newer-file-older-session");
}
#[test]
fn creates_and_lists_managed_sessions() {
// given

View File

@@ -3122,6 +3122,7 @@ struct SessionHandle {
struct ManagedSessionSummary {
id: String,
path: PathBuf,
updated_at_ms: u64,
modified_epoch_millis: u128,
message_count: usize,
parent_session_id: Option<String>,
@@ -4711,6 +4712,7 @@ fn list_managed_sessions() -> Result<Vec<ManagedSessionSummary>, Box<dyn std::er
.map(|session| ManagedSessionSummary {
id: session.id,
path: session.path,
updated_at_ms: session.updated_at_ms,
modified_epoch_millis: session.modified_epoch_millis,
message_count: session.message_count,
parent_session_id: session.parent_session_id,
@@ -4726,6 +4728,7 @@ fn latest_managed_session() -> Result<ManagedSessionSummary, Box<dyn std::error:
Ok(ManagedSessionSummary {
id: session.id,
path: session.path,
updated_at_ms: session.updated_at_ms,
modified_epoch_millis: session.modified_epoch_millis,
message_count: session.message_count,
parent_session_id: session.parent_session_id,