Skip to content

Repository files navigation

WalProof

CI Python 3.11+ License: MIT

Your SQLite backup exited zero. Did it include the rows still committed only in the WAL?

WalProof is a zero-runtime-dependency CLI that rehearses an arbitrary SQLite backup command against a generated WAL-mode database while writes continue. It restores the result and produces machine-readable evidence for the exact failure that ordinary file checks miss: an openable, internally consistent backup that silently lost committed rows.

WalProof does not back up production data. It gives your backup command a disposable {source} and {backup}, then judges what that command actually preserved.

Why this exists

In SQLite WAL mode, commits are appended to a separate write-ahead log and reach the main database file later during a checkpoint. SQLite's own documentation therefore treats the WAL as part of the database's persistent state. Copying only app.sqlite can produce a file that opens and passes PRAGMA quick_check while still being stale. See the official WAL documentation and Online Backup API.

WalProof turns that warning into an executable regression test:

generated WAL database + concurrent writer
                 │
                 ▼
       your exact backup argv
                 │
                 ▼
restore → identity + committed rows + sequence + payload checks

Quick start

Install the released wheel with Python 3.11 or newer:

python -m pip install https://github.com/KanadeK/walproof/releases/download/v0.1.0/walproof-0.1.0-py3-none-any.whl
walproof demo --report-dir walproof-reports

The demo intentionally runs two real child processes. A main-file copy must fail the committed-WAL check, the SQLite Backup API must pass, and only that pair makes the demo itself succeed:

[FAIL] unsafe-copy: ...
[PASS] backup-api: ...
[PASS] demo verdict

The exact event counts vary because the writer is live. The verdict does not depend on capturing writes that happened after the backup command started.

Test your own backup command

Create a strict version 1 plan. Each item is passed directly to the child process; no shell parses the command.

{
  "schema_version": 1,
  "name": "my-backup",
  "backup_argv": [
    "{python}",
    "backup.py",
    "{source}",
    "{backup}"
  ],
  "timeout_seconds": 30
}

{source} and {backup} are required exact arguments. {python} is optional. Relative paths resolve from the plan file's directory.

walproof rehearse plan.json --report-dir walproof-reports

The repository includes both sides of the proof:

walproof rehearse examples/unsafe-plan.json
walproof rehearse examples/safe-plan.json

The first exits 1 even though its backup opens. The second uses sqlite3.Connection.backup and exits 0.

Evidence

A rehearsal passes only when every applicable check passes:

Check What it proves
command_exit_zero The child process exited normally with code 0.
backup_exists The expected backup artifact was created.
integrity_ok SQLite opened it and PRAGMA quick_check returned ok.
lab_identity_matches The file came from this rehearsal, not a stale artifact.
baseline_commits_present Every row committed before command launch survived.
event_sequence_contiguous Restored fixture IDs contain no gaps.
event_payloads_valid Restored payloads match the deterministic fixture contract.

JSON and Markdown reports contain the schema/tool version, outcome, safe command metadata, row bounds, and ordered checks. The evidence model never stores literal argv or exception text, and child stdout/stderr is discarded because an arbitrary command may expose credentials there.

Exit codes are stable:

Code Meaning
0 The rehearsal passed, or the demo proved both expected paths.
1 The command ran or was attempted, but backup evidence failed.
2 CLI input, the plan, or report output was invalid.

When a run fails

WalProof fails fast and keeps evidence when --report-dir is set.

Failure Repair path
baseline_commits_present fails while integrity_ok passes Your method made a stale snapshot. Replace main-file copy with the SQLite Backup API or another coherent snapshot mechanism, then rerun the same plan.
lab_identity_matches fails The command copied the wrong source or reused an old destination. Make it write exactly {backup} from {source}.
command_exit_zero reports timed_out Run the backup command directly in a trusted environment to diagnose it. Increase timeout_seconds only if the operation legitimately needs more time; the valid range is 1–300 seconds.
Exit 2 reports an invalid plan Remove unknown fields, use an argv array, and include exact {source} and {backup} items. Name an interpreter explicitly instead of a .bat or .cmd launcher.
The release gate fails after dependency changes Run uv sync --locked, fix the first failing gate, and rerun the complete command. Do not skip the audit, isolated install, or demo.

WalProof discards child output by design. If diagnosis needs that output, execute the reviewed command manually in an environment where displaying it is safe.

Trust boundary and limits

  • A plan is executable code under your current user account. Review it first. WalProof uses an argv array and shell=False, but v0.1 does not sandbox the executable or descendants.
  • The child inherits the current environment. Put secrets in neither the plan nor diagnostic output. Reports never persist raw command arguments or child output.
  • WalProof creates and checks only its own temporary SQLite database. Its public CLI accepts no production database path.
  • Passing proves the command handled this synthetic WAL/live-write scenario. It does not prove filesystem crash durability, remote upload completeness, retention, encryption, object-store consistency, or a production restore runbook.
  • SQLite WAL is not intended for a database on a network filesystem shared by multiple hosts; see SQLite's WAL limitations.

Python's subprocess guidance likewise recommends passing arguments as a sequence and notes the platform-specific shell risks; WalProof always launches with shell=False. See the official subprocess security considerations.

Development and acceptance

The lockfile is authoritative. Install uv, then run:

uv sync --locked
uv run --no-sync python scripts/check.py

That single release gate runs Ruff, strict mypy, branch coverage at 90% or higher, pip-audit, wheel/sdist builds, an isolated wheel install, the installed demo, the example bundle, and release checksums. CI runs the same command on Ubuntu and Windows with the oldest and newest supported Python versions.

The project deliberately differs from backup engines and post-hoc integrity checkers. See the dated landscape and differentiation review and the architecture decision.

Contributing and security

Read CONTRIBUTING.md before opening a pull request. Report suspected vulnerabilities privately as described in SECURITY.md.

WalProof is released under the MIT License.

About

Prove an SQLite backup command preserves committed WAL data

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages