Skip to content

fix(F8): session schema versioning with migration hook + corruption diag - #83

Merged
yogthos merged 1 commit into
mainfrom
fix/f8-session-schema-version
May 21, 2026
Merged

fix(F8): session schema versioning with migration hook + corruption diag#83
yogthos merged 1 commit into
mainfrom
fix/f8-session-schema-version

Conversation

@yogthos

@yogthos yogthos commented May 21, 2026

Copy link
Copy Markdown
Collaborator

Track F-HIGH #8. Session gains schema_version: u32 with serde default 0 for pre-F8 files. load_session runs migrate_session on version-lower files, warns on version-higher, and wraps corrupted-JSON errors with the file path so users can identify which session is broken. 2 new tests, 664 pass.

Track F-HIGH #8 from ROADMAP.md.

## Problem

`load_session` (`storage.rs:98-104`) deserialized raw JSON with
no schema versioning. Two pain points:

1. **Corrupted files** (truncated mid-write before atomic save
   shipped, or external mutation) surfaced as
   `expected ',' or '}' at line N column M` with no file path.
   A user with many sessions had to grep to figure out which
   one was broken.
2. **No migration path** for future schema bumps. Field
   additions so far used `#[serde(default)]` so they migrated
   transparently, but a real shape change (renaming a field,
   restructuring `tree.entries`) would silently corrupt old
   sessions on first load with no way to detect.

## Fix

`Session` gains `pub schema_version: u32` with
`#[serde(default)]` so pre-F8 files load with `schema_version
= 0`. `Session::new` sets it to the current `SCHEMA_VERSION`
constant (initial value: 1 — first versioned schema).

`load_session` now:

1. Wraps the parse error with `path.display()` context so the
   user sees `failed to parse <path>: <serde error>`.
2. After successful deserialize, compares `session.schema_version`
   against `SCHEMA_VERSION`:
   - Lower → call `migrate_session(&mut session)` then bump.
   - Equal → no-op.
   - Higher (file from newer dirge) → warn via tracing but
     still load. Most fields default-migrate via serde; only
     truly new fields get default values.

`migrate_session` is a single function with version gates ready
for future schema work. The v0 → v1 step is a no-op (current
schema is structurally identical to pre-F8; only the field
introduction marks the version).

## Tests

Two new tests in `session::storage::tests`:

- `load_session_migrates_pre_f8_files`: writes a minimal pre-F8
  session JSON (no schema_version field) to disk, loads it,
  asserts schema_version is bumped to SCHEMA_VERSION and the
  data is intact.
- `load_session_corrupted_file_includes_path_in_error`: writes
  a truncated JSON, asserts the error mentions the path so the
  user can grep `dirge` logs and identify the broken file.

664 pass (was 662). All build profiles clean.
@yogthos
yogthos merged commit 4776a74 into main May 21, 2026
@yogthos
yogthos deleted the fix/f8-session-schema-version branch May 21, 2026 04:29
allen-munsch pushed a commit to allen-munsch/dirge that referenced this pull request Jun 3, 2026
…iag (dirge-code#83)

Track F-HIGH dirge-code#8 from ROADMAP.md.

## Problem

`load_session` (`storage.rs:98-104`) deserialized raw JSON with
no schema versioning. Two pain points:

1. **Corrupted files** (truncated mid-write before atomic save
   shipped, or external mutation) surfaced as
   `expected ',' or '}' at line N column M` with no file path.
   A user with many sessions had to grep to figure out which
   one was broken.
2. **No migration path** for future schema bumps. Field
   additions so far used `#[serde(default)]` so they migrated
   transparently, but a real shape change (renaming a field,
   restructuring `tree.entries`) would silently corrupt old
   sessions on first load with no way to detect.

## Fix

`Session` gains `pub schema_version: u32` with
`#[serde(default)]` so pre-F8 files load with `schema_version
= 0`. `Session::new` sets it to the current `SCHEMA_VERSION`
constant (initial value: 1 — first versioned schema).

`load_session` now:

1. Wraps the parse error with `path.display()` context so the
   user sees `failed to parse <path>: <serde error>`.
2. After successful deserialize, compares `session.schema_version`
   against `SCHEMA_VERSION`:
   - Lower → call `migrate_session(&mut session)` then bump.
   - Equal → no-op.
   - Higher (file from newer dirge) → warn via tracing but
     still load. Most fields default-migrate via serde; only
     truly new fields get default values.

`migrate_session` is a single function with version gates ready
for future schema work. The v0 → v1 step is a no-op (current
schema is structurally identical to pre-F8; only the field
introduction marks the version).

## Tests

Two new tests in `session::storage::tests`:

- `load_session_migrates_pre_f8_files`: writes a minimal pre-F8
  session JSON (no schema_version field) to disk, loads it,
  asserts schema_version is bumped to SCHEMA_VERSION and the
  data is intact.
- `load_session_corrupted_file_includes_path_in_error`: writes
  a truncated JSON, asserts the error mentions the path so the
  user can grep `dirge` logs and identify the broken file.

664 pass (was 662). All build profiles clean.

Co-authored-by: Yogthos <yogthos@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant