Skip to content

Stored data schema versioning (ROADMAP 2.10) - #28

Merged
SeanathanVT merged 3 commits into
developmentfrom
feature/schema-versioning
Oct 21, 2026
Merged

SeanathanVT merged 3 commits into
developmentfrom
feature/schema-versioning

Conversation

@SeanathanVT

@SeanathanVT SeanathanVT commented Oct 21, 2026 •

Copy link
Copy Markdown
Owner

Summary

Every format WalkingDad stores on disk now declares its version. Older data is upgraded in order, and data written by a newer WalkingDad is refused instead of risked. The original ROADMAP entry was written when history was still JSON; this PR re-scopes it to the SQLite database plus the two JSON files.

Store Older Newer than this code
walkingdad.db (meta.schema_version) Backed up to data/backups/<db>.v<old>-<timestamp>, then upgraded one step at a time, each in its own transaction Refuses to start
config.json Missing version = 1; upgraded in memory, saved on the next Settings save Refuses to start
session_state.json Missing version = 1; upgraded in memory on restore Refuses to start

Every past config.json and session_state.json change was additive (checked in git history), so all existing files are v1 and no JSON migrations exist yet.

Changes

  • storage.py: init_db(path, backup_dir) is now a migration runner (_MIGRATIONS, first step = v3 profiles table).
    • The pre-upgrade backup uses SQLite's online backup API, so writes still in the WAL file are included. The copy is switched to journal_mode=DELETE.
    • A failed step rolls back, leaving the DB at its last good version, and the error names the backup.
    • Newer, non-integer, or below-v2 versions raise DataVersionError.
    • New and JSON-imported rows record sessions.schema_version.
  • config.py: prepare_data() is the single startup data step: relocate legacy files, version-check both JSON stores, then create or upgrade the DB. migrate() is the in-memory upgrade hook for the JSON stores. warn_unknown_settings() logs config.json keys it doesn't recognize, e.g. max_speed_kph.
  • run.py: sets up logging, then runs prepare_data() right after check_port() and before launching Waitress. A refusal or failed upgrade exits with a clear message instead of a child-process traceback and a dead browser tab. Running it after the port check means a newer copy can't upgrade the DB under an older one still running on that port.
  • app.py: startup calls prepare_data() as a backstop for launching waitress-serve directly. _write_config migrates, then stamps schema_version. The session-state snapshot carries schema_version.
  • Docs: ROADMAP 2.10 rewritten and marked complete, CHANGELOG [Unreleased], and a README "Data format versions" section covering recovery from a backup.

Deliberate choices

  • No read-only mode for older checkouts. It's only safe for additive changes, which older code can't detect, and it would mean gating every write path. Refusing to start, with a backup to fall back on, is the safe minimum.
  • A newer session_state.json is refused even when it says no walk is active. That field can't be trusted in an unknown format, and misreading it would let the startup sweep close a recoverable walk.
  • JSON stores are never rewritten at startup, so just launching a newer version doesn't lock out an older one.
  • The CSV export and Apple Health Shortcut payload are not versioned. They're outbound formats, and changing the Shortcut payload means re-publishing the .shortcut file.

Test plan

  • pytest passes on Python 3.10, 3.12, 3.13 (500 tests); ruff check . clean
  • Upgrade from a v2 fixture: backup created, data intact, schema identical to a fresh DB
  • Failed migration step rolls back and leaves the version unchanged
  • Backup includes rows only present in an un-checkpointed WAL (test fails if backup() is swapped for a file copy)
  • Newer, non-integer, and too-low versions refused for all three stores
  • run.py against a copy of a v99 DB: refusal printed, Waitress never launched
  • macOS: on a copy of real data set to v2, python run.py creates data/backups/walkingdad.db.v2-* and history still shows

Windows: backup and file-locking behavior during an upgrade is untested (the maintainer doesn't run Windows).

🤖 Generated with Claude Code

SeanathanVT and others added 3 commits October 21, 2026 09:13
…(ROADMAP 2.10)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… rows (ROADMAP 2.10)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… test (ROADMAP 2.10)

- Move the unknown config.json key warning out of prepare_data() into
  warn_unknown_settings(), called only from the server's startup. Both
  run.py and the server ran prepare_data, so the warning printed twice.
- Switch pre-upgrade DB backups to journal_mode=DELETE, so opening a
  backup doesn't leave -wal/-shm files in data/backups/.
- Test that a backup includes writes still in the WAL file (a connection
  held open with autocheckpoint off), which the earlier test couldn't
  reach because closing the connection checkpointed the WAL.
- README: an existing config.json gets its version on the next Settings
  save, backups are named after the database file, and quit any running
  copy before starting a newer one (the check only notices one on the
  same port).
- ROADMAP: record why a newer session_state.json is refused even when it
  says no walk is active.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@SeanathanVT
SeanathanVT merged commit 52b96a2 into development Oct 21, 2026
2 checks passed
@SeanathanVT
SeanathanVT deleted the feature/schema-versioning branch October 21, 2026 15:54
@SeanathanVT SeanathanVT mentioned this pull request Oct 30, 2026
SeanathanVT added a commit that referenced this pull request Oct 30, 2026
* feat(storage): version and migrate stored data, refuse newer formats (ROADMAP 2.10)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(storage): refuse unusable schema versions cleanly, stamp imported rows (ROADMAP 2.10)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(storage): single unknown-key warning, non-WAL backups, WAL backup test (ROADMAP 2.10)

- Move the unknown config.json key warning out of prepare_data() into
  warn_unknown_settings(), called only from the server's startup. Both
  run.py and the server ran prepare_data, so the warning printed twice.
- Switch pre-upgrade DB backups to journal_mode=DELETE, so opening a
  backup doesn't leave -wal/-shm files in data/backups/.
- Test that a backup includes writes still in the WAL file (a connection
  held open with autocheckpoint off), which the earlier test couldn't
  reach because closing the connection checkpointed the WAL.
- README: an existing config.json gets its version on the next Settings
  save, backups are named after the database file, and quit any running
  copy before starting a newer one (the check only notices one on the
  same port).
- ROADMAP: record why a newer session_state.json is refused even when it
  says no walk is active.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.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