Skip to content

quality: schema migration up-path for ~/.deepseek/ records — currently reject-only, no upgrade #350

@Hmbown

Description

@Hmbown

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

  • Migration framework lands for all three persistence types.
  • At least one no-op migrate_v(N)_to_v(N+1) exists per type, even if it's a stub — proves the wiring.
  • Backup-before-migrate writes *.bak files.
  • Test: load a fixture v(N-1) record, verify it migrates, verify it persists at vN, verify backup exists.
  • Documentation in 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions