Problem
session_manager.rs:189-194, runtime_threads.rs, and task_manager.rs all gate on schema_version > CURRENT_* and reject newer records (good — prevents silent truncation of fields the running binary doesn't know about).
What's missing: the upgrade path for older records. When we bump CURRENT_SESSION_SCHEMA_VERSION from 3 to 4 (e.g. to add a field), every v3 session on disk breaks — the load function has no migration for version < CURRENT. Today this only works because we haven't bumped any version since users started accumulating sessions; the next bump silently drops every existing session.
Fix
Per-record migration framework:
fn load_session(path: &Path) -> io::Result<Session> {
let raw: Value = serde_json::from_reader(File::open(path)?)?;
let on_disk_version = raw.get("schema_version").and_then(|v| v.as_u64()).unwrap_or(1) as u32;
if on_disk_version > CURRENT_SESSION_SCHEMA_VERSION {
return Err(...); // existing reject-newer behavior
}
let migrated = (on_disk_version..CURRENT_SESSION_SCHEMA_VERSION)
.try_fold(raw, |v, from| migrate_session(from, from + 1, v))?;
serde_json::from_value(migrated).map_err(...)
}
fn migrate_session(from: u32, to: u32, value: Value) -> io::Result<Value> {
match (from, to) {
(1, 2) => migrate_session_v1_to_v2(value),
(2, 3) => migrate_session_v2_to_v3(value),
_ => Err(io::Error::new(InvalidData, format!("no migration {from}→{to}"))),
}
}
Backup-before-migrate: write <file>.v<N>.bak before persisting the migrated form, so users can recover if a migration has a bug.
Apply to
session_manager.rs (Session, OfflineQueueState).
runtime_threads.rs (thread/turn/item store).
task_manager.rs (task queue + timelines).
Acceptance criteria
This unblocks future schema bumps. Today the first bump that lands without this work loses every user's session history.
Problem
session_manager.rs:189-194,runtime_threads.rs, andtask_manager.rsall gate onschema_version > CURRENT_*and reject newer records (good — prevents silent truncation of fields the running binary doesn't know about).What's missing: the upgrade path for older records. When we bump
CURRENT_SESSION_SCHEMA_VERSIONfrom 3 to 4 (e.g. to add a field), every v3 session on disk breaks — the load function has no migration forversion < CURRENT. Today this only works because we haven't bumped any version since users started accumulating sessions; the next bump silently drops every existing session.Fix
Per-record migration framework:
Backup-before-migrate: write
<file>.v<N>.bakbefore persisting the migrated form, so users can recover if a migration has a bug.Apply to
session_manager.rs(Session, OfflineQueueState).runtime_threads.rs(thread/turn/item store).task_manager.rs(task queue + timelines).Acceptance criteria
migrate_v(N)_to_v(N+1)exists per type, even if it's a stub — proves the wiring.*.bakfiles.docs/PERSISTENCE.md(new) describing the schema lifecycle.This unblocks future schema bumps. Today the first bump that lands without this work loses every user's session history.